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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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();
/// <summary>
/// Gets the type of the projection.
/// </summary>
@ -49,7 +47,7 @@ namespace MapControl
/// <summary>
/// Gets the relative map scale at the specified Location.
/// </summary>
public virtual Scale GetRelativeScale(Location location) => new Scale(1d, 1d);
public virtual Point GetRelativeScale(Location location) => new Point(1d, 1d);
/// <summary>
/// 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);
/// <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.
/// </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)
{
@ -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;
}
/// <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.
/// </summary>
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
/// <summary>
/// Gets the BBOX parameter value for a WMS GetMap request.
/// </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,
// 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)));
}
}
}

View file

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

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
{
public interface ITileImageLoader
{
Task LoadTilesAsync(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress);
}
public abstract class MapTileLayerBase : Panel, IMapLayer
{
public static readonly DependencyProperty TileSourceProperty = DependencyProperty.Register(

View file

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

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

View file

@ -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
/// <summary>
/// Loads and optionally caches map tile images for a MapTileLayer.
/// </summary>
public interface ITileImageLoader
{
Task LoadTilesAsync(IEnumerable<Tile> tiles, TileSource tileSource, string cacheName, IProgress<double> progress);
}
public partial class TileImageLoader : ITileImageLoader
{
private class TileQueue : ConcurrentStack<Tile>

View file

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

View file

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

View file

@ -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
{
/// <summary>
/// Rotated rectangle used to arrange and rotate an element with a BoundingBox.
/// </summary>
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; }
}
}

View file

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

View file

@ -239,9 +239,9 @@ namespace MapControl
/// </summary>
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<string, string> queryParameters)

View file

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

View file

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

View file

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

View file

@ -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<IMapDrawingItem> items)
private DrawingImage GetImage(MapProjection projection, Rect rect, IEnumerable<IMapDrawingItem> 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(

View file

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

View file

@ -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<WriteableBitmap> LoadImageAsync(BitmapDecoder decoder)
{
var image = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight);

View file

@ -7,7 +7,7 @@ namespace MapControl
/// <summary>
/// Replaces Windows.Foundation.Point for double floating point precision.
/// </summary>
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)
{

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
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)

View file

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

View file

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

View file

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