MapPanel longitude coercion

This commit is contained in:
ClemensFischer 2024-08-29 21:35:58 +02:00
parent 94c3faf805
commit 4b0a0d29fd
9 changed files with 110 additions and 189 deletions

View file

@ -12,7 +12,6 @@
<ItemGroup> <ItemGroup>
<Compile Include="..\Shared\*.cs" /> <Compile Include="..\Shared\*.cs" />
<Compile Remove="..\Shared\ViewTransform.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -2,47 +2,10 @@
// Copyright © 2024 Clemens Fischer // Copyright © 2024 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL) // Licensed under the Microsoft Public License (Ms-PL)
using System;
namespace MapControl namespace MapControl
{ {
/// <summary> public partial class ViewTransform
/// Defines the transformation between projected map coordinates in meters
/// and view coordinates in pixels.
/// </summary>
public class ViewTransform
{ {
public static double ZoomLevelToScale(double zoomLevel)
{
return 256d * Math.Pow(2d, zoomLevel) / (360d * MapProjection.Wgs84MeterPerDegree);
}
public static double ScaleToZoomLevel(double scale)
{
return Math.Log(scale * 360d * MapProjection.Wgs84MeterPerDegree / 256d, 2d);
}
/// <summary>
/// Gets the scaling factor from projected map coordinates to view coordinates,
/// as pixels per meter.
/// </summary>
public double Scale { get; private set; }
/// <summary>
/// Gets the rotation angle of the transform matrix.
/// </summary>
public double Rotation { get; private set; }
/// <summary>
/// Gets the transform matrix from projected map coordinates to view coordinates.
/// </summary>
public Matrix MapToViewMatrix { get; private set; }
/// <summary>
/// Gets the transform matrix from view coordinates to projected map coordinates.
/// </summary>
public Matrix ViewToMapMatrix { get; private set; }
/// <summary> /// <summary>
/// Initializes a ViewTransform from a map center point in projected coordinates, /// Initializes a ViewTransform from a map center point in projected coordinates,
/// a view conter point, a scaling factor from projected coordinates to view coordinates /// a view conter point, a scaling factor from projected coordinates to view coordinates
@ -62,31 +25,6 @@ namespace MapControl
ViewToMapMatrix = MapToViewMatrix.Invert(); ViewToMapMatrix = MapToViewMatrix.Invert();
} }
/// <summary>
/// Transforms a Point from projected map coordinates to view coordinates.
/// </summary>
public Point MapToView(Point point)
{
return MapToViewMatrix.Transform(point);
}
/// <summary>
/// Transforms a Point from view coordinates to projected map coordinates.
/// </summary>
public Point ViewToMap(Point point)
{
return ViewToMapMatrix.Transform(point);
}
/// <summary>
/// Transform relative to absolute map scale. Returns horizontal and vertical
/// scaling factors from meters to view coordinates.
/// </summary>
public Point GetMapScale(Point relativeScale)
{
return new Point(Scale * relativeScale.X, Scale * relativeScale.Y);
}
/// <summary> /// <summary>
/// Gets a transform Matrix from meters to view coordinates for a relative map scale. /// Gets a transform Matrix from meters to view coordinates for a relative map scale.
/// </summary> /// </summary>

View file

@ -17,7 +17,7 @@ namespace MapControl
#endif #endif
public class BoundingBox public class BoundingBox
{ {
public BoundingBox() protected BoundingBox()
{ {
} }
@ -34,15 +34,27 @@ namespace MapControl
{ {
} }
public double South { get; } public double South { get; private set; }
public double North { get; } public double North { get; private set; }
public double West { get; } public double West { get; private set; }
public double East { get; } public double East { get; private set; }
public virtual double Width => East - West; public virtual double Width => East - West;
public virtual double Height => North - South; public virtual double Height => North - South;
public virtual Location Center => new Location((South + North) / 2d, (West + East) / 2d); public virtual Location Center
{
get => new Location((South + North) / 2d, (West + East) / 2d);
set
{
var latOffset = value.Latitude - (South + North) / 2d;
var lonOffset = value.Longitude - (West + East) / 2d;
South += latOffset;
North += latOffset;
West += lonOffset;
East += lonOffset;
}
}
/// <summary> /// <summary>
/// Creates a BoundingBox instance from a string containing a comma-separated sequence of four floating point numbers. /// Creates a BoundingBox instance from a string containing a comma-separated sequence of four floating point numbers.

View file

@ -8,19 +8,15 @@ namespace MapControl
{ {
public class CenteredBoundingBox : BoundingBox public class CenteredBoundingBox : BoundingBox
{ {
private readonly Location center; public CenteredBoundingBox(Location center, double width, double height)
private readonly double width;
private readonly double height;
public CenteredBoundingBox(Location c, double w, double h)
{ {
center = c; Center = center;
width = Math.Max(w, 0d); Width = Math.Max(width, 0d);
height = Math.Max(h, 0d); Height = Math.Max(height, 0d);
} }
public override Location Center => center; public override Location Center { get; set; }
public override double Width => width; public override double Width { get; }
public override double Height => height; public override double Height { get; }
} }
} }

View file

@ -31,6 +31,10 @@ namespace MapControl
/// </summary> /// </summary>
public partial class MapBase : MapPanel public partial class MapBase : MapPanel
{ {
public static double ZoomLevelToScale(double zoomLevel) => 256d * Math.Pow(2d, zoomLevel) / (360d * MapProjection.Wgs84MeterPerDegree);
public static double ScaleToZoomLevel(double scale) => Math.Log(scale * 360d * MapProjection.Wgs84MeterPerDegree / 256d, 2d);
public static TimeSpan ImageFadeDuration { get; set; } = TimeSpan.FromSeconds(0.1); public static TimeSpan ImageFadeDuration { get; set; } = TimeSpan.FromSeconds(0.1);
public static readonly DependencyProperty AnimationDurationProperty = public static readonly DependencyProperty AnimationDurationProperty =
@ -361,7 +365,7 @@ namespace MapControl
{ {
var scale = Math.Min(ActualWidth / rect.Value.Width, ActualHeight / rect.Value.Height); var scale = Math.Min(ActualWidth / rect.Value.Width, ActualHeight / rect.Value.Height);
TargetZoomLevel = ViewTransform.ScaleToZoomLevel(scale); TargetZoomLevel = ScaleToZoomLevel(scale);
TargetCenter = targetCenter; TargetCenter = targetCenter;
TargetHeading = 0d; TargetHeading = 0d;
} }
@ -497,7 +501,7 @@ namespace MapControl
private void UpdateTransform(bool resetTransformCenter = false, bool projectionChanged = false) private void UpdateTransform(bool resetTransformCenter = false, bool projectionChanged = false)
{ {
var transformCenterChanged = false; var transformCenterChanged = false;
var viewScale = ViewTransform.ZoomLevelToScale(ZoomLevel); var viewScale = ZoomLevelToScale(ZoomLevel);
var projection = MapProjection; var projection = MapProjection;
projection.Center = ProjectionCenter ?? Center; projection.Center = ProjectionCenter ?? Center;

View file

@ -189,66 +189,53 @@ namespace MapControl
protected Point? GetViewPosition(Location location) protected Point? GetViewPosition(Location location)
{ {
var position = parentMap.LocationToView(location); if (parentMap.MapProjection.Type <= MapProjectionType.NormalCylindrical)
if (parentMap.MapProjection.Type <= MapProjectionType.NormalCylindrical &&
position.HasValue &&
!InsideViewport(position.Value))
{ {
position = parentMap.LocationToView( var longitude = parentMap.CoerceLongitude(location.Longitude);
new Location(location.Latitude, parentMap.CoerceLongitude(location.Longitude)));
if (longitude != location.Longitude)
{
location = new Location(location.Latitude, longitude);
}
} }
return position; return parentMap.LocationToView(location);
} }
protected ViewRect? GetViewRect(BoundingBox boundingBox) protected ViewRect? GetViewRect(BoundingBox boundingBox)
{ {
var mapRect = parentMap.MapProjection.BoundingBoxToMap(boundingBox); if (parentMap.MapProjection.Type <= MapProjectionType.NormalCylindrical)
if (!mapRect.HasValue)
{ {
return null; var center = boundingBox.Center;
var longitude = parentMap.CoerceLongitude(center.Longitude);
if (longitude != center.Longitude)
{
boundingBox.Center = new Location(center.Latitude, longitude);
}
} }
var mapRect = parentMap.MapProjection.BoundingBoxToMap(boundingBox);
if (mapRect.HasValue)
{
return GetViewRect(mapRect.Value); return GetViewRect(mapRect.Value);
} }
return null;
}
protected ViewRect GetViewRect(Rect mapRect) protected ViewRect GetViewRect(Rect mapRect)
{ {
var transform = parentMap.ViewTransform;
var center = new Point(mapRect.X + mapRect.Width / 2d, mapRect.Y + mapRect.Height / 2d); var center = new Point(mapRect.X + mapRect.Width / 2d, mapRect.Y + mapRect.Height / 2d);
var position = parentMap.ViewTransform.MapToView(center); var position = transform.MapToView(center);
var projection = parentMap.MapProjection; var width = mapRect.Width * transform.Scale;
var height = mapRect.Height * transform.Scale;
if (projection.Type <= MapProjectionType.NormalCylindrical &&
!InsideViewport(position))
{
var location = projection.MapToLocation(center);
if (location != null)
{
var pos = parentMap.LocationToView(
new Location(location.Latitude, parentMap.CoerceLongitude(location.Longitude)));
if (pos.HasValue)
{
position = pos.Value;
}
}
}
var width = mapRect.Width * parentMap.ViewTransform.Scale;
var height = mapRect.Height * parentMap.ViewTransform.Scale;
var x = position.X - width / 2d; var x = position.X - width / 2d;
var y = position.Y - height / 2d; var y = position.Y - height / 2d;
return new ViewRect(x, y, width, height, parentMap.ViewTransform.Rotation); return new ViewRect(x, y, width, height, transform.Rotation);
}
private bool InsideViewport(Point point)
{
return point.X >= 0d && point.X <= parentMap.ActualWidth
&& point.Y >= 0d && point.Y <= parentMap.ActualHeight;
} }
private void ArrangeChildElement(FrameworkElement element, Size panelSize) private void ArrangeChildElement(FrameworkElement element, Size panelSize)
@ -263,7 +250,10 @@ namespace MapControl
if (GetAutoCollapse(element)) if (GetAutoCollapse(element))
{ {
SetVisible(element, position.HasValue && InsideViewport(position.Value)); SetVisible(element,
position.HasValue &&
position.Value.X >= 0d && position.Value.X <= parentMap.ActualWidth &&
position.Value.Y >= 0d && position.Value.Y <= parentMap.ActualHeight);
} }
if (position.HasValue) if (position.HasValue)

View file

@ -4,13 +4,10 @@
#if WPF #if WPF
using System.Windows; using System.Windows;
using System.Windows.Media;
#elif UWP #elif UWP
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
#elif WINUI #elif WINUI
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
#endif #endif
namespace MapControl namespace MapControl
@ -79,6 +76,18 @@ namespace MapControl
MapPanel.SetLocation(this, Location); MapPanel.SetLocation(this, Location);
} }
protected double GetLongitudeOffset(Location location)
{
var longitudeOffset = 0d;
if (location != null && parentMap.MapProjection.Type <= MapProjectionType.NormalCylindrical)
{
longitudeOffset = parentMap.CoerceLongitude(location.Longitude) - location.Longitude;
}
return longitudeOffset;
}
protected Point? LocationToMap(Location location, double longitudeOffset) protected Point? LocationToMap(Location location, double longitudeOffset)
{ {
if (longitudeOffset != 0d) if (longitudeOffset != 0d)
@ -107,31 +116,12 @@ namespace MapControl
{ {
var point = LocationToMap(location, longitudeOffset); var point = LocationToMap(location, longitudeOffset);
if (!point.HasValue) if (point.HasValue)
{ {
return null; point = parentMap.ViewTransform.MapToView(point.Value);
} }
return parentMap.ViewTransform.MapToView(point.Value); return point;
}
protected double GetLongitudeOffset(Location location)
{
var longitudeOffset = 0d;
if (location != null && parentMap.MapProjection.Type <= MapProjectionType.NormalCylindrical)
{
var point = parentMap.LocationToView(location);
if (point.HasValue &&
(point.Value.X < 0d || point.Value.X > parentMap.ActualWidth ||
point.Value.Y < 0d || point.Value.Y > parentMap.ActualHeight))
{
longitudeOffset = parentMap.CoerceLongitude(location.Longitude) - location.Longitude;
}
}
return longitudeOffset;
} }
} }
} }

View file

@ -156,7 +156,7 @@ namespace MapControl
// //
var tileMatrixOrigin = new Point(TileSize * TileMatrix.XMin, TileSize * TileMatrix.YMin); var tileMatrixOrigin = new Point(TileSize * TileMatrix.XMin, TileSize * TileMatrix.YMin);
var tileMatrixScale = ViewTransform.ZoomLevelToScale(TileMatrix.ZoomLevel); var tileMatrixScale = MapBase.ZoomLevelToScale(TileMatrix.ZoomLevel);
((MatrixTransform)RenderTransform).Matrix = ((MatrixTransform)RenderTransform).Matrix =
ParentMap.ViewTransform.GetTileLayerTransform(tileMatrixScale, MapTopLeft, tileMatrixOrigin); ParentMap.ViewTransform.GetTileLayerTransform(tileMatrixScale, MapTopLeft, tileMatrixOrigin);
@ -169,7 +169,7 @@ namespace MapControl
// //
var tileMatrixZoomLevel = (int)Math.Floor(ParentMap.ZoomLevel - ZoomLevelOffset + 0.001); var tileMatrixZoomLevel = (int)Math.Floor(ParentMap.ZoomLevel - ZoomLevelOffset + 0.001);
var tileMatrixScale = ViewTransform.ZoomLevelToScale(tileMatrixZoomLevel); var tileMatrixScale = MapBase.ZoomLevelToScale(tileMatrixZoomLevel);
// Bounds in tile pixels from view size. // Bounds in tile pixels from view size.
// //

View file

@ -2,7 +2,6 @@
// Copyright © 2024 Clemens Fischer // Copyright © 2024 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL) // Licensed under the Microsoft Public License (Ms-PL)
using System;
#if WPF #if WPF
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
@ -18,18 +17,8 @@ namespace MapControl
/// Defines the transformation between projected map coordinates in meters /// Defines the transformation between projected map coordinates in meters
/// and view coordinates in pixels. /// and view coordinates in pixels.
/// </summary> /// </summary>
public class ViewTransform public partial class ViewTransform
{ {
public static double ZoomLevelToScale(double zoomLevel)
{
return 256d * Math.Pow(2d, zoomLevel) / (360d * MapProjection.Wgs84MeterPerDegree);
}
public static double ScaleToZoomLevel(double scale)
{
return Math.Log(scale * 360d * MapProjection.Wgs84MeterPerDegree / 256d, 2d);
}
/// <summary> /// <summary>
/// Gets the scaling factor from projected map coordinates to view coordinates, /// Gets the scaling factor from projected map coordinates to view coordinates,
/// as pixels per meter. /// as pixels per meter.
@ -51,6 +40,32 @@ namespace MapControl
/// </summary> /// </summary>
public Matrix ViewToMapMatrix { get; private set; } public Matrix ViewToMapMatrix { get; private set; }
/// <summary>
/// Transforms a Point from projected map coordinates to view coordinates.
/// </summary>
public Point MapToView(Point point)
{
return MapToViewMatrix.Transform(point);
}
/// <summary>
/// Transforms a Point from view coordinates to projected map coordinates.
/// </summary>
public Point ViewToMap(Point point)
{
return ViewToMapMatrix.Transform(point);
}
/// <summary>
/// Transform relative to absolute map scale. Returns horizontal and vertical
/// scaling factors from meters to view coordinates.
/// </summary>
public Point GetMapScale(Point relativeScale)
{
return new Point(Scale * relativeScale.X, Scale * relativeScale.Y);
}
#if WPF || UWP || WINUI
/// <summary> /// <summary>
/// Initializes a ViewTransform from a map center point in projected coordinates, /// Initializes a ViewTransform from a map center point in projected coordinates,
/// a view conter point, a scaling factor from projected coordinates to view coordinates /// a view conter point, a scaling factor from projected coordinates to view coordinates
@ -72,30 +87,6 @@ namespace MapControl
ViewToMapMatrix = transform; ViewToMapMatrix = transform;
} }
/// <summary>
/// Transforms a Point from projected map coordinates to view coordinates.
/// </summary>
public Point MapToView(Point point)
{
return MapToViewMatrix.Transform(point);
}
/// <summary>
/// Transforms a Point from view coordinates to projected map coordinates.
/// </summary>
public Point ViewToMap(Point point)
{
return ViewToMapMatrix.Transform(point);
}
/// <summary>
/// Gets scaling factors from meters to view coordinates for a relative map scale.
/// </summary>
public Point GetMapScale(Point relativeScale)
{
return new Point(Scale * relativeScale.X, Scale * relativeScale.Y);
}
/// <summary> /// <summary>
/// Gets a transform Matrix from meters to view coordinates for a relative map scale. /// Gets a transform Matrix from meters to view coordinates for a relative map scale.
/// </summary> /// </summary>
@ -170,5 +161,6 @@ namespace MapControl
return transform; return transform;
} }
#endif
} }
} }