LatLonBox transformation

This commit is contained in:
ClemensFischer 2024-09-09 16:44:45 +02:00
parent f143bdb5e6
commit 8efcee2585
10 changed files with 145 additions and 145 deletions

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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