diff --git a/MBTiles/Shared/MBTileLayer.cs b/MBTiles/Shared/MBTileLayer.cs index 394d5f93..4353ff71 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 partial class MBTileLayer : MapTileLayer + public 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 92af2522..38c210a0 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 partial class MBTileSource : TileSource, IDisposable + public sealed class MBTileSource : TileSource, IDisposable { private static ILogger logger; private static ILogger Logger => logger ??= ImageLoader.LoggerFactory?.CreateLogger(); @@ -24,6 +24,8 @@ namespace MapControl.MBTiles public IDictionary Metadata { get; } = new Dictionary(); + public override bool Cacheable => false; + public async Task OpenAsync(string file) { Close(); @@ -85,5 +87,10 @@ namespace MapControl.MBTiles return image; } + + public override Uri GetUri(int zoomLevel, int column, int row) + { + throw new NotSupportedException(); + } } } diff --git a/MapControl/Avalonia/Tile.Avalonia.cs b/MapControl/Avalonia/ImageTile.Avalonia.cs similarity index 82% rename from MapControl/Avalonia/Tile.Avalonia.cs rename to MapControl/Avalonia/ImageTile.Avalonia.cs index a7c5f700..7e436993 100644 --- a/MapControl/Avalonia/Tile.Avalonia.cs +++ b/MapControl/Avalonia/ImageTile.Avalonia.cs @@ -1,5 +1,6 @@ using Avalonia; using Avalonia.Animation; +using Avalonia.Controls; using Avalonia.Media; using Avalonia.Styling; using Avalonia.Threading; @@ -8,9 +9,12 @@ using System.Threading.Tasks; namespace MapControl { - public partial class Tile + public class ImageTile(int zoomLevel, int x, int y, int columnCount) + : Tile(zoomLevel, x, y, columnCount) { - public async Task LoadImageAsync(Func> loadImageFunc) + public Image Image { get; } = new Image { Stretch = Stretch.Fill }; + + public override async Task LoadImageAsync(Func> loadImageFunc) { var image = await loadImageFunc().ConfigureAwait(false); diff --git a/MapControl/Shared/BoundingBoxTileSource.cs b/MapControl/Shared/BoundingBoxTileSource.cs index 51ee3ed6..588c862f 100644 --- a/MapControl/Shared/BoundingBoxTileSource.cs +++ b/MapControl/Shared/BoundingBoxTileSource.cs @@ -4,7 +4,7 @@ using System.Text; namespace MapControl { - public class BoundingBoxTileSource : TileSource + public class BoundingBoxTileSource : UriTileSource { public override Uri GetUri(int zoomLevel, int column, int row) { diff --git a/MapControl/Shared/TileCollection.cs b/MapControl/Shared/ImageTileList.cs similarity index 72% rename from MapControl/Shared/TileCollection.cs rename to MapControl/Shared/ImageTileList.cs index 58257e99..bf058f18 100644 --- a/MapControl/Shared/TileCollection.cs +++ b/MapControl/Shared/ImageTileList.cs @@ -3,12 +3,12 @@ using System.Linq; namespace MapControl { - public partial class TileCollection : List + public partial class ImageTileList : List { /// - /// Adds existing Tiles from the source collection or newly created Tiles to fill the specified tile matrix. + /// Adds existing ImageTile from the source collection or newly created ImageTile to fill the specified tile matrix. /// - public void FillMatrix(TileCollection source, int zoomLevel, int xMin, int yMin, int xMax, int yMax, int columnCount) + public void FillMatrix(ImageTileList source, int zoomLevel, int xMin, int yMin, int xMax, int yMax, int columnCount) { for (var y = yMin; y <= yMax; y++) { @@ -18,7 +18,7 @@ namespace MapControl if (tile == null) { - tile = new Tile(zoomLevel, x, y, columnCount); + tile = new ImageTile(zoomLevel, x, y, columnCount); var equivalentTile = source.FirstOrDefault( t => t.Image.Source != null && t.ZoomLevel == tile.ZoomLevel && t.Column == tile.Column && t.Row == tile.Row); diff --git a/MapControl/Shared/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs index f2986741..4ec4391f 100644 --- a/MapControl/Shared/MapTileLayer.cs +++ b/MapControl/Shared/MapTileLayer.cs @@ -43,7 +43,7 @@ namespace MapControl /// public static MapTileLayer OpenStreetMapTileLayer => new() { - TileSource = new TileSource { UriTemplate = "https://tile.openstreetmap.org/{z}/{x}/{y}.png" }, + TileSource = TileSource.Parse("https://tile.openstreetmap.org/{z}/{x}/{y}.png"), SourceName = "OpenStreetMap", Description = "© [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)" }; @@ -52,7 +52,7 @@ namespace MapControl public TileMatrix TileMatrix { get; private set; } - public TileCollection Tiles { get; private set; } = []; + public ImageTileList Tiles { get; private set; } = []; /// /// Minimum zoom level supported by the MapTileLayer. Default value is 0. @@ -178,7 +178,7 @@ namespace MapControl private void UpdateTiles(bool reset) { - var tiles = new TileCollection(); + var tiles = new ImageTileList(); if (TileSource != null && TileMatrix != null) { diff --git a/MapControl/Shared/Tile.cs b/MapControl/Shared/Tile.cs index 411b4ee0..b4b96ef6 100644 --- a/MapControl/Shared/Tile.cs +++ b/MapControl/Shared/Tile.cs @@ -1,20 +1,18 @@ -#if WPF -using System.Windows.Controls; +using System; +using System.Threading.Tasks; +#if WPF using System.Windows.Media; #elif UWP -using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; #elif WINUI -using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; #elif AVALONIA -using Avalonia.Controls; -using Avalonia.Media; +using ImageSource = Avalonia.Media.IImage; #endif namespace MapControl { - public partial class Tile(int zoomLevel, int x, int y, int columnCount) : ITile + public abstract class Tile(int zoomLevel, int x, int y, int columnCount) { public int ZoomLevel { get; } = zoomLevel; public int X { get; } = x; @@ -22,6 +20,10 @@ namespace MapControl public int Column { get; } = ((x % columnCount) + columnCount) % columnCount; public int Row => Y; public bool IsPending { get; set; } = true; - public Image Image { get; } = new Image { Stretch = Stretch.Fill }; + + /// + /// Runs a tile image download Task and marshals the result to the UI thread. + /// + public abstract Task LoadImageAsync(Func> loadImageFunc); } } diff --git a/MapControl/Shared/TileImageLoader.cs b/MapControl/Shared/TileImageLoader.cs index c2559da4..d83162e3 100644 --- a/MapControl/Shared/TileImageLoader.cs +++ b/MapControl/Shared/TileImageLoader.cs @@ -7,61 +7,16 @@ 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 { - 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 { /// /// 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); + void BeginLoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress); /// /// Cancels a potentially ongoing tile loading task. @@ -115,11 +70,11 @@ 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; - public void BeginLoadTiles(IEnumerable tiles, ITileSource tileSource, string cacheName, IProgress progress) + public void BeginLoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress) { if (Cache == null || !tileSource.Cacheable) { @@ -158,9 +113,9 @@ namespace MapControl } } - private async Task LoadTilesFromQueue(ITileSource tileSource, string cacheName, IProgress progress) + private async Task LoadTilesFromQueue(TileSource tileSource, string cacheName, IProgress progress) { - bool TryDequeueTile(out ITile tile) + bool TryDequeueTile(out Tile tile) { lock (tileQueue) { @@ -178,7 +133,7 @@ namespace MapControl return false; } - while (TryDequeueTile(out ITile tile)) + while (TryDequeueTile(out Tile tile)) { tile.IsPending = false; @@ -218,7 +173,7 @@ namespace MapControl } } - private static async Task LoadCachedBuffer(ITile tile, Uri uri, string cacheName) + private static async Task LoadCachedBuffer(Tile tile, Uri uri, string cacheName) { var extension = Path.GetExtension(uri.LocalPath).ToLower(); diff --git a/MapControl/Shared/TilePyramidLayer.cs b/MapControl/Shared/TilePyramidLayer.cs index f8216a80..3db0876e 100644 --- a/MapControl/Shared/TilePyramidLayer.cs +++ b/MapControl/Shared/TilePyramidLayer.cs @@ -192,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 4c73c62f..667fdd32 100644 --- a/MapControl/Shared/TileSource.cs +++ b/MapControl/Shared/TileSource.cs @@ -21,7 +21,41 @@ namespace MapControl #else [System.ComponentModel.TypeConverter(typeof(TileSourceConverter))] #endif - public class TileSource : ITileSource + public abstract class TileSource + { + /// + /// Indicates whether tile images from this source should be cached. + /// + public abstract bool Cacheable { get; } + + /// + /// Gets the image Uri for the specified zoom level and tile indices. + /// + public abstract Uri GetUri(int zoomLevel, int column, int row); + + /// + /// Loads a tile image whithout caching. + /// + public abstract Task LoadImageAsync(int zoomLevel, int column, int row); + + /// + /// Loads a cacheable tile image from an encoded frame buffer. + /// + public virtual Task LoadImageAsync(byte[] buffer) + { + return ImageLoader.LoadImageAsync(buffer); + } + + /// + /// Creates a TileSource instance from an Uri template string. + /// + public static TileSource Parse(string uriTemplate) + { + return new UriTileSource { UriTemplate = uriTemplate }; + } + } + + public class UriTileSource : TileSource { private string uriTemplate; @@ -44,9 +78,9 @@ namespace MapControl public string[] Subdomains { get; set; } - public bool Cacheable => UriTemplate != null && UriTemplate.StartsWith("http"); + public override bool Cacheable => UriTemplate != null && UriTemplate.StartsWith("http"); - public virtual Uri GetUri(int zoomLevel, int column, int row) + public override Uri GetUri(int zoomLevel, int column, int row) { Uri uri = null; @@ -69,33 +103,20 @@ namespace MapControl return uri; } - public virtual Task LoadImageAsync(int zoomLevel, int column, int row) + public override Task LoadImageAsync(int zoomLevel, int column, int row) { var uri = GetUri(zoomLevel, column, row); return uri != null ? ImageLoader.LoadImageAsync(uri) : Task.FromResult((ImageSource)null); } - public virtual Task LoadImageAsync(byte[] buffer) - { - return ImageLoader.LoadImageAsync(buffer); - } - public override string ToString() { return UriTemplate; } - - /// - /// Creates a TileSource instance from an Uri template string. - /// - public static TileSource Parse(string uriTemplate) - { - return new TileSource { UriTemplate = uriTemplate }; - } } - public class TmsTileSource : TileSource + public class TmsTileSource : UriTileSource { public override Uri GetUri(int zoomLevel, int column, int row) { diff --git a/MapControl/Shared/WmtsTileMatrixLayer.cs b/MapControl/Shared/WmtsTileMatrixLayer.cs index bb5d569f..f1566d6c 100644 --- a/MapControl/Shared/WmtsTileMatrixLayer.cs +++ b/MapControl/Shared/WmtsTileMatrixLayer.cs @@ -35,7 +35,7 @@ namespace MapControl public TileMatrix TileMatrix { get; private set; } - public TileCollection Tiles { get; private set; } = []; + public ImageTileList Tiles { get; private set; } = []; public void UpdateRenderTransform(ViewTransform viewTransform) { @@ -85,7 +85,7 @@ namespace MapControl TileMatrix = new TileMatrix(TileMatrix.ZoomLevel, xMin, yMin, xMax, yMax); - var tiles = new TileCollection(); + var tiles = new ImageTileList(); tiles.FillMatrix(Tiles, TileMatrix.ZoomLevel, xMin, yMin, xMax, yMax, WmtsTileMatrix.MatrixWidth); Tiles = tiles; diff --git a/MapControl/Shared/WmtsTileSource.cs b/MapControl/Shared/WmtsTileSource.cs index 0bebd836..7810e507 100644 --- a/MapControl/Shared/WmtsTileSource.cs +++ b/MapControl/Shared/WmtsTileSource.cs @@ -3,7 +3,7 @@ using System.Text; namespace MapControl { - public class WmtsTileSource : TileSource + public class WmtsTileSource : UriTileSource { public WmtsTileMatrixSet TileMatrixSet { get; set; } diff --git a/MapControl/WPF/Tile.WPF.cs b/MapControl/WPF/ImageTile.WPF.cs similarity index 87% rename from MapControl/WPF/Tile.WPF.cs rename to MapControl/WPF/ImageTile.WPF.cs index 5bb491fa..251d01ac 100644 --- a/MapControl/WPF/Tile.WPF.cs +++ b/MapControl/WPF/ImageTile.WPF.cs @@ -8,9 +8,12 @@ using System.Windows.Media.Imaging; namespace MapControl { - public partial class Tile + public class ImageTile(int zoomLevel, int x, int y, int columnCount) + : Tile(zoomLevel, x, y, columnCount) { - public async Task LoadImageAsync(Func> loadImageFunc) + public Image Image { get; } = new Image { Stretch = Stretch.Fill }; + + public override async Task LoadImageAsync(Func> loadImageFunc) { var image = await loadImageFunc().ConfigureAwait(false); diff --git a/MapControl/WinUI/Tile.WinUI.cs b/MapControl/WinUI/ImageTile.WinUI.cs similarity index 89% rename from MapControl/WinUI/Tile.WinUI.cs rename to MapControl/WinUI/ImageTile.WinUI.cs index ffc56d66..fa218e1b 100644 --- a/MapControl/WinUI/Tile.WinUI.cs +++ b/MapControl/WinUI/ImageTile.WinUI.cs @@ -3,12 +3,14 @@ using System.Threading.Tasks; #if UWP using Windows.UI.Core; using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Animation; using Windows.UI.Xaml.Media.Imaging; #else using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Animation; using Microsoft.UI.Xaml.Media.Imaging; @@ -16,9 +18,12 @@ using Microsoft.UI.Xaml.Media.Imaging; namespace MapControl { - public partial class Tile + public class ImageTile(int zoomLevel, int x, int y, int columnCount) + : Tile(zoomLevel, x, y, columnCount) { - public async Task LoadImageAsync(Func> loadImageFunc) + public Image Image { get; } = new Image { Stretch = Stretch.Fill }; + + public override async Task LoadImageAsync(Func> loadImageFunc) { var tcs = new TaskCompletionSource();