From 20e4fcce7502d1fe93829833bd0f54a8f8dfe4fe Mon Sep 17 00:00:00 2001 From: ClemensFischer Date: Thu, 13 Nov 2025 13:36:28 +0100 Subject: [PATCH] ITile, ITileSource interfaces --- MBTiles/Shared/MBTileLayer.cs | 2 +- MBTiles/Shared/MBTileSource.cs | 2 +- .../DependencyPropertyHelper.Avalonia.cs | 3 +- MapControl/Avalonia/MapBase.Avalonia.cs | 8 +- MapControl/Avalonia/MapPanel.Avalonia.cs | 10 +-- MapControl/Shared/ImageLoader.cs | 2 + MapControl/Shared/MapBase.MapLayer.cs | 1 + MapControl/Shared/MapBase.cs | 1 + MapControl/Shared/MapImageLayer.cs | 2 + MapControl/Shared/MapItemsControl.cs | 1 + MapControl/Shared/MapScale.cs | 1 + MapControl/Shared/MapTileLayer.cs | 2 +- MapControl/Shared/Tile.cs | 5 +- MapControl/Shared/TileImageLoader.cs | 78 +++++++++++++++---- ...ilePyramidLayer.cs => TilePyramidLayer.cs} | 25 +++--- MapControl/Shared/TileSource.cs | 22 ++---- MapControl/Shared/TypeConverters.cs | 2 +- MapControl/Shared/WmsImageLayer.cs | 1 + MapControl/Shared/WmtsTileLayer.cs | 2 +- 19 files changed, 107 insertions(+), 63 deletions(-) rename MapControl/Shared/{MapTilePyramidLayer.cs => TilePyramidLayer.cs} (86%) diff --git a/MBTiles/Shared/MBTileLayer.cs b/MBTiles/Shared/MBTileLayer.cs index 4353ff71..394d5f93 100644 --- a/MBTiles/Shared/MBTileLayer.cs +++ b/MBTiles/Shared/MBTileLayer.cs @@ -16,7 +16,7 @@ namespace MapControl.MBTiles /// /// MapTileLayer that uses an MBTiles SQLite Database. See https://wiki.openstreetmap.org/wiki/MBTiles. /// - public class MBTileLayer : MapTileLayer + public partial class MBTileLayer : MapTileLayer { private static ILogger logger; private static ILogger Logger => logger ??= ImageLoader.LoggerFactory?.CreateLogger(); diff --git a/MBTiles/Shared/MBTileSource.cs b/MBTiles/Shared/MBTileSource.cs index 3984208c..92af2522 100644 --- a/MBTiles/Shared/MBTileSource.cs +++ b/MBTiles/Shared/MBTileSource.cs @@ -15,7 +15,7 @@ using ImageSource = Avalonia.Media.IImage; namespace MapControl.MBTiles { - public sealed class MBTileSource : TileSource, IDisposable + public sealed partial class MBTileSource : TileSource, IDisposable { private static ILogger logger; private static ILogger Logger => logger ??= ImageLoader.LoggerFactory?.CreateLogger(); diff --git a/MapControl/Avalonia/DependencyPropertyHelper.Avalonia.cs b/MapControl/Avalonia/DependencyPropertyHelper.Avalonia.cs index a286d156..7804d31a 100644 --- a/MapControl/Avalonia/DependencyPropertyHelper.Avalonia.cs +++ b/MapControl/Avalonia/DependencyPropertyHelper.Avalonia.cs @@ -1,4 +1,5 @@ -using Avalonia; +global using DependencyProperty = Avalonia.AvaloniaProperty; +using Avalonia; using Avalonia.Controls; using Avalonia.Data; using System; diff --git a/MapControl/Avalonia/MapBase.Avalonia.cs b/MapControl/Avalonia/MapBase.Avalonia.cs index df48b817..1ee197fb 100644 --- a/MapControl/Avalonia/MapBase.Avalonia.cs +++ b/MapControl/Avalonia/MapBase.Avalonia.cs @@ -1,9 +1,4 @@ -global using DependencyProperty = Avalonia.AvaloniaProperty; -global using FrameworkElement = Avalonia.Controls.Control; -global using Brush = Avalonia.Media.IBrush; -global using ImageSource = Avalonia.Media.IImage; -global using PropertyPath = System.String; -using Avalonia; +using Avalonia; using Avalonia.Animation; using Avalonia.Animation.Easings; using Avalonia.Controls; @@ -13,6 +8,7 @@ using Avalonia.Media; using Avalonia.Styling; using System.Threading; using System.Threading.Tasks; +using Brush = Avalonia.Media.IBrush; namespace MapControl { diff --git a/MapControl/Avalonia/MapPanel.Avalonia.cs b/MapControl/Avalonia/MapPanel.Avalonia.cs index b419beff..0f38ad2c 100644 --- a/MapControl/Avalonia/MapPanel.Avalonia.cs +++ b/MapControl/Avalonia/MapPanel.Avalonia.cs @@ -1,5 +1,5 @@ -using Avalonia; -using Avalonia.Controls; +global using FrameworkElement = Avalonia.Controls.Control; +using Avalonia; using Avalonia.Media; namespace MapControl @@ -20,18 +20,18 @@ namespace MapControl AffectsParentArrange(LocationProperty, BoundingBoxProperty); } - public static MapBase GetParentMap(Control element) + public static MapBase GetParentMap(FrameworkElement element) { return (MapBase)element.GetValue(ParentMapProperty); } - public static void SetRenderTransform(Control element, Transform transform, double originX = 0d, double originY = 0d) + public static void SetRenderTransform(FrameworkElement element, Transform transform, double originX = 0d, double originY = 0d) { element.RenderTransform = transform; element.RenderTransformOrigin = new RelativePoint(originX, originY, RelativeUnit.Relative); } - private static void SetVisible(Control element, bool visible) + private static void SetVisible(FrameworkElement element, bool visible) { element.IsVisible = visible; } diff --git a/MapControl/Shared/ImageLoader.cs b/MapControl/Shared/ImageLoader.cs index dc051064..9522e11a 100644 --- a/MapControl/Shared/ImageLoader.cs +++ b/MapControl/Shared/ImageLoader.cs @@ -9,6 +9,8 @@ using System.Windows.Media; using Windows.UI.Xaml.Media; #elif WINUI using Microsoft.UI.Xaml.Media; +#elif AVALONIA +using ImageSource = Avalonia.Media.IImage; #endif namespace MapControl diff --git a/MapControl/Shared/MapBase.MapLayer.cs b/MapControl/Shared/MapBase.MapLayer.cs index c336a200..70115306 100644 --- a/MapControl/Shared/MapBase.MapLayer.cs +++ b/MapControl/Shared/MapBase.MapLayer.cs @@ -14,6 +14,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; #elif AVALONIA using Avalonia.Controls; +using Brush = Avalonia.Media.IBrush; #endif namespace MapControl diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs index 9d4daa6e..18925dbc 100644 --- a/MapControl/Shared/MapBase.cs +++ b/MapControl/Shared/MapBase.cs @@ -10,6 +10,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; #elif AVALONIA using Avalonia; +using Brush = Avalonia.Media.IBrush; #endif namespace MapControl diff --git a/MapControl/Shared/MapImageLayer.cs b/MapControl/Shared/MapImageLayer.cs index 2871e598..2705930a 100644 --- a/MapControl/Shared/MapImageLayer.cs +++ b/MapControl/Shared/MapImageLayer.cs @@ -18,6 +18,8 @@ using Microsoft.UI.Xaml.Media; using Avalonia; using Avalonia.Controls; using Avalonia.Media; +using Brush = Avalonia.Media.IBrush; +using ImageSource = Avalonia.Media.IImage; #endif namespace MapControl diff --git a/MapControl/Shared/MapItemsControl.cs b/MapControl/Shared/MapItemsControl.cs index d4842afe..d7e73ab5 100644 --- a/MapControl/Shared/MapItemsControl.cs +++ b/MapControl/Shared/MapItemsControl.cs @@ -15,6 +15,7 @@ using Microsoft.UI.Xaml.Data; using Avalonia; using Avalonia.Controls; using Avalonia.Data; +using PropertyPath = System.String; #endif namespace MapControl diff --git a/MapControl/Shared/MapScale.cs b/MapControl/Shared/MapScale.cs index 6ffac495..a5e3dcf0 100644 --- a/MapControl/Shared/MapScale.cs +++ b/MapControl/Shared/MapScale.cs @@ -27,6 +27,7 @@ using Avalonia.Controls.Shapes; using Avalonia.Data; using Avalonia.Layout; using PointCollection = System.Collections.Generic.List; +using PropertyPath = System.String; #endif namespace MapControl diff --git a/MapControl/Shared/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs index 705fd4f2..f2986741 100644 --- a/MapControl/Shared/MapTileLayer.cs +++ b/MapControl/Shared/MapTileLayer.cs @@ -22,7 +22,7 @@ namespace MapControl /// /// Displays a standard Web Mercator map tile pyramid, e.g. a OpenStreetMap tiles. /// - public partial class MapTileLayer : MapTilePyramidLayer + public partial class MapTileLayer : TilePyramidLayer { public static readonly DependencyProperty MinZoomLevelProperty = DependencyPropertyHelper.Register(nameof(MinZoomLevel), 0); diff --git a/MapControl/Shared/Tile.cs b/MapControl/Shared/Tile.cs index fdabcef1..411b4ee0 100644 --- a/MapControl/Shared/Tile.cs +++ b/MapControl/Shared/Tile.cs @@ -14,15 +14,14 @@ using Avalonia.Media; namespace MapControl { - public partial class Tile(int zoomLevel, int x, int y, int columnCount) + public partial class Tile(int zoomLevel, int x, int y, int columnCount) : ITile { public int ZoomLevel { get; } = zoomLevel; public int X { get; } = x; public int Y { get; } = y; public int Column { get; } = ((x % columnCount) + columnCount) % columnCount; public int Row => Y; - - public Image Image { get; } = new Image { Stretch = Stretch.Fill }; public bool IsPending { get; set; } = true; + public Image Image { get; } = new Image { Stretch = Stretch.Fill }; } } diff --git a/MapControl/Shared/TileImageLoader.cs b/MapControl/Shared/TileImageLoader.cs index 1525e1fc..c2559da4 100644 --- a/MapControl/Shared/TileImageLoader.cs +++ b/MapControl/Shared/TileImageLoader.cs @@ -7,19 +7,71 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +#if WPF +using System.Windows.Media; +#elif UWP +using Windows.UI.Xaml.Media; +#elif WINUI +using Microsoft.UI.Xaml.Media; +#elif AVALONIA +using ImageSource = Avalonia.Media.IImage; +#endif namespace MapControl { - /// - /// Loads and optionally caches map tile images for a MapTileLayer. - /// + public interface ITile + { + int ZoomLevel { get; } + int Column { get; } + int Row { get; } + bool IsPending { get; set; } + + /// + /// Runs a tile image download Task and marshals the result to the UI thread. + /// + Task LoadImageAsync(Func> loadImageFunc); + } + + public interface ITileSource + { + /// + /// Indicates whether tile images from this source should be cached. + /// + bool Cacheable { get; } + + /// + /// Gets the image Uri for the specified zoom level and tile indices. + /// + Uri GetUri(int zoomLevel, int column, int row); + + /// + /// Loads a tile image whithout caching. + /// + Task LoadImageAsync(int zoomLevel, int column, int row); + + /// + /// Loads a cacheable tile image from an encoded frame buffer. + /// + Task LoadImageAsync(byte[] buffer); + } + public interface ITileImageLoader { - void BeginLoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress); + /// + /// Loads all pending tiles from the tiles collection. + /// Tile image caching is enabled when tileSource.UriFormat starts with "http" and cacheName is a non-empty string. + /// + void BeginLoadTiles(IEnumerable tiles, ITileSource tileSource, string cacheName, IProgress progress); + /// + /// Cancels a potentially ongoing tile loading task. + /// void CancelLoadTiles(); } + /// + /// Loads and optionally caches map tile images for a MapTilePyramidLayer. + /// public class TileImageLoader : ITileImageLoader { private static ILogger logger; @@ -63,17 +115,13 @@ namespace MapControl /// public static int MaxLoadTasks { get; set; } = 4; - private readonly Queue tileQueue = new(); + private readonly Queue tileQueue = new(); private int tileCount; private int taskCount; - /// - /// Loads all pending tiles from the tiles collection. Tile image caching is enabled when the Cache - /// property is not null and tileSource.UriFormat starts with "http" and cacheName is a non-empty string. - /// - public void BeginLoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress) + public void BeginLoadTiles(IEnumerable tiles, ITileSource tileSource, string cacheName, IProgress progress) { - if (Cache == null || tileSource.UriTemplate == null || !tileSource.UriTemplate.StartsWith("http")) + if (Cache == null || !tileSource.Cacheable) { cacheName = null; // disable caching } @@ -110,9 +158,9 @@ namespace MapControl } } - private async Task LoadTilesFromQueue(TileSource tileSource, string cacheName, IProgress progress) + private async Task LoadTilesFromQueue(ITileSource tileSource, string cacheName, IProgress progress) { - bool TryDequeueTile(out Tile tile) + bool TryDequeueTile(out ITile tile) { lock (tileQueue) { @@ -130,7 +178,7 @@ namespace MapControl return false; } - while (TryDequeueTile(out Tile tile)) + while (TryDequeueTile(out ITile tile)) { tile.IsPending = false; @@ -170,7 +218,7 @@ namespace MapControl } } - private static async Task LoadCachedBuffer(Tile tile, Uri uri, string cacheName) + private static async Task LoadCachedBuffer(ITile tile, Uri uri, string cacheName) { var extension = Path.GetExtension(uri.LocalPath).ToLower(); diff --git a/MapControl/Shared/MapTilePyramidLayer.cs b/MapControl/Shared/TilePyramidLayer.cs similarity index 86% rename from MapControl/Shared/MapTilePyramidLayer.cs rename to MapControl/Shared/TilePyramidLayer.cs index a63a37f1..f8216a80 100644 --- a/MapControl/Shared/MapTilePyramidLayer.cs +++ b/MapControl/Shared/TilePyramidLayer.cs @@ -20,47 +20,48 @@ using Microsoft.UI.Xaml.Media; #elif AVALONIA using Avalonia.Controls; using Avalonia.Media; +using Brush = Avalonia.Media.IBrush; #endif namespace MapControl { - public abstract class MapTilePyramidLayer : Panel, IMapLayer + public abstract class TilePyramidLayer : Panel, IMapLayer { public static readonly DependencyProperty TileSourceProperty = - DependencyPropertyHelper.Register(nameof(TileSource), null, + DependencyPropertyHelper.Register(nameof(TileSource), null, (layer, oldValue, newValue) => layer.UpdateTiles(true)); public static readonly DependencyProperty SourceNameProperty = - DependencyPropertyHelper.Register(nameof(SourceName)); + DependencyPropertyHelper.Register(nameof(SourceName)); public static readonly DependencyProperty DescriptionProperty = - DependencyPropertyHelper.Register(nameof(Description)); + DependencyPropertyHelper.Register(nameof(Description)); public static readonly DependencyProperty MaxBackgroundLevelsProperty = - DependencyPropertyHelper.Register(nameof(MaxBackgroundLevels), 5); + DependencyPropertyHelper.Register(nameof(MaxBackgroundLevels), 5); public static readonly DependencyProperty UpdateIntervalProperty = - DependencyPropertyHelper.Register(nameof(UpdateInterval), TimeSpan.FromSeconds(0.2), + DependencyPropertyHelper.Register(nameof(UpdateInterval), TimeSpan.FromSeconds(0.2), (layer, oldValue, newValue) => layer.updateTimer.Interval = newValue); public static readonly DependencyProperty UpdateWhileViewportChangingProperty = - DependencyPropertyHelper.Register(nameof(UpdateWhileViewportChanging)); + DependencyPropertyHelper.Register(nameof(UpdateWhileViewportChanging)); public static readonly DependencyProperty MapBackgroundProperty = - DependencyPropertyHelper.Register(nameof(MapBackground)); + DependencyPropertyHelper.Register(nameof(MapBackground)); public static readonly DependencyProperty MapForegroundProperty = - DependencyPropertyHelper.Register(nameof(MapForeground)); + DependencyPropertyHelper.Register(nameof(MapForeground)); public static readonly DependencyProperty LoadingProgressProperty = - DependencyPropertyHelper.Register(nameof(LoadingProgress), 1d); + DependencyPropertyHelper.Register(nameof(LoadingProgress), 1d); private readonly Progress loadingProgress; private readonly UpdateTimer updateTimer; private ITileImageLoader tileImageLoader; private MapBase parentMap; - protected MapTilePyramidLayer() + protected TilePyramidLayer() { IsHitTestVisible = false; @@ -191,7 +192,7 @@ namespace MapControl protected bool IsBaseMapLayer => parentMap != null && parentMap.Children.Count > 0 && parentMap.Children[0] == this; - protected void BeginLoadTiles(IEnumerable tiles, string cacheName) + protected void BeginLoadTiles(IEnumerable tiles, string cacheName) { if (TileSource != null && tiles != null && tiles.Any(tile => tile.IsPending)) { diff --git a/MapControl/Shared/TileSource.cs b/MapControl/Shared/TileSource.cs index cc2b7844..4c73c62f 100644 --- a/MapControl/Shared/TileSource.cs +++ b/MapControl/Shared/TileSource.cs @@ -7,6 +7,8 @@ using System.Windows.Media; using Windows.UI.Xaml.Media; #elif WINUI using Microsoft.UI.Xaml.Media; +#elif AVALONIA +using ImageSource = Avalonia.Media.IImage; #endif namespace MapControl @@ -19,7 +21,7 @@ namespace MapControl #else [System.ComponentModel.TypeConverter(typeof(TileSourceConverter))] #endif - public class TileSource + public class TileSource : ITileSource { private string uriTemplate; @@ -35,19 +37,15 @@ namespace MapControl if (uriTemplate != null && uriTemplate.Contains("{s}") && Subdomains == null) { - Subdomains = new string[] { "a", "b", "c" }; // default OpenStreetMap subdomains + Subdomains = ["a", "b", "c"]; // default OpenStreetMap subdomains } } } - /// - /// Gets or sets an array of request subdomain names that are replaced for the {s} format specifier. - /// public string[] Subdomains { get; set; } - /// - /// Gets the image Uri for the specified tile indices and zoom level. - /// + public bool Cacheable => UriTemplate != null && UriTemplate.StartsWith("http"); + public virtual Uri GetUri(int zoomLevel, int column, int row) { Uri uri = null; @@ -71,10 +69,6 @@ namespace MapControl return uri; } - /// - /// Loads a tile ImageSource asynchronously from GetUri(zoomLevel, column, row). - /// This method is called by TileImageLoader when caching is disabled. - /// public virtual Task LoadImageAsync(int zoomLevel, int column, int row) { var uri = GetUri(zoomLevel, column, row); @@ -82,10 +76,6 @@ namespace MapControl return uri != null ? ImageLoader.LoadImageAsync(uri) : Task.FromResult((ImageSource)null); } - /// - /// Loads a tile ImageSource asynchronously from an encoded frame buffer in a byte array. - /// This method is called by TileImageLoader when caching is enabled. - /// public virtual Task LoadImageAsync(byte[] buffer) { return ImageLoader.LoadImageAsync(buffer); diff --git a/MapControl/Shared/TypeConverters.cs b/MapControl/Shared/TypeConverters.cs index 010d241f..52e775d7 100644 --- a/MapControl/Shared/TypeConverters.cs +++ b/MapControl/Shared/TypeConverters.cs @@ -110,7 +110,7 @@ namespace MapControl } } - public class MapProjectionConverter : TypeConverter, IValueConverter + public partial class MapProjectionConverter : TypeConverter, IValueConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { diff --git a/MapControl/Shared/WmsImageLayer.cs b/MapControl/Shared/WmsImageLayer.cs index 36010aac..35484f4c 100644 --- a/MapControl/Shared/WmsImageLayer.cs +++ b/MapControl/Shared/WmsImageLayer.cs @@ -17,6 +17,7 @@ using Microsoft.UI.Xaml.Media; #elif AVALONIA using Avalonia; using Avalonia.Interactivity; +using ImageSource = Avalonia.Media.IImage; #endif namespace MapControl diff --git a/MapControl/Shared/WmtsTileLayer.cs b/MapControl/Shared/WmtsTileLayer.cs index 1550a701..8093af0d 100644 --- a/MapControl/Shared/WmtsTileLayer.cs +++ b/MapControl/Shared/WmtsTileLayer.cs @@ -20,7 +20,7 @@ namespace MapControl /// /// Displays map tiles from a Web Map Tile Service (WMTS). /// - public partial class WmtsTileLayer : MapTilePyramidLayer + public partial class WmtsTileLayer : TilePyramidLayer { private static ILogger logger; private static ILogger Logger => logger ??= ImageLoader.LoggerFactory?.CreateLogger(typeof(WmtsTileLayer));