From 16fb98ac867eb10d1ee97125eba981a869887c0b Mon Sep 17 00:00:00 2001 From: Clemens Date: Sat, 5 Mar 2022 18:40:57 +0100 Subject: [PATCH] Added MapProjection.Type --- .../Shared/AutoEquirectangularProjection.cs | 2 +- .../Shared/AzimuthalEquidistantProjection.cs | 5 ++ MapControl/Shared/AzimuthalProjection.cs | 5 ++ .../Shared/EquirectangularProjection.cs | 2 +- MapControl/Shared/MapBase.cs | 49 +++++++++++----- MapControl/Shared/MapPanel.cs | 4 +- MapControl/Shared/MapPath.cs | 2 +- MapControl/Shared/MapProjection.cs | 33 ++++++----- MapControl/Shared/MapProjectionFactory.cs | 2 +- MapControl/Shared/MapTileLayer.cs | 2 +- MapControl/Shared/WebMercatorProjection.cs | 4 +- MapControl/Shared/WorldMercatorProjection.cs | 3 +- MapControl/WPF/MapGraticule.WPF.cs | 57 ++++++++++--------- MapControl/WinUI/MapGraticule.WinUI.cs | 11 ++-- MapProjections/Shared/GeoApiProjection.cs | 22 +++++-- 15 files changed, 126 insertions(+), 77 deletions(-) diff --git a/MapControl/Shared/AutoEquirectangularProjection.cs b/MapControl/Shared/AutoEquirectangularProjection.cs index dead303e..7ba9bf93 100644 --- a/MapControl/Shared/AutoEquirectangularProjection.cs +++ b/MapControl/Shared/AutoEquirectangularProjection.cs @@ -20,8 +20,8 @@ namespace MapControl public AutoEquirectangularProjection() { + Type = MapProjectionType.NormalCylindrical; CrsId = DefaultCrsId; - IsNormalCylindrical = true; } public override Point LocationToMap(Location location) diff --git a/MapControl/Shared/AzimuthalEquidistantProjection.cs b/MapControl/Shared/AzimuthalEquidistantProjection.cs index 5e0953d9..73f8aba2 100644 --- a/MapControl/Shared/AzimuthalEquidistantProjection.cs +++ b/MapControl/Shared/AzimuthalEquidistantProjection.cs @@ -15,6 +15,11 @@ namespace MapControl /// public class AzimuthalEquidistantProjection : AzimuthalProjection { + public AzimuthalEquidistantProjection(string crsId) + { + CrsId = crsId; + } + public override Point LocationToMap(Location location) { if (location.Equals(Center)) diff --git a/MapControl/Shared/AzimuthalProjection.cs b/MapControl/Shared/AzimuthalProjection.cs index 936fcb1d..b27e3b84 100644 --- a/MapControl/Shared/AzimuthalProjection.cs +++ b/MapControl/Shared/AzimuthalProjection.cs @@ -16,6 +16,11 @@ namespace MapControl /// public abstract class AzimuthalProjection : MapProjection { + protected AzimuthalProjection() + { + Type = MapProjectionType.Azimuthal; + } + public override Rect BoundingBoxToRect(BoundingBox boundingBox) { var center = LocationToMap(boundingBox.Center); diff --git a/MapControl/Shared/EquirectangularProjection.cs b/MapControl/Shared/EquirectangularProjection.cs index ef518b4b..4ad2f0d6 100644 --- a/MapControl/Shared/EquirectangularProjection.cs +++ b/MapControl/Shared/EquirectangularProjection.cs @@ -23,8 +23,8 @@ namespace MapControl public EquirectangularProjection() { + Type = MapProjectionType.NormalCylindrical; CrsId = DefaultCrsId; - IsNormalCylindrical = true; } public override Vector GetRelativeScale(Location location) diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs index c08f3608..69b7bd59 100644 --- a/MapControl/Shared/MapBase.cs +++ b/MapControl/Shared/MapBase.cs @@ -44,7 +44,7 @@ namespace MapControl public static readonly DependencyProperty MapProjectionProperty = DependencyProperty.Register( nameof(MapProjection), typeof(MapProjection), typeof(MapBase), - new PropertyMetadata(new WebMercatorProjection(), (o, e) => ((MapBase)o).MapProjectionPropertyChanged())); + new PropertyMetadata(new WebMercatorProjection(), (o, e) => ((MapBase)o).MapProjectionPropertyChanged((MapProjection)e.NewValue))); public static readonly DependencyProperty ProjectionCenterProperty = DependencyProperty.Register( nameof(ProjectionCenter), typeof(Location), typeof(MapBase), @@ -72,6 +72,7 @@ namespace MapControl private Location transformCenter; private Point viewCenter; private double centerLongitude; + private double maxLatitude = 90d; private bool internalPropertyChange; /// @@ -436,8 +437,23 @@ namespace MapControl } } - private void MapProjectionPropertyChanged() + private void MapProjectionPropertyChanged(MapProjection projection) { + maxLatitude = 90d; + + if (projection.Type <= MapProjectionType.NormalCylindrical) + { + var maxLocation = projection.MapToLocation(new Point(0d, 180d * MapProjection.Wgs84MeterPerDegree)); + + if (maxLocation != null && maxLocation.Latitude < 90d) + { + maxLatitude = maxLocation.Latitude; + + var center = Center; + AdjustCenterProperty(CenterProperty, ref center); + } + } + ResetTransformCenter(); UpdateTransform(false, true); } @@ -450,16 +466,23 @@ namespace MapControl private void AdjustCenterProperty(DependencyProperty property, ref Location center) { - if (center == null || - center.Longitude < -180d || center.Longitude > 180d || - center.Latitude < -MapProjection.MaxLatitude || center.Latitude > MapProjection.MaxLatitude) - { - center = (center == null) - ? new Location() - : new Location( - Math.Min(Math.Max(center.Latitude, -MapProjection.MaxLatitude), MapProjection.MaxLatitude), - Location.NormalizeLongitude(center.Longitude)); + var c = center; + if (center == null) + { + center = new Location(); + } + else if ( + center.Latitude < -maxLatitude || center.Latitude > maxLatitude || + center.Longitude < -180d || center.Longitude > 180d) + { + center = new Location( + Math.Min(Math.Max(center.Latitude, -maxLatitude), maxLatitude), + Location.NormalizeLongitude(center.Longitude)); + } + + if (center != c) + { SetValueInternal(property, center); } } @@ -727,9 +750,9 @@ namespace MapControl { center.Longitude = Location.NormalizeLongitude(center.Longitude); - if (center.Latitude < -projection.MaxLatitude || center.Latitude > projection.MaxLatitude) + if (center.Latitude < -maxLatitude || center.Latitude > maxLatitude) { - center.Latitude = Math.Min(Math.Max(center.Latitude, -projection.MaxLatitude), projection.MaxLatitude); + center.Latitude = Math.Min(Math.Max(center.Latitude, -maxLatitude), maxLatitude); resetTransformCenter = true; } diff --git a/MapControl/Shared/MapPanel.cs b/MapControl/Shared/MapPanel.cs index e88dc7a2..78515e07 100644 --- a/MapControl/Shared/MapPanel.cs +++ b/MapControl/Shared/MapPanel.cs @@ -118,7 +118,7 @@ namespace MapControl var position = parentMap.LocationToView(location); - if (parentMap.MapProjection.IsNormalCylindrical && IsOutsideViewport(position)) + if (parentMap.MapProjection.Type <= MapProjectionType.NormalCylindrical && IsOutsideViewport(position)) { location = new Location(location.Latitude, parentMap.ConstrainedLongitude(location.Longitude)); @@ -144,7 +144,7 @@ namespace MapControl var center = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d); var position = parentMap.ViewTransform.MapToView(center); - if (parentMap.MapProjection.IsNormalCylindrical && IsOutsideViewport(position)) + if (parentMap.MapProjection.Type <= MapProjectionType.NormalCylindrical && IsOutsideViewport(position)) { var location = parentMap.MapProjection.MapToLocation(center); if (location != null) diff --git a/MapControl/Shared/MapPath.cs b/MapControl/Shared/MapPath.cs index 6b927611..08079599 100644 --- a/MapControl/Shared/MapPath.cs +++ b/MapControl/Shared/MapPath.cs @@ -87,7 +87,7 @@ namespace MapControl { var longitudeOffset = 0d; - if (location != null && parentMap.MapProjection.IsNormalCylindrical) + if (location != null && parentMap.MapProjection.Type <= MapProjectionType.NormalCylindrical) { var pos = parentMap.LocationToView(location); diff --git a/MapControl/Shared/MapProjection.cs b/MapControl/Shared/MapProjection.cs index 7c742a31..60ec4730 100644 --- a/MapControl/Shared/MapProjection.cs +++ b/MapControl/Shared/MapProjection.cs @@ -12,6 +12,15 @@ using System.Windows; namespace MapControl { + public enum MapProjectionType + { + WebMercator, // normal cylindrical projection compatible with MapTileLayer + NormalCylindrical, + TransverseCylindrical, + Azimuthal, + Other + } + /// /// Defines a map projection between geographic coordinates and cartesian map coordinates. /// @@ -25,30 +34,20 @@ namespace MapControl public static MapProjectionFactory Factory { get; set; } = new MapProjectionFactory(); /// - /// Gets or sets the WMS 1.3.0 CRS identifier. + /// Gets the type of the projection. /// - public string CrsId { get; set; } = string.Empty; + public MapProjectionType Type { get; protected set; } = MapProjectionType.Other; + + /// + /// Gets the WMS 1.3.0 CRS identifier. + /// + public string CrsId { get; protected set; } = string.Empty; /// /// Gets or sets the projection center. /// public Location Center { get; set; } = new Location(); - /// - /// Indicates if this is a normal cylindrical projection. - /// - public bool IsNormalCylindrical { get; protected set; } - - /// - /// Indicates if this is a web mercator projection, i.e. compatible with MapTileLayer. - /// - public bool IsWebMercator { get; protected set; } - - /// - /// Gets the absolute value of the minimum and maximum latitude that can be transformed. - /// - public double MaxLatitude { get; protected set; } = 90d; - /// /// Gets the relative map scale at the specified Location. /// diff --git a/MapControl/Shared/MapProjectionFactory.cs b/MapControl/Shared/MapProjectionFactory.cs index e4f292a0..b111a030 100644 --- a/MapControl/Shared/MapProjectionFactory.cs +++ b/MapControl/Shared/MapProjectionFactory.cs @@ -41,7 +41,7 @@ namespace MapControl break; case "EPSG:97003": // proprietary CRS ID - projection = new AzimuthalEquidistantProjection { CrsId = crsId }; + projection = new AzimuthalEquidistantProjection(crsId); break; default: diff --git a/MapControl/Shared/MapTileLayer.cs b/MapControl/Shared/MapTileLayer.cs index e98e772d..6daa411e 100644 --- a/MapControl/Shared/MapTileLayer.cs +++ b/MapControl/Shared/MapTileLayer.cs @@ -120,7 +120,7 @@ namespace MapControl { var update = false; - if (ParentMap == null || !ParentMap.MapProjection.IsWebMercator) + if (ParentMap == null || ParentMap.MapProjection.Type != MapProjectionType.WebMercator) { update = TileMatrix != null; TileMatrix = null; diff --git a/MapControl/Shared/WebMercatorProjection.cs b/MapControl/Shared/WebMercatorProjection.cs index 0660f028..78ec758c 100644 --- a/MapControl/Shared/WebMercatorProjection.cs +++ b/MapControl/Shared/WebMercatorProjection.cs @@ -19,10 +19,8 @@ namespace MapControl public WebMercatorProjection() { + Type = MapProjectionType.WebMercator; CrsId = DefaultCrsId; - IsNormalCylindrical = true; - IsWebMercator = true; - MaxLatitude = YToLatitude(180d); } public override Vector GetRelativeScale(Location location) diff --git a/MapControl/Shared/WorldMercatorProjection.cs b/MapControl/Shared/WorldMercatorProjection.cs index 88e4d5ff..fa52d0ca 100644 --- a/MapControl/Shared/WorldMercatorProjection.cs +++ b/MapControl/Shared/WorldMercatorProjection.cs @@ -22,9 +22,8 @@ namespace MapControl public WorldMercatorProjection() { + Type = MapProjectionType.NormalCylindrical; CrsId = DefaultCrsId; - IsNormalCylindrical = true; - MaxLatitude = YToLatitude(180d); } public override Vector GetRelativeScale(Location location) diff --git a/MapControl/WPF/MapGraticule.WPF.cs b/MapControl/WPF/MapGraticule.WPF.cs index 465e3e65..346dcafa 100644 --- a/MapControl/WPF/MapGraticule.WPF.cs +++ b/MapControl/WPF/MapGraticule.WPF.cs @@ -43,7 +43,7 @@ namespace MapControl if (projection != null) { - if (projection.IsNormalCylindrical) + if (projection.Type <= MapProjectionType.NormalCylindrical) { DrawCylindricalGraticule(drawingContext); } @@ -126,9 +126,6 @@ namespace MapControl var lineDistance = GetLineDistance(); var labelFormat = GetLabelFormat(lineDistance); - var centerLon = Math.Floor(ParentMap.Center.Longitude / lineDistance) * lineDistance; - var minLon = centerLon - lineDistance; - var maxLon = centerLon + lineDistance; var minLat = 0d; var maxLat = 0d; @@ -139,16 +136,21 @@ namespace MapControl var interpolationDistance = lineDistance / interpolationCount; var latPoints = latSegments * interpolationCount; + var centerLon = Math.Floor(ParentMap.Center.Longitude / lineDistance) * lineDistance; + var minLon = centerLon - lineDistance; + var maxLon = centerLon + lineDistance; + var lonRange = ParentMap.MapProjection.Type == MapProjectionType.TransverseCylindrical ? 15d : 180d; + if (DrawMeridian(path.Figures, centerLon, minLat, interpolationDistance, latPoints)) { - while (minLon > centerLon - 180d && - DrawMeridian(path.Figures, minLon, minLat, interpolationDistance, latPoints)) + while (DrawMeridian(path.Figures, minLon, minLat, interpolationDistance, latPoints) && + minLon > centerLon - lonRange) { minLon -= lineDistance; } - while (maxLon <= centerLon + 180d && - DrawMeridian(path.Figures, maxLon, minLat, interpolationDistance, latPoints)) + while (DrawMeridian(path.Figures, maxLon, minLat, interpolationDistance, latPoints) && + maxLon <= centerLon + lonRange) { maxLon += lineDistance; } @@ -160,12 +162,14 @@ namespace MapControl { var lat = minLat + s * lineDistance; var lon = minLon; + var location = new Location(lat, lon); var points = new List(); - var p = ParentMap.LocationToView(new Location(lat, lon)); + var p = ParentMap.LocationToView(location); if (MapProjection.IsValid(p)) { points.Add(p); + DrawLabel(drawingContext, typeface, pixelsPerDip, p, location, labelFormat); } for (int i = 0; i < lonSegments; i++) @@ -173,7 +177,8 @@ namespace MapControl for (int j = 1; j <= interpolationCount; j++) { lon = minLon + i * lineDistance + j * interpolationDistance; - p = ParentMap.LocationToView(new Location(lat, lon)); + location = new Location(lat, lon); + p = ParentMap.LocationToView(location); if (MapProjection.IsValid(p)) { @@ -181,11 +186,7 @@ namespace MapControl } } - if (p.X >= 0d && p.X <= ParentMap.RenderSize.Width && - p.Y >= 0d && p.Y <= ParentMap.RenderSize.Height) - { - DrawLabel(drawingContext, typeface, pixelsPerDip, p, new Location(lat, lon), labelFormat); - } + DrawLabel(drawingContext, typeface, pixelsPerDip, p, location, labelFormat); } if (points.Count >= 2) @@ -232,18 +233,22 @@ namespace MapControl private void DrawLabel(DrawingContext drawingContext, Typeface typeface, double pixelsPerDip, Point position, Location location, string labelFormat) { - var latText = new FormattedText(GetLabelText(location.Latitude, labelFormat, "NS"), - CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, pixelsPerDip); - var lonText = new FormattedText(GetLabelText(location.Longitude, labelFormat, "EW"), - CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, pixelsPerDip); - - location.Longitude += latText.Width / PixelPerLongitudeDegree(location); - - var p = ParentMap.LocationToView(location); - - if (MapProjection.IsValid(p)) + if (position.X >= 0d && position.X <= ParentMap.RenderSize.Width && + position.Y >= 0d && position.Y <= ParentMap.RenderSize.Height) { - DrawLabel(drawingContext, latText, lonText, position, Vector.AngleBetween(new Vector(1d, 0d), p - position)); + var latText = new FormattedText(GetLabelText(location.Latitude, labelFormat, "NS"), + CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, pixelsPerDip); + var lonText = new FormattedText(GetLabelText(location.Longitude, labelFormat, "EW"), + CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground, pixelsPerDip); + + location.Longitude += latText.Width / PixelPerLongitudeDegree(location); + + var p = ParentMap.LocationToView(location); + + if (MapProjection.IsValid(p)) + { + DrawLabel(drawingContext, latText, lonText, position, Vector.AngleBetween(new Vector(1d, 0d), p - position)); + } } } diff --git a/MapControl/WinUI/MapGraticule.WinUI.cs b/MapControl/WinUI/MapGraticule.WinUI.cs index d45fd79f..41b61219 100644 --- a/MapControl/WinUI/MapGraticule.WinUI.cs +++ b/MapControl/WinUI/MapGraticule.WinUI.cs @@ -30,7 +30,7 @@ namespace MapControl var map = ParentMap; var projection = map.MapProjection; - if (projection.IsNormalCylindrical) + if (projection.Type <= MapProjectionType.NormalCylindrical) { if (path == null) { @@ -43,11 +43,14 @@ namespace MapControl Children.Add(path); } + var maxLocation = projection.MapToLocation(new Point(0d, 180d * MapProjection.Wgs84MeterPerDegree)); + var maxLatitude = maxLocation != null && maxLocation.Latitude < 90d ? maxLocation.Latitude : 90d; + var bounds = map.ViewRectToBoundingBox(new Rect(0d, 0d, map.RenderSize.Width, map.RenderSize.Height)); var lineDistance = GetLineDistance(); var labelStart = new Location( - Math.Ceiling(bounds.South / lineDistance) * lineDistance, + Math.Ceiling(bounds.South / lineDistance) * lineDistance, Math.Ceiling(bounds.West / lineDistance) * lineDistance); var labelEnd = new Location( @@ -55,11 +58,11 @@ namespace MapControl Math.Floor(bounds.East / lineDistance) * lineDistance); var lineStart = new Location( - Math.Min(Math.Max(labelStart.Latitude - lineDistance, -projection.MaxLatitude), projection.MaxLatitude), + Math.Min(Math.Max(labelStart.Latitude - lineDistance, -maxLatitude), maxLatitude), labelStart.Longitude - lineDistance); var lineEnd = new Location( - Math.Min(Math.Max(labelEnd.Latitude + lineDistance, -projection.MaxLatitude), projection.MaxLatitude), + Math.Min(Math.Max(labelEnd.Latitude + lineDistance, -maxLatitude), maxLatitude), labelEnd.Longitude + lineDistance); var geometry = (PathGeometry)path.Data; diff --git a/MapProjections/Shared/GeoApiProjection.cs b/MapProjections/Shared/GeoApiProjection.cs index ceae2e51..580f7208 100644 --- a/MapProjections/Shared/GeoApiProjection.cs +++ b/MapProjections/Shared/GeoApiProjection.cs @@ -81,19 +81,31 @@ namespace MapControl.Projections var falseEasting = projection.GetParameter("false_easting"); var falseNorthing = projection.GetParameter("false_northing"); - IsNormalCylindrical = + if (CrsId == "EPSG:3857") + { + Type = MapProjectionType.WebMercator; + } + else if ( (centralMeridian == null || centralMeridian.Value == 0d) && (centralParallel == null || centralParallel.Value == 0d) && (falseEasting == null || falseEasting.Value == 0d) && - (falseNorthing == null || falseNorthing.Value == 0d); - IsWebMercator = CrsId == "EPSG:3857"; + (falseNorthing == null || falseNorthing.Value == 0d)) + { + Type = MapProjectionType.NormalCylindrical; + } + else if ( + projection.Name.StartsWith("UTM") || + projection.Name.StartsWith("Transverse")) + { + Type = MapProjectionType.TransverseCylindrical; + } + scaleFactor = 1d; bboxFormat = "{0},{1},{2},{3}"; } else { - IsNormalCylindrical = true; - IsWebMercator = false; + Type = MapProjectionType.NormalCylindrical; scaleFactor = Wgs84MeterPerDegree; bboxFormat = "{1},{0},{3},{2}"; }