mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2026-01-03 23:30:25 +01:00
LatLonBox transformation
This commit is contained in:
parent
f143bdb5e6
commit
8efcee2585
|
|
@ -21,7 +21,7 @@ namespace MapControl
|
|||
|
||||
public override Rect? BoundingBoxToMap(BoundingBox boundingBox)
|
||||
{
|
||||
Rect? mapRect = null;
|
||||
Rect? rect = null;
|
||||
var center = LocationToMap(boundingBox.Center);
|
||||
|
||||
if (center.HasValue)
|
||||
|
|
@ -31,21 +31,21 @@ namespace MapControl
|
|||
var x = center.Value.X - width / 2d;
|
||||
var y = center.Value.Y - height / 2d;
|
||||
|
||||
mapRect = new Rect(x, y, width, height);
|
||||
rect = new Rect(x, y, width, height);
|
||||
}
|
||||
|
||||
return mapRect;
|
||||
return rect;
|
||||
}
|
||||
|
||||
public override BoundingBox MapToBoundingBox(Rect mapRect)
|
||||
public override BoundingBox MapToBoundingBox(Rect rect)
|
||||
{
|
||||
BoundingBox boundingBox = null;
|
||||
var rectCenter = new Point(mapRect.X + mapRect.Width / 2d, mapRect.Y + mapRect.Height / 2d);
|
||||
var rectCenter = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d);
|
||||
var center = MapToLocation(rectCenter);
|
||||
|
||||
if (center != null)
|
||||
{
|
||||
boundingBox = new CenteredBoundingBox(center, mapRect.Width / Wgs84MeterPerDegree, mapRect.Height / Wgs84MeterPerDegree);
|
||||
boundingBox = new CenteredBoundingBox(center, rect.Width / Wgs84MeterPerDegree, rect.Height / Wgs84MeterPerDegree);
|
||||
}
|
||||
|
||||
return boundingBox;
|
||||
|
|
|
|||
|
|
@ -21,13 +21,12 @@ namespace MapControl
|
|||
{
|
||||
}
|
||||
|
||||
public BoundingBox(double latitude1, double longitude1, double latitude2, double longitude2, double rotation = 0d)
|
||||
public BoundingBox(double latitude1, double longitude1, double latitude2, double longitude2)
|
||||
{
|
||||
South = Math.Min(Math.Max(Math.Min(latitude1, latitude2), -90d), 90d);
|
||||
North = Math.Min(Math.Max(Math.Max(latitude1, latitude2), -90d), 90d);
|
||||
West = Math.Min(longitude1, longitude2);
|
||||
East = Math.Max(longitude1, longitude2);
|
||||
Rotation = rotation;
|
||||
}
|
||||
|
||||
public BoundingBox(Location location1, Location location2)
|
||||
|
|
@ -39,7 +38,6 @@ namespace MapControl
|
|||
public double North { get; }
|
||||
public double West { get; }
|
||||
public double East { get; }
|
||||
public double Rotation { get; }
|
||||
|
||||
public virtual double Width => East - West;
|
||||
public virtual double Height => North - South;
|
||||
|
|
@ -47,7 +45,7 @@ namespace MapControl
|
|||
public virtual Location Center => new Location((South + North) / 2d, (West + East) / 2d);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a BoundingBox instance from a string containing a comma-separated sequence of four floating point numbers.
|
||||
/// Creates a BoundingBox instance from a string containing a comma-separated sequence of four or five floating point numbers.
|
||||
/// </summary>
|
||||
public static BoundingBox Parse(string boundingBox)
|
||||
{
|
||||
|
|
@ -67,7 +65,9 @@ namespace MapControl
|
|||
? double.Parse(values[4], NumberStyles.Float, CultureInfo.InvariantCulture)
|
||||
: 0d;
|
||||
|
||||
return new BoundingBox(
|
||||
// always return a LatLonBox, i.e. a BoundingBox with optional rotation, as used by GeoImage and GroundOverlay
|
||||
//
|
||||
return new LatLonBox(
|
||||
double.Parse(values[0], NumberStyles.Float, CultureInfo.InvariantCulture),
|
||||
double.Parse(values[1], NumberStyles.Float, CultureInfo.InvariantCulture),
|
||||
double.Parse(values[2], NumberStyles.Float, CultureInfo.InvariantCulture),
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ namespace MapControl
|
|||
|
||||
var boundingBox = geoImage.mapProjection != null
|
||||
? geoImage.mapProjection.MapToBoundingBox(new Rect(p1, p2))
|
||||
: new BoundingBox(p1.Y, p1.X, p2.Y, p2.X);
|
||||
: new LatLonBox(p1.Y, p1.X, p2.Y, p2.X, 0d);
|
||||
|
||||
if (element is Image image)
|
||||
{
|
||||
|
|
@ -94,6 +94,18 @@ namespace MapControl
|
|||
else if (element is Shape shape)
|
||||
{
|
||||
shape.Fill = geoImage.ImageBrush;
|
||||
#if WPF
|
||||
MapPanel.GetParentMap(shape).Children.Add(new MapPolygon
|
||||
{
|
||||
Stroke = Brushes.Green,
|
||||
StrokeThickness = 1,
|
||||
Locations = new LocationCollection(
|
||||
new Location(boundingBox.North, boundingBox.West),
|
||||
new Location(boundingBox.North, boundingBox.East),
|
||||
new Location(boundingBox.South, boundingBox.East),
|
||||
new Location(boundingBox.South, boundingBox.West))
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
MapPanel.SetBoundingBox(element, boundingBox);
|
||||
|
|
|
|||
|
|
@ -171,8 +171,8 @@ namespace MapControl
|
|||
{
|
||||
foreach (var groundOverlayElement in folderElement.Elements(ns + "GroundOverlay"))
|
||||
{
|
||||
var boundingBoxElement = groundOverlayElement.Element(ns + "LatLonBox");
|
||||
var boundingBox = boundingBoxElement != null ? ReadBoundingBox(boundingBoxElement) : null;
|
||||
var latLonBoxElement = groundOverlayElement.Element(ns + "LatLonBox");
|
||||
var latLonBox = latLonBoxElement != null ? ReadLatLonBox(latLonBoxElement) : null;
|
||||
|
||||
var imagePathElement = groundOverlayElement.Element(ns + "Icon");
|
||||
var imagePath = imagePathElement?.Element(ns + "href")?.Value;
|
||||
|
|
@ -180,15 +180,15 @@ namespace MapControl
|
|||
var drawOrder = groundOverlayElement.Element(ns + "drawOrder")?.Value;
|
||||
var zIndex = drawOrder != null ? int.Parse(drawOrder) : 0;
|
||||
|
||||
if (boundingBox != null && imagePath != null)
|
||||
if (latLonBox != null && imagePath != null)
|
||||
{
|
||||
yield return new ImageOverlay(boundingBox, imagePath, zIndex);
|
||||
yield return new ImageOverlay(latLonBox, imagePath, zIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static BoundingBox ReadBoundingBox(XElement latLonBoxElement)
|
||||
private static LatLonBox ReadLatLonBox(XElement latLonBoxElement)
|
||||
{
|
||||
var ns = latLonBoxElement.Name.Namespace;
|
||||
var north = double.NaN;
|
||||
|
|
@ -234,7 +234,7 @@ namespace MapControl
|
|||
throw new FormatException("Invalid LatLonBox");
|
||||
}
|
||||
|
||||
return new BoundingBox(south, west, north, east, rotation);
|
||||
return new LatLonBox(south, west, north, east, rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
MapControl/Shared/LatLonBox.cs
Normal file
29
MapControl/Shared/LatLonBox.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// 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>
|
||||
/// A BoundingBox with optional rotation. Used by GeoImage and GroundOverlay.
|
||||
/// </summary>
|
||||
public class LatLonBox : BoundingBox
|
||||
{
|
||||
public LatLonBox(double latitude1, double longitude1, double latitude2, double longitude2, double rotation)
|
||||
: base(latitude1, longitude1, latitude2, longitude2)
|
||||
{
|
||||
Rotation = rotation;
|
||||
}
|
||||
|
||||
public LatLonBox(Location location1, Location location2, double rotation)
|
||||
: base(location1, location2)
|
||||
{
|
||||
Rotation = rotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a counterclockwise rotation angle in degrees.
|
||||
/// </summary>
|
||||
public double Rotation { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -206,16 +206,28 @@ namespace MapControl
|
|||
return position;
|
||||
}
|
||||
|
||||
private Rect? GetViewRect(BoundingBox boundingBox)
|
||||
private Tuple<Rect, double> GetViewRect(BoundingBox boundingBox)
|
||||
{
|
||||
var rect = parentMap.MapProjection.BoundingBoxToMap(boundingBox);
|
||||
|
||||
if (rect.HasValue)
|
||||
if (boundingBox is LatLonBox latLonBox)
|
||||
{
|
||||
rect = GetViewRect(rect.Value);
|
||||
var rotatedRect = parentMap.MapProjection.LatLonBoxToMap(latLonBox);
|
||||
|
||||
if (rotatedRect != null)
|
||||
{
|
||||
return new Tuple<Rect, double>(GetViewRect(rotatedRect.Item1), rotatedRect.Item2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var mapRect = parentMap.MapProjection.BoundingBoxToMap(boundingBox);
|
||||
|
||||
if (mapRect.HasValue)
|
||||
{
|
||||
return new Tuple<Rect, double>(GetViewRect(mapRect.Value), 0d);
|
||||
}
|
||||
}
|
||||
|
||||
return rect;
|
||||
return null;
|
||||
}
|
||||
|
||||
private Rect GetViewRect(Rect mapRect)
|
||||
|
|
@ -287,16 +299,16 @@ namespace MapControl
|
|||
|
||||
private void ArrangeElement(FrameworkElement element, BoundingBox boundingBox)
|
||||
{
|
||||
var rect = GetViewRect(boundingBox);
|
||||
var viewRect = GetViewRect(boundingBox);
|
||||
|
||||
if (rect.HasValue)
|
||||
if (viewRect != null)
|
||||
{
|
||||
element.Width = rect.Value.Width;
|
||||
element.Height = rect.Value.Height;
|
||||
element.Width = viewRect.Item1.Width;
|
||||
element.Height = viewRect.Item1.Height;
|
||||
|
||||
element.Arrange(rect.Value);
|
||||
element.Arrange(viewRect.Item1);
|
||||
|
||||
var rotation = parentMap.ViewTransform.Rotation - boundingBox.Rotation;
|
||||
var rotation = parentMap.ViewTransform.Rotation - viewRect.Item2;
|
||||
|
||||
if (element.RenderTransform is RotateTransform rotateTransform)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -66,27 +66,28 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public virtual Rect? BoundingBoxToMap(BoundingBox boundingBox)
|
||||
{
|
||||
Rect? mapRect = null;
|
||||
Rect? rect = null;
|
||||
|
||||
var southWest = LocationToMap(new Location(boundingBox.South, boundingBox.West));
|
||||
var northEast = LocationToMap(new Location(boundingBox.North, boundingBox.East));
|
||||
|
||||
if (southWest.HasValue && northEast.HasValue)
|
||||
{
|
||||
mapRect = new Rect(southWest.Value, northEast.Value);
|
||||
rect = new Rect(southWest.Value, northEast.Value);
|
||||
}
|
||||
|
||||
return mapRect;
|
||||
return rect;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 MapToBoundingBox(Rect mapRect)
|
||||
public virtual BoundingBox MapToBoundingBox(Rect rect)
|
||||
{
|
||||
BoundingBox boundingBox = null;
|
||||
var southWest = MapToLocation(new Point(mapRect.X, mapRect.Y));
|
||||
var northEast = MapToLocation(new Point(mapRect.X + mapRect.Width, mapRect.Y + mapRect.Height));
|
||||
var southWest = MapToLocation(new Point(rect.X, rect.Y));
|
||||
var northEast = MapToLocation(new Point(rect.X + rect.Width, rect.Y + rect.Height));
|
||||
|
||||
if (southWest != null && northEast != null)
|
||||
{
|
||||
|
|
@ -95,5 +96,42 @@ namespace MapControl
|
|||
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a LatLonBox in geographic coordinates to a rotated Rect in projected map coordinates.
|
||||
/// Returns null when the LatLonBox can not be transformed.
|
||||
/// </summary>
|
||||
public virtual Tuple<Rect, double> LatLonBoxToMap(LatLonBox latLonBox)
|
||||
{
|
||||
Tuple<Rect, double> rotatedRect = null;
|
||||
Point? center, north, south, west, east;
|
||||
var boxCenter = latLonBox.Center;
|
||||
|
||||
if ((center = LocationToMap(boxCenter)).HasValue &&
|
||||
(north = LocationToMap(new Location(latLonBox.North, boxCenter.Longitude))).HasValue &&
|
||||
(south = LocationToMap(new Location(latLonBox.South, boxCenter.Longitude))).HasValue &&
|
||||
(west = LocationToMap(new Location(boxCenter.Latitude, latLonBox.West))).HasValue &&
|
||||
(east = LocationToMap(new Location(boxCenter.Latitude, latLonBox.East))).HasValue)
|
||||
{
|
||||
var dx1 = east.Value.X - west.Value.X;
|
||||
var dy1 = east.Value.Y - west.Value.Y;
|
||||
var dx2 = north.Value.X - south.Value.X;
|
||||
var dy2 = north.Value.Y - south.Value.Y;
|
||||
var width = Math.Sqrt(dx1 * dx1 + dy1 * dy1);
|
||||
var height = Math.Sqrt(dx2 * dx2 + dy2 * dy2);
|
||||
var x = center.Value.X - width / 2d;
|
||||
var y = center.Value.Y - height / 2d;
|
||||
|
||||
// angles measured relative to horizontal and vertical axis
|
||||
var r1 = (Math.Atan2(dy1, dx1) * 180d / Math.PI + 180d) % 360d - 180d;
|
||||
var r2 = (Math.Atan2(-dx2, dy2) * 180d / Math.PI + 180d) % 360d - 180d;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"{r1}, {r2}");
|
||||
|
||||
rotatedRect = new Tuple<Rect, double>(new Rect(x, y, width, height), latLonBox.Rotation + (r1 + r2) / 2d);
|
||||
}
|
||||
|
||||
return rotatedRect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,12 +232,12 @@ namespace MapControl
|
|||
protected virtual string GetMapRequestUri(BoundingBox boundingBox)
|
||||
{
|
||||
string uri = null;
|
||||
var mapRect = ParentMap.MapProjection.BoundingBoxToMap(boundingBox);
|
||||
var bbox = ParentMap.MapProjection.BoundingBoxToMap(boundingBox);
|
||||
|
||||
if (mapRect.HasValue)
|
||||
if (bbox.HasValue)
|
||||
{
|
||||
var width = ParentMap.ViewTransform.Scale * mapRect.Value.Width;
|
||||
var height = ParentMap.ViewTransform.Scale * mapRect.Value.Height;
|
||||
var width = ParentMap.ViewTransform.Scale * bbox.Value.Width;
|
||||
var height = ParentMap.ViewTransform.Scale * bbox.Value.Height;
|
||||
|
||||
uri = GetRequestUri(new Dictionary<string, string>
|
||||
{
|
||||
|
|
@ -248,7 +248,7 @@ namespace MapControl
|
|||
{ "STYLES", WmsStyles ?? "" },
|
||||
{ "FORMAT", "image/png" },
|
||||
{ "CRS", GetCrsValue() },
|
||||
{ "BBOX", GetBboxValue(boundingBox, mapRect.Value) },
|
||||
{ "BBOX", GetBboxValue(boundingBox, bbox.Value) },
|
||||
{ "WIDTH", Math.Round(width).ToString("F0") },
|
||||
{ "HEIGHT", Math.Round(height).ToString("F0") }
|
||||
});
|
||||
|
|
@ -263,12 +263,12 @@ namespace MapControl
|
|||
protected virtual string GetFeatureInfoRequestUri(BoundingBox boundingBox, Point position, string format)
|
||||
{
|
||||
string uri = null;
|
||||
var mapRect = ParentMap.MapProjection.BoundingBoxToMap(boundingBox);
|
||||
var bbox = ParentMap.MapProjection.BoundingBoxToMap(boundingBox);
|
||||
|
||||
if (mapRect.HasValue)
|
||||
if (bbox.HasValue)
|
||||
{
|
||||
var width = ParentMap.ViewTransform.Scale * mapRect.Value.Width;
|
||||
var height = ParentMap.ViewTransform.Scale * mapRect.Value.Height;
|
||||
var width = ParentMap.ViewTransform.Scale * bbox.Value.Width;
|
||||
var height = ParentMap.ViewTransform.Scale * bbox.Value.Height;
|
||||
|
||||
var transform = ViewTransform.CreateTransformMatrix(
|
||||
-ParentMap.ActualWidth / 2d, -ParentMap.ActualWidth / 2d,
|
||||
|
|
@ -287,7 +287,7 @@ namespace MapControl
|
|||
{ "FORMAT", "image/png" },
|
||||
{ "INFO_FORMAT", format },
|
||||
{ "CRS", GetCrsValue() },
|
||||
{ "BBOX", GetBboxValue(boundingBox, mapRect.Value) },
|
||||
{ "BBOX", GetBboxValue(boundingBox, bbox.Value) },
|
||||
{ "WIDTH", Math.Round(width).ToString("F0") },
|
||||
{ "HEIGHT", Math.Round(height).ToString("F0") },
|
||||
{ "I", Math.Round(imagePos.X).ToString("F0") },
|
||||
|
|
@ -315,7 +315,7 @@ namespace MapControl
|
|||
return crsId;
|
||||
}
|
||||
|
||||
protected virtual string GetBboxValue(BoundingBox boundingBox, Rect mapRect)
|
||||
protected virtual string GetBboxValue(BoundingBox boundingBox, Rect mapBoundingBox)
|
||||
{
|
||||
var crsId = ParentMap.MapProjection.CrsId;
|
||||
string format;
|
||||
|
|
@ -332,10 +332,10 @@ namespace MapControl
|
|||
else
|
||||
{
|
||||
format = "{0:F2},{1:F2},{2:F2},{3:F2}";
|
||||
x1 = mapRect.X;
|
||||
y1 = mapRect.Y;
|
||||
x2 = mapRect.X + mapRect.Width;
|
||||
y2 = mapRect.Y + mapRect.Height;
|
||||
x1 = mapBoundingBox.X;
|
||||
y1 = mapBoundingBox.Y;
|
||||
x2 = mapBoundingBox.X + mapBoundingBox.Width;
|
||||
y2 = mapBoundingBox.Y + mapBoundingBox.Height;
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture, format, x1, y1, x2, y2);
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@
|
|||
<Compile Include="..\Shared\ImageLoader.cs">
|
||||
<Link>ImageLoader.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\LatLonBox.cs">
|
||||
<Link>LatLonBox.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Location.cs">
|
||||
<Link>Location.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
|||
|
|
@ -1,94 +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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
public interface IMapDrawingItem
|
||||
{
|
||||
IEnumerable<Location> Locations { get; }
|
||||
|
||||
Drawing GetDrawing(IList<Point> points, double scale, double rotation);
|
||||
}
|
||||
|
||||
public class MapItemsImageLayer : MapImageLayer
|
||||
{
|
||||
public static readonly DependencyProperty ItemsSourceProperty =
|
||||
DependencyPropertyHelper.Register<MapItemsImageLayer, IEnumerable<IMapDrawingItem>>(nameof(ItemsSource));
|
||||
|
||||
public IEnumerable<IMapDrawingItem> ItemsSource
|
||||
{
|
||||
get => (IEnumerable<IMapDrawingItem>)GetValue(ItemsSourceProperty);
|
||||
set => SetValue(ItemsSourceProperty, value);
|
||||
}
|
||||
|
||||
protected override async Task<ImageSource> GetImageAsync(BoundingBox boundingBox, IProgress<double> progress)
|
||||
{
|
||||
ImageSource image = null;
|
||||
var projection = ParentMap?.MapProjection;
|
||||
var items = ItemsSource;
|
||||
|
||||
if (projection != null && items != null)
|
||||
{
|
||||
var mapRect = projection.BoundingBoxToMap(boundingBox);
|
||||
|
||||
if (mapRect.HasValue)
|
||||
{
|
||||
image = await Task.Run(() => GetImage(projection, mapRect.Value, items));
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private DrawingImage GetImage(MapProjection projection, Rect mapRect, IEnumerable<IMapDrawingItem> items)
|
||||
{
|
||||
var scale = ParentMap.ViewTransform.Scale;
|
||||
var rotation = ParentMap.ViewTransform.Rotation;
|
||||
var drawings = new DrawingGroup();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var points = item.Locations
|
||||
.Select(location => projection.LocationToMap(location))
|
||||
.Where(point => point.HasValue)
|
||||
.Select(point => point.Value)
|
||||
.ToList();
|
||||
|
||||
if (points.Any(point => mapRect.Contains(point)))
|
||||
{
|
||||
for (int i = 0; i < points.Count; i++)
|
||||
{
|
||||
points[i] = new Point(
|
||||
scale * (points[i].X - mapRect.X),
|
||||
scale * ((mapRect.Y + mapRect.Height) - points[i].Y));
|
||||
}
|
||||
|
||||
drawings.Children.Add(item.GetDrawing(points, scale, rotation));
|
||||
}
|
||||
}
|
||||
|
||||
var drawingBrush = new DrawingBrush
|
||||
{
|
||||
Drawing = drawings,
|
||||
ViewboxUnits = BrushMappingMode.Absolute,
|
||||
Viewbox = new Rect(0, 0, scale * mapRect.Width, scale * mapRect.Height),
|
||||
};
|
||||
|
||||
var drawing = new GeometryDrawing(
|
||||
drawingBrush, null, new RectangleGeometry(drawingBrush.Viewbox));
|
||||
|
||||
var image = new DrawingImage(drawing);
|
||||
image.Freeze();
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue