2025-02-27 18:46:32 +01:00
|
|
|
|
using System;
|
2025-09-19 18:49:12 +02:00
|
|
|
|
using System.Collections.Generic;
|
2018-04-30 23:13:50 +02:00
|
|
|
|
using System.Linq;
|
2017-10-27 17:15:18 +02:00
|
|
|
|
using System.Threading.Tasks;
|
2024-05-22 11:25:32 +02:00
|
|
|
|
#if WPF
|
|
|
|
|
|
using System.Windows;
|
|
|
|
|
|
using System.Windows.Controls;
|
|
|
|
|
|
using System.Windows.Media;
|
|
|
|
|
|
#elif UWP
|
|
|
|
|
|
using Windows.UI.Xaml;
|
|
|
|
|
|
using Windows.UI.Xaml.Controls;
|
|
|
|
|
|
using Windows.UI.Xaml.Media;
|
2024-05-21 17:39:03 +02:00
|
|
|
|
#elif WINUI
|
2021-06-14 21:41:37 +02:00
|
|
|
|
using Microsoft.UI.Xaml;
|
|
|
|
|
|
using Microsoft.UI.Xaml.Controls;
|
|
|
|
|
|
using Microsoft.UI.Xaml.Media;
|
2025-08-19 19:43:02 +02:00
|
|
|
|
#elif AVALONIA
|
|
|
|
|
|
using Avalonia;
|
|
|
|
|
|
using Avalonia.Controls;
|
|
|
|
|
|
using Avalonia.Media;
|
2014-04-05 14:53:58 +02:00
|
|
|
|
#endif
|
2013-05-07 18:12:25 +02:00
|
|
|
|
|
|
|
|
|
|
namespace MapControl
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
2020-04-19 10:53:25 +02:00
|
|
|
|
/// Displays a single map image, e.g. from a Web Map Service (WMS).
|
|
|
|
|
|
/// The image must be provided by the abstract GetImageAsync() method.
|
2013-05-07 18:12:25 +02:00
|
|
|
|
/// </summary>
|
2025-01-05 09:22:50 +01:00
|
|
|
|
public abstract partial class MapImageLayer : MapPanel, IMapLayer
|
2013-05-07 18:12:25 +02:00
|
|
|
|
{
|
2024-05-21 17:39:03 +02:00
|
|
|
|
public static readonly DependencyProperty DescriptionProperty =
|
|
|
|
|
|
DependencyPropertyHelper.Register<MapImageLayer, string>(nameof(Description));
|
2020-03-28 21:53:38 +01:00
|
|
|
|
|
2024-05-21 17:39:03 +02:00
|
|
|
|
public static readonly DependencyProperty RelativeImageSizeProperty =
|
|
|
|
|
|
DependencyPropertyHelper.Register<MapImageLayer, double>(nameof(RelativeImageSize), 1d);
|
2013-05-15 15:58:07 +02:00
|
|
|
|
|
2024-05-21 17:39:03 +02:00
|
|
|
|
public static readonly DependencyProperty UpdateIntervalProperty =
|
|
|
|
|
|
DependencyPropertyHelper.Register<MapImageLayer, TimeSpan>(nameof(UpdateInterval), TimeSpan.FromSeconds(0.2),
|
2024-05-23 18:22:52 +02:00
|
|
|
|
(layer, oldValue, newValue) => layer.updateTimer.Interval = newValue);
|
2017-06-25 23:05:48 +02:00
|
|
|
|
|
2024-05-21 17:39:03 +02:00
|
|
|
|
public static readonly DependencyProperty UpdateWhileViewportChangingProperty =
|
|
|
|
|
|
DependencyPropertyHelper.Register<MapImageLayer, bool>(nameof(UpdateWhileViewportChanging));
|
2017-06-25 23:05:48 +02:00
|
|
|
|
|
2024-05-21 17:39:03 +02:00
|
|
|
|
public static readonly DependencyProperty MapBackgroundProperty =
|
|
|
|
|
|
DependencyPropertyHelper.Register<MapImageLayer, Brush>(nameof(MapBackground));
|
2017-06-25 23:05:48 +02:00
|
|
|
|
|
2024-05-21 17:39:03 +02:00
|
|
|
|
public static readonly DependencyProperty MapForegroundProperty =
|
|
|
|
|
|
DependencyPropertyHelper.Register<MapImageLayer, Brush>(nameof(MapForeground));
|
2015-08-09 20:04:44 +02:00
|
|
|
|
|
2024-05-21 17:39:03 +02:00
|
|
|
|
public static readonly DependencyProperty LoadingProgressProperty =
|
2025-01-04 15:18:27 +01:00
|
|
|
|
DependencyPropertyHelper.Register<MapImageLayer, double>(nameof(LoadingProgress), 1d);
|
2022-08-05 18:54:19 +02:00
|
|
|
|
|
2023-08-13 07:52:11 +02:00
|
|
|
|
private readonly Progress<double> loadingProgress;
|
2025-10-26 17:52:54 +01:00
|
|
|
|
private readonly UpdateTimer updateTimer;
|
2025-08-21 22:40:51 +02:00
|
|
|
|
private bool updateInProgress;
|
2013-05-07 18:12:25 +02:00
|
|
|
|
|
|
|
|
|
|
public MapImageLayer()
|
|
|
|
|
|
{
|
2024-05-25 18:58:51 +02:00
|
|
|
|
IsHitTestVisible = false;
|
|
|
|
|
|
|
2024-05-21 17:39:03 +02:00
|
|
|
|
loadingProgress = new Progress<double>(p => SetValue(LoadingProgressProperty, p));
|
2022-08-05 18:54:19 +02:00
|
|
|
|
|
2025-10-26 17:52:54 +01:00
|
|
|
|
updateTimer = new UpdateTimer { Interval = UpdateInterval };
|
2018-02-09 17:43:47 +01:00
|
|
|
|
updateTimer.Tick += async (s, e) => await UpdateImageAsync();
|
2014-04-05 14:53:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-28 21:53:38 +01:00
|
|
|
|
/// <summary>
|
2021-11-28 23:50:13 +01:00
|
|
|
|
/// Description of the layer. Used to display copyright information on top of the map.
|
2020-03-28 21:53:38 +01:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public string Description
|
|
|
|
|
|
{
|
2022-08-06 10:40:59 +02:00
|
|
|
|
get => (string)GetValue(DescriptionProperty);
|
|
|
|
|
|
set => SetValue(DescriptionProperty, value);
|
2020-03-28 21:53:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-02-05 18:35:30 +01:00
|
|
|
|
/// <summary>
|
2020-03-28 21:53:38 +01:00
|
|
|
|
/// Relative size of the map image in relation to the current view size.
|
2017-07-17 21:31:09 +02:00
|
|
|
|
/// Setting a value greater than one will let MapImageLayer request images that
|
2020-03-28 21:53:38 +01:00
|
|
|
|
/// are larger than the view, in order to support smooth panning.
|
2013-05-15 16:28:57 +02:00
|
|
|
|
/// </summary>
|
2013-05-15 15:58:07 +02:00
|
|
|
|
public double RelativeImageSize
|
|
|
|
|
|
{
|
2022-08-06 10:40:59 +02:00
|
|
|
|
get => (double)GetValue(RelativeImageSizeProperty);
|
|
|
|
|
|
set => SetValue(RelativeImageSizeProperty, value);
|
2013-05-15 15:58:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-08-09 20:04:44 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Minimum time interval between images updates.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public TimeSpan UpdateInterval
|
|
|
|
|
|
{
|
2022-08-06 10:40:59 +02:00
|
|
|
|
get => (TimeSpan)GetValue(UpdateIntervalProperty);
|
|
|
|
|
|
set => SetValue(UpdateIntervalProperty, value);
|
2015-08-09 20:04:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-06-25 23:05:48 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Controls if images are updated while the viewport is still changing.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public bool UpdateWhileViewportChanging
|
|
|
|
|
|
{
|
2022-08-06 10:40:59 +02:00
|
|
|
|
get => (bool)GetValue(UpdateWhileViewportChangingProperty);
|
|
|
|
|
|
set => SetValue(UpdateWhileViewportChangingProperty, value);
|
2017-06-25 23:05:48 +02:00
|
|
|
|
}
|
2017-02-05 18:35:30 +01:00
|
|
|
|
|
2017-06-25 23:05:48 +02:00
|
|
|
|
/// <summary>
|
2021-11-28 23:50:13 +01:00
|
|
|
|
/// Optional background brush. Sets MapBase.Background if not null and this layer is the base map layer.
|
2017-06-25 23:05:48 +02:00
|
|
|
|
/// </summary>
|
2021-11-28 23:50:13 +01:00
|
|
|
|
public Brush MapBackground
|
2017-06-25 23:05:48 +02:00
|
|
|
|
{
|
2022-08-06 10:40:59 +02:00
|
|
|
|
get => (Brush)GetValue(MapBackgroundProperty);
|
|
|
|
|
|
set => SetValue(MapBackgroundProperty, value);
|
2017-06-25 23:05:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2021-11-28 23:50:13 +01:00
|
|
|
|
/// Optional foreground brush. Sets MapBase.Foreground if not null and this layer is the base map layer.
|
2017-06-25 23:05:48 +02:00
|
|
|
|
/// </summary>
|
2021-11-28 23:50:13 +01:00
|
|
|
|
public Brush MapForeground
|
2017-06-25 23:05:48 +02:00
|
|
|
|
{
|
2022-08-06 10:40:59 +02:00
|
|
|
|
get => (Brush)GetValue(MapForegroundProperty);
|
|
|
|
|
|
set => SetValue(MapForegroundProperty, value);
|
2016-02-23 20:07:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-05 18:54:19 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the progress of the ImageLoader as a double value between 0 and 1.
|
|
|
|
|
|
/// </summary>
|
2025-01-25 16:47:42 +01:00
|
|
|
|
public double LoadingProgress => (double)GetValue(LoadingProgressProperty);
|
2022-08-05 18:54:19 +02:00
|
|
|
|
|
2025-10-29 22:20:54 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets a collection of all CRSs supported by a MapImageLayer.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public IReadOnlyCollection<string> SupportedCrsIds { get; protected set; }
|
2025-09-19 18:49:12 +02:00
|
|
|
|
|
2021-11-14 22:25:34 +01:00
|
|
|
|
protected override void SetParentMap(MapBase map)
|
|
|
|
|
|
{
|
2022-11-12 17:27:49 +01:00
|
|
|
|
if (map != null)
|
2021-11-14 22:25:34 +01:00
|
|
|
|
{
|
2022-11-12 17:27:49 +01:00
|
|
|
|
while (Children.Count < 2)
|
2022-11-12 11:08:10 +01:00
|
|
|
|
{
|
2025-01-26 15:17:08 +01:00
|
|
|
|
Children.Add(new Image
|
|
|
|
|
|
{
|
|
|
|
|
|
Opacity = 0d,
|
|
|
|
|
|
Stretch = Stretch.Fill
|
|
|
|
|
|
});
|
2022-11-12 11:08:10 +01:00
|
|
|
|
}
|
2021-11-14 22:25:34 +01:00
|
|
|
|
}
|
2022-11-12 17:27:49 +01:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
updateTimer.Stop();
|
|
|
|
|
|
ClearImages();
|
|
|
|
|
|
Children.Clear();
|
|
|
|
|
|
}
|
2021-11-14 22:25:34 +01:00
|
|
|
|
|
|
|
|
|
|
base.SetParentMap(map);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-03 07:20:42 +02:00
|
|
|
|
protected override async void OnViewportChanged(ViewportChangedEventArgs e)
|
2013-05-07 18:12:25 +02:00
|
|
|
|
{
|
2022-11-25 19:05:48 +01:00
|
|
|
|
base.OnViewportChanged(e);
|
|
|
|
|
|
|
2017-06-25 23:05:48 +02:00
|
|
|
|
if (e.ProjectionChanged)
|
2017-02-05 18:35:30 +01:00
|
|
|
|
{
|
2017-10-27 17:15:18 +02:00
|
|
|
|
ClearImages();
|
2022-11-24 23:24:51 +01:00
|
|
|
|
await UpdateImageAsync(); // update immediately
|
2017-06-25 23:05:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-10-26 17:52:54 +01:00
|
|
|
|
updateTimer.Run(!UpdateWhileViewportChanging);
|
2017-06-25 23:05:48 +02:00
|
|
|
|
}
|
2013-05-07 18:12:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 22:40:51 +02:00
|
|
|
|
protected abstract Task<ImageSource> GetImageAsync(BoundingBox boundingBox, IProgress<double> progress);
|
2022-11-30 17:59:38 +01:00
|
|
|
|
|
2020-10-25 16:09:09 +01:00
|
|
|
|
protected async Task UpdateImageAsync()
|
2013-05-13 23:49:48 +02:00
|
|
|
|
{
|
2025-10-25 23:26:36 +02:00
|
|
|
|
if (!updateInProgress)
|
2025-08-21 22:40:51 +02:00
|
|
|
|
{
|
|
|
|
|
|
updateInProgress = true;
|
|
|
|
|
|
updateTimer.Stop();
|
2017-11-02 19:05:46 +01:00
|
|
|
|
|
2025-08-21 22:40:51 +02:00
|
|
|
|
ImageSource image = null;
|
|
|
|
|
|
BoundingBox boundingBox = null;
|
2025-04-04 14:35:18 +02:00
|
|
|
|
|
2025-09-19 18:49:12 +02:00
|
|
|
|
if (ParentMap != null &&
|
|
|
|
|
|
ParentMap.ActualWidth > 0d &&
|
|
|
|
|
|
ParentMap.ActualHeight > 0d &&
|
2025-09-22 09:17:26 +02:00
|
|
|
|
(SupportedCrsIds == null || SupportedCrsIds.Contains(ParentMap.MapProjection.CrsId)))
|
2025-08-20 00:29:56 +02:00
|
|
|
|
{
|
2025-08-21 22:40:51 +02:00
|
|
|
|
var width = ParentMap.ActualWidth * RelativeImageSize;
|
|
|
|
|
|
var height = ParentMap.ActualHeight * RelativeImageSize;
|
|
|
|
|
|
var x = (ParentMap.ActualWidth - width) / 2d;
|
|
|
|
|
|
var y = (ParentMap.ActualHeight - height) / 2d;
|
2025-04-04 14:35:18 +02:00
|
|
|
|
|
2025-08-21 22:40:51 +02:00
|
|
|
|
boundingBox = ParentMap.ViewRectToBoundingBox(new Rect(x, y, width, height));
|
|
|
|
|
|
image = await GetImageAsync(boundingBox, loadingProgress);
|
2025-08-19 23:20:11 +02:00
|
|
|
|
}
|
2025-08-21 22:40:51 +02:00
|
|
|
|
|
|
|
|
|
|
SwapImages(image, boundingBox);
|
|
|
|
|
|
updateInProgress = false;
|
2017-10-27 17:15:18 +02:00
|
|
|
|
}
|
2025-10-26 17:52:54 +01:00
|
|
|
|
else // update on next timer tick
|
2025-10-25 23:26:36 +02:00
|
|
|
|
{
|
2025-10-26 17:52:54 +01:00
|
|
|
|
updateTimer.Run();
|
2025-10-25 23:26:36 +02:00
|
|
|
|
}
|
2017-10-27 17:15:18 +02:00
|
|
|
|
}
|
2017-06-25 23:05:48 +02:00
|
|
|
|
|
2017-10-27 17:15:18 +02:00
|
|
|
|
private void ClearImages()
|
|
|
|
|
|
{
|
2021-11-14 22:25:34 +01:00
|
|
|
|
foreach (var image in Children.OfType<Image>())
|
2015-01-26 19:45:05 +01:00
|
|
|
|
{
|
2021-11-14 22:25:34 +01:00
|
|
|
|
image.ClearValue(BoundingBoxProperty);
|
|
|
|
|
|
image.ClearValue(Image.SourceProperty);
|
2014-10-19 21:50:23 +02:00
|
|
|
|
}
|
2017-10-27 17:15:18 +02:00
|
|
|
|
}
|
2014-04-05 14:53:58 +02:00
|
|
|
|
|
2025-01-05 09:22:50 +01:00
|
|
|
|
private void SwapImages(ImageSource image, BoundingBox boundingBox)
|
2017-10-27 17:15:18 +02:00
|
|
|
|
{
|
2025-08-21 22:40:51 +02:00
|
|
|
|
if (Children.Count >= 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
var topImage = (Image)Children[0];
|
2017-10-27 17:15:18 +02:00
|
|
|
|
|
2025-08-21 22:40:51 +02:00
|
|
|
|
Children.RemoveAt(0);
|
|
|
|
|
|
Children.Insert(1, topImage);
|
2017-10-27 17:15:18 +02:00
|
|
|
|
|
2025-08-21 22:40:51 +02:00
|
|
|
|
topImage.Source = image;
|
|
|
|
|
|
SetBoundingBox(topImage, boundingBox);
|
2017-10-27 17:15:18 +02:00
|
|
|
|
|
2025-08-21 22:40:51 +02:00
|
|
|
|
if (MapBase.ImageFadeDuration > TimeSpan.Zero)
|
|
|
|
|
|
{
|
|
|
|
|
|
FadeOver();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
topImage.Opacity = 1d;
|
|
|
|
|
|
Children[0].Opacity = 0d;
|
|
|
|
|
|
}
|
2021-11-14 22:25:34 +01:00
|
|
|
|
}
|
2013-05-07 18:12:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|