XAML-Map-Control/MapControl/Shared/GroundOverlay.cs

265 lines
9 KiB
C#
Raw Normal View History

2025-03-31 21:33:52 +02:00
using Microsoft.Extensions.Logging;
using System;
2022-01-18 21:30:22 +01:00
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
2024-06-22 08:10:45 +02:00
using System.Xml.Linq;
2024-05-22 11:25:32 +02:00
#if WPF
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
2022-01-18 21:30:22 +01:00
#elif UWP
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
2024-05-22 11:25:32 +02:00
#elif WINUI
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.Controls;
using Avalonia.Media;
2022-01-18 21:30:22 +01:00
#endif
namespace MapControl
{
2022-01-19 16:43:00 +01:00
public class GroundOverlay : MapPanel
2022-01-18 21:30:22 +01:00
{
2024-09-13 23:50:30 +02:00
private class ImageOverlay
2022-01-18 21:30:22 +01:00
{
2025-09-04 10:36:39 +02:00
private readonly string imagePath;
private readonly LatLonBox boundingBox;
private readonly int zIndex;
public ImageOverlay(string path, LatLonBox latLonBox, int zOrder)
2022-01-18 21:30:22 +01:00
{
2025-09-04 10:36:39 +02:00
imagePath = path;
boundingBox = latLonBox;
zIndex = zOrder;
2022-01-18 21:30:22 +01:00
}
2025-09-04 16:29:26 +02:00
public Image Image { get; private set; }
public async Task LoadImage(ZipArchive archive)
2025-09-04 08:11:15 +02:00
{
2025-09-04 10:36:39 +02:00
var entry = archive.GetEntry(imagePath);
2025-09-04 08:11:15 +02:00
if (entry != null)
{
2025-09-04 12:35:04 +02:00
MemoryStream memoryStream;
// ZipArchive does not support multithreading, synchronously copy ZipArchiveEntry stream to MemoryStream.
//
2025-09-04 08:11:15 +02:00
using (var zipStream = entry.Open())
{
2025-09-04 12:35:04 +02:00
memoryStream = new MemoryStream((int)zipStream.Length);
zipStream.CopyTo(memoryStream);
2025-09-04 10:36:39 +02:00
memoryStream.Seek(0, SeekOrigin.Begin);
2025-09-04 08:11:15 +02:00
}
2025-09-04 12:35:04 +02:00
// Close Zip Stream before awaiting.
//
2025-09-04 16:29:26 +02:00
CreateImage(await ImageLoader.LoadImageAsync(memoryStream));
2025-09-04 08:11:15 +02:00
}
}
2025-09-04 16:29:26 +02:00
public async Task LoadImage(Uri docUri)
2025-09-03 23:42:38 +02:00
{
2025-09-04 16:29:26 +02:00
CreateImage(await ImageLoader.LoadImageAsync(new Uri(docUri, imagePath)));
2025-09-03 23:42:38 +02:00
}
2025-09-04 16:29:26 +02:00
private void CreateImage(ImageSource image)
2025-09-03 23:42:38 +02:00
{
2025-09-04 16:29:26 +02:00
if (image != null)
2025-09-04 10:36:39 +02:00
{
2025-09-04 16:29:26 +02:00
Image = new Image { Source = image, Stretch = Stretch.Fill };
Image.SetValue(Canvas.ZIndexProperty, zIndex);
SetBoundingBox(Image, boundingBox);
2025-09-04 10:36:39 +02:00
}
}
2022-01-18 21:30:22 +01:00
}
2025-08-22 11:06:37 +02:00
private static ILogger logger;
private static ILogger Logger => logger ?? (logger = ImageLoader.LoggerFactory?.CreateLogger<GroundOverlay>());
2024-05-22 17:04:31 +02:00
public static readonly DependencyProperty SourcePathProperty =
2024-05-23 18:22:52 +02:00
DependencyPropertyHelper.Register<GroundOverlay, string>(nameof(SourcePath), null,
async (groundOverlay, oldValue, newValue) => await groundOverlay.LoadAsync(newValue));
2022-01-18 21:30:22 +01:00
2022-01-18 23:44:08 +01:00
public string SourcePath
2022-01-18 21:30:22 +01:00
{
2022-08-06 10:40:59 +02:00
get => (string)GetValue(SourcePathProperty);
set => SetValue(SourcePathProperty, value);
2022-01-18 21:30:22 +01:00
}
public static async Task<GroundOverlay> CreateAsync(string sourcePath)
{
var groundOverlay = new GroundOverlay();
await groundOverlay.LoadAsync(sourcePath);
return groundOverlay;
}
public async Task LoadAsync(string sourcePath)
2022-01-18 21:30:22 +01:00
{
2025-09-04 16:29:26 +02:00
List<ImageOverlay> imageOverlays = null;
2022-01-18 21:30:22 +01:00
2022-01-18 23:44:08 +01:00
if (!string.IsNullOrEmpty(sourcePath))
2022-01-18 21:30:22 +01:00
{
try
{
2022-01-18 23:44:08 +01:00
var ext = Path.GetExtension(sourcePath).ToLower();
2022-01-18 21:30:22 +01:00
if (ext == ".kmz")
{
2025-01-26 22:40:33 +01:00
imageOverlays = await LoadGroundOverlaysFromArchive(sourcePath);
2022-01-18 21:30:22 +01:00
}
else if (ext == ".kml")
{
2025-01-26 22:40:33 +01:00
imageOverlays = await LoadGroundOverlaysFromFile(sourcePath);
2022-01-18 21:30:22 +01:00
}
}
catch (Exception ex)
{
2025-08-22 11:06:37 +02:00
Logger?.LogError(ex, "Failed loading from {path}", sourcePath);
2022-01-18 21:30:22 +01:00
}
}
2024-05-22 17:04:31 +02:00
Children.Clear();
2022-01-18 21:30:22 +01:00
if (imageOverlays != null)
{
2025-09-04 16:29:26 +02:00
foreach (var image in imageOverlays.Select(imageOverlay => imageOverlay.Image).Where(image => image != null))
2022-01-18 21:30:22 +01:00
{
2024-09-06 16:03:16 +02:00
Children.Add(image);
}
2022-01-18 21:30:22 +01:00
}
}
2025-09-04 16:29:26 +02:00
private static async Task<List<ImageOverlay>> LoadGroundOverlaysFromArchive(string archiveFilePath)
2022-01-18 21:30:22 +01:00
{
2025-09-04 16:29:26 +02:00
List<ImageOverlay> imageOverlays;
2023-01-11 17:51:00 +01:00
using (var archive = ZipFile.OpenRead(archiveFilePath))
2022-01-18 21:30:22 +01:00
{
2023-01-11 17:51:00 +01:00
var docEntry = archive.GetEntry("doc.kml") ??
2025-09-01 18:21:41 +02:00
archive.Entries.FirstOrDefault(e => e.Name.EndsWith(".kml")) ??
throw new ArgumentException($"No KML entry found in {archiveFilePath}.");
2022-01-18 21:30:22 +01:00
2025-09-01 18:21:41 +02:00
using (var docStream = docEntry.Open())
2022-01-18 21:30:22 +01:00
{
2025-09-01 18:21:41 +02:00
imageOverlays = await ReadGroundOverlays(docStream);
}
2022-01-18 21:30:22 +01:00
2025-09-04 16:29:26 +02:00
await Task.WhenAll(imageOverlays.Select(imageOverlay => imageOverlay.LoadImage(archive)));
2022-01-18 21:30:22 +01:00
}
2025-09-04 16:29:26 +02:00
return imageOverlays;
2022-01-18 21:30:22 +01:00
}
2025-09-04 16:29:26 +02:00
private static async Task<List<ImageOverlay>> LoadGroundOverlaysFromFile(string docFilePath)
2022-01-18 21:30:22 +01:00
{
2025-09-01 18:21:41 +02:00
List<ImageOverlay> imageOverlays;
2022-01-18 21:30:22 +01:00
2025-09-04 16:29:26 +02:00
var docUri = new Uri(FilePath.GetFullPath(docFilePath));
using (var docStream = File.OpenRead(docUri.AbsolutePath))
2022-01-18 21:30:22 +01:00
{
2025-09-01 18:21:41 +02:00
imageOverlays = await ReadGroundOverlays(docStream);
}
2022-01-18 21:30:22 +01:00
2025-09-04 16:29:26 +02:00
await Task.WhenAll(imageOverlays.Select(imageOverlay => imageOverlay.LoadImage(docUri)));
2025-09-03 23:42:38 +02:00
2022-01-18 21:30:22 +01:00
return imageOverlays;
}
2025-09-01 18:21:41 +02:00
private static async Task<List<ImageOverlay>> ReadGroundOverlays(Stream docStream)
2022-01-18 21:30:22 +01:00
{
2025-09-01 18:21:41 +02:00
#if NETFRAMEWORK
2025-09-04 08:11:15 +02:00
var document = await Task.Run(() => XDocument.Load(docStream, LoadOptions.None));
2025-09-01 18:21:41 +02:00
#else
var document = await XDocument.LoadAsync(docStream, LoadOptions.None, System.Threading.CancellationToken.None);
#endif
var rootElement = document.Root;
var ns = rootElement.Name.Namespace;
var docElement = rootElement.Element(ns + "Document") ?? rootElement;
var imageOverlays = new List<ImageOverlay>();
2022-01-18 21:30:22 +01:00
2024-06-22 08:10:45 +02:00
foreach (var folderElement in docElement.Elements(ns + "Folder"))
{
foreach (var groundOverlayElement in folderElement.Elements(ns + "GroundOverlay"))
2022-01-18 21:30:22 +01:00
{
2025-09-04 10:36:39 +02:00
var pathElement = groundOverlayElement.Element(ns + "Icon");
var path = pathElement?.Element(ns + "href")?.Value;
2022-01-18 21:30:22 +01:00
var latLonBoxElement = groundOverlayElement.Element(ns + "LatLonBox");
var latLonBox = latLonBoxElement != null ? ReadLatLonBox(latLonBoxElement) : null;
2024-06-22 08:10:45 +02:00
var drawOrder = groundOverlayElement.Element(ns + "drawOrder")?.Value;
2025-09-04 10:36:39 +02:00
var zOrder = drawOrder != null ? int.Parse(drawOrder) : 0;
2022-01-18 21:30:22 +01:00
2025-09-04 10:36:39 +02:00
if (latLonBox != null && path != null)
2024-06-22 08:10:45 +02:00
{
2025-09-04 10:36:39 +02:00
imageOverlays.Add(new ImageOverlay(path, latLonBox, zOrder));
2024-06-22 08:10:45 +02:00
}
2022-01-18 21:30:22 +01:00
}
}
2025-09-01 18:21:41 +02:00
return imageOverlays;
2022-01-18 21:30:22 +01:00
}
2024-09-09 16:44:45 +02:00
private static LatLonBox ReadLatLonBox(XElement latLonBoxElement)
2022-01-18 21:30:22 +01:00
{
2024-06-22 08:10:45 +02:00
var ns = latLonBoxElement.Name.Namespace;
2022-12-01 22:48:08 +01:00
var north = double.NaN;
var south = double.NaN;
var east = double.NaN;
var west = double.NaN;
var rotation = 0d;
2022-01-18 21:30:22 +01:00
2024-06-22 08:10:45 +02:00
var value = latLonBoxElement.Element(ns + "north")?.Value;
if (value != null)
2022-01-18 21:30:22 +01:00
{
2024-06-22 08:10:45 +02:00
north = double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
}
value = latLonBoxElement.Element(ns + "south")?.Value;
if (value != null)
{
south = double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
}
value = latLonBoxElement.Element(ns + "east")?.Value;
if (value != null)
{
east = double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
}
value = latLonBoxElement.Element(ns + "west")?.Value;
if (value != null)
{
west = double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
}
value = latLonBoxElement.Element(ns + "rotation")?.Value;
if (value != null)
{
rotation = double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
2022-01-18 21:30:22 +01:00
}
2024-06-22 08:10:45 +02:00
if (double.IsNaN(north) || double.IsNaN(south) ||
double.IsNaN(east) || double.IsNaN(west) ||
north <= south || east <= west)
2022-12-08 15:05:23 +01:00
{
throw new FormatException("Invalid LatLonBox");
}
2024-09-09 16:44:45 +02:00
return new LatLonBox(south, west, north, east, rotation);
2022-01-18 21:30:22 +01:00
}
}
}