Version 4.16.0. Improved MapProjection.

This commit is contained in:
ClemensF 2019-12-12 19:23:41 +01:00
parent 60e0093785
commit 12566506f8
14 changed files with 136 additions and 121 deletions

View file

@ -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)

View file

@ -14,15 +14,7 @@ namespace MapControl
/// </summary>
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)
{

View file

@ -16,6 +16,11 @@ namespace MapControl
/// </summary>
public abstract class AzimuthalProjection : MapProjection
{
public override bool IsNormalCylindrical
{
get { return false; }
}
public override Rect BoundingBoxToRect(BoundingBox boundingBox)
{
var cbbox = boundingBox as CenteredBoundingBox;

View file

@ -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));
}
}
}

View file

@ -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)

View file

@ -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);

View file

@ -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
/// <summary>
/// Gets or sets the WMS 1.3.0 CRS identifier.
/// </summary>
public string CrsId { get; protected set; }
/// <summary>
/// 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").
/// </summary>
public bool HasLatLonBoundingBox { get; protected set; }
public string CrsId { get; set; }
/// <summary>
/// Indicates if this is a normal cylindrical projection.
/// </summary>
public bool IsNormalCylindrical { get; protected set; }
public virtual bool IsNormalCylindrical
{
get { return true; }
}
/// <summary>
/// Indicates if this is a web mercator projection, i.e. compatible with MapTileLayer.
/// </summary>
public bool IsWebMercator { get; protected set; }
public virtual bool IsWebMercator
{
get { return false; }
}
/// <summary>
/// Gets the absolute value of the minimum and maximum latitude that can be transformed.
/// </summary>
public virtual double MaxLatitude
{
get { return 90d; }
}
/// <summary>
/// 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.
/// </summary>
public double TrueScale { get; protected set; } = Wgs84MetersPerDegree;
/// <summary>
/// Gets the absolute value of the minimum and maximum latitude that can be transformed.
/// </summary>
public double MaxLatitude { get; protected set; } = 90d;
public virtual double TrueScale
{
get { return Wgs84MetersPerDegree; }
}
/// <summary>
/// Gets the projection center. Only relevant for azimuthal pprojections.
@ -152,13 +158,32 @@ namespace MapControl
return RectToBoundingBox(rect);
}
/// <summary>
/// Gets the CRS parameter value for a WMS GetMap request.
/// </summary>
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;
}
/// <summary>
/// Gets the BBOX parameter value for a WMS GetMap request.
/// </summary>
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));
}
/// <summary>
/// Sets ProjectionCenter, ViewportScale, ViewportTransform and InverseViewportTransform.
/// </summary>
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);

View file

@ -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)

View file

@ -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)

View file

@ -15,17 +15,21 @@ namespace MapControl
/// </summary>
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)

View file

@ -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
/// <summary>
/// Gets a list of all layer names returned by a GetCapabilities response.
/// </summary>
public async Task<IList<string>> GetLayerNamesAsync()
public async Task<List<string>> GetLayerNamesAsync()
{
IList<string> layerNames = null;
List<string> 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;
}
/// <summary>
/// Calls GetImageUri() and asynchronously loads an ImageSource from the returned GetMap URL.
/// </summary>
protected override async Task<ImageSource> GetImageAsync()
{
var uri = GetImageUri();
return uri != null ? await ImageLoader.LoadImageAsync(uri) : null;
return uri != null
? await ImageLoader.LoadImageAsync(new Uri(uri.Replace(" ", "%20")))
: null;
}
/// <summary>
/// Returns a GetMap request URL for the current BoundingBox.
/// Returns a GetMap request URL string.
/// </summary>
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;
}
/// <summary>
/// Gets the effective value of the CRS query parameter.
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Gets the effective value of the BBOX (or some equivalent) query parameter.
/// </summary>
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)

View file

@ -15,19 +15,19 @@ namespace MapControl
/// </summary>
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)

View file

@ -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));
}
}
}

View file

@ -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;