// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control // Copyright © Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) using System; using System.Collections.Generic; using System.Threading.Tasks; #if WPF using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Threading; #elif UWP using Windows.UI.Composition; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Hosting; using Windows.UI.Xaml.Media; #elif WINUI using Microsoft.UI.Composition; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Hosting; using Microsoft.UI.Xaml.Media; using DispatcherTimer = Microsoft.UI.Dispatching.DispatcherQueueTimer; #endif namespace MapControl { public abstract class MapTileLayerBase : Panel, IMapLayer { public static readonly DependencyProperty TileSourceProperty = DependencyPropertyHelper.Register(nameof(TileSource), null, async (layer, oldValue, newValue) => await layer.Update(true)); public static readonly DependencyProperty SourceNameProperty = DependencyPropertyHelper.Register(nameof(SourceName)); public static readonly DependencyProperty DescriptionProperty = DependencyPropertyHelper.Register(nameof(Description)); public static readonly DependencyProperty MaxBackgroundLevelsProperty = DependencyPropertyHelper.Register(nameof(MaxBackgroundLevels), 5); public static readonly DependencyProperty UpdateIntervalProperty = DependencyPropertyHelper.Register(nameof(UpdateInterval), TimeSpan.FromSeconds(0.2)); public static readonly DependencyProperty UpdateWhileViewportChangingProperty = DependencyPropertyHelper.Register(nameof(UpdateWhileViewportChanging)); public static readonly DependencyProperty MapBackgroundProperty = DependencyPropertyHelper.Register(nameof(MapBackground)); public static readonly DependencyProperty MapForegroundProperty = DependencyPropertyHelper.Register(nameof(MapForeground)); public static readonly DependencyProperty LoadingProgressProperty = DependencyPropertyHelper.Register(nameof(LoadingProgress), 1d); private readonly Progress loadingProgress; private readonly DispatcherTimer updateTimer; private ITileImageLoader tileImageLoader; private MapBase parentMap; protected MapTileLayerBase() { IsHitTestVisible = false; loadingProgress = new Progress(p => SetValue(LoadingProgressProperty, p)); updateTimer = this.CreateTimer(UpdateInterval); updateTimer.Tick += async (s, e) => await Update(false); MapPanel.SetRenderTransform(this, new MatrixTransform()); #if WPF RenderOptions.SetEdgeMode(this, EdgeMode.Aliased); #elif UWP || WINUI ElementCompositionPreview.GetElementVisual(this).BorderMode = CompositionBorderMode.Hard; MapPanel.InitMapElement(this); #endif } public ITileImageLoader TileImageLoader { get => tileImageLoader ?? (tileImageLoader = new TileImageLoader()); set => tileImageLoader = value; } /// /// Provides map tile URIs or images. /// public TileSource TileSource { get => (TileSource)GetValue(TileSourceProperty); set => SetValue(TileSourceProperty, value); } /// /// Name of the tile source that is used as component of a tile cache key. /// Tile images are not cached when SourceName is null or empty. /// public string SourceName { get => (string)GetValue(SourceNameProperty); set => SetValue(SourceNameProperty, value); } /// /// Description of the layer. Used to display copyright information on top of the map. /// public string Description { get => (string)GetValue(DescriptionProperty); set => SetValue(DescriptionProperty, value); } /// /// Maximum number of background tile levels. Default value is 5. /// Only effective in a MapTileLayer or WmtsTileLayer that is the MapLayer of its ParentMap. /// public int MaxBackgroundLevels { get => (int)GetValue(MaxBackgroundLevelsProperty); set => SetValue(MaxBackgroundLevelsProperty, value); } /// /// Minimum time interval between tile updates. /// public TimeSpan UpdateInterval { get => (TimeSpan)GetValue(UpdateIntervalProperty); set => SetValue(UpdateIntervalProperty, value); } /// /// Controls if tiles are updated while the viewport is still changing. /// public bool UpdateWhileViewportChanging { get => (bool)GetValue(UpdateWhileViewportChangingProperty); set => SetValue(UpdateWhileViewportChangingProperty, value); } /// /// Optional background brush. Sets MapBase.Background if not null and this layer is the base map layer. /// public Brush MapBackground { get => (Brush)GetValue(MapBackgroundProperty); set => SetValue(MapBackgroundProperty, value); } /// /// Optional foreground brush. Sets MapBase.Foreground if not null and this layer is the base map layer. /// public Brush MapForeground { get => (Brush)GetValue(MapForegroundProperty); set => SetValue(MapForegroundProperty, value); } /// /// Gets the progress of the TileImageLoader as a double value between 0 and 1. /// public double LoadingProgress => (double)GetValue(LoadingProgressProperty); /// /// Implements IMapElement.ParentMap. /// public MapBase ParentMap { get => parentMap; set { if (parentMap != null) { parentMap.ViewportChanged -= OnViewportChanged; } parentMap = value; if (parentMap != null) { parentMap.ViewportChanged += OnViewportChanged; } updateTimer.Run(); } } protected bool IsBaseMapLayer => parentMap != null && parentMap.Children.Count > 0 && parentMap.Children[0] == this; protected abstract void SetRenderTransform(); protected abstract Task UpdateTileLayer(bool tileSourceChanged); protected Task LoadTiles(IEnumerable tiles, string cacheName) { return TileImageLoader.LoadTilesAsync(tiles, TileSource, cacheName, loadingProgress); } private Task Update(bool tileSourceChanged) { updateTimer.Stop(); return UpdateTileLayer(tileSourceChanged); } private async void OnViewportChanged(object sender, ViewportChangedEventArgs e) { if (e.TransformCenterChanged || e.ProjectionChanged || Children.Count == 0) { await Update(false); // update immediately } else { SetRenderTransform(); updateTimer.Run(!UpdateWhileViewportChanging); } } } }