From e52698586b48d884696361977710f18ea2e3baac Mon Sep 17 00:00:00 2001 From: ClemensF Date: Thu, 25 Oct 2012 08:42:51 +0200 Subject: [PATCH] Added classes MapOverlay and MapScale. --- MapControl/MapControl.csproj | 2 + MapControl/MapGraticule.cs | 180 +++----------- MapControl/MapOverlay.cs | 224 ++++++++++++++++++ MapControl/MapPolygon.cs | 4 +- MapControl/MapPolyline.cs | 180 ++++++++------ MapControl/MapScale.cs | 92 +++++++ SampleApps/SampleApplication/MainWindow.xaml | 3 +- .../ViewportPositionToVisibilityConverter.cs | 8 +- 8 files changed, 461 insertions(+), 232 deletions(-) create mode 100644 MapControl/MapOverlay.cs create mode 100644 MapControl/MapScale.cs diff --git a/MapControl/MapControl.csproj b/MapControl/MapControl.csproj index b141ef95..7ee5737c 100644 --- a/MapControl/MapControl.csproj +++ b/MapControl/MapControl.csproj @@ -56,6 +56,8 @@ + + diff --git a/MapControl/MapGraticule.cs b/MapControl/MapGraticule.cs index 2c6e3841..ee4aebad 100644 --- a/MapControl/MapGraticule.cs +++ b/MapControl/MapGraticule.cs @@ -5,107 +5,24 @@ using System; using System.Linq; using System.Windows; -using System.Windows.Controls; using System.Windows.Media; -using System.Windows.Shapes; namespace MapControl { /// - /// Draws a graticule overlay. The minimum spacing in pixels between adjacent - /// graticule lines is specified by the MinLineSpacing property. + /// Draws a graticule overlay. /// - public class MapGraticule : MapElement + public class MapGraticule : MapOverlay { - 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 MinLineSpacingProperty = DependencyProperty.Register( "MinLineSpacing", typeof(double), typeof(MapGraticule), new FrameworkPropertyMetadata(100d)); - public static double[] Spacings = + /// + /// Graticule line spacings in degrees. + /// + public static double[] LineSpacings = 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); } - } - /// /// Minimum spacing in pixels between adjacent graticule lines. /// @@ -115,90 +32,61 @@ namespace MapControl set { SetValue(MinLineSpacingProperty, value); } } - protected override int VisualChildrenCount - { - get { return 1; } - } - - protected override Visual GetVisualChild(int index) - { - return visual; - } - - protected override void OnViewportChanged() + protected override void OnRender(DrawingContext drawingContext) { MapBase parentMap = ParentMap; Rect bounds = parentMap.ViewportTransform.Inverse.TransformBounds(new Rect(parentMap.RenderSize)); Location loc1 = parentMap.MapTransform.TransformBack(bounds.TopLeft); Location loc2 = parentMap.MapTransform.TransformBack(bounds.BottomRight); double minSpacing = MinLineSpacing * 360d / (Math.Pow(2d, parentMap.ZoomLevel) * 256d); - double spacing = Spacings[Spacings.Length - 1]; + double spacing = LineSpacings[LineSpacings.Length - 1]; if (spacing >= minSpacing) { - spacing = Spacings.FirstOrDefault(s => s >= minSpacing); + spacing = LineSpacings.FirstOrDefault(s => s >= minSpacing); } double latitudeStart = Math.Ceiling(loc1.Latitude / spacing) * spacing; double longitudeStart = Math.Ceiling(loc1.Longitude / spacing) * spacing; - if (pen.Brush == null) + for (double lat = latitudeStart; lat <= loc2.Latitude; lat += spacing) { - pen.Brush = Stroke != null ? Stroke : Foreground; + drawingContext.DrawLine(Pen, + parentMap.LocationToViewportPoint(new Location(lat, loc1.Longitude)), + parentMap.LocationToViewportPoint(new Location(lat, loc2.Longitude))); } - using (DrawingContext drawingContext = visual.RenderOpen()) + for (double lon = longitudeStart; lon <= loc2.Longitude; lon += spacing) { + drawingContext.DrawLine(Pen, + parentMap.LocationToViewportPoint(new Location(loc1.Latitude, lon)), + parentMap.LocationToViewportPoint(new Location(loc2.Latitude, lon))); + } + + if (Foreground != null && Foreground != Brushes.Transparent) + { + string format = spacing < 1d ? "{0} {1}°{2:00}'" : "{0} {1}°"; + for (double lat = latitudeStart; lat <= loc2.Latitude; lat += spacing) { - drawingContext.DrawLine(pen, - parentMap.LocationToViewportPoint(new Location(lat, loc1.Longitude)), - parentMap.LocationToViewportPoint(new Location(lat, loc2.Longitude))); - } - - for (double lon = longitudeStart; lon <= loc2.Longitude; lon += spacing) - { - drawingContext.DrawLine(pen, - parentMap.LocationToViewportPoint(new Location(loc1.Latitude, lon)), - parentMap.LocationToViewportPoint(new Location(loc2.Latitude, lon))); - } - - if (Foreground != null && Foreground != Brushes.Transparent) - { - string format = spacing < 1d ? "{0} {1}°{2:00}'" : "{0} {1}°"; - - if (typeface == null) + for (double lon = longitudeStart; lon <= loc2.Longitude; lon += spacing) { - typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch); - } + double t = StrokeThickness / 2d; + Point p = parentMap.LocationToViewportPoint(new Location(lat, lon)); + Point latPos = new Point(p.X + t + 2d, p.Y - t - FontSize / 4d); + Point lonPos = new Point(p.X + t + 2d, p.Y + t + FontSize); + string latString = CoordinateString(lat, format, "NS"); + string lonString = CoordinateString(Location.NormalizeLongitude(lon), format, "EW"); - for (double lat = latitudeStart; lat <= loc2.Latitude; lat += spacing) - { - for (double lon = longitudeStart; lon <= loc2.Longitude; lon += spacing) - { - double t = StrokeThickness / 2d; - Point p = parentMap.LocationToViewportPoint(new Location(lat, lon)); - Point latPos = new Point(p.X + t + 2d, p.Y - t - FontSize / 4d); - Point lonPos = new Point(p.X + t + 2d, p.Y + t + FontSize); - string latString = CoordinateString(lat, format, "NS"); - string lonString = CoordinateString(Location.NormalizeLongitude(lon), 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(); - } + 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; - OnViewportChanged(); - } - private static string CoordinateString(double value, string format, string hemispheres) { char hemisphere = hemispheres[0]; diff --git a/MapControl/MapOverlay.cs b/MapControl/MapOverlay.cs new file mode 100644 index 00000000..750c6dfc --- /dev/null +++ b/MapControl/MapOverlay.cs @@ -0,0 +1,224 @@ +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace MapControl +{ + /// + /// Base class for map overlays with font, background, foreground and stroke properties. + /// + public class MapOverlay : MapElement + { + public static readonly DependencyProperty FontFamilyProperty = Control.FontFamilyProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).typeface = null)); + + public static readonly DependencyProperty FontStyleProperty = Control.FontStyleProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).typeface = null)); + + public static readonly DependencyProperty FontWeightProperty = Control.FontWeightProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).typeface = null)); + + public static readonly DependencyProperty FontStretchProperty = Control.FontStretchProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).typeface = null)); + + public static readonly DependencyProperty FontSizeProperty = Control.FontSizeProperty.AddOwner( + typeof(MapOverlay)); + + public static readonly DependencyProperty BackgroundProperty = Control.BackgroundProperty.AddOwner( + typeof(MapOverlay)); + + public static readonly DependencyProperty ForegroundProperty = Control.ForegroundProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).ForegroundChanged((Brush)e.NewValue))); + + public static readonly DependencyProperty StrokeProperty = Shape.StrokeProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).pen.Brush = (Brush)e.NewValue)); + + public static readonly DependencyProperty StrokeThicknessProperty = Shape.StrokeThicknessProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata(0.5, FrameworkPropertyMetadataOptions.AffectsMeasure, (o, e) => ((MapOverlay)o).pen.Thickness = (double)e.NewValue)); + + public static readonly DependencyProperty StrokeDashArrayProperty = Shape.StrokeDashArrayProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).pen.DashStyle = new DashStyle((DoubleCollection)e.NewValue, ((MapOverlay)o).StrokeDashOffset))); + + public static readonly DependencyProperty StrokeDashOffsetProperty = Shape.StrokeDashOffsetProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).pen.DashStyle = new DashStyle(((MapOverlay)o).StrokeDashArray, (double)e.NewValue))); + + public static readonly DependencyProperty StrokeDashCapProperty = Shape.StrokeDashCapProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).pen.DashCap = (PenLineCap)e.NewValue)); + + public static readonly DependencyProperty StrokeStartLineCapProperty = Shape.StrokeStartLineCapProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).pen.StartLineCap = (PenLineCap)e.NewValue)); + + public static readonly DependencyProperty StrokeEndLineCapProperty = Shape.StrokeEndLineCapProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).pen.EndLineCap = (PenLineCap)e.NewValue)); + + public static readonly DependencyProperty StrokeLineJoinProperty = Shape.StrokeLineJoinProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).pen.LineJoin = (PenLineJoin)e.NewValue)); + + public static readonly DependencyProperty StrokeMiterLimitProperty = Shape.StrokeMiterLimitProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata((o, e) => ((MapOverlay)o).pen.MiterLimit = (double)e.NewValue)); + + private readonly Pen pen; + private Typeface typeface; + + static MapOverlay() + { + UIElement.IsHitTestVisibleProperty.OverrideMetadata( + typeof(MapOverlay), new FrameworkPropertyMetadata(false)); + } + + public MapOverlay() + { + pen = new Pen + { + Brush = Stroke, + Thickness = StrokeThickness, + DashStyle = new DashStyle(StrokeDashArray, StrokeDashOffset), + DashCap = StrokeDashCap, + StartLineCap = StrokeStartLineCap, + EndLineCap = StrokeEndLineCap, + LineJoin = StrokeLineJoin, + MiterLimit = StrokeMiterLimit + }; + } + + 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 double FontSize + { + get { return (double)GetValue(FontSizeProperty); } + set { SetValue(FontSizeProperty, value); } + } + + public Brush Background + { + get { return (Brush)GetValue(BackgroundProperty); } + set { SetValue(BackgroundProperty, value); } + } + + public Brush Foreground + { + get { return (Brush)GetValue(ForegroundProperty); } + set { SetValue(ForegroundProperty, 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 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); } + } + + protected Pen Pen + { + get + { + if (pen.Brush == null) + { + pen.Brush = Foreground; + } + + return pen; + } + } + + protected Typeface Typeface + { + get + { + if (typeface == null) + { + typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch); + } + + return typeface; + } + } + + protected override void OnViewportChanged() + { + InvalidateVisual(); + } + + private void ForegroundChanged(Brush foreground) + { + if (Stroke == null) + { + pen.Brush = foreground; + } + } + } +} diff --git a/MapControl/MapPolygon.cs b/MapControl/MapPolygon.cs index 2c086cc4..ab855e0d 100644 --- a/MapControl/MapPolygon.cs +++ b/MapControl/MapPolygon.cs @@ -14,11 +14,11 @@ namespace MapControl public class MapPolygon : MapPolyline { public static readonly DependencyProperty FillProperty = Shape.FillProperty.AddOwner( - typeof(MapPolygon), new FrameworkPropertyMetadata((o, e) => ((MapPolygon)o).drawing.Brush = (Brush)e.NewValue)); + typeof(MapPolygon), new FrameworkPropertyMetadata((o, e) => ((MapPolygon)o).Drawing.Brush = (Brush)e.NewValue)); public MapPolygon() { - drawing.Brush = Fill; + Drawing.Brush = Fill; } public Brush Fill diff --git a/MapControl/MapPolyline.cs b/MapControl/MapPolyline.cs index 34d5245c..6535cf15 100644 --- a/MapControl/MapPolyline.cs +++ b/MapControl/MapPolyline.cs @@ -3,8 +3,10 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; +using System.Globalization; using System.Linq; using System.Windows; +using System.Windows.Data; using System.Windows.Media; using System.Windows.Shapes; @@ -13,54 +15,68 @@ namespace MapControl /// /// An open map polygon, defined by a collection of geographic locations in the Locations property. /// - public class MapPolyline : MapElement + public class MapPolyline : FrameworkElement { public static readonly DependencyProperty StrokeProperty = Shape.StrokeProperty.AddOwner( - typeof(MapPolyline), new FrameworkPropertyMetadata(Brushes.Black, (o, e) => ((MapPolyline)o).drawing.Pen.Brush = (Brush)e.NewValue)); - - public static readonly DependencyProperty StrokeDashArrayProperty = Shape.StrokeDashArrayProperty.AddOwner( - typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.DashStyle = new DashStyle((DoubleCollection)e.NewValue, ((MapPolyline)o).StrokeDashOffset))); - - public static readonly DependencyProperty StrokeDashOffsetProperty = Shape.StrokeDashOffsetProperty.AddOwner( - typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.DashStyle = new DashStyle(((MapPolyline)o).StrokeDashArray, (double)e.NewValue))); - - public static readonly DependencyProperty StrokeDashCapProperty = Shape.StrokeDashCapProperty.AddOwner( - typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.DashCap = (PenLineCap)e.NewValue)); - - public static readonly DependencyProperty StrokeStartLineCapProperty = Shape.StrokeStartLineCapProperty.AddOwner( - typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.StartLineCap = (PenLineCap)e.NewValue)); - - public static readonly DependencyProperty StrokeEndLineCapProperty = Shape.StrokeEndLineCapProperty.AddOwner( - typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.EndLineCap = (PenLineCap)e.NewValue)); - - public static readonly DependencyProperty StrokeLineJoinProperty = Shape.StrokeLineJoinProperty.AddOwner( - typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.LineJoin = (PenLineJoin)e.NewValue)); - - public static readonly DependencyProperty StrokeMiterLimitProperty = Shape.StrokeMiterLimitProperty.AddOwner( - typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.MiterLimit = (double)e.NewValue)); + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).Drawing.Pen.Brush = (Brush)e.NewValue)); public static readonly DependencyProperty StrokeThicknessProperty = Shape.StrokeThicknessProperty.AddOwner( - typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).UpdatePenThickness())); + typeof(MapPolyline)); + + public static readonly DependencyProperty StrokeDashArrayProperty = Shape.StrokeDashArrayProperty.AddOwner( + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).Drawing.Pen.DashStyle = new DashStyle((DoubleCollection)e.NewValue, ((MapPolyline)o).StrokeDashOffset))); + + public static readonly DependencyProperty StrokeDashOffsetProperty = Shape.StrokeDashOffsetProperty.AddOwner( + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).Drawing.Pen.DashStyle = new DashStyle(((MapPolyline)o).StrokeDashArray, (double)e.NewValue))); + + public static readonly DependencyProperty StrokeDashCapProperty = Shape.StrokeDashCapProperty.AddOwner( + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).Drawing.Pen.DashCap = (PenLineCap)e.NewValue)); + + public static readonly DependencyProperty StrokeStartLineCapProperty = Shape.StrokeStartLineCapProperty.AddOwner( + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).Drawing.Pen.StartLineCap = (PenLineCap)e.NewValue)); + + public static readonly DependencyProperty StrokeEndLineCapProperty = Shape.StrokeEndLineCapProperty.AddOwner( + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).Drawing.Pen.EndLineCap = (PenLineCap)e.NewValue)); + + public static readonly DependencyProperty StrokeLineJoinProperty = Shape.StrokeLineJoinProperty.AddOwner( + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).Drawing.Pen.LineJoin = (PenLineJoin)e.NewValue)); + + public static readonly DependencyProperty StrokeMiterLimitProperty = Shape.StrokeMiterLimitProperty.AddOwner( + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).Drawing.Pen.MiterLimit = (double)e.NewValue)); public static readonly DependencyProperty TransformStrokeProperty = DependencyProperty.Register( - "TransformStroke", typeof(bool), typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).UpdatePenThickness())); + "TransformStroke", typeof(bool), typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).SetPenThicknessBinding())); public static readonly DependencyProperty LocationsProperty = DependencyProperty.Register( "Locations", typeof(LocationCollection), typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).UpdateGeometry())); - protected readonly DrawingVisual visual = new DrawingVisual(); - protected readonly GeometryDrawing drawing = new GeometryDrawing(); + protected readonly GeometryDrawing Drawing = new GeometryDrawing(); + + static MapPolyline() + { + MapPanel.ParentMapPropertyKey.OverrideMetadata( + typeof(MapPolyline), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, ParentMapPropertyChanged)); + } public MapPolyline() { - drawing.Pen = new Pen(Stroke, StrokeThickness); - - using (DrawingContext drawingContext = visual.RenderOpen()) + Drawing.Pen = new Pen { - drawingContext.DrawDrawing(drawing); - } + Brush = Stroke, + Thickness = StrokeThickness, + DashStyle = new DashStyle(StrokeDashArray, StrokeDashOffset), + DashCap = StrokeDashCap, + StartLineCap = StrokeStartLineCap, + EndLineCap = StrokeEndLineCap, + LineJoin = StrokeLineJoin, + MiterLimit = StrokeMiterLimit + }; + } - Loaded += (o, e) => UpdateGeometry(); + public MapBase ParentMap + { + get { return MapPanel.GetParentMap(this); } } public Brush Stroke @@ -69,6 +85,12 @@ namespace MapControl set { SetValue(StrokeProperty, value); } } + public double StrokeThickness + { + get { return (double)GetValue(StrokeThicknessProperty); } + set { SetValue(StrokeThicknessProperty, value); } + } + public DoubleCollection StrokeDashArray { get { return (DoubleCollection)GetValue(StrokeDashArrayProperty); } @@ -111,12 +133,6 @@ namespace MapControl set { SetValue(StrokeMiterLimitProperty, value); } } - public double StrokeThickness - { - get { return (double)GetValue(StrokeThicknessProperty); } - set { SetValue(StrokeThicknessProperty, value); } - } - public bool TransformStroke { get { return (bool)GetValue(TransformStrokeProperty); } @@ -129,43 +145,19 @@ namespace MapControl set { SetValue(LocationsProperty, value); } } - public double TransformedStrokeThickness - { - get { return drawing.Pen.Thickness; } - } - public PathGeometry TransformedGeometry { - get { return drawing.Geometry as PathGeometry; } + get { return Drawing.Geometry as PathGeometry; } } - protected override int VisualChildrenCount + public double TransformedStrokeThickness { - get { return 1; } + get { return Drawing.Pen.Thickness; } } - protected override Visual GetVisualChild(int index) + protected override void OnRender(DrawingContext drawingContext) { - return visual; - } - - protected override void OnInitialized(EventArgs e) - { - base.OnInitialized(e); - - AddVisualChild(visual); - } - - protected override void OnViewportChanged() - { - double scale = 1d; - - if (TransformStroke) - { - scale = ParentMap.CenterScale * MapBase.MeterPerDegree; - } - - drawing.Pen.Thickness = scale * StrokeThickness; + drawingContext.DrawDrawing(Drawing); } protected virtual void UpdateGeometry() @@ -177,20 +169,11 @@ namespace MapControl { if (ParentMap != null && Locations != null && Locations.Count > 0) { - drawing.Geometry = CreateGeometry(Locations, closed); - OnViewportChanged(); + Drawing.Geometry = CreateGeometry(Locations, closed); } else { - drawing.Geometry = null; - } - } - - private void UpdatePenThickness() - { - if (ParentMap != null) - { - OnViewportChanged(); + Drawing.Geometry = null; } } @@ -213,5 +196,44 @@ namespace MapControl return geometry; } + + private void SetPenThicknessBinding() + { + BindingBase binding = new Binding { Source = this, Path = new PropertyPath(MapPolyline.StrokeThicknessProperty)}; + + if (TransformStroke && ParentMap != null) + { + MultiBinding multiBinding = new MultiBinding { Converter = new PenThicknessConverter() }; + multiBinding.Bindings.Add(binding); + multiBinding.Bindings.Add(new Binding { Source = ParentMap, Path = new PropertyPath(MapBase.CenterScaleProperty)}); + binding = multiBinding; + } + + BindingOperations.SetBinding(Drawing.Pen, Pen.ThicknessProperty, binding); + } + + private static void ParentMapPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + MapPolyline polyline = obj as MapPolyline; + + if (polyline != null) + { + polyline.UpdateGeometry(); + polyline.SetPenThicknessBinding(); + } + } + + private class PenThicknessConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + return (double)values[0] * (double)values[1] * MapBase.MeterPerDegree; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } } } diff --git a/MapControl/MapScale.cs b/MapControl/MapScale.cs new file mode 100644 index 00000000..2b070fc6 --- /dev/null +++ b/MapControl/MapScale.cs @@ -0,0 +1,92 @@ +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace MapControl +{ + /// + /// Draws a map scale overlay. + /// + public class MapScale : MapOverlay + { + public static readonly DependencyProperty PaddingProperty = Control.PaddingProperty.AddOwner( + typeof(MapOverlay), new FrameworkPropertyMetadata(new Thickness(2d))); + + private double length; + private Size size; + + static MapScale() + { + FrameworkElement.MinWidthProperty.OverrideMetadata( + typeof(MapScale), new FrameworkPropertyMetadata(100d)); + + FrameworkElement.HorizontalAlignmentProperty.OverrideMetadata( + typeof(MapScale), new FrameworkPropertyMetadata(HorizontalAlignment.Right)); + + FrameworkElement.VerticalAlignmentProperty.OverrideMetadata( + typeof(MapScale), new FrameworkPropertyMetadata(VerticalAlignment.Bottom)); + + MapOverlay.StrokeStartLineCapProperty.OverrideMetadata( + typeof(MapScale), new FrameworkPropertyMetadata(PenLineCap.Round)); + + MapOverlay.StrokeEndLineCapProperty.OverrideMetadata( + typeof(MapScale), new FrameworkPropertyMetadata(PenLineCap.Round)); + } + + public Thickness Padding + { + get { return (Thickness)GetValue(PaddingProperty); } + set { SetValue(PaddingProperty, value); } + } + + protected override Size MeasureOverride(Size availableSize) + { + double scale = ParentMap.CenterScale; // px/m + length = MinWidth / scale; + double magnitude = Math.Pow(10d, Math.Floor(Math.Log10(length))); + + if (length / magnitude < 2d) + { + length = 2d * magnitude; + } + else if (length / magnitude < 5d) + { + length = 5d * magnitude; + } + else + { + length = 10d * magnitude; + } + + size.Width = length * scale + StrokeThickness + Padding.Left + Padding.Right; + size.Height = FontSize + 2d * StrokeThickness + Padding.Top + Padding.Bottom; + return size; + } + + protected override void OnRender(DrawingContext drawingContext) + { + double x1 = Padding.Left + StrokeThickness / 2d; + double x2 = size.Width - Padding.Right - StrokeThickness / 2d; + double y1 = size.Height / 2d; + double y2 = size.Height - Padding.Bottom - StrokeThickness / 2d; + string text = length >= 1000d ? string.Format("{0:0} km", length / 1000d) : string.Format("{0:0} m", length); + + drawingContext.DrawRectangle(Background ?? ParentMap.Background, null, new Rect(size)); + drawingContext.DrawLine(Pen, new Point(x1, y1), new Point(x1, y2)); + drawingContext.DrawLine(Pen, new Point(x2, y1), new Point(x2, y2)); + drawingContext.DrawLine(Pen, new Point(x1, y2), new Point(x2, y2)); + drawingContext.DrawGlyphRun(Foreground, + GlyphRunText.Create(text, Typeface, FontSize, new Vector(size.Width / 2d, y1 - StrokeThickness - 1d))); + } + + protected override void OnViewportChanged() + { + InvalidateMeasure(); + } + } +} diff --git a/SampleApps/SampleApplication/MainWindow.xaml b/SampleApps/SampleApplication/MainWindow.xaml index fe2c441a..e03ce68b 100644 --- a/SampleApps/SampleApplication/MainWindow.xaml +++ b/SampleApps/SampleApplication/MainWindow.xaml @@ -136,6 +136,7 @@ ManipulationInertiaStarting="MapManipulationInertiaStarting" MouseMove="MapMouseMove" MouseLeave="MapMouseLeave"> + - diff --git a/SampleApps/SampleApplication/ViewportPositionToVisibilityConverter.cs b/SampleApps/SampleApplication/ViewportPositionToVisibilityConverter.cs index 1af8458d..5a1aaf24 100644 --- a/SampleApps/SampleApplication/ViewportPositionToVisibilityConverter.cs +++ b/SampleApps/SampleApplication/ViewportPositionToVisibilityConverter.cs @@ -10,17 +10,17 @@ namespace SampleApplication { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - Visibility visibility = Visibility.Visible; + Visibility visibility = Visibility.Hidden; if (values.Length == 2 && values[0] is Map && values[1] is Point? && ((Point?)values[1]).HasValue) { Map parentMap = (Map)values[0]; Point position = ((Point?)values[1]).Value; - if (position.X < 0d || position.X > parentMap.ActualWidth || - position.Y < 0d || position.Y > parentMap.ActualHeight) + if (position.X >= 0d && position.X <= parentMap.ActualWidth && + position.Y >= 0d && position.Y <= parentMap.ActualHeight) { - visibility = Visibility.Hidden; + visibility = Visibility.Visible; } }