Extend map projections

This commit is contained in:
ClemensFischer 2022-12-14 18:02:19 +01:00
parent 57e614978b
commit d8d1cbccaf
27 changed files with 448 additions and 207 deletions

View file

@ -0,0 +1,40 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// © 2022 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
namespace MapControl
{
/// <summary>
/// ETRS89 UTM Projection with zone number.
/// </summary>
public class Etrs89UtmProjection : TransverseMercatorProjection
{
public const int FirstZone = 28;
public const int LastZone = 38;
public const int FirstZoneEpsgCode = 25800 + FirstZone;
public const int LastZoneEpsgCode = 25800 + LastZone;
public int Zone { get; }
public Etrs89UtmProjection(int zone)
{
if (zone < FirstZone || zone > LastZone)
{
throw new ArgumentException($"Invalid ETRS89 UTM zone {zone}.", nameof(zone));
}
Zone = zone;
CrsId = $"EPSG:{Zone - FirstZone + FirstZoneEpsgCode}";
// GRS 1980
EquatorialRadius = 6378137d;
Flattening = 1d / 298.257222101;
ScaleFactor = DefaultScaleFactor;
CentralMeridian = Zone * 6d - 183d;
FalseEasting = 5e5;
FalseNorthing = 0d;
}
}
}

View file

@ -381,6 +381,9 @@ namespace MapControl
public void ZoomToBounds(BoundingBox boundingBox) public void ZoomToBounds(BoundingBox boundingBox)
{ {
var mapRect = MapProjection.BoundingBoxToMapRect(boundingBox); var mapRect = MapProjection.BoundingBoxToMapRect(boundingBox);
if (mapRect != null)
{
var targetCenter = MapProjection.MapToLocation(mapRect.Center); var targetCenter = MapProjection.MapToLocation(mapRect.Center);
if (targetCenter != null) if (targetCenter != null)
@ -392,6 +395,7 @@ namespace MapControl
TargetHeading = 0d; TargetHeading = 0d;
} }
} }
}
internal double ConstrainedLongitude(double longitude) internal double ConstrainedLongitude(double longitude)
{ {

View file

@ -134,15 +134,11 @@ namespace MapControl
// //
const double p = 0.01; const double p = 0.01;
var bbox = string.Format(CultureInfo.InvariantCulture, "{0:F2},{1:F2},{2:F2},{3:F2}", return string.Format(CultureInfo.InvariantCulture, "{0:F2},{1:F2},{2:F2},{3:F2}",
p * Math.Ceiling(mapRect.XMin / p), p * Math.Ceiling(mapRect.XMin / p),
p * Math.Ceiling(mapRect.YMin / p), p * Math.Ceiling(mapRect.YMin / p),
p * Math.Floor(mapRect.XMax / p), p * Math.Floor(mapRect.XMax / p),
p * Math.Floor(mapRect.YMax / p)); p * Math.Floor(mapRect.YMax / p));
System.Diagnostics.Debug.WriteLine(bbox);
return bbox;
} }
} }
} }

View file

@ -40,6 +40,10 @@ namespace MapControl
projection = new Nad27UtmProjection(epsgCode % 100); projection = new Nad27UtmProjection(epsgCode % 100);
break; break;
case var c when c >= Nad83UtmProjection.FirstZoneEpsgCode && c <= Nad83UtmProjection.LastZoneEpsgCode:
projection = new Nad83UtmProjection(epsgCode % 100);
break;
case var c when c >= Wgs84UtmProjection.FirstZoneNorthEpsgCode && c <= Wgs84UtmProjection.LastZoneNorthEpsgCode: case var c when c >= Wgs84UtmProjection.FirstZoneNorthEpsgCode && c <= Wgs84UtmProjection.LastZoneNorthEpsgCode:
projection = new Wgs84UtmProjection(epsgCode % 100, true); projection = new Wgs84UtmProjection(epsgCode % 100, true);
break; break;

View file

@ -0,0 +1,40 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// © 2022 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
namespace MapControl
{
/// <summary>
/// NAD27 UTM Projection with zone number.
/// </summary>
public class Nad27UtmProjection : TransverseMercatorProjection
{
public const int FirstZone = 1;
public const int LastZone = 22;
public const int FirstZoneEpsgCode = 26700 + FirstZone;
public const int LastZoneEpsgCode = 26700 + LastZone;
public int Zone { get; }
public Nad27UtmProjection(int zone)
{
if (zone < FirstZone || zone > LastZone)
{
throw new ArgumentException($"Invalid NAD27 UTM zone {zone}.", nameof(zone));
}
Zone = zone;
CrsId = $"EPSG:{Zone - FirstZone + FirstZoneEpsgCode}";
// Clarke 1866
EquatorialRadius = 6378206.4;
Flattening = 1d / 294.978698213898;
ScaleFactor = DefaultScaleFactor;
CentralMeridian = Zone * 6d - 183d;
FalseEasting = 5e5;
FalseNorthing = 0d;
}
}
}

View file

@ -0,0 +1,40 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// © 2022 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
namespace MapControl
{
/// <summary>
/// NAD83 UTM Projection with zone number.
/// </summary>
public class Nad83UtmProjection : TransverseMercatorProjection
{
public const int FirstZone = 1;
public const int LastZone = 23;
public const int FirstZoneEpsgCode = 26900 + FirstZone;
public const int LastZoneEpsgCode = 26900 + LastZone;
public int Zone { get; }
public Nad83UtmProjection(int zone)
{
if (zone < FirstZone || zone > LastZone)
{
throw new ArgumentException($"Invalid NAD83 UTM zone {zone}.", nameof(zone));
}
Zone = zone;
CrsId = $"EPSG:{Zone - FirstZone + FirstZoneEpsgCode}";
// GRS 1980
EquatorialRadius = 6378137d;
Flattening = 1d / 298.257222101;
ScaleFactor = DefaultScaleFactor;
CentralMeridian = Zone * 6d - 183d;
FalseEasting = 5e5;
FalseNorthing = 0d;
}
}
}

View file

@ -6,70 +6,6 @@ using System;
namespace MapControl namespace MapControl
{ {
/// <summary>
/// ETRS89 UTM Projection with zone number.
/// </summary>
public class Etrs89UtmProjection : TransverseMercatorProjection
{
public const int FirstZone = 28;
public const int LastZone = 38;
public const int FirstZoneEpsgCode = 25800 + FirstZone;
public const int LastZoneEpsgCode = 25800 + LastZone;
public int Zone { get; }
public Etrs89UtmProjection(int zone)
{
if (zone < FirstZone || zone > LastZone)
{
throw new ArgumentException($"Invalid ETRS89 UTM zone {zone}.", nameof(zone));
}
Zone = zone;
CrsId = $"EPSG:{Zone - FirstZone + FirstZoneEpsgCode}";
// GRS 1980
EquatorialRadius = 6378137d;
Flattening = 1d / 298.257222101;
ScaleFactor = DefaultScaleFactor;
CentralMeridian = Zone * 6d - 183d;
FalseEasting = 5e5;
FalseNorthing = 0d;
}
}
/// <summary>
/// NAD27 UTM Projection with zone number.
/// </summary>
public class Nad27UtmProjection : TransverseMercatorProjection
{
public const int FirstZone = 1;
public const int LastZone = 22;
public const int FirstZoneEpsgCode = 26700 + FirstZone;
public const int LastZoneEpsgCode = 26700 + LastZone;
public int Zone { get; }
public Nad27UtmProjection(int zone)
{
if (zone < FirstZone || zone > LastZone)
{
throw new ArgumentException($"Invalid NAD27 UTM zone {zone}.", nameof(zone));
}
Zone = zone;
CrsId = $"EPSG:{Zone - FirstZone + FirstZoneEpsgCode}";
// Clarke 1866
EquatorialRadius = 6378206.4;
Flattening = 1d / 294.978698213898;
ScaleFactor = DefaultScaleFactor;
CentralMeridian = Zone * 6d - 183d;
FalseEasting = 5e5;
FalseNorthing = 0d;
}
}
/// <summary> /// <summary>
/// WGS84 UTM Projection with zone number and north/south flag. /// WGS84 UTM Projection with zone number and north/south flag.
/// </summary> /// </summary>
@ -107,7 +43,7 @@ namespace MapControl
IsNorth = north; IsNorth = north;
CrsId = crsId ?? $"EPSG:{epsgCode}"; CrsId = crsId ?? $"EPSG:{epsgCode}";
EquatorialRadius = Wgs84EquatorialRadius; EquatorialRadius = Wgs84EquatorialRadius;
Flattening = 1d / Wgs84Flattening; Flattening = Wgs84Flattening;
ScaleFactor = DefaultScaleFactor; ScaleFactor = DefaultScaleFactor;
CentralMeridian = Zone * 6d - 183d; CentralMeridian = Zone * 6d - 183d;
FalseEasting = 5e5; FalseEasting = 5e5;

View file

@ -240,6 +240,12 @@ namespace MapControl
protected virtual string GetMapRequestUri(BoundingBox boundingBox) protected virtual string GetMapRequestUri(BoundingBox boundingBox)
{ {
var mapRect = ParentMap.MapProjection.BoundingBoxToMapRect(boundingBox); var mapRect = ParentMap.MapProjection.BoundingBoxToMapRect(boundingBox);
if (mapRect == null)
{
return null;
}
var viewScale = ParentMap.ViewTransform.Scale; var viewScale = ParentMap.ViewTransform.Scale;
return GetRequestUri(new Dictionary<string, string> return GetRequestUri(new Dictionary<string, string>
@ -265,6 +271,12 @@ namespace MapControl
var viewSize = ParentMap.RenderSize; var viewSize = ParentMap.RenderSize;
var boundingBox = ParentMap.ViewRectToBoundingBox(new Rect(0d, 0d, viewSize.Width, viewSize.Height)); var boundingBox = ParentMap.ViewRectToBoundingBox(new Rect(0d, 0d, viewSize.Width, viewSize.Height));
var mapRect = ParentMap.MapProjection.BoundingBoxToMapRect(boundingBox); var mapRect = ParentMap.MapProjection.BoundingBoxToMapRect(boundingBox);
if (mapRect == null)
{
return null;
}
var viewRect = GetViewRect(mapRect); var viewRect = GetViewRect(mapRect);
var transform = new Matrix(1, 0, 0, 1, -viewSize.Width / 2, -viewSize.Height / 2); var transform = new Matrix(1, 0, 0, 1, -viewSize.Width / 2, -viewSize.Height / 2);

View file

@ -71,6 +71,9 @@
<Compile Include="..\Shared\EquirectangularProjection.cs"> <Compile Include="..\Shared\EquirectangularProjection.cs">
<Link>EquirectangularProjection.cs</Link> <Link>EquirectangularProjection.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\Etrs89UtmProjection.cs">
<Link>Etrs89UtmProjection.cs</Link>
</Compile>
<Compile Include="..\Shared\FilePath.cs"> <Compile Include="..\Shared\FilePath.cs">
<Link>FilePath.cs</Link> <Link>FilePath.cs</Link>
</Compile> </Compile>
@ -143,6 +146,12 @@
<Compile Include="..\Shared\MapTileLayerBase.cs"> <Compile Include="..\Shared\MapTileLayerBase.cs">
<Link>MapTileLayerBase.cs</Link> <Link>MapTileLayerBase.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\Nad27UtmProjection.cs">
<Link>Nad27UtmProjection.cs</Link>
</Compile>
<Compile Include="..\Shared\Nad83UtmProjection.cs">
<Link>Nad83UtmProjection.cs</Link>
</Compile>
<Compile Include="..\Shared\OrthographicProjection.cs"> <Compile Include="..\Shared\OrthographicProjection.cs">
<Link>OrthographicProjection.cs</Link> <Link>OrthographicProjection.cs</Link>
</Compile> </Compile>
@ -176,9 +185,6 @@
<Compile Include="..\Shared\TransverseMercatorProjection.cs"> <Compile Include="..\Shared\TransverseMercatorProjection.cs">
<Link>TransverseMercatorProjection.cs</Link> <Link>TransverseMercatorProjection.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\UtmProjection.cs">
<Link>UtmProjection.cs</Link>
</Compile>
<Compile Include="..\Shared\ViewportChangedEventArgs.cs"> <Compile Include="..\Shared\ViewportChangedEventArgs.cs">
<Link>ViewportChangedEventArgs.cs</Link> <Link>ViewportChangedEventArgs.cs</Link>
</Compile> </Compile>
@ -191,6 +197,9 @@
<Compile Include="..\Shared\WebMercatorProjection.cs"> <Compile Include="..\Shared\WebMercatorProjection.cs">
<Link>WebMercatorProjection.cs</Link> <Link>WebMercatorProjection.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\Wgs84UtmProjection.cs">
<Link>Wgs84UtmProjection.cs</Link>
</Compile>
<Compile Include="..\Shared\WmsImageLayer.cs"> <Compile Include="..\Shared\WmsImageLayer.cs">
<Link>WmsImageLayer.cs</Link> <Link>WmsImageLayer.cs</Link>
</Compile> </Compile>

View file

@ -37,17 +37,21 @@ namespace MapControl
if (projection != null && items != null) if (projection != null && items != null)
{ {
image = await Task.Run(() => GetImage(projection, boundingBox, items)); var mapRect = projection.BoundingBoxToMapRect(boundingBox);
if (mapRect != null)
{
image = await Task.Run(() => GetImage(projection, mapRect, items));
}
} }
return image; return image;
} }
private DrawingImage GetImage(MapProjection projection, BoundingBox boundingBox, IEnumerable<IMapDrawingItem> items) private DrawingImage GetImage(MapProjection projection, MapRect mapRect, IEnumerable<IMapDrawingItem> items)
{ {
var scale = ParentMap.ViewTransform.Scale; var scale = ParentMap.ViewTransform.Scale;
var rotation = ParentMap.ViewTransform.Rotation; var rotation = ParentMap.ViewTransform.Rotation;
var mapRect = projection.BoundingBoxToMapRect(boundingBox);
var drawings = new DrawingGroup(); var drawings = new DrawingGroup();
foreach (var item in items) foreach (var item in items)

View file

@ -6,15 +6,26 @@ using System;
namespace MapControl.Projections namespace MapControl.Projections
{ {
/// <summary>
/// ED50 UTM Projection with zone number.
/// </summary>
public class Ed50UtmProjection : GeoApiProjection public class Ed50UtmProjection : GeoApiProjection
{ {
public const int FirstZone = 28;
public const int LastZone = 38;
public const int FirstZoneEpsgCode = 23000 + FirstZone;
public const int LastZoneEpsgCode = 23000 + LastZone;
public int Zone { get; }
public Ed50UtmProjection(int zone) public Ed50UtmProjection(int zone)
{ {
if (zone < 28 || zone > 38) if (zone < FirstZone || zone > LastZone)
{ {
throw new ArgumentException($"Invalid UTM zone {zone}.", nameof(zone)); throw new ArgumentException($"Invalid ED50 UTM zone {zone}.", nameof(zone));
} }
Zone = zone;
CoordinateSystemWkt CoordinateSystemWkt
= $"PROJCS[\"ED50 / UTM zone {zone}N\"," = $"PROJCS[\"ED50 / UTM zone {zone}N\","
+ "GEOGCS[\"ED50\"," + "GEOGCS[\"ED50\","
@ -23,10 +34,8 @@ namespace MapControl.Projections
+ "AUTHORITY[\"EPSG\",\"7022\"]]," + "AUTHORITY[\"EPSG\",\"7022\"]],"
+ "TOWGS84[-87,-98,-121,0,0,0,0]," + "TOWGS84[-87,-98,-121,0,0,0,0],"
+ "AUTHORITY[\"EPSG\",\"6230\"]]," + "AUTHORITY[\"EPSG\",\"6230\"]],"
+ "PRIMEM[\"Greenwich\",0," + "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
+ "AUTHORITY[\"EPSG\",\"8901\"]]," + "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
+ "UNIT[\"degree\",0.0174532925199433,"
+ "AUTHORITY[\"EPSG\",\"9122\"]],"
+ "AUTHORITY[\"EPSG\",\"4230\"]]," + "AUTHORITY[\"EPSG\",\"4230\"]],"
+ "PROJECTION[\"Transverse_Mercator\"]," + "PROJECTION[\"Transverse_Mercator\"],"
+ "PARAMETER[\"latitude_of_origin\",0]," + "PARAMETER[\"latitude_of_origin\",0],"
@ -34,8 +43,7 @@ namespace MapControl.Projections
+ "PARAMETER[\"scale_factor\",0.9996]," + "PARAMETER[\"scale_factor\",0.9996],"
+ "PARAMETER[\"false_easting\",500000]," + "PARAMETER[\"false_easting\",500000],"
+ "PARAMETER[\"false_northing\",0]," + "PARAMETER[\"false_northing\",0],"
+ "UNIT[\"metre\",1," + "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],"
+ "AUTHORITY[\"EPSG\",\"9001\"]],"
+ "AXIS[\"Easting\",EAST]," + "AXIS[\"Easting\",EAST],"
+ "AXIS[\"Northing\",NORTH]," + "AXIS[\"Northing\",NORTH],"
+ $"AUTHORITY[\"EPSG\",\"230{zone}\"]]"; + $"AUTHORITY[\"EPSG\",\"230{zone}\"]]";

View file

@ -6,15 +6,26 @@ using System;
namespace MapControl.Projections namespace MapControl.Projections
{ {
/// <summary>
/// ETRS89 UTM Projection with zone number.
/// </summary>
public class Etrs89UtmProjection : GeoApiProjection public class Etrs89UtmProjection : GeoApiProjection
{ {
public const int FirstZone = 28;
public const int LastZone = 38;
public const int FirstZoneEpsgCode = 25800 + FirstZone;
public const int LastZoneEpsgCode = 25800 + LastZone;
public int Zone { get; }
public Etrs89UtmProjection(int zone) public Etrs89UtmProjection(int zone)
{ {
if (zone < 28 || zone > 38) if (zone < FirstZone || zone > LastZone)
{ {
throw new ArgumentException($"Invalid UTM zone {zone}.", nameof(zone)); throw new ArgumentException($"Invalid ETRS89 UTM zone {zone}.", nameof(zone));
} }
Zone = zone;
CoordinateSystemWkt CoordinateSystemWkt
= $"PROJCS[\"ETRS89 / UTM zone {zone}N\"," = $"PROJCS[\"ETRS89 / UTM zone {zone}N\","
+ "GEOGCS[\"ETRS89\"," + "GEOGCS[\"ETRS89\","
@ -23,10 +34,8 @@ namespace MapControl.Projections
+ "AUTHORITY[\"EPSG\",\"7019\"]]," + "AUTHORITY[\"EPSG\",\"7019\"]],"
+ "TOWGS84[0,0,0,0,0,0,0]," + "TOWGS84[0,0,0,0,0,0,0],"
+ "AUTHORITY[\"EPSG\",\"6258\"]]," + "AUTHORITY[\"EPSG\",\"6258\"]],"
+ "PRIMEM[\"Greenwich\",0," + "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
+ "AUTHORITY[\"EPSG\",\"8901\"]]," + "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
+ "UNIT[\"degree\",0.0174532925199433,"
+ "AUTHORITY[\"EPSG\",\"9122\"]],"
+ "AUTHORITY[\"EPSG\",\"4258\"]]," + "AUTHORITY[\"EPSG\",\"4258\"]],"
+ "PROJECTION[\"Transverse_Mercator\"]," + "PROJECTION[\"Transverse_Mercator\"],"
+ "PARAMETER[\"latitude_of_origin\",0]," + "PARAMETER[\"latitude_of_origin\",0],"
@ -34,8 +43,7 @@ namespace MapControl.Projections
+ "PARAMETER[\"scale_factor\",0.9996]," + "PARAMETER[\"scale_factor\",0.9996],"
+ "PARAMETER[\"false_easting\",500000]," + "PARAMETER[\"false_easting\",500000],"
+ "PARAMETER[\"false_northing\",0]," + "PARAMETER[\"false_northing\",0],"
+ "UNIT[\"metre\",1," + "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],"
+ "AUTHORITY[\"EPSG\",\"9001\"]],"
+ "AXIS[\"Easting\",EAST]," + "AXIS[\"Easting\",EAST],"
+ "AXIS[\"Northing\",NORTH]," + "AXIS[\"Northing\",NORTH],"
+ $"AUTHORITY[\"EPSG\",\"258{zone}\"]]"; + $"AUTHORITY[\"EPSG\",\"258{zone}\"]]";

View file

@ -8,17 +8,6 @@ namespace MapControl.Projections
{ {
public class GeoApiProjectionFactory : MapProjectionFactory public class GeoApiProjectionFactory : MapProjectionFactory
{ {
private const int WorldMercator = 3395;
private const int WebMercator = 3857;
private const int Ed50UtmFirst = 23028;
private const int Ed50UtmLast = 23038;
private const int Etrs89UtmFirst = 25828;
private const int Etrs89UtmLast = 25838;
private const int Wgs84UtmNorthFirst = 32601;
private const int Wgs84UtmNorthLast = 32660;
private const int Wgs84UtmSouthFirst = 32701;
private const int Wgs84UtmSouthLast = 32760;
public Dictionary<int, string> CoordinateSystemWkts { get; } = new Dictionary<int, string>(); public Dictionary<int, string> CoordinateSystemWkts { get; } = new Dictionary<int, string>();
public override MapProjection GetProjection(int epsgCode) public override MapProjection GetProjection(int epsgCode)
@ -33,27 +22,35 @@ namespace MapControl.Projections
{ {
switch (epsgCode) switch (epsgCode)
{ {
case WorldMercator: case WorldMercatorProjection.DefaultEpsgCode:
projection = new WorldMercatorProjection(); projection = new WorldMercatorProjection();
break; break;
case WebMercator: case WebMercatorProjection.DefaultEpsgCode:
projection = new WebMercatorProjection(); projection = new WebMercatorProjection();
break; break;
case int c when c >= Ed50UtmFirst && c <= Ed50UtmLast: case int c when c >= Ed50UtmProjection.FirstZoneEpsgCode && c <= Ed50UtmProjection.LastZoneEpsgCode:
projection = new Ed50UtmProjection(epsgCode % 100); projection = new Ed50UtmProjection(epsgCode % 100);
break; break;
case int c when c >= Etrs89UtmFirst && c <= Etrs89UtmLast: case var c when c >= Etrs89UtmProjection.FirstZoneEpsgCode && c <= Etrs89UtmProjection.LastZoneEpsgCode:
projection = new Etrs89UtmProjection(epsgCode % 100); projection = new Etrs89UtmProjection(epsgCode % 100);
break; break;
case int c when c >= Wgs84UtmNorthFirst && c <= Wgs84UtmNorthLast: case var c when c >= Nad27UtmProjection.FirstZoneEpsgCode && c <= Nad27UtmProjection.LastZoneEpsgCode:
projection = new Nad27UtmProjection(epsgCode % 100);
break;
case var c when c >= Nad83UtmProjection.FirstZoneEpsgCode && c <= Nad83UtmProjection.LastZoneEpsgCode:
projection = new Nad83UtmProjection(epsgCode % 100);
break;
case var c when c >= Wgs84UtmProjection.FirstZoneNorthEpsgCode && c <= Wgs84UtmProjection.LastZoneNorthEpsgCode:
projection = new Wgs84UtmProjection(epsgCode % 100, true); projection = new Wgs84UtmProjection(epsgCode % 100, true);
break; break;
case int c when c >= Wgs84UtmSouthFirst && c <= Wgs84UtmSouthLast: case var c when c >= Wgs84UtmProjection.FirstZoneSouthEpsgCode && c <= Wgs84UtmProjection.LastZoneSouthEpsgCode:
projection = new Wgs84UtmProjection(epsgCode % 100, false); projection = new Wgs84UtmProjection(epsgCode % 100, false);
break; break;

View file

@ -0,0 +1,50 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// © 2022 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
namespace MapControl.Projections
{
/// <summary>
/// NAD27 UTM Projection with zone number.
/// </summary>
public class Nad27UtmProjection : GeoApiProjection
{
public const int FirstZone = 1;
public const int LastZone = 22;
public const int FirstZoneEpsgCode = 26700 + FirstZone;
public const int LastZoneEpsgCode = 26700 + LastZone;
public int Zone { get; }
public Nad27UtmProjection(int zone)
{
if (zone < FirstZone || zone > LastZone)
{
throw new ArgumentException($"Invalid NAD27 UTM zone {zone}.", nameof(zone));
}
Zone = zone;
CoordinateSystemWkt
= $"PROJCS[\"NAD27 / UTM zone {zone}N\","
+ "GEOGCS[\"NAD27\","
+ "DATUM[\"North_American_Datum_1927\","
+ "SPHEROID[\"Clarke 1866\",6378206.4,294.978698213898],"
+ "]," //"EXTENSION[\"PROJ4_GRIDS\",\"NTv2_0.gsb\"]]," -- not recognized
+ "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
+ "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
+ "AUTHORITY[\"EPSG\",\"4267\"]],"
+ "PROJECTION[\"Transverse_Mercator\"],"
+ "PARAMETER[\"latitude_of_origin\",0],"
+ $"PARAMETER[\"central_meridian\",{6 * zone - 183}],"
+ "PARAMETER[\"scale_factor\",0.9996],"
+ "PARAMETER[\"false_easting\",500000],"
+ "PARAMETER[\"false_northing\",0],"
+ "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],"
+ "AXIS[\"Easting\",EAST],"
+ "AXIS[\"Northing\",NORTH],"
+ $"AUTHORITY[\"EPSG\",\"267{zone}\"]]";
}
}
}

View file

@ -0,0 +1,50 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// © 2022 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
namespace MapControl.Projections
{
/// <summary>
/// NAD83 UTM Projection with zone number.
/// </summary>
public class Nad83UtmProjection : GeoApiProjection
{
public const int FirstZone = 1;
public const int LastZone = 23;
public const int FirstZoneEpsgCode = 26900 + FirstZone;
public const int LastZoneEpsgCode = 26900 + LastZone;
public int Zone { get; }
public Nad83UtmProjection(int zone)
{
if (zone < FirstZone || zone > LastZone)
{
throw new ArgumentException($"Invalid NAD83 UTM zone {zone}.", nameof(zone));
}
Zone = zone;
CoordinateSystemWkt
= $"PROJCS[\"NAD83 / UTM zone {zone}N\","
+ "GEOGCS[\"NAD83\","
+ "DATUM[\"North_American_Datum_1983\","
+ "SPHEROID[\"GRS 1980\",6378137,298.257222101],"
+ "TOWGS84[0,0,0,0,0,0,0]],"
+ "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
+ "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
+ "AUTHORITY[\"EPSG\",\"4269\"]],"
+ "PROJECTION[\"Transverse_Mercator\"],"
+ "PARAMETER[\"latitude_of_origin\",0],"
+ $"PARAMETER[\"central_meridian\",{6 * zone - 183}],"
+ "PARAMETER[\"scale_factor\",0.9996],"
+ "PARAMETER[\"false_easting\",500000],"
+ "PARAMETER[\"false_northing\",0],"
+ "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],"
+ "AXIS[\"Easting\",EAST],"
+ "AXIS[\"Northing\",NORTH],"
+ $"AUTHORITY[\"EPSG\",\"269{zone}\"]]";
}
}
}

View file

@ -13,6 +13,8 @@ namespace MapControl.Projections
/// </summary> /// </summary>
public class WebMercatorProjection : GeoApiProjection public class WebMercatorProjection : GeoApiProjection
{ {
public const int DefaultEpsgCode = 3857;
public WebMercatorProjection() public WebMercatorProjection()
{ {
CoordinateSystem = ProjectedCoordinateSystem.WebMercator; CoordinateSystem = ProjectedCoordinateSystem.WebMercator;

View file

@ -1,56 +0,0 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// © 2022 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using ProjNet.CoordinateSystems;
using System;
namespace MapControl.Projections
{
public class Wgs84AutoUtmProjection : GeoApiProjection
{
public const string DefaultCrsId = "AUTO2:42001";
public Wgs84AutoUtmProjection(bool useZoneCrsId = false)
{
UseZoneCrsId = useZoneCrsId;
UpdateZone();
}
public int Zone { get; private set; }
public bool IsNorth { get; private set; }
public bool UseZoneCrsId { get; }
public override Location Center
{
get => base.Center;
set
{
if (!Equals(base.Center, value))
{
base.Center = value;
UpdateZone();
}
}
}
private void UpdateZone()
{
var lon = Location.NormalizeLongitude(Center.Longitude);
var zone = (int)Math.Floor(lon / 6d) + 31;
var north = Center.Latitude >= 0d;
if (Zone != zone || IsNorth != north)
{
Zone = zone;
IsNorth = north;
CoordinateSystem = ProjectedCoordinateSystem.WGS84_UTM(Zone, IsNorth);
if (!UseZoneCrsId)
{
CrsId = DefaultCrsId;
}
}
}
}
}

View file

@ -7,16 +7,86 @@ using System;
namespace MapControl.Projections namespace MapControl.Projections
{ {
/// <summary>
/// WGS84 UTM Projection with zone number and north/south flag.
/// </summary>
public class Wgs84UtmProjection : GeoApiProjection public class Wgs84UtmProjection : GeoApiProjection
{ {
public Wgs84UtmProjection(int zone, bool north) public const int FirstZone = 1;
public const int LastZone = 60;
public const int FirstZoneNorthEpsgCode = 32600 + FirstZone;
public const int LastZoneNorthEpsgCode = 32600 + LastZone;
public const int FirstZoneSouthEpsgCode = 32700 + FirstZone;
public const int LastZoneSouthEpsgCode = 32700 + LastZone;
public int Zone { get; private set; }
public bool IsNorth { get; private set; }
protected Wgs84UtmProjection()
{ {
if (zone < 1 || zone > 60)
{
throw new ArgumentException($"Invalid UTM zone {zone}.", nameof(zone));
} }
CoordinateSystem = ProjectedCoordinateSystem.WGS84_UTM(zone, north); public Wgs84UtmProjection(int zone, bool north)
{
SetZone(zone, north);
}
protected void SetZone(int zone, bool north)
{
if (zone < FirstZone || zone > LastZone)
{
throw new ArgumentException($"Invalid WGS84 UTM zone {zone}.", nameof(zone));
}
Zone = zone;
IsNorth = north;
CoordinateSystem = ProjectedCoordinateSystem.WGS84_UTM(Zone, IsNorth);
}
}
/// <summary>
/// WGS84 UTM Projection with automatic zone selection from projection center.
/// </summary>
public class Wgs84AutoUtmProjection : Wgs84UtmProjection
{
public const string DefaultCrsId = "AUTO2:42001";
public Wgs84AutoUtmProjection(bool useZoneCrsId = false)
{
UseZoneCrsId = useZoneCrsId;
UpdateZone();
}
public bool UseZoneCrsId { get; }
public override Location Center
{
get => base.Center;
set
{
if (!Equals(base.Center, value))
{
base.Center = value;
UpdateZone();
}
}
}
private void UpdateZone()
{
var lon = Location.NormalizeLongitude(Center.Longitude);
var zone = (int)Math.Floor(lon / 6d) + 31;
var north = Center.Latitude >= 0d;
if (Zone != zone || IsNorth != north || string.IsNullOrEmpty(CrsId))
{
SetZone(zone, north);
if (!UseZoneCrsId)
{
CrsId = DefaultCrsId;
}
}
} }
} }
} }

View file

@ -12,6 +12,8 @@ namespace MapControl.Projections
/// </summary> /// </summary>
public class WorldMercatorProjection : GeoApiProjection public class WorldMercatorProjection : GeoApiProjection
{ {
public const int DefaultEpsgCode = 3395;
public WorldMercatorProjection() public WorldMercatorProjection()
{ {
CoordinateSystemWkt CoordinateSystemWkt

View file

@ -55,9 +55,6 @@
<Compile Include="..\Shared\WebMercatorProjection.cs"> <Compile Include="..\Shared\WebMercatorProjection.cs">
<Link>WebMercatorProjection.cs</Link> <Link>WebMercatorProjection.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\Wgs84AutoUtmProjection.cs">
<Link>Wgs84AutoUtmProjection.cs</Link>
</Compile>
<Compile Include="..\Shared\Wgs84UtmProjection.cs"> <Compile Include="..\Shared\Wgs84UtmProjection.cs">
<Link>Wgs84UtmProjection.cs</Link> <Link>Wgs84UtmProjection.cs</Link>
</Compile> </Compile>

Binary file not shown.

View file

@ -210,6 +210,19 @@
ServiceUri="http://ows.terrestris.de/osm/service"/> ServiceUri="http://ows.terrestris.de/osm/service"/>
</tools:MapLayerItem> </tools:MapLayerItem>
<tools:MapLayersMenuButton.MapOverlays> <tools:MapLayersMenuButton.MapOverlays>
<tools:MapLayerItem Text="Graticule">
<map:MapGraticule Opacity="0.7"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Scale">
<map:MapScale HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Seamarks">
<map:MapTileLayer SourceName="Seamarks" MinZoomLevel="9" MaxZoomLevel="18">
<map:MapTileLayer.TileSource>
<map:TileSource UriTemplate="http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png"/>
</map:MapTileLayer.TileSource>
</map:MapTileLayer>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Sample Image"> <tools:MapLayerItem Text="Sample Image">
<Image Source="10_535_330.jpg" Stretch="Fill"> <Image Source="10_535_330.jpg" Stretch="Fill">
<map:MapPanel.BoundingBox> <map:MapPanel.BoundingBox>
@ -220,18 +233,8 @@
<tools:MapLayerItem Text="Mount Etna KML"> <tools:MapLayerItem Text="Mount Etna KML">
<map:GroundOverlay SourcePath="etna.kml"/> <map:GroundOverlay SourcePath="etna.kml"/>
</tools:MapLayerItem> </tools:MapLayerItem>
<tools:MapLayerItem Text="Seamarks"> <tools:MapLayerItem Text="Chicago GeoTIFF">
<map:MapTileLayer SourceName="Seamarks" MinZoomLevel="9" MaxZoomLevel="18"> <map:GeoImage SourcePath="UTM2GTIF.tiff"/>
<map:MapTileLayer.TileSource>
<map:TileSource UriTemplate="http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png"/>
</map:MapTileLayer.TileSource>
</map:MapTileLayer>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Graticule">
<map:MapGraticule Opacity="0.7"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Scale">
<map:MapScale HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</tools:MapLayerItem> </tools:MapLayerItem>
</tools:MapLayersMenuButton.MapOverlays> </tools:MapLayersMenuButton.MapOverlays>
</tools:MapLayersMenuButton> </tools:MapLayersMenuButton>
@ -242,6 +245,8 @@
Map="{Binding ElementName=map}"> Map="{Binding ElementName=map}">
<tools:MapProjectionItem Text="Web Mercator" Projection="EPSG:3857"/> <tools:MapProjectionItem Text="Web Mercator" Projection="EPSG:3857"/>
<tools:MapProjectionItem Text="Equirectangular" Projection="EPSG:4326"/> <tools:MapProjectionItem Text="Equirectangular" Projection="EPSG:4326"/>
<tools:MapProjectionItem Text="ETRS89 / UTM zone 32N" Projection="EPSG:25832"/>
<tools:MapProjectionItem Text="WGS84 / Auto UTM" Projection="AUTO2:42001"/>
</tools:MapProjectionsMenuButton> </tools:MapProjectionsMenuButton>
<Slider Orientation="Vertical" Margin="4,8" Height="100" <Slider Orientation="Vertical" Margin="4,8" Height="100"

View file

@ -78,6 +78,9 @@
<Content Include="..\Shared\10_535_330.jpg"> <Content Include="..\Shared\10_535_330.jpg">
<Link>10_535_330.jpg</Link> <Link>10_535_330.jpg</Link>
</Content> </Content>
<Content Include="..\Shared\UTM2GTIF.tiff">
<Link>UTM2GTIF.tiff</Link>
</Content>
<Content Include="Properties\Default.rd.xml" /> <Content Include="Properties\Default.rd.xml" />
<Content Include="Assets\LockScreenLogo.scale-200.png" /> <Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\SplashScreen.scale-200.png" /> <Content Include="Assets\SplashScreen.scale-200.png" />

View file

@ -218,6 +218,19 @@
ServiceUri="http://ows.terrestris.de/osm/service"/> ServiceUri="http://ows.terrestris.de/osm/service"/>
</tools:MapLayerItem> </tools:MapLayerItem>
<tools:MapLayersMenuButton.MapOverlays> <tools:MapLayersMenuButton.MapOverlays>
<tools:MapLayerItem Text="Graticule">
<map:MapGraticule Opacity="0.7"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Scale">
<map:MapScale HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Seamarks">
<map:MapTileLayer SourceName="Seamarks" MinZoomLevel="9" MaxZoomLevel="18">
<map:MapTileLayer.TileSource>
<map:TileSource UriTemplate="http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png"/>
</map:MapTileLayer.TileSource>
</map:MapTileLayer>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Sample Image"> <tools:MapLayerItem Text="Sample Image">
<Image Source="10_535_330.jpg" Stretch="Fill"> <Image Source="10_535_330.jpg" Stretch="Fill">
<map:MapPanel.BoundingBox> <map:MapPanel.BoundingBox>
@ -228,18 +241,8 @@
<tools:MapLayerItem Text="Mount Etna KML"> <tools:MapLayerItem Text="Mount Etna KML">
<map:GroundOverlay SourcePath="etna.kml"/> <map:GroundOverlay SourcePath="etna.kml"/>
</tools:MapLayerItem> </tools:MapLayerItem>
<tools:MapLayerItem Text="Seamarks"> <tools:MapLayerItem Text="Chicago GeoTIFF">
<map:MapTileLayer SourceName="Seamarks" MinZoomLevel="9" MaxZoomLevel="18"> <map:GeoImage SourcePath="UTM2GTIF.tiff"/>
<map:MapTileLayer.TileSource>
<map:TileSource UriTemplate="http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png"/>
</map:MapTileLayer.TileSource>
</map:MapTileLayer>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Graticule">
<map:MapGraticule Opacity="0.7"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Scale">
<map:MapScale HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</tools:MapLayerItem> </tools:MapLayerItem>
</tools:MapLayersMenuButton.MapOverlays> </tools:MapLayersMenuButton.MapOverlays>
</tools:MapLayersMenuButton> </tools:MapLayersMenuButton>
@ -249,6 +252,8 @@
Map="{Binding ElementName=map}"> Map="{Binding ElementName=map}">
<tools:MapProjectionItem Text="Web Mercator" Projection="EPSG:3857"/> <tools:MapProjectionItem Text="Web Mercator" Projection="EPSG:3857"/>
<tools:MapProjectionItem Text="Equirectangular" Projection="EPSG:4326"/> <tools:MapProjectionItem Text="Equirectangular" Projection="EPSG:4326"/>
<tools:MapProjectionItem Text="ETRS89 / UTM zone 32N" Projection="EPSG:25832"/>
<tools:MapProjectionItem Text="WGS84 / Auto UTM" Projection="AUTO2:42001"/>
</tools:MapProjectionsMenuButton> </tools:MapProjectionsMenuButton>
<Slider Orientation="Vertical" Margin="4,8" Height="100" <Slider Orientation="Vertical" Margin="4,8" Height="100"

View file

@ -26,6 +26,7 @@
<ItemGroup> <ItemGroup>
<Content Include="..\Shared\10_535_330.jpg" Link="10_535_330.jpg" /> <Content Include="..\Shared\10_535_330.jpg" Link="10_535_330.jpg" />
<Content Include="..\Shared\etna.kml" Link="etna.kml" /> <Content Include="..\Shared\etna.kml" Link="etna.kml" />
<Content Include="..\Shared\UTM2GTIF.tiff" Link="UTM2GTIF.tiff" />
<Content Include="Assets\SplashScreen.scale-200.png" /> <Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\LockScreenLogo.scale-200.png" /> <Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" /> <Content Include="Assets\Square150x150Logo.scale-200.png" />

View file

@ -184,6 +184,13 @@
<tools:MapLayerItem Text="TopPlusOpen WMS" Layer="{StaticResource TopPlusOpenWMS}"/> <tools:MapLayerItem Text="TopPlusOpen WMS" Layer="{StaticResource TopPlusOpenWMS}"/>
<tools:MapLayerItem Text="OpenStreetMap WMS" Layer="{StaticResource OpenStreetMapWMS}"/> <tools:MapLayerItem Text="OpenStreetMap WMS" Layer="{StaticResource OpenStreetMapWMS}"/>
<tools:MapLayersMenuButton.MapOverlays> <tools:MapLayersMenuButton.MapOverlays>
<tools:MapLayerItem Text="Graticule">
<map:MapGraticule Opacity="0.7"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Scale">
<map:MapScale HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Seamarks" Layer="{StaticResource Seamarks}"/>
<tools:MapLayerItem Text="Sample Image"> <tools:MapLayerItem Text="Sample Image">
<Image Source="10_535_330.jpg" Stretch="Fill" <Image Source="10_535_330.jpg" Stretch="Fill"
map:MapPanel.BoundingBox="53.54031,8.08594,53.74871,8.43750"/> map:MapPanel.BoundingBox="53.54031,8.08594,53.74871,8.43750"/>
@ -191,12 +198,8 @@
<tools:MapLayerItem Text="Mount Etna KML"> <tools:MapLayerItem Text="Mount Etna KML">
<map:GroundOverlay SourcePath="etna.kml"/> <map:GroundOverlay SourcePath="etna.kml"/>
</tools:MapLayerItem> </tools:MapLayerItem>
<tools:MapLayerItem Text="Seamarks" Layer="{StaticResource Seamarks}"/> <tools:MapLayerItem Text="Chicago GeoTIFF">
<tools:MapLayerItem Text="Graticule"> <map:GeoImage SourcePath="UTM2GTIF.tiff"/>
<map:MapGraticule Opacity="0.7"/>
</tools:MapLayerItem>
<tools:MapLayerItem Text="Scale">
<map:MapScale HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</tools:MapLayerItem> </tools:MapLayerItem>
</tools:MapLayersMenuButton.MapOverlays> </tools:MapLayersMenuButton.MapOverlays>
</tools:MapLayersMenuButton> </tools:MapLayersMenuButton>
@ -206,6 +209,8 @@
Map="{Binding ElementName=map}"> Map="{Binding ElementName=map}">
<tools:MapProjectionItem Text="Web Mercator" Projection="EPSG:3857"/> <tools:MapProjectionItem Text="Web Mercator" Projection="EPSG:3857"/>
<tools:MapProjectionItem Text="Equirectangular" Projection="EPSG:4326"/> <tools:MapProjectionItem Text="Equirectangular" Projection="EPSG:4326"/>
<tools:MapProjectionItem Text="ETRS89 / UTM zone 32N" Projection="EPSG:25832"/>
<tools:MapProjectionItem Text="WGS84 / Auto UTM" Projection="AUTO2:42001"/>
</tools:MapProjectionsMenuButton> </tools:MapProjectionsMenuButton>
<Slider Orientation="Vertical" Margin="8" Height="100" <Slider Orientation="Vertical" Margin="8" Height="100"

View file

@ -32,10 +32,19 @@
<Content Include="..\Shared\etna.kml" Link="etna.kml"> <Content Include="..\Shared\etna.kml" Link="etna.kml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="..\Shared\UTM2GTIF.tiff" Link="UTM2GTIF.tiff">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net48'"> <ItemGroup Condition="'$(TargetFramework)'=='net48'">
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
<Reference Include="System.Runtime.Caching" /> <Reference Include="System.Runtime.Caching" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="..\Shared\TileLoader.cs">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Compile>
</ItemGroup>
</Project> </Project>