mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2026-02-06 15:54:14 +01:00
Updated MapProjection and MapGraticule
This commit is contained in:
parent
2a45c1165c
commit
cfed3575ac
|
|
@ -4,7 +4,6 @@ using Avalonia.Controls.Documents;
|
|||
using Avalonia.Media;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
|
|
@ -96,17 +95,9 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
private static PathFigure CreatePolylineFigure(IEnumerable<Point> points)
|
||||
private static PolyLineSegment CreatePolyLineSegment(IEnumerable<Point> points)
|
||||
{
|
||||
var figure = new PathFigure
|
||||
{
|
||||
StartPoint = points.First(),
|
||||
IsClosed = false,
|
||||
IsFilled = false
|
||||
};
|
||||
|
||||
figure.Segments.Add(new PolyLineSegment(points.Skip(1)));
|
||||
return figure;
|
||||
return new PolyLineSegment(points);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,13 +184,13 @@ namespace MapControl
|
|||
throw new ArgumentException($"Insufficient number of parameters in world file {worldFilePath}.");
|
||||
}
|
||||
|
||||
return new Matrix(
|
||||
parameters[0], // line 1: A or M11
|
||||
parameters[1], // line 2: D or M12
|
||||
parameters[2], // line 3: B or M21
|
||||
parameters[3], // line 4: E or M22
|
||||
parameters[4], // line 5: C or OffsetX
|
||||
parameters[5]); // line 6: F or OffsetY
|
||||
return new Matrix( // https://en.wikipedia.org/wiki/World_file
|
||||
parameters[0], // line 1: A
|
||||
parameters[1], // line 2: D
|
||||
parameters[2], // line 3: B
|
||||
parameters[3], // line 4: E
|
||||
parameters[4], // line 5: C
|
||||
parameters[5]); // line 6: F
|
||||
}
|
||||
|
||||
private static MapProjection GetProjection(short[] geoKeyDirectory)
|
||||
|
|
|
|||
|
|
@ -375,8 +375,7 @@ namespace MapControl
|
|||
|
||||
if (projection.IsNormalCylindrical)
|
||||
{
|
||||
var maxLocation = projection.MapToLocation(0d, 180d * MapProjection.Wgs84MeterPerDegree);
|
||||
maxLatitude = maxLocation.Latitude;
|
||||
maxLatitude = projection.MapToLocation(0d, 180d * MapProjection.Wgs84MeterPerDegree).Latitude;
|
||||
Center = CoerceCenterProperty(Center);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@ namespace MapControl
|
|||
public static readonly DependencyProperty StrokeThicknessProperty =
|
||||
DependencyPropertyHelper.Register<MapGraticule, double>(nameof(StrokeThickness), 0.5);
|
||||
|
||||
private static readonly double[] lineDistances = [
|
||||
1d/3600d, 1d/1800d, 1d/720d, 1d/360d, 1d/240d, 1d/120d,
|
||||
1d/60d, 1d/30d, 1d/12d, 1d/6d, 1d/4d, 1d/2d,
|
||||
1d, 2d, 5d, 10d, 15d, 30d];
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Minimum graticule line distance in pixels. The default value is 150.
|
||||
/// </summary>
|
||||
|
|
@ -75,32 +81,31 @@ namespace MapControl
|
|||
|
||||
private List<Label> DrawGraticule(PathFigureCollection figures)
|
||||
{
|
||||
figures.Clear();
|
||||
|
||||
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}°";
|
||||
|
||||
figures.Clear();
|
||||
|
||||
if (ParentMap.MapProjection.IsNormalCylindrical)
|
||||
{
|
||||
DrawCylindricalGraticule(figures, labels, lineDistance, labelFormat);
|
||||
DrawNormalCylindrical(figures, labels);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawGraticule(figures, labels, lineDistance, labelFormat);
|
||||
DrawGraticule(figures, labels);
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
private void DrawCylindricalGraticule(PathFigureCollection figures, List<Label> labels, double lineDistance, string labelFormat)
|
||||
private void DrawNormalCylindrical(PathFigureCollection figures, List<Label> labels)
|
||||
{
|
||||
var southWest = ParentMap.ViewToLocation(new Point(0d, ParentMap.ActualHeight));
|
||||
var northEast = ParentMap.ViewToLocation(new Point(ParentMap.ActualWidth, 0d));
|
||||
|
||||
var lineDistance = GetLineDistance(MinLineDistance /
|
||||
(ParentMap.ViewTransform.Scale * MapProjection.Wgs84MeterPerDegree));
|
||||
var latLabelStart = Math.Ceiling(southWest.Latitude / lineDistance) * lineDistance;
|
||||
var lonLabelStart = Math.Ceiling(southWest.Longitude / lineDistance) * lineDistance;
|
||||
var labelFormat = GetLabelFormat(Math.Min(lineDistance, lineDistance));
|
||||
|
||||
for (var lat = latLabelStart; lat <= northEast.Latitude; lat += lineDistance)
|
||||
{
|
||||
|
|
@ -122,43 +127,52 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawGraticule(PathFigureCollection figures, List<Label> labels, double lineDistance, string labelFormat)
|
||||
private void DrawGraticule(PathFigureCollection figures, List<Label> labels)
|
||||
{
|
||||
var lineDistance = GetLineDistance(MinLineDistance /
|
||||
(ParentMap.ViewTransform.Scale * MapProjection.Wgs84MeterPerDegree * Math.Cos(ParentMap.Center.Latitude * Math.PI / 180d)));
|
||||
var labelFormat = GetLabelFormat(Math.Min(lineDistance, lineDistance));
|
||||
|
||||
GetLatitudeRange(lineDistance, out double minLat, out double maxLat);
|
||||
|
||||
var latSegments = (int)Math.Round(Math.Abs(maxLat - minLat) / lineDistance);
|
||||
var latSegments = (int)Math.Ceiling(Math.Abs(maxLat - minLat) / lineDistance);
|
||||
var interpolationCount = Math.Max(1, (int)Math.Ceiling(lineDistance));
|
||||
var interpolationDistance = lineDistance / interpolationCount;
|
||||
var latPoints = latSegments * interpolationCount;
|
||||
var interpolationStep = lineDistance / interpolationCount;
|
||||
|
||||
var latStep = lineDistance;
|
||||
var startLat = minLat;
|
||||
|
||||
if (Math.Abs(minLat) > Math.Abs(maxLat))
|
||||
{
|
||||
startLat = maxLat;
|
||||
latStep = -lineDistance;
|
||||
}
|
||||
|
||||
var centerLon = Math.Round(ParentMap.Center.Longitude / lineDistance) * lineDistance;
|
||||
var minLon = centerLon - lineDistance;
|
||||
var minLon = centerLon;
|
||||
var maxLon = centerLon + lineDistance;
|
||||
|
||||
if (DrawMeridian(figures, centerLon, minLat, interpolationDistance, latPoints))
|
||||
while (DrawMeridian(figures, minLon, minLat, interpolationStep, latSegments * interpolationCount) &&
|
||||
minLon > centerLon - 180d)
|
||||
{
|
||||
while (DrawMeridian(figures, minLon, minLat, interpolationDistance, latPoints) &&
|
||||
minLon > centerLon - 180d)
|
||||
{
|
||||
minLon -= lineDistance;
|
||||
}
|
||||
minLon -= lineDistance;
|
||||
}
|
||||
|
||||
while (DrawMeridian(figures, maxLon, minLat, interpolationDistance, latPoints) &&
|
||||
maxLon < centerLon + 180d)
|
||||
{
|
||||
maxLon += lineDistance;
|
||||
}
|
||||
while (DrawMeridian(figures, maxLon, minLat, interpolationStep, latSegments * interpolationCount) &&
|
||||
maxLon < centerLon + 180d)
|
||||
{
|
||||
maxLon += lineDistance;
|
||||
}
|
||||
|
||||
var lonSegments = (int)Math.Round(Math.Abs(maxLon - minLon) / lineDistance);
|
||||
|
||||
for (var i = 1; i < latSegments; i++)
|
||||
{
|
||||
var lat = minLat + i * lineDistance;
|
||||
var lat = startLat + i * latStep;
|
||||
var lon = minLon;
|
||||
var points = new List<Point>();
|
||||
var mapPoint = ParentMap.MapProjection.LocationToMap(lat, lon);
|
||||
var position = ParentMap.ViewTransform.MapToView(mapPoint);
|
||||
var rotation = -ParentMap.MapProjection.GridConvergence(mapPoint.X, mapPoint.Y);
|
||||
var position = ParentMap.LocationToView(lat, lon);
|
||||
var rotation = -ParentMap.MapProjection.GridConvergence(lat, lon);
|
||||
|
||||
points.Add(position);
|
||||
AddLabel(labels, labelFormat, lat, lon, position, rotation);
|
||||
|
|
@ -167,13 +181,12 @@ namespace MapControl
|
|||
{
|
||||
for (int k = 1; k <= interpolationCount; k++)
|
||||
{
|
||||
lon = minLon + j * lineDistance + k * interpolationDistance;
|
||||
mapPoint = ParentMap.MapProjection.LocationToMap(lat, lon);
|
||||
position = ParentMap.ViewTransform.MapToView(mapPoint);
|
||||
lon = minLon + j * lineDistance + k * interpolationStep;
|
||||
position = ParentMap.LocationToView(lat, lon);
|
||||
points.Add(position);
|
||||
}
|
||||
|
||||
rotation = -ParentMap.MapProjection.GridConvergence(mapPoint.X, mapPoint.Y);
|
||||
rotation = -ParentMap.MapProjection.GridConvergence(lat, lon);
|
||||
AddLabel(labels, labelFormat, lat, lon, position, rotation);
|
||||
}
|
||||
|
||||
|
|
@ -222,18 +235,16 @@ namespace MapControl
|
|||
|
||||
if (minLat > -90d || maxLat < 90d)
|
||||
{
|
||||
var width = ParentMap.ActualWidth;
|
||||
var height = ParentMap.ActualHeight;
|
||||
var locations = new Location[]
|
||||
{
|
||||
ParentMap.ViewToLocation(new Point(0d, 0d)),
|
||||
ParentMap.ViewToLocation(new Point(width, 0d)),
|
||||
ParentMap.ViewToLocation(new Point(0d, height)),
|
||||
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)),
|
||||
ParentMap.ViewToLocation(new Point(ParentMap.ActualWidth, 0d)),
|
||||
ParentMap.ViewToLocation(new Point(0d, ParentMap.ActualHeight)),
|
||||
ParentMap.ViewToLocation(new Point(ParentMap.ActualWidth, ParentMap.ActualHeight)),
|
||||
ParentMap.ViewToLocation(new Point(ParentMap.ActualWidth / 2d, 0d)),
|
||||
ParentMap.ViewToLocation(new Point(ParentMap.ActualWidth / 2d, ParentMap.ActualHeight)),
|
||||
ParentMap.ViewToLocation(new Point(0d, ParentMap.ActualHeight / 2d)),
|
||||
ParentMap.ViewToLocation(new Point(ParentMap.ActualWidth, ParentMap.ActualHeight / 2d)),
|
||||
};
|
||||
|
||||
var latitudes = locations.Select(loc => loc.Latitude).Distinct();
|
||||
|
|
@ -241,32 +252,8 @@ namespace MapControl
|
|||
maxLat = Math.Max(maxLat, latitudes.Max());
|
||||
}
|
||||
|
||||
minLatitude = Math.Max(Math.Floor(minLat / lineDistance - 1d) * lineDistance, -90d);
|
||||
maxLatitude = Math.Min(Math.Ceiling(maxLat / 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, 30d);
|
||||
minLatitude = Math.Max(Math.Floor(minLat / lineDistance) * lineDistance, -90d);
|
||||
maxLatitude = Math.Min(Math.Ceiling(maxLat / lineDistance) * lineDistance, 90d);
|
||||
}
|
||||
|
||||
private void AddLabel(List<Label> labels, string labelFormat, double latitude, double longitude, Point position, double rotation = 0d)
|
||||
|
|
@ -291,6 +278,20 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
private static double GetLineDistance(double minDistance)
|
||||
{
|
||||
minDistance = Math.Max(minDistance, lineDistances.First());
|
||||
minDistance = Math.Min(minDistance, lineDistances.Last());
|
||||
|
||||
return lineDistances.First(d => d >= minDistance);
|
||||
}
|
||||
|
||||
private static string GetLabelFormat(double lineDistance)
|
||||
{
|
||||
return lineDistance < 1d / 60d ? "{0} {1}°{2:00}'{3:00}\"" :
|
||||
lineDistance < 1d ? "{0} {1}°{2:00}'" : "{0} {1}°";
|
||||
}
|
||||
|
||||
private static string GetLabelText(double value, string labelFormat, string hemispheres)
|
||||
{
|
||||
var hemisphere = hemispheres[0];
|
||||
|
|
@ -318,5 +319,18 @@ namespace MapControl
|
|||
figure.Segments.Add(new LineSegment { Point = p2 });
|
||||
return figure;
|
||||
}
|
||||
|
||||
private static PathFigure CreatePolylineFigure(IEnumerable<Point> points)
|
||||
{
|
||||
var figure = new PathFigure
|
||||
{
|
||||
StartPoint = points.First(),
|
||||
IsClosed = false,
|
||||
IsFilled = false
|
||||
};
|
||||
|
||||
figure.Segments.Add(CreatePolyLineSegment(points.Skip(1)));
|
||||
return figure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ namespace MapControl
|
|||
{
|
||||
var longitudeOffset = 0d;
|
||||
|
||||
if (ParentMap.MapProjection.IsNormalCylindrical && location != null)
|
||||
if (location != null != ParentMap.MapProjection.IsNormalCylindrical)
|
||||
{
|
||||
var position = ParentMap.LocationToView(location);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ namespace MapControl
|
|||
/// <summary>
|
||||
/// Implements a map projection, a transformation between geographic coordinates,
|
||||
/// i.e. latitude and longitude in degrees, and cartesian map coordinates in meters.
|
||||
/// See https://en.wikipedia.org/wiki/Map_projection.
|
||||
/// </summary>
|
||||
#if UWP || WINUI
|
||||
[Windows.Foundation.Metadata.CreateFromString(MethodName = "Parse")]
|
||||
|
|
@ -40,7 +41,7 @@ namespace MapControl
|
|||
/// <summary>
|
||||
/// Gets the WMS 1.3.0 CRS identifier.
|
||||
/// </summary>
|
||||
public string CrsId { get; protected set; } = "";
|
||||
public string CrsId { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the projection is normal cylindrical, see
|
||||
|
|
@ -48,10 +49,16 @@ namespace MapControl
|
|||
/// </summary>
|
||||
public bool IsNormalCylindrical { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The earth ellipsoid semi-major axis, or spherical earth radius respectively, in meters.
|
||||
/// </summary>
|
||||
public double EquatorialRadius { get; set; } = Wgs84EquatorialRadius;
|
||||
public double CentralMeridian { get; set; }
|
||||
public double LatitudeOfOrigin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the grid convergence angle in degrees at the specified geographic 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 latitude, double longitude) => 0d;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relative transform at the specified geographic coordinates.
|
||||
|
|
@ -69,13 +76,6 @@ 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>
|
||||
|
|
@ -114,17 +114,22 @@ namespace MapControl
|
|||
var north = new Point((northWest.X + northEast.X) / 2d, (northWest.Y + northEast.Y) / 2d);
|
||||
var centerX = (south.X + north.X) / 2d;
|
||||
var centerY = (south.Y + north.Y) / 2d;
|
||||
var dx = north.X - south.X;
|
||||
var dy = north.Y - south.Y;
|
||||
var dxW = northWest.X - southWest.X;
|
||||
var dyW = northWest.Y - southWest.Y;
|
||||
var dxE = northEast.X - southEast.X;
|
||||
var dyE = northEast.Y - southEast.Y;
|
||||
var dxS = southEast.X - southWest.X;
|
||||
var dyS = southEast.Y - southWest.Y;
|
||||
var dxN = northEast.X - northWest.X;
|
||||
var dyN = northEast.Y - northWest.Y;
|
||||
var width = (Math.Sqrt(dxS * dxS + dyS * dyS) + Math.Sqrt(dxN * dxN + dyN * dyN)) / 2d;
|
||||
var height = Math.Sqrt(dx * dx + dy * dy);
|
||||
var height = (Math.Sqrt(dxW * dxW + dyW * dyW) + Math.Sqrt(dxE * dxE + dyE * dyE)) / 2d;
|
||||
|
||||
rect = new Rect(centerX - width / 2d, centerY - height / 2d, width, height);
|
||||
rotation = -GridConvergence(centerX, centerY); // invert direction for RotateTransform
|
||||
|
||||
rotation = -GridConvergence( // invert direction for RotateTransform
|
||||
(boundingBox.South + boundingBox.North) / 2d,
|
||||
(boundingBox.West + boundingBox.East) / 2d);
|
||||
}
|
||||
|
||||
return (rect, rotation);
|
||||
|
|
|
|||
|
|
@ -1,25 +1,54 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
public class MapProjectionFactory
|
||||
{
|
||||
public virtual MapProjection GetProjection(string crsId)
|
||||
public MapProjection GetProjection(string crsId)
|
||||
{
|
||||
var projection = crsId switch
|
||||
var projection = CreateProjection(crsId);
|
||||
|
||||
if (projection == null &&
|
||||
crsId.StartsWith("EPSG:") &&
|
||||
int.TryParse(crsId.Substring(5), out int epsgCode))
|
||||
{
|
||||
WebMercatorProjection.DefaultCrsId => new WebMercatorProjection(),
|
||||
WorldMercatorProjection.DefaultCrsId => new WorldMercatorProjection(),
|
||||
EquirectangularProjection.DefaultCrsId or "CRS:84" => new EquirectangularProjection(crsId),
|
||||
Wgs84UpsNorthProjection.DefaultCrsId => new Wgs84UpsNorthProjection(),
|
||||
Wgs84UpsSouthProjection.DefaultCrsId => new Wgs84UpsSouthProjection(),
|
||||
_ => GetProjectionFromEpsgCode(crsId),
|
||||
};
|
||||
projection = CreateProjection(epsgCode);
|
||||
}
|
||||
|
||||
return projection ?? throw new NotSupportedException($"MapProjection \"{crsId}\" is not supported.");
|
||||
}
|
||||
|
||||
public virtual MapProjection GetProjection(int epsgCode)
|
||||
protected virtual MapProjection CreateProjection(string crsId)
|
||||
{
|
||||
MapProjection projection = crsId switch
|
||||
{
|
||||
WebMercatorProjection.DefaultCrsId => new WebMercatorProjection(),
|
||||
WorldMercatorProjection.DefaultCrsId => new WorldMercatorProjection(),
|
||||
Wgs84UpsNorthProjection.DefaultCrsId => new Wgs84UpsNorthProjection(),
|
||||
Wgs84UpsSouthProjection.DefaultCrsId => new Wgs84UpsSouthProjection(),
|
||||
EquirectangularProjection.DefaultCrsId or "CRS:84" => new EquirectangularProjection(crsId),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (projection == null && crsId.StartsWith(StereographicProjection.DefaultCrsId))
|
||||
{
|
||||
var parameters = crsId.Split(',');
|
||||
|
||||
if (parameters.Length == 4 &&
|
||||
parameters[0] == StereographicProjection.DefaultCrsId &&
|
||||
parameters[1] == "1" &&
|
||||
double.TryParse(parameters[2], NumberStyles.Float, CultureInfo.InvariantCulture, out double longitude) &&
|
||||
double.TryParse(parameters[3], NumberStyles.Float, CultureInfo.InvariantCulture, out double latitude))
|
||||
{
|
||||
projection = new StereographicProjection(longitude, latitude);
|
||||
}
|
||||
}
|
||||
|
||||
return projection;
|
||||
}
|
||||
|
||||
protected virtual MapProjection CreateProjection(int epsgCode)
|
||||
{
|
||||
return epsgCode switch
|
||||
{
|
||||
|
|
@ -36,10 +65,5 @@ namespace MapControl
|
|||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
protected MapProjection GetProjectionFromEpsgCode(string crsId)
|
||||
{
|
||||
return crsId.StartsWith("EPSG:") && int.TryParse(crsId.Substring(5), out int epsgCode) ? GetProjection(epsgCode) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,37 @@ namespace MapControl
|
|||
public double ScaleFactor { get; set; } = 0.994;
|
||||
public double FalseEasting { get; set; } = 2e6;
|
||||
public double FalseNorthing { get; set; } = 2e6;
|
||||
public Hemisphere Hemisphere { get; set; }
|
||||
|
||||
public override double GridConvergence(double latitude, double longitude)
|
||||
{
|
||||
return Math.Sign(LatitudeOfOrigin) * (longitude - CentralMeridian);
|
||||
}
|
||||
|
||||
public override Matrix RelativeTransform(double latitude, double longitude)
|
||||
{
|
||||
var sign = Math.Sign(LatitudeOfOrigin);
|
||||
var phi = sign * latitude * Math.PI / 180d;
|
||||
|
||||
var e = Math.Sqrt((2d - Flattening) * Flattening);
|
||||
var eSinPhi = e * Math.Sin(phi);
|
||||
|
||||
var t = Math.Tan(Math.PI / 4d - phi / 2d)
|
||||
/ Math.Pow((1d - eSinPhi) / (1d + eSinPhi), e / 2d); // p.161 (15-9)
|
||||
|
||||
// r == ρ/a
|
||||
var r = 2d * ScaleFactor * t / Math.Sqrt(Math.Pow(1d + e, 1d + e) * Math.Pow(1d - e, 1d - e)); // p.161 (21-33)
|
||||
var m = Math.Cos(phi) / Math.Sqrt(1d - eSinPhi * eSinPhi); // p.160 (14-15)
|
||||
var k = r / m; // p.161 (21-32)
|
||||
|
||||
var transform = new Matrix(k, 0d, 0d, k, 0d, 0d);
|
||||
transform.Rotate(-sign * longitude);
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
public override Point LocationToMap(double latitude, double longitude)
|
||||
{
|
||||
var sign = Hemisphere == Hemisphere.North ? 1d : -1d;
|
||||
var sign = Math.Sign(LatitudeOfOrigin);
|
||||
var phi = sign * latitude * Math.PI / 180d;
|
||||
var lambda = sign * longitude * Math.PI / 180d;
|
||||
|
||||
|
|
@ -42,30 +68,9 @@ namespace MapControl
|
|||
return new Point(x + FalseEasting, y + FalseNorthing);
|
||||
}
|
||||
|
||||
public override Matrix RelativeTransform(double latitude, double longitude)
|
||||
{
|
||||
var sign = Hemisphere == Hemisphere.North ? 1d : -1d;
|
||||
var phi = sign * latitude * Math.PI / 180d;
|
||||
|
||||
var e = Math.Sqrt((2d - Flattening) * Flattening);
|
||||
var eSinPhi = e * Math.Sin(phi);
|
||||
|
||||
var t = Math.Tan(Math.PI / 4d - phi / 2d)
|
||||
/ Math.Pow((1d - eSinPhi) / (1d + eSinPhi), e / 2d); // p.161 (15-9)
|
||||
|
||||
// r == ρ/a
|
||||
var r = 2d * ScaleFactor * t / Math.Sqrt(Math.Pow(1d + e, 1d + e) * Math.Pow(1d - e, 1d - e)); // p.161 (21-33)
|
||||
var m = Math.Cos(phi) / Math.Sqrt(1d - eSinPhi * eSinPhi); // p.160 (14-15)
|
||||
var k = r / m; // p.161 (21-32)
|
||||
|
||||
var transform = new Matrix(k, 0d, 0d, k, 0d, 0d);
|
||||
transform.Rotate(-sign * longitude);
|
||||
return transform;
|
||||
}
|
||||
|
||||
public override Location MapToLocation(double x, double y)
|
||||
{
|
||||
var sign = Hemisphere == Hemisphere.North ? 1d : -1d;
|
||||
var sign = Math.Sign(LatitudeOfOrigin);
|
||||
x = sign * (x - FalseEasting);
|
||||
y = sign * (y - FalseNorthing);
|
||||
|
||||
|
|
@ -80,17 +85,6 @@ namespace MapControl
|
|||
|
||||
return new Location(sign * phi * 180d / Math.PI, sign * lambda * 180d / Math.PI);
|
||||
}
|
||||
|
||||
public override double GridConvergence(double x, double y)
|
||||
{
|
||||
var sign = Hemisphere == Hemisphere.North ? 1d : -1d;
|
||||
x = sign * (x - FalseEasting);
|
||||
y = sign * (y - FalseNorthing);
|
||||
|
||||
var lambda = Math.Atan2(x, -y); // p.162 (20-16)
|
||||
|
||||
return lambda * 180d / Math.PI;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -108,7 +102,7 @@ namespace MapControl
|
|||
public Wgs84UpsNorthProjection(string crsId)
|
||||
{
|
||||
CrsId = crsId;
|
||||
Hemisphere = Hemisphere.North;
|
||||
LatitudeOfOrigin = 90d;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +121,7 @@ namespace MapControl
|
|||
public Wgs84UpsSouthProjection(string crsId)
|
||||
{
|
||||
CrsId = crsId;
|
||||
Hemisphere = Hemisphere.South;
|
||||
LatitudeOfOrigin = -90d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
97
MapControl/Shared/StereographicProjection.cs
Normal file
97
MapControl/Shared/StereographicProjection.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
#if WPF
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
#elif AVALONIA
|
||||
using Avalonia;
|
||||
#endif
|
||||
|
||||
namespace MapControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Spherical Stereographic Projection - AUTO2:97002.
|
||||
/// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.157-160.
|
||||
/// </summary>
|
||||
public class StereographicProjection : MapProjection
|
||||
{
|
||||
public const string DefaultCrsId = "AUTO2:97002"; // GeoServer non-standard CRS identifier
|
||||
|
||||
public StereographicProjection(double centerLongitude, double centerLatitude, string crsId = DefaultCrsId)
|
||||
{
|
||||
CentralMeridian = centerLongitude;
|
||||
LatitudeOfOrigin = centerLatitude;
|
||||
CrsId = string.Format(CultureInfo.InvariantCulture,
|
||||
"{0},1,{1:0.########},{2:0.########}", crsId, centerLongitude, centerLatitude);
|
||||
}
|
||||
|
||||
public override double GridConvergence(double latitude, double longitude)
|
||||
{
|
||||
var p0 = LocationToMap(latitude, longitude);
|
||||
var p1 = LocationToMap(latitude + 1e-3, longitude);
|
||||
|
||||
return Math.Atan2(p0.X - p1.X, p1.Y - p0.Y) * 180d / Math.PI;
|
||||
}
|
||||
|
||||
public override Matrix RelativeTransform(double latitude, double longitude)
|
||||
{
|
||||
return new Matrix(1d, 0d, 0d, 1d, 0d, 0d);
|
||||
}
|
||||
|
||||
public override Point LocationToMap(double latitude, double longitude)
|
||||
{
|
||||
var phi = latitude * Math.PI / 180d;
|
||||
var phi1 = LatitudeOfOrigin * Math.PI / 180d;
|
||||
var dLambda = (longitude - CentralMeridian) * Math.PI / 180d; // λ - λ0
|
||||
var cosPhi = Math.Cos(phi);
|
||||
var sinPhi = Math.Sin(phi);
|
||||
var cosPhi1 = Math.Cos(phi1);
|
||||
var sinPhi1 = Math.Sin(phi1);
|
||||
var cosLambda = Math.Cos(dLambda);
|
||||
var sinLambda = Math.Sin(dLambda);
|
||||
var x = cosPhi * sinLambda;
|
||||
var y = cosPhi1 * sinPhi - sinPhi1 * cosPhi * cosLambda;
|
||||
var cosC = sinPhi1 * sinPhi + cosPhi1 * cosPhi * cosLambda; // (5-3)
|
||||
cosC = Math.Min(Math.Max(cosC, -1d), 1d); // protect against rounding errors
|
||||
var k = 2d / (1d + cosC); // p.157 (21-4), k0 == 1
|
||||
|
||||
return new Point(
|
||||
EquatorialRadius * k * x,
|
||||
EquatorialRadius * k * y); // p.157 (21-2/3)
|
||||
}
|
||||
|
||||
public override Location MapToLocation(double x, double y)
|
||||
{
|
||||
var rho = Math.Sqrt(x * x + y * y);
|
||||
var c = 2d * Math.Atan(rho / (2d * EquatorialRadius)); // p.159 (21-15), k0 == 1
|
||||
var cosC = Math.Cos(c);
|
||||
var sinC = Math.Sin(c);
|
||||
|
||||
var phi1 = LatitudeOfOrigin * Math.PI / 180d;
|
||||
var cosPhi1 = Math.Cos(phi1);
|
||||
var sinPhi1 = Math.Sin(phi1);
|
||||
var phi = Math.Asin(cosC * sinPhi1 + y * sinC * cosPhi1 / rho); // (20-14)
|
||||
double u, v;
|
||||
|
||||
if (LatitudeOfOrigin == 90d) // (20-16)
|
||||
{
|
||||
u = x;
|
||||
v = -y;
|
||||
}
|
||||
else if (LatitudeOfOrigin == -90d) // (20-17)
|
||||
{
|
||||
u = x;
|
||||
v = y;
|
||||
}
|
||||
else // (20-15)
|
||||
{
|
||||
u = x * sinC;
|
||||
v = rho * cosPhi1 * cosC - y * sinPhi1 * sinC;
|
||||
}
|
||||
|
||||
return new Location(
|
||||
phi * 180d / Math.PI,
|
||||
Math.Atan2(u, v) * 180d / Math.PI + CentralMeridian);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,10 @@ using Avalonia;
|
|||
namespace MapControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Transverse Mercator Projection.
|
||||
/// See https://en.wikipedia.org/wiki/Transverse_Mercator_projection
|
||||
/// and https://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system.
|
||||
/// Transverse Mercator Projection. See
|
||||
/// https://en.wikipedia.org/wiki/Transverse_Mercator_projection,
|
||||
/// https://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system,
|
||||
/// https://en.wikipedia.org/wiki/Transverse_Mercator_projection#Convergence.
|
||||
/// </summary>
|
||||
public class TransverseMercatorProjection : MapProjection
|
||||
{
|
||||
|
|
@ -27,6 +28,11 @@ namespace MapControl
|
|||
private double f1; // A/a
|
||||
private double f2; // 2*sqrt(n)/(1+n)
|
||||
|
||||
public TransverseMercatorProjection()
|
||||
{
|
||||
Flattening = Wgs84Flattening;
|
||||
}
|
||||
|
||||
public double Flattening
|
||||
{
|
||||
get;
|
||||
|
|
@ -50,43 +56,20 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
public double CentralMeridian { get; set; }
|
||||
public double ScaleFactor { get; set; } = 0.9996;
|
||||
public double FalseEasting { get; set; } = 5e5;
|
||||
public double FalseNorthing { get; set; }
|
||||
|
||||
public TransverseMercatorProjection()
|
||||
{
|
||||
Flattening = Wgs84Flattening;
|
||||
}
|
||||
|
||||
public override Point LocationToMap(double latitude, double longitude)
|
||||
public override double GridConvergence(double latitude, double longitude)
|
||||
{
|
||||
// φ
|
||||
var phi = latitude * Math.PI / 180d;
|
||||
var sinPhi = Math.Sin(phi);
|
||||
// t
|
||||
var t = Math.Sinh(Atanh(sinPhi) - f2 * Atanh(f2 * sinPhi));
|
||||
// λ - λ0
|
||||
var dLambda = (longitude - CentralMeridian) * Math.PI / 180d;
|
||||
// ξ'
|
||||
var xi_ = Math.Atan2(t, Math.Cos(dLambda));
|
||||
// η'
|
||||
var eta_ = Atanh(Math.Sin(dLambda) / Math.Sqrt(1d + t * t));
|
||||
// k0 * A
|
||||
var k0A = ScaleFactor * EquatorialRadius * f1;
|
||||
|
||||
var x = FalseEasting + k0A * (eta_ +
|
||||
a1 * Math.Cos(2d * xi_) * Math.Sinh(2d * eta_) +
|
||||
a2 * Math.Cos(4d * xi_) * Math.Sinh(4d * eta_) +
|
||||
a3 * Math.Cos(6d * xi_) * Math.Sinh(6d * eta_));
|
||||
|
||||
var y = FalseNorthing + k0A * (xi_ +
|
||||
a1 * Math.Sin(2d * xi_) * Math.Cosh(2d * eta_) +
|
||||
a2 * Math.Sin(4d * xi_) * Math.Cosh(4d * eta_) +
|
||||
a3 * Math.Sin(6d * xi_) * Math.Cosh(6d * eta_));
|
||||
|
||||
return new Point(x, y);
|
||||
// γ calculation for the sphere is sufficiently accurate
|
||||
//
|
||||
return Math.Atan(Math.Tan(dLambda) * Math.Sin(phi)) * 180d / Math.PI;
|
||||
}
|
||||
|
||||
public override Matrix RelativeTransform(double latitude, double longitude)
|
||||
|
|
@ -120,7 +103,7 @@ namespace MapControl
|
|||
var m = (1d - n) / (1d + n) * Math.Tan(phi);
|
||||
var k = ScaleFactor * f1 * Math.Sqrt((1d + m * m) * (sigma * sigma + tau * tau) / (t * t + cosLambda * cosLambda));
|
||||
|
||||
// γ, meridian convergence angle
|
||||
// γ, grid convergence
|
||||
var gamma = Math.Atan2(tau * u + sigma * t * tanLambda, sigma * u - tau * t * tanLambda);
|
||||
|
||||
var transform = new Matrix(k, 0d, 0d, k, 0d, 0d);
|
||||
|
|
@ -129,6 +112,35 @@ namespace MapControl
|
|||
return transform;
|
||||
}
|
||||
|
||||
public override Point LocationToMap(double latitude, double longitude)
|
||||
{
|
||||
// φ
|
||||
var phi = latitude * Math.PI / 180d;
|
||||
var sinPhi = Math.Sin(phi);
|
||||
// t
|
||||
var t = Math.Sinh(Atanh(sinPhi) - f2 * Atanh(f2 * sinPhi));
|
||||
// λ - λ0
|
||||
var dLambda = (longitude - CentralMeridian) * Math.PI / 180d;
|
||||
// ξ'
|
||||
var xi_ = Math.Atan2(t, Math.Cos(dLambda));
|
||||
// η'
|
||||
var eta_ = Atanh(Math.Sin(dLambda) / Math.Sqrt(1d + t * t));
|
||||
// k0 * A
|
||||
var k0A = ScaleFactor * EquatorialRadius * f1;
|
||||
|
||||
var x = FalseEasting + k0A * (eta_ +
|
||||
a1 * Math.Cos(2d * xi_) * Math.Sinh(2d * eta_) +
|
||||
a2 * Math.Cos(4d * xi_) * Math.Sinh(4d * eta_) +
|
||||
a3 * Math.Cos(6d * xi_) * Math.Sinh(6d * eta_));
|
||||
|
||||
var y = FalseNorthing + k0A * (xi_ +
|
||||
a1 * Math.Sin(2d * xi_) * Math.Cosh(2d * eta_) +
|
||||
a2 * Math.Sin(4d * xi_) * Math.Cosh(4d * eta_) +
|
||||
a3 * Math.Sin(6d * xi_) * Math.Cosh(6d * eta_));
|
||||
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
public override Location MapToLocation(double x, double y)
|
||||
{
|
||||
// k0 * A
|
||||
|
|
@ -162,42 +174,6 @@ namespace MapControl
|
|||
dLambda * 180d / Math.PI + CentralMeridian);
|
||||
}
|
||||
|
||||
public override double GridConvergence(double x, double y)
|
||||
{
|
||||
// k0 * A
|
||||
var k0A = ScaleFactor * EquatorialRadius * f1;
|
||||
// ξ
|
||||
var xi = (y - FalseNorthing) / k0A;
|
||||
// η
|
||||
var eta = (x - FalseEasting) / k0A;
|
||||
// ξ'
|
||||
var xi_ = xi -
|
||||
b1 * Math.Sin(2d * xi) * Math.Cosh(2d * eta) -
|
||||
b2 * Math.Sin(4d * xi) * Math.Cosh(4d * eta) -
|
||||
b3 * Math.Sin(6d * xi) * Math.Cosh(6d * eta);
|
||||
// η'
|
||||
var eta_ = eta -
|
||||
b1 * Math.Cos(2d * xi) * Math.Sinh(2d * eta) -
|
||||
b2 * Math.Cos(4d * xi) * Math.Sinh(4d * eta) -
|
||||
b3 * Math.Cos(6d * xi) * Math.Sinh(6d * eta);
|
||||
// σ'
|
||||
var sigma_ = 1 -
|
||||
2d * b1 * Math.Cos(2d * xi) * Math.Cosh(2d * eta) +
|
||||
4d * b2 * Math.Cos(4d * xi) * Math.Cosh(4d * eta) +
|
||||
6d * b3 * Math.Cos(6d * xi) * Math.Cosh(6d * eta);
|
||||
// τ'
|
||||
var tau_ =
|
||||
2d * b1 * Math.Sin(2d * xi) * Math.Sinh(2d * eta) +
|
||||
4d * b2 * Math.Sin(4d * xi) * Math.Sinh(4d * eta) +
|
||||
6d * b3 * Math.Sin(6d * xi) * Math.Sinh(6d * eta);
|
||||
|
||||
var tanXi_tanhEta_ = Math.Tan(xi_) * Math.Tanh(eta_);
|
||||
// γ
|
||||
var gamma = Math.Atan2(tau_ + sigma_ * tanXi_tanhEta_, sigma_ + -tau_ * tanXi_tanhEta_);
|
||||
|
||||
return gamma * 180d / Math.PI;
|
||||
}
|
||||
|
||||
#if NETFRAMEWORK
|
||||
private static double Atanh(double x) => Math.Log((1d + x) / (1d - x)) / 2d;
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -10,45 +10,68 @@ namespace MapControl
|
|||
{
|
||||
/// <summary>
|
||||
/// Elliptical Transverse Mercator Projection.
|
||||
/// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.60-64.
|
||||
/// See "Map Projections - A Working Manual" (https://pubs.usgs.gov/publication/pp1395), p.60-64,
|
||||
/// and https://en.wikipedia.org/wiki/Transverse_Mercator_projection#Convergence.
|
||||
/// </summary>
|
||||
public class TransverseMercatorProjectionSnyder : MapProjection
|
||||
{
|
||||
private double M0;
|
||||
|
||||
public double Flattening { get; set; } = Wgs84Flattening;
|
||||
public double CentralMeridian { get; set; }
|
||||
public double ScaleFactor { get; set; } = 0.9996;
|
||||
public double FalseEasting { get; set; } = 5e5;
|
||||
public double FalseNorthing { get; set; }
|
||||
|
||||
public double LatitudeOfOrigin
|
||||
public override double GridConvergence(double latitude, double longitude)
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
M0 = MeridianDistance(value * Math.PI / 180d);
|
||||
}
|
||||
// φ
|
||||
var phi = latitude * Math.PI / 180d;
|
||||
// λ - λ0
|
||||
var dLambda = (longitude - CentralMeridian) * Math.PI / 180d;
|
||||
|
||||
// γ calculation for the sphere is sufficiently accurate
|
||||
//
|
||||
return Math.Atan(Math.Tan(dLambda) * Math.Sin(phi)) * 180d / Math.PI;
|
||||
}
|
||||
|
||||
private double MeridianDistance(double phi)
|
||||
public override Matrix RelativeTransform(double latitude, double longitude)
|
||||
{
|
||||
var e2 = (2d - Flattening) * Flattening;
|
||||
var e4 = e2 * e2;
|
||||
var e6 = e2 * e4;
|
||||
var k = ScaleFactor;
|
||||
var gamma = 0d; // γ
|
||||
|
||||
return EquatorialRadius * (
|
||||
(1d - e2 / 4d - e4 * 3d / 64d - e6 * 5d / 256d) * phi -
|
||||
(e2 * 3d / 8d + e4 * 3d / 32d + e6 * 45d / 1024d) * Math.Sin(2d * phi) +
|
||||
(e4 * 15d / 256d + e6 * 45d / 1024d) * Math.Sin(4d * phi) -
|
||||
e6 * 35d / 3072d * Math.Sin(6d * phi)); // (3-21)
|
||||
if (latitude > -90d && latitude < 90d)
|
||||
{
|
||||
var phi = latitude * Math.PI / 180d;
|
||||
var sinPhi = Math.Sin(phi);
|
||||
var cosPhi = Math.Cos(phi);
|
||||
var tanPhi = sinPhi / cosPhi;
|
||||
var dLambda = (longitude - CentralMeridian) * Math.PI / 180d;
|
||||
|
||||
var e2 = (2d - Flattening) * Flattening;
|
||||
var e_2 = e2 / (1d - e2); // (8-12)
|
||||
var T = tanPhi * tanPhi; // (8-13)
|
||||
var C = e_2 * cosPhi * cosPhi; // (8-14)
|
||||
var A = dLambda * cosPhi; // (8-15)
|
||||
var A2 = A * A;
|
||||
var A4 = A2 * A2;
|
||||
var A6 = A2 * A4;
|
||||
|
||||
k *= 1d + (1d + C) * A2 / 2d +
|
||||
(5d - 4d * T + 42d * C + 13d * C * C - 28d * e_2) * A4 / 24d +
|
||||
(61d - 148d * T + 16 * T * T) * A6 / 720d; // (8-11)
|
||||
|
||||
gamma = Math.Atan(Math.Tan(dLambda) * sinPhi) * 180d / Math.PI;
|
||||
}
|
||||
|
||||
var transform = new Matrix(k, 0d, 0d, k, 0d, 0d);
|
||||
transform.Rotate(-gamma);
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
public override Point LocationToMap(double latitude, double longitude)
|
||||
{
|
||||
var phi = latitude * Math.PI / 180d;
|
||||
var M = MeridianDistance(phi);
|
||||
var M0 = MeridianDistance(LatitudeOfOrigin * Math.PI / 180d);
|
||||
double x, y;
|
||||
|
||||
if (latitude > -90d && latitude < 90d)
|
||||
|
|
@ -83,41 +106,6 @@ namespace MapControl
|
|||
return new Point(x + FalseEasting, y + FalseNorthing);
|
||||
}
|
||||
|
||||
public override Matrix RelativeTransform(double latitude, double longitude)
|
||||
{
|
||||
var k = ScaleFactor;
|
||||
var gamma = 0d; // γ, https://en.wikipedia.org/wiki/Transverse_Mercator_projection#Convergence
|
||||
|
||||
if (latitude > -90d && latitude < 90d)
|
||||
{
|
||||
var phi = latitude * Math.PI / 180d;
|
||||
var sinPhi = Math.Sin(phi);
|
||||
var cosPhi = Math.Cos(phi);
|
||||
var tanPhi = sinPhi / cosPhi;
|
||||
var dLambda = (longitude - CentralMeridian) * Math.PI / 180d;
|
||||
|
||||
var e2 = (2d - Flattening) * Flattening;
|
||||
var e_2 = e2 / (1d - e2); // (8-12)
|
||||
var T = tanPhi * tanPhi; // (8-13)
|
||||
var C = e_2 * cosPhi * cosPhi; // (8-14)
|
||||
var A = dLambda * cosPhi; // (8-15)
|
||||
var A2 = A * A;
|
||||
var A4 = A2 * A2;
|
||||
var A6 = A2 * A4;
|
||||
|
||||
k *= 1d + (1d + C) * A2 / 2d +
|
||||
(5d - 4d * T + 42d * C + 13d * C * C - 28d * e_2) * A4 / 24d +
|
||||
(61d - 148d * T + 16 * T * T) * A6 / 720d; // (8-11)
|
||||
|
||||
gamma = Math.Atan(Math.Tan(dLambda) * sinPhi) * 180d / Math.PI;
|
||||
}
|
||||
|
||||
var transform = new Matrix(k, 0d, 0d, k, 0d, 0d);
|
||||
transform.Rotate(-gamma);
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
public override Location MapToLocation(double x, double y)
|
||||
{
|
||||
var e2 = (2d - Flattening) * Flattening;
|
||||
|
|
@ -129,6 +117,7 @@ namespace MapControl
|
|||
var e13 = e1 * e12;
|
||||
var e14 = e1 * e13;
|
||||
|
||||
var M0 = MeridianDistance(LatitudeOfOrigin * Math.PI / 180d);
|
||||
var M = M0 + (y - FalseNorthing) / ScaleFactor; // (8-20)
|
||||
var mu = M / (EquatorialRadius * (1d - e2 / 4d - e4 * 3d / 64d - e6 * 5d / 256d)); // (7-19)
|
||||
var phi1 = mu +
|
||||
|
|
@ -165,51 +154,17 @@ namespace MapControl
|
|||
dLambda * 180d / Math.PI + CentralMeridian);
|
||||
}
|
||||
|
||||
public override double GridConvergence(double x, double y)
|
||||
private double MeridianDistance(double phi)
|
||||
{
|
||||
var e2 = (2d - Flattening) * Flattening;
|
||||
var e4 = e2 * e2;
|
||||
var e6 = e2 * e4;
|
||||
var s = Math.Sqrt(1d - e2);
|
||||
var e1 = (1d - s) / (1d + s); // (3-24)
|
||||
var e12 = e1 * e1;
|
||||
var e13 = e1 * e12;
|
||||
var e14 = e1 * e13;
|
||||
|
||||
var M = M0 + (y - FalseNorthing) / ScaleFactor; // (8-20)
|
||||
var mu = M / (EquatorialRadius * (1d - e2 / 4d - e4 * 3d / 64d - e6 * 5d / 256d)); // (7-19)
|
||||
var phi1 = mu +
|
||||
(e1 * 3d / 2d - e13 * 27d / 32d) * Math.Sin(2d * mu) +
|
||||
(e12 * 21d / 16d - e14 * 55d / 32d) * Math.Sin(4d * mu) +
|
||||
e13 * 151d / 96d * Math.Sin(6d * mu) +
|
||||
e14 * 1097d / 512d * Math.Sin(8d * mu); // (3-26)
|
||||
|
||||
var sinPhi1 = Math.Sin(phi1);
|
||||
var cosPhi1 = Math.Cos(phi1);
|
||||
var tanPhi1 = sinPhi1 / cosPhi1;
|
||||
|
||||
var e_2 = e2 / (1d - e2); // (8-12)
|
||||
var C1 = e_2 * cosPhi1 * cosPhi1; // (8-21)
|
||||
var T1 = sinPhi1 * sinPhi1 / (cosPhi1 * cosPhi1); // (8-22)
|
||||
s = Math.Sqrt(1d - e2 * sinPhi1 * sinPhi1);
|
||||
var N1 = EquatorialRadius / s; // (8-23)
|
||||
var R1 = EquatorialRadius * (1d - e2) / (s * s * s); // (8-24)
|
||||
var D = (x - FalseEasting) / (N1 * ScaleFactor); // (8-25)
|
||||
var D2 = D * D;
|
||||
var D3 = D * D2;
|
||||
var D4 = D * D3;
|
||||
var D5 = D * D4;
|
||||
var D6 = D * D5;
|
||||
|
||||
var phi = phi1 - N1 * tanPhi1 / R1 * (D2 / 2d - (5d + 3d * T1 + 10d * C1 - 4d * C1 * C1 - 9d * e_2) * D4 / 24d +
|
||||
(61d + 90d * T1 + 45d * T1 * T1 + 298 * C1 - 3d * C1 * C1 - 252d * e_2) * D6 / 720d); // (8-17)
|
||||
|
||||
var dLambda = (D - (1d + 2d * T1 + C1) * D3 / 6d +
|
||||
(5d - 2d * C1 - 3d * C1 * C1 + 28d * T1 + 24d * T1 * T1 + 8d * e_2) * D5 / 120d) / cosPhi1; // (8-18)
|
||||
|
||||
// γ, https://en.wikipedia.org/wiki/Transverse_Mercator_projection#Convergence
|
||||
//
|
||||
return Math.Atan(Math.Tan(dLambda) * Math.Sin(phi)) * 180d / Math.PI;
|
||||
return EquatorialRadius * (
|
||||
(1d - e2 / 4d - e4 * 3d / 64d - e6 * 5d / 256d) * phi -
|
||||
(e2 * 3d / 8d + e4 * 3d / 32d + e6 * 45d / 1024d) * Math.Sin(2d * phi) +
|
||||
(e4 * 15d / 256d + e6 * 45d / 1024d) * Math.Sin(4d * phi) -
|
||||
e6 * 35d / 3072d * Math.Sin(6d * phi)); // (3-21)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,7 +190,8 @@ namespace MapControl
|
|||
var xMin = -180d * MapProjection.Wgs84MeterPerDegree;
|
||||
var xMax = 180d * MapProjection.Wgs84MeterPerDegree;
|
||||
|
||||
if (bbox.X >= xMin && bbox.X + bbox.Width <= xMax || !ParentMap.MapProjection.IsNormalCylindrical)
|
||||
if (!ParentMap.MapProjection.IsNormalCylindrical ||
|
||||
bbox.X >= xMin && bbox.X + bbox.Width <= xMax)
|
||||
{
|
||||
var uri = GetMapRequestUri(bbox);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
|
|
@ -87,17 +86,9 @@ namespace MapControl
|
|||
}
|
||||
}
|
||||
|
||||
private static PathFigure CreatePolylineFigure(IEnumerable<Point> points)
|
||||
private static PolyLineSegment CreatePolyLineSegment(IEnumerable<Point> points)
|
||||
{
|
||||
var figure = new PathFigure
|
||||
{
|
||||
StartPoint = points.First(),
|
||||
IsClosed = false,
|
||||
IsFilled = false
|
||||
};
|
||||
|
||||
figure.Segments.Add(new PolyLineSegment(points.Skip(1), true));
|
||||
return figure;
|
||||
return new PolyLineSegment(points, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using Windows.Foundation;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
#if UWP
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
|
@ -112,24 +111,16 @@ namespace MapControl
|
|||
base.OnViewportChanged(e);
|
||||
}
|
||||
|
||||
private static PathFigure CreatePolylineFigure(IEnumerable<Point> points)
|
||||
private static PolyLineSegment CreatePolyLineSegment(IEnumerable<Point> points)
|
||||
{
|
||||
var figure = new PathFigure
|
||||
{
|
||||
StartPoint = points.First(),
|
||||
IsClosed = false,
|
||||
IsFilled = false
|
||||
};
|
||||
|
||||
var polyline = new PolyLineSegment();
|
||||
|
||||
foreach (var p in points.Skip(1))
|
||||
foreach (var p in points)
|
||||
{
|
||||
polyline.Points.Add(p);
|
||||
}
|
||||
|
||||
figure.Segments.Add(polyline);
|
||||
return figure;
|
||||
return polyline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,10 +52,13 @@ namespace MapControl.Projections
|
|||
var projection = field.Projection ??
|
||||
throw new ArgumentException("CoordinateSystem.Projection must not be null.", nameof(value));
|
||||
|
||||
IsNormalCylindrical = projection.Name.StartsWith("Mercator") || projection.Name.Contains("Pseudo-Mercator");
|
||||
|
||||
var ellipsoid = field.GeographicCoordinateSystem.HorizontalDatum.Ellipsoid;
|
||||
var transformFactory = new CoordinateTransformationFactory();
|
||||
|
||||
CrsId = !string.IsNullOrEmpty(field.Authority) && field.AuthorityCode > 0
|
||||
? $"{field.Authority}:{field.AuthorityCode}"
|
||||
: string.Empty;
|
||||
|
||||
LocationToMapTransform = transformFactory
|
||||
.CreateFromCoordinateSystems(GeographicCoordinateSystem.WGS84, field)
|
||||
.MathTransform;
|
||||
|
|
@ -64,14 +67,9 @@ namespace MapControl.Projections
|
|||
.CreateFromCoordinateSystems(field, GeographicCoordinateSystem.WGS84)
|
||||
.MathTransform;
|
||||
|
||||
CrsId = !string.IsNullOrEmpty(field.Authority) && field.AuthorityCode > 0
|
||||
? $"{field.Authority}:{field.AuthorityCode}"
|
||||
: string.Empty;
|
||||
|
||||
var ellipsoid = field.GeographicCoordinateSystem.HorizontalDatum.Ellipsoid;
|
||||
|
||||
if (projection.Name.Contains("Pseudo-Mercator"))
|
||||
{
|
||||
IsNormalCylindrical = true;
|
||||
FallbackProjection = new MapControl.WebMercatorProjection
|
||||
{
|
||||
EquatorialRadius = ellipsoid.SemiMajorAxis
|
||||
|
|
@ -79,6 +77,7 @@ namespace MapControl.Projections
|
|||
}
|
||||
else if (projection.Name.StartsWith("Mercator"))
|
||||
{
|
||||
IsNormalCylindrical = true;
|
||||
FallbackProjection = new MapControl.WorldMercatorProjection
|
||||
{
|
||||
EquatorialRadius = ellipsoid.SemiMajorAxis,
|
||||
|
|
@ -106,7 +105,7 @@ namespace MapControl.Projections
|
|||
ScaleFactor = projection.GetParameter("scale_factor").Value,
|
||||
FalseEasting = projection.GetParameter("false_easting").Value,
|
||||
FalseNorthing = projection.GetParameter("false_northing").Value,
|
||||
Hemisphere = projection.GetParameter("latitude_of_origin").Value >= 0 ? Hemisphere.North : Hemisphere.South
|
||||
LatitudeOfOrigin = projection.GetParameter("latitude_of_origin").Value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace MapControl.Projections
|
|||
{ 29193, WktConstants.ProjCsSad69Utm23S },
|
||||
};
|
||||
|
||||
public override MapProjection GetProjection(string crsId)
|
||||
protected override MapProjection CreateProjection(string crsId)
|
||||
{
|
||||
return crsId switch
|
||||
{
|
||||
|
|
@ -32,11 +32,11 @@ namespace MapControl.Projections
|
|||
MapControl.WorldMercatorProjection.DefaultCrsId => new WorldMercatorProjection(),
|
||||
MapControl.Wgs84UpsNorthProjection.DefaultCrsId => new Wgs84UpsNorthProjection(),
|
||||
MapControl.Wgs84UpsSouthProjection.DefaultCrsId => new Wgs84UpsSouthProjection(),
|
||||
_ => GetProjectionFromEpsgCode(crsId) ?? base.GetProjection(crsId)
|
||||
_ => base.CreateProjection(crsId)
|
||||
};
|
||||
}
|
||||
|
||||
public override MapProjection GetProjection(int epsgCode)
|
||||
protected override MapProjection CreateProjection(int epsgCode)
|
||||
{
|
||||
if (CoordinateSystemWkts.TryGetValue(epsgCode, out string wkt))
|
||||
{
|
||||
|
|
@ -57,7 +57,7 @@ namespace MapControl.Projections
|
|||
and <= MapControl.Wgs84UtmProjection.LastZoneNorthEpsgCode => new Wgs84UtmProjection(c % 100, Hemisphere.North),
|
||||
var c when c is >= MapControl.Wgs84UtmProjection.FirstZoneSouthEpsgCode
|
||||
and <= MapControl.Wgs84UtmProjection.LastZoneSouthEpsgCode => new Wgs84UtmProjection(c % 100, Hemisphere.South),
|
||||
_ => base.GetProjection(epsgCode)
|
||||
_ => base.CreateProjection(epsgCode)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue