Abstract classes Tile, TileSource

This commit is contained in:
ClemensFischer 2025-11-13 15:32:01 +01:00
parent 20e4fcce75
commit cb8fff0dd1
14 changed files with 95 additions and 98 deletions

View file

@ -16,7 +16,7 @@ namespace MapControl.MBTiles
/// <summary>
/// MapTileLayer that uses an MBTiles SQLite Database. See https://wiki.openstreetmap.org/wiki/MBTiles.
/// </summary>
public partial class MBTileLayer : MapTileLayer
public class MBTileLayer : MapTileLayer
{
private static ILogger logger;
private static ILogger Logger => logger ??= ImageLoader.LoggerFactory?.CreateLogger<MBTileLayer>();

View file

@ -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<MBTileSource>();
@ -24,6 +24,8 @@ namespace MapControl.MBTiles
public IDictionary<string, string> Metadata { get; } = new Dictionary<string, string>();
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();
}
}
}

View file

@ -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<Task<IImage>> loadImageFunc)
public Image Image { get; } = new Image { Stretch = Stretch.Fill };
public override async Task LoadImageAsync(Func<Task<IImage>> loadImageFunc)
{
var image = await loadImageFunc().ConfigureAwait(false);

View file

@ -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)
{

View file

@ -3,12 +3,12 @@ using System.Linq;
namespace MapControl
{
public partial class TileCollection : List<Tile>
public partial class ImageTileList : List<ImageTile>
{
/// <summary>
/// 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.
/// </summary>
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);

View file

@ -43,7 +43,7 @@ namespace MapControl
/// </summary>
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; } = [];
/// <summary>
/// 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)
{

View file

@ -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 };
/// <summary>
/// Runs a tile image download Task and marshals the result to the UI thread.
/// </summary>
public abstract Task LoadImageAsync(Func<Task<ImageSource>> loadImageFunc);
}
}

View file

@ -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; }
/// <summary>
/// Runs a tile image download Task and marshals the result to the UI thread.
/// </summary>
Task LoadImageAsync(Func<Task<ImageSource>> loadImageFunc);
}
public interface ITileSource
{
/// <summary>
/// Indicates whether tile images from this source should be cached.
/// </summary>
bool Cacheable { get; }
/// <summary>
/// Gets the image Uri for the specified zoom level and tile indices.
/// </summary>
Uri GetUri(int zoomLevel, int column, int row);
/// <summary>
/// Loads a tile image whithout caching.
/// </summary>
Task<ImageSource> LoadImageAsync(int zoomLevel, int column, int row);
/// <summary>
/// Loads a cacheable tile image from an encoded frame buffer.
/// </summary>
Task<ImageSource> LoadImageAsync(byte[] buffer);
}
public interface ITileImageLoader
{
/// <summary>
/// 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.
/// </summary>
void BeginLoadTiles(IEnumerable<ITile> tiles, ITileSource tileSource, string cacheName, IProgress<double> progress);
void BeginLoadTiles(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress);
/// <summary>
/// Cancels a potentially ongoing tile loading task.
@ -115,11 +70,11 @@ namespace MapControl
/// </summary>
public static int MaxLoadTasks { get; set; } = 4;
private readonly Queue<ITile> tileQueue = new();
private readonly Queue<Tile> tileQueue = new();
private int tileCount;
private int taskCount;
public void BeginLoadTiles(IEnumerable<ITile> tiles, ITileSource tileSource, string cacheName, IProgress<double> progress)
public void BeginLoadTiles(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress)
{
if (Cache == null || !tileSource.Cacheable)
{
@ -158,9 +113,9 @@ namespace MapControl
}
}
private async Task LoadTilesFromQueue(ITileSource tileSource, string cacheName, IProgress<double> progress)
private async Task LoadTilesFromQueue(TileSource tileSource, string cacheName, IProgress<double> 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<byte[]> LoadCachedBuffer(ITile tile, Uri uri, string cacheName)
private static async Task<byte[]> LoadCachedBuffer(Tile tile, Uri uri, string cacheName)
{
var extension = Path.GetExtension(uri.LocalPath).ToLower();

View file

@ -192,7 +192,7 @@ namespace MapControl
protected bool IsBaseMapLayer => parentMap != null && parentMap.Children.Count > 0 && parentMap.Children[0] == this;
protected void BeginLoadTiles(IEnumerable<ITile> tiles, string cacheName)
protected void BeginLoadTiles(IEnumerable<Tile> tiles, string cacheName)
{
if (TileSource != null && tiles != null && tiles.Any(tile => tile.IsPending))
{

View file

@ -21,7 +21,41 @@ namespace MapControl
#else
[System.ComponentModel.TypeConverter(typeof(TileSourceConverter))]
#endif
public class TileSource : ITileSource
public abstract class TileSource
{
/// <summary>
/// Indicates whether tile images from this source should be cached.
/// </summary>
public abstract bool Cacheable { get; }
/// <summary>
/// Gets the image Uri for the specified zoom level and tile indices.
/// </summary>
public abstract Uri GetUri(int zoomLevel, int column, int row);
/// <summary>
/// Loads a tile image whithout caching.
/// </summary>
public abstract Task<ImageSource> LoadImageAsync(int zoomLevel, int column, int row);
/// <summary>
/// Loads a cacheable tile image from an encoded frame buffer.
/// </summary>
public virtual Task<ImageSource> LoadImageAsync(byte[] buffer)
{
return ImageLoader.LoadImageAsync(buffer);
}
/// <summary>
/// Creates a TileSource instance from an Uri template string.
/// </summary>
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<ImageSource> LoadImageAsync(int zoomLevel, int column, int row)
public override Task<ImageSource> 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<ImageSource> LoadImageAsync(byte[] buffer)
{
return ImageLoader.LoadImageAsync(buffer);
}
public override string ToString()
{
return UriTemplate;
}
/// <summary>
/// Creates a TileSource instance from an Uri template string.
/// </summary>
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)
{

View file

@ -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;

View file

@ -3,7 +3,7 @@ using System.Text;
namespace MapControl
{
public class WmtsTileSource : TileSource
public class WmtsTileSource : UriTileSource
{
public WmtsTileMatrixSet TileMatrixSet { get; set; }

View file

@ -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<Task<ImageSource>> loadImageFunc)
public Image Image { get; } = new Image { Stretch = Stretch.Fill };
public override async Task LoadImageAsync(Func<Task<ImageSource>> loadImageFunc)
{
var image = await loadImageFunc().ConfigureAwait(false);

View file

@ -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<Task<ImageSource>> loadImageFunc)
public Image Image { get; } = new Image { Stretch = Stretch.Fill };
public override async Task LoadImageAsync(Func<Task<ImageSource>> loadImageFunc)
{
var tcs = new TaskCompletionSource<object>();