mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2025-12-06 07:12:04 +01:00
.
This commit is contained in:
commit
caab7208a3
30
MapControl.sln
Normal file
30
MapControl.sln
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 11.00
|
||||||
|
# Visual Studio 2010
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl", "MapControl\MapControl.csproj", "{06481252-2310-414A-B9FC-D5739FDF6BD3}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|Mixed Platforms = Debug|Mixed Platforms
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|Mixed Platforms = Release|Mixed Platforms
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{06481252-2310-414A-B9FC-D5739FDF6BD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{06481252-2310-414A-B9FC-D5739FDF6BD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{06481252-2310-414A-B9FC-D5739FDF6BD3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{06481252-2310-414A-B9FC-D5739FDF6BD3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{06481252-2310-414A-B9FC-D5739FDF6BD3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{06481252-2310-414A-B9FC-D5739FDF6BD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{06481252-2310-414A-B9FC-D5739FDF6BD3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{06481252-2310-414A-B9FC-D5739FDF6BD3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{06481252-2310-414A-B9FC-D5739FDF6BD3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{06481252-2310-414A-B9FC-D5739FDF6BD3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
61
MapControl/GlyphRunText.cs
Normal file
61
MapControl/GlyphRunText.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
public static class GlyphRunText
|
||||||
|
{
|
||||||
|
public static GlyphRun Create(string text, Typeface typeface, double emSize, Point baselineOrigin)
|
||||||
|
{
|
||||||
|
GlyphTypeface glyphTypeface;
|
||||||
|
|
||||||
|
if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(string.Format("{0}: no GlyphTypeface found", typeface.FontFamily));
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort[] glyphIndices = new ushort[text.Length];
|
||||||
|
double[] advanceWidths = new double[text.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
ushort glyphIndex = glyphTypeface.CharacterToGlyphMap[text[i]];
|
||||||
|
glyphIndices[i] = glyphIndex;
|
||||||
|
advanceWidths[i] = glyphTypeface.AdvanceWidths[glyphIndex] * emSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GlyphRun(glyphTypeface, 0, false, emSize, glyphIndices, baselineOrigin, advanceWidths,
|
||||||
|
null, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GlyphRun Create(string text, Typeface typeface, double emSize, Vector centerOffset)
|
||||||
|
{
|
||||||
|
GlyphTypeface glyphTypeface;
|
||||||
|
|
||||||
|
if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(string.Format("{0}: no GlyphTypeface found", typeface.FontFamily));
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort[] glyphIndices = new ushort[text.Length];
|
||||||
|
double[] advanceWidths = new double[text.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
ushort glyphIndex = glyphTypeface.CharacterToGlyphMap[text[i]];
|
||||||
|
glyphIndices[i] = glyphIndex;
|
||||||
|
advanceWidths[i] = glyphTypeface.AdvanceWidths[glyphIndex] * emSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
GlyphRun glyphRun = new GlyphRun(glyphTypeface, 0, false, emSize, glyphIndices, new Point(), advanceWidths,
|
||||||
|
null, null, null, null, null, null);
|
||||||
|
|
||||||
|
Rect bbox = glyphRun.ComputeInkBoundingBox();
|
||||||
|
Point baselineOrigin = new Point(centerOffset.X - bbox.X - bbox.Width / 2d, centerOffset.Y - bbox.Y - bbox.Height / 2d);
|
||||||
|
|
||||||
|
return new GlyphRun(glyphTypeface, 0, false, emSize, glyphIndices, baselineOrigin, advanceWidths,
|
||||||
|
null, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
708
MapControl/Map.cs
Normal file
708
MapControl/Map.cs
Normal file
|
|
@ -0,0 +1,708 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Animation;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
public partial class Map : MapPanel
|
||||||
|
{
|
||||||
|
public const double MeterPerDegree = 1852d * 60d;
|
||||||
|
|
||||||
|
public static readonly DependencyProperty FontSizeProperty = Control.FontSizeProperty.AddOwner(typeof(Map));
|
||||||
|
public static readonly DependencyProperty FontFamilyProperty = Control.FontFamilyProperty.AddOwner(typeof(Map));
|
||||||
|
public static readonly DependencyProperty FontStyleProperty = Control.FontStyleProperty.AddOwner(typeof(Map));
|
||||||
|
public static readonly DependencyProperty FontWeightProperty = Control.FontWeightProperty.AddOwner(typeof(Map));
|
||||||
|
public static readonly DependencyProperty FontStretchProperty = Control.FontStretchProperty.AddOwner(typeof(Map));
|
||||||
|
public static readonly DependencyProperty ForegroundProperty = Control.ForegroundProperty.AddOwner(typeof(Map));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty LightForegroundProperty = DependencyProperty.Register(
|
||||||
|
"LightForeground", typeof(Brush), typeof(Map));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty DarkForegroundProperty = DependencyProperty.Register(
|
||||||
|
"DarkForeground", typeof(Brush), typeof(Map));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty LightBackgroundProperty = DependencyProperty.Register(
|
||||||
|
"LightBackground", typeof(Brush), typeof(Map));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty DarkBackgroundProperty = DependencyProperty.Register(
|
||||||
|
"DarkBackground", typeof(Brush), typeof(Map));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty TileLayersProperty = DependencyProperty.Register(
|
||||||
|
"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 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)));
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty ZoomLevelProperty = DependencyProperty.Register(
|
||||||
|
"ZoomLevel", typeof(double), typeof(Map), new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||||
|
(o, e) => ((Map)o).SetZoomLevel((double)e.NewValue),
|
||||||
|
(o, v) => ((Map)o).CoerceZoomLevel((double)v)));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty TargetZoomLevelProperty = DependencyProperty.Register(
|
||||||
|
"TargetZoomLevel", typeof(double), typeof(Map), new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||||
|
(o, e) => ((Map)o).SetTargetZoomLevel((double)e.NewValue),
|
||||||
|
(o, v) => ((Map)o).CoerceZoomLevel((double)v)));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty HeadingProperty = DependencyProperty.Register(
|
||||||
|
"Heading", typeof(double), typeof(Map), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||||
|
(o, e) => ((Map)o).SetHeading((double)e.NewValue),
|
||||||
|
(o, v) => ((Map)o).CoerceHeading((double)v)));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty TargetHeadingProperty = DependencyProperty.Register(
|
||||||
|
"TargetHeading", typeof(double), typeof(Map), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||||
|
(o, e) => ((Map)o).SetTargetHeading((double)e.NewValue),
|
||||||
|
(o, v) => ((Map)o).CoerceHeading((double)v)));
|
||||||
|
|
||||||
|
private static readonly DependencyPropertyKey CenterScalePropertyKey = DependencyProperty.RegisterReadOnly(
|
||||||
|
"CenterScale", typeof(double), typeof(Map), null);
|
||||||
|
|
||||||
|
public static readonly DependencyProperty CenterScaleProperty = CenterScalePropertyKey.DependencyProperty;
|
||||||
|
|
||||||
|
private readonly TileContainer tileContainer = new TileContainer();
|
||||||
|
private readonly MapViewTransform mapViewTransform = new MapViewTransform();
|
||||||
|
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 DoubleAnimation zoomLevelAnimation;
|
||||||
|
private DoubleAnimation headingAnimation;
|
||||||
|
private bool updateTransform = true;
|
||||||
|
|
||||||
|
public Map()
|
||||||
|
{
|
||||||
|
MinZoomLevel = 1;
|
||||||
|
MaxZoomLevel = 20;
|
||||||
|
|
||||||
|
AddVisualChild(tileContainer);
|
||||||
|
mapViewTransform.ViewTransform = tileContainer.ViewTransform;
|
||||||
|
|
||||||
|
BaseTileLayer = new TileLayer
|
||||||
|
{
|
||||||
|
Description = "© {y} OpenStreetMap Contributors, CC-BY-SA",
|
||||||
|
TileSource = new OpenStreetMapTileSource { UriFormat = "http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png" }
|
||||||
|
};
|
||||||
|
|
||||||
|
SetValue(ParentMapProperty, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the ViewTransform property has changed.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler ViewTransformChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the TileLayers property has changed.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler TileLayersChanged;
|
||||||
|
|
||||||
|
public double MinZoomLevel { get; set; }
|
||||||
|
public double MaxZoomLevel { get; set; }
|
||||||
|
|
||||||
|
public double FontSize
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(FontSizeProperty); }
|
||||||
|
set { SetValue(FontSizeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontFamily FontFamily
|
||||||
|
{
|
||||||
|
get { return (FontFamily)GetValue(FontFamilyProperty); }
|
||||||
|
set { SetValue(FontFamilyProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontStyle FontStyle
|
||||||
|
{
|
||||||
|
get { return (FontStyle)GetValue(FontStyleProperty); }
|
||||||
|
set { SetValue(FontStyleProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontWeight FontWeight
|
||||||
|
{
|
||||||
|
get { return (FontWeight)GetValue(FontWeightProperty); }
|
||||||
|
set { SetValue(FontWeightProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontStretch FontStretch
|
||||||
|
{
|
||||||
|
get { return (FontStretch)GetValue(FontStretchProperty); }
|
||||||
|
set { SetValue(FontStretchProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush Foreground
|
||||||
|
{
|
||||||
|
get { return (Brush)GetValue(ForegroundProperty); }
|
||||||
|
set { SetValue(ForegroundProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush LightForeground
|
||||||
|
{
|
||||||
|
get { return (Brush)GetValue(LightForegroundProperty); }
|
||||||
|
set { SetValue(LightForegroundProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush DarkForeground
|
||||||
|
{
|
||||||
|
get { return (Brush)GetValue(DarkForegroundProperty); }
|
||||||
|
set { SetValue(DarkForegroundProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush LightBackground
|
||||||
|
{
|
||||||
|
get { return (Brush)GetValue(LightBackgroundProperty); }
|
||||||
|
set { SetValue(LightBackgroundProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush DarkBackground
|
||||||
|
{
|
||||||
|
get { return (Brush)GetValue(DarkBackgroundProperty); }
|
||||||
|
set { SetValue(DarkBackgroundProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the TileLayers used by this Map.
|
||||||
|
/// </summary>
|
||||||
|
public TileLayerCollection TileLayers
|
||||||
|
{
|
||||||
|
get { return (TileLayerCollection)GetValue(TileLayersProperty); }
|
||||||
|
set { SetValue(TileLayersProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the base TileLayer used by this Map, i.e. TileLayers[0].
|
||||||
|
/// </summary>
|
||||||
|
public TileLayer BaseTileLayer
|
||||||
|
{
|
||||||
|
get { return (TileLayer)GetValue(BaseTileLayerProperty); }
|
||||||
|
set { SetValue(BaseTileLayerProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the opacity of the tile layers.
|
||||||
|
/// </summary>
|
||||||
|
public double TileOpacity
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(TileOpacityProperty); }
|
||||||
|
set { SetValue(TileOpacityProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the world coordinates (latitude and longitude) of the center point.
|
||||||
|
/// </summary>
|
||||||
|
public Point Center
|
||||||
|
{
|
||||||
|
get { return (Point)GetValue(CenterProperty); }
|
||||||
|
set { SetValue(CenterProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the target value of a Center animation.
|
||||||
|
/// </summary>
|
||||||
|
public Point TargetCenter
|
||||||
|
{
|
||||||
|
get { return (Point)GetValue(TargetCenterProperty); }
|
||||||
|
set { SetValue(TargetCenterProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the map zoom level.
|
||||||
|
/// </summary>
|
||||||
|
public double ZoomLevel
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(ZoomLevelProperty); }
|
||||||
|
set { SetValue(ZoomLevelProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the target value of a ZoomLevel animation.
|
||||||
|
/// </summary>
|
||||||
|
public double TargetZoomLevel
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(TargetZoomLevelProperty); }
|
||||||
|
set { SetValue(TargetZoomLevelProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the map rotation angle in degrees.
|
||||||
|
/// </summary>
|
||||||
|
public double Heading
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(HeadingProperty); }
|
||||||
|
set { SetValue(HeadingProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the target value of a Heading animation.
|
||||||
|
/// </summary>
|
||||||
|
public double TargetHeading
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(TargetHeadingProperty); }
|
||||||
|
set { SetValue(TargetHeadingProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the map scale at the Center point as view coordinate units (pixels) per meter.
|
||||||
|
/// </summary>
|
||||||
|
public double CenterScale
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(CenterScaleProperty); }
|
||||||
|
private set { SetValue(CenterScalePropertyKey, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the transformation from world coordinates (latitude and longitude)
|
||||||
|
/// to cartesian map coordinates.
|
||||||
|
/// </summary>
|
||||||
|
public MapTransform MapTransform
|
||||||
|
{
|
||||||
|
get { return mapViewTransform.MapTransform; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the transformation from cartesian map coordinates to view coordinates.
|
||||||
|
/// </summary>
|
||||||
|
public Transform ViewTransform
|
||||||
|
{
|
||||||
|
get { return mapViewTransform.ViewTransform; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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.
|
||||||
|
/// </summary>
|
||||||
|
public Transform ScaleTransform
|
||||||
|
{
|
||||||
|
get { return scaleTransform; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the transformation that rotates by the value of the Heading property.
|
||||||
|
/// </summary>
|
||||||
|
public Transform RotateTransform
|
||||||
|
{
|
||||||
|
get { return rotateTransform; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the combination of ScaleTransform and RotateTransform
|
||||||
|
/// </summary>
|
||||||
|
public Transform ScaleRotateTransform
|
||||||
|
{
|
||||||
|
get { return scaleRotateTransform; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets an intermittent origin point in world 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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.
|
||||||
|
/// </summary>
|
||||||
|
public void ResetTransformOrigin()
|
||||||
|
{
|
||||||
|
transformOrigin = null;
|
||||||
|
viewOrigin = new Point(RenderSize.Width / 2d, RenderSize.Height / 2d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the Center property according to the specified translation in view coordinates.
|
||||||
|
/// </summary>
|
||||||
|
public void TranslateMap(Vector translation)
|
||||||
|
{
|
||||||
|
if (translation.X != 0d || translation.Y != 0d)
|
||||||
|
{
|
||||||
|
ResetTransformOrigin();
|
||||||
|
Center = MapViewTransform.Inverse.Transform(viewOrigin - 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.
|
||||||
|
/// </summary>
|
||||||
|
public void TransformMap(Point origin, Vector translation, double rotation, double scale)
|
||||||
|
{
|
||||||
|
if (rotation != 0d || scale != 1d)
|
||||||
|
{
|
||||||
|
SetTransformViewOrigin(origin);
|
||||||
|
updateTransform = false;
|
||||||
|
Heading += rotation;
|
||||||
|
ZoomLevel += Math.Log(scale, 2d);
|
||||||
|
updateTransform = true;
|
||||||
|
SetViewTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
TranslateMap(translation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the value of the ZoomLevel property while retaining the specified origin point
|
||||||
|
/// in view coordinates.
|
||||||
|
/// </summary>
|
||||||
|
public void ZoomMap(Point origin, double zoomLevel)
|
||||||
|
{
|
||||||
|
SetTransformViewOrigin(origin);
|
||||||
|
TargetZoomLevel = zoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the map scale at the specified world coordinate point
|
||||||
|
/// as view coordinate units (pixels) per meter.
|
||||||
|
/// </summary>
|
||||||
|
public double GetMapScale(Point point)
|
||||||
|
{
|
||||||
|
return MapTransform.RelativeScale(point) * Math.Pow(2d, ZoomLevel) * 256d / (MeterPerDegree * 360d);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int VisualChildrenCount
|
||||||
|
{
|
||||||
|
get { return InternalChildren.Count + 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Visual GetVisualChild(int index)
|
||||||
|
{
|
||||||
|
if (index == 0)
|
||||||
|
{
|
||||||
|
return tileContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return InternalChildren[index - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
|
||||||
|
{
|
||||||
|
base.OnRenderSizeChanged(sizeInfo);
|
||||||
|
|
||||||
|
ResetTransformOrigin();
|
||||||
|
SetViewTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRender(DrawingContext drawingContext)
|
||||||
|
{
|
||||||
|
drawingContext.DrawRectangle(Background, null, new Rect(RenderSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnViewTransformChanged(Map map)
|
||||||
|
{
|
||||||
|
base.OnViewTransformChanged(map);
|
||||||
|
|
||||||
|
if (ViewTransformChanged != null)
|
||||||
|
{
|
||||||
|
ViewTransformChanged(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void OnTileLayersChanged()
|
||||||
|
{
|
||||||
|
if (tileContainer.TileLayers != null &&
|
||||||
|
tileContainer.TileLayers.Count > 0 &&
|
||||||
|
tileContainer.TileLayers[0].HasDarkBackground)
|
||||||
|
{
|
||||||
|
if (DarkForeground != null)
|
||||||
|
{
|
||||||
|
Foreground = DarkForeground;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DarkBackground != null)
|
||||||
|
{
|
||||||
|
Background = DarkBackground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (LightForeground != null)
|
||||||
|
{
|
||||||
|
Foreground = LightForeground;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LightBackground != null)
|
||||||
|
{
|
||||||
|
Background = LightBackground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TileLayersChanged != null)
|
||||||
|
{
|
||||||
|
TileLayersChanged(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTileLayers(TileLayerCollection tileLayers)
|
||||||
|
{
|
||||||
|
BaseTileLayer = tileLayers.Count > 0 ? tileLayers[0] : null;
|
||||||
|
tileContainer.TileLayers = tileLayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetBaseTileLayer(TileLayer baseTileLayer)
|
||||||
|
{
|
||||||
|
if (baseTileLayer != null)
|
||||||
|
{
|
||||||
|
if (TileLayers == null)
|
||||||
|
{
|
||||||
|
TileLayers = new TileLayerCollection(baseTileLayer);
|
||||||
|
}
|
||||||
|
else if (TileLayers.Count == 0)
|
||||||
|
{
|
||||||
|
TileLayers.Add(baseTileLayer);
|
||||||
|
}
|
||||||
|
else if (TileLayers[0] != baseTileLayer)
|
||||||
|
{
|
||||||
|
TileLayers[0] = baseTileLayer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TileLayer CoerceBaseTileLayer(TileLayer baseTileLayer)
|
||||||
|
{
|
||||||
|
if (baseTileLayer == null && TileLayers.Count > 0)
|
||||||
|
{
|
||||||
|
baseTileLayer = TileLayers[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseTileLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetCenter(Point center)
|
||||||
|
{
|
||||||
|
if (updateTransform)
|
||||||
|
{
|
||||||
|
ResetTransformOrigin();
|
||||||
|
SetViewTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (centerAnimation == null)
|
||||||
|
{
|
||||||
|
TargetCenter = center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTargetCenter(Point targetCenter)
|
||||||
|
{
|
||||||
|
if (targetCenter != Center)
|
||||||
|
{
|
||||||
|
if (centerAnimation != null)
|
||||||
|
{
|
||||||
|
centerAnimation.Completed -= CenterAnimationCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
centerAnimation = new PointAnimation
|
||||||
|
{
|
||||||
|
From = Center,
|
||||||
|
To = targetCenter,
|
||||||
|
Duration = TimeSpan.FromSeconds(0.5),
|
||||||
|
FillBehavior = FillBehavior.Stop,
|
||||||
|
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }
|
||||||
|
};
|
||||||
|
|
||||||
|
centerAnimation.Completed += CenterAnimationCompleted;
|
||||||
|
|
||||||
|
updateTransform = false;
|
||||||
|
Center = targetCenter;
|
||||||
|
updateTransform = true;
|
||||||
|
|
||||||
|
BeginAnimation(CenterProperty, centerAnimation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CenterAnimationCompleted(object sender, EventArgs eventArgs)
|
||||||
|
{
|
||||||
|
centerAnimation = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point CoerceCenter(Point point)
|
||||||
|
{
|
||||||
|
point.X = ((point.X + 180d) % 360d + 360d) % 360d - 180d;
|
||||||
|
point.Y = Math.Min(Math.Max(point.Y, -MapTransform.MaxLatitude), MapTransform.MaxLatitude);
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetZoomLevel(double zoomLevel)
|
||||||
|
{
|
||||||
|
if (updateTransform)
|
||||||
|
{
|
||||||
|
SetViewTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zoomLevelAnimation == null)
|
||||||
|
{
|
||||||
|
TargetZoomLevel = zoomLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTargetZoomLevel(double targetZoomLevel)
|
||||||
|
{
|
||||||
|
if (targetZoomLevel != ZoomLevel)
|
||||||
|
{
|
||||||
|
if (zoomLevelAnimation != null)
|
||||||
|
{
|
||||||
|
zoomLevelAnimation.Completed -= ZoomLevelAnimationCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomLevelAnimation = new DoubleAnimation
|
||||||
|
{
|
||||||
|
From = ZoomLevel,
|
||||||
|
To = targetZoomLevel,
|
||||||
|
Duration = TimeSpan.FromSeconds(0.5),
|
||||||
|
FillBehavior = FillBehavior.Stop,
|
||||||
|
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }
|
||||||
|
};
|
||||||
|
|
||||||
|
zoomLevelAnimation.Completed += ZoomLevelAnimationCompleted;
|
||||||
|
|
||||||
|
updateTransform = false;
|
||||||
|
ZoomLevel = targetZoomLevel;
|
||||||
|
updateTransform = true;
|
||||||
|
|
||||||
|
BeginAnimation(ZoomLevelProperty, zoomLevelAnimation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ZoomLevelAnimationCompleted(object sender, EventArgs eventArgs)
|
||||||
|
{
|
||||||
|
zoomLevelAnimation = null;
|
||||||
|
ResetTransformOrigin();
|
||||||
|
}
|
||||||
|
|
||||||
|
private double CoerceZoomLevel(double zoomLevel)
|
||||||
|
{
|
||||||
|
return Math.Min(Math.Max(zoomLevel, MinZoomLevel), MaxZoomLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetHeading(double heading)
|
||||||
|
{
|
||||||
|
if (updateTransform)
|
||||||
|
{
|
||||||
|
SetViewTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headingAnimation == null)
|
||||||
|
{
|
||||||
|
TargetHeading = heading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTargetHeading(double targetHeading)
|
||||||
|
{
|
||||||
|
if (targetHeading != Heading)
|
||||||
|
{
|
||||||
|
if (headingAnimation != null)
|
||||||
|
{
|
||||||
|
headingAnimation.Completed -= HeadingAnimationCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
double delta = targetHeading - Heading;
|
||||||
|
|
||||||
|
if (delta > 180d)
|
||||||
|
{
|
||||||
|
delta -= 360d;
|
||||||
|
}
|
||||||
|
else if (delta < -180d)
|
||||||
|
{
|
||||||
|
delta += 360d;
|
||||||
|
}
|
||||||
|
|
||||||
|
headingAnimation = new DoubleAnimation
|
||||||
|
{
|
||||||
|
From = Heading,
|
||||||
|
By = delta,
|
||||||
|
Duration = TimeSpan.FromSeconds(0.5),
|
||||||
|
FillBehavior = FillBehavior.Stop,
|
||||||
|
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }
|
||||||
|
};
|
||||||
|
|
||||||
|
headingAnimation.Completed += HeadingAnimationCompleted;
|
||||||
|
|
||||||
|
updateTransform = false;
|
||||||
|
Heading = targetHeading;
|
||||||
|
updateTransform = true;
|
||||||
|
|
||||||
|
BeginAnimation(HeadingProperty, headingAnimation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HeadingAnimationCompleted(object sender, EventArgs eventArgs)
|
||||||
|
{
|
||||||
|
headingAnimation = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double CoerceHeading(double heading)
|
||||||
|
{
|
||||||
|
return ((heading % 360d) + 360d) % 360d;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetViewTransform()
|
||||||
|
{
|
||||||
|
double scale;
|
||||||
|
|
||||||
|
if (transformOrigin.HasValue)
|
||||||
|
{
|
||||||
|
scale = tileContainer.SetTransform(ZoomLevel, Heading, MapTransform.Transform(transformOrigin.Value), viewOrigin, RenderSize);
|
||||||
|
updateTransform = false;
|
||||||
|
Center = MapViewTransform.Inverse.Transform(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d));
|
||||||
|
updateTransform = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
scale = tileContainer.SetTransform(ZoomLevel, Heading, MapTransform.Transform(Center), viewOrigin, RenderSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
scale *= MapTransform.RelativeScale(Center) / MeterPerDegree; // Pixels per meter at center latitude
|
||||||
|
|
||||||
|
CenterScale = scale;
|
||||||
|
scaleTransform.ScaleX = scale;
|
||||||
|
scaleTransform.ScaleY = scale;
|
||||||
|
rotateTransform.Angle = Heading;
|
||||||
|
scaleRotateTransform.Matrix = scaleTransform.Value * rotateTransform.Value;
|
||||||
|
|
||||||
|
OnViewTransformChanged(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
MapControl/MapControl.csproj
Normal file
82
MapControl/MapControl.csproj
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProductVersion>8.0.30703</ProductVersion>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<ProjectGuid>{06481252-2310-414A-B9FC-D5739FDF6BD3}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>MapControl</RootNamespace>
|
||||||
|
<AssemblyName>MapControl</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="PresentationCore" />
|
||||||
|
<Reference Include="PresentationFramework" />
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.configuration" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xaml" />
|
||||||
|
<Reference Include="WindowsBase" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="GlyphRunText.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="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" />
|
||||||
|
<Compile Include="TileContainer.cs" />
|
||||||
|
<Compile Include="TileLayer.cs" />
|
||||||
|
<Compile Include="TileLayerCollection.cs" />
|
||||||
|
<Compile Include="TileSource.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Include="Themes\Generic.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
</Project>
|
||||||
44
MapControl/MapElement.cs
Normal file
44
MapControl/MapElement.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
internal interface INotifyParentMapChanged
|
||||||
|
{
|
||||||
|
void ParentMapChanged(Map oldParentMap, Map newParentMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class MapElement : FrameworkElement, INotifyParentMapChanged
|
||||||
|
{
|
||||||
|
protected MapElement()
|
||||||
|
{
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||||
|
VerticalAlignment = VerticalAlignment.Stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map ParentMap
|
||||||
|
{
|
||||||
|
get { return MapPanel.GetParentMap(this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void OnViewTransformChanged(Map parentMap);
|
||||||
|
|
||||||
|
private void OnViewTransformChanged(object sender, EventArgs eventArgs)
|
||||||
|
{
|
||||||
|
OnViewTransformChanged((Map)sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
void INotifyParentMapChanged.ParentMapChanged(Map oldParentMap, Map newParentMap)
|
||||||
|
{
|
||||||
|
if (oldParentMap != null)
|
||||||
|
{
|
||||||
|
oldParentMap.ViewTransformChanged -= OnViewTransformChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newParentMap != null)
|
||||||
|
{
|
||||||
|
newParentMap.ViewTransformChanged += OnViewTransformChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
203
MapControl/MapGraticule.cs
Normal file
203
MapControl/MapGraticule.cs
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
public class MapGraticule : MapElement
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty ForegroundProperty = Control.ForegroundProperty.AddOwner(
|
||||||
|
typeof(MapGraticule), new FrameworkPropertyMetadata((o, e) => ((MapGraticule)o).UpdateBrush()));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty FontSizeProperty = Control.FontSizeProperty.AddOwner(
|
||||||
|
typeof(MapGraticule));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty FontFamilyProperty = Control.FontFamilyProperty.AddOwner(
|
||||||
|
typeof(MapGraticule), new FrameworkPropertyMetadata((o, e) => ((MapGraticule)o).typeface = null));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty FontStyleProperty = Control.FontStyleProperty.AddOwner(
|
||||||
|
typeof(MapGraticule), new FrameworkPropertyMetadata((o, e) => ((MapGraticule)o).typeface = null));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty FontWeightProperty = Control.FontWeightProperty.AddOwner(
|
||||||
|
typeof(MapGraticule), new FrameworkPropertyMetadata((o, e) => ((MapGraticule)o).typeface = null));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty FontStretchProperty = Control.FontStretchProperty.AddOwner(
|
||||||
|
typeof(MapGraticule), new FrameworkPropertyMetadata((o, e) => ((MapGraticule)o).typeface = null));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty StrokeProperty = Shape.StrokeProperty.AddOwner(
|
||||||
|
typeof(MapGraticule), new FrameworkPropertyMetadata((o, e) => ((MapGraticule)o).UpdateBrush()));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty StrokeThicknessProperty = Shape.StrokeThicknessProperty.AddOwner(
|
||||||
|
typeof(MapGraticule), new FrameworkPropertyMetadata(0.5, (o, e) => ((MapGraticule)o).pen.Thickness = (double)e.NewValue));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty MinSpacingPixelsProperty = DependencyProperty.Register(
|
||||||
|
"MinSpacingPixels", typeof(double), typeof(MapGraticule), new FrameworkPropertyMetadata(100d));
|
||||||
|
|
||||||
|
public static double[] GridSpacings =
|
||||||
|
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();
|
||||||
|
private readonly Pen pen;
|
||||||
|
private Typeface typeface;
|
||||||
|
|
||||||
|
public MapGraticule()
|
||||||
|
{
|
||||||
|
pen = new Pen(null, StrokeThickness);
|
||||||
|
IsHitTestVisible = false;
|
||||||
|
AddVisualChild(visual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush Foreground
|
||||||
|
{
|
||||||
|
get { return (Brush)GetValue(ForegroundProperty); }
|
||||||
|
set { SetValue(ForegroundProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public double FontSize
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(FontSizeProperty); }
|
||||||
|
set { SetValue(FontSizeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontFamily FontFamily
|
||||||
|
{
|
||||||
|
get { return (FontFamily)GetValue(FontFamilyProperty); }
|
||||||
|
set { SetValue(FontFamilyProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontStyle FontStyle
|
||||||
|
{
|
||||||
|
get { return (FontStyle)GetValue(FontStyleProperty); }
|
||||||
|
set { SetValue(FontStyleProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontWeight FontWeight
|
||||||
|
{
|
||||||
|
get { return (FontWeight)GetValue(FontWeightProperty); }
|
||||||
|
set { SetValue(FontWeightProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public FontStretch FontStretch
|
||||||
|
{
|
||||||
|
get { return (FontStretch)GetValue(FontStretchProperty); }
|
||||||
|
set { SetValue(FontStretchProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush Stroke
|
||||||
|
{
|
||||||
|
get { return (Brush)GetValue(StrokeProperty); }
|
||||||
|
set { SetValue(StrokeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public double StrokeThickness
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(StrokeThicknessProperty); }
|
||||||
|
set { SetValue(StrokeThicknessProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public double MinSpacingPixels
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(MinSpacingPixelsProperty); }
|
||||||
|
set { SetValue(MinSpacingPixelsProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int VisualChildrenCount
|
||||||
|
{
|
||||||
|
get { return 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Visual GetVisualChild(int index)
|
||||||
|
{
|
||||||
|
return visual;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnViewTransformChanged(Map parentMap)
|
||||||
|
{
|
||||||
|
Rect bounds = parentMap.MapViewTransform.Inverse.TransformBounds(new Rect(parentMap.RenderSize));
|
||||||
|
double minSpacing = MinSpacingPixels * 360d / (Math.Pow(2d, parentMap.ZoomLevel) * 256d);
|
||||||
|
double spacing = GridSpacings[GridSpacings.Length - 1];
|
||||||
|
|
||||||
|
if (spacing >= minSpacing)
|
||||||
|
{
|
||||||
|
spacing = GridSpacings.FirstOrDefault(s => s >= minSpacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
double longitudeStart = Math.Ceiling(bounds.Left / spacing) * spacing;
|
||||||
|
double latitudeStart = Math.Ceiling(bounds.Top / spacing) * spacing;
|
||||||
|
|
||||||
|
if (pen.Brush == null)
|
||||||
|
{
|
||||||
|
pen.Brush = Stroke != null ? Stroke : Foreground;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (DrawingContext drawingContext = visual.RenderOpen())
|
||||||
|
{
|
||||||
|
for (double lon = longitudeStart; lon <= bounds.Right; lon += spacing)
|
||||||
|
{
|
||||||
|
drawingContext.DrawLine(pen,
|
||||||
|
parentMap.MapViewTransform.Transform(new Point(lon, bounds.Bottom)),
|
||||||
|
parentMap.MapViewTransform.Transform(new Point(lon, bounds.Top)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (double lat = latitudeStart; lat <= bounds.Bottom; lat += spacing)
|
||||||
|
{
|
||||||
|
drawingContext.DrawLine(pen,
|
||||||
|
parentMap.MapViewTransform.Transform(new Point(bounds.Left, lat)),
|
||||||
|
parentMap.MapViewTransform.Transform(new Point(bounds.Right, lat)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Foreground != null && Foreground != Brushes.Transparent)
|
||||||
|
{
|
||||||
|
string format = spacing < 1d ? "{0} {1}°{2:00}'" : "{0} {1}°";
|
||||||
|
|
||||||
|
if (typeface == null)
|
||||||
|
{
|
||||||
|
typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (double lon = longitudeStart; lon <= bounds.Right; lon += spacing)
|
||||||
|
{
|
||||||
|
for (double lat = latitudeStart; lat <= bounds.Bottom; lat += spacing)
|
||||||
|
{
|
||||||
|
double t = StrokeThickness / 2d;
|
||||||
|
Point p = parentMap.MapViewTransform.Transform(new Point(lon, lat));
|
||||||
|
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");
|
||||||
|
string lonString = CoordinateString(((lon + 180d) % 360d + 360d) % 360d - 180d, format, "EW");
|
||||||
|
|
||||||
|
drawingContext.PushTransform(new RotateTransform(parentMap.Heading, p.X, p.Y));
|
||||||
|
drawingContext.DrawGlyphRun(Foreground, GlyphRunText.Create(latString, typeface, FontSize, latPos));
|
||||||
|
drawingContext.DrawGlyphRun(Foreground, GlyphRunText.Create(lonString, typeface, FontSize, lonPos));
|
||||||
|
drawingContext.Pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBrush()
|
||||||
|
{
|
||||||
|
pen.Brush = null;
|
||||||
|
OnViewTransformChanged(ParentMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CoordinateString(double value, string format, string hemispheres)
|
||||||
|
{
|
||||||
|
char hemisphere = hemispheres[0];
|
||||||
|
|
||||||
|
if (value < -1e-8) // ~1mm
|
||||||
|
{
|
||||||
|
value = -value;
|
||||||
|
hemisphere = hemispheres[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
int minutes = (int)(value * 60d + 0.5);
|
||||||
|
|
||||||
|
return string.Format(format, hemisphere, minutes / 60, (double)(minutes % 60));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
MapControl/MapInput.cs
Normal file
81
MapControl/MapInput.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
public partial class Map
|
||||||
|
{
|
||||||
|
private double mouseWheelZoom = 0.25;
|
||||||
|
private Point? mousePosition;
|
||||||
|
|
||||||
|
public double MouseWheelZoom
|
||||||
|
{
|
||||||
|
get { return mouseWheelZoom; }
|
||||||
|
set { mouseWheelZoom = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseWheel(MouseWheelEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
base.OnMouseWheel(eventArgs);
|
||||||
|
|
||||||
|
ZoomMap(eventArgs.GetPosition(this), TargetZoomLevel + mouseWheelZoom * Math.Sign(eventArgs.Delta));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseRightButtonDown(MouseButtonEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
base.OnMouseRightButtonDown(eventArgs);
|
||||||
|
|
||||||
|
if (eventArgs.ClickCount == 2)
|
||||||
|
{
|
||||||
|
ZoomMap(eventArgs.GetPosition(this), Math.Ceiling(ZoomLevel - 1.5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
base.OnMouseLeftButtonDown(eventArgs);
|
||||||
|
|
||||||
|
if (eventArgs.ClickCount == 1)
|
||||||
|
{
|
||||||
|
mousePosition = eventArgs.GetPosition(this);
|
||||||
|
CaptureMouse();
|
||||||
|
}
|
||||||
|
else if (eventArgs.ClickCount == 2)
|
||||||
|
{
|
||||||
|
ZoomMap(eventArgs.GetPosition(this), Math.Floor(ZoomLevel + 1.5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
base.OnMouseLeftButtonUp(eventArgs);
|
||||||
|
|
||||||
|
if (mousePosition.HasValue)
|
||||||
|
{
|
||||||
|
mousePosition = null;
|
||||||
|
ReleaseMouseCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseMove(MouseEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
base.OnMouseMove(eventArgs);
|
||||||
|
|
||||||
|
if (mousePosition.HasValue)
|
||||||
|
{
|
||||||
|
Point position = eventArgs.GetPosition(this);
|
||||||
|
TranslateMap(position - mousePosition.Value);
|
||||||
|
mousePosition = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnManipulationDelta(ManipulationDeltaEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
base.OnManipulationDelta(eventArgs);
|
||||||
|
|
||||||
|
ManipulationDelta d = eventArgs.DeltaManipulation;
|
||||||
|
TransformMap(eventArgs.ManipulationOrigin, d.Translation, d.Rotation, (d.Scale.X + d.Scale.Y) / 2d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
210
MapControl/MapItem.cs
Normal file
210
MapControl/MapItem.cs
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
using System.Windows.Input;
|
||||||
|
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")]
|
||||||
|
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)));
|
||||||
|
|
||||||
|
private static readonly DependencyPropertyKey IsCurrentPropertyKey = DependencyProperty.RegisterReadOnly(
|
||||||
|
"IsCurrent", typeof(bool), typeof(MapItem), null);
|
||||||
|
|
||||||
|
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),
|
||||||
|
new FrameworkPropertyMetadata(typeof(MapItem)));
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
add { AddHandler(SelectedEvent, value); }
|
||||||
|
remove { RemoveHandler(SelectedEvent, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event RoutedEventHandler Unselected
|
||||||
|
{
|
||||||
|
add { AddHandler(UnselectedEvent, value); }
|
||||||
|
remove { RemoveHandler(UnselectedEvent, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map ParentMap
|
||||||
|
{
|
||||||
|
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); }
|
||||||
|
set { SetValue(IsSelectedProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsCurrent
|
||||||
|
{
|
||||||
|
get { return (bool)GetValue(IsCurrentProperty); }
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
if (IsCurrent != value)
|
||||||
|
{
|
||||||
|
SetValue(IsCurrentPropertyKey, value);
|
||||||
|
int zIndex = Panel.GetZIndex(this);
|
||||||
|
Panel.SetZIndex(this, value ? (zIndex | 0x40000000) : (zIndex & ~0x40000000));
|
||||||
|
VisualStateManager.GoToState(this, value ? "Current" : "NonCurrent", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsInsideMapBounds
|
||||||
|
{
|
||||||
|
get { return (bool)GetValue(IsInsideMapBoundsProperty); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Item
|
||||||
|
{
|
||||||
|
get { return item; }
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
item = value;
|
||||||
|
if (HasViewPosition)
|
||||||
|
{
|
||||||
|
ViewPositionChanged(ViewPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseEnter(MouseEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnMouseEnter(e);
|
||||||
|
CommonStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseLeave(MouseEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnMouseLeave(e);
|
||||||
|
CommonStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
base.OnMouseLeftButtonDown(eventArgs);
|
||||||
|
eventArgs.Handled = true;
|
||||||
|
IsSelected = !IsSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnTouchDown(TouchEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
base.OnTouchDown(eventArgs);
|
||||||
|
eventArgs.Handled = true; // get TouchUp event
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnTouchUp(TouchEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
base.OnTouchUp(eventArgs);
|
||||||
|
eventArgs.Handled = true;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
VisualStateManager.GoToState(this, "Disabled", true);
|
||||||
|
}
|
||||||
|
else if (IsMouseOver)
|
||||||
|
{
|
||||||
|
VisualStateManager.GoToState(this, "MouseOver", true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
VisualStateManager.GoToState(this, "Normal", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IsSelectedChanged(bool isSelected)
|
||||||
|
{
|
||||||
|
if (isSelected)
|
||||||
|
{
|
||||||
|
VisualStateManager.GoToState(this, "Selected", true);
|
||||||
|
RaiseEvent(new RoutedEventArgs(SelectedEvent));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
VisualStateManager.GoToState(this, "Unselected", true);
|
||||||
|
RaiseEvent(new RoutedEventArgs(UnselectedEvent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
128
MapControl/MapItemsControl.cs
Normal file
128
MapControl/MapItemsControl.cs
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
public enum MapItemSelectionMode { Single, Extended }
|
||||||
|
|
||||||
|
public class MapItemsControl : MultiSelector
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty SelectionModeProperty = DependencyProperty.Register(
|
||||||
|
"SelectionMode", typeof(MapItemSelectionMode), typeof(MapItemsControl),
|
||||||
|
new FrameworkPropertyMetadata((o, e) => ((MapItemsControl)o).CanSelectMultipleItems = (MapItemSelectionMode)e.NewValue != MapItemSelectionMode.Single));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty SelectionGeometryProperty = DependencyProperty.Register(
|
||||||
|
"SelectionGeometry", typeof(Geometry), typeof(MapItemsControl),
|
||||||
|
new FrameworkPropertyMetadata((o, e) => ((MapItemsControl)o).SelectionGeometryChanged((Geometry)e.NewValue)));
|
||||||
|
|
||||||
|
public MapItemsControl()
|
||||||
|
{
|
||||||
|
CanSelectMultipleItems = false;
|
||||||
|
Style = (Style)FindResource(typeof(MapItemsControl));
|
||||||
|
Items.CurrentChanging += OnCurrentItemChanging;
|
||||||
|
Items.CurrentChanged += OnCurrentItemChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MapItemSelectionMode SelectionMode
|
||||||
|
{
|
||||||
|
get { return (MapItemSelectionMode)GetValue(SelectionModeProperty); }
|
||||||
|
set { SetValue(SelectionModeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Geometry SelectionGeometry
|
||||||
|
{
|
||||||
|
get { return (Geometry)GetValue(SelectionGeometryProperty); }
|
||||||
|
set { SetValue(SelectionGeometryProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public MapItem GetMapItem(object item)
|
||||||
|
{
|
||||||
|
return item != null ? ItemContainerGenerator.ContainerFromItem(item) as MapItem : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object GetHitItem(Point point)
|
||||||
|
{
|
||||||
|
DependencyObject obj = InputHitTest(point) as DependencyObject;
|
||||||
|
|
||||||
|
while (obj != null)
|
||||||
|
{
|
||||||
|
if (obj is MapItem)
|
||||||
|
{
|
||||||
|
return ((MapItem)obj).Item;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = VisualTreeHelper.GetParent(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DependencyObject GetContainerForItemOverride()
|
||||||
|
{
|
||||||
|
return new MapItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
|
||||||
|
{
|
||||||
|
MapItem mapItem = (MapItem)element;
|
||||||
|
mapItem.Item = item;
|
||||||
|
base.PrepareContainerForItemOverride(element, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
|
||||||
|
{
|
||||||
|
MapItem mapItem = (MapItem)element;
|
||||||
|
mapItem.Item = null;
|
||||||
|
base.ClearContainerForItemOverride(element, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCurrentItemChanging(object sender, CurrentChangingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
MapItem mapItem = GetMapItem(Items.CurrentItem);
|
||||||
|
|
||||||
|
if (mapItem != null)
|
||||||
|
{
|
||||||
|
mapItem.IsCurrent = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCurrentItemChanged(object sender, EventArgs eventArgs)
|
||||||
|
{
|
||||||
|
MapItem mapItem = GetMapItem(Items.CurrentItem);
|
||||||
|
|
||||||
|
if (mapItem != null)
|
||||||
|
{
|
||||||
|
mapItem.IsCurrent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectionGeometryChanged(Geometry geometry)
|
||||||
|
{
|
||||||
|
if (geometry != null)
|
||||||
|
{
|
||||||
|
SelectionMode = MapItemSelectionMode.Extended;
|
||||||
|
|
||||||
|
BeginUpdateSelectedItems();
|
||||||
|
SelectedItems.Clear();
|
||||||
|
|
||||||
|
if (!geometry.IsEmpty())
|
||||||
|
{
|
||||||
|
foreach (object item in Items)
|
||||||
|
{
|
||||||
|
MapItem mapItem = GetMapItem(item);
|
||||||
|
|
||||||
|
if (mapItem != null && mapItem.HasViewPosition && geometry.FillContains(mapItem.ViewPosition))
|
||||||
|
{
|
||||||
|
SelectedItems.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EndUpdateSelectedItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
253
MapControl/MapPanel.cs
Normal file
253
MapControl/MapPanel.cs
Normal file
|
|
@ -0,0 +1,253 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
public class MapPanel : Panel, INotifyParentMapChanged
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty ParentMapProperty = DependencyProperty.RegisterAttached(
|
||||||
|
"ParentMap", typeof(Map), typeof(MapPanel),
|
||||||
|
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, ParentMapPropertyChanged));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty LocationProperty = DependencyProperty.RegisterAttached(
|
||||||
|
"Location", typeof(Point?), 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 ViewPositionTransformPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
|
||||||
|
"ViewPositionTransform", typeof(Transform), typeof(MapPanel), null);
|
||||||
|
|
||||||
|
public static readonly DependencyProperty ViewPositionProperty = ViewPositionPropertyKey.DependencyProperty;
|
||||||
|
public static readonly DependencyProperty ViewPositionTransformProperty = ViewPositionTransformPropertyKey.DependencyProperty;
|
||||||
|
|
||||||
|
public MapPanel()
|
||||||
|
{
|
||||||
|
ClipToBounds = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map ParentMap
|
||||||
|
{
|
||||||
|
get { return (Map)GetValue(ParentMapProperty); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map GetParentMap(UIElement element)
|
||||||
|
{
|
||||||
|
return (Map)element.GetValue(ParentMapProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point? GetLocation(UIElement element)
|
||||||
|
{
|
||||||
|
return (Point?)element.GetValue(LocationProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetLocation(UIElement element, Point? value)
|
||||||
|
{
|
||||||
|
element.SetValue(LocationProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point GetViewPosition(UIElement element)
|
||||||
|
{
|
||||||
|
return (Point)element.GetValue(ViewPositionProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Transform GetViewPositionTransform(UIElement element)
|
||||||
|
{
|
||||||
|
return (Transform)element.GetValue(ViewPositionTransformProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Size MeasureOverride(Size availableSize)
|
||||||
|
{
|
||||||
|
Size infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
|
||||||
|
|
||||||
|
foreach (UIElement element in InternalChildren)
|
||||||
|
{
|
||||||
|
element.Measure(infiniteSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Size();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Size ArrangeOverride(Size finalSize)
|
||||||
|
{
|
||||||
|
foreach (UIElement element in InternalChildren)
|
||||||
|
{
|
||||||
|
object viewPosition = element.ReadLocalValue(ViewPositionProperty);
|
||||||
|
|
||||||
|
if (viewPosition == DependencyProperty.UnsetValue ||
|
||||||
|
!ArrangeElement(element, (Point)viewPosition))
|
||||||
|
{
|
||||||
|
ArrangeElement(element, finalSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnViewTransformChanged(Map parentMap)
|
||||||
|
{
|
||||||
|
foreach (UIElement element in InternalChildren)
|
||||||
|
{
|
||||||
|
Point? location = GetLocation(element);
|
||||||
|
|
||||||
|
if (location.HasValue)
|
||||||
|
{
|
||||||
|
SetViewPosition(element, parentMap, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnViewTransformChanged(object sender, EventArgs eventArgs)
|
||||||
|
{
|
||||||
|
OnViewTransformChanged((Map)sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
void INotifyParentMapChanged.ParentMapChanged(Map oldParentMap, Map newParentMap)
|
||||||
|
{
|
||||||
|
if (oldParentMap != null && oldParentMap != this)
|
||||||
|
{
|
||||||
|
oldParentMap.ViewTransformChanged -= OnViewTransformChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newParentMap != null && newParentMap != this)
|
||||||
|
{
|
||||||
|
newParentMap.ViewTransformChanged += OnViewTransformChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ParentMapPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
INotifyParentMapChanged notifyChanged = obj as INotifyParentMapChanged;
|
||||||
|
|
||||||
|
if (notifyChanged != null)
|
||||||
|
{
|
||||||
|
notifyChanged.ParentMapChanged(eventArgs.OldValue as Map, eventArgs.NewValue as Map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LocationPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
UIElement element = (UIElement)obj;
|
||||||
|
Point? location = (Point?)eventArgs.NewValue;
|
||||||
|
Map parentMap;
|
||||||
|
|
||||||
|
if (location.HasValue && (parentMap = Map.GetParentMap(element)) != null)
|
||||||
|
{
|
||||||
|
SetViewPosition(element, parentMap, location);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
element.ClearValue(ViewPositionPropertyKey);
|
||||||
|
element.ClearValue(ViewPositionTransformPropertyKey);
|
||||||
|
element.Arrange(new Rect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetViewPosition(UIElement element, Map parentMap, Point? location)
|
||||||
|
{
|
||||||
|
Point viewPosition = parentMap.MapViewTransform.Transform(location.Value);
|
||||||
|
|
||||||
|
element.SetValue(ViewPositionPropertyKey, viewPosition);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ArrangeElement(UIElement element, Point position)
|
||||||
|
{
|
||||||
|
Rect rect = new Rect(position, element.DesiredSize);
|
||||||
|
FrameworkElement frameworkElement = element as FrameworkElement;
|
||||||
|
|
||||||
|
if (frameworkElement != null)
|
||||||
|
{
|
||||||
|
if (frameworkElement.HorizontalAlignment == HorizontalAlignment.Stretch &&
|
||||||
|
frameworkElement.VerticalAlignment == VerticalAlignment.Stretch)
|
||||||
|
{
|
||||||
|
return false; // do not arrange at position
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (frameworkElement.HorizontalAlignment)
|
||||||
|
{
|
||||||
|
case HorizontalAlignment.Center:
|
||||||
|
rect.X -= rect.Width / 2d;
|
||||||
|
break;
|
||||||
|
case HorizontalAlignment.Right:
|
||||||
|
rect.X -= rect.Width;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (frameworkElement.VerticalAlignment)
|
||||||
|
{
|
||||||
|
case VerticalAlignment.Center:
|
||||||
|
rect.Y -= rect.Height / 2d;
|
||||||
|
break;
|
||||||
|
case VerticalAlignment.Bottom:
|
||||||
|
rect.Y -= rect.Height;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.Arrange(rect);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ArrangeElement(UIElement element, Size panelSize)
|
||||||
|
{
|
||||||
|
Rect rect = new Rect(element.DesiredSize);
|
||||||
|
FrameworkElement frameworkElement = element as FrameworkElement;
|
||||||
|
|
||||||
|
if (frameworkElement != null)
|
||||||
|
{
|
||||||
|
switch (frameworkElement.HorizontalAlignment)
|
||||||
|
{
|
||||||
|
case HorizontalAlignment.Center:
|
||||||
|
rect.X = (panelSize.Width - rect.Width) / 2d;
|
||||||
|
break;
|
||||||
|
case HorizontalAlignment.Right:
|
||||||
|
rect.X = panelSize.Width - rect.Width;
|
||||||
|
break;
|
||||||
|
case HorizontalAlignment.Stretch:
|
||||||
|
rect.Width = panelSize.Width;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (frameworkElement.VerticalAlignment)
|
||||||
|
{
|
||||||
|
case VerticalAlignment.Center:
|
||||||
|
rect.Y = (panelSize.Height - rect.Height) / 2d;
|
||||||
|
break;
|
||||||
|
case VerticalAlignment.Bottom:
|
||||||
|
rect.Y = panelSize.Height - rect.Height;
|
||||||
|
break;
|
||||||
|
case VerticalAlignment.Stretch:
|
||||||
|
rect.Height = panelSize.Height;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.Arrange(rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
200
MapControl/MapPath.cs
Normal file
200
MapControl/MapPath.cs
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
public class MapPath : 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));
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty StrokeDashCapProperty = Shape.StrokeDashCapProperty.AddOwner(
|
||||||
|
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)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));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty StrokeEndLineCapProperty = Shape.StrokeEndLineCapProperty.AddOwner(
|
||||||
|
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)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));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty StrokeMiterLimitProperty = Shape.StrokeMiterLimitProperty.AddOwner(
|
||||||
|
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.MiterLimit = (double)e.NewValue));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty StrokeThicknessProperty = Shape.StrokeThicknessProperty.AddOwner(
|
||||||
|
typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).UpdatePenThickness()));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty TransformStrokeProperty = DependencyProperty.Register(
|
||||||
|
"TransformStroke", typeof(bool), typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).UpdatePenThickness()));
|
||||||
|
|
||||||
|
private readonly DrawingVisual visual = new DrawingVisual();
|
||||||
|
private readonly GeometryDrawing drawing = new GeometryDrawing();
|
||||||
|
|
||||||
|
public MapPath()
|
||||||
|
{
|
||||||
|
drawing.Brush = Fill;
|
||||||
|
drawing.Pen = new Pen(Stroke, StrokeThickness);
|
||||||
|
|
||||||
|
using (DrawingContext drawingContext = visual.RenderOpen())
|
||||||
|
{
|
||||||
|
drawingContext.DrawDrawing(drawing);
|
||||||
|
}
|
||||||
|
|
||||||
|
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); }
|
||||||
|
set { SetValue(StrokeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public DoubleCollection StrokeDashArray
|
||||||
|
{
|
||||||
|
get { return (DoubleCollection)GetValue(StrokeDashArrayProperty); }
|
||||||
|
set { SetValue(StrokeDashArrayProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public double StrokeDashOffset
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(StrokeDashOffsetProperty); }
|
||||||
|
set { SetValue(StrokeDashOffsetProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public PenLineCap StrokeDashCap
|
||||||
|
{
|
||||||
|
get { return (PenLineCap)GetValue(StrokeDashCapProperty); }
|
||||||
|
set { SetValue(StrokeDashCapProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public PenLineCap StrokeStartLineCap
|
||||||
|
{
|
||||||
|
get { return (PenLineCap)GetValue(StrokeStartLineCapProperty); }
|
||||||
|
set { SetValue(StrokeStartLineCapProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public PenLineCap StrokeEndLineCap
|
||||||
|
{
|
||||||
|
get { return (PenLineCap)GetValue(StrokeEndLineCapProperty); }
|
||||||
|
set { SetValue(StrokeEndLineCapProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public PenLineJoin StrokeLineJoin
|
||||||
|
{
|
||||||
|
get { return (PenLineJoin)GetValue(StrokeLineJoinProperty); }
|
||||||
|
set { SetValue(StrokeLineJoinProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public double StrokeMiterLimit
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(StrokeMiterLimitProperty); }
|
||||||
|
set { SetValue(StrokeMiterLimitProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public double StrokeThickness
|
||||||
|
{
|
||||||
|
get { return (double)GetValue(StrokeThicknessProperty); }
|
||||||
|
set { SetValue(StrokeThicknessProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TransformStroke
|
||||||
|
{
|
||||||
|
get { return (bool)GetValue(TransformStrokeProperty); }
|
||||||
|
set { SetValue(TransformStrokeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public double TransformedStrokeThickness
|
||||||
|
{
|
||||||
|
get { return drawing.Pen.Thickness; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public PathGeometry TransformedGeometry
|
||||||
|
{
|
||||||
|
get { return drawing.Geometry as PathGeometry; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int VisualChildrenCount
|
||||||
|
{
|
||||||
|
get { return 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Visual GetVisualChild(int index)
|
||||||
|
{
|
||||||
|
return visual;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInitialized(EventArgs eventArgs)
|
||||||
|
{
|
||||||
|
base.OnInitialized(eventArgs);
|
||||||
|
|
||||||
|
AddVisualChild(visual);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnViewTransformChanged(Map parentMap)
|
||||||
|
{
|
||||||
|
double scale = 1d;
|
||||||
|
|
||||||
|
if (TransformStroke && Data != null)
|
||||||
|
{
|
||||||
|
Point center = Data.Bounds.Location + (Vector)Data.Bounds.Size / 2d;
|
||||||
|
scale = parentMap.GetMapScale(center) * Map.MeterPerDegree;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawing.Pen.Thickness = scale * StrokeThickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateGeometry()
|
||||||
|
{
|
||||||
|
Map parentMap = MapPanel.GetParentMap(this);
|
||||||
|
|
||||||
|
if (parentMap != null && Data != null)
|
||||||
|
{
|
||||||
|
drawing.Geometry = parentMap.MapTransform.Transform(Data);
|
||||||
|
drawing.Geometry.Transform = parentMap.ViewTransform;
|
||||||
|
OnViewTransformChanged(parentMap);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
drawing.Geometry = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePenThickness()
|
||||||
|
{
|
||||||
|
Map parentMap = MapPanel.GetParentMap(this);
|
||||||
|
|
||||||
|
if (parentMap != null)
|
||||||
|
{
|
||||||
|
OnViewTransformChanged(parentMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
155
MapControl/MapPathGeometry.cs
Normal file
155
MapControl/MapPathGeometry.cs
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
104
MapControl/MapStreamGeometry.cs
Normal file
104
MapControl/MapStreamGeometry.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
MapControl/MapTransform.cs
Normal file
25
MapControl/MapTransform.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a normal cylindrical projection. Latitude and longitude values in degrees
|
||||||
|
/// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the absolute value of the minimum and maximum latitude that can be transformed.
|
||||||
|
/// </summary>
|
||||||
|
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).
|
||||||
|
/// </summary>
|
||||||
|
public abstract double RelativeScale(Point point);
|
||||||
|
}
|
||||||
|
}
|
||||||
72
MapControl/MapViewTransform.cs
Normal file
72
MapControl/MapViewTransform.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
110
MapControl/MercatorTransform.cs
Normal file
110
MapControl/MercatorTransform.cs
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Transforms latitude and longitude values in degrees to cartesian coordinates
|
||||||
|
/// according to the Mercator transform.
|
||||||
|
/// </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)
|
||||||
|
{
|
||||||
|
if (point.Y <= -90d)
|
||||||
|
{
|
||||||
|
return double.NegativeInfinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (point.Y >= 90d)
|
||||||
|
{
|
||||||
|
return double.PositiveInfinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1d / Math.Cos(point.Y * Math.PI / 180d);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool TryTransform(Point point, out Point result)
|
||||||
|
{
|
||||||
|
result = point;
|
||||||
|
|
||||||
|
if (point.Y <= -90d)
|
||||||
|
{
|
||||||
|
result.Y = double.NegativeInfinity;
|
||||||
|
}
|
||||||
|
else if (point.Y >= 90d)
|
||||||
|
{
|
||||||
|
result.Y = double.PositiveInfinity;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double lat = point.Y * Math.PI / 180d;
|
||||||
|
result.Y = (Math.Log(Math.Tan(lat) + 1d / Math.Cos(lat))) / 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 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
MapControl/Properties/AssemblyInfo.cs
Normal file
40
MapControl/Properties/AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
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: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||||
|
[assembly: AssemblyProduct("MapControl")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © Clemens Fischer 2012")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("81bb1cbe-eb23-47d4-a79d-71f425876d02")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
|
|
||||||
|
// ResourceDictionary locations for default control styles
|
||||||
|
[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
|
||||||
60
MapControl/Themes/Generic.xaml
Normal file
60
MapControl/Themes/Generic.xaml
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:map="clr-namespace:MapControl">
|
||||||
|
<Style x:Key="{x:Type map:MapItemsControl}" TargetType="{x:Type map:MapItemsControl}">
|
||||||
|
<Setter Property="ItemsPanel">
|
||||||
|
<Setter.Value>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<map:MapPanel/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
<Style x:Key="{x:Type map:MapItem}" TargetType="{x:Type map:MapItem}">
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||||
|
<Setter Property="Padding" Value="3"/>
|
||||||
|
<Setter Property="Background" Value="White"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type map:MapItem}">
|
||||||
|
<Grid>
|
||||||
|
<VisualStateManager.VisualStateGroups>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal"/>
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Opacity" Duration="0" To=".55"/>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="MouseOver">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetName="mouseOverRect" Storyboard.TargetProperty="Opacity" Duration="0" To=".35"/>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
<VisualStateGroup x:Name="SelectionStates">
|
||||||
|
<VisualState x:Name="Unselected"/>
|
||||||
|
<VisualState x:Name="Selected"/>
|
||||||
|
</VisualStateGroup>
|
||||||
|
<VisualStateGroup x:Name="CurrentStates">
|
||||||
|
<VisualState x:Name="NonCurrent"/>
|
||||||
|
<VisualState x:Name="Current"/>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateManager.VisualStateGroups>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Path Grid.Row="1" Margin="0,-1,0,0" Fill="{TemplateBinding Background}" Data="M 0,0 L 0,16 16,0"/>
|
||||||
|
<Rectangle Fill="{TemplateBinding Background}"/>
|
||||||
|
<ContentPresenter x:Name="contentPresenter" Margin="{TemplateBinding Padding}"
|
||||||
|
Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
|
||||||
|
<Rectangle x:Name="mouseOverRect" Margin="{TemplateBinding Padding}" Fill="White" Opacity="0"/>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
59
MapControl/Tile.cs
Normal file
59
MapControl/Tile.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Animation;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
public enum TileLoadState { NotLoaded, Loading, Loaded };
|
||||||
|
|
||||||
|
internal class Tile
|
||||||
|
{
|
||||||
|
private static readonly DoubleAnimation opacityAnimation = new DoubleAnimation(0d, 1d, TimeSpan.FromSeconds(0.5), FillBehavior.Stop);
|
||||||
|
|
||||||
|
public readonly int ZoomLevel;
|
||||||
|
public readonly int X;
|
||||||
|
public readonly int Y;
|
||||||
|
public readonly Uri Uri;
|
||||||
|
public readonly ImageBrush Brush = new ImageBrush();
|
||||||
|
|
||||||
|
public Tile(TileSource tileSource, int zoomLevel, int x, int y)
|
||||||
|
{
|
||||||
|
ZoomLevel = zoomLevel;
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Uri = tileSource.GetUri(XIndex, Y, ZoomLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TileLoadState LoadState { get; set; }
|
||||||
|
|
||||||
|
public int XIndex
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int numTiles = 1 << ZoomLevel;
|
||||||
|
return ((X % numTiles) + numTiles) % numTiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageSource Image
|
||||||
|
{
|
||||||
|
get { return Brush.ImageSource; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Brush.ImageSource == null)
|
||||||
|
{
|
||||||
|
Brush.BeginAnimation(ImageBrush.OpacityProperty, opacityAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
Brush.ImageSource = value;
|
||||||
|
LoadState = TileLoadState.Loaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format("{0}.{1}.{2}", ZoomLevel, X, Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
215
MapControl/TileContainer.cs
Normal file
215
MapControl/TileContainer.cs
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
internal class TileContainer : ContainerVisual
|
||||||
|
{
|
||||||
|
private const double maxScaledTileSize = 400d; // scaled tile size 200..400 units
|
||||||
|
private static double zoomLevelSwitchOffset = Math.Log(maxScaledTileSize / 256d, 2d);
|
||||||
|
|
||||||
|
private Size size;
|
||||||
|
private Point origin;
|
||||||
|
private Vector offset;
|
||||||
|
private double rotation;
|
||||||
|
private double zoomLevel;
|
||||||
|
private int tileZoomLevel;
|
||||||
|
private Int32Rect tileGrid;
|
||||||
|
private TileLayerCollection tileLayers;
|
||||||
|
private readonly DispatcherTimer updateTimer;
|
||||||
|
private readonly MatrixTransform viewTransform = new MatrixTransform();
|
||||||
|
|
||||||
|
public TileContainer()
|
||||||
|
{
|
||||||
|
updateTimer = new DispatcherTimer(TimeSpan.FromSeconds(0.5), DispatcherPriority.Background, UpdateTiles, Dispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TileLayerCollection TileLayers
|
||||||
|
{
|
||||||
|
get { return tileLayers; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (tileLayers != null)
|
||||||
|
{
|
||||||
|
tileLayers.CollectionChanged -= TileLayersChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
tileLayers = value;
|
||||||
|
ClearChildren();
|
||||||
|
|
||||||
|
if (tileLayers != null)
|
||||||
|
{
|
||||||
|
tileLayers.CollectionChanged += TileLayersChanged;
|
||||||
|
AddChildren(0, tileLayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
((Map)VisualParent).OnTileLayersChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transform ViewTransform
|
||||||
|
{
|
||||||
|
get { return viewTransform; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public double SetTransform(double mapZoomLevel, double mapRotation, Point mapOrigin, Point viewOrigin, Size viewSize)
|
||||||
|
{
|
||||||
|
zoomLevel = mapZoomLevel;
|
||||||
|
rotation = mapRotation;
|
||||||
|
size = viewSize;
|
||||||
|
origin = viewOrigin;
|
||||||
|
|
||||||
|
double scale = Math.Pow(2d, zoomLevel) * 256d / 360d;
|
||||||
|
offset.X = origin.X - (180d + mapOrigin.X) * scale;
|
||||||
|
offset.Y = origin.Y - (180d - mapOrigin.Y) * scale;
|
||||||
|
|
||||||
|
Matrix transform = new Matrix(1d, 0d, 0d, -1d, 180d, 180d);
|
||||||
|
transform.Scale(scale, scale);
|
||||||
|
transform.Translate(offset.X, offset.Y);
|
||||||
|
transform.RotateAt(rotation, origin.X, origin.Y);
|
||||||
|
viewTransform.Matrix = transform;
|
||||||
|
|
||||||
|
transform = GetVisualTransform();
|
||||||
|
|
||||||
|
if (tileLayers != null)
|
||||||
|
{
|
||||||
|
foreach (TileLayer tileLayer in tileLayers)
|
||||||
|
{
|
||||||
|
tileLayer.TransformMatrix = transform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimer.IsEnabled = true;
|
||||||
|
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Matrix GetVisualTransform()
|
||||||
|
{
|
||||||
|
// Calculates the transform matrix that enables rendering of 256x256 tile rectangles in
|
||||||
|
// TileLayer.UpdateTiles with origin at tileGrid.X and tileGrid.Y to minimize rounding errors.
|
||||||
|
|
||||||
|
double scale = Math.Pow(2d, zoomLevel - tileZoomLevel);
|
||||||
|
Matrix transform = new Matrix(1d, 0d, 0d, 1d, tileGrid.X * 256d, tileGrid.Y * 256d);
|
||||||
|
transform.Scale(scale, scale);
|
||||||
|
transform.Translate(offset.X, offset.Y);
|
||||||
|
transform.RotateAt(rotation, origin.X, origin.Y);
|
||||||
|
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTiles(object sender, EventArgs eventArgs)
|
||||||
|
{
|
||||||
|
updateTimer.IsEnabled = false;
|
||||||
|
|
||||||
|
int zoom = (int)Math.Floor(zoomLevel + 1d - zoomLevelSwitchOffset);
|
||||||
|
int numTiles = 1 << zoom;
|
||||||
|
double mapToTileScale = (double)numTiles / 360d;
|
||||||
|
Matrix transform = viewTransform.Matrix;
|
||||||
|
transform.Invert(); // view to map coordinates
|
||||||
|
transform.Translate(180d, -180d);
|
||||||
|
transform.Scale(mapToTileScale, -mapToTileScale); // map coordinates to tile indices
|
||||||
|
|
||||||
|
// tile indices of visible rectangle
|
||||||
|
Point p1 = transform.Transform(new Point(0d, 0d));
|
||||||
|
Point p2 = transform.Transform(new Point(size.Width, 0d));
|
||||||
|
Point p3 = transform.Transform(new Point(0d, size.Height));
|
||||||
|
Point p4 = transform.Transform(new Point(size.Width, size.Height));
|
||||||
|
|
||||||
|
double left = Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X)));
|
||||||
|
double right = Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X)));
|
||||||
|
double top = Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y)));
|
||||||
|
double bottom = Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y)));
|
||||||
|
|
||||||
|
// index ranges of visible tiles
|
||||||
|
int x1 = (int)Math.Floor(left);
|
||||||
|
int x2 = (int)Math.Floor(right);
|
||||||
|
int y1 = Math.Max((int)Math.Floor(top), 0);
|
||||||
|
int y2 = Math.Min((int)Math.Floor(bottom), numTiles - 1);
|
||||||
|
Int32Rect grid = new Int32Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
|
||||||
|
|
||||||
|
if (tileZoomLevel != zoom || tileGrid != grid)
|
||||||
|
{
|
||||||
|
tileZoomLevel = zoom;
|
||||||
|
tileGrid = grid;
|
||||||
|
transform = GetVisualTransform();
|
||||||
|
|
||||||
|
if (tileLayers != null)
|
||||||
|
{
|
||||||
|
foreach (TileLayer tileLayer in tileLayers)
|
||||||
|
{
|
||||||
|
tileLayer.TransformMatrix = transform;
|
||||||
|
tileLayer.UpdateTiles(tileZoomLevel, tileGrid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TileLayersChanged(object sender, NotifyCollectionChangedEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
switch (eventArgs.Action)
|
||||||
|
{
|
||||||
|
case NotifyCollectionChangedAction.Add:
|
||||||
|
AddChildren(eventArgs.NewStartingIndex, eventArgs.NewItems);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotifyCollectionChangedAction.Remove:
|
||||||
|
RemoveChildren(eventArgs.OldStartingIndex, eventArgs.OldItems);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotifyCollectionChangedAction.Move:
|
||||||
|
case NotifyCollectionChangedAction.Replace:
|
||||||
|
RemoveChildren(eventArgs.OldStartingIndex, eventArgs.OldItems);
|
||||||
|
AddChildren(eventArgs.NewStartingIndex, eventArgs.NewItems);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotifyCollectionChangedAction.Reset:
|
||||||
|
ClearChildren();
|
||||||
|
if (eventArgs.NewItems != null)
|
||||||
|
{
|
||||||
|
AddChildren(0, eventArgs.NewItems);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
((Map)VisualParent).OnTileLayersChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddChildren(int index, IList layers)
|
||||||
|
{
|
||||||
|
Matrix transform = GetVisualTransform();
|
||||||
|
|
||||||
|
foreach (TileLayer tileLayer in layers)
|
||||||
|
{
|
||||||
|
Children.Insert(index++, tileLayer);
|
||||||
|
tileLayer.TransformMatrix = transform;
|
||||||
|
tileLayer.UpdateTiles(tileZoomLevel, tileGrid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveChildren(int index, IList layers)
|
||||||
|
{
|
||||||
|
foreach (TileLayer tileLayer in layers)
|
||||||
|
{
|
||||||
|
tileLayer.ClearTiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
Children.RemoveRange(index, layers.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearChildren()
|
||||||
|
{
|
||||||
|
foreach (TileLayer tileLayer in Children)
|
||||||
|
{
|
||||||
|
tileLayer.ClearTiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
Children.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
263
MapControl/TileImageLoader.cs
Normal file
263
MapControl/TileImageLoader.cs
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
public class TileImageLoader : DispatcherObject
|
||||||
|
{
|
||||||
|
public static string TileCacheDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapCache");
|
||||||
|
public static TimeSpan TileCacheExpiryAge = TimeSpan.FromDays(1);
|
||||||
|
|
||||||
|
private readonly Queue<Tile> pendingTiles = new Queue<Tile>();
|
||||||
|
private int numDownloads;
|
||||||
|
|
||||||
|
internal int MaxDownloads;
|
||||||
|
internal string TileLayerName;
|
||||||
|
|
||||||
|
internal int TilesPending
|
||||||
|
{
|
||||||
|
get { return pendingTiles.Count; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void BeginDownloadTiles(ICollection<Tile> tiles)
|
||||||
|
{
|
||||||
|
ThreadPool.QueueUserWorkItem(BeginDownloadTiles, new List<Tile>(tiles.Reverse().Where(t => t.LoadState == TileLoadState.NotLoaded)));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void EndDownloadTiles()
|
||||||
|
{
|
||||||
|
lock (pendingTiles)
|
||||||
|
{
|
||||||
|
pendingTiles.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginDownloadTiles(object newTilesList)
|
||||||
|
{
|
||||||
|
List<Tile> newTiles = (List<Tile>)newTilesList;
|
||||||
|
|
||||||
|
lock (pendingTiles)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(TileCacheDirectory) &&
|
||||||
|
!string.IsNullOrEmpty(TileLayerName))
|
||||||
|
{
|
||||||
|
List<Tile> expiredTiles = new List<Tile>(newTiles.Count);
|
||||||
|
|
||||||
|
newTiles.ForEach(tile =>
|
||||||
|
{
|
||||||
|
bool cacheExpired;
|
||||||
|
ImageSource image = GetCachedImage(tile, out cacheExpired);
|
||||||
|
|
||||||
|
if (image != null)
|
||||||
|
{
|
||||||
|
Dispatcher.BeginInvoke((Action)(() => tile.Image = image));
|
||||||
|
|
||||||
|
if (cacheExpired)
|
||||||
|
{
|
||||||
|
expiredTiles.Add(tile); // enqueue later
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pendingTiles.Enqueue(tile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expiredTiles.ForEach(tile => pendingTiles.Enqueue(tile));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newTiles.ForEach(tile => pendingTiles.Enqueue(tile));
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadNextTiles(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DownloadNextTiles(object o)
|
||||||
|
{
|
||||||
|
while (pendingTiles.Count > 0 && numDownloads < MaxDownloads)
|
||||||
|
{
|
||||||
|
Tile tile = pendingTiles.Dequeue();
|
||||||
|
tile.LoadState = TileLoadState.Loading;
|
||||||
|
numDownloads++;
|
||||||
|
|
||||||
|
ThreadPool.QueueUserWorkItem(DownloadTile, tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DownloadTile(object t)
|
||||||
|
{
|
||||||
|
Tile tile = (Tile)t;
|
||||||
|
ImageSource image = DownloadImage(tile);
|
||||||
|
|
||||||
|
Dispatcher.BeginInvoke((Action)(() => tile.Image = image));
|
||||||
|
|
||||||
|
lock (pendingTiles)
|
||||||
|
{
|
||||||
|
numDownloads--;
|
||||||
|
DownloadNextTiles(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageSource GetCachedImage(Tile tile, out bool expired)
|
||||||
|
{
|
||||||
|
string tileDir = TileDirectory(tile);
|
||||||
|
ImageSource image = null;
|
||||||
|
expired = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Directory.Exists(tileDir))
|
||||||
|
{
|
||||||
|
string[] tilePath = Directory.GetFiles(tileDir, string.Format("{0}.*", tile.Y));
|
||||||
|
|
||||||
|
if (tilePath.Length > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (Stream fileStream = File.OpenRead(tilePath[0]))
|
||||||
|
{
|
||||||
|
image = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
expired = File.GetLastWriteTime(tilePath[0]) + TileCacheExpiryAge <= DateTime.Now;
|
||||||
|
|
||||||
|
TraceInformation(expired ? "{0} - Cache Expired" : "{0} - Cached", tile.Uri);
|
||||||
|
}
|
||||||
|
catch (Exception exc)
|
||||||
|
{
|
||||||
|
TraceWarning("{0} - {1}", tilePath[0], exc.Message);
|
||||||
|
File.Delete(tilePath[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exc)
|
||||||
|
{
|
||||||
|
TraceWarning("{0} - {1}", tileDir, exc.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageSource DownloadImage(Tile tile)
|
||||||
|
{
|
||||||
|
ImageSource image = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TraceInformation("{0} - Requesting", tile.Uri);
|
||||||
|
|
||||||
|
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(tile.Uri);
|
||||||
|
webRequest.UserAgent = typeof(TileImageLoader).ToString();
|
||||||
|
webRequest.KeepAlive = true;
|
||||||
|
|
||||||
|
using (HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse())
|
||||||
|
{
|
||||||
|
using (Stream responseStream = response.GetResponseStream())
|
||||||
|
{
|
||||||
|
using (Stream memoryStream = new MemoryStream((int)response.ContentLength))
|
||||||
|
{
|
||||||
|
responseStream.CopyTo(memoryStream);
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
|
||||||
|
BitmapDecoder decoder = BitmapDecoder.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||||
|
image = decoder.Frames[0];
|
||||||
|
|
||||||
|
string tilePath;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(TileCacheDirectory) &&
|
||||||
|
!string.IsNullOrEmpty(TileLayerName) &&
|
||||||
|
(tilePath = TilePath(tile, decoder)) != null)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(tilePath));
|
||||||
|
|
||||||
|
using (Stream fileStream = File.OpenWrite(tilePath))
|
||||||
|
{
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
memoryStream.CopyTo(fileStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceInformation("{0} - Completed", tile.Uri);
|
||||||
|
}
|
||||||
|
catch (WebException exc)
|
||||||
|
{
|
||||||
|
if (exc.Status == WebExceptionStatus.ProtocolError)
|
||||||
|
{
|
||||||
|
TraceInformation("{0} - {1}", tile.Uri, ((HttpWebResponse)exc.Response).StatusCode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TraceWarning("{0} - {1}", tile.Uri, exc.Status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exc)
|
||||||
|
{
|
||||||
|
TraceWarning("{0} - {1}", tile.Uri, exc.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string TileDirectory(Tile tile)
|
||||||
|
{
|
||||||
|
return Path.Combine(TileCacheDirectory, TileLayerName, tile.ZoomLevel.ToString(), tile.XIndex.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private string TilePath(Tile tile, BitmapDecoder decoder)
|
||||||
|
{
|
||||||
|
string extension;
|
||||||
|
|
||||||
|
if (decoder is PngBitmapDecoder)
|
||||||
|
{
|
||||||
|
extension = "png";
|
||||||
|
}
|
||||||
|
else if (decoder is JpegBitmapDecoder)
|
||||||
|
{
|
||||||
|
extension = "jpg";
|
||||||
|
}
|
||||||
|
else if (decoder is BmpBitmapDecoder)
|
||||||
|
{
|
||||||
|
extension = "bmp";
|
||||||
|
}
|
||||||
|
else if (decoder is GifBitmapDecoder)
|
||||||
|
{
|
||||||
|
extension = "gif";
|
||||||
|
}
|
||||||
|
else if (decoder is TiffBitmapDecoder)
|
||||||
|
{
|
||||||
|
extension = "tif";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Path.Combine(TileDirectory(tile), string.Format("{0}.{1}", tile.Y, extension));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TraceWarning(string format, params object[] args)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Trace.TraceWarning("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TraceInformation(string format, params object[] args)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
System.Diagnostics.Trace.TraceInformation("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
159
MapControl/TileLayer.cs
Normal file
159
MapControl/TileLayer.cs
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Markup;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
[ContentProperty("TileSource")]
|
||||||
|
public class TileLayer : DrawingVisual
|
||||||
|
{
|
||||||
|
private readonly TileImageLoader tileImageLoader = new TileImageLoader();
|
||||||
|
private readonly List<Tile> tiles = new List<Tile>();
|
||||||
|
private string description = string.Empty;
|
||||||
|
private Int32Rect grid;
|
||||||
|
private int zoomLevel;
|
||||||
|
|
||||||
|
public TileLayer()
|
||||||
|
{
|
||||||
|
VisualEdgeMode = EdgeMode.Aliased;
|
||||||
|
VisualTransform = new MatrixTransform();
|
||||||
|
MinZoomLevel = 1;
|
||||||
|
MaxZoomLevel = 18;
|
||||||
|
MaxDownloads = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TileSource TileSource { get; set; }
|
||||||
|
public bool HasDarkBackground { get; set; }
|
||||||
|
public int MinZoomLevel { get; set; }
|
||||||
|
public int MaxZoomLevel { get; set; }
|
||||||
|
|
||||||
|
public int MaxDownloads
|
||||||
|
{
|
||||||
|
get { return tileImageLoader.MaxDownloads; }
|
||||||
|
set { tileImageLoader.MaxDownloads = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return tileImageLoader.TileLayerName; }
|
||||||
|
set { tileImageLoader.TileLayerName = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get { return description; }
|
||||||
|
set { description = value.Replace("{y}", DateTime.Now.Year.ToString()); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix TransformMatrix
|
||||||
|
{
|
||||||
|
get { return ((MatrixTransform)VisualTransform).Matrix; }
|
||||||
|
set { ((MatrixTransform)VisualTransform).Matrix = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateTiles(int zoomLevel, Int32Rect grid)
|
||||||
|
{
|
||||||
|
this.grid = grid;
|
||||||
|
this.zoomLevel = zoomLevel;
|
||||||
|
|
||||||
|
tileImageLoader.EndDownloadTiles();
|
||||||
|
|
||||||
|
if (VisualParent != null && TileSource != null)
|
||||||
|
{
|
||||||
|
SelectTiles();
|
||||||
|
RenderTiles();
|
||||||
|
|
||||||
|
tileImageLoader.BeginDownloadTiles(tiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearTiles()
|
||||||
|
{
|
||||||
|
tiles.Clear();
|
||||||
|
tileImageLoader.EndDownloadTiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Int32Rect GetTileGrid(int tileZoomLevel)
|
||||||
|
{
|
||||||
|
int tileSize = 1 << (zoomLevel - tileZoomLevel);
|
||||||
|
int max = (1 << tileZoomLevel) - 1;
|
||||||
|
int x1 = grid.X / tileSize - 1;
|
||||||
|
int x2 = (grid.X + grid.Width - 1) / tileSize + 1;
|
||||||
|
int y1 = Math.Max(0, grid.Y / tileSize - 1);
|
||||||
|
int y2 = Math.Min(max, (grid.Y + grid.Height - 1) / tileSize + 1);
|
||||||
|
|
||||||
|
return new Int32Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectTiles()
|
||||||
|
{
|
||||||
|
TileContainer tileContainer = VisualParent as TileContainer;
|
||||||
|
int maxZoom = Math.Min(zoomLevel, MaxZoomLevel);
|
||||||
|
int minZoom = maxZoom;
|
||||||
|
|
||||||
|
if (tileContainer != null && tileContainer.TileLayers.IndexOf(this) == 0)
|
||||||
|
{
|
||||||
|
minZoom = MinZoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles.RemoveAll(t =>
|
||||||
|
{
|
||||||
|
if (t.ZoomLevel > maxZoom || t.ZoomLevel < minZoom)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Int32Rect tileGrid = GetTileGrid(t.ZoomLevel);
|
||||||
|
return t.X < tileGrid.X || t.X >= tileGrid.X + tileGrid.Width || t.Y < tileGrid.Y || t.Y >= tileGrid.Y + tileGrid.Height;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int tileZoomLevel = minZoom; tileZoomLevel <= maxZoom; tileZoomLevel++)
|
||||||
|
{
|
||||||
|
Int32Rect tileGrid = GetTileGrid(tileZoomLevel);
|
||||||
|
|
||||||
|
for (int y = tileGrid.Y; y < tileGrid.Y + tileGrid.Height; y++)
|
||||||
|
{
|
||||||
|
for (int x = tileGrid.X; x < tileGrid.X + tileGrid.Width; x++)
|
||||||
|
{
|
||||||
|
if (tiles.Find(t => t.ZoomLevel == tileZoomLevel && t.X == x && t.Y == y) == null)
|
||||||
|
{
|
||||||
|
Tile tile = new Tile(TileSource, tileZoomLevel, x, y);
|
||||||
|
Tile equivalent = tiles.Find(t => t.Image != null && t.ZoomLevel == tile.ZoomLevel && t.XIndex == tile.XIndex && t.Y == tile.Y);
|
||||||
|
|
||||||
|
if (equivalent != null)
|
||||||
|
{
|
||||||
|
tile.Image = equivalent.Image;
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles.Add(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderTiles()
|
||||||
|
{
|
||||||
|
using (DrawingContext drawingContext = RenderOpen())
|
||||||
|
{
|
||||||
|
tiles.ForEach(tile =>
|
||||||
|
{
|
||||||
|
int tileSize = 256 << (zoomLevel - tile.ZoomLevel);
|
||||||
|
Rect tileRect = new Rect(tileSize * tile.X - 256 * grid.X, tileSize * tile.Y - 256 * grid.Y, tileSize, tileSize);
|
||||||
|
|
||||||
|
drawingContext.DrawRectangle(tile.Brush, null, tileRect);
|
||||||
|
|
||||||
|
//if (tile.ZoomLevel == zoomLevel)
|
||||||
|
// drawingContext.DrawText(new FormattedText(tile.ToString(), System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface("Segoe UI"), 14, Brushes.Black), tileRect.TopLeft);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
MapControl/TileLayerCollection.cs
Normal file
26
MapControl/TileLayerCollection.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
public class TileLayerCollection : ObservableCollection<TileLayer>
|
||||||
|
{
|
||||||
|
private string name;
|
||||||
|
|
||||||
|
public TileLayerCollection()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TileLayerCollection(TileLayer tileLayer)
|
||||||
|
{
|
||||||
|
Add(tileLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return !string.IsNullOrEmpty(name) ? name : (Count > 0 ? this[0].Name : string.Empty); }
|
||||||
|
set { name = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
163
MapControl/TileSource.cs
Normal file
163
MapControl/TileSource.cs
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace MapControl
|
||||||
|
{
|
||||||
|
[TypeConverter(typeof(TileSourceTypeConverter))]
|
||||||
|
public class TileSource
|
||||||
|
{
|
||||||
|
public string UriFormat { get; set; }
|
||||||
|
|
||||||
|
public virtual Uri GetUri(int x, int y, int zoomLevel)
|
||||||
|
{
|
||||||
|
return new Uri(UriFormat.
|
||||||
|
Replace("{x}", x.ToString()).
|
||||||
|
Replace("{y}", y.ToString()).
|
||||||
|
Replace("{z}", zoomLevel.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OpenStreetMapTileSource : TileSource
|
||||||
|
{
|
||||||
|
private static string[] hostChars = { "a", "b", "c" };
|
||||||
|
private int hostChar = -1;
|
||||||
|
|
||||||
|
public override Uri GetUri(int x, int y, int zoomLevel)
|
||||||
|
{
|
||||||
|
hostChar = (hostChar + 1) % 3;
|
||||||
|
|
||||||
|
return new Uri(UriFormat.
|
||||||
|
Replace("{c}", hostChars[hostChar]).
|
||||||
|
Replace("{x}", x.ToString()).
|
||||||
|
Replace("{y}", y.ToString()).
|
||||||
|
Replace("{z}", zoomLevel.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GoogleMapsTileSource : TileSource
|
||||||
|
{
|
||||||
|
private int hostIndex = -1;
|
||||||
|
|
||||||
|
public override Uri GetUri(int x, int y, int zoomLevel)
|
||||||
|
{
|
||||||
|
hostIndex = (hostIndex + 1) % 4;
|
||||||
|
|
||||||
|
return new Uri(UriFormat.
|
||||||
|
Replace("{i}", hostIndex.ToString()).
|
||||||
|
Replace("{x}", x.ToString()).
|
||||||
|
Replace("{y}", y.ToString()).
|
||||||
|
Replace("{z}", zoomLevel.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MapQuestTileSource : TileSource
|
||||||
|
{
|
||||||
|
private int hostNumber;
|
||||||
|
|
||||||
|
public override Uri GetUri(int x, int y, int zoomLevel)
|
||||||
|
{
|
||||||
|
hostNumber = (hostNumber % 4) + 1;
|
||||||
|
|
||||||
|
return new Uri(UriFormat.
|
||||||
|
Replace("{n}", hostNumber.ToString()).
|
||||||
|
Replace("{x}", x.ToString()).
|
||||||
|
Replace("{y}", y.ToString()).
|
||||||
|
Replace("{z}", zoomLevel.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class QuadKeyTileSource : TileSource
|
||||||
|
{
|
||||||
|
public override Uri GetUri(int x, int y, int zoomLevel)
|
||||||
|
{
|
||||||
|
StringBuilder key = new StringBuilder { Length = zoomLevel };
|
||||||
|
|
||||||
|
for (int z = zoomLevel - 1; z >= 0; z--, x /= 2, y /= 2)
|
||||||
|
{
|
||||||
|
key[z] = (char)('0' + 2 * (y % 2) + (x % 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uri(UriFormat.
|
||||||
|
Replace("{i}", key.ToString(key.Length - 1, 1)).
|
||||||
|
Replace("{q}", key.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BoundingBoxTileSource : TileSource
|
||||||
|
{
|
||||||
|
public override Uri GetUri(int x, int y, int zoomLevel)
|
||||||
|
{
|
||||||
|
InverseMercatorTransform t = new InverseMercatorTransform();
|
||||||
|
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));
|
||||||
|
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TileSourceTypeConverter : TypeConverter
|
||||||
|
{
|
||||||
|
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||||
|
{
|
||||||
|
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||||
|
{
|
||||||
|
string uriFormat = value as string;
|
||||||
|
|
||||||
|
if (uriFormat != null)
|
||||||
|
{
|
||||||
|
TileSource tileSource = null;
|
||||||
|
|
||||||
|
if (uriFormat.Contains("{x}") && uriFormat.Contains("{y}") && uriFormat.Contains("{z}"))
|
||||||
|
{
|
||||||
|
if (uriFormat.Contains("{c}"))
|
||||||
|
{
|
||||||
|
tileSource = new OpenStreetMapTileSource();
|
||||||
|
}
|
||||||
|
else if (uriFormat.Contains("{i}"))
|
||||||
|
{
|
||||||
|
tileSource = new GoogleMapsTileSource();
|
||||||
|
}
|
||||||
|
else if (uriFormat.Contains("{n}"))
|
||||||
|
{
|
||||||
|
tileSource = new MapQuestTileSource();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tileSource = new TileSource();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (uriFormat.Contains("{q}"))
|
||||||
|
{
|
||||||
|
tileSource = new QuadKeyTileSource();
|
||||||
|
}
|
||||||
|
else if (uriFormat.Contains("{w}") && uriFormat.Contains("{s}") && uriFormat.Contains("{e}") && uriFormat.Contains("{n}"))
|
||||||
|
{
|
||||||
|
tileSource = new BoundingBoxTileSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tileSource != null)
|
||||||
|
{
|
||||||
|
tileSource.UriFormat = uriFormat;
|
||||||
|
return tileSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.ConvertFrom(context, culture, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue