Replaced MapRect and Scale by Rect and Point

This commit is contained in:
ClemensFischer 2024-05-19 17:24:18 +02:00
parent dd62545b41
commit 7e18b6b984
32 changed files with 256 additions and 238 deletions

View file

@ -3,6 +3,9 @@
// Licensed under the Microsoft Public License (Ms-PL) // Licensed under the Microsoft Public License (Ms-PL)
using System; using System;
#if !WINUI && !UWP
using System.Windows;
#endif
namespace MapControl namespace MapControl
{ {
@ -16,12 +19,13 @@ namespace MapControl
Type = MapProjectionType.Azimuthal; 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 return center != null
? new CenteredBoundingBox(center, mapRect.Width / Wgs84MeterPerDegree, mapRect.Height / Wgs84MeterPerDegree) ? new CenteredBoundingBox(center, rect.Width / Wgs84MeterPerDegree, rect.Height / Wgs84MeterPerDegree)
: null; : null;
} }

View file

@ -26,9 +26,9 @@ namespace MapControl
CrsId = DefaultCrsId; 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 / Math.Cos(location.Latitude * Math.PI / 180d),
1d); 1d);
} }
@ -47,14 +47,14 @@ namespace MapControl
point.X / Wgs84MeterPerDegree); point.X / Wgs84MeterPerDegree);
} }
public override string GetBboxValue(MapRect mapRect) public override string GetBboxValue(Rect rect)
{ {
return string.Format(CultureInfo.InvariantCulture, return string.Format(CultureInfo.InvariantCulture,
CrsId == "CRS:84" ? "{0},{1},{2},{3}" : "{1},{0},{3},{2}", CrsId == "CRS:84" ? "{0},{1},{2},{3}" : "{1},{0},{3},{2}",
mapRect.XMin / Wgs84MeterPerDegree, rect.X / Wgs84MeterPerDegree,
mapRect.YMin / Wgs84MeterPerDegree, rect.Y / Wgs84MeterPerDegree,
mapRect.XMax / Wgs84MeterPerDegree, (rect.X + rect.Width) / Wgs84MeterPerDegree,
mapRect.YMax / Wgs84MeterPerDegree); (rect.Y + rect.Height) / Wgs84MeterPerDegree);
} }
} }
} }

View file

@ -119,15 +119,15 @@ namespace MapControl
var p1 = transform.Transform(new Point()); var p1 = transform.Transform(new Point());
var p2 = transform.Transform(new Point(geoBitmap.Bitmap.PixelWidth, geoBitmap.Bitmap.PixelHeight)); 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) if (geoBitmap.Projection != null)
{ {
boundingBox = geoBitmap.Projection.MapRectToBoundingBox(mapRect); boundingBox = geoBitmap.Projection.MapToBoundingBox(rect);
} }
else 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]; 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}."); throw new ArgumentException($"Can not create projection EPSG:{epsgCode} in {sourcePath}.");
break; break;

View file

@ -9,15 +9,14 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; 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;
using Microsoft.UI.Xaml.Media.Imaging;
#elif UWP #elif UWP
using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
#else #else
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging;
#endif #endif
namespace MapControl namespace MapControl
@ -43,11 +42,7 @@ namespace MapControl
try try
{ {
if (!uri.IsAbsoluteUri || uri.IsFile) if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
{
image = await LoadImageAsync(uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString);
}
else if (uri.Scheme == "http" || uri.Scheme == "https")
{ {
var response = await GetHttpResponseAsync(uri, progress); var response = await GetHttpResponseAsync(uri, progress);
@ -56,9 +51,13 @@ namespace MapControl
image = await LoadImageAsync(response.Buffer); image = await LoadImageAsync(response.Buffer);
} }
} }
else if (uri.IsFile || !uri.IsAbsoluteUri)
{
image = await LoadImageAsync(uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString);
}
else else
{ {
image = new BitmapImage(uri); image = LoadImage(uri);
} }
} }
catch (Exception ex) catch (Exception ex)

View file

@ -3,9 +3,7 @@
// Licensed under the Microsoft Public License (Ms-PL) // Licensed under the Microsoft Public License (Ms-PL)
using System; using System;
#if WINUI || UWP #if !WINUI && !UWP
using Windows.Foundation;
#else
using System.Windows; using System.Windows;
#endif #endif
@ -50,30 +48,30 @@ namespace MapControl
return true; return true;
} }
var topLeft = new Point(rect.Left, rect.Top); var topLeft = new Point(rect.X, rect.Y);
var topRight = new Point(rect.Right, rect.Top); var topRight = new Point(rect.X + rect.Width, rect.Y);
var bottomLeft = new Point(rect.Left, rect.Bottom); var bottomLeft = new Point(rect.X, rect.Y + rect.Height);
var bottomRight = new Point(rect.Right, rect.Bottom); var bottomRight = new Point(rect.X + rect.Width, rect.Y + rect.Height);
var numIntersections = 0; 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++; 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++; numIntersections++;
} }
if (numIntersections < 2 && 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++; numIntersections++;
} }
if (numIntersections < 2 && 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++; numIntersections++;
} }

View file

@ -231,9 +231,11 @@ namespace MapControl
/// Gets the map scale as the horizontal and vertical scaling factors from geographic /// 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. /// coordinates to view coordinates at the specified location, as pixels per meter.
/// </summary> /// </summary>
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);
} }
/// <summary> /// <summary>
@ -286,7 +288,7 @@ namespace MapControl
var x2 = Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))); 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))); 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));
} }
/// <summary> /// <summary>
@ -342,8 +344,7 @@ namespace MapControl
{ {
SetTransformCenter(center); SetTransformCenter(center);
viewCenter.X += translation.X; viewCenter = new Point(viewCenter.X + translation.X, viewCenter.Y + translation.Y);
viewCenter.Y += translation.Y;
if (rotation != 0d) if (rotation != 0d)
{ {
@ -392,15 +393,16 @@ namespace MapControl
/// </summary> /// </summary>
public void ZoomToBounds(BoundingBox boundingBox) 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) 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); TargetZoomLevel = ViewTransform.ScaleToZoomLevel(scale);
TargetCenter = targetCenter; TargetCenter = targetCenter;

View file

@ -183,9 +183,9 @@ namespace MapControl
{ {
var viewRect = GetViewRect(boundingBox); var viewRect = GetViewRect(boundingBox);
if (viewRect != null) if (viewRect.HasValue)
{ {
ArrangeElement(element, viewRect); ArrangeElement(element, viewRect.Value);
} }
} }
else else
@ -214,21 +214,27 @@ namespace MapControl
return position; 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;
} }
protected ViewRect GetViewRect(MapRect mapRect) return GetViewRect(rect.Value);
}
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 && if (parentMap.MapProjection.Type <= MapProjectionType.NormalCylindrical &&
IsOutsideViewport(position)) IsOutsideViewport(position))
{ {
var location = parentMap.MapProjection.MapToLocation(mapRect.Center); var location = parentMap.MapProjection.MapToLocation(rectCenter);
if (location != null) if (location != null)
{ {
@ -242,8 +248,8 @@ namespace MapControl
} }
} }
var width = mapRect.Width * parentMap.ViewTransform.Scale; var width = rect.Width * parentMap.ViewTransform.Scale;
var height = mapRect.Height * parentMap.ViewTransform.Scale; var height = rect.Height * parentMap.ViewTransform.Scale;
var x = position.X - width / 2d; var x = position.X - width / 2d;
var y = position.Y - height / 2d; var y = position.Y - height / 2d;
@ -259,16 +265,17 @@ namespace MapControl
private static void ArrangeElement(FrameworkElement element, Point position) private static void ArrangeElement(FrameworkElement element, Point position)
{ {
var size = GetDesiredSize(element); 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) switch (element.HorizontalAlignment)
{ {
case HorizontalAlignment.Center: case HorizontalAlignment.Center:
rect.X -= rect.Width / 2d; x -= size.Width / 2d;
break; break;
case HorizontalAlignment.Right: case HorizontalAlignment.Right:
rect.X -= rect.Width; x -= size.Width;
break; break;
default: default:
@ -278,37 +285,40 @@ namespace MapControl
switch (element.VerticalAlignment) switch (element.VerticalAlignment)
{ {
case VerticalAlignment.Center: case VerticalAlignment.Center:
rect.Y -= rect.Height / 2d; y -= size.Height / 2d;
break; break;
case VerticalAlignment.Bottom: case VerticalAlignment.Bottom:
rect.Y -= rect.Height; y -= size.Height;
break; break;
default: default:
break; break;
} }
ArrangeElement(element, rect); ArrangeElement(element, new Rect(x, y, size.Width, size.Height));
} }
private static void ArrangeElement(FrameworkElement element, Size parentSize) private static void ArrangeElement(FrameworkElement element, Size parentSize)
{ {
var size = GetDesiredSize(element); 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) switch (element.HorizontalAlignment)
{ {
case HorizontalAlignment.Center: case HorizontalAlignment.Center:
rect.X = (parentSize.Width - rect.Width) / 2d; x = (parentSize.Width - size.Width) / 2d;
break; break;
case HorizontalAlignment.Right: case HorizontalAlignment.Right:
rect.X = parentSize.Width - rect.Width; x = parentSize.Width - size.Width;
break; break;
case HorizontalAlignment.Stretch: case HorizontalAlignment.Stretch:
rect.Width = parentSize.Width; width = parentSize.Width;
break; break;
default: default:
@ -318,30 +328,30 @@ namespace MapControl
switch (element.VerticalAlignment) switch (element.VerticalAlignment)
{ {
case VerticalAlignment.Center: case VerticalAlignment.Center:
rect.Y = (parentSize.Height - rect.Height) / 2d; y = (parentSize.Height - size.Height) / 2d;
break; break;
case VerticalAlignment.Bottom: case VerticalAlignment.Bottom:
rect.Y = parentSize.Height - rect.Height; y = parentSize.Height - size.Height;
break; break;
case VerticalAlignment.Stretch: case VerticalAlignment.Stretch:
rect.Height = parentSize.Height; height = parentSize.Height;
break; break;
default: default:
break; break;
} }
ArrangeElement(element, rect); ArrangeElement(element, new Rect(x, y, width, height));
} }
private static void ArrangeElement(FrameworkElement element, ViewRect rect) private static void ArrangeElement(FrameworkElement element, ViewRect rect)
{ {
element.Width = rect.Width; element.Width = rect.Rect.Width;
element.Height = rect.Height; 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) if (element.RenderTransform is RotateTransform rotateTransform)
{ {
@ -359,10 +369,7 @@ namespace MapControl
{ {
if (element.UseLayoutRounding) if (element.UseLayoutRounding)
{ {
rect.X = Math.Round(rect.X); rect = new Rect(Math.Round(rect.X), Math.Round(rect.Y), Math.Round(rect.Width), Math.Round(rect.Height));
rect.Y = Math.Round(rect.Y);
rect.Width = Math.Round(rect.Width);
rect.Height = Math.Round(rect.Height);
} }
element.Arrange(rect); element.Arrange(rect);

View file

@ -29,8 +29,6 @@ namespace MapControl
public const double Wgs84Flattening = 1d / 298.257223563; public const double Wgs84Flattening = 1d / 298.257223563;
public static readonly double Wgs84Eccentricity = Math.Sqrt((2d - Wgs84Flattening) * Wgs84Flattening); public static readonly double Wgs84Eccentricity = Math.Sqrt((2d - Wgs84Flattening) * Wgs84Flattening);
public static MapProjectionFactory Factory { get; set; } = new MapProjectionFactory();
/// <summary> /// <summary>
/// Gets the type of the projection. /// Gets the type of the projection.
/// </summary> /// </summary>
@ -49,7 +47,7 @@ namespace MapControl
/// <summary> /// <summary>
/// Gets the relative map scale at the specified Location. /// Gets the relative map scale at the specified Location.
/// </summary> /// </summary>
public virtual Scale GetRelativeScale(Location location) => new Scale(1d, 1d); public virtual Point GetRelativeScale(Location location) => new Point(1d, 1d);
/// <summary> /// <summary>
/// Transforms a Location in geographic coordinates to a Point in projected map coordinates. /// 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); public abstract Location MapToLocation(Point point);
/// <summary> /// <summary>
/// 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. /// Returns null when the BoundingBox can not be transformed.
/// </summary> /// </summary>
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) if (boundingBox.South < boundingBox.North && boundingBox.West < boundingBox.East)
{ {
@ -78,7 +76,7 @@ namespace MapControl
if (p1.HasValue && p2.HasValue) if (p1.HasValue && p2.HasValue)
{ {
mapRect = new MapRect(p1.Value, p2.Value); rect = new Rect(p1.Value, p2.Value);
} }
} }
else if (boundingBox.Center != null) else if (boundingBox.Center != null)
@ -94,21 +92,21 @@ namespace MapControl
var x = center.Value.X - width / 2d; var x = center.Value.X - width / 2d;
var y = center.Value.Y - height / 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;
} }
/// <summary> /// <summary>
/// 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. /// Returns null when the MapRect can not be transformed.
/// </summary> /// </summary>
public virtual BoundingBox MapRectToBoundingBox(MapRect mapRect) public virtual BoundingBox MapToBoundingBox(Rect rect)
{ {
var southWest = MapToLocation(new Point(mapRect.XMin, mapRect.YMin)); var southWest = MapToLocation(new Point(rect.X, rect.Y));
var northEast = MapToLocation(new Point(mapRect.XMax, mapRect.YMax)); var northEast = MapToLocation(new Point(rect.X + rect.Width, rect.Y + rect.Height));
return southWest != null && northEast != null return southWest != null && northEast != null
? new BoundingBox(southWest, northEast) ? new BoundingBox(southWest, northEast)
@ -128,16 +126,16 @@ namespace MapControl
/// <summary> /// <summary>
/// Gets the BBOX parameter value for a WMS GetMap request. /// Gets the BBOX parameter value for a WMS GetMap request.
/// </summary> /// </summary>
public virtual string GetBboxValue(MapRect mapRect) public virtual string GetBboxValue(Rect rect)
{ {
// Truncate values for seamless stitching of two WMS images at 180° longitude, // Truncate values for seamless stitching of two WMS images at 180° longitude,
// as done in WmsImageLayer.GetImageAsync. // as done in WmsImageLayer.GetImageAsync.
// //
return string.Format(CultureInfo.InvariantCulture, "{0:F2},{1:F2},{2:F2},{3:F2}", 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 * rect.X),
0.01 * Math.Ceiling(100d * mapRect.YMin), 0.01 * Math.Ceiling(100d * rect.Y),
0.01 * Math.Floor(100d * mapRect.XMax), 0.01 * Math.Floor(100d * (rect.X + rect.Width)),
0.01 * Math.Floor(100d * mapRect.YMax)); 0.01 * Math.Floor(100d * (rect.Y + rect.Height)));
} }
} }
} }

View file

@ -6,6 +6,14 @@ namespace MapControl
{ {
public class MapProjectionFactory 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) public virtual MapProjection GetProjection(int epsgCode)
{ {
MapProjection projection = null; MapProjection projection = null;

View file

@ -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
{
/// <summary>
/// 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.
/// </summary>
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;
}
}

View file

@ -23,11 +23,6 @@ using System.Windows.Threading;
namespace MapControl namespace MapControl
{ {
public interface ITileImageLoader
{
Task LoadTilesAsync(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress);
}
public abstract class MapTileLayerBase : Panel, IMapLayer public abstract class MapTileLayerBase : Panel, IMapLayer
{ {
public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register( public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register(

View file

@ -28,7 +28,7 @@ namespace MapControl
public double FalseNorthing { get; set; } = 2e6; public double FalseNorthing { get; set; } = 2e6;
public bool IsNorth { get; set; } public bool IsNorth { get; set; }
public override Scale GetRelativeScale(Location location) public override Point GetRelativeScale(Location location)
{ {
var lat = location.Latitude * Math.PI / 180d; 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 m = Math.Cos(lat) / Math.Sqrt(1d - eSinLat * eSinLat); // p.160 (14-15)
var k = r / (EquatorialRadius * m); // p.161 (21-32) 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) public override Point? LocationToMap(Location location)

View file

@ -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);
}
}
}

View file

@ -3,21 +3,19 @@
// Licensed under the Microsoft Public License (Ms-PL) // Licensed under the Microsoft Public License (Ms-PL)
using System; using System;
#if WINUI #if AVALONIA
using Microsoft.UI.Xaml; using Avalonia.Controls;
using Avalonia.Media;
using ImageSource = Avalonia.Media.IImage;
#elif WINUI
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Animation;
#elif UWP #elif UWP
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;
#else #else
using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Animation;
#endif #endif
namespace MapControl namespace MapControl
@ -56,16 +54,5 @@ namespace MapControl
AnimateImageOpacity(); AnimateImageOpacity();
} }
} }
private void BeginOpacityAnimation()
{
Image.BeginAnimation(UIElement.OpacityProperty,
new DoubleAnimation
{
From = 0d,
Duration = MapBase.ImageFadeDuration,
FillBehavior = FillBehavior.Stop
});
}
} }
} }

View file

@ -9,7 +9,6 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -19,6 +18,11 @@ namespace MapControl
/// <summary> /// <summary>
/// Loads and optionally caches map tile images for a MapTileLayer. /// Loads and optionally caches map tile images for a MapTileLayer.
/// </summary> /// </summary>
public interface ITileImageLoader
{
Task LoadTilesAsync(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress);
}
public partial class TileImageLoader : ITileImageLoader public partial class TileImageLoader : ITileImageLoader
{ {
private class TileQueue : ConcurrentStack<Tile> private class TileQueue : ConcurrentStack<Tile>

View file

@ -4,7 +4,9 @@
using System; using System;
using System.Threading.Tasks; 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;
#elif UWP #elif UWP
using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media;

View file

@ -29,7 +29,7 @@ namespace MapControl
public double FalseEasting { get; set; } public double FalseEasting { get; set; }
public double FalseNorthing { get; set; } public double FalseNorthing { get; set; }
public override Scale GetRelativeScale(Location location) public override Point GetRelativeScale(Location location)
{ {
var k = ScaleFactor; var k = ScaleFactor;
@ -58,7 +58,7 @@ namespace MapControl
+ (61d - 148d * T + 16d * T_2) * A_6 / 720d); // p.61 (8-11) + (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) public override Point? LocationToMap(Location location)

View file

@ -2,26 +2,24 @@
// Copyright © 2024 Clemens Fischer // Copyright © 2024 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL) // Licensed under the Microsoft Public License (Ms-PL)
#if !WINUI && !UWP
using System.Windows;
#endif
namespace MapControl namespace MapControl
{ {
/// <summary> /// <summary>
/// Rotated rectangle used to arrange and rotate an element with a BoundingBox. /// Rotated rectangle used to arrange and rotate an element with a BoundingBox.
/// </summary> /// </summary>
public class ViewRect public readonly struct ViewRect
{ {
public ViewRect(double x, double y, double width, double height, double rotation) public ViewRect(double x, double y, double width, double height, double rotation)
{ {
X = x; Rect = new Rect(x, y, width, height);
Y = y;
Width = width;
Height = height;
Rotation = rotation; Rotation = rotation;
} }
public double X { get; } public Rect Rect { get; }
public double Y { get; }
public double Width { get; }
public double Height { get; }
public double Rotation { get; } public double Rotation { get; }
} }
} }

View file

@ -24,11 +24,11 @@ namespace MapControl
CrsId = DefaultCrsId; 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) 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) public override Point? LocationToMap(Location location)

View file

@ -239,9 +239,9 @@ namespace MapControl
/// </summary> /// </summary>
protected virtual string GetMapRequestUri(BoundingBox boundingBox) 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; return null;
} }
@ -257,9 +257,9 @@ namespace MapControl
{ "STYLES", Styles ?? "" }, { "STYLES", Styles ?? "" },
{ "FORMAT", "image/png" }, { "FORMAT", "image/png" },
{ "CRS", GetCrsValue() }, { "CRS", GetCrsValue() },
{ "BBOX", GetBboxValue(mapRect) }, { "BBOX", GetBboxValue(rect.Value) },
{ "WIDTH", Math.Round(viewScale * mapRect.Width).ToString("F0") }, { "WIDTH", Math.Round(viewScale * rect.Value.Width).ToString("F0") },
{ "HEIGHT", Math.Round(viewScale * mapRect.Height).ToString("F0") } { "HEIGHT", Math.Round(viewScale * rect.Value.Height).ToString("F0") }
}); });
} }
@ -270,18 +270,18 @@ namespace MapControl
{ {
var viewSize = ParentMap.RenderSize; var viewSize = ParentMap.RenderSize;
var boundingBox = ParentMap.ViewRectToBoundingBox(new Rect(0d, 0d, viewSize.Width, viewSize.Height)); 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; 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.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); var imagePos = transform.Transform(position);
@ -295,9 +295,9 @@ namespace MapControl
{ "FORMAT", "image/png" }, { "FORMAT", "image/png" },
{ "INFO_FORMAT", format }, { "INFO_FORMAT", format },
{ "CRS", GetCrsValue() }, { "CRS", GetCrsValue() },
{ "BBOX", GetBboxValue(mapRect) }, { "BBOX", GetBboxValue(rect.Value) },
{ "WIDTH", Math.Round(viewRect.Width).ToString("F0") }, { "WIDTH", Math.Round(viewRect.Rect.Width).ToString("F0") },
{ "HEIGHT", Math.Round(viewRect.Height).ToString("F0") }, { "HEIGHT", Math.Round(viewRect.Rect.Height).ToString("F0") },
{ "I", Math.Round(imagePos.X).ToString("F0") }, { "I", Math.Round(imagePos.X).ToString("F0") },
{ "J", Math.Round(imagePos.Y).ToString("F0") } { "J", Math.Round(imagePos.Y).ToString("F0") }
}; };
@ -310,9 +310,9 @@ namespace MapControl
return ParentMap.MapProjection.GetCrsValue(); 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<string, string> queryParameters) protected string GetRequestUri(IDictionary<string, string> queryParameters)

View file

@ -24,13 +24,13 @@ namespace MapControl
CrsId = DefaultCrsId; CrsId = DefaultCrsId;
} }
public override Scale GetRelativeScale(Location location) public override Point GetRelativeScale(Location location)
{ {
var lat = location.Latitude * Math.PI / 180d; var lat = location.Latitude * Math.PI / 180d;
var eSinLat = Wgs84Eccentricity * Math.Sin(lat); var eSinLat = Wgs84Eccentricity * Math.Sin(lat);
var k = Math.Sqrt(1d - eSinLat * eSinLat) / Math.Cos(lat); // p.44 (7-8) 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) public override Point? LocationToMap(Location location)

View file

@ -140,9 +140,6 @@
<Compile Include="..\Shared\MapProjectionFactory.cs"> <Compile Include="..\Shared\MapProjectionFactory.cs">
<Link>MapProjectionFactory.cs</Link> <Link>MapProjectionFactory.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\MapRect.cs">
<Link>MapRect.cs</Link>
</Compile>
<Compile Include="..\Shared\MapScale.cs"> <Compile Include="..\Shared\MapScale.cs">
<Link>MapScale.cs</Link> <Link>MapScale.cs</Link>
</Compile> </Compile>
@ -164,9 +161,6 @@
<Compile Include="..\Shared\PushpinBorder.cs"> <Compile Include="..\Shared\PushpinBorder.cs">
<Link>PushpinBorder.cs</Link> <Link>PushpinBorder.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\Scale.cs">
<Link>Scale.cs</Link>
</Compile>
<Compile Include="..\Shared\StereographicProjection.cs"> <Compile Include="..\Shared\StereographicProjection.cs">
<Link>StereographicProjection.cs</Link> <Link>StereographicProjection.cs</Link>
</Compile> </Compile>
@ -272,6 +266,9 @@
<Compile Include="..\WinUI\PushpinBorder.WinUI.cs"> <Compile Include="..\WinUI\PushpinBorder.WinUI.cs">
<Link>PushpinBorder.WinUI.cs</Link> <Link>PushpinBorder.WinUI.cs</Link>
</Compile> </Compile>
<Compile Include="..\WinUI\Rect.WinUI.cs">
<Link>Rect.WinUI.cs</Link>
</Compile>
<Compile Include="..\WinUI\Tile.WinUI.cs"> <Compile Include="..\WinUI\Tile.WinUI.cs">
<Link>Tile.WinUI.cs</Link> <Link>Tile.WinUI.cs</Link>
</Compile> </Compile>

View file

@ -13,6 +13,11 @@ namespace MapControl
{ {
public static partial class ImageLoader public static partial class ImageLoader
{ {
public static ImageSource LoadImage(Uri uri)
{
return new BitmapImage(uri);
}
public static ImageSource LoadImage(Stream stream) public static ImageSource LoadImage(Stream stream)
{ {
var image = new BitmapImage(); var image = new BitmapImage();

View file

@ -37,18 +37,18 @@ namespace MapControl
if (projection != null && items != null) 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; return image;
} }
private DrawingImage GetImage(MapProjection projection, MapRect mapRect, IEnumerable<IMapDrawingItem> items) private DrawingImage GetImage(MapProjection projection, Rect rect, IEnumerable<IMapDrawingItem> items)
{ {
var scale = ParentMap.ViewTransform.Scale; var scale = ParentMap.ViewTransform.Scale;
var rotation = ParentMap.ViewTransform.Rotation; var rotation = ParentMap.ViewTransform.Rotation;
@ -62,13 +62,13 @@ namespace MapControl
.Select(point => point.Value) .Select(point => point.Value)
.ToList(); .ToList();
if (points.Any(point => mapRect.Contains(point))) if (points.Any(point => rect.Contains(point)))
{ {
for (int i = 0; i < points.Count; i++) for (int i = 0; i < points.Count; i++)
{ {
points[i] = new Point( points[i] = new Point(
scale * (points[i].X - mapRect.XMin), scale * (points[i].X - rect.X),
scale * (mapRect.YMax - points[i].Y)); scale * ((rect.Y + rect.Height) - points[i].Y));
} }
drawings.Children.Add(item.GetDrawing(points, scale, rotation)); drawings.Children.Add(item.GetDrawing(points, scale, rotation));
@ -79,7 +79,7 @@ namespace MapControl
{ {
Drawing = drawings, Drawing = drawings,
ViewboxUnits = BrushMappingMode.Absolute, 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( var drawing = new GeometryDrawing(

View file

@ -3,14 +3,27 @@
// Licensed under the Microsoft Public License (Ms-PL) // Licensed under the Microsoft Public License (Ms-PL)
using System; using System;
using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
namespace MapControl namespace MapControl
{ {
public partial class Tile public partial class Tile
{ {
private void BeginOpacityAnimation()
{
Image.BeginAnimation(UIElement.OpacityProperty,
new DoubleAnimation
{
From = 0d,
Duration = MapBase.ImageFadeDuration,
FillBehavior = FillBehavior.Stop
});
}
private void AnimateImageOpacity() private void AnimateImageOpacity()
{ {
if (Image.Source is BitmapSource bitmap && bitmap.IsDownloading && !bitmap.IsFrozen) if (Image.Source is BitmapSource bitmap && bitmap.IsDownloading && !bitmap.IsFrozen)

View file

@ -21,6 +21,11 @@ namespace MapControl
{ {
public static partial class ImageLoader public static partial class ImageLoader
{ {
public static ImageSource LoadImage(Uri uri)
{
return new BitmapImage(uri);
}
public static async Task<WriteableBitmap> LoadImageAsync(BitmapDecoder decoder) public static async Task<WriteableBitmap> LoadImageAsync(BitmapDecoder decoder)
{ {
var image = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight); var image = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight);

View file

@ -7,7 +7,7 @@ namespace MapControl
/// <summary> /// <summary>
/// Replaces Windows.Foundation.Point for double floating point precision. /// Replaces Windows.Foundation.Point for double floating point precision.
/// </summary> /// </summary>
public struct Point public readonly struct Point
{ {
public Point(double x, double y) public Point(double x, double y)
{ {
@ -15,8 +15,8 @@ namespace MapControl
Y = y; Y = y;
} }
public double X { get; set; } public double X { get; }
public double Y { get; set; } public double Y { get; }
public static implicit operator Windows.Foundation.Point(Point p) public static implicit operator Windows.Foundation.Point(Point p)
{ {

View file

@ -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
{
/// <summary>
/// Replaces Windows.Foundation.Rect for double floating point precision.
/// </summary>
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);
}
}
}

View file

@ -4,9 +4,11 @@
#if WINUI #if WINUI
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Media.Imaging;
#else #else
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Media.Imaging; using Windows.UI.Xaml.Media.Imaging;
#endif #endif
@ -14,6 +16,17 @@ namespace MapControl
{ {
public partial class Tile public partial class Tile
{ {
private void BeginOpacityAnimation()
{
Image.BeginAnimation(UIElement.OpacityProperty,
new DoubleAnimation
{
From = 0d,
Duration = MapBase.ImageFadeDuration,
FillBehavior = FillBehavior.Stop
});
}
private void AnimateImageOpacity() private void AnimateImageOpacity()
{ {
if (Image.Source is BitmapImage bitmap && bitmap.UriSource != null) if (Image.Source is BitmapImage bitmap && bitmap.UriSource != null)

View file

@ -4,6 +4,9 @@
using ProjNet.CoordinateSystems; using ProjNet.CoordinateSystems;
using System; using System;
#if !WINUI && !UWP
using System.Windows;
#endif
namespace MapControl.Projections namespace MapControl.Projections
{ {
@ -20,11 +23,11 @@ namespace MapControl.Projections
CoordinateSystem = ProjectedCoordinateSystem.WebMercator; 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) var k = 1d / Math.Cos(location.Latitude * Math.PI / 180d); // p.44 (7-3)
return new Scale(k, k); return new Point(k, k);
} }
} }
} }

View file

@ -3,6 +3,9 @@
// Licensed under the Microsoft Public License (Ms-PL) // Licensed under the Microsoft Public License (Ms-PL)
using System; using System;
#if !WINUI && !UWP
using System.Windows;
#endif
namespace MapControl.Projections namespace MapControl.Projections
{ {
@ -41,13 +44,13 @@ namespace MapControl.Projections
+ "AUTHORITY[\"EPSG\",\"3395\"]]"; + "AUTHORITY[\"EPSG\",\"3395\"]]";
} }
public override Scale GetRelativeScale(Location location) public override Point GetRelativeScale(Location location)
{ {
var lat = location.Latitude * Math.PI / 180d; var lat = location.Latitude * Math.PI / 180d;
var eSinLat = Wgs84Eccentricity * Math.Sin(lat); var eSinLat = Wgs84Eccentricity * Math.Sin(lat);
var k = Math.Sqrt(1d - eSinLat * eSinLat) / Math.Cos(lat); // p.44 (7-8) var k = Math.Sqrt(1d - eSinLat * eSinLat) / Math.Cos(lat); // p.44 (7-8)
return new Scale(k, k); return new Point(k, k);
} }
} }
} }

View file

@ -89,7 +89,7 @@ namespace MapControl.UiTools
if (selectedProjection != projection) if (selectedProjection != projection)
{ {
selectedProjection = projection; selectedProjection = projection;
Map.MapProjection = MapProjection.Factory.GetProjection(selectedProjection); Map.MapProjection = MapProjectionFactory.Instance.GetProjection(selectedProjection);
} }
UpdateCheckedStates(); UpdateCheckedStates();