2012-05-04 12:52:20 +02:00
|
|
|
|
// WPF MapControl - http://wpfmapcontrol.codeplex.com/
|
|
|
|
|
|
// Copyright © 2012 Clemens Fischer
|
|
|
|
|
|
// Licensed under the Microsoft Public License (Ms-PL)
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
2012-04-25 22:02:53 +02:00
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Windows;
|
|
|
|
|
|
using System.Windows.Controls;
|
|
|
|
|
|
using System.Windows.Media;
|
|
|
|
|
|
using System.Windows.Shapes;
|
|
|
|
|
|
|
|
|
|
|
|
namespace MapControl
|
|
|
|
|
|
{
|
2012-05-04 12:52:20 +02:00
|
|
|
|
/// <summary>
|
2012-07-04 17:19:48 +02:00
|
|
|
|
/// Draws a graticule overlay. The minimum spacing in pixels between adjacent
|
|
|
|
|
|
/// graticule lines is specified by the MinLineSpacing property.
|
2012-05-04 12:52:20 +02:00
|
|
|
|
/// </summary>
|
2012-04-25 22:02:53 +02:00
|
|
|
|
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));
|
|
|
|
|
|
|
2012-07-04 17:19:48 +02:00
|
|
|
|
public static readonly DependencyProperty MinLineSpacingProperty = DependencyProperty.Register(
|
|
|
|
|
|
"MinLineSpacing", typeof(double), typeof(MapGraticule), new FrameworkPropertyMetadata(100d));
|
2012-04-25 22:02:53 +02:00
|
|
|
|
|
2012-05-04 12:52:20 +02:00
|
|
|
|
public static double[] Spacings =
|
2012-04-25 22:02:53 +02:00
|
|
|
|
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); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-07-04 17:19:48 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Minimum spacing in pixels between adjacent graticule lines.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public double MinLineSpacing
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2012-07-04 17:19:48 +02:00
|
|
|
|
get { return (double)GetValue(MinLineSpacingProperty); }
|
|
|
|
|
|
set { SetValue(MinLineSpacingProperty, value); }
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override int VisualChildrenCount
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return 1; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override Visual GetVisualChild(int index)
|
|
|
|
|
|
{
|
|
|
|
|
|
return visual;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-08 20:42:06 +02:00
|
|
|
|
protected override void OnViewportChanged()
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2012-08-08 20:42:06 +02:00
|
|
|
|
Map parentMap = ParentMap;
|
2012-05-04 12:52:20 +02:00
|
|
|
|
Rect bounds = parentMap.ViewportTransform.Inverse.TransformBounds(new Rect(parentMap.RenderSize));
|
|
|
|
|
|
Location loc1 = parentMap.MapTransform.TransformBack(bounds.TopLeft);
|
|
|
|
|
|
Location loc2 = parentMap.MapTransform.TransformBack(bounds.BottomRight);
|
2012-07-04 17:19:48 +02:00
|
|
|
|
double minSpacing = MinLineSpacing * 360d / (Math.Pow(2d, parentMap.ZoomLevel) * 256d);
|
2012-05-04 12:52:20 +02:00
|
|
|
|
double spacing = Spacings[Spacings.Length - 1];
|
2012-04-25 22:02:53 +02:00
|
|
|
|
|
|
|
|
|
|
if (spacing >= minSpacing)
|
|
|
|
|
|
{
|
2012-05-04 12:52:20 +02:00
|
|
|
|
spacing = Spacings.FirstOrDefault(s => s >= minSpacing);
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-05-04 12:52:20 +02:00
|
|
|
|
double latitudeStart = Math.Ceiling(loc1.Latitude / spacing) * spacing;
|
|
|
|
|
|
double longitudeStart = Math.Ceiling(loc1.Longitude / spacing) * spacing;
|
2012-04-25 22:02:53 +02:00
|
|
|
|
|
|
|
|
|
|
if (pen.Brush == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
pen.Brush = Stroke != null ? Stroke : Foreground;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
using (DrawingContext drawingContext = visual.RenderOpen())
|
|
|
|
|
|
{
|
2012-05-04 12:52:20 +02:00
|
|
|
|
for (double lat = latitudeStart; lat <= loc2.Latitude; lat += spacing)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
|
|
|
|
|
drawingContext.DrawLine(pen,
|
2012-05-04 12:52:20 +02:00
|
|
|
|
parentMap.LocationToViewportPoint(new Location(lat, loc1.Longitude)),
|
|
|
|
|
|
parentMap.LocationToViewportPoint(new Location(lat, loc2.Longitude)));
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-05-04 12:52:20 +02:00
|
|
|
|
for (double lon = longitudeStart; lon <= loc2.Longitude; lon += spacing)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
|
|
|
|
|
drawingContext.DrawLine(pen,
|
2012-05-04 12:52:20 +02:00
|
|
|
|
parentMap.LocationToViewportPoint(new Location(loc1.Latitude, lon)),
|
|
|
|
|
|
parentMap.LocationToViewportPoint(new Location(loc2.Latitude, lon)));
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-05-04 12:52:20 +02:00
|
|
|
|
for (double lat = latitudeStart; lat <= loc2.Latitude; lat += spacing)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2012-05-04 12:52:20 +02:00
|
|
|
|
for (double lon = longitudeStart; lon <= loc2.Longitude; lon += spacing)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
|
|
|
|
|
double t = StrokeThickness / 2d;
|
2012-05-04 12:52:20 +02:00
|
|
|
|
Point p = parentMap.LocationToViewportPoint(new Location(lat, lon));
|
2012-04-25 22:02:53 +02:00
|
|
|
|
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");
|
2012-05-06 11:28:47 +02:00
|
|
|
|
string lonString = CoordinateString(Location.NormalizeLongitude(lon), format, "EW");
|
2012-04-25 22:02:53 +02:00
|
|
|
|
|
|
|
|
|
|
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;
|
2012-08-08 20:42:06 +02:00
|
|
|
|
OnViewportChanged();
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|