diff --git a/MapControl/Shared/AutoEquirectangularProjection.cs b/MapControl/Shared/AutoEquirectangularProjection.cs index dbdb00c8..af022da4 100644 --- a/MapControl/Shared/AutoEquirectangularProjection.cs +++ b/MapControl/Shared/AutoEquirectangularProjection.cs @@ -14,14 +14,8 @@ namespace MapControl public class AutoEquirectangularProjection : MapProjection { public AutoEquirectangularProjection() - : this("AUTO2:42004") { - } - - public AutoEquirectangularProjection(string crsId) - { - CrsId = crsId; - IsNormalCylindrical = true; + CrsId = "AUTO2:42004"; } public override Point LocationToPoint(Location location) diff --git a/MapControl/Shared/AzimuthalEquidistantProjection.cs b/MapControl/Shared/AzimuthalEquidistantProjection.cs index ec093b03..3630cecf 100644 --- a/MapControl/Shared/AzimuthalEquidistantProjection.cs +++ b/MapControl/Shared/AzimuthalEquidistantProjection.cs @@ -14,15 +14,7 @@ namespace MapControl /// public class AzimuthalEquidistantProjection : AzimuthalProjection { - public AzimuthalEquidistantProjection() - { - // No known standard or de-facto standard CRS ID - } - - public AzimuthalEquidistantProjection(string crsId) - { - CrsId = crsId; - } + // No standard CRS ID public override Point LocationToPoint(Location location) { diff --git a/MapControl/Shared/AzimuthalProjection.cs b/MapControl/Shared/AzimuthalProjection.cs index 7af050f0..79728336 100644 --- a/MapControl/Shared/AzimuthalProjection.cs +++ b/MapControl/Shared/AzimuthalProjection.cs @@ -16,6 +16,11 @@ namespace MapControl /// public abstract class AzimuthalProjection : MapProjection { + public override bool IsNormalCylindrical + { + get { return false; } + } + public override Rect BoundingBoxToRect(BoundingBox boundingBox) { var cbbox = boundingBox as CenteredBoundingBox; diff --git a/MapControl/Shared/EquirectangularProjection.cs b/MapControl/Shared/EquirectangularProjection.cs index c7e0c955..7e85f1b2 100644 --- a/MapControl/Shared/EquirectangularProjection.cs +++ b/MapControl/Shared/EquirectangularProjection.cs @@ -3,6 +3,7 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; +using System.Globalization; #if !WINDOWS_UWP using System.Windows; #endif @@ -16,16 +17,13 @@ namespace MapControl public class EquirectangularProjection : MapProjection { public EquirectangularProjection() - : this("EPSG:4326") { + CrsId = "EPSG:4326"; } - public EquirectangularProjection(string crsId) + public override double TrueScale { - CrsId = crsId; - HasLatLonBoundingBox = CrsId != "CRS:84"; - IsNormalCylindrical = true; - TrueScale = 1d; + get { return 1d; } } public override Vector GetMapScale(Location location) @@ -44,5 +42,12 @@ namespace MapControl { return new Location(point.Y, point.X); } + + public override string GetBboxValue(Rect rect) + { + return string.Format(CultureInfo.InvariantCulture, + CrsId != "CRS:84" ? "{1},{0},{3},{2}" : "{0},{1},{2},{3}", + rect.X, rect.Y, (rect.X + rect.Width), (rect.Y + rect.Height)); + } } } diff --git a/MapControl/Shared/GnomonicProjection.cs b/MapControl/Shared/GnomonicProjection.cs index 2570aab6..26ea93cf 100644 --- a/MapControl/Shared/GnomonicProjection.cs +++ b/MapControl/Shared/GnomonicProjection.cs @@ -15,13 +15,8 @@ namespace MapControl public class GnomonicProjection : AzimuthalProjection { public GnomonicProjection() - : this("AUTO2:97001") // GeoServer non-standard CRS ID { - } - - public GnomonicProjection(string crsId) - { - CrsId = crsId; + CrsId = "AUTO2:97001"; // GeoServer non-standard CRS ID } public override Point LocationToPoint(Location location) diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs index ebd4ad70..6a1abc0c 100644 --- a/MapControl/Shared/MapBase.cs +++ b/MapControl/Shared/MapBase.cs @@ -351,7 +351,7 @@ namespace MapControl var rect = MapProjection.BoundingBoxToRect(boundingBox); var center = new Point(rect.X + rect.Width / 2d, rect.Y + rect.Height / 2d); var scale = Math.Min(RenderSize.Width / rect.Width, RenderSize.Height / rect.Height) - * MapProjection.TrueScale / MapProjection.PixelPerDegree; + * MapProjection.TrueScale * 360d / MapProjection.TileSize; TargetZoomLevel = Math.Log(scale, 2d); TargetCenter = MapProjection.PointToLocation(center); diff --git a/MapControl/Shared/MapProjection.cs b/MapControl/Shared/MapProjection.cs index 721b6889..a2c091ab 100644 --- a/MapControl/Shared/MapProjection.cs +++ b/MapControl/Shared/MapProjection.cs @@ -3,6 +3,7 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; +using System.Globalization; #if WINDOWS_UWP using Windows.Foundation; #else @@ -18,7 +19,6 @@ namespace MapControl public abstract class MapProjection { public const int TileSize = 256; - public const double PixelPerDegree = TileSize / 360d; public const double Wgs84EquatorialRadius = 6378137d; public const double Wgs84Flattening = 1d / 298.257223563; @@ -29,34 +29,40 @@ namespace MapControl /// /// Gets or sets the WMS 1.3.0 CRS identifier. /// - public string CrsId { get; protected set; } - - /// - /// Indicates if a lat/lon coordinate system is used for the WMS BBOX query parameter, - /// like e.g. in an EquirectangularProjection with CrsId="EPSG:4326" (but not CrsId="CRS:84"). - /// - public bool HasLatLonBoundingBox { get; protected set; } + public string CrsId { get; set; } /// /// Indicates if this is a normal cylindrical projection. /// - public bool IsNormalCylindrical { get; protected set; } + public virtual bool IsNormalCylindrical + { + get { return true; } + } /// /// Indicates if this is a web mercator projection, i.e. compatible with MapTileLayer. /// - public bool IsWebMercator { get; protected set; } + public virtual bool IsWebMercator + { + get { return false; } + } + + /// + /// Gets the absolute value of the minimum and maximum latitude that can be transformed. + /// + public virtual double MaxLatitude + { + get { return 90d; } + } /// /// Gets the scale factor from geographic to cartesian coordinates, on the line of true scale of a /// cylindrical projection (usually the equator), or at the projection center of an azimuthal projection. /// - public double TrueScale { get; protected set; } = Wgs84MetersPerDegree; - - /// - /// Gets the absolute value of the minimum and maximum latitude that can be transformed. - /// - public double MaxLatitude { get; protected set; } = 90d; + public virtual double TrueScale + { + get { return Wgs84MetersPerDegree; } + } /// /// Gets the projection center. Only relevant for azimuthal pprojections. @@ -152,13 +158,32 @@ namespace MapControl return RectToBoundingBox(rect); } + /// + /// Gets the CRS parameter value for a WMS GetMap request. + /// + public virtual string GetCrsValue() + { + return CrsId.StartsWith("AUTO:") || CrsId.StartsWith("AUTO2:") + ? string.Format(CultureInfo.InvariantCulture, "{0},1,{1},{2}", CrsId, ProjectionCenter.Longitude, ProjectionCenter.Latitude) + : CrsId; + } + + /// + /// Gets the BBOX parameter value for a WMS GetMap request. + /// + public virtual string GetBboxValue(Rect rect) + { + return string.Format(CultureInfo.InvariantCulture, + "{0},{1},{2},{3}", rect.X, rect.Y, (rect.X + rect.Width), (rect.Y + rect.Height)); + } + /// /// Sets ProjectionCenter, ViewportScale, ViewportTransform and InverseViewportTransform. /// public void SetViewportTransform(Location projectionCenter, Location mapCenter, Point viewportCenter, double zoomLevel, double heading) { ProjectionCenter = projectionCenter; - ViewportScale = Math.Pow(2d, zoomLevel) * PixelPerDegree / TrueScale; + ViewportScale = Math.Pow(2d, zoomLevel) * TileSize / (360d * TrueScale); var center = LocationToPoint(mapCenter); var matrix = CreateTransformMatrix(center, ViewportScale, -ViewportScale, heading, viewportCenter); diff --git a/MapControl/Shared/OrthographicProjection.cs b/MapControl/Shared/OrthographicProjection.cs index cbcaf7a3..acea7799 100644 --- a/MapControl/Shared/OrthographicProjection.cs +++ b/MapControl/Shared/OrthographicProjection.cs @@ -15,13 +15,8 @@ namespace MapControl public class OrthographicProjection : AzimuthalProjection { public OrthographicProjection() - : this("AUTO2:42003") { - } - - public OrthographicProjection(string crsId) - { - CrsId = crsId; + CrsId = "AUTO2:42003"; } public override Point LocationToPoint(Location location) diff --git a/MapControl/Shared/StereographicProjection.cs b/MapControl/Shared/StereographicProjection.cs index d27f9d80..cd9c7171 100644 --- a/MapControl/Shared/StereographicProjection.cs +++ b/MapControl/Shared/StereographicProjection.cs @@ -15,13 +15,8 @@ namespace MapControl public class StereographicProjection : AzimuthalProjection { public StereographicProjection() - : this("AUTO2:97002") // GeoServer non-standard CRS ID { - } - - public StereographicProjection(string crsId) - { - CrsId = crsId; + CrsId = "AUTO2:97002"; // GeoServer non-standard CRS ID } public override Point LocationToPoint(Location location) diff --git a/MapControl/Shared/WebMercatorProjection.cs b/MapControl/Shared/WebMercatorProjection.cs index e7972fae..1ce60b6a 100644 --- a/MapControl/Shared/WebMercatorProjection.cs +++ b/MapControl/Shared/WebMercatorProjection.cs @@ -15,17 +15,21 @@ namespace MapControl /// public class WebMercatorProjection : MapProjection { + private static readonly double maxLatitude = YToLatitude(180d); + public WebMercatorProjection() - : this("EPSG:3857") { + CrsId = "EPSG:3857"; } - public WebMercatorProjection(string crsId) + public override bool IsWebMercator { - CrsId = crsId; - IsNormalCylindrical = true; - IsWebMercator = true; - MaxLatitude = YToLatitude(180d); + get { return true; } + } + + public override double MaxLatitude + { + get { return maxLatitude; } } public override Vector GetMapScale(Location location) diff --git a/MapControl/Shared/WmsImageLayer.cs b/MapControl/Shared/WmsImageLayer.cs index 9eb86381..59e5858a 100644 --- a/MapControl/Shared/WmsImageLayer.cs +++ b/MapControl/Shared/WmsImageLayer.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using System.Globalization; using System.Xml.Linq; #if WINDOWS_UWP using Windows.Foundation; @@ -65,17 +64,17 @@ namespace MapControl /// /// Gets a list of all layer names returned by a GetCapabilities response. /// - public async Task> GetLayerNamesAsync() + public async Task> GetLayerNamesAsync() { - IList layerNames = null; + List layerNames = null; if (ServiceUri != null) { - var capabilitiesUri = GetRequestUri("GetCapabilities").Replace(" ", "%20"); + var uri = GetRequestUri("GetCapabilities").Replace(" ", "%20"); try { - var stream = await ImageLoader.HttpClient.GetStreamAsync(capabilitiesUri); + var stream = await ImageLoader.HttpClient.GetStreamAsync(uri); var capabilities = XDocument.Load(stream).Root; var ns = capabilities.Name.Namespace; @@ -88,31 +87,36 @@ namespace MapControl } catch (Exception ex) { - Debug.WriteLine("WmsImageLayer: {0}: {1}", capabilitiesUri, ex.Message); + Debug.WriteLine("WmsImageLayer: {0}: {1}", uri, ex.Message); } } return layerNames; } + /// + /// Calls GetImageUri() and asynchronously loads an ImageSource from the returned GetMap URL. + /// protected override async Task GetImageAsync() { var uri = GetImageUri(); - return uri != null ? await ImageLoader.LoadImageAsync(uri) : null; + return uri != null + ? await ImageLoader.LoadImageAsync(new Uri(uri.Replace(" ", "%20"))) + : null; } /// - /// Returns a GetMap request URL for the current BoundingBox. + /// Returns a GetMap request URL string. /// - protected virtual Uri GetImageUri() + protected virtual string GetImageUri() { - Uri imageUri = null; + string uri = null; var projection = ParentMap?.MapProjection; if (ServiceUri != null && projection != null && !string.IsNullOrEmpty(projection.CrsId)) { - var uri = GetRequestUri("GetMap"); + uri = GetRequestUri("GetMap"); if (uri.IndexOf("LAYERS=", StringComparison.OrdinalIgnoreCase) < 0 && Layers != null) { @@ -131,42 +135,13 @@ namespace MapControl var rect = projection.BoundingBoxToRect(BoundingBox); - uri += "&" + GetCrsParam(projection); - uri += "&" + GetBboxParam(projection, rect); + uri += "&CRS=" + projection.GetCrsValue(); + uri += "&BBOX=" + projection.GetBboxValue(rect); uri += "&WIDTH=" + (int)Math.Round(projection.ViewportScale * rect.Width); uri += "&HEIGHT=" + (int)Math.Round(projection.ViewportScale * rect.Height); - - imageUri = new Uri(uri.Replace(" ", "%20")); } - return imageUri; - } - - /// - /// Gets the effective value of the CRS query parameter. - /// - /// - protected virtual string GetCrsParam(MapProjection projection) - { - var crs = "CRS=" + projection.CrsId; - - if (projection.CrsId.StartsWith("AUTO2:")) - { - crs += string.Format(CultureInfo.InvariantCulture, ",1,{0},{1}", - projection.ProjectionCenter.Longitude, projection.ProjectionCenter.Latitude); - } - - return crs; - } - - /// - /// Gets the effective value of the BBOX (or some equivalent) query parameter. - /// - protected virtual string GetBboxParam(MapProjection projection, Rect bbox) - { - return string.Format(CultureInfo.InvariantCulture, - projection.HasLatLonBoundingBox ? "BBOX={1},{0},{3},{2}" : "BBOX={0},{1},{2},{3}", - bbox.X, bbox.Y, (bbox.X + bbox.Width), (bbox.Y + bbox.Height)); + return uri; } private string GetRequestUri(string request) diff --git a/MapControl/Shared/WorldMercatorProjection.cs b/MapControl/Shared/WorldMercatorProjection.cs index 74e08ffb..5801b66d 100644 --- a/MapControl/Shared/WorldMercatorProjection.cs +++ b/MapControl/Shared/WorldMercatorProjection.cs @@ -15,19 +15,19 @@ namespace MapControl /// public class WorldMercatorProjection : MapProjection { + private static readonly double maxLatitude = YToLatitude(180d); + public static double ConvergenceTolerance = 1e-6; public static int MaxIterations = 10; public WorldMercatorProjection() - : this("EPSG:3395") { + CrsId = "EPSG:3395"; } - public WorldMercatorProjection(string crsId) + public override double MaxLatitude { - CrsId = crsId; - IsNormalCylindrical = true; - MaxLatitude = YToLatitude(180d); + get { return maxLatitude; } } public override Vector GetMapScale(Location location) diff --git a/MapProjections/Shared/GeoApiProjection.cs b/MapProjections/Shared/GeoApiProjection.cs index c7332df5..3d4bd867 100644 --- a/MapProjections/Shared/GeoApiProjection.cs +++ b/MapProjections/Shared/GeoApiProjection.cs @@ -3,6 +3,7 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; +using System.Globalization; #if !WINDOWS_UWP using System.Windows; #endif @@ -20,6 +21,10 @@ namespace MapControl.Projections public class GeoApiProjection : MapProjection { private ICoordinateSystem coordinateSystem; + private bool isNormalCylindrical; + private bool isWebMercator; + private double trueScale; + private string bboxFormat; public IMathTransform LocationToPointTransform { get; private set; } public IMathTransform PointToLocationTransform { get; private set; } @@ -63,21 +68,21 @@ namespace MapControl.Projections var falseNorthing = projection.GetParameter("false_northing"); var scaleFactor = projection.GetParameter("scale_factor"); - HasLatLonBoundingBox = false; - IsNormalCylindrical = + isNormalCylindrical = centralMeridian != null && centralMeridian.Value == 0d && centralParallel != null && centralParallel.Value == 0d && (falseEasting == null || falseEasting.Value == 0d) && (falseNorthing == null || falseNorthing.Value == 0d); - IsWebMercator = CrsId == "EPSG:3857" || CrsId == "EPSG:900913"; - TrueScale = (scaleFactor != null ? scaleFactor.Value : 1d) * Wgs84MetersPerDegree; + isWebMercator = CrsId == "EPSG:3857" || CrsId == "EPSG:900913"; + trueScale = (scaleFactor != null ? scaleFactor.Value : 1d) * Wgs84MetersPerDegree; + bboxFormat = "{0},{1},{2},{3}"; } else { - HasLatLonBoundingBox = true; - IsNormalCylindrical = true; - IsWebMercator = false; - TrueScale = 1d; + isNormalCylindrical = true; + isWebMercator = false; + trueScale = 1d; + bboxFormat = "{1},{0},{3},{2}"; } } } @@ -93,6 +98,21 @@ namespace MapControl.Projections set { CoordinateSystem = new CoordinateSystemFactory().CreateFromWkt(value); } } + public override bool IsNormalCylindrical + { + get { return isNormalCylindrical; } + } + + public override bool IsWebMercator + { + get { return isWebMercator; } + } + + public override double TrueScale + { + get { return trueScale; } + } + public override Point LocationToPoint(Location location) { if (LocationToPointTransform == null) @@ -116,5 +136,11 @@ namespace MapControl.Projections return new Location(coordinate.Y, coordinate.X); } + + public override string GetBboxValue(Rect rect) + { + return string.Format(CultureInfo.InvariantCulture, + bboxFormat, rect.X, rect.Y, (rect.X + rect.Width), (rect.Y + rect.Height)); + } } } diff --git a/MapProjections/Shared/PolarStereographicProjection.cs b/MapProjections/Shared/PolarStereographicProjection.cs index fb2d04e8..0b46a19a 100644 --- a/MapProjections/Shared/PolarStereographicProjection.cs +++ b/MapProjections/Shared/PolarStereographicProjection.cs @@ -27,13 +27,17 @@ namespace MapControl.Projections public PolarStereographicProjection(string crsId, bool north, double scaleFactor = 1d, double falseEasting = 0d, double falseNorthing = 0d) { CrsId = crsId; - TrueScale = scaleFactor * Wgs84MetersPerDegree; this.north = north; this.scaleFactor = scaleFactor; this.falseEasting = falseEasting; this.falseNorthing = falseNorthing; } + public override double TrueScale + { + get { return scaleFactor * Wgs84MetersPerDegree; } + } + public override Vector GetMapScale(Location location) { var lat = (north ? location.Latitude : -location.Latitude) * Math.PI / 180d;