From 9db1987ee560a44a8bf115e5717835e8547f6d0f Mon Sep 17 00:00:00 2001 From: ClemensFischer Date: Wed, 30 Nov 2022 17:59:38 +0100 Subject: [PATCH] Rework Map/WmsImageLayer --- MapControl/Shared/ImageLoader.cs | 8 +- MapControl/Shared/MapBase.cs | 10 +- MapControl/Shared/MapImageLayer.cs | 68 +++++++------- MapControl/Shared/WmsImageLayer.cs | 112 +++++++++++----------- MapControl/WPF/ImageLoader.WPF.cs | 49 ++++++++++ MapControl/WPF/MapItemsImageLayer.WPF.cs | 8 +- MapControl/WinUI/ImageLoader.WinUI.cs | 113 ++++++++++++++++++++++- 7 files changed, 265 insertions(+), 103 deletions(-) diff --git a/MapControl/Shared/ImageLoader.cs b/MapControl/Shared/ImageLoader.cs index 779f524f..3b31b557 100644 --- a/MapControl/Shared/ImageLoader.cs +++ b/MapControl/Shared/ImageLoader.cs @@ -33,6 +33,8 @@ namespace MapControl { ImageSource image = null; + progress?.Report(0d); + try { if (!uri.IsAbsoluteUri || uri.IsFile) @@ -58,6 +60,8 @@ namespace MapControl Debug.WriteLine($"ImageLoader: {uri}: {ex.Message}"); } + progress?.Report(1d); + return image; } @@ -77,8 +81,6 @@ namespace MapControl { HttpResponse response = null; - progress?.Report(0d); - try { using (var responseMessage = await HttpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false)) @@ -108,8 +110,6 @@ namespace MapControl Debug.WriteLine($"ImageLoader: {uri}: {ex.Message}"); } - progress?.Report(1d); - return response; } diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs index 7fd8d35b..cb13962e 100644 --- a/MapControl/Shared/MapBase.cs +++ b/MapControl/Shared/MapBase.cs @@ -262,12 +262,12 @@ namespace MapControl var p3 = ViewTransform.ViewToMap(new Point(rect.X + rect.Width, rect.Y)); var p4 = ViewTransform.ViewToMap(new Point(rect.X + rect.Width, rect.Y + rect.Height)); - rect.X = Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X))); - rect.Y = Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y))); - rect.Width = Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))) - rect.X; - rect.Height = Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))) - rect.Y; + var x1 = Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X))); + var x2 = Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))); + var y1 = Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y))); + var y2 = Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))); - return MapProjection.RectToBoundingBox(rect); + return MapProjection.RectToBoundingBox(new Rect(x1, y1, x2 - x1, y2 - y1)); } /// diff --git a/MapControl/Shared/MapImageLayer.cs b/MapControl/Shared/MapImageLayer.cs index 8faf0e74..2412cee9 100644 --- a/MapControl/Shared/MapImageLayer.cs +++ b/MapControl/Shared/MapImageLayer.cs @@ -134,11 +134,6 @@ namespace MapControl private set => SetValue(LoadingProgressProperty, value); } - /// - /// The current BoundingBox - /// - public BoundingBox BoundingBox { get; private set; } - protected override void SetParentMap(MapBase map) { if (map != null) @@ -179,6 +174,8 @@ namespace MapControl } } + protected abstract Task GetImageAsync(BoundingBox boundingBox, IProgress progress); + protected async Task UpdateImageAsync() { if (updateInProgress) @@ -190,45 +187,44 @@ namespace MapControl else { updateTimer.Stop(); + updateInProgress = true; - if (ParentMap != null && ParentMap.RenderSize.Width > 0 && ParentMap.RenderSize.Height > 0) + ImageSource image = null; + var boundingBox = GetImageBoundingBox(); + + if (boundingBox != null) { - updateInProgress = true; - - UpdateBoundingBox(); - - ImageSource image = null; - - if (BoundingBox != null) + try { - try - { - image = await GetImageAsync(imageProgress); - } - catch (Exception ex) - { - Debug.WriteLine($"MapImageLayer: {ex.Message}"); - } + image = await GetImageAsync(boundingBox, imageProgress); + } + catch (Exception ex) + { + Debug.WriteLine($"MapImageLayer: {ex.Message}"); } - - SwapImages(image); - - updateInProgress = false; } + + SwapImages(image, boundingBox); + + updateInProgress = false; } } - protected abstract Task GetImageAsync(IProgress progress); - - private void UpdateBoundingBox() + protected BoundingBox GetImageBoundingBox() { - var width = ParentMap.RenderSize.Width * RelativeImageSize; - var height = ParentMap.RenderSize.Height * RelativeImageSize; - var x = (ParentMap.RenderSize.Width - width) / 2d; - var y = (ParentMap.RenderSize.Height - height) / 2d; - var rect = new Rect(x, y, width, height); + BoundingBox boundingBox = null; - BoundingBox = ParentMap.ViewRectToBoundingBox(rect); + if (ParentMap != null && ParentMap.RenderSize.Width > 0d && ParentMap.RenderSize.Height > 0d) + { + var width = ParentMap.RenderSize.Width * RelativeImageSize; + var height = ParentMap.RenderSize.Height * RelativeImageSize; + var x = (ParentMap.RenderSize.Width - width) / 2d; + var y = (ParentMap.RenderSize.Height - height) / 2d; + + boundingBox = ParentMap.ViewRectToBoundingBox(new Rect(x, y, width, height)); + } + + return boundingBox; } private void ClearImages() @@ -240,7 +236,7 @@ namespace MapControl } } - private void SwapImages(ImageSource image) + private void SwapImages(ImageSource image, BoundingBox boundingBox) { if (Children.Count >= 2) { @@ -251,7 +247,7 @@ namespace MapControl Children.Insert(1, topImage); topImage.Source = image; - SetBoundingBox(topImage, BoundingBox); + SetBoundingBox(topImage, boundingBox); topImage.BeginAnimation(OpacityProperty, new DoubleAnimation { diff --git a/MapControl/Shared/WmsImageLayer.cs b/MapControl/Shared/WmsImageLayer.cs index c2ecc273..4774a3e4 100644 --- a/MapControl/Shared/WmsImageLayer.cs +++ b/MapControl/Shared/WmsImageLayer.cs @@ -134,44 +134,17 @@ namespace MapControl return element; } - /// - /// Loads an XElement from the URL returned by GetFeatureInfoRequestUri(). - /// - public async Task GetFeatureInfoAsync(Point position) - { - XElement element = null; - - if (ServiceUri != null) - { - var uri = GetFeatureInfoRequestUri(position, "text/xml"); - - if (!string.IsNullOrEmpty(uri)) - { - try - { - using (var stream = await ImageLoader.HttpClient.GetStreamAsync(uri)) - { - element = XDocument.Load(stream).Root; - } - } - catch (Exception ex) - { - Debug.WriteLine($"WmsImageLayer: {uri}: {ex.Message}"); - } - } - } - - return element; - } - /// /// Gets a response string from the URL returned by GetFeatureInfoRequestUri(). /// - public async Task GetFeatureInfoTextAsync(Point position, string format = "text/plain") + public async Task GetFeatureInfoAsync(Point position, string format = "text/plain") { string response = null; - if (ServiceUri != null) + if (ServiceUri != null && + ParentMap?.MapProjection != null && + ParentMap.RenderSize.Width > 0d && + ParentMap.RenderSize.Height > 0d) { var uri = GetFeatureInfoRequestUri(position, format); @@ -194,11 +167,11 @@ namespace MapControl /// /// Loads an ImageSource from the URL returned by GetMapRequestUri(). /// - protected override async Task GetImageAsync(IProgress progress) + protected override async Task GetImageAsync(BoundingBox boundingBox, IProgress progress) { ImageSource image = null; - if (ServiceUri != null) + if (ServiceUri != null && ParentMap?.MapProjection != null) { if (Layers == null && ServiceUri.ToString().IndexOf("LAYERS=", StringComparison.OrdinalIgnoreCase) < 0) @@ -206,11 +179,38 @@ namespace MapControl Layers = (await GetLayerNamesAsync())?.FirstOrDefault() ?? ""; // get first Layer from Capabilities } - var uri = GetMapRequestUri(); - - if (!string.IsNullOrEmpty(uri)) + if (boundingBox.West >= -180d && boundingBox.East <= 180d || + ParentMap.MapProjection.Type > MapProjectionType.NormalCylindrical) { - image = await ImageLoader.LoadImageAsync(new Uri(uri), progress); + var uri = CreateUri(GetMapRequestUri(boundingBox)); + + if (uri != null) + { + image = await ImageLoader.LoadImageAsync(uri, progress); + } + } + else + { + BoundingBox bbox1, bbox2; + + if (boundingBox.West < -180d) + { + bbox1 = new BoundingBox(boundingBox.South, boundingBox.West + 360, boundingBox.North, 180d); + bbox2 = new BoundingBox(boundingBox.South, -180d, boundingBox.North, boundingBox.East); + } + else + { + bbox1 = new BoundingBox(boundingBox.South, boundingBox.West, boundingBox.North, 180d); + bbox2 = new BoundingBox(boundingBox.South, -180d, boundingBox.North, boundingBox.East - 360d); + } + + var uri1 = CreateUri(GetMapRequestUri(bbox1)); + var uri2 = CreateUri(GetMapRequestUri(bbox2)); + + if (uri1 != null && uri2 != null) + { + image = await ImageLoader.LoadMergedImageAsync(uri1, uri2, progress); + } } } @@ -233,14 +233,9 @@ namespace MapControl /// /// Returns a GetMap request URL string. /// - protected virtual string GetMapRequestUri() + protected virtual string GetMapRequestUri(BoundingBox boundingBox) { - if (ParentMap?.MapProjection == null) - { - return null; - } - - var mapRect = ParentMap.MapProjection.BoundingBoxToRect(BoundingBox); + var mapRect = ParentMap.MapProjection.BoundingBoxToRect(boundingBox); var viewScale = ParentMap.ViewTransform.Scale; return GetRequestUri(new Dictionary @@ -263,14 +258,10 @@ namespace MapControl /// protected virtual string GetFeatureInfoRequestUri(Point position, string format) { - if (ParentMap?.MapProjection == null) - { - return null; - } - - var mapRect = ParentMap.MapProjection.BoundingBoxToRect(BoundingBox); - var viewRect = GetViewRect(mapRect); var viewSize = ParentMap.RenderSize; + var boundingBox = ParentMap.ViewRectToBoundingBox(new Rect(0d, 0d, viewSize.Width, viewSize.Height)); + var mapRect = ParentMap.MapProjection.BoundingBoxToRect(boundingBox); + var viewRect = GetViewRect(mapRect); var transform = new Matrix(1, 0, 0, 1, -viewSize.Width / 2, -viewSize.Height / 2); transform.Rotate(-viewRect.Rotation); @@ -326,5 +317,22 @@ namespace MapControl return uri.Replace(" ", "%20"); } + + private static Uri CreateUri(string uri) + { + if (!string.IsNullOrEmpty(uri)) + { + try + { + return new Uri(uri, UriKind.RelativeOrAbsolute); + } + catch (Exception ex) + { + Debug.WriteLine($"WmsImageLayer: {uri}: {ex.Message}"); + } + } + + return null; + } } } diff --git a/MapControl/WPF/ImageLoader.WPF.cs b/MapControl/WPF/ImageLoader.WPF.cs index 20f704e4..1f3eeab4 100644 --- a/MapControl/WPF/ImageLoader.WPF.cs +++ b/MapControl/WPF/ImageLoader.WPF.cs @@ -2,6 +2,7 @@ // © 2022 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) +using System; using System.IO; using System.Threading.Tasks; using System.Windows.Media; @@ -55,5 +56,53 @@ namespace MapControl return bitmapImage; } + + internal static async Task LoadMergedImageAsync(Uri uri1, Uri uri2, IProgress progress) + { + ImageSource image = null; + IProgress progress1 = null; + IProgress progress2 = null; + + if (progress != null) + { + var p1 = 0d; + var p2 = 0d; + progress1 = new Progress(p => { p1 = p; progress.Report((p1 + p2) / 2d); }); + progress2 = new Progress(p => { p2 = p; progress.Report((p1 + p2) / 2d); }); + } + + var images = await Task.WhenAll(LoadImageAsync(uri1, progress1), LoadImageAsync(uri2, progress2)); + + if (images.Length == 2 && + images[0] is BitmapSource image1 && + images[1] is BitmapSource image2 && + image1.PixelHeight == image2.PixelHeight && + image1.Format == image2.Format && + image1.Format.BitsPerPixel % 8 == 0) + { + var format = image1.Format; + var width = image1.PixelWidth + image2.PixelWidth; + var height = image1.PixelHeight; + var stride1 = image1.PixelWidth * format.BitsPerPixel / 8; + var stride2 = image2.PixelWidth * format.BitsPerPixel / 8; + var buffer1 = new byte[stride1 * height]; + var buffer2 = new byte[stride2 * height]; + var stride = stride1 + stride2; + var buffer = new byte[stride * height]; + + image1.CopyPixels(buffer1, stride1, 0); + image2.CopyPixels(buffer2, stride2, 0); + + for (var i = 0; i < height; i++) + { + Array.Copy(buffer1, i * stride1, buffer, i * stride, stride1); + Array.Copy(buffer2, i * stride2, buffer, i * stride + stride1, stride2); + } + + image = BitmapSource.Create(width, height, 96, 96, format, null, buffer, stride); + } + + return image; + } } } diff --git a/MapControl/WPF/MapItemsImageLayer.WPF.cs b/MapControl/WPF/MapItemsImageLayer.WPF.cs index 3d60fc4e..08384bc8 100644 --- a/MapControl/WPF/MapItemsImageLayer.WPF.cs +++ b/MapControl/WPF/MapItemsImageLayer.WPF.cs @@ -29,7 +29,7 @@ namespace MapControl set => SetValue(ItemsSourceProperty, value); } - protected override async Task GetImageAsync(IProgress progress) + protected override async Task GetImageAsync(BoundingBox boundingBox, IProgress progress) { ImageSource image = null; var projection = ParentMap?.MapProjection; @@ -37,17 +37,17 @@ namespace MapControl if (projection != null && items != null) { - image = await Task.Run(() => GetImage(projection, items)); + image = await Task.Run(() => GetImage(projection, boundingBox, items)); } return image; } - private DrawingImage GetImage(MapProjection projection, IEnumerable items) + private DrawingImage GetImage(MapProjection projection, BoundingBox boundingBox, IEnumerable items) { var scale = ParentMap.ViewTransform.Scale; var rotation = ParentMap.ViewTransform.Rotation; - var mapRect = projection.BoundingBoxToRect(BoundingBox); + var mapRect = projection.BoundingBoxToRect(boundingBox); var drawings = new DrawingGroup(); foreach (var item in items) diff --git a/MapControl/WinUI/ImageLoader.WinUI.cs b/MapControl/WinUI/ImageLoader.WinUI.cs index 44f17819..97acb97f 100644 --- a/MapControl/WinUI/ImageLoader.WinUI.cs +++ b/MapControl/WinUI/ImageLoader.WinUI.cs @@ -3,8 +3,11 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; +using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; +using Windows.Graphics.Imaging; using Windows.Storage; using Windows.Storage.Streams; #if WINUI @@ -33,11 +36,11 @@ namespace MapControl return LoadImageAsync(stream.AsRandomAccessStream()); } - public static Task LoadImageAsync(byte[] buffer) + public static async Task LoadImageAsync(byte[] buffer) { using (var stream = new MemoryStream(buffer)) { - return LoadImageAsync(stream); + return await LoadImageAsync(stream); // await before closing stream } } @@ -57,5 +60,111 @@ namespace MapControl return image; } + + public static async Task LoadWriteableBitmapAsync(Uri uri, IProgress progress = null) + { + WriteableBitmap image = null; + + progress?.Report(0d); + + try + { + if (!uri.IsAbsoluteUri || uri.IsFile) + { + var file = await StorageFile.GetFileFromPathAsync(uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString); + + using (var stream = await file.OpenReadAsync()) + { + image = await LoadWriteableBitmapAsync(stream); + } + } + else if (uri.Scheme == "http" || uri.Scheme == "https") + { + var response = await GetHttpResponseAsync(uri, progress); + + if (response != null && response.Buffer != null) + { + using (var stream = new MemoryStream(response.Buffer)) + { + image = await LoadWriteableBitmapAsync(stream.AsRandomAccessStream()); + } + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"ImageLoader: {uri}: {ex.Message}"); + } + + progress?.Report(1d); + + return image; + } + + public static async Task LoadWriteableBitmapAsync(IRandomAccessStream stream) + { + var decoder = await BitmapDecoder.CreateAsync(stream); + var pixelData = await decoder.GetPixelDataAsync( + BitmapPixelFormat.Bgra8, + BitmapAlphaMode.Straight, + new BitmapTransform(), + ExifOrientationMode.IgnoreExifOrientation, + ColorManagementMode.DoNotColorManage); + + var pixels = pixelData.DetachPixelData(); + var image = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight); + + using (var pixelStream = image.PixelBuffer.AsStream()) + { + await pixelStream.WriteAsync(pixels, 0, pixels.Length); + } + + return image; + } + + internal static async Task LoadMergedImageAsync(Uri uri1, Uri uri2, IProgress progress) + { + WriteableBitmap image = null; + IProgress progress1 = null; + IProgress progress2 = null; + + if (progress != null) + { + var p1 = 0d; + var p2 = 0d; + progress1 = new Progress(p => { p1 = p; progress.Report((p1 + p2) / 2d); }); + progress2 = new Progress(p => { p2 = p; progress.Report((p1 + p2) / 2d); }); + } + + var images = await Task.WhenAll( + LoadWriteableBitmapAsync(uri1, progress1), + LoadWriteableBitmapAsync(uri2, progress2)); + + if (images.Length == 2 && + images[0] != null && + images[1] != null && + images[0].PixelHeight == images[1].PixelHeight) + { + var width = images[0].PixelWidth + images[1].PixelWidth; + var height = images[1].PixelHeight; + var stride1 = images[0].PixelWidth * 4; + var stride2 = images[1].PixelWidth * 4; + var buffer1 = images[0].PixelBuffer.ToArray(); + var buffer2 = images[1].PixelBuffer.ToArray(); + + image = new WriteableBitmap(width, height); + + using (var pixelStream = image.PixelBuffer.AsStream()) + { + for (var i = 0; i < height; i++) + { + await pixelStream.WriteAsync(buffer1, i * stride1, stride1); + await pixelStream.WriteAsync(buffer2, i * stride2, stride2); + } + } + } + + return image; + } } }