diff --git a/MapControl/Shared/BoundingBox.cs b/MapControl/Shared/BoundingBox.cs
index 27a153bd..42f1e66c 100644
--- a/MapControl/Shared/BoundingBox.cs
+++ b/MapControl/Shared/BoundingBox.cs
@@ -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; }
+ ///
+ /// Indicates whether a MapProjection projects the BoundingBox to an axis-aligned or skewed rectangle.
+ ///
+ public bool ProjectAxisAligned { get; } = projectAxisAligned;
public override string ToString()
{
@@ -41,7 +29,7 @@ namespace MapControl
}
///
- /// 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.
///
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));
}
}
}
diff --git a/MapControl/Shared/GeoImage.cs b/MapControl/Shared/GeoImage.cs
index 0057833d..5fda3faf 100644
--- a/MapControl/Shared/GeoImage.cs
+++ b/MapControl/Shared/GeoImage.cs
@@ -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)
{
diff --git a/MapControl/Shared/GroundOverlay.cs b/MapControl/Shared/GroundOverlay.cs
index f3150b2b..6a3e7156 100644
--- a/MapControl/Shared/GroundOverlay.cs
+++ b/MapControl/Shared/GroundOverlay.cs
@@ -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);
}
}
}
diff --git a/MapControl/Shared/LatLonBox.cs b/MapControl/Shared/LatLonBox.cs
deleted file mode 100644
index 8a26580a..00000000
--- a/MapControl/Shared/LatLonBox.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-namespace MapControl
-{
- ///
- /// A BoundingBox with optional rotation. Used by GeoImage and GroundOverlay.
- ///
- 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;
- }
-
- ///
- /// Gets a counterclockwise rotation angle in degrees.
- ///
- public double Rotation { get; }
- }
-}
diff --git a/MapControl/Shared/Location.cs b/MapControl/Shared/Location.cs
index 1c0e3d1f..1c7a3108 100644
--- a/MapControl/Shared/Location.cs
+++ b/MapControl/Shared/Location.cs
@@ -58,7 +58,7 @@ namespace MapControl
if (!string.IsNullOrEmpty(location))
{
- values = location.Split([',']);
+ values = location.Split(',');
}
if (values?.Length != 2)
diff --git a/MapControl/Shared/LocationCollection.cs b/MapControl/Shared/LocationCollection.cs
index 3a2f95f2..363ff3c3 100644
--- a/MapControl/Shared/LocationCollection.cs
+++ b/MapControl/Shared/LocationCollection.cs
@@ -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));
}
diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs
index f3d77bc9..c45be0ca 100644
--- a/MapControl/Shared/MapBase.cs
+++ b/MapControl/Shared/MapBase.cs
@@ -342,7 +342,7 @@ namespace MapControl
///
public void ZoomToBounds(BoundingBox bounds)
{
- var mapRect = MapProjection.BoundingBoxToMap(bounds);
+ (var mapRect, var _) = MapProjection.BoundingBoxToMap(bounds);
if (mapRect.HasValue)
{
diff --git a/MapControl/Shared/MapImageLayer.cs b/MapControl/Shared/MapImageLayer.cs
index 91475c8e..a1c37032 100644
--- a/MapControl/Shared/MapImageLayer.cs
+++ b/MapControl/Shared/MapImageLayer.cs
@@ -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)
{
diff --git a/MapControl/Shared/MapPanel.cs b/MapControl/Shared/MapPanel.cs
index 484eae3a..e1d7e9ef 100644
--- a/MapControl/Shared/MapPanel.cs
+++ b/MapControl/Shared/MapPanel.cs
@@ -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)
{
diff --git a/MapControl/Shared/MapProjection.cs b/MapControl/Shared/MapProjection.cs
index 06780f6a..fcdb89f2 100644
--- a/MapControl/Shared/MapProjection.cs
+++ b/MapControl/Shared/MapProjection.cs
@@ -138,71 +138,69 @@ namespace MapControl
public Location MapToLocation(Point point) => MapToLocation(point.X, point.Y);
///
- /// 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.
///
- 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);
}
///
/// Transforms a Rect in projected map coordinates to a BoundingBox in geographic coordinates.
/// Returns null when the Rect can not be transformed.
///
- 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;
- }
-
- ///
- /// Transforms a LatLonBox in geographic coordinates to a rotated Rect in projected map coordinates.
- /// Returns (null, null) when the LatLonBox can not be transformed.
- ///
- 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()