Use Matrix for projection relative scale

This commit is contained in:
ClemensFischer 2026-01-20 09:48:16 +01:00
parent 2c9e478095
commit ab155a26e7
21 changed files with 93 additions and 104 deletions

View file

@ -1,6 +1,7 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
@ -27,11 +28,11 @@ namespace MapControl
CrsId = crsId;
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
return new Point(
return new Matrix(
Math.Cos(Center.Latitude * Math.PI / 180d) / Math.Cos(latitude * Math.PI / 180d),
1d);
0d, 0d, 1d, 0d, 0d);
}
public override Point? LocationToMap(double latitude, double longitude)

View file

@ -1,6 +1,7 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
@ -25,7 +26,7 @@ namespace MapControl
CrsId = crsId;
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
(var cosC, var _, var _) = GetPointValues(latitude, longitude);
var k = 1d;
@ -36,7 +37,7 @@ namespace MapControl
k = c / Math.Sin(c); // p.195 (25-2)
}
return new Point(k, k);
return new Matrix(k, 0d, 0d, k, 0d, 0d);
}
public override Point? LocationToMap(double latitude, double longitude)

View file

@ -1,6 +1,7 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
@ -27,9 +28,9 @@ namespace MapControl
CrsId = crsId;
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
return new Point(1d / Math.Cos(latitude * Math.PI / 180d), 1d);
return new Matrix(1d / Math.Cos(latitude * Math.PI / 180d), 0d, 0d, 1d, 0d, 0d);
}
public override Point? LocationToMap(double latitude, double longitude)

View file

@ -1,6 +1,7 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
@ -25,12 +26,14 @@ namespace MapControl
CrsId = crsId;
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
(var cosC, var _, var _) = GetPointValues(latitude, longitude);
var h = 1d / (cosC * cosC); // p.165 (22-2)
(var cosC, var x, var y) = GetPointValues(latitude, longitude);
var k = 1d / cosC; // p.165 (22-3)
var h = k * k; // p.165 (22-2)
return new Point(h, h); // TODO: rotate
var scale = new Matrix(h, 0d, 0d, h, 0d, 0d);
return scale;
}
public override Point? LocationToMap(double latitude, double longitude)

View file

@ -177,37 +177,25 @@ namespace MapControl
public ViewTransform ViewTransform { get; } = new ViewTransform();
/// <summary>
/// Gets the map scale as horizontal and vertical scaling factors from meters to
/// view coordinates at the specified geographic coordinates.
/// Gets a transform Matrix from meters to view coordinates for scaling and rotating
/// at the specified geographic coordinates.
/// </summary>
public Point GetMapScale(double latitude, double longitude)
public Matrix GetMapToViewTransform(double latitude, double longitude)
{
var relativeScale = MapProjection.RelativeScale(latitude, longitude);
var transform = MapProjection.RelativeScale(latitude, longitude);
transform.Scale(ViewTransform.Scale, ViewTransform.Scale);
transform.Rotate(ViewTransform.Rotation);
return new Point(ViewTransform.Scale * relativeScale.X, ViewTransform.Scale * relativeScale.Y);
}
/// <summary>
/// Gets the map scale as horizontal and vertical scaling factors from meters to
/// view coordinates at the specified location.
/// </summary>
public Point GetMapScale(Location location)
{
return GetMapScale(location.Latitude, location.Longitude);
return transform;
}
/// <summary>
/// Gets a transform Matrix from meters to view coordinates for scaling and rotating
/// objects that are anchored at the specified Location.
/// at the specified Location.
/// </summary>
public Matrix GetMapTransform(Location location)
public Matrix GetMapToViewTransform(Location location)
{
var mapScale = GetMapScale(location.Latitude, location.Longitude);
var transform = new Matrix(mapScale.X, 0d, 0d, mapScale.Y, 0d, 0d);
transform.Rotate(ViewTransform.Rotation);
return transform;
return GetMapToViewTransform(location.Latitude, location.Longitude);
}
/// <summary>

View file

@ -75,12 +75,14 @@ namespace MapControl
set => SetValue(FontSizeProperty, value);
}
private double PixelPerDegree => Math.Max(1d, ParentMap.ViewTransform.Scale * MapProjection.Wgs84MeterPerDegree);
private double lineDistance;
private string labelFormat;
private void SetLineDistance()
{
var minDistance = MinLineDistance / PixelPerLongitudeDegree(ParentMap.Center.Latitude, ParentMap.Center.Longitude);
var minDistance = MinLineDistance / PixelPerDegree;
var scale = minDistance < 1d / 60d ? 3600d : minDistance < 1d ? 60d : 1d;
minDistance *= scale;
@ -98,14 +100,6 @@ namespace MapControl
: lineDistance < 1d ? "{0} {1}°{2:00}'" : "{0} {1}°";
}
private double PixelPerLongitudeDegree(double latitude, double longitude)
{
var scale = ParentMap.GetMapScale(latitude, longitude);
return Math.Max(1d, // a reasonable lower limit
scale.X * Math.Cos(latitude * Math.PI / 180d) * MapProjection.Wgs84MeterPerDegree);
}
private string GetLabelText(double value, string hemispheres)
{
var hemisphere = hemispheres[0];
@ -131,7 +125,7 @@ namespace MapControl
{
// Get rotation from second location with same latitude.
//
var pos = ParentMap.LocationToView(latitude, longitude + 10d / PixelPerLongitudeDegree(latitude, longitude));
var pos = ParentMap.LocationToView(latitude, longitude + 10d / PixelPerDegree);
if (pos.HasValue)
{

View file

@ -96,7 +96,7 @@ namespace MapControl
{
if (MapTransform != null && ParentMap != null && Location != null)
{
MapTransform.Matrix = ParentMap.GetMapTransform(Location);
MapTransform.Matrix = ParentMap.GetMapToViewTransform(Location);
}
}
}

View file

@ -66,7 +66,7 @@ namespace MapControl
{
if (ParentMap != null && Location != null && Data != null)
{
SetDataTransform(ParentMap.GetMapTransform(Location));
SetDataTransform(ParentMap.GetMapToViewTransform(Location));
}
MapPanel.SetLocation(this, Location);

View file

@ -1,6 +1,7 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
@ -82,9 +83,10 @@ namespace MapControl
}
/// <summary>
/// Gets the relative map scale at the specified geographic coordinates.
/// Gets the relative scale at the specified geographic coordinates.
/// The returned Matrix represents the local distortion of the map projection.
/// </summary>
public virtual Point RelativeScale(double latitude, double longitude) => new Point(1d, 1d);
public virtual Matrix RelativeScale(double latitude, double longitude) => new Matrix(1d, 0d, 0d, 1d, 0d, 0d);
/// <summary>
/// Transforms geographic coordinates to a Point in projected map coordinates.
@ -101,7 +103,7 @@ namespace MapControl
/// <summary>
/// Gets the relative map scale at the specified geographic Location.
/// </summary>
public Point RelativeScale(Location location) => RelativeScale(location.Latitude, location.Longitude);
public Matrix RelativeScale(Location location) => RelativeScale(location.Latitude, location.Longitude);
/// <summary>
/// Transforms a Location in geographic coordinates to a Point in projected map coordinates.

View file

@ -88,13 +88,13 @@ namespace MapControl
protected override Size MeasureOverride(Size availableSize)
{
double scale;
if (ParentMap == null || (scale = ParentMap.GetMapScale(ParentMap.Center).X) <= 0d)
if (ParentMap == null)
{
return new Size();
}
var p = ParentMap.GetMapToViewTransform(ParentMap.Center).Transform(new Point(1d, 0d));
var scale = Math.Sqrt(p.X * p.X + p.Y * p.Y);
var length = MinWidth / scale;
var magnitude = Math.Pow(10d, Math.Floor(Math.Log10(length)));

View file

@ -50,6 +50,19 @@ namespace MapControl
OffsetY += y;
}
public void Scale(double scaleX, double scaleY)
{
// equivalent to Multiply(new Matrix(scaleX, 0, 0, scaleY, 0d, 0d));
//
SetMatrix(
M11 * scaleX,
M12 * scaleY,
M21 * scaleX,
M22 * scaleY,
OffsetX * scaleX,
OffsetY * scaleY);
}
public void Rotate(double angle)
{
if (angle != 0d)
@ -60,12 +73,12 @@ namespace MapControl
// equivalent to Multiply(new Matrix(cos, sin, -sin, cos, 0d, 0d));
//
SetMatrix(
cos * M11 - sin * M12,
sin * M11 + cos * M12,
cos * M21 - sin * M22,
sin * M21 + cos * M22,
cos * OffsetX - sin * OffsetY,
sin * OffsetX + cos * OffsetY);
M11 * cos - M12 * sin,
M11 * sin + M12 * cos,
M21 * cos - M22 * sin,
M21 * sin + M22 * cos,
OffsetX * cos - OffsetY * sin,
OffsetX * sin + OffsetY * cos);
}
}

View file

@ -1,6 +1,7 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
@ -25,12 +26,13 @@ namespace MapControl
CrsId = crsId;
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
(var cosC, var x, var y) = GetPointValues(latitude, longitude);
var h = cosC; // p.149 (20-5)
return new Point(h, h);
var scale = new Matrix(h, 0d, 0d, h, 0d, 0d);
return scale;
}
public override Point? LocationToMap(double latitude, double longitude)

View file

@ -1,6 +1,7 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
@ -26,7 +27,7 @@ namespace MapControl
public double FalseNorthing { get; set; } = 2e6;
public Hemisphere Hemisphere { get; set; }
public static double RelativeScale(Hemisphere hemisphere, double flattening, double scaleFactor, double latitude)
public static double RelativeScale(Hemisphere hemisphere, double flattening, double latitude)
{
var sign = hemisphere == Hemisphere.North ? 1d : -1d;
var phi = sign * latitude * Math.PI / 180d;
@ -37,20 +38,18 @@ namespace MapControl
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)
// r == ρ/(a*k0), omit k0 for relative scale
var r = 2d * 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)
return r / m; // p.161 (21-32)
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
var k = RelativeScale(Hemisphere, Flattening, ScaleFactor, latitude);
var k = RelativeScale(Hemisphere, Flattening, latitude);
return new Point(k, k);
return new Matrix(k, 0d, 0d, k, 0d, 0d);
}
public override Point? LocationToMap(double latitude, double longitude)

View file

@ -1,6 +1,7 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
@ -25,12 +26,12 @@ namespace MapControl
CrsId = crsId;
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
(var cosC, var _, var _) = GetPointValues(latitude, longitude);
var k = 2d / (1d + cosC); // p.157 (21-4), k0 == 1
return new Point(k, k);
return new Matrix(k, 0d, 0d, k, 0d, 0d);
}
public override Point? LocationToMap(double latitude, double longitude)

View file

@ -61,11 +61,6 @@ namespace MapControl
Flattening = Wgs84Flattening;
}
public override Point RelativeScale(double latitude, double longitude)
{
return new Point(ScaleFactor, ScaleFactor); // sufficiently precise
}
public override Point? LocationToMap(double latitude, double longitude)
{
#if NETFRAMEWORK

View file

@ -1,6 +1,7 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
@ -26,11 +27,11 @@ namespace MapControl
CrsId = crsId;
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
var k = 1d / Math.Cos(latitude * Math.PI / 180d); // p.44 (7-3)
return new Point(k, k);
return new Matrix(k, 0d, 0d, k, 0d, 0d);
}
public override Point? LocationToMap(double latitude, double longitude)

View file

@ -1,6 +1,7 @@
using System;
#if WPF
using System.Windows;
using System.Windows.Media;
#elif AVALONIA
using Avalonia;
#endif
@ -28,11 +29,11 @@ namespace MapControl
CrsId = crsId;
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
var k = RelativeScale(latitude);
return new Point(k, k);
return new Matrix(k, 0d, 0d, k, 0d, 0d);
}
public override Point? LocationToMap(double latitude, double longitude)

View file

@ -93,13 +93,6 @@ namespace MapControl.Projections
public MathTransform MapToLocationTransform { get; private set; }
public override Point RelativeScale(double latitude, double longitude)
{
var k = CoordinateSystem?.Projection?.GetParameter("scale_factor")?.Value ?? 1d;
return new Point(k, k);
}
public override Point? LocationToMap(double latitude, double longitude)
{
if (LocationToMapTransform == null)

View file

@ -1,9 +1,7 @@
using ProjNet.CoordinateSystems;
using System;
#if WPF
using System.Windows;
#elif AVALONIA
using Avalonia;
using System.Windows.Media;
#endif
namespace MapControl.Projections
@ -19,11 +17,11 @@ namespace MapControl.Projections
CoordinateSystem = ProjectedCoordinateSystem.WebMercator;
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
var k = 1d / Math.Cos(latitude * Math.PI / 180d); // p.44 (7-3)
return new Point(k, k);
return new Matrix(k, 0d, 0d, k, 0d, 0d);
}
}
}

View file

@ -1,7 +1,5 @@
#if WPF
using System.Windows;
#elif AVALONIA
using Avalonia;
using System.Windows.Media;
#endif
namespace MapControl.Projections
@ -23,11 +21,11 @@ namespace MapControl.Projections
"AUTHORITY[\"EPSG\",\"32661\"]]";
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
var k = PolarStereographicProjection.RelativeScale(Hemisphere.North, Wgs84Flattening, 0.994, latitude);
var k = PolarStereographicProjection.RelativeScale(Hemisphere.North, Wgs84Flattening, latitude);
return new Point(k, k);
return new Matrix(k, 0d, 0d, k, 0d, 0d);
}
}
@ -48,11 +46,11 @@ namespace MapControl.Projections
"AUTHORITY[\"EPSG\",\"32761\"]]";
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
var k = PolarStereographicProjection.RelativeScale(Hemisphere.South, Wgs84Flattening, 0.994, latitude);
var k = PolarStereographicProjection.RelativeScale(Hemisphere.South, Wgs84Flattening, latitude);
return new Point(k, k);
return new Matrix(k, 0d, 0d, k, 0d, 0d);
}
}
}

View file

@ -1,7 +1,5 @@
#if WPF
using System.Windows;
#elif AVALONIA
using Avalonia;
using System.Windows.Media;
#endif
namespace MapControl.Projections
@ -29,11 +27,11 @@ namespace MapControl.Projections
"AUTHORITY[\"EPSG\",\"3395\"]]";
}
public override Point RelativeScale(double latitude, double longitude)
public override Matrix RelativeScale(double latitude, double longitude)
{
var k = MapControl.WorldMercatorProjection.RelativeScale(latitude);
return new Point(k, k);
return new Matrix(k, 0d, 0d, k, 0d, 0d);
}
}
}