mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2026-01-31 04:44:14 +01:00
Removed LatLonBox
The rotation behavior of a KML LatLonBox has no sufficiently precise specification.
This commit is contained in:
parent
6c89dfcdae
commit
a0e82964ef
|
|
@ -11,29 +11,17 @@ namespace MapControl
|
|||
#else
|
||||
[System.ComponentModel.TypeConverter(typeof(BoundingBoxConverter))]
|
||||
#endif
|
||||
public class BoundingBox
|
||||
public class BoundingBox(double latitude1, double longitude1, double latitude2, double longitude2, bool projectAxisAligned = false)
|
||||
{
|
||||
protected BoundingBox()
|
||||
{
|
||||
}
|
||||
public double South { get; } = Math.Min(Math.Max(Math.Min(latitude1, latitude2), -90d), 90d);
|
||||
public double North { get; } = Math.Min(Math.Max(Math.Max(latitude1, latitude2), -90d), 90d);
|
||||
public double West { get; } = Math.Min(longitude1, longitude2);
|
||||
public double East { get; } = Math.Max(longitude1, longitude2);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public BoundingBox(Location location1, Location location2)
|
||||
: this(location1.Latitude, location1.Longitude, location2.Latitude, location2.Longitude)
|
||||
{
|
||||
}
|
||||
|
||||
public double South { get; }
|
||||
public double North { get; }
|
||||
public double West { get; }
|
||||
public double East { get; }
|
||||
/// <summary>
|
||||
/// Indicates whether a MapProjection projects the BoundingBox to an axis-aligned or skewed rectangle.
|
||||
/// </summary>
|
||||
public bool ProjectAxisAligned { get; } = projectAxisAligned;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
|
@ -41,7 +29,7 @@ namespace MapControl
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a BoundingBox instance from a string containing a comma-separated sequence of four or five floating point numbers.
|
||||
/// Creates a BoundingBox instance from a string containing a comma-separated sequence of four floating point numbers.
|
||||
/// </summary>
|
||||
public static BoundingBox Parse(string boundingBox)
|
||||
{
|
||||
|
|
@ -49,26 +37,19 @@ namespace MapControl
|
|||
|
||||
if (!string.IsNullOrEmpty(boundingBox))
|
||||
{
|
||||
values = boundingBox.Split(new char[] { ',' });
|
||||
values = boundingBox.Split(',');
|
||||
}
|
||||
|
||||
if (values == null || values.Length != 4 && values.Length != 5)
|
||||
{
|
||||
throw new FormatException($"{nameof(BoundingBox)} string must contain a comma-separated sequence of four or five floating point numbers.");
|
||||
throw new FormatException($"{nameof(BoundingBox)} string must contain a comma-separated sequence of four floating point numbers.");
|
||||
}
|
||||
|
||||
var rotation = values.Length == 5
|
||||
? double.Parse(values[4], NumberStyles.Float, CultureInfo.InvariantCulture)
|
||||
: 0d;
|
||||
|
||||
// Always return a LatLonBox, i.e. a BoundingBox with optional rotation, as used by GeoImage and GroundOverlay.
|
||||
//
|
||||
return new LatLonBox(
|
||||
return new BoundingBox(
|
||||
double.Parse(values[0], NumberStyles.Float, CultureInfo.InvariantCulture),
|
||||
double.Parse(values[1], NumberStyles.Float, CultureInfo.InvariantCulture),
|
||||
double.Parse(values[2], NumberStyles.Float, CultureInfo.InvariantCulture),
|
||||
double.Parse(values[3], NumberStyles.Float, CultureInfo.InvariantCulture),
|
||||
rotation);
|
||||
double.Parse(values[3], NumberStyles.Float, CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,13 +44,13 @@ namespace MapControl
|
|||
var p2 = transform.Transform(new Point(bitmap.PixelWidth, bitmap.PixelHeight));
|
||||
#endif
|
||||
BitmapSource = bitmap;
|
||||
LatLonBox = projection != null
|
||||
? new LatLonBox(projection.MapToBoundingBox(new Rect(p1, p2)))
|
||||
: new LatLonBox(p1.Y, p1.X, p2.Y, p2.X);
|
||||
BoundingBox = projection != null
|
||||
? projection.MapToBoundingBox(new Rect(p1, p2))
|
||||
: new BoundingBox(p1.Y, p1.X, p2.Y, p2.X);
|
||||
}
|
||||
|
||||
public BitmapSource BitmapSource { get; }
|
||||
public LatLonBox LatLonBox { get; }
|
||||
public BoundingBox BoundingBox { get; }
|
||||
}
|
||||
|
||||
private const ushort ProjectedCRSGeoKey = 3072;
|
||||
|
|
@ -124,7 +124,7 @@ namespace MapControl
|
|||
};
|
||||
}
|
||||
|
||||
MapPanel.SetBoundingBox(element, geoBitmap.LatLonBox);
|
||||
MapPanel.SetBoundingBox(element, geoBitmap.BoundingBox);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace MapControl
|
|||
{
|
||||
private class ImageOverlay
|
||||
{
|
||||
public ImageOverlay(string path, LatLonBox latLonBox, int zIndex)
|
||||
public ImageOverlay(string path, BoundingBox latLonBox, int zIndex)
|
||||
{
|
||||
ImagePath = path;
|
||||
SetBoundingBox(Image, latLonBox);
|
||||
|
|
@ -191,14 +191,13 @@ namespace MapControl
|
|||
return imageOverlays;
|
||||
}
|
||||
|
||||
private static LatLonBox ReadLatLonBox(XElement latLonBoxElement)
|
||||
private static BoundingBox ReadLatLonBox(XElement latLonBoxElement)
|
||||
{
|
||||
var ns = latLonBoxElement.Name.Namespace;
|
||||
var north = double.NaN;
|
||||
var south = double.NaN;
|
||||
var east = double.NaN;
|
||||
var west = double.NaN;
|
||||
var rotation = 0d;
|
||||
|
||||
var value = latLonBoxElement.Element(ns + "north")?.Value;
|
||||
if (value != null)
|
||||
|
|
@ -224,12 +223,6 @@ namespace MapControl
|
|||
west = double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
value = latLonBoxElement.Element(ns + "rotation")?.Value;
|
||||
if (value != null)
|
||||
{
|
||||
rotation = double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (double.IsNaN(north) || double.IsNaN(south) ||
|
||||
double.IsNaN(east) || double.IsNaN(west) ||
|
||||
north <= south || east <= west)
|
||||
|
|
@ -237,7 +230,7 @@ namespace MapControl
|
|||
throw new FormatException("Invalid LatLonBox");
|
||||
}
|
||||
|
||||
return new LatLonBox(south, west, north, east, rotation);
|
||||
return new BoundingBox(south, west, north, east);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
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 = 0d)
|
||||
: base(latitude1, longitude1, latitude2, longitude2)
|
||||
{
|
||||
Rotation = rotation;
|
||||
}
|
||||
|
||||
public LatLonBox(Location location1, Location location2, double rotation = 0d)
|
||||
: base(location1, location2)
|
||||
{
|
||||
Rotation = rotation;
|
||||
}
|
||||
|
||||
public LatLonBox(BoundingBox boundingBox, double rotation = 0d)
|
||||
: base(boundingBox.South, boundingBox.West, boundingBox.North, boundingBox.East)
|
||||
{
|
||||
Rotation = rotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a counterclockwise rotation angle in degrees.
|
||||
/// </summary>
|
||||
public double Rotation { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ namespace MapControl
|
|||
|
||||
if (!string.IsNullOrEmpty(location))
|
||||
{
|
||||
values = location.Split([',']);
|
||||
values = location.Split(',');
|
||||
}
|
||||
|
||||
if (values?.Length != 2)
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ namespace MapControl
|
|||
return string.IsNullOrEmpty(locations)
|
||||
? new LocationCollection()
|
||||
: new LocationCollection(locations
|
||||
.Split(new char[] { ' ', ';' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Split([' ', ';'], StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(Location.Parse));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public void ZoomToBounds(BoundingBox bounds)
|
||||
{
|
||||
var mapRect = MapProjection.BoundingBoxToMap(bounds);
|
||||
(var mapRect, var _) = MapProjection.BoundingBoxToMap(bounds);
|
||||
|
||||
if (mapRect.HasValue)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ namespace MapControl
|
|||
var x = (ParentMap.ActualWidth - width) / 2d;
|
||||
var y = (ParentMap.ActualHeight - height) / 2d;
|
||||
var mapRect = ParentMap.ViewTransform.ViewToMapBounds(new Rect(x, y, width, height));
|
||||
boundingBox = ParentMap.MapProjection.MapToBoundingBox(mapRect);
|
||||
boundingBox = ParentMap.MapProjection.MapToBoundingBox(mapRect, true);
|
||||
|
||||
if (boundingBox != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -286,17 +286,7 @@ namespace MapControl
|
|||
|
||||
private void ArrangeElement(FrameworkElement element, BoundingBox boundingBox)
|
||||
{
|
||||
Rect? mapRect;
|
||||
Matrix? transform = null;
|
||||
|
||||
if (boundingBox is LatLonBox latLonBox)
|
||||
{
|
||||
(mapRect, transform) = parentMap.MapProjection.LatLonBoxToMap(latLonBox);
|
||||
}
|
||||
else
|
||||
{
|
||||
mapRect = parentMap.MapProjection.BoundingBoxToMap(boundingBox);
|
||||
}
|
||||
(var mapRect, var transform) = parentMap.MapProjection.BoundingBoxToMap(boundingBox);
|
||||
|
||||
if (mapRect.HasValue)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -138,71 +138,69 @@ namespace MapControl
|
|||
public Location MapToLocation(Point point) => MapToLocation(point.X, point.Y);
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a BoundingBox in geographic coordinates to a Rect in projected map coordinates.
|
||||
/// Returns null when the BoundingBox can not be transformed.
|
||||
/// Transforms a BoundingBox in geographic coordinates to a Rect in projected map coordinates
|
||||
/// with an optional transform Matrix when the BoundingBox is not projected axis-aligned.
|
||||
/// Returns (null, null) when the BoundingBox can not be transformed.
|
||||
/// </summary>
|
||||
public Rect? BoundingBoxToMap(BoundingBox boundingBox)
|
||||
public (Rect?, Matrix?) BoundingBoxToMap(BoundingBox boundingBox)
|
||||
{
|
||||
Rect? rect = null;
|
||||
Matrix? transform = null;
|
||||
var sw = LocationToMap(boundingBox.South, boundingBox.West);
|
||||
var ne = LocationToMap(boundingBox.North, boundingBox.East);
|
||||
|
||||
return sw.HasValue && ne.HasValue ? new Rect(sw.Value, ne.Value) : null;
|
||||
if (sw.HasValue && ne.HasValue)
|
||||
{
|
||||
if (boundingBox.ProjectAxisAligned)
|
||||
{
|
||||
rect = new Rect(sw.Value, ne.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var se = LocationToMap(boundingBox.South, boundingBox.East);
|
||||
var nw = LocationToMap(boundingBox.North, boundingBox.West);
|
||||
|
||||
if (se.HasValue && nw.HasValue)
|
||||
{
|
||||
var south = new Point((sw.Value.X + se.Value.X) / 2d, (sw.Value.Y + se.Value.Y) / 2d); // south midpoint
|
||||
var north = new Point((nw.Value.X + ne.Value.X) / 2d, (nw.Value.Y + ne.Value.Y) / 2d); // north midpoint
|
||||
var west = new Point((nw.Value.X + sw.Value.X) / 2d, (nw.Value.Y + sw.Value.Y) / 2d); // west midpoint
|
||||
var east = new Point((ne.Value.X + se.Value.X) / 2d, (ne.Value.Y + se.Value.Y) / 2d); // east midpoint
|
||||
var center = new Point((west.X + east.X) / 2d, (west.Y + east.Y) / 2d); // midpoint of segment west-east
|
||||
var dx1 = east.X - west.X;
|
||||
var dy1 = east.Y - west.Y;
|
||||
var dx2 = north.X - south.X;
|
||||
var dy2 = north.Y - south.Y;
|
||||
var width = Math.Sqrt(dx1 * dx1 + dy1 * dy1); // distance west-east
|
||||
var height = Math.Sqrt(dx2 * dx2 + dy2 * dy2); // distance south-north
|
||||
|
||||
rect = new Rect(center.X - width / 2d, center.Y - height / 2d, width, height);
|
||||
|
||||
if (dy1 != 0d || dx2 != 0d)
|
||||
{
|
||||
// Skew matrix with skewX = Atan(-dx2 / dy2) and skewY = Atan(-dy1 / dx1).
|
||||
//
|
||||
transform = new Matrix(1d, -dy1 / dx1, -dx2 / dy2, 1d, 0d, 0d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (rect, transform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a Rect in projected map coordinates to a BoundingBox in geographic coordinates.
|
||||
/// Returns null when the Rect can not be transformed.
|
||||
/// </summary>
|
||||
public BoundingBox MapToBoundingBox(Rect rect)
|
||||
public BoundingBox MapToBoundingBox(Rect rect, bool axisAligned = false)
|
||||
{
|
||||
var sw = MapToLocation(rect.X, rect.Y);
|
||||
var ne = MapToLocation(rect.X + rect.Width, rect.Y + rect.Height);
|
||||
|
||||
return sw != null && ne != null ? new BoundingBox(sw, ne) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a LatLonBox in geographic coordinates to a rotated Rect in projected map coordinates.
|
||||
/// Returns (null, null) when the LatLonBox can not be transformed.
|
||||
/// </summary>
|
||||
public (Rect?, Matrix?) LatLonBoxToMap(LatLonBox latLonBox)
|
||||
{
|
||||
Rect? rect = null;
|
||||
Matrix? transform = null;
|
||||
var sw = LocationToMap(latLonBox.South, latLonBox.West);
|
||||
var se = LocationToMap(latLonBox.South, latLonBox.East);
|
||||
var nw = LocationToMap(latLonBox.North, latLonBox.West);
|
||||
var ne = LocationToMap(latLonBox.North, latLonBox.East);
|
||||
|
||||
if (sw.HasValue && se.HasValue && nw.HasValue && ne.HasValue)
|
||||
{
|
||||
var south = new Point((sw.Value.X + se.Value.X) / 2d, (sw.Value.Y + se.Value.Y) / 2d); // south midpoint
|
||||
var north = new Point((nw.Value.X + ne.Value.X) / 2d, (nw.Value.Y + ne.Value.Y) / 2d); // north midpoint
|
||||
var west = new Point((nw.Value.X + sw.Value.X) / 2d, (nw.Value.Y + sw.Value.Y) / 2d); // west midpoint
|
||||
var east = new Point((ne.Value.X + se.Value.X) / 2d, (ne.Value.Y + se.Value.Y) / 2d); // east midpoint
|
||||
var center = new Point((west.X + east.X) / 2d, (west.Y + east.Y) / 2d); // midpoint of segment west-east
|
||||
var dx1 = east.X - west.X;
|
||||
var dy1 = east.Y - west.Y;
|
||||
var dx2 = north.X - south.X;
|
||||
var dy2 = north.Y - south.Y;
|
||||
var width = Math.Sqrt(dx1 * dx1 + dy1 * dy1); // distance west-east
|
||||
var height = Math.Sqrt(dx2 * dx2 + dy2 * dy2); // distance south-north
|
||||
var x = center.X - width / 2d;
|
||||
var y = center.Y - height / 2d;
|
||||
|
||||
rect = new Rect(x, y, width, height);
|
||||
|
||||
if (dy1 != 0d || dx2 != 0d || latLonBox.Rotation != 0d)
|
||||
{
|
||||
// Skew matrix with skewX = Atan(-dx2 / dy2) and skewY = Atan(-dy1 / dx1).
|
||||
//
|
||||
var t = new Matrix(1d, -dy1 / dx1, -dx2 / dy2, 1d, 0d, 0d);
|
||||
t.Rotate(-latLonBox.Rotation);
|
||||
transform = t;
|
||||
}
|
||||
}
|
||||
|
||||
return (rect, transform);
|
||||
return sw != null && ne != null
|
||||
? new BoundingBox(sw.Latitude, sw.Longitude, ne.Latitude, ne.Longitude, axisAligned)
|
||||
: null;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
|
|
|||
Loading…
Reference in a new issue