mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2026-02-06 15:54:14 +01:00
Removed MapProjection.MapToBoundingBox
This commit is contained in:
parent
4fa2470739
commit
5c8d000378
|
|
@ -44,9 +44,17 @@ namespace MapControl
|
|||
var p2 = transform.Transform(new Point(bitmap.PixelWidth, bitmap.PixelHeight));
|
||||
#endif
|
||||
BitmapSource = bitmap;
|
||||
BoundingBox = projection != null
|
||||
? projection.MapToBoundingBox(new Rect(p1, p2))
|
||||
: new BoundingBox(p1.Y, p1.X, p2.Y, p2.X);
|
||||
|
||||
if (projection != null)
|
||||
{
|
||||
var sw = projection.MapToLocation(p1);
|
||||
var ne = projection.MapToLocation(p2);
|
||||
BoundingBox = new BoundingBox(sw.Latitude, sw.Longitude, ne.Latitude, ne.Longitude);
|
||||
}
|
||||
else
|
||||
{
|
||||
BoundingBox = new BoundingBox(p1.Y, p1.X, p2.Y, p2.X);
|
||||
}
|
||||
}
|
||||
|
||||
public BitmapSource BitmapSource { get; }
|
||||
|
|
|
|||
|
|
@ -195,11 +195,6 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public Location ViewToLocation(Point point) => MapProjection.MapToLocation(ViewTransform.ViewToMap(point));
|
||||
|
||||
/// <summary>
|
||||
/// Gets a BoundingBox in geographic coordinates that covers a rectangle in view coordinates.
|
||||
/// </summary>
|
||||
public BoundingBox ViewToBoundingBox(Rect rect) => MapProjection.MapToBoundingBox(ViewTransform.ViewToMapBounds(rect));
|
||||
|
||||
/// <summary>
|
||||
/// Sets a temporary center point in view coordinates for scaling and rotation transformations.
|
||||
/// This center point is automatically reset when the Center property is set by application code
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@ namespace MapControl
|
|||
public double Rotation => rotation;
|
||||
}
|
||||
|
||||
private const double LineInterpolationResolution = 2d;
|
||||
|
||||
public static readonly DependencyProperty MinLineDistanceProperty =
|
||||
DependencyPropertyHelper.Register<MapGraticule, double>(nameof(MinLineDistance), 150d);
|
||||
|
||||
|
|
@ -75,124 +73,61 @@ namespace MapControl
|
|||
set => SetValue(FontSizeProperty, value);
|
||||
}
|
||||
|
||||
private double PixelPerDegree => Math.Max(1d, ParentMap.ViewTransform.Scale * MapProjection.Wgs84MeterPerDegree);
|
||||
|
||||
private double lineDistance;
|
||||
private string labelFormat;
|
||||
|
||||
private void SetLineDistance()
|
||||
{
|
||||
var minDistance = MinLineDistance / PixelPerDegree;
|
||||
var scale = minDistance < 1d / 60d ? 3600d : minDistance < 1d ? 60d : 1d;
|
||||
minDistance *= scale;
|
||||
|
||||
var lineDistances = new double[] { 1d, 2d, 5d, 10d, 15d, 30d, 60d };
|
||||
var i = 0;
|
||||
|
||||
while (i < lineDistances.Length - 1 && lineDistances[i] < minDistance)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
lineDistance = Math.Min(lineDistances[i] / scale, 30d);
|
||||
|
||||
labelFormat = lineDistance < 1d / 60d ? "{0} {1}°{2:00}'{3:00}\""
|
||||
: lineDistance < 1d ? "{0} {1}°{2:00}'" : "{0} {1}°";
|
||||
}
|
||||
|
||||
private string GetLabelText(double value, string hemispheres)
|
||||
{
|
||||
var hemisphere = hemispheres[0];
|
||||
|
||||
if (value < -1e-8) // ~1 mm
|
||||
{
|
||||
value = -value;
|
||||
hemisphere = hemispheres[1];
|
||||
}
|
||||
|
||||
var seconds = (int)Math.Round(value * 3600d);
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
labelFormat, hemisphere, seconds / 3600, seconds / 60 % 60, seconds % 60);
|
||||
}
|
||||
|
||||
private void AddLabel(List<Label> labels, double latitude, double longitude, Point position, double? rotation = null)
|
||||
{
|
||||
if (position.X >= 0d && position.X <= ParentMap.ActualWidth &&
|
||||
position.Y >= 0d && position.Y <= ParentMap.ActualHeight)
|
||||
{
|
||||
if (!rotation.HasValue)
|
||||
{
|
||||
// Get rotation from second location with same latitude.
|
||||
//
|
||||
var pos = ParentMap.LocationToView(latitude, longitude + 10d / PixelPerDegree);
|
||||
|
||||
rotation = Math.Atan2(pos.Y - position.Y, pos.X - position.X) * 180d / Math.PI;
|
||||
}
|
||||
|
||||
if (rotation.HasValue)
|
||||
{
|
||||
labels.Add(new Label(
|
||||
GetLabelText(latitude, "NS"),
|
||||
GetLabelText(Location.NormalizeLongitude(longitude), "EW"),
|
||||
position.X, position.Y, rotation.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Label> DrawGraticule(PathFigureCollection figures)
|
||||
{
|
||||
var labels = new List<Label>();
|
||||
|
||||
figures.Clear();
|
||||
|
||||
SetLineDistance();
|
||||
var labels = new List<Label>();
|
||||
var lineDistance = GetLineDistance();
|
||||
var labelFormat = lineDistance < 1d / 60d ? "{0} {1}°{2:00}'{3:00}\""
|
||||
: lineDistance < 1d ? "{0} {1}°{2:00}'" : "{0} {1}°";
|
||||
|
||||
if (ParentMap.MapProjection.IsNormalCylindrical)
|
||||
{
|
||||
DrawCylindricalGraticule(figures, labels);
|
||||
DrawCylindricalGraticule(figures, labels, lineDistance, labelFormat);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawGraticule(figures, labels);
|
||||
DrawGraticule(figures, labels, lineDistance, labelFormat);
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
private void DrawCylindricalGraticule(PathFigureCollection figures, List<Label> labels)
|
||||
private void DrawCylindricalGraticule(PathFigureCollection figures, List<Label> labels, double lineDistance, string labelFormat)
|
||||
{
|
||||
var boundingBox = ParentMap.ViewToBoundingBox(new Rect(0d, 0d, ParentMap.ActualWidth, ParentMap.ActualHeight));
|
||||
var latLabelStart = Math.Ceiling(boundingBox.South / lineDistance) * lineDistance;
|
||||
var lonLabelStart = Math.Ceiling(boundingBox.West / lineDistance) * lineDistance;
|
||||
var southWest = ParentMap.ViewToLocation(new Point(0d, ParentMap.ActualHeight));
|
||||
var northEast = ParentMap.ViewToLocation(new Point(ParentMap.ActualWidth, 0d));
|
||||
|
||||
for (var lat = latLabelStart; lat <= boundingBox.North; lat += lineDistance)
|
||||
var latLabelStart = Math.Ceiling(southWest.Latitude / lineDistance) * lineDistance;
|
||||
var lonLabelStart = Math.Ceiling(southWest.Longitude / lineDistance) * lineDistance;
|
||||
|
||||
for (var lat = latLabelStart; lat <= northEast.Latitude; lat += lineDistance)
|
||||
{
|
||||
var p1 = ParentMap.LocationToView(lat, boundingBox.West);
|
||||
var p2 = ParentMap.LocationToView(lat, boundingBox.East);
|
||||
var p1 = ParentMap.LocationToView(lat, southWest.Longitude);
|
||||
var p2 = ParentMap.LocationToView(lat, northEast.Longitude);
|
||||
figures.Add(CreateLineFigure(p1, p2));
|
||||
}
|
||||
|
||||
for (var lon = lonLabelStart; lon <= boundingBox.East; lon += lineDistance)
|
||||
for (var lon = lonLabelStart; lon <= northEast.Longitude; lon += lineDistance)
|
||||
{
|
||||
var p1 = ParentMap.LocationToView(boundingBox.South, lon);
|
||||
var p2 = ParentMap.LocationToView(boundingBox.North, lon);
|
||||
var p1 = ParentMap.LocationToView(southWest.Latitude, lon);
|
||||
var p2 = ParentMap.LocationToView(northEast.Latitude, lon);
|
||||
figures.Add(CreateLineFigure(p1, p2));
|
||||
|
||||
for (var lat = latLabelStart; lat <= boundingBox.North; lat += lineDistance)
|
||||
for (var lat = latLabelStart; lat <= northEast.Latitude; lat += lineDistance)
|
||||
{
|
||||
var position = ParentMap.LocationToView(lat, lon);
|
||||
AddLabel(labels, lat, lon, position, ParentMap.ViewTransform.Rotation);
|
||||
AddLabel(labels, labelFormat, lat, lon, ParentMap.LocationToView(lat, lon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGraticule(PathFigureCollection figures, List<Label> labels)
|
||||
private void DrawGraticule(PathFigureCollection figures, List<Label> labels, double lineDistance, string labelFormat)
|
||||
{
|
||||
GetLatitudeRange(lineDistance, out double minLat, out double maxLat);
|
||||
|
||||
var latSegments = (int)Math.Round(Math.Abs(maxLat - minLat) / lineDistance);
|
||||
var interpolationCount = Math.Max(1, (int)Math.Ceiling(lineDistance / LineInterpolationResolution));
|
||||
var interpolationCount = Math.Max(1, (int)Math.Ceiling(lineDistance));
|
||||
var interpolationDistance = lineDistance / interpolationCount;
|
||||
var latPoints = latSegments * interpolationCount;
|
||||
var centerLon = Math.Round(ParentMap.Center.Longitude / lineDistance) * lineDistance;
|
||||
|
|
@ -221,21 +156,25 @@ namespace MapControl
|
|||
var lat = minLat + i * lineDistance;
|
||||
var lon = minLon;
|
||||
var points = new List<Point>();
|
||||
var position = ParentMap.LocationToView(lat, lon);
|
||||
var mapPoint = ParentMap.MapProjection.LocationToMap(lat, lon);
|
||||
var position = ParentMap.ViewTransform.MapToView(mapPoint);
|
||||
var rotation = -ParentMap.MapProjection.GridConvergence(mapPoint.X, mapPoint.Y);
|
||||
|
||||
points.Add(position);
|
||||
AddLabel(labels, lat, lon, position);
|
||||
AddLabel(labels, labelFormat, lat, lon, position, rotation);
|
||||
|
||||
for (int j = 0; j < lonSegments; j++)
|
||||
{
|
||||
for (int k = 1; k <= interpolationCount; k++)
|
||||
{
|
||||
lon = minLon + j * lineDistance + k * interpolationDistance;
|
||||
position = ParentMap.LocationToView(lat, lon);
|
||||
mapPoint = ParentMap.MapProjection.LocationToMap(lat, lon);
|
||||
position = ParentMap.ViewTransform.MapToView(mapPoint);
|
||||
points.Add(position);
|
||||
}
|
||||
|
||||
AddLabel(labels, lat, lon, position);
|
||||
rotation = -ParentMap.MapProjection.GridConvergence(mapPoint.X, mapPoint.Y);
|
||||
AddLabel(labels, labelFormat, lat, lon, position, rotation);
|
||||
}
|
||||
|
||||
if (points.Count >= 2)
|
||||
|
|
@ -262,7 +201,7 @@ namespace MapControl
|
|||
points.Add(p);
|
||||
}
|
||||
|
||||
if (points.Count >= 2)
|
||||
if (visible && points.Count >= 2)
|
||||
{
|
||||
figures.Add(CreatePolylineFigure(points));
|
||||
}
|
||||
|
|
@ -274,19 +213,31 @@ namespace MapControl
|
|||
{
|
||||
var width = ParentMap.ActualWidth;
|
||||
var height = ParentMap.ActualHeight;
|
||||
var locations = new Location[]
|
||||
var locations = new List<Location>
|
||||
{
|
||||
ParentMap.ViewToLocation(new Point(0d, 0d)),
|
||||
ParentMap.ViewToLocation(new Point(width / 2d, 0d)),
|
||||
ParentMap.ViewToLocation(new Point(width, 0d)),
|
||||
ParentMap.ViewToLocation(new Point(width, height / 2d)),
|
||||
ParentMap.ViewToLocation(new Point(width, height)),
|
||||
ParentMap.ViewToLocation(new Point(width / 2d, height)),
|
||||
ParentMap.ViewToLocation(new Point(0d, height)),
|
||||
ParentMap.ViewToLocation(new Point(0d, height / 2)),
|
||||
ParentMap.ViewToLocation(new Point(width, height)),
|
||||
ParentMap.ViewToLocation(new Point(width / 2d, 0d)),
|
||||
ParentMap.ViewToLocation(new Point(width / 2d, height)),
|
||||
ParentMap.ViewToLocation(new Point(0d, height / 2d)),
|
||||
ParentMap.ViewToLocation(new Point(width, height / 2d)),
|
||||
};
|
||||
|
||||
var latitudes = locations.Where(loc => loc != null).Select(loc => loc.Latitude).Distinct();
|
||||
var pole = ParentMap.LocationToView(90d, 0d);
|
||||
if (pole.X >= 0d && pole.X <= width && pole.Y >= 0d && pole.Y <= height)
|
||||
{
|
||||
locations.Add(new Location(90d, 0d));
|
||||
}
|
||||
|
||||
pole = ParentMap.LocationToView(-90d, 0d);
|
||||
if (pole.X >= 0d && pole.X <= width && pole.Y >= 0d && pole.Y <= height)
|
||||
{
|
||||
locations.Add(new Location(-90d, 0d));
|
||||
}
|
||||
|
||||
var latitudes = locations.Select(loc => loc.Latitude).Distinct();
|
||||
var south = -90d;
|
||||
var north = 90d;
|
||||
|
||||
|
|
@ -296,8 +247,71 @@ namespace MapControl
|
|||
north = latitudes.Max();
|
||||
}
|
||||
|
||||
minLatitude = Math.Max(Math.Floor(south / lineDistance) * lineDistance, -90d);
|
||||
maxLatitude = Math.Min(Math.Ceiling(north / lineDistance) * lineDistance, 90d);
|
||||
minLatitude = Math.Max(Math.Floor(south / lineDistance - 1d) * lineDistance, -90d);
|
||||
maxLatitude = Math.Min(Math.Ceiling(north / lineDistance + 1d) * lineDistance, 90d);
|
||||
}
|
||||
|
||||
private double GetLineDistance()
|
||||
{
|
||||
var pixelPerDegree = ParentMap.ViewTransform.Scale * MapProjection.Wgs84MeterPerDegree;
|
||||
|
||||
if (!ParentMap.MapProjection.IsNormalCylindrical)
|
||||
{
|
||||
pixelPerDegree *= Math.Cos(ParentMap.Center.Latitude * Math.PI / 180d);
|
||||
}
|
||||
|
||||
var minDistance = MinLineDistance / Math.Max(1d, pixelPerDegree);
|
||||
var scale = minDistance < 1d / 60d ? 3600d : minDistance < 1d ? 60d : 1d;
|
||||
minDistance *= scale;
|
||||
|
||||
var lineDistances = new double[] { 1d, 2d, 5d, 10d, 15d, 30d, 60d };
|
||||
var i = 0;
|
||||
|
||||
while (i < lineDistances.Length - 1 && lineDistances[i] < minDistance)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
return Math.Min(lineDistances[i] / scale, 60d);
|
||||
}
|
||||
|
||||
private void AddLabel(List<Label> labels, string labelFormat, double latitude, double longitude, Point position, double rotation = 0d)
|
||||
{
|
||||
if (position.X >= 0d && position.X <= ParentMap.ActualWidth &&
|
||||
position.Y >= 0d && position.Y <= ParentMap.ActualHeight)
|
||||
{
|
||||
rotation = (rotation + ParentMap.ViewTransform.Rotation) % 360d;
|
||||
|
||||
if (rotation < -90d)
|
||||
{
|
||||
rotation += 180d;
|
||||
}
|
||||
else if (rotation > 90d)
|
||||
{
|
||||
rotation -= 180d;
|
||||
}
|
||||
|
||||
labels.Add(new Label(
|
||||
GetLabelText(latitude, labelFormat, "NS"),
|
||||
GetLabelText(Location.NormalizeLongitude(longitude), labelFormat, "EW"),
|
||||
position.X, position.Y, rotation));
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLabelText(double value, string labelFormat, string hemispheres)
|
||||
{
|
||||
var hemisphere = hemispheres[0];
|
||||
|
||||
if (value < -1e-8) // ~1 mm
|
||||
{
|
||||
value = -value;
|
||||
hemisphere = hemispheres[1];
|
||||
}
|
||||
|
||||
var seconds = (int)Math.Round(value * 3600d);
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture,
|
||||
labelFormat, hemisphere, seconds / 3600, seconds / 60 % 60, seconds % 60);
|
||||
}
|
||||
|
||||
private static PathFigure CreateLineFigure(Point p1, Point p2)
|
||||
|
|
|
|||
|
|
@ -59,13 +59,6 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public abstract Matrix RelativeTransform(double latitude, double longitude);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the grid convergence angle in degrees at the specified projected map coordinates.
|
||||
/// Used for rotating the Rect resulting from BoundingBoxToMap in non-normal-cylindrical
|
||||
/// projections, i.e. Transverse Mercator and Polar Stereographic.
|
||||
/// </summary>
|
||||
public virtual double GridConvergence(double x, double y) => 0d;
|
||||
|
||||
/// <summary>
|
||||
/// Transforms geographic coordinates to a Point in projected map coordinates.
|
||||
/// </summary>
|
||||
|
|
@ -76,6 +69,13 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public abstract Location MapToLocation(double x, double y);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the grid convergence angle in degrees at the specified projected map coordinates.
|
||||
/// Used for rotating the Rect resulting from BoundingBoxToMap in non-normal-cylindrical
|
||||
/// projections, i.e. Transverse Mercator and Polar Stereographic.
|
||||
/// </summary>
|
||||
public virtual double GridConvergence(double x, double y) => 0d;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relative transform at the specified geographic Location.
|
||||
/// </summary>
|
||||
|
|
@ -93,7 +93,7 @@ namespace MapControl
|
|||
|
||||
/// <summary>
|
||||
/// Transforms a BoundingBox in geographic coordinates to a Rect in projected map coordinates
|
||||
/// with an optional rotation angle in degrees.
|
||||
/// with an optional rotation angle in degrees for non-normal-cylindrical projections.
|
||||
/// </summary>
|
||||
public (Rect, double) BoundingBoxToMap(BoundingBox boundingBox)
|
||||
{
|
||||
|
|
@ -124,24 +124,12 @@ namespace MapControl
|
|||
var height = Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
rect = new Rect(centerX - width / 2d, centerY - height / 2d, width, height);
|
||||
|
||||
rotation = -GridConvergence(centerX, centerY); // invert direction for RotateTransform
|
||||
}
|
||||
|
||||
return (rect, rotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a Rect in projected map coordinates to a BoundingBox in geographic coordinates.
|
||||
/// </summary>
|
||||
public BoundingBox MapToBoundingBox(Rect rect)
|
||||
{
|
||||
var southWest = MapToLocation(rect.X, rect.Y);
|
||||
var northEast = MapToLocation(rect.X + rect.Width, rect.Y + rect.Height);
|
||||
|
||||
return new BoundingBox(southWest.Latitude, southWest.Longitude, northEast.Latitude, northEast.Longitude);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return CrsId;
|
||||
|
|
|
|||
Loading…
Reference in a new issue