// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control // © 2021 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) using System; using System.Collections.Generic; using System.Threading.Tasks; #if WINUI using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; #elif WINDOWS_UWP using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; #else using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Threading; #endif namespace MapControl { public interface ITileImageLoader { TileSource TileSource { get; } Task LoadTiles(IEnumerable tiles, TileSource tileSource, string cacheName); } public abstract class MapTileLayerBase : Panel, IMapLayer { public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register( nameof(TileSource), typeof(TileSource), typeof(MapTileLayerBase), new PropertyMetadata(null, async (o, e) => await ((MapTileLayerBase)o).Update())); public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register( nameof(SourceName), typeof(string), typeof(MapTileLayerBase), new PropertyMetadata(null)); public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( nameof(Description), typeof(string), typeof(MapTileLayerBase), new PropertyMetadata(null)); public static readonly DependencyProperty MaxBackgroundLevelsProperty = DependencyProperty.Register( nameof(MaxBackgroundLevels), typeof(int), typeof(MapTileLayerBase), new PropertyMetadata(8)); public static readonly DependencyProperty UpdateIntervalProperty = DependencyProperty.Register( nameof(UpdateInterval), typeof(TimeSpan), typeof(MapTileLayerBase), new PropertyMetadata(TimeSpan.FromSeconds(0.2), (o, e) => ((MapTileLayerBase)o).updateTimer.Interval = (TimeSpan)e.NewValue)); public static readonly DependencyProperty UpdateWhileViewportChangingProperty = DependencyProperty.Register( nameof(UpdateWhileViewportChanging), typeof(bool), typeof(MapTileLayerBase), new PropertyMetadata(false)); public static readonly DependencyProperty MapBackgroundProperty = DependencyProperty.Register( nameof(MapBackground), typeof(Brush), typeof(MapTileLayerBase), new PropertyMetadata(null)); public static readonly DependencyProperty MapForegroundProperty = DependencyProperty.Register( nameof(MapForeground), typeof(Brush), typeof(MapTileLayerBase), new PropertyMetadata(null)); #if WINUI private readonly DispatcherQueueTimer updateTimer; #else private readonly DispatcherTimer updateTimer = new DispatcherTimer(); #endif private MapBase parentMap; protected MapTileLayerBase(ITileImageLoader tileImageLoader) { RenderTransform = new MatrixTransform(); TileImageLoader = tileImageLoader; #if WINUI updateTimer = DispatcherQueue.CreateTimer(); #endif updateTimer.Interval = UpdateInterval; updateTimer.Tick += async (s, e) => await Update(); #if WINUI || WINDOWS_UWP MapPanel.InitMapElement(this); #endif } public ITileImageLoader TileImageLoader { get; } /// /// Provides map tile URIs or images. /// public TileSource TileSource { get { return (TileSource)GetValue(TileSourceProperty); } set { SetValue(TileSourceProperty, value); } } /// /// Name of the TileSource. Used as component of a tile cache key. /// public string SourceName { get { return (string)GetValue(SourceNameProperty); } set { SetValue(SourceNameProperty, value); } } /// /// Description of the tile layer. Used to display copyright information on top of the map. /// public string Description { get { return (string)GetValue(DescriptionProperty); } set { SetValue(DescriptionProperty, value); } } /// /// Maximum number of background tile levels. Default value is 8. /// Only effective in a MapTileLayer or WmtsTileLayer that is the MapLayer of its ParentMap. /// public int MaxBackgroundLevels { get { return (int)GetValue(MaxBackgroundLevelsProperty); } set { SetValue(MaxBackgroundLevelsProperty, value); } } /// /// Minimum time interval between tile updates. /// public TimeSpan UpdateInterval { get { return (TimeSpan)GetValue(UpdateIntervalProperty); } set { SetValue(UpdateIntervalProperty, value); } } /// /// Controls if tiles are updated while the viewport is still changing. /// public bool UpdateWhileViewportChanging { get { return (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 { return (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 { return (Brush)GetValue(MapForegroundProperty); } set { SetValue(MapForegroundProperty, value); } } public MapBase ParentMap { get { return parentMap; } set { if (parentMap != null) { parentMap.ViewportChanged -= OnViewportChanged; } parentMap = value; if (parentMap != null) { parentMap.ViewportChanged += OnViewportChanged; } updateTimer.Start(); } } private async void OnViewportChanged(object sender, ViewportChangedEventArgs e) { if (Children.Count == 0 || e.ProjectionChanged || Math.Abs(e.LongitudeOffset) > 180d) { await Update(); // update immediately when projection has changed or center has moved across 180° longitude } else { SetRenderTransform(); if (!UpdateWhileViewportChanging) { updateTimer.Stop(); // restart } updateTimer.Start(); } } private Task Update() { updateTimer.Stop(); return UpdateTileLayer(); } protected abstract Task UpdateTileLayer(); protected abstract void SetRenderTransform(); } }