From 7e18b6b984b49d16003defa84a67b62b41176f66 Mon Sep 17 00:00:00 2001 From: ClemensFischer Date: Sun, 19 May 2024 17:24:18 +0200 Subject: [PATCH] Replaced MapRect and Scale by Rect and Point --- MapControl/Shared/AzimuthalProjection.cs | 10 ++- .../Shared/EquirectangularProjection.cs | 14 ++-- MapControl/Shared/GeoImage.cs | 8 +-- MapControl/Shared/ImageLoader.cs | 19 +++-- MapControl/Shared/Intersections.cs | 20 +++--- MapControl/Shared/MapBase.cs | 20 +++--- MapControl/Shared/MapPanel.cs | 69 ++++++++++--------- MapControl/Shared/MapProjection.cs | 34 +++++---- MapControl/Shared/MapProjectionFactory.cs | 8 +++ MapControl/Shared/MapRect.cs | 43 ------------ MapControl/Shared/MapTileLayerBase.cs | 5 -- .../Shared/PolarStereographicProjection.cs | 4 +- MapControl/Shared/Scale.cs | 23 ------- MapControl/Shared/Tile.cs | 23 ++----- MapControl/Shared/TileImageLoader.cs | 6 +- MapControl/Shared/TileSource.cs | 4 +- .../Shared/TransverseMercatorProjection.cs | 4 +- MapControl/Shared/ViewRect.cs | 16 ++--- MapControl/Shared/WebMercatorProjection.cs | 4 +- MapControl/Shared/WmsImageLayer.cs | 30 ++++---- MapControl/Shared/WorldMercatorProjection.cs | 4 +- MapControl/UWP/MapControl.UWP.csproj | 9 +-- MapControl/WPF/ImageLoader.WPF.cs | 5 ++ MapControl/WPF/MapItemsImageLayer.WPF.cs | 16 ++--- MapControl/WPF/Tile.WPF.cs | 13 ++++ MapControl/WinUI/ImageLoader.WinUI.cs | 5 ++ MapControl/WinUI/Point.WinUI.cs | 6 +- MapControl/WinUI/Rect.WinUI.cs | 43 ++++++++++++ MapControl/WinUI/Tile.WinUI.cs | 13 ++++ .../Shared/WebMercatorProjection.cs | 7 +- .../Shared/WorldMercatorProjection.cs | 7 +- MapUiTools/Shared/MapProjectionsMenuButton.cs | 2 +- 32 files changed, 256 insertions(+), 238 deletions(-) delete mode 100644 MapControl/Shared/MapRect.cs delete mode 100644 MapControl/Shared/Scale.cs create mode 100644 MapControl/WinUI/Rect.WinUI.cs diff --git a/MapControl/Shared/AzimuthalProjection.cs b/MapControl/Shared/AzimuthalProjection.cs index da935a0b..6d1de9a2 100644 --- a/MapControl/Shared/AzimuthalProjection.cs +++ b/MapControl/Shared/AzimuthalProjection.cs @@ -3,6 +3,9 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; +#if !WINUI && !UWP +using System.Windows; +#endif namespace MapControl { @@ -16,12 +19,13 @@ namespace MapControl Type = MapProjectionType.Azimuthal; } - public override BoundingBox MapRectToBoundingBox(MapRect mapRect) + public override BoundingBox MapToBoundingBox(Rect rect) { - var center = MapToLocation(mapRect.Center); + var rectCenter = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d); + var center = MapToLocation(rectCenter); return center != null - ? new CenteredBoundingBox(center, mapRect.Width / Wgs84MeterPerDegree, mapRect.Height / Wgs84MeterPerDegree) + ? new CenteredBoundingBox(center, rect.Width / Wgs84MeterPerDegree, rect.Height / Wgs84MeterPerDegree) : null; } diff --git a/MapControl/Shared/EquirectangularProjection.cs b/MapControl/Shared/EquirectangularProjection.cs index 43ee09ac..770391d1 100644 --- a/MapControl/Shared/EquirectangularProjection.cs +++ b/MapControl/Shared/EquirectangularProjection.cs @@ -26,9 +26,9 @@ namespace MapControl CrsId = DefaultCrsId; } - public override Scale GetRelativeScale(Location location) + public override Point GetRelativeScale(Location location) { - return new Scale( + return new Point( 1d / Math.Cos(location.Latitude * Math.PI / 180d), 1d); } @@ -47,14 +47,14 @@ namespace MapControl point.X / Wgs84MeterPerDegree); } - public override string GetBboxValue(MapRect mapRect) + public override string GetBboxValue(Rect rect) { return string.Format(CultureInfo.InvariantCulture, CrsId == "CRS:84" ? "{0},{1},{2},{3}" : "{1},{0},{3},{2}", - mapRect.XMin / Wgs84MeterPerDegree, - mapRect.YMin / Wgs84MeterPerDegree, - mapRect.XMax / Wgs84MeterPerDegree, - mapRect.YMax / Wgs84MeterPerDegree); + rect.X / Wgs84MeterPerDegree, + rect.Y / Wgs84MeterPerDegree, + (rect.X + rect.Width) / Wgs84MeterPerDegree, + (rect.Y + rect.Height) / Wgs84MeterPerDegree); } } } diff --git a/MapControl/Shared/GeoImage.cs b/MapControl/Shared/GeoImage.cs index 7b24650d..07900e10 100644 --- a/MapControl/Shared/GeoImage.cs +++ b/MapControl/Shared/GeoImage.cs @@ -119,15 +119,15 @@ namespace MapControl var p1 = transform.Transform(new Point()); var p2 = transform.Transform(new Point(geoBitmap.Bitmap.PixelWidth, geoBitmap.Bitmap.PixelHeight)); - var mapRect = new MapRect(p1, p2); + var rect = new Rect(p1, p2); if (geoBitmap.Projection != null) { - boundingBox = geoBitmap.Projection.MapRectToBoundingBox(mapRect); + boundingBox = geoBitmap.Projection.MapToBoundingBox(rect); } else { - boundingBox = new BoundingBox(mapRect.YMin, mapRect.XMin, mapRect.YMax, mapRect.XMax); + boundingBox = new BoundingBox(rect.Y, rect.X, rect.Y + rect.Height, rect.X + rect.Width); } } @@ -181,7 +181,7 @@ namespace MapControl { int epsgCode = geoKeyDirectory[i + 3]; - projection = MapProjection.Factory.GetProjection(epsgCode) ?? + projection = MapProjectionFactory.Instance.GetProjection(epsgCode) ?? throw new ArgumentException($"Can not create projection EPSG:{epsgCode} in {sourcePath}."); break; diff --git a/MapControl/Shared/ImageLoader.cs b/MapControl/Shared/ImageLoader.cs index 74225a42..9ad54366 100644 --- a/MapControl/Shared/ImageLoader.cs +++ b/MapControl/Shared/ImageLoader.cs @@ -9,15 +9,14 @@ using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; -#if WINUI +#if AVALONIA +using ImageSource = Avalonia.Media.IImage; +#elif WINUI using Microsoft.UI.Xaml.Media; -using Microsoft.UI.Xaml.Media.Imaging; #elif UWP using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Media.Imaging; #else using System.Windows.Media; -using System.Windows.Media.Imaging; #endif namespace MapControl @@ -43,11 +42,7 @@ namespace MapControl try { - if (!uri.IsAbsoluteUri || uri.IsFile) - { - image = await LoadImageAsync(uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString); - } - else if (uri.Scheme == "http" || uri.Scheme == "https") + if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) { var response = await GetHttpResponseAsync(uri, progress); @@ -56,9 +51,13 @@ namespace MapControl image = await LoadImageAsync(response.Buffer); } } + else if (uri.IsFile || !uri.IsAbsoluteUri) + { + image = await LoadImageAsync(uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString); + } else { - image = new BitmapImage(uri); + image = LoadImage(uri); } } catch (Exception ex) diff --git a/MapControl/Shared/Intersections.cs b/MapControl/Shared/Intersections.cs index f85f5559..4d974909 100644 --- a/MapControl/Shared/Intersections.cs +++ b/MapControl/Shared/Intersections.cs @@ -3,9 +3,7 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; -#if WINUI || UWP -using Windows.Foundation; -#else +#if !WINUI && !UWP using System.Windows; #endif @@ -50,30 +48,30 @@ namespace MapControl return true; } - var topLeft = new Point(rect.Left, rect.Top); - var topRight = new Point(rect.Right, rect.Top); - var bottomLeft = new Point(rect.Left, rect.Bottom); - var bottomRight = new Point(rect.Right, rect.Bottom); + var topLeft = new Point(rect.X, rect.Y); + var topRight = new Point(rect.X + rect.Width, rect.Y); + var bottomLeft = new Point(rect.X, rect.Y + rect.Height); + var bottomRight = new Point(rect.X + rect.Width, rect.Y + rect.Height); var numIntersections = 0; - if (GetIntersection(ref p1, ref p2, topLeft, bottomLeft, p => p.X <= rect.Left)) // left edge + if (GetIntersection(ref p1, ref p2, topLeft, bottomLeft, p => p.X <= rect.X)) // left edge { numIntersections++; } - if (GetIntersection(ref p1, ref p2, topLeft, topRight, p => p.Y <= rect.Top)) // top edge + if (GetIntersection(ref p1, ref p2, topLeft, topRight, p => p.Y <= rect.Y)) // top edge { numIntersections++; } if (numIntersections < 2 && - GetIntersection(ref p1, ref p2, topRight, bottomRight, p => p.X >= rect.Right)) // right edge + GetIntersection(ref p1, ref p2, topRight, bottomRight, p => p.X >= rect.X + rect.Width)) // right edge { numIntersections++; } if (numIntersections < 2 && - GetIntersection(ref p1, ref p2, bottomLeft, bottomRight, p => p.Y >= rect.Bottom)) // bottom edge + GetIntersection(ref p1, ref p2, bottomLeft, bottomRight, p => p.Y >= rect.Y + rect.Height)) // bottom edge { numIntersections++; } diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs index 02499f67..ce8367ff 100644 --- a/MapControl/Shared/MapBase.cs +++ b/MapControl/Shared/MapBase.cs @@ -231,9 +231,11 @@ namespace MapControl /// Gets the map scale as the horizontal and vertical scaling factors from geographic /// coordinates to view coordinates at the specified location, as pixels per meter. /// - public Scale GetScale(Location location) + public Point GetScale(Location location) { - return ViewTransform.Scale * MapProjection.GetRelativeScale(location); + var relativeScale = MapProjection.GetRelativeScale(location); + + return new Point(ViewTransform.Scale * relativeScale.X, ViewTransform.Scale * relativeScale.Y); } /// @@ -286,7 +288,7 @@ namespace MapControl var x2 = Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))); var y2 = Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))); - return MapProjection.MapRectToBoundingBox(new MapRect(x1, y1, x2, y2)); + return MapProjection.MapToBoundingBox(new Rect(x1, y1, x2 - x1, y2 - y1)); } /// @@ -342,8 +344,7 @@ namespace MapControl { SetTransformCenter(center); - viewCenter.X += translation.X; - viewCenter.Y += translation.Y; + viewCenter = new Point(viewCenter.X + translation.X, viewCenter.Y + translation.Y); if (rotation != 0d) { @@ -392,15 +393,16 @@ namespace MapControl /// public void ZoomToBounds(BoundingBox boundingBox) { - var mapRect = MapProjection.BoundingBoxToMapRect(boundingBox); + var rect = MapProjection.BoundingBoxToMap(boundingBox); - if (mapRect != null) + if (rect.HasValue) { - var targetCenter = MapProjection.MapToLocation(mapRect.Center); + var rectCenter = new Point(rect.Value.X + rect.Value.Width / 2d, rect.Value.Y + rect.Value.Height / 2d); + var targetCenter = MapProjection.MapToLocation(rectCenter); if (targetCenter != null) { - var scale = Math.Min(RenderSize.Width / mapRect.Width, RenderSize.Height / mapRect.Height); + var scale = Math.Min(RenderSize.Width / rect.Value.Width, RenderSize.Height / rect.Value.Height); TargetZoomLevel = ViewTransform.ScaleToZoomLevel(scale); TargetCenter = targetCenter; diff --git a/MapControl/Shared/MapPanel.cs b/MapControl/Shared/MapPanel.cs index 5d22fd78..f68e6074 100644 --- a/MapControl/Shared/MapPanel.cs +++ b/MapControl/Shared/MapPanel.cs @@ -183,9 +183,9 @@ namespace MapControl { var viewRect = GetViewRect(boundingBox); - if (viewRect != null) + if (viewRect.HasValue) { - ArrangeElement(element, viewRect); + ArrangeElement(element, viewRect.Value); } } else @@ -214,21 +214,27 @@ namespace MapControl return position; } - protected ViewRect GetViewRect(BoundingBox boundingBox) + protected ViewRect? GetViewRect(BoundingBox boundingBox) { - var mapRect = parentMap.MapProjection.BoundingBoxToMapRect(boundingBox); + var rect = parentMap.MapProjection.BoundingBoxToMap(boundingBox); - return mapRect != null ? GetViewRect(mapRect) : null; + if (!rect.HasValue) + { + return null; + } + + return GetViewRect(rect.Value); } - protected ViewRect GetViewRect(MapRect mapRect) + protected ViewRect GetViewRect(Rect rect) { - var position = parentMap.ViewTransform.MapToView(mapRect.Center); + var rectCenter = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d); + var position = parentMap.ViewTransform.MapToView(rectCenter); if (parentMap.MapProjection.Type <= MapProjectionType.NormalCylindrical && IsOutsideViewport(position)) { - var location = parentMap.MapProjection.MapToLocation(mapRect.Center); + var location = parentMap.MapProjection.MapToLocation(rectCenter); if (location != null) { @@ -242,8 +248,8 @@ namespace MapControl } } - var width = mapRect.Width * parentMap.ViewTransform.Scale; - var height = mapRect.Height * parentMap.ViewTransform.Scale; + var width = rect.Width * parentMap.ViewTransform.Scale; + var height = rect.Height * parentMap.ViewTransform.Scale; var x = position.X - width / 2d; var y = position.Y - height / 2d; @@ -259,16 +265,17 @@ namespace MapControl private static void ArrangeElement(FrameworkElement element, Point position) { var size = GetDesiredSize(element); - var rect = new Rect(position.X, position.Y, size.Width, size.Height); + var x = position.X; + var y = position.Y; switch (element.HorizontalAlignment) { case HorizontalAlignment.Center: - rect.X -= rect.Width / 2d; + x -= size.Width / 2d; break; case HorizontalAlignment.Right: - rect.X -= rect.Width; + x -= size.Width; break; default: @@ -278,37 +285,40 @@ namespace MapControl switch (element.VerticalAlignment) { case VerticalAlignment.Center: - rect.Y -= rect.Height / 2d; + y -= size.Height / 2d; break; case VerticalAlignment.Bottom: - rect.Y -= rect.Height; + y -= size.Height; break; default: break; } - ArrangeElement(element, rect); + ArrangeElement(element, new Rect(x, y, size.Width, size.Height)); } private static void ArrangeElement(FrameworkElement element, Size parentSize) { var size = GetDesiredSize(element); - var rect = new Rect(0d, 0d, size.Width, size.Height); + var x = 0d; + var y = 0d; + var width = size.Width; + var height = size.Height; switch (element.HorizontalAlignment) { case HorizontalAlignment.Center: - rect.X = (parentSize.Width - rect.Width) / 2d; + x = (parentSize.Width - size.Width) / 2d; break; case HorizontalAlignment.Right: - rect.X = parentSize.Width - rect.Width; + x = parentSize.Width - size.Width; break; case HorizontalAlignment.Stretch: - rect.Width = parentSize.Width; + width = parentSize.Width; break; default: @@ -318,30 +328,30 @@ namespace MapControl switch (element.VerticalAlignment) { case VerticalAlignment.Center: - rect.Y = (parentSize.Height - rect.Height) / 2d; + y = (parentSize.Height - size.Height) / 2d; break; case VerticalAlignment.Bottom: - rect.Y = parentSize.Height - rect.Height; + y = parentSize.Height - size.Height; break; case VerticalAlignment.Stretch: - rect.Height = parentSize.Height; + height = parentSize.Height; break; default: break; } - ArrangeElement(element, rect); + ArrangeElement(element, new Rect(x, y, width, height)); } private static void ArrangeElement(FrameworkElement element, ViewRect rect) { - element.Width = rect.Width; - element.Height = rect.Height; + element.Width = rect.Rect.Width; + element.Height = rect.Rect.Height; - ArrangeElement(element, new Rect(rect.X, rect.Y, rect.Width, rect.Height)); + ArrangeElement(element, rect.Rect); if (element.RenderTransform is RotateTransform rotateTransform) { @@ -359,10 +369,7 @@ namespace MapControl { if (element.UseLayoutRounding) { - rect.X = Math.Round(rect.X); - rect.Y = Math.Round(rect.Y); - rect.Width = Math.Round(rect.Width); - rect.Height = Math.Round(rect.Height); + rect = new Rect(Math.Round(rect.X), Math.Round(rect.Y), Math.Round(rect.Width), Math.Round(rect.Height)); } element.Arrange(rect); diff --git a/MapControl/Shared/MapProjection.cs b/MapControl/Shared/MapProjection.cs index 840c070e..80f8f94d 100644 --- a/MapControl/Shared/MapProjection.cs +++ b/MapControl/Shared/MapProjection.cs @@ -29,8 +29,6 @@ namespace MapControl public const double Wgs84Flattening = 1d / 298.257223563; public static readonly double Wgs84Eccentricity = Math.Sqrt((2d - Wgs84Flattening) * Wgs84Flattening); - public static MapProjectionFactory Factory { get; set; } = new MapProjectionFactory(); - /// /// Gets the type of the projection. /// @@ -49,7 +47,7 @@ namespace MapControl /// /// Gets the relative map scale at the specified Location. /// - public virtual Scale GetRelativeScale(Location location) => new Scale(1d, 1d); + public virtual Point GetRelativeScale(Location location) => new Point(1d, 1d); /// /// Transforms a Location in geographic coordinates to a Point in projected map coordinates. @@ -64,12 +62,12 @@ namespace MapControl public abstract Location MapToLocation(Point point); /// - /// Transforms a BoundingBox in geographic coordinates to a MapRect in projected map coordinates. + /// Transforms a BoundingBox in geographic coordinates to a Rect in projected map coordinates. /// Returns null when the BoundingBox can not be transformed. /// - public virtual MapRect BoundingBoxToMapRect(BoundingBox boundingBox) + public virtual Rect? BoundingBoxToMap(BoundingBox boundingBox) { - MapRect mapRect = null; + Rect? rect = null; if (boundingBox.South < boundingBox.North && boundingBox.West < boundingBox.East) { @@ -78,7 +76,7 @@ namespace MapControl if (p1.HasValue && p2.HasValue) { - mapRect = new MapRect(p1.Value, p2.Value); + rect = new Rect(p1.Value, p2.Value); } } else if (boundingBox.Center != null) @@ -94,21 +92,21 @@ namespace MapControl var x = center.Value.X - width / 2d; var y = center.Value.Y - height / 2d; - mapRect = new MapRect(x, y, x + width, y + height); + rect = new Rect(x, y, width, height); } } - return mapRect; + return rect; } /// - /// Transforms a MapRect in projected map coordinates to a BoundingBox in geographic coordinates. + /// Transforms a Rect in projected map coordinates to a BoundingBox in geographic coordinates. /// Returns null when the MapRect can not be transformed. /// - public virtual BoundingBox MapRectToBoundingBox(MapRect mapRect) + public virtual BoundingBox MapToBoundingBox(Rect rect) { - var southWest = MapToLocation(new Point(mapRect.XMin, mapRect.YMin)); - var northEast = MapToLocation(new Point(mapRect.XMax, mapRect.YMax)); + var southWest = MapToLocation(new Point(rect.X, rect.Y)); + var northEast = MapToLocation(new Point(rect.X + rect.Width, rect.Y + rect.Height)); return southWest != null && northEast != null ? new BoundingBox(southWest, northEast) @@ -128,16 +126,16 @@ namespace MapControl /// /// Gets the BBOX parameter value for a WMS GetMap request. /// - public virtual string GetBboxValue(MapRect mapRect) + public virtual string GetBboxValue(Rect rect) { // Truncate values for seamless stitching of two WMS images at 180° longitude, // as done in WmsImageLayer.GetImageAsync. // return string.Format(CultureInfo.InvariantCulture, "{0:F2},{1:F2},{2:F2},{3:F2}", - 0.01 * Math.Ceiling(100d * mapRect.XMin), - 0.01 * Math.Ceiling(100d * mapRect.YMin), - 0.01 * Math.Floor(100d * mapRect.XMax), - 0.01 * Math.Floor(100d * mapRect.YMax)); + 0.01 * Math.Ceiling(100d * rect.X), + 0.01 * Math.Ceiling(100d * rect.Y), + 0.01 * Math.Floor(100d * (rect.X + rect.Width)), + 0.01 * Math.Floor(100d * (rect.Y + rect.Height))); } } } diff --git a/MapControl/Shared/MapProjectionFactory.cs b/MapControl/Shared/MapProjectionFactory.cs index 0d83bc2f..417d40a3 100644 --- a/MapControl/Shared/MapProjectionFactory.cs +++ b/MapControl/Shared/MapProjectionFactory.cs @@ -6,6 +6,14 @@ namespace MapControl { public class MapProjectionFactory { + private static MapProjectionFactory instance; + + public static MapProjectionFactory Instance + { + get => instance ?? (instance = new MapProjectionFactory()); + set => instance = value; + } + public virtual MapProjection GetProjection(int epsgCode) { MapProjection projection = null; diff --git a/MapControl/Shared/MapRect.cs b/MapControl/Shared/MapRect.cs deleted file mode 100644 index 109a713b..00000000 --- a/MapControl/Shared/MapRect.cs +++ /dev/null @@ -1,43 +0,0 @@ -// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control -// Copyright © 2024 Clemens Fischer -// Licensed under the Microsoft Public License (Ms-PL) - -using System; -#if !WINUI && !UWP -using System.Windows; -#endif - -namespace MapControl -{ - /// - /// Map rectangle with double floating point precision, in contrast to Windows.Foundation.Rect. - /// Used by MapProjection when converting geographic bounding boxes to/from projected map coordinates. - /// - public class MapRect - { - public MapRect(double x1, double y1, double x2, double y2) - { - XMin = Math.Min(x1, x2); - YMin = Math.Min(y1, y2); - XMax = Math.Max(x1, x2); - YMax = Math.Max(y1, y2); - } - - public MapRect(Point point1, Point point2) - : this(point1.X, point1.Y, point2.X, point2.Y) - { - } - - public double XMin { get; } - public double YMin { get; } - public double XMax { get; } - public double YMax { get; } - - public double Width => XMax - XMin; - public double Height => YMax - YMin; - - public Point Center => new Point((XMin + XMax) / 2d, (YMin + YMax) / 2d); - - public bool Contains(Point p) => p.X >= XMin && p.X <= XMax && p.Y >= YMin && p.Y <= YMax; - } -} diff --git a/MapControl/Shared/MapTileLayerBase.cs b/MapControl/Shared/MapTileLayerBase.cs index d703213d..9f39d500 100644 --- a/MapControl/Shared/MapTileLayerBase.cs +++ b/MapControl/Shared/MapTileLayerBase.cs @@ -23,11 +23,6 @@ using System.Windows.Threading; namespace MapControl { - public interface ITileImageLoader - { - Task LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress); - } - public abstract class MapTileLayerBase : Panel, IMapLayer { public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register( diff --git a/MapControl/Shared/PolarStereographicProjection.cs b/MapControl/Shared/PolarStereographicProjection.cs index 449bd222..10db0bde 100644 --- a/MapControl/Shared/PolarStereographicProjection.cs +++ b/MapControl/Shared/PolarStereographicProjection.cs @@ -28,7 +28,7 @@ namespace MapControl public double FalseNorthing { get; set; } = 2e6; public bool IsNorth { get; set; } - public override Scale GetRelativeScale(Location location) + public override Point GetRelativeScale(Location location) { var lat = location.Latitude * Math.PI / 180d; @@ -48,7 +48,7 @@ namespace MapControl var m = Math.Cos(lat) / Math.Sqrt(1d - eSinLat * eSinLat); // p.160 (14-15) var k = r / (EquatorialRadius * m); // p.161 (21-32) - return new Scale(k, k); + return new Point(k, k); } public override Point? LocationToMap(Location location) diff --git a/MapControl/Shared/Scale.cs b/MapControl/Shared/Scale.cs deleted file mode 100644 index 146b1eed..00000000 --- a/MapControl/Shared/Scale.cs +++ /dev/null @@ -1,23 +0,0 @@ -// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control -// Copyright © 2024 Clemens Fischer -// Licensed under the Microsoft Public License (Ms-PL) - -namespace MapControl -{ - public readonly struct Scale - { - public Scale(double x, double y) - { - X = x; - Y = y; - } - - public double X { get; } - public double Y { get; } - - public static Scale operator *(double f, Scale v) - { - return new Scale(f * v.X, f * v.Y); - } - } -} diff --git a/MapControl/Shared/Tile.cs b/MapControl/Shared/Tile.cs index ec747937..35b3a5ef 100644 --- a/MapControl/Shared/Tile.cs +++ b/MapControl/Shared/Tile.cs @@ -3,21 +3,19 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; -#if WINUI -using Microsoft.UI.Xaml; +#if AVALONIA +using Avalonia.Controls; +using Avalonia.Media; +using ImageSource = Avalonia.Media.IImage; +#elif WINUI using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; -using Microsoft.UI.Xaml.Media.Animation; #elif UWP -using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Media.Animation; #else -using System.Windows; using System.Windows.Controls; using System.Windows.Media; -using System.Windows.Media.Animation; #endif namespace MapControl @@ -56,16 +54,5 @@ namespace MapControl AnimateImageOpacity(); } } - - private void BeginOpacityAnimation() - { - Image.BeginAnimation(UIElement.OpacityProperty, - new DoubleAnimation - { - From = 0d, - Duration = MapBase.ImageFadeDuration, - FillBehavior = FillBehavior.Stop - }); - } } } diff --git a/MapControl/Shared/TileImageLoader.cs b/MapControl/Shared/TileImageLoader.cs index 0d2735c5..9aee0683 100644 --- a/MapControl/Shared/TileImageLoader.cs +++ b/MapControl/Shared/TileImageLoader.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -19,6 +18,11 @@ namespace MapControl /// /// Loads and optionally caches map tile images for a MapTileLayer. /// + public interface ITileImageLoader + { + Task LoadTilesAsync(IEnumerable tiles, TileSource tileSource, string cacheName, IProgress progress); + } + public partial class TileImageLoader : ITileImageLoader { private class TileQueue : ConcurrentStack diff --git a/MapControl/Shared/TileSource.cs b/MapControl/Shared/TileSource.cs index dcfdd81c..84c6fb75 100644 --- a/MapControl/Shared/TileSource.cs +++ b/MapControl/Shared/TileSource.cs @@ -4,7 +4,9 @@ using System; using System.Threading.Tasks; -#if WINUI +#if AVALONIA +using ImageSource = Avalonia.Media.IImage; +#elif WINUI using Microsoft.UI.Xaml.Media; #elif UWP using Windows.UI.Xaml.Media; diff --git a/MapControl/Shared/TransverseMercatorProjection.cs b/MapControl/Shared/TransverseMercatorProjection.cs index 34ab106c..61b821dd 100644 --- a/MapControl/Shared/TransverseMercatorProjection.cs +++ b/MapControl/Shared/TransverseMercatorProjection.cs @@ -29,7 +29,7 @@ namespace MapControl public double FalseEasting { get; set; } public double FalseNorthing { get; set; } - public override Scale GetRelativeScale(Location location) + public override Point GetRelativeScale(Location location) { var k = ScaleFactor; @@ -58,7 +58,7 @@ namespace MapControl + (61d - 148d * T + 16d * T_2) * A_6 / 720d); // p.61 (8-11) } - return new Scale(k, k); + return new Point(k, k); } public override Point? LocationToMap(Location location) diff --git a/MapControl/Shared/ViewRect.cs b/MapControl/Shared/ViewRect.cs index 73650191..d60341cb 100644 --- a/MapControl/Shared/ViewRect.cs +++ b/MapControl/Shared/ViewRect.cs @@ -2,26 +2,24 @@ // Copyright © 2024 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) +#if !WINUI && !UWP +using System.Windows; +#endif + namespace MapControl { /// /// Rotated rectangle used to arrange and rotate an element with a BoundingBox. /// - public class ViewRect + public readonly struct ViewRect { public ViewRect(double x, double y, double width, double height, double rotation) { - X = x; - Y = y; - Width = width; - Height = height; + Rect = new Rect(x, y, width, height); Rotation = rotation; } - public double X { get; } - public double Y { get; } - public double Width { get; } - public double Height { get; } + public Rect Rect { get; } public double Rotation { get; } } } diff --git a/MapControl/Shared/WebMercatorProjection.cs b/MapControl/Shared/WebMercatorProjection.cs index a2721c69..90602a9f 100644 --- a/MapControl/Shared/WebMercatorProjection.cs +++ b/MapControl/Shared/WebMercatorProjection.cs @@ -24,11 +24,11 @@ namespace MapControl CrsId = DefaultCrsId; } - public override Scale GetRelativeScale(Location location) + public override Point GetRelativeScale(Location location) { var k = 1d / Math.Cos(location.Latitude * Math.PI / 180d); // p.44 (7-3) - return new Scale(k, k); + return new Point(k, k); } public override Point? LocationToMap(Location location) diff --git a/MapControl/Shared/WmsImageLayer.cs b/MapControl/Shared/WmsImageLayer.cs index aa435d2f..56096ab9 100644 --- a/MapControl/Shared/WmsImageLayer.cs +++ b/MapControl/Shared/WmsImageLayer.cs @@ -239,9 +239,9 @@ namespace MapControl /// protected virtual string GetMapRequestUri(BoundingBox boundingBox) { - var mapRect = ParentMap.MapProjection.BoundingBoxToMapRect(boundingBox); + var rect = ParentMap.MapProjection.BoundingBoxToMap(boundingBox); - if (mapRect == null) + if (!rect.HasValue) { return null; } @@ -257,9 +257,9 @@ namespace MapControl { "STYLES", Styles ?? "" }, { "FORMAT", "image/png" }, { "CRS", GetCrsValue() }, - { "BBOX", GetBboxValue(mapRect) }, - { "WIDTH", Math.Round(viewScale * mapRect.Width).ToString("F0") }, - { "HEIGHT", Math.Round(viewScale * mapRect.Height).ToString("F0") } + { "BBOX", GetBboxValue(rect.Value) }, + { "WIDTH", Math.Round(viewScale * rect.Value.Width).ToString("F0") }, + { "HEIGHT", Math.Round(viewScale * rect.Value.Height).ToString("F0") } }); } @@ -270,18 +270,18 @@ namespace MapControl { var viewSize = ParentMap.RenderSize; var boundingBox = ParentMap.ViewRectToBoundingBox(new Rect(0d, 0d, viewSize.Width, viewSize.Height)); - var mapRect = ParentMap.MapProjection.BoundingBoxToMapRect(boundingBox); + var rect = ParentMap.MapProjection.BoundingBoxToMap(boundingBox); - if (mapRect == null) + if (!rect.HasValue) { return null; } - var viewRect = GetViewRect(mapRect); + var viewRect = GetViewRect(rect.Value); - var transform = new Matrix(1, 0, 0, 1, -viewSize.Width / 2, -viewSize.Height / 2); + var transform = new Matrix(1d, 0d, 0d, 1d, -viewSize.Width / 2d, -viewSize.Height / 2d); transform.Rotate(-viewRect.Rotation); - transform.Translate(viewRect.Width / 2, viewRect.Height / 2); + transform.Translate(viewRect.Rect.Width / 2d, viewRect.Rect.Height / 2d); var imagePos = transform.Transform(position); @@ -295,9 +295,9 @@ namespace MapControl { "FORMAT", "image/png" }, { "INFO_FORMAT", format }, { "CRS", GetCrsValue() }, - { "BBOX", GetBboxValue(mapRect) }, - { "WIDTH", Math.Round(viewRect.Width).ToString("F0") }, - { "HEIGHT", Math.Round(viewRect.Height).ToString("F0") }, + { "BBOX", GetBboxValue(rect.Value) }, + { "WIDTH", Math.Round(viewRect.Rect.Width).ToString("F0") }, + { "HEIGHT", Math.Round(viewRect.Rect.Height).ToString("F0") }, { "I", Math.Round(imagePos.X).ToString("F0") }, { "J", Math.Round(imagePos.Y).ToString("F0") } }; @@ -310,9 +310,9 @@ namespace MapControl return ParentMap.MapProjection.GetCrsValue(); } - protected virtual string GetBboxValue(MapRect mapRect) + protected virtual string GetBboxValue(Rect rect) { - return ParentMap.MapProjection.GetBboxValue(mapRect); + return ParentMap.MapProjection.GetBboxValue(rect); } protected string GetRequestUri(IDictionary queryParameters) diff --git a/MapControl/Shared/WorldMercatorProjection.cs b/MapControl/Shared/WorldMercatorProjection.cs index d4b6fc92..1aefac77 100644 --- a/MapControl/Shared/WorldMercatorProjection.cs +++ b/MapControl/Shared/WorldMercatorProjection.cs @@ -24,13 +24,13 @@ namespace MapControl CrsId = DefaultCrsId; } - public override Scale GetRelativeScale(Location location) + public override Point GetRelativeScale(Location location) { var lat = location.Latitude * Math.PI / 180d; var eSinLat = Wgs84Eccentricity * Math.Sin(lat); var k = Math.Sqrt(1d - eSinLat * eSinLat) / Math.Cos(lat); // p.44 (7-8) - return new Scale(k, k); + return new Point(k, k); } public override Point? LocationToMap(Location location) diff --git a/MapControl/UWP/MapControl.UWP.csproj b/MapControl/UWP/MapControl.UWP.csproj index 1b33b130..bec8a391 100644 --- a/MapControl/UWP/MapControl.UWP.csproj +++ b/MapControl/UWP/MapControl.UWP.csproj @@ -140,9 +140,6 @@ MapProjectionFactory.cs - - MapRect.cs - MapScale.cs @@ -164,9 +161,6 @@ PushpinBorder.cs - - Scale.cs - StereographicProjection.cs @@ -272,6 +266,9 @@ PushpinBorder.WinUI.cs + + Rect.WinUI.cs + Tile.WinUI.cs diff --git a/MapControl/WPF/ImageLoader.WPF.cs b/MapControl/WPF/ImageLoader.WPF.cs index dfa395f5..851b0491 100644 --- a/MapControl/WPF/ImageLoader.WPF.cs +++ b/MapControl/WPF/ImageLoader.WPF.cs @@ -13,6 +13,11 @@ namespace MapControl { public static partial class ImageLoader { + public static ImageSource LoadImage(Uri uri) + { + return new BitmapImage(uri); + } + public static ImageSource LoadImage(Stream stream) { var image = new BitmapImage(); diff --git a/MapControl/WPF/MapItemsImageLayer.WPF.cs b/MapControl/WPF/MapItemsImageLayer.WPF.cs index e28ab40b..1bb76ba1 100644 --- a/MapControl/WPF/MapItemsImageLayer.WPF.cs +++ b/MapControl/WPF/MapItemsImageLayer.WPF.cs @@ -37,18 +37,18 @@ namespace MapControl if (projection != null && items != null) { - var mapRect = projection.BoundingBoxToMapRect(boundingBox); + var rect = projection.BoundingBoxToMap(boundingBox); - if (mapRect != null) + if (rect.HasValue) { - image = await Task.Run(() => GetImage(projection, mapRect, items)); + image = await Task.Run(() => GetImage(projection, rect.Value, items)); } } return image; } - private DrawingImage GetImage(MapProjection projection, MapRect mapRect, IEnumerable items) + private DrawingImage GetImage(MapProjection projection, Rect rect, IEnumerable items) { var scale = ParentMap.ViewTransform.Scale; var rotation = ParentMap.ViewTransform.Rotation; @@ -62,13 +62,13 @@ namespace MapControl .Select(point => point.Value) .ToList(); - if (points.Any(point => mapRect.Contains(point))) + if (points.Any(point => rect.Contains(point))) { for (int i = 0; i < points.Count; i++) { points[i] = new Point( - scale * (points[i].X - mapRect.XMin), - scale * (mapRect.YMax - points[i].Y)); + scale * (points[i].X - rect.X), + scale * ((rect.Y + rect.Height) - points[i].Y)); } drawings.Children.Add(item.GetDrawing(points, scale, rotation)); @@ -79,7 +79,7 @@ namespace MapControl { Drawing = drawings, ViewboxUnits = BrushMappingMode.Absolute, - Viewbox = new Rect(0, 0, scale * mapRect.Width, scale * mapRect.Height), + Viewbox = new Rect(0, 0, scale * rect.Width, scale * rect.Height), }; var drawing = new GeometryDrawing( diff --git a/MapControl/WPF/Tile.WPF.cs b/MapControl/WPF/Tile.WPF.cs index b3a0d03e..f99bc295 100644 --- a/MapControl/WPF/Tile.WPF.cs +++ b/MapControl/WPF/Tile.WPF.cs @@ -3,14 +3,27 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; +using System.Windows; using System.Windows.Controls; using System.Windows.Media; +using System.Windows.Media.Animation; using System.Windows.Media.Imaging; namespace MapControl { public partial class Tile { + private void BeginOpacityAnimation() + { + Image.BeginAnimation(UIElement.OpacityProperty, + new DoubleAnimation + { + From = 0d, + Duration = MapBase.ImageFadeDuration, + FillBehavior = FillBehavior.Stop + }); + } + private void AnimateImageOpacity() { if (Image.Source is BitmapSource bitmap && bitmap.IsDownloading && !bitmap.IsFrozen) diff --git a/MapControl/WinUI/ImageLoader.WinUI.cs b/MapControl/WinUI/ImageLoader.WinUI.cs index b7013ce7..9f3c3467 100644 --- a/MapControl/WinUI/ImageLoader.WinUI.cs +++ b/MapControl/WinUI/ImageLoader.WinUI.cs @@ -21,6 +21,11 @@ namespace MapControl { public static partial class ImageLoader { + public static ImageSource LoadImage(Uri uri) + { + return new BitmapImage(uri); + } + public static async Task LoadImageAsync(BitmapDecoder decoder) { var image = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight); diff --git a/MapControl/WinUI/Point.WinUI.cs b/MapControl/WinUI/Point.WinUI.cs index c1e30edf..3b758d62 100644 --- a/MapControl/WinUI/Point.WinUI.cs +++ b/MapControl/WinUI/Point.WinUI.cs @@ -7,7 +7,7 @@ namespace MapControl /// /// Replaces Windows.Foundation.Point for double floating point precision. /// - public struct Point + public readonly struct Point { public Point(double x, double y) { @@ -15,8 +15,8 @@ namespace MapControl Y = y; } - public double X { get; set; } - public double Y { get; set; } + public double X { get; } + public double Y { get; } public static implicit operator Windows.Foundation.Point(Point p) { diff --git a/MapControl/WinUI/Rect.WinUI.cs b/MapControl/WinUI/Rect.WinUI.cs new file mode 100644 index 00000000..7399024a --- /dev/null +++ b/MapControl/WinUI/Rect.WinUI.cs @@ -0,0 +1,43 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// Copyright © 2024 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +namespace MapControl +{ + /// + /// Replaces Windows.Foundation.Rect for double floating point precision. + /// + public readonly struct Rect + { + public Rect(double x, double y, double width, double height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + + + public Rect(Point p1, Point p2) + : this(p1.X, p1.Y, p2.X - p1.X, p2.Y - p1.Y) + { + } + + public double X { get; } + public double Y { get; } + public double Width { get; } + public double Height { get; } + + public bool Contains(Point p) => p.X >= X && p.X <= X + Width && p.Y >= Y && p.Y <= Y + Height; + + public static implicit operator Windows.Foundation.Rect(Rect r) + { + return new Windows.Foundation.Rect(r.X, r.Y, r.Width, r.Height); + } + + public static implicit operator Rect(Windows.Foundation.Rect r) + { + return new Rect(r.X, r.Y, r.Width, r.Height); + } + } +} diff --git a/MapControl/WinUI/Tile.WinUI.cs b/MapControl/WinUI/Tile.WinUI.cs index e96bc147..71202b61 100644 --- a/MapControl/WinUI/Tile.WinUI.cs +++ b/MapControl/WinUI/Tile.WinUI.cs @@ -4,9 +4,11 @@ #if WINUI using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media.Animation; using Microsoft.UI.Xaml.Media.Imaging; #else using Windows.UI.Xaml; +using Windows.UI.Xaml.Media.Animation; using Windows.UI.Xaml.Media.Imaging; #endif @@ -14,6 +16,17 @@ namespace MapControl { public partial class Tile { + private void BeginOpacityAnimation() + { + Image.BeginAnimation(UIElement.OpacityProperty, + new DoubleAnimation + { + From = 0d, + Duration = MapBase.ImageFadeDuration, + FillBehavior = FillBehavior.Stop + }); + } + private void AnimateImageOpacity() { if (Image.Source is BitmapImage bitmap && bitmap.UriSource != null) diff --git a/MapProjections/Shared/WebMercatorProjection.cs b/MapProjections/Shared/WebMercatorProjection.cs index 0b123cbd..dbcba0d0 100644 --- a/MapProjections/Shared/WebMercatorProjection.cs +++ b/MapProjections/Shared/WebMercatorProjection.cs @@ -4,6 +4,9 @@ using ProjNet.CoordinateSystems; using System; +#if !WINUI && !UWP +using System.Windows; +#endif namespace MapControl.Projections { @@ -20,11 +23,11 @@ namespace MapControl.Projections CoordinateSystem = ProjectedCoordinateSystem.WebMercator; } - public override Scale GetRelativeScale(Location location) + public override Point GetRelativeScale(Location location) { var k = 1d / Math.Cos(location.Latitude * Math.PI / 180d); // p.44 (7-3) - return new Scale(k, k); + return new Point(k, k); } } } diff --git a/MapProjections/Shared/WorldMercatorProjection.cs b/MapProjections/Shared/WorldMercatorProjection.cs index fa9dc787..69d39a12 100644 --- a/MapProjections/Shared/WorldMercatorProjection.cs +++ b/MapProjections/Shared/WorldMercatorProjection.cs @@ -3,6 +3,9 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; +#if !WINUI && !UWP +using System.Windows; +#endif namespace MapControl.Projections { @@ -41,13 +44,13 @@ namespace MapControl.Projections + "AUTHORITY[\"EPSG\",\"3395\"]]"; } - public override Scale GetRelativeScale(Location location) + public override Point GetRelativeScale(Location location) { var lat = location.Latitude * Math.PI / 180d; var eSinLat = Wgs84Eccentricity * Math.Sin(lat); var k = Math.Sqrt(1d - eSinLat * eSinLat) / Math.Cos(lat); // p.44 (7-8) - return new Scale(k, k); + return new Point(k, k); } } } diff --git a/MapUiTools/Shared/MapProjectionsMenuButton.cs b/MapUiTools/Shared/MapProjectionsMenuButton.cs index e3b14c56..87c37e10 100644 --- a/MapUiTools/Shared/MapProjectionsMenuButton.cs +++ b/MapUiTools/Shared/MapProjectionsMenuButton.cs @@ -89,7 +89,7 @@ namespace MapControl.UiTools if (selectedProjection != projection) { selectedProjection = projection; - Map.MapProjection = MapProjection.Factory.GetProjection(selectedProjection); + Map.MapProjection = MapProjectionFactory.Instance.GetProjection(selectedProjection); } UpdateCheckedStates();