Added Location

This commit is contained in:
ClemensF 2012-05-04 12:52:20 +02:00
parent caab7208a3
commit a98f59e1a9
35 changed files with 1235 additions and 717 deletions

View file

@ -1,9 +1,16 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Windows;
using System.Windows.Media;
namespace MapControl
{
/// <summary>
/// Contains helper methods for creating GlyphRun objects.
/// </summary>
public static class GlyphRunText
{
public static GlyphRun Create(string text, Typeface typeface, double emSize, Point baselineOrigin)

51
MapControl/Location.cs Normal file
View file

@ -0,0 +1,51 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
namespace MapControl
{
/// <summary>
/// A geographic location given as latitude and longitude.
/// </summary>
[TypeConverter(typeof(LocationConverter))]
public class Location
{
public Location()
{
}
public Location(double latitude, double longitude)
{
Latitude = latitude;
Longitude = longitude;
}
public double Latitude { get; set; }
public double Longitude { get; set; }
public static Location Parse(string source)
{
Point p = Point.Parse(source);
return new Location(p.X, p.Y);
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0:0.00000},{1:0.00000}", Latitude, Longitude);
}
}
public class LocationConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return Location.Parse((string)value);
}
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.Windows;
using System.Windows.Media.Animation;
namespace MapControl
{
/// <summary>
/// Animates the value of a Location property between two values.
/// </summary>
public class LocationAnimation : AnimationTimeline
{
public Location From { get; set; }
public Location To { get; set; }
public IEasingFunction EasingFunction { get; set; }
public override Type TargetPropertyType
{
get { return typeof(Location); }
}
protected override Freezable CreateInstanceCore()
{
return new LocationAnimation
{
From = From,
To = To,
EasingFunction = EasingFunction
};
}
public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
{
if (!animationClock.CurrentProgress.HasValue)
{
return defaultOriginValue;
}
double progress = animationClock.CurrentProgress.Value;
if (EasingFunction != null)
{
progress = EasingFunction.Ease(progress);
}
return new Location(
(1d - progress) * From.Latitude + progress * To.Latitude,
(1d - progress) * From.Longitude + progress * To.Longitude);
}
}
}

View file

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
namespace MapControl
{
/// <summary>
/// A collection of geographic locations.
/// </summary>
[TypeConverter(typeof(LocationCollectionConverter))]
public class LocationCollection : ObservableCollection<Location>
{
public LocationCollection()
{
}
public LocationCollection(IEnumerable<Location> locations)
{
foreach (Location location in locations)
{
Add(location);
}
}
public static LocationCollection Parse(string source)
{
LocationCollection locations = new LocationCollection();
foreach (string locString in source.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
{
locations.Add(Location.Parse(locString));
}
return locations;
}
}
public class LocationCollectionConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return LocationCollection.Parse((string)value);
}
}
}

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
@ -7,6 +11,12 @@ using System.Windows.Media.Animation;
namespace MapControl
{
/// <summary>
/// The main map control. Draws map content provided by the TileLayers or the MainTileLayer property.
/// The visible map area is defined by the Center and ZoomLevel properties. The map can be rotated
/// by an angle that is given by the Heading property.
/// Map is a MapPanel and hence can contain map overlays like other MapPanels or MapItemsControls.
/// </summary>
public partial class Map : MapPanel
{
public const double MeterPerDegree = 1852d * 60d;
@ -34,24 +44,24 @@ namespace MapControl
"TileLayers", typeof(TileLayerCollection), typeof(Map), new FrameworkPropertyMetadata(
(o, e) => ((Map)o).SetTileLayers((TileLayerCollection)e.NewValue)));
public static readonly DependencyProperty BaseTileLayerProperty = DependencyProperty.Register(
"BaseTileLayer", typeof(TileLayer), typeof(Map), new FrameworkPropertyMetadata(
(o, e) => ((Map)o).SetBaseTileLayer((TileLayer)e.NewValue),
(o, v) => ((Map)o).CoerceBaseTileLayer((TileLayer)v)));
public static readonly DependencyProperty MainTileLayerProperty = DependencyProperty.Register(
"MainTileLayer", typeof(TileLayer), typeof(Map), new FrameworkPropertyMetadata(
(o, e) => ((Map)o).SetMainTileLayer((TileLayer)e.NewValue),
(o, v) => ((Map)o).CoerceMainTileLayer((TileLayer)v)));
public static readonly DependencyProperty TileOpacityProperty = DependencyProperty.Register(
"TileOpacity", typeof(double), typeof(Map), new FrameworkPropertyMetadata(1d,
(o, e) => ((Map)o).tileContainer.Opacity = (double)e.NewValue));
public static readonly DependencyProperty CenterProperty = DependencyProperty.Register(
"Center", typeof(Point), typeof(Map), new FrameworkPropertyMetadata(new Point(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, e) => ((Map)o).SetCenter((Point)e.NewValue),
(o, v) => ((Map)o).CoerceCenter((Point)v)));
"Center", typeof(Location), typeof(Map), new FrameworkPropertyMetadata(new Location(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, e) => ((Map)o).SetCenter((Location)e.NewValue),
(o, v) => ((Map)o).CoerceCenter((Location)v)));
public static readonly DependencyProperty TargetCenterProperty = DependencyProperty.Register(
"TargetCenter", typeof(Point), typeof(Map), new FrameworkPropertyMetadata(new Point(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, e) => ((Map)o).SetTargetCenter((Point)e.NewValue),
(o, v) => ((Map)o).CoerceCenter((Point)v)));
"TargetCenter", typeof(Location), typeof(Map), new FrameworkPropertyMetadata(new Location(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, e) => ((Map)o).SetTargetCenter((Location)e.NewValue),
(o, v) => ((Map)o).CoerceCenter((Location)v)));
public static readonly DependencyProperty ZoomLevelProperty = DependencyProperty.Register(
"ZoomLevel", typeof(double), typeof(Map), new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
@ -79,13 +89,13 @@ namespace MapControl
public static readonly DependencyProperty CenterScaleProperty = CenterScalePropertyKey.DependencyProperty;
private readonly TileContainer tileContainer = new TileContainer();
private readonly MapViewTransform mapViewTransform = new MapViewTransform();
private readonly MapTransform mapTransform = new MercatorTransform();
private readonly ScaleTransform scaleTransform = new ScaleTransform();
private readonly RotateTransform rotateTransform = new RotateTransform();
private readonly MatrixTransform scaleRotateTransform = new MatrixTransform();
private Point? transformOrigin;
private Point viewOrigin;
private PointAnimation centerAnimation;
private Location transformOrigin;
private Point viewportOrigin;
private LocationAnimation centerAnimation;
private DoubleAnimation zoomLevelAnimation;
private DoubleAnimation headingAnimation;
private bool updateTransform = true;
@ -96,9 +106,8 @@ namespace MapControl
MaxZoomLevel = 20;
AddVisualChild(tileContainer);
mapViewTransform.ViewTransform = tileContainer.ViewTransform;
BaseTileLayer = new TileLayer
MainTileLayer = new TileLayer
{
Description = "© {y} OpenStreetMap Contributors, CC-BY-SA",
TileSource = new OpenStreetMapTileSource { UriFormat = "http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png" }
@ -190,12 +199,12 @@ namespace MapControl
}
/// <summary>
/// Gets or sets the base TileLayer used by this Map, i.e. TileLayers[0].
/// Gets or sets the main TileLayer used by this Map, i.e. TileLayers[0].
/// </summary>
public TileLayer BaseTileLayer
public TileLayer MainTileLayer
{
get { return (TileLayer)GetValue(BaseTileLayerProperty); }
set { SetValue(BaseTileLayerProperty, value); }
get { return (TileLayer)GetValue(MainTileLayerProperty); }
set { SetValue(MainTileLayerProperty, value); }
}
/// <summary>
@ -208,20 +217,20 @@ namespace MapControl
}
/// <summary>
/// Gets or sets the world coordinates (latitude and longitude) of the center point.
/// Gets or sets the location of the center point of the Map.
/// </summary>
public Point Center
public Location Center
{
get { return (Point)GetValue(CenterProperty); }
get { return (Location)GetValue(CenterProperty); }
set { SetValue(CenterProperty, value); }
}
/// <summary>
/// Gets or sets the target value of a Center animation.
/// </summary>
public Point TargetCenter
public Location TargetCenter
{
get { return (Point)GetValue(TargetCenterProperty); }
get { return (Location)GetValue(TargetCenterProperty); }
set { SetValue(TargetCenterProperty, value); }
}
@ -262,7 +271,7 @@ namespace MapControl
}
/// <summary>
/// Gets the map scale at the Center point as view coordinate units (pixels) per meter.
/// Gets the map scale at the Center location as viewport coordinate units (pixels) per meter.
/// </summary>
public double CenterScale
{
@ -271,34 +280,24 @@ namespace MapControl
}
/// <summary>
/// Gets the transformation from world coordinates (latitude and longitude)
/// to cartesian map coordinates.
/// Gets the transformation from geographic coordinates to cartesian map coordinates.
/// </summary>
public MapTransform MapTransform
{
get { return mapViewTransform.MapTransform; }
get { return mapTransform; }
}
/// <summary>
/// Gets the transformation from cartesian map coordinates to view coordinates.
/// Gets the transformation from cartesian map coordinates to viewport coordinates.
/// </summary>
public Transform ViewTransform
public Transform ViewportTransform
{
get { return mapViewTransform.ViewTransform; }
get { return tileContainer.ViewportTransform; }
}
/// <summary>
/// Gets the combination of MapTransform and ViewTransform, i.e. the transformation
/// from longitude and latitude values to view coordinates.
/// </summary>
public GeneralTransform MapViewTransform
{
get { return mapViewTransform; }
}
/// <summary>
/// Gets the scaling transformation from meters to view coordinate units (pixels)
/// at the view center point.
/// Gets the scaling transformation from meters to viewport coordinate units (pixels)
/// at the viewport center point.
/// </summary>
public Transform ScaleTransform
{
@ -322,57 +321,73 @@ namespace MapControl
}
/// <summary>
/// Sets an intermittent origin point in world coordinates for scaling and rotation transformations.
/// Transforms a geographic location to a viewport coordinates point.
/// </summary>
public Point LocationToViewportPoint(Location location)
{
return ViewportTransform.Transform(MapTransform.Transform(location));
}
/// <summary>
/// Transforms a viewport coordinates point to a geographic location.
/// </summary>
public Location ViewportPointToLocation(Point point)
{
return MapTransform.TransformBack(ViewportTransform.Inverse.Transform(point));
}
/// <summary>
/// Sets a temporary origin location in geographic coordinates for scaling and rotation transformations.
/// This origin location is automatically removed when the Center property is set by application code.
/// </summary>
public void SetTransformOrigin(Location origin)
{
transformOrigin = origin;
viewportOrigin = LocationToViewportPoint(origin);
}
/// <summary>
/// Sets a temporary origin point in viewport coordinates for scaling and rotation transformations.
/// This origin point is automatically removed when the Center property is set by application code.
/// </summary>
public void SetTransformOrigin(Point origin)
{
transformOrigin = origin;
viewOrigin = MapViewTransform.Transform(origin);
viewportOrigin.X = Math.Min(Math.Max(origin.X, 0d), RenderSize.Width);
viewportOrigin.Y = Math.Min(Math.Max(origin.Y, 0d), RenderSize.Height);
transformOrigin = CoerceCenter(ViewportPointToLocation(viewportOrigin));
}
/// <summary>
/// Sets an intermittent origin point in view coordinates for scaling and rotation transformations.
/// This origin point is automatically removed when the Center property is set by application code.
/// </summary>
public void SetTransformViewOrigin(Point origin)
{
viewOrigin.X = Math.Min(Math.Max(origin.X, 0d), RenderSize.Width);
viewOrigin.Y = Math.Min(Math.Max(origin.Y, 0d), RenderSize.Height);
transformOrigin = CoerceCenter(MapViewTransform.Inverse.Transform(viewOrigin));
}
/// <summary>
/// Removes the intermittent transform origin point set by SetTransformOrigin.
/// Removes the temporary transform origin point set by SetTransformOrigin.
/// </summary>
public void ResetTransformOrigin()
{
transformOrigin = null;
viewOrigin = new Point(RenderSize.Width / 2d, RenderSize.Height / 2d);
viewportOrigin = new Point(RenderSize.Width / 2d, RenderSize.Height / 2d);
}
/// <summary>
/// Changes the Center property according to the specified translation in view coordinates.
/// Changes the Center property according to the specified translation in viewport coordinates.
/// </summary>
public void TranslateMap(Vector translation)
{
if (translation.X != 0d || translation.Y != 0d)
{
ResetTransformOrigin();
Center = MapViewTransform.Inverse.Transform(viewOrigin - translation);
Center = ViewportPointToLocation(viewportOrigin - translation);
}
}
/// <summary>
/// Changes the Center, Heading and ZoomLevel properties according to the specified
/// view coordinate translation, rotation and scale delta values. Rotation and scaling
/// is performed relative to the specified origin point in view coordinates.
/// viewport coordinate translation, rotation and scale delta values. Rotation and scaling
/// is performed relative to the specified origin point in viewport coordinates.
/// </summary>
public void TransformMap(Point origin, Vector translation, double rotation, double scale)
{
if (rotation != 0d || scale != 1d)
{
SetTransformViewOrigin(origin);
SetTransformOrigin(origin);
updateTransform = false;
Heading += rotation;
ZoomLevel += Math.Log(scale, 2d);
@ -385,21 +400,20 @@ namespace MapControl
/// <summary>
/// Sets the value of the ZoomLevel property while retaining the specified origin point
/// in view coordinates.
/// in viewport coordinates.
/// </summary>
public void ZoomMap(Point origin, double zoomLevel)
{
SetTransformViewOrigin(origin);
SetTransformOrigin(origin);
TargetZoomLevel = zoomLevel;
}
/// <summary>
/// Gets the map scale at the specified world coordinate point
/// as view coordinate units (pixels) per meter.
/// Gets the map scale at the specified location as viewport coordinate units (pixels) per meter.
/// </summary>
public double GetMapScale(Point point)
public double GetMapScale(Location location)
{
return MapTransform.RelativeScale(point) * Math.Pow(2d, ZoomLevel) * 256d / (MeterPerDegree * 360d);
return mapTransform.RelativeScale(location) * Math.Pow(2d, ZoomLevel) * 256d / (MeterPerDegree * 360d);
}
protected override int VisualChildrenCount
@ -477,40 +491,40 @@ namespace MapControl
private void SetTileLayers(TileLayerCollection tileLayers)
{
BaseTileLayer = tileLayers.Count > 0 ? tileLayers[0] : null;
MainTileLayer = tileLayers.Count > 0 ? tileLayers[0] : null;
tileContainer.TileLayers = tileLayers;
}
private void SetBaseTileLayer(TileLayer baseTileLayer)
private void SetMainTileLayer(TileLayer mainTileLayer)
{
if (baseTileLayer != null)
if (mainTileLayer != null)
{
if (TileLayers == null)
{
TileLayers = new TileLayerCollection(baseTileLayer);
TileLayers = new TileLayerCollection(mainTileLayer);
}
else if (TileLayers.Count == 0)
{
TileLayers.Add(baseTileLayer);
TileLayers.Add(mainTileLayer);
}
else if (TileLayers[0] != baseTileLayer)
else if (TileLayers[0] != mainTileLayer)
{
TileLayers[0] = baseTileLayer;
TileLayers[0] = mainTileLayer;
}
}
}
private TileLayer CoerceBaseTileLayer(TileLayer baseTileLayer)
private TileLayer CoerceMainTileLayer(TileLayer mainTileLayer)
{
if (baseTileLayer == null && TileLayers.Count > 0)
if (mainTileLayer == null && TileLayers.Count > 0)
{
baseTileLayer = TileLayers[0];
mainTileLayer = TileLayers[0];
}
return baseTileLayer;
return mainTileLayer;
}
private void SetCenter(Point center)
private void SetCenter(Location center)
{
if (updateTransform)
{
@ -524,7 +538,7 @@ namespace MapControl
}
}
private void SetTargetCenter(Point targetCenter)
private void SetTargetCenter(Location targetCenter)
{
if (targetCenter != Center)
{
@ -533,7 +547,7 @@ namespace MapControl
centerAnimation.Completed -= CenterAnimationCompleted;
}
centerAnimation = new PointAnimation
centerAnimation = new LocationAnimation
{
From = Center,
To = targetCenter,
@ -557,11 +571,11 @@ namespace MapControl
centerAnimation = null;
}
private Point CoerceCenter(Point point)
private Location CoerceCenter(Location location)
{
point.X = ((point.X + 180d) % 360d + 360d) % 360d - 180d;
point.Y = Math.Min(Math.Max(point.Y, -MapTransform.MaxLatitude), MapTransform.MaxLatitude);
return point;
location.Latitude = Math.Min(Math.Max(location.Latitude, -MapTransform.MaxLatitude), MapTransform.MaxLatitude);
location.Longitude = ((location.Longitude + 180d) % 360d + 360d) % 360d - 180d;
return location;
}
private void SetZoomLevel(double zoomLevel)
@ -682,16 +696,16 @@ namespace MapControl
{
double scale;
if (transformOrigin.HasValue)
if (transformOrigin != null)
{
scale = tileContainer.SetTransform(ZoomLevel, Heading, MapTransform.Transform(transformOrigin.Value), viewOrigin, RenderSize);
scale = tileContainer.SetTransform(ZoomLevel, Heading, MapTransform.Transform(transformOrigin), viewportOrigin, RenderSize);
updateTransform = false;
Center = MapViewTransform.Inverse.Transform(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d));
Center = ViewportPointToLocation(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d));
updateTransform = true;
}
else
{
scale = tileContainer.SetTransform(ZoomLevel, Heading, MapTransform.Transform(Center), viewOrigin, RenderSize);
scale = tileContainer.SetTransform(ZoomLevel, Heading, MapTransform.Transform(Center), viewportOrigin, RenderSize);
}
scale *= MapTransform.RelativeScale(Center) / MeterPerDegree; // Pixels per meter at center latitude

View file

@ -24,10 +24,11 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<DefineConstants>
</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
@ -42,21 +43,20 @@
</ItemGroup>
<ItemGroup>
<Compile Include="GlyphRunText.cs" />
<Compile Include="Location.cs" />
<Compile Include="LocationAnimation.cs" />
<Compile Include="LocationCollection.cs" />
<Compile Include="MapElement.cs" />
<Compile Include="MapGraticule.cs" />
<Compile Include="MapItem.cs" />
<Compile Include="MapItemsControl.cs" />
<Compile Include="MapPanel.cs" />
<Compile Include="MapPath.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="MapPathGeometry.cs" />
<Compile Include="MapStreamGeometry.cs" />
<Compile Include="MapPolygon.cs" />
<Compile Include="MapPolyline.cs" />
<Compile Include="TileImageLoader.cs" />
<Compile Include="MapTransform.cs" />
<Compile Include="Map.cs" />
<Compile Include="MapInput.cs" />
<Compile Include="MapViewTransform.cs" />
<Compile Include="MercatorTransform.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tile.cs" />

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Windows;
namespace MapControl
@ -8,6 +12,9 @@ namespace MapControl
void ParentMapChanged(Map oldParentMap, Map newParentMap);
}
/// <summary>
/// Base class for child elements of a MapPanel.
/// </summary>
public abstract class MapElement : FrameworkElement, INotifyParentMapChanged
{
protected MapElement()

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
@ -7,6 +11,10 @@ using System.Windows.Shapes;
namespace MapControl
{
/// <summary>
/// Draws a graticule overlay. The minimum spacing in pixels between adjacent graticule lines
/// is specified by the MinSpacingPixels property.
/// </summary>
public class MapGraticule : MapElement
{
public static readonly DependencyProperty ForegroundProperty = Control.ForegroundProperty.AddOwner(
@ -36,7 +44,7 @@ namespace MapControl
public static readonly DependencyProperty MinSpacingPixelsProperty = DependencyProperty.Register(
"MinSpacingPixels", typeof(double), typeof(MapGraticule), new FrameworkPropertyMetadata(100d));
public static double[] GridSpacings =
public static double[] Spacings =
new double[] { 1d / 60d, 1d / 30d, 1d / 12d, 1d / 6d, 1d / 4d, 1d / 3d, 1d / 2d, 1d, 2d, 5d, 10d, 15d, 20d, 30d, 45d };
private readonly DrawingVisual visual = new DrawingVisual();
@ -116,17 +124,19 @@ namespace MapControl
protected override void OnViewTransformChanged(Map parentMap)
{
Rect bounds = parentMap.MapViewTransform.Inverse.TransformBounds(new Rect(parentMap.RenderSize));
Rect bounds = parentMap.ViewportTransform.Inverse.TransformBounds(new Rect(parentMap.RenderSize));
Location loc1 = parentMap.MapTransform.TransformBack(bounds.TopLeft);
Location loc2 = parentMap.MapTransform.TransformBack(bounds.BottomRight);
double minSpacing = MinSpacingPixels * 360d / (Math.Pow(2d, parentMap.ZoomLevel) * 256d);
double spacing = GridSpacings[GridSpacings.Length - 1];
double spacing = Spacings[Spacings.Length - 1];
if (spacing >= minSpacing)
{
spacing = GridSpacings.FirstOrDefault(s => s >= minSpacing);
spacing = Spacings.FirstOrDefault(s => s >= minSpacing);
}
double longitudeStart = Math.Ceiling(bounds.Left / spacing) * spacing;
double latitudeStart = Math.Ceiling(bounds.Top / spacing) * spacing;
double latitudeStart = Math.Ceiling(loc1.Latitude / spacing) * spacing;
double longitudeStart = Math.Ceiling(loc1.Longitude / spacing) * spacing;
if (pen.Brush == null)
{
@ -135,18 +145,18 @@ namespace MapControl
using (DrawingContext drawingContext = visual.RenderOpen())
{
for (double lon = longitudeStart; lon <= bounds.Right; lon += spacing)
for (double lat = latitudeStart; lat <= loc2.Latitude; lat += spacing)
{
drawingContext.DrawLine(pen,
parentMap.MapViewTransform.Transform(new Point(lon, bounds.Bottom)),
parentMap.MapViewTransform.Transform(new Point(lon, bounds.Top)));
parentMap.LocationToViewportPoint(new Location(lat, loc1.Longitude)),
parentMap.LocationToViewportPoint(new Location(lat, loc2.Longitude)));
}
for (double lat = latitudeStart; lat <= bounds.Bottom; lat += spacing)
for (double lon = longitudeStart; lon <= loc2.Longitude; lon += spacing)
{
drawingContext.DrawLine(pen,
parentMap.MapViewTransform.Transform(new Point(bounds.Left, lat)),
parentMap.MapViewTransform.Transform(new Point(bounds.Right, lat)));
parentMap.LocationToViewportPoint(new Location(loc1.Latitude, lon)),
parentMap.LocationToViewportPoint(new Location(loc2.Latitude, lon)));
}
if (Foreground != null && Foreground != Brushes.Transparent)
@ -158,12 +168,12 @@ namespace MapControl
typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
}
for (double lon = longitudeStart; lon <= bounds.Right; lon += spacing)
for (double lat = latitudeStart; lat <= loc2.Latitude; lat += spacing)
{
for (double lat = latitudeStart; lat <= bounds.Bottom; lat += spacing)
for (double lon = longitudeStart; lon <= loc2.Longitude; lon += spacing)
{
double t = StrokeThickness / 2d;
Point p = parentMap.MapViewTransform.Transform(new Point(lon, lat));
Point p = parentMap.LocationToViewportPoint(new Location(lat, lon));
Point latPos = new Point(p.X + t + 2d, p.Y - t - FontSize / 4d);
Point lonPos = new Point(p.X + t + 2d, p.Y + t + FontSize);
string latString = CoordinateString(lat, format, "NS");

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Windows;
using System.Windows.Input;

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
@ -7,22 +11,21 @@ using System.Windows.Media;
namespace MapControl
{
[TemplateVisualState(GroupName = "CommonStates", Name = "Normal")]
[TemplateVisualState(GroupName = "CommonStates", Name = "Disabled")]
[TemplateVisualState(GroupName = "CommonStates", Name = "MouseOver")]
[TemplateVisualState(GroupName = "SelectionStates", Name = "Unselected")]
[TemplateVisualState(GroupName = "SelectionStates", Name = "Selected")]
[TemplateVisualState(GroupName = "CurrentStates", Name = "NonCurrent")]
[TemplateVisualState(GroupName = "CurrentStates", Name = "Current")]
/// <summary>
/// Container class for an item in a MapItemsControl.
/// </summary>
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Unselected", GroupName = "SelectionStates")]
[TemplateVisualState(Name = "Selected", GroupName = "SelectionStates")]
[TemplateVisualState(Name = "NotCurrent", GroupName = "CurrentStates")]
[TemplateVisualState(Name = "Current", GroupName = "CurrentStates")]
public class MapItem : ContentControl
{
public static readonly RoutedEvent SelectedEvent = ListBoxItem.SelectedEvent.AddOwner(typeof(MapItem));
public static readonly RoutedEvent UnselectedEvent = ListBoxItem.UnselectedEvent.AddOwner(typeof(MapItem));
public static readonly DependencyProperty LocationProperty = MapPanel.LocationProperty.AddOwner(typeof(MapItem));
public static readonly DependencyProperty ViewPositionProperty = MapPanel.ViewPositionProperty.AddOwner(typeof(MapItem));
public static readonly DependencyProperty ViewPositionTransformProperty = MapPanel.ViewPositionTransformProperty.AddOwner(typeof(MapItem));
public static readonly DependencyProperty IsSelectedProperty = Selector.IsSelectedProperty.AddOwner(
typeof(MapItem), new FrameworkPropertyMetadata((o, e) => ((MapItem)o).IsSelectedChanged((bool)e.NewValue)));
@ -31,13 +34,6 @@ namespace MapControl
public static readonly DependencyProperty IsCurrentProperty = IsCurrentPropertyKey.DependencyProperty;
private static readonly DependencyPropertyKey IsInsideMapBoundsPropertyKey = DependencyProperty.RegisterReadOnly(
"IsInsideMapBounds", typeof(bool), typeof(MapItem), null);
public static readonly DependencyProperty IsInsideMapBoundsProperty = IsInsideMapBoundsPropertyKey.DependencyProperty;
private object item;
static MapItem()
{
FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItem),
@ -45,9 +41,6 @@ namespace MapControl
UIElement.IsEnabledProperty.OverrideMetadata(typeof(MapItem),
new FrameworkPropertyMetadata((o, e) => ((MapItem)o).CommonStateChanged()));
MapPanel.ViewPositionPropertyKey.OverrideMetadata(typeof(MapItem),
new FrameworkPropertyMetadata((o, e) => ((MapItem)o).ViewPositionChanged((Point)e.NewValue)));
}
public event RoutedEventHandler Selected
@ -67,27 +60,6 @@ namespace MapControl
get { return MapPanel.GetParentMap(this); }
}
public Point? Location
{
get { return (Point?)GetValue(LocationProperty); }
set { SetValue(LocationProperty, value); }
}
public bool HasViewPosition
{
get { return ReadLocalValue(ViewPositionProperty) != DependencyProperty.UnsetValue; }
}
public Point ViewPosition
{
get { return (Point)GetValue(ViewPositionProperty); }
}
public Transform ViewPositionTransform
{
get { return (Transform)GetValue(ViewPositionTransformProperty); }
}
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
@ -104,28 +76,12 @@ namespace MapControl
SetValue(IsCurrentPropertyKey, value);
int zIndex = Panel.GetZIndex(this);
Panel.SetZIndex(this, value ? (zIndex | 0x40000000) : (zIndex & ~0x40000000));
VisualStateManager.GoToState(this, value ? "Current" : "NonCurrent", true);
VisualStateManager.GoToState(this, value ? "Current" : "NotCurrent", true);
}
}
}
public bool IsInsideMapBounds
{
get { return (bool)GetValue(IsInsideMapBoundsProperty); }
}
public object Item
{
get { return item; }
internal set
{
item = value;
if (HasViewPosition)
{
ViewPositionChanged(ViewPosition);
}
}
}
public object Item { get; internal set; }
protected override void OnMouseEnter(MouseEventArgs e)
{
@ -159,24 +115,6 @@ namespace MapControl
IsSelected = !IsSelected;
}
protected virtual void OnViewPositionChanged(Point viewPosition)
{
}
private void ViewPositionChanged(Point viewPosition)
{
Map map = ParentMap;
if (map != null)
{
SetValue(IsInsideMapBoundsPropertyKey,
viewPosition.X >= 0d && viewPosition.X <= map.ActualWidth &&
viewPosition.Y >= 0d && viewPosition.Y <= map.ActualHeight);
OnViewPositionChanged(viewPosition);
}
}
private void CommonStateChanged()
{
if (!IsEnabled)

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls.Primitives;
@ -8,6 +12,10 @@ namespace MapControl
{
public enum MapItemSelectionMode { Single, Extended }
/// <summary>
/// Manages a collection of selectable items on a Map. Uses MapItem as container for items
/// and updates the MapItem.IsCurrent property when Items.CurrentItem changes.
/// </summary>
public class MapItemsControl : MultiSelector
{
public static readonly DependencyProperty SelectionModeProperty = DependencyProperty.Register(
@ -114,7 +122,7 @@ namespace MapControl
{
MapItem mapItem = GetMapItem(item);
if (mapItem != null && mapItem.HasViewPosition && geometry.FillContains(mapItem.ViewPosition))
if (mapItem != null && MapPanel.HasViewportPosition(mapItem) && geometry.FillContains(MapPanel.GetViewportPosition(mapItem)))
{
SelectedItems.Add(item);
}

View file

@ -1,10 +1,21 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace MapControl
{
/// <summary>
/// Positions child elements on a Map. A child element's position is specified by the
/// attached property Location, given as geographic location with latitude and longitude.
/// The attached property ViewportPosition gets a child element's position in viewport
/// coordinates. IsInsideMapBounds indicates if the viewport coordinates are located
/// inside the visible part of the map.
/// </summary>
public class MapPanel : Panel, INotifyParentMapChanged
{
public static readonly DependencyProperty ParentMapProperty = DependencyProperty.RegisterAttached(
@ -12,17 +23,17 @@ namespace MapControl
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, ParentMapPropertyChanged));
public static readonly DependencyProperty LocationProperty = DependencyProperty.RegisterAttached(
"Location", typeof(Point?), typeof(MapPanel),
"Location", typeof(Location), typeof(MapPanel),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, LocationPropertyChanged));
internal static readonly DependencyPropertyKey ViewPositionPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
"ViewPosition", typeof(Point), typeof(MapPanel), null);
private static readonly DependencyPropertyKey ViewportPositionPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
"ViewportPosition", typeof(Point), typeof(MapPanel), null);
private static readonly DependencyPropertyKey ViewPositionTransformPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
"ViewPositionTransform", typeof(Transform), typeof(MapPanel), null);
private static readonly DependencyPropertyKey IsInsideMapBoundsPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
"IsInsideMapBounds", typeof(bool), typeof(MapPanel), null);
public static readonly DependencyProperty ViewPositionProperty = ViewPositionPropertyKey.DependencyProperty;
public static readonly DependencyProperty ViewPositionTransformProperty = ViewPositionTransformPropertyKey.DependencyProperty;
public static readonly DependencyProperty ViewportPositionProperty = ViewportPositionPropertyKey.DependencyProperty;
public static readonly DependencyProperty IsInsideMapBoundsProperty = IsInsideMapBoundsPropertyKey.DependencyProperty;
public MapPanel()
{
@ -39,24 +50,29 @@ namespace MapControl
return (Map)element.GetValue(ParentMapProperty);
}
public static Point? GetLocation(UIElement element)
public static Location GetLocation(UIElement element)
{
return (Point?)element.GetValue(LocationProperty);
return (Location)element.GetValue(LocationProperty);
}
public static void SetLocation(UIElement element, Point? value)
public static void SetLocation(UIElement element, Location value)
{
element.SetValue(LocationProperty, value);
}
public static Point GetViewPosition(UIElement element)
public static bool HasViewportPosition(UIElement element)
{
return (Point)element.GetValue(ViewPositionProperty);
return element.ReadLocalValue(ViewportPositionProperty) != DependencyProperty.UnsetValue;
}
public static Transform GetViewPositionTransform(UIElement element)
public static Point GetViewportPosition(UIElement element)
{
return (Transform)element.GetValue(ViewPositionTransformProperty);
return (Point)element.GetValue(ViewportPositionProperty);
}
public static bool GetIsInsideMapBounds(UIElement element)
{
return (bool)element.GetValue(IsInsideMapBoundsProperty);
}
protected override Size MeasureOverride(Size availableSize)
@ -75,10 +91,10 @@ namespace MapControl
{
foreach (UIElement element in InternalChildren)
{
object viewPosition = element.ReadLocalValue(ViewPositionProperty);
object viewportPosition = element.ReadLocalValue(ViewportPositionProperty);
if (viewPosition == DependencyProperty.UnsetValue ||
!ArrangeElement(element, (Point)viewPosition))
if (viewportPosition == DependencyProperty.UnsetValue ||
!ArrangeElement(element, (Point)viewportPosition))
{
ArrangeElement(element, finalSize);
}
@ -91,11 +107,11 @@ namespace MapControl
{
foreach (UIElement element in InternalChildren)
{
Point? location = GetLocation(element);
Location location = GetLocation(element);
if (location.HasValue)
if (location != null)
{
SetViewPosition(element, parentMap, location);
SetViewportPosition(element, parentMap, location);
}
}
}
@ -131,40 +147,31 @@ namespace MapControl
private static void LocationPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs eventArgs)
{
UIElement element = (UIElement)obj;
Point? location = (Point?)eventArgs.NewValue;
Location location = (Location)eventArgs.NewValue;
Map parentMap;
if (location.HasValue && (parentMap = Map.GetParentMap(element)) != null)
if (location != null && (parentMap = GetParentMap(element)) != null)
{
SetViewPosition(element, parentMap, location);
SetViewportPosition(element, parentMap, location);
}
else
{
element.ClearValue(ViewPositionPropertyKey);
element.ClearValue(ViewPositionTransformPropertyKey);
element.ClearValue(ViewportPositionPropertyKey);
element.ClearValue(IsInsideMapBoundsPropertyKey);
element.Arrange(new Rect());
}
}
private static void SetViewPosition(UIElement element, Map parentMap, Point? location)
private static void SetViewportPosition(UIElement element, Map parentMap, Location location)
{
Point viewPosition = parentMap.MapViewTransform.Transform(location.Value);
Point viewportPosition = parentMap.LocationToViewportPoint(location);
element.SetValue(ViewPositionPropertyKey, viewPosition);
element.SetValue(ViewportPositionPropertyKey, viewportPosition);
element.SetValue(IsInsideMapBoundsPropertyKey,
viewportPosition.X >= 0d && viewportPosition.X <= parentMap.ActualWidth &&
viewportPosition.Y >= 0d && viewportPosition.Y <= parentMap.ActualHeight);
Matrix matrix = new Matrix(1d, 0d, 0d, 1d, viewPosition.X, viewPosition.Y);
MatrixTransform viewTransform = element.GetValue(ViewPositionTransformProperty) as MatrixTransform;
if (viewTransform != null)
{
viewTransform.Matrix = matrix;
}
else
{
element.SetValue(ViewPositionTransformPropertyKey, new MatrixTransform(matrix));
}
ArrangeElement(element, viewPosition);
ArrangeElement(element, viewportPosition);
}
private static bool ArrangeElement(UIElement element, Point position)

View file

@ -1,155 +0,0 @@
using System;
using System.Linq;
using System.Windows;
using System.Windows.Media;
namespace MapControl
{
public static class MapPathGeometry
{
public static PathGeometry Transform(this GeneralTransform transform, Geometry geometry)
{
PathGeometry pathGeometry = geometry as PathGeometry;
if (pathGeometry == null)
{
pathGeometry = PathGeometry.CreateFromGeometry(geometry);
}
if (geometry.Transform != null && geometry.Transform != System.Windows.Media.Transform.Identity)
{
GeneralTransformGroup transformGroup = new GeneralTransformGroup();
transformGroup.Children.Add(geometry.Transform);
transformGroup.Children.Add(transform);
transform = transformGroup;
}
return new PathGeometry(Transform(transform, pathGeometry.Figures),
pathGeometry.FillRule, System.Windows.Media.Transform.Identity);
}
public static PathFigureCollection Transform(this GeneralTransform transform, PathFigureCollection figures)
{
PathFigureCollection transformedFigures = new PathFigureCollection();
foreach (PathFigure figure in figures)
{
transformedFigures.Add(Transform(transform, figure));
}
transformedFigures.Freeze();
return transformedFigures;
}
public static PathFigure Transform(this GeneralTransform transform, PathFigure figure)
{
PathSegmentCollection transformedSegments = new PathSegmentCollection(figure.Segments.Count);
foreach (PathSegment segment in figure.Segments)
{
PathSegment transformedSegment = null;
if (segment is LineSegment)
{
LineSegment lineSegment = (LineSegment)segment;
transformedSegment = new LineSegment(
transform.Transform(lineSegment.Point),
lineSegment.IsStroked);
}
else if (segment is PolyLineSegment)
{
PolyLineSegment polyLineSegment = (PolyLineSegment)segment;
transformedSegment = new PolyLineSegment(
polyLineSegment.Points.Select(transform.Transform),
polyLineSegment.IsStroked);
}
else if (segment is ArcSegment)
{
ArcSegment arcSegment = (ArcSegment)segment;
Size size = arcSegment.Size;
MapTransform mapTransform = transform as MapTransform;
if (mapTransform != null)
{
double yScale = mapTransform.RelativeScale(arcSegment.Point);
if (arcSegment.RotationAngle == 0d)
{
size.Height *= yScale;
}
else
{
double sinR = Math.Sin(arcSegment.RotationAngle * Math.PI / 180d);
double cosR = Math.Cos(arcSegment.RotationAngle * Math.PI / 180d);
size.Width *= Math.Sqrt(yScale * yScale * sinR * sinR + cosR * cosR);
size.Height *= Math.Sqrt(yScale * yScale * cosR * cosR + sinR * sinR);
}
}
transformedSegment = new ArcSegment(
transform.Transform(arcSegment.Point),
size,
arcSegment.RotationAngle,
arcSegment.IsLargeArc,
arcSegment.SweepDirection,
arcSegment.IsStroked);
}
else if (segment is BezierSegment)
{
BezierSegment bezierSegment = (BezierSegment)segment;
transformedSegment = new BezierSegment(
transform.Transform(bezierSegment.Point1),
transform.Transform(bezierSegment.Point2),
transform.Transform(bezierSegment.Point3),
bezierSegment.IsStroked);
}
else if (segment is PolyBezierSegment)
{
PolyBezierSegment polyBezierSegment = (PolyBezierSegment)segment;
transformedSegment = new PolyBezierSegment(
polyBezierSegment.Points.Select(transform.Transform),
polyBezierSegment.IsStroked);
}
else if (segment is QuadraticBezierSegment)
{
QuadraticBezierSegment quadraticBezierSegment = (QuadraticBezierSegment)segment;
transformedSegment = new QuadraticBezierSegment(
transform.Transform(quadraticBezierSegment.Point1),
transform.Transform(quadraticBezierSegment.Point2),
quadraticBezierSegment.IsStroked);
}
else if (segment is PolyQuadraticBezierSegment)
{
PolyQuadraticBezierSegment polyQuadraticBezierSegment = (PolyQuadraticBezierSegment)segment;
transformedSegment = new PolyQuadraticBezierSegment(
polyQuadraticBezierSegment.Points.Select(transform.Transform),
polyQuadraticBezierSegment.IsStroked);
}
if (transformedSegment != null)
{
transformedSegment.IsSmoothJoin = segment.IsSmoothJoin;
transformedSegments.Add(transformedSegment);
}
}
PathFigure transformedFigure = new PathFigure(
transform.Transform(figure.StartPoint),
transformedSegments,
figure.IsClosed);
transformedFigure.IsFilled = figure.IsFilled;
transformedFigure.Freeze();
return transformedFigure;
}
}
}

35
MapControl/MapPolygon.cs Normal file
View file

@ -0,0 +1,35 @@
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
namespace MapControl
{
/// <summary>
/// A closed map polygon, defined by a collection of geographic locations in the Locations property.
/// </summary>
public class MapPolygon : MapPolyline
{
public static readonly DependencyProperty FillProperty = Shape.FillProperty.AddOwner(
typeof(MapPolygon), new FrameworkPropertyMetadata((o, e) => ((MapPolygon)o).drawing.Brush = (Brush)e.NewValue));
public MapPolygon()
{
drawing.Brush = Fill;
}
public Brush Fill
{
get { return (Brush)GetValue(FillProperty); }
set { SetValue(FillProperty, value); }
}
protected override void UpdateGeometry()
{
UpdateGeometry(true);
}
}
}

View file

@ -1,54 +1,58 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
namespace MapControl
{
public class MapPath : MapElement
/// <summary>
/// An open map polygon, defined by a collection of geographic locations in the Locations property.
/// </summary>
public class MapPolyline : MapElement
{
public static readonly DependencyProperty DataProperty = Path.DataProperty.AddOwner(
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).UpdateGeometry()));
public static readonly DependencyProperty FillProperty = Shape.FillProperty.AddOwner(
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Brush = (Brush)e.NewValue));
public static readonly DependencyProperty StrokeProperty = Shape.StrokeProperty.AddOwner(
typeof(MapPath), new FrameworkPropertyMetadata(Brushes.Black, (o, e) => ((MapPath)o).drawing.Pen.Brush = (Brush)e.NewValue));
typeof(MapPolyline), new FrameworkPropertyMetadata(Brushes.Black, (o, e) => ((MapPolyline)o).drawing.Pen.Brush = (Brush)e.NewValue));
public static readonly DependencyProperty StrokeDashArrayProperty = Shape.StrokeDashArrayProperty.AddOwner(
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.DashStyle = new DashStyle((DoubleCollection)e.NewValue, ((MapPath)o).StrokeDashOffset)));
typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.DashStyle = new DashStyle((DoubleCollection)e.NewValue, ((MapPolyline)o).StrokeDashOffset)));
public static readonly DependencyProperty StrokeDashOffsetProperty = Shape.StrokeDashOffsetProperty.AddOwner(
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.DashStyle = new DashStyle(((MapPath)o).StrokeDashArray, (double)e.NewValue)));
typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.DashStyle = new DashStyle(((MapPolyline)o).StrokeDashArray, (double)e.NewValue)));
public static readonly DependencyProperty StrokeDashCapProperty = Shape.StrokeDashCapProperty.AddOwner(
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.DashCap = (PenLineCap)e.NewValue));
typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.DashCap = (PenLineCap)e.NewValue));
public static readonly DependencyProperty StrokeStartLineCapProperty = Shape.StrokeStartLineCapProperty.AddOwner(
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.StartLineCap = (PenLineCap)e.NewValue));
typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.StartLineCap = (PenLineCap)e.NewValue));
public static readonly DependencyProperty StrokeEndLineCapProperty = Shape.StrokeEndLineCapProperty.AddOwner(
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.EndLineCap = (PenLineCap)e.NewValue));
typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.EndLineCap = (PenLineCap)e.NewValue));
public static readonly DependencyProperty StrokeLineJoinProperty = Shape.StrokeLineJoinProperty.AddOwner(
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.LineJoin = (PenLineJoin)e.NewValue));
typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.LineJoin = (PenLineJoin)e.NewValue));
public static readonly DependencyProperty StrokeMiterLimitProperty = Shape.StrokeMiterLimitProperty.AddOwner(
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.MiterLimit = (double)e.NewValue));
typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.MiterLimit = (double)e.NewValue));
public static readonly DependencyProperty StrokeThicknessProperty = Shape.StrokeThicknessProperty.AddOwner(
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).UpdatePenThickness()));
typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).UpdatePenThickness()));
public static readonly DependencyProperty TransformStrokeProperty = DependencyProperty.Register(
"TransformStroke", typeof(bool), typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).UpdatePenThickness()));
"TransformStroke", typeof(bool), typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).UpdatePenThickness()));
private readonly DrawingVisual visual = new DrawingVisual();
private readonly GeometryDrawing drawing = new GeometryDrawing();
public static readonly DependencyProperty LocationsProperty = DependencyProperty.Register(
"Locations", typeof(LocationCollection), typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).UpdateGeometry()));
public MapPath()
protected readonly DrawingVisual visual = new DrawingVisual();
protected readonly GeometryDrawing drawing = new GeometryDrawing();
public MapPolyline()
{
drawing.Brush = Fill;
drawing.Pen = new Pen(Stroke, StrokeThickness);
using (DrawingContext drawingContext = visual.RenderOpen())
@ -59,18 +63,6 @@ namespace MapControl
Loaded += (o, e) => UpdateGeometry();
}
public Geometry Data
{
get { return (Geometry)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public Brush Fill
{
get { return (Brush)GetValue(FillProperty); }
set { SetValue(FillProperty, value); }
}
public Brush Stroke
{
get { return (Brush)GetValue(StrokeProperty); }
@ -131,6 +123,12 @@ namespace MapControl
set { SetValue(TransformStrokeProperty, value); }
}
public LocationCollection Locations
{
get { return (LocationCollection)GetValue(LocationsProperty); }
set { SetValue(LocationsProperty, value); }
}
public double TransformedStrokeThickness
{
get { return drawing.Pen.Thickness; }
@ -162,23 +160,26 @@ namespace MapControl
{
double scale = 1d;
if (TransformStroke && Data != null)
if (TransformStroke)
{
Point center = Data.Bounds.Location + (Vector)Data.Bounds.Size / 2d;
scale = parentMap.GetMapScale(center) * Map.MeterPerDegree;
scale = parentMap.CenterScale * Map.MeterPerDegree;
}
drawing.Pen.Thickness = scale * StrokeThickness;
}
private void UpdateGeometry()
protected virtual void UpdateGeometry()
{
UpdateGeometry(false);
}
protected void UpdateGeometry(bool closed)
{
Map parentMap = MapPanel.GetParentMap(this);
if (parentMap != null && Data != null)
if (parentMap != null && Locations != null && Locations.Count > 0)
{
drawing.Geometry = parentMap.MapTransform.Transform(Data);
drawing.Geometry.Transform = parentMap.ViewTransform;
drawing.Geometry = CreateGeometry(parentMap, Locations, closed);
OnViewTransformChanged(parentMap);
}
else
@ -196,5 +197,25 @@ namespace MapControl
OnViewTransformChanged(parentMap);
}
}
private Geometry CreateGeometry(Map parentMap, LocationCollection locations, bool closed)
{
StreamGeometry geometry = new StreamGeometry
{
Transform = parentMap.ViewportTransform
};
using (StreamGeometryContext sgc = geometry.Open())
{
sgc.BeginFigure(parentMap.MapTransform.Transform(locations.First()), closed, closed);
if (Locations.Count > 1)
{
sgc.PolyLineTo(parentMap.MapTransform.Transform(locations.Skip(1)), true, true);
}
}
return geometry;
}
}
}

View file

@ -1,104 +0,0 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
namespace MapControl
{
public static class MapStreamGeometry
{
public static MapStreamGeometryContext Open(this StreamGeometry mapGeometry, MapTransform transform)
{
return new MapStreamGeometryContext(mapGeometry.Open(), transform);
}
}
public class MapStreamGeometryContext : IDisposable
{
StreamGeometryContext context;
MapTransform transform;
public MapStreamGeometryContext(StreamGeometryContext context, MapTransform transform)
{
this.context = context;
this.transform = transform;
}
void IDisposable.Dispose()
{
context.Close();
}
public void Close()
{
context.Close();
}
public void BeginFigure(Point startPoint, bool isFilled, bool isClosed)
{
context.BeginFigure(transform.Transform(startPoint), isFilled, isClosed);
}
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked, bool isSmoothJoin)
{
double yScale = transform.RelativeScale(point);
if (rotationAngle == 0d)
{
size.Height *= yScale;
}
else
{
double sinR = Math.Sin(rotationAngle * Math.PI / 180d);
double cosR = Math.Cos(rotationAngle * Math.PI / 180d);
size.Width *= Math.Sqrt(yScale * yScale * sinR * sinR + cosR * cosR);
size.Height *= Math.Sqrt(yScale * yScale * cosR * cosR + sinR * sinR);
}
context.ArcTo(transform.Transform(point), size, rotationAngle, isLargeArc, sweepDirection, isStroked, isSmoothJoin);
}
public void LineTo(Point point, bool isStroked, bool isSmoothJoin)
{
context.LineTo(transform.Transform(point), isStroked, isSmoothJoin);
}
public void QuadraticBezierTo(Point point1, Point point2, bool isStroked, bool isSmoothJoin)
{
context.QuadraticBezierTo(transform.Transform(point1), transform.Transform(point2), isStroked, isSmoothJoin);
}
public void BezierTo(Point point1, Point point2, Point point3, bool isStroked, bool isSmoothJoin)
{
context.BezierTo(transform.Transform(point1), transform.Transform(point2), transform.Transform(point3), isStroked, isSmoothJoin);
}
public void PolyLineTo(IList<Point> points, bool isStroked, bool isSmoothJoin)
{
context.PolyLineTo(TransformPoints(points), isStroked, isSmoothJoin);
}
public void PolyQuadraticBezierTo(IList<Point> points, bool isStroked, bool isSmoothJoin)
{
context.PolyQuadraticBezierTo(TransformPoints(points), isStroked, isSmoothJoin);
}
public void PolyBezierTo(IList<Point> points, bool isStroked, bool isSmoothJoin)
{
context.PolyBezierTo(TransformPoints(points), isStroked, isSmoothJoin);
}
private IList<Point> TransformPoints(IList<Point> points)
{
Point[] transformedPoints = new Point[points.Count];
for (int i = 0; i < transformedPoints.Length; i++)
{
transformedPoints[i] = transform.Transform(points[i]);
}
return transformedPoints;
}
}
}

View file

@ -1,4 +1,10 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
@ -9,7 +15,7 @@ namespace MapControl
/// are transformed to cartesian coordinates with origin at latitude = 0 and longitude = 0.
/// Longitude values are transformed identically to x values in the interval [-180 .. 180].
/// </summary>
public abstract class MapTransform : GeneralTransform
public abstract class MapTransform
{
/// <summary>
/// Gets the absolute value of the minimum and maximum latitude that can be transformed.
@ -17,9 +23,35 @@ namespace MapControl
public abstract double MaxLatitude { get; }
/// <summary>
/// Gets the point scale factor of the map projection at the specified point
/// relative to the point (0, 0).
/// Gets the scale factor of the map projection at the specified geographic location
/// relative to the scale at latitude zero.
/// </summary>
public abstract double RelativeScale(Point point);
public abstract double RelativeScale(Location location);
/// <summary>
/// Transforms a geographic location to a cartesian coordinate point.
/// </summary>
public abstract Point Transform(Location location);
/// <summary>
/// Transforms a cartesian coordinate point to a geographic location.
/// </summary>
public abstract Location TransformBack(Point point);
/// <summary>
/// Transforms a collection of geographic locations to a collection of cartesian coordinate points.
/// </summary>
public PointCollection Transform(IEnumerable<Location> locations)
{
return new PointCollection(locations.Select(l => Transform(l)));
}
/// <summary>
/// Transforms a collection of cartesian coordinate points to a collections of geographic location.
/// </summary>
public LocationCollection TransformBack(IEnumerable<Point> points)
{
return new LocationCollection(points.Select(p => TransformBack(p)));
}
}
}

View file

@ -1,72 +0,0 @@
using System;
using System.Windows;
using System.Windows.Media;
namespace MapControl
{
public class MapViewTransform : GeneralTransform
{
private readonly GeneralTransform inverse;
public MapViewTransform()
{
MapTransform = new MercatorTransform();
inverse = new InverseMapViewTransform(this);
}
public MapTransform MapTransform { get; set; }
public Transform ViewTransform { get; set; }
public override GeneralTransform Inverse
{
get { return inverse; }
}
public override bool TryTransform(Point point, out Point result)
{
result = ViewTransform.Transform(MapTransform.Transform(point));
return true;
}
public override Rect TransformBounds(Rect rect)
{
return ViewTransform.TransformBounds(MapTransform.TransformBounds(rect));
}
protected override Freezable CreateInstanceCore()
{
return new MapViewTransform();
}
}
internal class InverseMapViewTransform : GeneralTransform
{
private readonly MapViewTransform inverse;
public InverseMapViewTransform(MapViewTransform inverse)
{
this.inverse = inverse;
}
public override GeneralTransform Inverse
{
get { return inverse; }
}
public override bool TryTransform(Point point, out Point result)
{
result = inverse.MapTransform.Inverse.Transform(inverse.ViewTransform.Inverse.Transform(point));
return true;
}
public override Rect TransformBounds(Rect rect)
{
return inverse.MapTransform.Inverse.TransformBounds(inverse.ViewTransform.Inverse.TransformBounds(rect));
}
protected override Freezable CreateInstanceCore()
{
return new InverseMapViewTransform(inverse);
}
}
}

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Windows;
using System.Windows.Media;
@ -10,101 +14,50 @@ namespace MapControl
/// </summary>
public class MercatorTransform : MapTransform
{
private GeneralTransform inverse = new InverseMercatorTransform();
public MercatorTransform()
{
Freeze();
}
public override GeneralTransform Inverse
{
get { return inverse; }
}
public override double MaxLatitude
{
get { return 85.0511; }
}
public override double RelativeScale(Point point)
public override double RelativeScale(Location location)
{
if (point.Y <= -90d)
if (location.Latitude <= -90d)
{
return double.NegativeInfinity;
}
if (point.Y >= 90d)
if (location.Latitude >= 90d)
{
return double.PositiveInfinity;
}
return 1d / Math.Cos(point.Y * Math.PI / 180d);
return 1d / Math.Cos(location.Latitude * Math.PI / 180d);
}
public override bool TryTransform(Point point, out Point result)
public override Point Transform(Location location)
{
result = point;
Point result = new Point(location.Longitude, 0d);
if (point.Y <= -90d)
if (location.Latitude <= -90d)
{
result.Y = double.NegativeInfinity;
}
else if (point.Y >= 90d)
else if (location.Latitude >= 90d)
{
result.Y = double.PositiveInfinity;
}
else
{
double lat = point.Y * Math.PI / 180d;
double lat = location.Latitude * Math.PI / 180d;
result.Y = (Math.Log(Math.Tan(lat) + 1d / Math.Cos(lat))) / Math.PI * 180d;
}
return true;
return result;
}
public override Rect TransformBounds(Rect rect)
public override Location TransformBack(Point point)
{
return new Rect(Transform(rect.TopLeft), Transform(rect.BottomRight));
}
protected override Freezable CreateInstanceCore()
{
return new MercatorTransform();
}
}
/// <summary>
/// Transforms cartesian coordinates to latitude and longitude values in degrees
/// according to the inverse Mercator transform.
/// </summary>
public class InverseMercatorTransform : GeneralTransform
{
public InverseMercatorTransform()
{
Freeze();
}
public override GeneralTransform Inverse
{
get { return new MercatorTransform(); }
}
public override bool TryTransform(Point point, out Point result)
{
result = point;
result.Y = Math.Atan(Math.Sinh(point.Y * Math.PI / 180d)) / Math.PI * 180d;
return true;
}
public override Rect TransformBounds(Rect rect)
{
return new Rect(Transform(rect.TopLeft), Transform(rect.BottomRight));
}
protected override Freezable CreateInstanceCore()
{
return new InverseMercatorTransform();
return new Location(Math.Atan(Math.Sinh(point.Y * Math.PI / 180d)) / Math.PI * 180d, point.X);
}
}
}

View file

@ -6,12 +6,12 @@ using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MapControl")]
[assembly: AssemblyDescription("MapControl")]
[assembly: AssemblyTitle("Map Control")]
[assembly: AssemblyDescription("WPF Map Control")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyProduct("MapControl")]
[assembly: AssemblyCopyright("Copyright © Clemens Fischer 2012")]
[assembly: AssemblyProduct("WPF Map Control")]
[assembly: AssemblyCopyright("Copyright © 2012 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows;
@ -22,7 +26,7 @@ namespace MapControl
private Int32Rect tileGrid;
private TileLayerCollection tileLayers;
private readonly DispatcherTimer updateTimer;
private readonly MatrixTransform viewTransform = new MatrixTransform();
private readonly MatrixTransform viewportTransform = new MatrixTransform();
public TileContainer()
{
@ -52,17 +56,17 @@ namespace MapControl
}
}
public Transform ViewTransform
public Transform ViewportTransform
{
get { return viewTransform; }
get { return viewportTransform; }
}
public double SetTransform(double mapZoomLevel, double mapRotation, Point mapOrigin, Point viewOrigin, Size viewSize)
public double SetTransform(double mapZoomLevel, double mapRotation, Point mapOrigin, Point viewportOrigin, Size viewportSize)
{
zoomLevel = mapZoomLevel;
rotation = mapRotation;
size = viewSize;
origin = viewOrigin;
size = viewportSize;
origin = viewportOrigin;
double scale = Math.Pow(2d, zoomLevel) * 256d / 360d;
offset.X = origin.X - (180d + mapOrigin.X) * scale;
@ -72,7 +76,7 @@ namespace MapControl
transform.Scale(scale, scale);
transform.Translate(offset.X, offset.Y);
transform.RotateAt(rotation, origin.X, origin.Y);
viewTransform.Matrix = transform;
viewportTransform.Matrix = transform;
transform = GetVisualTransform();
@ -110,7 +114,7 @@ namespace MapControl
int zoom = (int)Math.Floor(zoomLevel + 1d - zoomLevelSwitchOffset);
int numTiles = 1 << zoom;
double mapToTileScale = (double)numTiles / 360d;
Matrix transform = viewTransform.Matrix;
Matrix transform = viewportTransform.Matrix;
transform.Invert(); // view to map coordinates
transform.Translate(180d, -180d);
transform.Scale(mapToTileScale, -mapToTileScale); // map coordinates to tile indices

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -10,9 +14,13 @@ using System.Windows.Threading;
namespace MapControl
{
/// <summary>
/// Loads map tiles by their URIs and optionally caches their image files in a folder
/// defined by the static TileCacheFolder property.
/// </summary>
public class TileImageLoader : DispatcherObject
{
public static string TileCacheDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapCache");
public static string TileCacheFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl Cache");
public static TimeSpan TileCacheExpiryAge = TimeSpan.FromDays(1);
private readonly Queue<Tile> pendingTiles = new Queue<Tile>();
@ -45,7 +53,7 @@ namespace MapControl
lock (pendingTiles)
{
if (!string.IsNullOrEmpty(TileCacheDirectory) &&
if (!string.IsNullOrEmpty(TileCacheFolder) &&
!string.IsNullOrEmpty(TileLayerName))
{
List<Tile> expiredTiles = new List<Tile>(newTiles.Count);
@ -174,7 +182,7 @@ namespace MapControl
string tilePath;
if (!string.IsNullOrEmpty(TileCacheDirectory) &&
if (!string.IsNullOrEmpty(TileCacheFolder) &&
!string.IsNullOrEmpty(TileLayerName) &&
(tilePath = TilePath(tile, decoder)) != null)
{
@ -213,7 +221,7 @@ namespace MapControl
private string TileDirectory(Tile tile)
{
return Path.Combine(TileCacheDirectory, TileLayerName, tile.ZoomLevel.ToString(), tile.XIndex.ToString());
return Path.Combine(TileCacheFolder, TileLayerName, tile.ZoomLevel.ToString(), tile.XIndex.ToString());
}
private string TilePath(Tile tile, BitmapDecoder decoder)
@ -255,7 +263,7 @@ namespace MapControl
private static void TraceInformation(string format, params object[] args)
{
#if DEBUG
#if TRACE
System.Diagnostics.Trace.TraceInformation("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args));
#endif
}

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
@ -7,11 +11,17 @@ using System.Windows.Media;
namespace MapControl
{
/// <summary>
/// Fills a rectangular area with map tiles from a TileSource. If the IsCached property is true,
/// map tiles are cached in a folder defined by the TileImageLoader.TileCacheFolder property.
/// </summary>
[ContentProperty("TileSource")]
public class TileLayer : DrawingVisual
{
private readonly TileImageLoader tileImageLoader = new TileImageLoader();
private readonly List<Tile> tiles = new List<Tile>();
private bool isCached = false;
private string name = string.Empty;
private string description = string.Empty;
private Int32Rect grid;
private int zoomLevel;
@ -36,10 +46,24 @@ namespace MapControl
set { tileImageLoader.MaxDownloads = value; }
}
public bool IsCached
{
get { return isCached; }
set
{
isCached = value;
tileImageLoader.TileLayerName = isCached ? name : null;
}
}
public string Name
{
get { return tileImageLoader.TileLayerName; }
set { tileImageLoader.TileLayerName = value; }
get { return name; }
set
{
name = value;
tileImageLoader.TileLayerName = isCached ? name : null;
}
}
public string Description
@ -136,7 +160,7 @@ namespace MapControl
tiles.Sort((t1, t2) => t1.ZoomLevel - t2.ZoomLevel);
System.Diagnostics.Trace.TraceInformation("{0} Tiles: {1}", tiles.Count, string.Join(", ", tiles.Select(t => t.ZoomLevel.ToString())));
//System.Diagnostics.Trace.TraceInformation("{0} Tiles: {1}", tiles.Count, string.Join(", ", tiles.Select(t => t.ZoomLevel.ToString())));
}
private void RenderTiles()

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.ObjectModel;
using System.Windows.Media;

View file

@ -1,4 +1,8 @@
using System;
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
// Copyright © 2012 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.ComponentModel;
using System.Globalization;
using System.Text;
@ -6,6 +10,9 @@ using System.Windows;
namespace MapControl
{
/// <summary>
/// Defines the URI of a map tile.
/// </summary>
[TypeConverter(typeof(TileSourceTypeConverter))]
public class TileSource
{
@ -90,20 +97,20 @@ namespace MapControl
{
public override Uri GetUri(int x, int y, int zoomLevel)
{
InverseMercatorTransform t = new InverseMercatorTransform();
MercatorTransform t = new MercatorTransform();
double n = 1 << zoomLevel;
double x1 = (double)x * 360d / n - 180d;
double x2 = (double)(x + 1) * 360d / n - 180d;
double y1 = 180d - (double)(y + 1) * 360d / n;
double y2 = 180d - (double)y * 360d / n;
Point p1 = t.Transform(new Point(x1, y1));
Point p2 = t.Transform(new Point(x2, y2));
Location p1 = t.TransformBack(new Point(x1, y1));
Location p2 = t.TransformBack(new Point(x2, y2));
return new Uri(UriFormat.
Replace("{w}", p1.X.ToString(CultureInfo.InvariantCulture)).
Replace("{s}", p1.Y.ToString(CultureInfo.InvariantCulture)).
Replace("{e}", p2.X.ToString(CultureInfo.InvariantCulture)).
Replace("{n}", p2.Y.ToString(CultureInfo.InvariantCulture)));
Replace("{w}", p1.Longitude.ToString(CultureInfo.InvariantCulture)).
Replace("{s}", p1.Latitude.ToString(CultureInfo.InvariantCulture)).
Replace("{e}", p2.Longitude.ToString(CultureInfo.InvariantCulture)).
Replace("{n}", p2.Latitude.ToString(CultureInfo.InvariantCulture)));
}
}