diff --git a/MapControl.sln b/MapControl.sln index 838e0c8f..a4c6eb04 100644 --- a/MapControl.sln +++ b/MapControl.sln @@ -3,6 +3,8 @@ 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 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication", "TestApplication\TestApplication.csproj", "{A3F251EE-A1A0-4752-8C3D-C6BACBFB2AF2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,6 +25,16 @@ Global {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 + {A3F251EE-A1A0-4752-8C3D-C6BACBFB2AF2}.Debug|Any CPU.ActiveCfg = Debug|x86 + {A3F251EE-A1A0-4752-8C3D-C6BACBFB2AF2}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {A3F251EE-A1A0-4752-8C3D-C6BACBFB2AF2}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {A3F251EE-A1A0-4752-8C3D-C6BACBFB2AF2}.Debug|x86.ActiveCfg = Debug|x86 + {A3F251EE-A1A0-4752-8C3D-C6BACBFB2AF2}.Debug|x86.Build.0 = Debug|x86 + {A3F251EE-A1A0-4752-8C3D-C6BACBFB2AF2}.Release|Any CPU.ActiveCfg = Release|x86 + {A3F251EE-A1A0-4752-8C3D-C6BACBFB2AF2}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {A3F251EE-A1A0-4752-8C3D-C6BACBFB2AF2}.Release|Mixed Platforms.Build.0 = Release|x86 + {A3F251EE-A1A0-4752-8C3D-C6BACBFB2AF2}.Release|x86.ActiveCfg = Release|x86 + {A3F251EE-A1A0-4752-8C3D-C6BACBFB2AF2}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MapControl/GlyphRunText.cs b/MapControl/GlyphRunText.cs index 641f2d4b..0822dc43 100644 --- a/MapControl/GlyphRunText.cs +++ b/MapControl/GlyphRunText.cs @@ -1,9 +1,16 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Windows; using System.Windows.Media; namespace MapControl { + /// + /// Contains helper methods for creating GlyphRun objects. + /// public static class GlyphRunText { public static GlyphRun Create(string text, Typeface typeface, double emSize, Point baselineOrigin) diff --git a/MapControl/Location.cs b/MapControl/Location.cs new file mode 100644 index 00000000..ed2ccb56 --- /dev/null +++ b/MapControl/Location.cs @@ -0,0 +1,51 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Windows; + +namespace MapControl +{ + /// + /// A geographic location given as latitude and longitude. + /// + [TypeConverter(typeof(LocationConverter))] + public class Location + { + public Location() + { + } + + public Location(double latitude, double longitude) + { + Latitude = latitude; + Longitude = longitude; + } + + public double Latitude { get; set; } + public double Longitude { get; set; } + + public static Location Parse(string source) + { + Point p = Point.Parse(source); + return new Location(p.X, p.Y); + } + + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "{0:0.00000},{1:0.00000}", Latitude, Longitude); + } + } + + public class LocationConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + return Location.Parse((string)value); + } + } +} diff --git a/MapControl/LocationAnimation.cs b/MapControl/LocationAnimation.cs new file mode 100644 index 00000000..9f7178e4 --- /dev/null +++ b/MapControl/LocationAnimation.cs @@ -0,0 +1,50 @@ +using System; +using System.Windows; +using System.Windows.Media.Animation; + +namespace MapControl +{ + /// + /// Animates the value of a Location property between two values. + /// + public class LocationAnimation : AnimationTimeline + { + public Location From { get; set; } + public Location To { get; set; } + public IEasingFunction EasingFunction { get; set; } + + public override Type TargetPropertyType + { + get { return typeof(Location); } + } + + protected override Freezable CreateInstanceCore() + { + return new LocationAnimation + { + From = From, + To = To, + EasingFunction = EasingFunction + }; + } + + public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock) + { + if (!animationClock.CurrentProgress.HasValue) + { + return defaultOriginValue; + } + + double progress = animationClock.CurrentProgress.Value; + + if (EasingFunction != null) + { + progress = EasingFunction.Ease(progress); + } + + return new Location( + (1d - progress) * From.Latitude + progress * To.Latitude, + (1d - progress) * From.Longitude + progress * To.Longitude); + } + } +} diff --git a/MapControl/LocationCollection.cs b/MapControl/LocationCollection.cs new file mode 100644 index 00000000..3e5a921a --- /dev/null +++ b/MapControl/LocationCollection.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Globalization; + +namespace MapControl +{ + /// + /// A collection of geographic locations. + /// + [TypeConverter(typeof(LocationCollectionConverter))] + public class LocationCollection : ObservableCollection + { + public LocationCollection() + { + } + + public LocationCollection(IEnumerable locations) + { + foreach (Location location in locations) + { + Add(location); + } + } + + public static LocationCollection Parse(string source) + { + LocationCollection locations = new LocationCollection(); + + foreach (string locString in source.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) + { + locations.Add(Location.Parse(locString)); + } + + return locations; + } + } + + public class LocationCollectionConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + return LocationCollection.Parse((string)value); + } + } +} diff --git a/MapControl/Map.cs b/MapControl/Map.cs index bf511993..e9339e20 100644 --- a/MapControl/Map.cs +++ b/MapControl/Map.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Collections.Generic; using System.Windows; using System.Windows.Controls; @@ -7,6 +11,12 @@ using System.Windows.Media.Animation; namespace MapControl { + /// + /// The main map control. Draws map content provided by the TileLayers or the MainTileLayer property. + /// The visible map area is defined by the Center and ZoomLevel properties. The map can be rotated + /// by an angle that is given by the Heading property. + /// Map is a MapPanel and hence can contain map overlays like other MapPanels or MapItemsControls. + /// public partial class Map : MapPanel { public const double MeterPerDegree = 1852d * 60d; @@ -34,24 +44,24 @@ namespace MapControl "TileLayers", typeof(TileLayerCollection), typeof(Map), new FrameworkPropertyMetadata( (o, e) => ((Map)o).SetTileLayers((TileLayerCollection)e.NewValue))); - public static readonly DependencyProperty BaseTileLayerProperty = DependencyProperty.Register( - "BaseTileLayer", typeof(TileLayer), typeof(Map), new FrameworkPropertyMetadata( - (o, e) => ((Map)o).SetBaseTileLayer((TileLayer)e.NewValue), - (o, v) => ((Map)o).CoerceBaseTileLayer((TileLayer)v))); + public static readonly DependencyProperty MainTileLayerProperty = DependencyProperty.Register( + "MainTileLayer", typeof(TileLayer), typeof(Map), new FrameworkPropertyMetadata( + (o, e) => ((Map)o).SetMainTileLayer((TileLayer)e.NewValue), + (o, v) => ((Map)o).CoerceMainTileLayer((TileLayer)v))); public static readonly DependencyProperty TileOpacityProperty = DependencyProperty.Register( "TileOpacity", typeof(double), typeof(Map), new FrameworkPropertyMetadata(1d, (o, e) => ((Map)o).tileContainer.Opacity = (double)e.NewValue)); public static readonly DependencyProperty CenterProperty = DependencyProperty.Register( - "Center", typeof(Point), typeof(Map), new FrameworkPropertyMetadata(new Point(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, - (o, e) => ((Map)o).SetCenter((Point)e.NewValue), - (o, v) => ((Map)o).CoerceCenter((Point)v))); + "Center", typeof(Location), typeof(Map), new FrameworkPropertyMetadata(new Location(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + (o, e) => ((Map)o).SetCenter((Location)e.NewValue), + (o, v) => ((Map)o).CoerceCenter((Location)v))); public static readonly DependencyProperty TargetCenterProperty = DependencyProperty.Register( - "TargetCenter", typeof(Point), typeof(Map), new FrameworkPropertyMetadata(new Point(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, - (o, e) => ((Map)o).SetTargetCenter((Point)e.NewValue), - (o, v) => ((Map)o).CoerceCenter((Point)v))); + "TargetCenter", typeof(Location), typeof(Map), new FrameworkPropertyMetadata(new Location(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + (o, e) => ((Map)o).SetTargetCenter((Location)e.NewValue), + (o, v) => ((Map)o).CoerceCenter((Location)v))); public static readonly DependencyProperty ZoomLevelProperty = DependencyProperty.Register( "ZoomLevel", typeof(double), typeof(Map), new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, @@ -79,13 +89,13 @@ namespace MapControl public static readonly DependencyProperty CenterScaleProperty = CenterScalePropertyKey.DependencyProperty; private readonly TileContainer tileContainer = new TileContainer(); - private readonly MapViewTransform mapViewTransform = new MapViewTransform(); + private readonly MapTransform mapTransform = new MercatorTransform(); private readonly ScaleTransform scaleTransform = new ScaleTransform(); private readonly RotateTransform rotateTransform = new RotateTransform(); private readonly MatrixTransform scaleRotateTransform = new MatrixTransform(); - private Point? transformOrigin; - private Point viewOrigin; - private PointAnimation centerAnimation; + private Location transformOrigin; + private Point viewportOrigin; + private LocationAnimation centerAnimation; private DoubleAnimation zoomLevelAnimation; private DoubleAnimation headingAnimation; private bool updateTransform = true; @@ -96,9 +106,8 @@ namespace MapControl MaxZoomLevel = 20; AddVisualChild(tileContainer); - mapViewTransform.ViewTransform = tileContainer.ViewTransform; - BaseTileLayer = new TileLayer + MainTileLayer = new TileLayer { Description = "© {y} OpenStreetMap Contributors, CC-BY-SA", TileSource = new OpenStreetMapTileSource { UriFormat = "http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png" } @@ -190,12 +199,12 @@ namespace MapControl } /// - /// Gets or sets the base TileLayer used by this Map, i.e. TileLayers[0]. + /// Gets or sets the main TileLayer used by this Map, i.e. TileLayers[0]. /// - public TileLayer BaseTileLayer + public TileLayer MainTileLayer { - get { return (TileLayer)GetValue(BaseTileLayerProperty); } - set { SetValue(BaseTileLayerProperty, value); } + get { return (TileLayer)GetValue(MainTileLayerProperty); } + set { SetValue(MainTileLayerProperty, value); } } /// @@ -208,20 +217,20 @@ namespace MapControl } /// - /// Gets or sets the world coordinates (latitude and longitude) of the center point. + /// Gets or sets the location of the center point of the Map. /// - public Point Center + public Location Center { - get { return (Point)GetValue(CenterProperty); } + get { return (Location)GetValue(CenterProperty); } set { SetValue(CenterProperty, value); } } /// /// Gets or sets the target value of a Center animation. /// - public Point TargetCenter + public Location TargetCenter { - get { return (Point)GetValue(TargetCenterProperty); } + get { return (Location)GetValue(TargetCenterProperty); } set { SetValue(TargetCenterProperty, value); } } @@ -262,7 +271,7 @@ namespace MapControl } /// - /// Gets the map scale at the Center point as view coordinate units (pixels) per meter. + /// Gets the map scale at the Center location as viewport coordinate units (pixels) per meter. /// public double CenterScale { @@ -271,34 +280,24 @@ namespace MapControl } /// - /// Gets the transformation from world coordinates (latitude and longitude) - /// to cartesian map coordinates. + /// Gets the transformation from geographic coordinates to cartesian map coordinates. /// public MapTransform MapTransform { - get { return mapViewTransform.MapTransform; } + get { return mapTransform; } } /// - /// Gets the transformation from cartesian map coordinates to view coordinates. + /// Gets the transformation from cartesian map coordinates to viewport coordinates. /// - public Transform ViewTransform + public Transform ViewportTransform { - get { return mapViewTransform.ViewTransform; } + get { return tileContainer.ViewportTransform; } } /// - /// Gets the combination of MapTransform and ViewTransform, i.e. the transformation - /// from longitude and latitude values to view coordinates. - /// - public GeneralTransform MapViewTransform - { - get { return mapViewTransform; } - } - - /// - /// Gets the scaling transformation from meters to view coordinate units (pixels) - /// at the view center point. + /// Gets the scaling transformation from meters to viewport coordinate units (pixels) + /// at the viewport center point. /// public Transform ScaleTransform { @@ -322,57 +321,73 @@ namespace MapControl } /// - /// Sets an intermittent origin point in world coordinates for scaling and rotation transformations. + /// Transforms a geographic location to a viewport coordinates point. + /// + public Point LocationToViewportPoint(Location location) + { + return ViewportTransform.Transform(MapTransform.Transform(location)); + } + + /// + /// Transforms a viewport coordinates point to a geographic location. + /// + public Location ViewportPointToLocation(Point point) + { + return MapTransform.TransformBack(ViewportTransform.Inverse.Transform(point)); + } + + /// + /// Sets a temporary origin location in geographic coordinates for scaling and rotation transformations. + /// This origin location is automatically removed when the Center property is set by application code. + /// + public void SetTransformOrigin(Location origin) + { + transformOrigin = origin; + viewportOrigin = LocationToViewportPoint(origin); + } + + /// + /// Sets a temporary origin point in viewport coordinates for scaling and rotation transformations. /// This origin point is automatically removed when the Center property is set by application code. /// public void SetTransformOrigin(Point origin) { - transformOrigin = origin; - viewOrigin = MapViewTransform.Transform(origin); + viewportOrigin.X = Math.Min(Math.Max(origin.X, 0d), RenderSize.Width); + viewportOrigin.Y = Math.Min(Math.Max(origin.Y, 0d), RenderSize.Height); + transformOrigin = CoerceCenter(ViewportPointToLocation(viewportOrigin)); } /// - /// 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. - /// - 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)); - } - - /// - /// Removes the intermittent transform origin point set by SetTransformOrigin. + /// Removes the temporary transform origin point set by SetTransformOrigin. /// public void ResetTransformOrigin() { transformOrigin = null; - viewOrigin = new Point(RenderSize.Width / 2d, RenderSize.Height / 2d); + viewportOrigin = new Point(RenderSize.Width / 2d, RenderSize.Height / 2d); } /// - /// Changes the Center property according to the specified translation in view coordinates. + /// Changes the Center property according to the specified translation in viewport coordinates. /// public void TranslateMap(Vector translation) { if (translation.X != 0d || translation.Y != 0d) { ResetTransformOrigin(); - Center = MapViewTransform.Inverse.Transform(viewOrigin - translation); + Center = ViewportPointToLocation(viewportOrigin - translation); } } /// /// Changes the Center, Heading and ZoomLevel properties according to the specified - /// view coordinate translation, rotation and scale delta values. Rotation and scaling - /// is performed relative to the specified origin point in view coordinates. + /// viewport coordinate translation, rotation and scale delta values. Rotation and scaling + /// is performed relative to the specified origin point in viewport coordinates. /// public void TransformMap(Point origin, Vector translation, double rotation, double scale) { if (rotation != 0d || scale != 1d) { - SetTransformViewOrigin(origin); + SetTransformOrigin(origin); updateTransform = false; Heading += rotation; ZoomLevel += Math.Log(scale, 2d); @@ -385,21 +400,20 @@ namespace MapControl /// /// Sets the value of the ZoomLevel property while retaining the specified origin point - /// in view coordinates. + /// in viewport coordinates. /// public void ZoomMap(Point origin, double zoomLevel) { - SetTransformViewOrigin(origin); + SetTransformOrigin(origin); TargetZoomLevel = zoomLevel; } /// - /// Gets the map scale at the specified world coordinate point - /// as view coordinate units (pixels) per meter. + /// Gets the map scale at the specified location as viewport coordinate units (pixels) per meter. /// - public double GetMapScale(Point point) + public double GetMapScale(Location location) { - return MapTransform.RelativeScale(point) * Math.Pow(2d, ZoomLevel) * 256d / (MeterPerDegree * 360d); + return mapTransform.RelativeScale(location) * Math.Pow(2d, ZoomLevel) * 256d / (MeterPerDegree * 360d); } protected override int VisualChildrenCount @@ -477,40 +491,40 @@ namespace MapControl private void SetTileLayers(TileLayerCollection tileLayers) { - BaseTileLayer = tileLayers.Count > 0 ? tileLayers[0] : null; + MainTileLayer = tileLayers.Count > 0 ? tileLayers[0] : null; tileContainer.TileLayers = tileLayers; } - private void SetBaseTileLayer(TileLayer baseTileLayer) + private void SetMainTileLayer(TileLayer mainTileLayer) { - if (baseTileLayer != null) + if (mainTileLayer != null) { if (TileLayers == null) { - TileLayers = new TileLayerCollection(baseTileLayer); + TileLayers = new TileLayerCollection(mainTileLayer); } else if (TileLayers.Count == 0) { - TileLayers.Add(baseTileLayer); + TileLayers.Add(mainTileLayer); } - else if (TileLayers[0] != baseTileLayer) + else if (TileLayers[0] != mainTileLayer) { - TileLayers[0] = baseTileLayer; + TileLayers[0] = mainTileLayer; } } } - private TileLayer CoerceBaseTileLayer(TileLayer baseTileLayer) + private TileLayer CoerceMainTileLayer(TileLayer mainTileLayer) { - if (baseTileLayer == null && TileLayers.Count > 0) + if (mainTileLayer == null && TileLayers.Count > 0) { - baseTileLayer = TileLayers[0]; + mainTileLayer = TileLayers[0]; } - return baseTileLayer; + return mainTileLayer; } - private void SetCenter(Point center) + private void SetCenter(Location center) { if (updateTransform) { @@ -524,7 +538,7 @@ namespace MapControl } } - private void SetTargetCenter(Point targetCenter) + private void SetTargetCenter(Location targetCenter) { if (targetCenter != Center) { @@ -533,7 +547,7 @@ namespace MapControl centerAnimation.Completed -= CenterAnimationCompleted; } - centerAnimation = new PointAnimation + centerAnimation = new LocationAnimation { From = Center, To = targetCenter, @@ -557,11 +571,11 @@ namespace MapControl centerAnimation = null; } - private Point CoerceCenter(Point point) + private Location CoerceCenter(Location location) { - point.X = ((point.X + 180d) % 360d + 360d) % 360d - 180d; - point.Y = Math.Min(Math.Max(point.Y, -MapTransform.MaxLatitude), MapTransform.MaxLatitude); - return point; + location.Latitude = Math.Min(Math.Max(location.Latitude, -MapTransform.MaxLatitude), MapTransform.MaxLatitude); + location.Longitude = ((location.Longitude + 180d) % 360d + 360d) % 360d - 180d; + return location; } private void SetZoomLevel(double zoomLevel) @@ -682,16 +696,16 @@ namespace MapControl { double scale; - if (transformOrigin.HasValue) + if (transformOrigin != null) { - scale = tileContainer.SetTransform(ZoomLevel, Heading, MapTransform.Transform(transformOrigin.Value), viewOrigin, RenderSize); + scale = tileContainer.SetTransform(ZoomLevel, Heading, MapTransform.Transform(transformOrigin), viewportOrigin, RenderSize); updateTransform = false; - Center = MapViewTransform.Inverse.Transform(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d)); + Center = ViewportPointToLocation(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d)); updateTransform = true; } else { - scale = tileContainer.SetTransform(ZoomLevel, Heading, MapTransform.Transform(Center), viewOrigin, RenderSize); + scale = tileContainer.SetTransform(ZoomLevel, Heading, MapTransform.Transform(Center), viewportOrigin, RenderSize); } scale *= MapTransform.RelativeScale(Center) / MeterPerDegree; // Pixels per meter at center latitude diff --git a/MapControl/MapControl.csproj b/MapControl/MapControl.csproj index 0e714c36..8c212bac 100644 --- a/MapControl/MapControl.csproj +++ b/MapControl/MapControl.csproj @@ -24,10 +24,11 @@ 4 - pdbonly + none true bin\Release\ - TRACE + + prompt 4 @@ -42,21 +43,20 @@ + + + - - Code - - - + + - diff --git a/MapControl/MapElement.cs b/MapControl/MapElement.cs index 342074f2..3366c188 100644 --- a/MapControl/MapElement.cs +++ b/MapControl/MapElement.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Windows; namespace MapControl @@ -8,6 +12,9 @@ namespace MapControl void ParentMapChanged(Map oldParentMap, Map newParentMap); } + /// + /// Base class for child elements of a MapPanel. + /// public abstract class MapElement : FrameworkElement, INotifyParentMapChanged { protected MapElement() diff --git a/MapControl/MapGraticule.cs b/MapControl/MapGraticule.cs index 86ea3982..53d5e7f2 100644 --- a/MapControl/MapGraticule.cs +++ b/MapControl/MapGraticule.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -7,6 +11,10 @@ using System.Windows.Shapes; namespace MapControl { + /// + /// Draws a graticule overlay. The minimum spacing in pixels between adjacent graticule lines + /// is specified by the MinSpacingPixels property. + /// public class MapGraticule : MapElement { public static readonly DependencyProperty ForegroundProperty = Control.ForegroundProperty.AddOwner( @@ -36,7 +44,7 @@ namespace MapControl public static readonly DependencyProperty MinSpacingPixelsProperty = DependencyProperty.Register( "MinSpacingPixels", typeof(double), typeof(MapGraticule), new FrameworkPropertyMetadata(100d)); - public static double[] GridSpacings = + public static double[] Spacings = new double[] { 1d / 60d, 1d / 30d, 1d / 12d, 1d / 6d, 1d / 4d, 1d / 3d, 1d / 2d, 1d, 2d, 5d, 10d, 15d, 20d, 30d, 45d }; private readonly DrawingVisual visual = new DrawingVisual(); @@ -116,17 +124,19 @@ namespace MapControl protected override void OnViewTransformChanged(Map parentMap) { - Rect bounds = parentMap.MapViewTransform.Inverse.TransformBounds(new Rect(parentMap.RenderSize)); + Rect bounds = parentMap.ViewportTransform.Inverse.TransformBounds(new Rect(parentMap.RenderSize)); + Location loc1 = parentMap.MapTransform.TransformBack(bounds.TopLeft); + Location loc2 = parentMap.MapTransform.TransformBack(bounds.BottomRight); double minSpacing = MinSpacingPixels * 360d / (Math.Pow(2d, parentMap.ZoomLevel) * 256d); - double spacing = GridSpacings[GridSpacings.Length - 1]; + double spacing = Spacings[Spacings.Length - 1]; if (spacing >= minSpacing) { - spacing = GridSpacings.FirstOrDefault(s => s >= minSpacing); + spacing = Spacings.FirstOrDefault(s => s >= minSpacing); } - double longitudeStart = Math.Ceiling(bounds.Left / spacing) * spacing; - double latitudeStart = Math.Ceiling(bounds.Top / spacing) * spacing; + double latitudeStart = Math.Ceiling(loc1.Latitude / spacing) * spacing; + double longitudeStart = Math.Ceiling(loc1.Longitude / spacing) * spacing; if (pen.Brush == null) { @@ -135,18 +145,18 @@ namespace MapControl using (DrawingContext drawingContext = visual.RenderOpen()) { - for (double lon = longitudeStart; lon <= bounds.Right; lon += spacing) + for (double lat = latitudeStart; lat <= loc2.Latitude; lat += spacing) { drawingContext.DrawLine(pen, - parentMap.MapViewTransform.Transform(new Point(lon, bounds.Bottom)), - parentMap.MapViewTransform.Transform(new Point(lon, bounds.Top))); + parentMap.LocationToViewportPoint(new Location(lat, loc1.Longitude)), + parentMap.LocationToViewportPoint(new Location(lat, loc2.Longitude))); } - for (double lat = latitudeStart; lat <= bounds.Bottom; lat += spacing) + for (double lon = longitudeStart; lon <= loc2.Longitude; lon += spacing) { drawingContext.DrawLine(pen, - parentMap.MapViewTransform.Transform(new Point(bounds.Left, lat)), - parentMap.MapViewTransform.Transform(new Point(bounds.Right, lat))); + parentMap.LocationToViewportPoint(new Location(loc1.Latitude, lon)), + parentMap.LocationToViewportPoint(new Location(loc2.Latitude, lon))); } if (Foreground != null && Foreground != Brushes.Transparent) @@ -158,12 +168,12 @@ namespace MapControl typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch); } - for (double lon = longitudeStart; lon <= bounds.Right; lon += spacing) + for (double lat = latitudeStart; lat <= loc2.Latitude; lat += spacing) { - for (double lat = latitudeStart; lat <= bounds.Bottom; lat += spacing) + for (double lon = longitudeStart; lon <= loc2.Longitude; lon += spacing) { double t = StrokeThickness / 2d; - Point p = parentMap.MapViewTransform.Transform(new Point(lon, lat)); + Point p = parentMap.LocationToViewportPoint(new Location(lat, lon)); Point latPos = new Point(p.X + t + 2d, p.Y - t - FontSize / 4d); Point lonPos = new Point(p.X + t + 2d, p.Y + t + FontSize); string latString = CoordinateString(lat, format, "NS"); diff --git a/MapControl/MapInput.cs b/MapControl/MapInput.cs index df9bc807..6e4af765 100644 --- a/MapControl/MapInput.cs +++ b/MapControl/MapInput.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Windows; using System.Windows.Input; diff --git a/MapControl/MapItem.cs b/MapControl/MapItem.cs index 0b877071..e644b515 100644 --- a/MapControl/MapItem.cs +++ b/MapControl/MapItem.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; @@ -7,22 +11,21 @@ using System.Windows.Media; namespace MapControl { - [TemplateVisualState(GroupName = "CommonStates", Name = "Normal")] - [TemplateVisualState(GroupName = "CommonStates", Name = "Disabled")] - [TemplateVisualState(GroupName = "CommonStates", Name = "MouseOver")] - [TemplateVisualState(GroupName = "SelectionStates", Name = "Unselected")] - [TemplateVisualState(GroupName = "SelectionStates", Name = "Selected")] - [TemplateVisualState(GroupName = "CurrentStates", Name = "NonCurrent")] - [TemplateVisualState(GroupName = "CurrentStates", Name = "Current")] + /// + /// Container class for an item in a MapItemsControl. + /// + [TemplateVisualState(Name = "Normal", GroupName = "CommonStates")] + [TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")] + [TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")] + [TemplateVisualState(Name = "Unselected", GroupName = "SelectionStates")] + [TemplateVisualState(Name = "Selected", GroupName = "SelectionStates")] + [TemplateVisualState(Name = "NotCurrent", GroupName = "CurrentStates")] + [TemplateVisualState(Name = "Current", GroupName = "CurrentStates")] public class MapItem : ContentControl { public static readonly RoutedEvent SelectedEvent = ListBoxItem.SelectedEvent.AddOwner(typeof(MapItem)); public static readonly RoutedEvent UnselectedEvent = ListBoxItem.UnselectedEvent.AddOwner(typeof(MapItem)); - public static readonly DependencyProperty LocationProperty = MapPanel.LocationProperty.AddOwner(typeof(MapItem)); - public static readonly DependencyProperty ViewPositionProperty = MapPanel.ViewPositionProperty.AddOwner(typeof(MapItem)); - public static readonly DependencyProperty ViewPositionTransformProperty = MapPanel.ViewPositionTransformProperty.AddOwner(typeof(MapItem)); - public static readonly DependencyProperty IsSelectedProperty = Selector.IsSelectedProperty.AddOwner( typeof(MapItem), new FrameworkPropertyMetadata((o, e) => ((MapItem)o).IsSelectedChanged((bool)e.NewValue))); @@ -31,13 +34,6 @@ namespace MapControl public static readonly DependencyProperty IsCurrentProperty = IsCurrentPropertyKey.DependencyProperty; - private static readonly DependencyPropertyKey IsInsideMapBoundsPropertyKey = DependencyProperty.RegisterReadOnly( - "IsInsideMapBounds", typeof(bool), typeof(MapItem), null); - - public static readonly DependencyProperty IsInsideMapBoundsProperty = IsInsideMapBoundsPropertyKey.DependencyProperty; - - private object item; - static MapItem() { FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItem), @@ -45,9 +41,6 @@ namespace MapControl UIElement.IsEnabledProperty.OverrideMetadata(typeof(MapItem), new FrameworkPropertyMetadata((o, e) => ((MapItem)o).CommonStateChanged())); - - MapPanel.ViewPositionPropertyKey.OverrideMetadata(typeof(MapItem), - new FrameworkPropertyMetadata((o, e) => ((MapItem)o).ViewPositionChanged((Point)e.NewValue))); } public event RoutedEventHandler Selected @@ -67,27 +60,6 @@ namespace MapControl get { return MapPanel.GetParentMap(this); } } - public Point? Location - { - get { return (Point?)GetValue(LocationProperty); } - set { SetValue(LocationProperty, value); } - } - - public bool HasViewPosition - { - get { return ReadLocalValue(ViewPositionProperty) != DependencyProperty.UnsetValue; } - } - - public Point ViewPosition - { - get { return (Point)GetValue(ViewPositionProperty); } - } - - public Transform ViewPositionTransform - { - get { return (Transform)GetValue(ViewPositionTransformProperty); } - } - public bool IsSelected { get { return (bool)GetValue(IsSelectedProperty); } @@ -104,28 +76,12 @@ namespace MapControl SetValue(IsCurrentPropertyKey, value); int zIndex = Panel.GetZIndex(this); Panel.SetZIndex(this, value ? (zIndex | 0x40000000) : (zIndex & ~0x40000000)); - VisualStateManager.GoToState(this, value ? "Current" : "NonCurrent", true); + VisualStateManager.GoToState(this, value ? "Current" : "NotCurrent", true); } } } - public bool IsInsideMapBounds - { - get { return (bool)GetValue(IsInsideMapBoundsProperty); } - } - - public object Item - { - get { return item; } - internal set - { - item = value; - if (HasViewPosition) - { - ViewPositionChanged(ViewPosition); - } - } - } + public object Item { get; internal set; } protected override void OnMouseEnter(MouseEventArgs e) { @@ -159,24 +115,6 @@ namespace MapControl IsSelected = !IsSelected; } - protected virtual void OnViewPositionChanged(Point viewPosition) - { - } - - private void ViewPositionChanged(Point viewPosition) - { - Map map = ParentMap; - - if (map != null) - { - SetValue(IsInsideMapBoundsPropertyKey, - viewPosition.X >= 0d && viewPosition.X <= map.ActualWidth && - viewPosition.Y >= 0d && viewPosition.Y <= map.ActualHeight); - - OnViewPositionChanged(viewPosition); - } - } - private void CommonStateChanged() { if (!IsEnabled) diff --git a/MapControl/MapItemsControl.cs b/MapControl/MapItemsControl.cs index 58ec9cbd..477111d0 100644 --- a/MapControl/MapItemsControl.cs +++ b/MapControl/MapItemsControl.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls.Primitives; @@ -8,6 +12,10 @@ namespace MapControl { public enum MapItemSelectionMode { Single, Extended } + /// + /// Manages a collection of selectable items on a Map. Uses MapItem as container for items + /// and updates the MapItem.IsCurrent property when Items.CurrentItem changes. + /// public class MapItemsControl : MultiSelector { public static readonly DependencyProperty SelectionModeProperty = DependencyProperty.Register( @@ -114,7 +122,7 @@ namespace MapControl { MapItem mapItem = GetMapItem(item); - if (mapItem != null && mapItem.HasViewPosition && geometry.FillContains(mapItem.ViewPosition)) + if (mapItem != null && MapPanel.HasViewportPosition(mapItem) && geometry.FillContains(MapPanel.GetViewportPosition(mapItem))) { SelectedItems.Add(item); } diff --git a/MapControl/MapPanel.cs b/MapControl/MapPanel.cs index e899387e..0f19d30d 100644 --- a/MapControl/MapPanel.cs +++ b/MapControl/MapPanel.cs @@ -1,10 +1,21 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace MapControl { + /// + /// Positions child elements on a Map. A child element's position is specified by the + /// attached property Location, given as geographic location with latitude and longitude. + /// The attached property ViewportPosition gets a child element's position in viewport + /// coordinates. IsInsideMapBounds indicates if the viewport coordinates are located + /// inside the visible part of the map. + /// public class MapPanel : Panel, INotifyParentMapChanged { public static readonly DependencyProperty ParentMapProperty = DependencyProperty.RegisterAttached( @@ -12,17 +23,17 @@ namespace MapControl new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, ParentMapPropertyChanged)); public static readonly DependencyProperty LocationProperty = DependencyProperty.RegisterAttached( - "Location", typeof(Point?), typeof(MapPanel), + "Location", typeof(Location), typeof(MapPanel), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, LocationPropertyChanged)); - internal static readonly DependencyPropertyKey ViewPositionPropertyKey = DependencyProperty.RegisterAttachedReadOnly( - "ViewPosition", typeof(Point), typeof(MapPanel), null); + private static readonly DependencyPropertyKey ViewportPositionPropertyKey = DependencyProperty.RegisterAttachedReadOnly( + "ViewportPosition", typeof(Point), typeof(MapPanel), null); - private static readonly DependencyPropertyKey ViewPositionTransformPropertyKey = DependencyProperty.RegisterAttachedReadOnly( - "ViewPositionTransform", typeof(Transform), typeof(MapPanel), null); + private static readonly DependencyPropertyKey IsInsideMapBoundsPropertyKey = DependencyProperty.RegisterAttachedReadOnly( + "IsInsideMapBounds", typeof(bool), typeof(MapPanel), null); - public static readonly DependencyProperty ViewPositionProperty = ViewPositionPropertyKey.DependencyProperty; - public static readonly DependencyProperty ViewPositionTransformProperty = ViewPositionTransformPropertyKey.DependencyProperty; + public static readonly DependencyProperty ViewportPositionProperty = ViewportPositionPropertyKey.DependencyProperty; + public static readonly DependencyProperty IsInsideMapBoundsProperty = IsInsideMapBoundsPropertyKey.DependencyProperty; public MapPanel() { @@ -39,24 +50,29 @@ namespace MapControl return (Map)element.GetValue(ParentMapProperty); } - public static Point? GetLocation(UIElement element) + public static Location GetLocation(UIElement element) { - return (Point?)element.GetValue(LocationProperty); + return (Location)element.GetValue(LocationProperty); } - public static void SetLocation(UIElement element, Point? value) + public static void SetLocation(UIElement element, Location value) { element.SetValue(LocationProperty, value); } - public static Point GetViewPosition(UIElement element) + public static bool HasViewportPosition(UIElement element) { - return (Point)element.GetValue(ViewPositionProperty); + return element.ReadLocalValue(ViewportPositionProperty) != DependencyProperty.UnsetValue; } - public static Transform GetViewPositionTransform(UIElement element) + public static Point GetViewportPosition(UIElement element) { - return (Transform)element.GetValue(ViewPositionTransformProperty); + return (Point)element.GetValue(ViewportPositionProperty); + } + + public static bool GetIsInsideMapBounds(UIElement element) + { + return (bool)element.GetValue(IsInsideMapBoundsProperty); } protected override Size MeasureOverride(Size availableSize) @@ -75,10 +91,10 @@ namespace MapControl { foreach (UIElement element in InternalChildren) { - object viewPosition = element.ReadLocalValue(ViewPositionProperty); + object viewportPosition = element.ReadLocalValue(ViewportPositionProperty); - if (viewPosition == DependencyProperty.UnsetValue || - !ArrangeElement(element, (Point)viewPosition)) + if (viewportPosition == DependencyProperty.UnsetValue || + !ArrangeElement(element, (Point)viewportPosition)) { ArrangeElement(element, finalSize); } @@ -91,11 +107,11 @@ namespace MapControl { foreach (UIElement element in InternalChildren) { - Point? location = GetLocation(element); + Location location = GetLocation(element); - if (location.HasValue) + if (location != null) { - SetViewPosition(element, parentMap, location); + SetViewportPosition(element, parentMap, location); } } } @@ -131,40 +147,31 @@ namespace MapControl private static void LocationPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs eventArgs) { UIElement element = (UIElement)obj; - Point? location = (Point?)eventArgs.NewValue; + Location location = (Location)eventArgs.NewValue; Map parentMap; - if (location.HasValue && (parentMap = Map.GetParentMap(element)) != null) + if (location != null && (parentMap = GetParentMap(element)) != null) { - SetViewPosition(element, parentMap, location); + SetViewportPosition(element, parentMap, location); } else { - element.ClearValue(ViewPositionPropertyKey); - element.ClearValue(ViewPositionTransformPropertyKey); + element.ClearValue(ViewportPositionPropertyKey); + element.ClearValue(IsInsideMapBoundsPropertyKey); element.Arrange(new Rect()); } } - private static void SetViewPosition(UIElement element, Map parentMap, Point? location) + private static void SetViewportPosition(UIElement element, Map parentMap, Location location) { - Point viewPosition = parentMap.MapViewTransform.Transform(location.Value); + Point viewportPosition = parentMap.LocationToViewportPoint(location); - element.SetValue(ViewPositionPropertyKey, viewPosition); + element.SetValue(ViewportPositionPropertyKey, viewportPosition); + element.SetValue(IsInsideMapBoundsPropertyKey, + viewportPosition.X >= 0d && viewportPosition.X <= parentMap.ActualWidth && + viewportPosition.Y >= 0d && viewportPosition.Y <= parentMap.ActualHeight); - Matrix matrix = new Matrix(1d, 0d, 0d, 1d, viewPosition.X, viewPosition.Y); - MatrixTransform viewTransform = element.GetValue(ViewPositionTransformProperty) as MatrixTransform; - - if (viewTransform != null) - { - viewTransform.Matrix = matrix; - } - else - { - element.SetValue(ViewPositionTransformPropertyKey, new MatrixTransform(matrix)); - } - - ArrangeElement(element, viewPosition); + ArrangeElement(element, viewportPosition); } private static bool ArrangeElement(UIElement element, Point position) diff --git a/MapControl/MapPathGeometry.cs b/MapControl/MapPathGeometry.cs deleted file mode 100644 index 6694db4b..00000000 --- a/MapControl/MapPathGeometry.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.Linq; -using System.Windows; -using System.Windows.Media; - -namespace MapControl -{ - public static class MapPathGeometry - { - public static PathGeometry Transform(this GeneralTransform transform, Geometry geometry) - { - PathGeometry pathGeometry = geometry as PathGeometry; - - if (pathGeometry == null) - { - pathGeometry = PathGeometry.CreateFromGeometry(geometry); - } - - if (geometry.Transform != null && geometry.Transform != System.Windows.Media.Transform.Identity) - { - GeneralTransformGroup transformGroup = new GeneralTransformGroup(); - transformGroup.Children.Add(geometry.Transform); - transformGroup.Children.Add(transform); - transform = transformGroup; - } - - return new PathGeometry(Transform(transform, pathGeometry.Figures), - pathGeometry.FillRule, System.Windows.Media.Transform.Identity); - } - - public static PathFigureCollection Transform(this GeneralTransform transform, PathFigureCollection figures) - { - PathFigureCollection transformedFigures = new PathFigureCollection(); - - foreach (PathFigure figure in figures) - { - transformedFigures.Add(Transform(transform, figure)); - } - - transformedFigures.Freeze(); - - return transformedFigures; - } - - public static PathFigure Transform(this GeneralTransform transform, PathFigure figure) - { - PathSegmentCollection transformedSegments = new PathSegmentCollection(figure.Segments.Count); - - foreach (PathSegment segment in figure.Segments) - { - PathSegment transformedSegment = null; - - if (segment is LineSegment) - { - LineSegment lineSegment = (LineSegment)segment; - - transformedSegment = new LineSegment( - transform.Transform(lineSegment.Point), - lineSegment.IsStroked); - } - else if (segment is PolyLineSegment) - { - PolyLineSegment polyLineSegment = (PolyLineSegment)segment; - - transformedSegment = new PolyLineSegment( - polyLineSegment.Points.Select(transform.Transform), - polyLineSegment.IsStroked); - } - else if (segment is ArcSegment) - { - ArcSegment arcSegment = (ArcSegment)segment; - Size size = arcSegment.Size; - MapTransform mapTransform = transform as MapTransform; - - if (mapTransform != null) - { - double yScale = mapTransform.RelativeScale(arcSegment.Point); - - if (arcSegment.RotationAngle == 0d) - { - size.Height *= yScale; - } - else - { - double sinR = Math.Sin(arcSegment.RotationAngle * Math.PI / 180d); - double cosR = Math.Cos(arcSegment.RotationAngle * Math.PI / 180d); - - size.Width *= Math.Sqrt(yScale * yScale * sinR * sinR + cosR * cosR); - size.Height *= Math.Sqrt(yScale * yScale * cosR * cosR + sinR * sinR); - } - } - - transformedSegment = new ArcSegment( - transform.Transform(arcSegment.Point), - size, - arcSegment.RotationAngle, - arcSegment.IsLargeArc, - arcSegment.SweepDirection, - arcSegment.IsStroked); - } - else if (segment is BezierSegment) - { - BezierSegment bezierSegment = (BezierSegment)segment; - - transformedSegment = new BezierSegment( - transform.Transform(bezierSegment.Point1), - transform.Transform(bezierSegment.Point2), - transform.Transform(bezierSegment.Point3), - bezierSegment.IsStroked); - } - else if (segment is PolyBezierSegment) - { - PolyBezierSegment polyBezierSegment = (PolyBezierSegment)segment; - - transformedSegment = new PolyBezierSegment( - polyBezierSegment.Points.Select(transform.Transform), - polyBezierSegment.IsStroked); - } - else if (segment is QuadraticBezierSegment) - { - QuadraticBezierSegment quadraticBezierSegment = (QuadraticBezierSegment)segment; - - transformedSegment = new QuadraticBezierSegment( - transform.Transform(quadraticBezierSegment.Point1), - transform.Transform(quadraticBezierSegment.Point2), - quadraticBezierSegment.IsStroked); - } - else if (segment is PolyQuadraticBezierSegment) - { - PolyQuadraticBezierSegment polyQuadraticBezierSegment = (PolyQuadraticBezierSegment)segment; - - transformedSegment = new PolyQuadraticBezierSegment( - polyQuadraticBezierSegment.Points.Select(transform.Transform), - polyQuadraticBezierSegment.IsStroked); - } - - if (transformedSegment != null) - { - transformedSegment.IsSmoothJoin = segment.IsSmoothJoin; - transformedSegments.Add(transformedSegment); - } - } - - PathFigure transformedFigure = new PathFigure( - transform.Transform(figure.StartPoint), - transformedSegments, - figure.IsClosed); - - transformedFigure.IsFilled = figure.IsFilled; - transformedFigure.Freeze(); - - return transformedFigure; - } - } -} diff --git a/MapControl/MapPolygon.cs b/MapControl/MapPolygon.cs new file mode 100644 index 00000000..2c086cc4 --- /dev/null +++ b/MapControl/MapPolygon.cs @@ -0,0 +1,35 @@ +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System.Windows; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace MapControl +{ + /// + /// A closed map polygon, defined by a collection of geographic locations in the Locations property. + /// + public class MapPolygon : MapPolyline + { + public static readonly DependencyProperty FillProperty = Shape.FillProperty.AddOwner( + typeof(MapPolygon), new FrameworkPropertyMetadata((o, e) => ((MapPolygon)o).drawing.Brush = (Brush)e.NewValue)); + + public MapPolygon() + { + drawing.Brush = Fill; + } + + public Brush Fill + { + get { return (Brush)GetValue(FillProperty); } + set { SetValue(FillProperty, value); } + } + + protected override void UpdateGeometry() + { + UpdateGeometry(true); + } + } +} diff --git a/MapControl/MapPath.cs b/MapControl/MapPolyline.cs similarity index 58% rename from MapControl/MapPath.cs rename to MapControl/MapPolyline.cs index 6b430c8b..6e532a13 100644 --- a/MapControl/MapPath.cs +++ b/MapControl/MapPolyline.cs @@ -1,54 +1,58 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Linq; using System.Windows; using System.Windows.Media; using System.Windows.Shapes; namespace MapControl { - public class MapPath : MapElement + /// + /// An open map polygon, defined by a collection of geographic locations in the Locations property. + /// + public class MapPolyline : MapElement { - public static readonly DependencyProperty DataProperty = Path.DataProperty.AddOwner( - typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).UpdateGeometry())); - - public static readonly DependencyProperty FillProperty = Shape.FillProperty.AddOwner( - typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Brush = (Brush)e.NewValue)); - public static readonly DependencyProperty StrokeProperty = Shape.StrokeProperty.AddOwner( - typeof(MapPath), new FrameworkPropertyMetadata(Brushes.Black, (o, e) => ((MapPath)o).drawing.Pen.Brush = (Brush)e.NewValue)); + typeof(MapPolyline), new FrameworkPropertyMetadata(Brushes.Black, (o, e) => ((MapPolyline)o).drawing.Pen.Brush = (Brush)e.NewValue)); public static readonly DependencyProperty StrokeDashArrayProperty = Shape.StrokeDashArrayProperty.AddOwner( - typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.DashStyle = new DashStyle((DoubleCollection)e.NewValue, ((MapPath)o).StrokeDashOffset))); + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.DashStyle = new DashStyle((DoubleCollection)e.NewValue, ((MapPolyline)o).StrokeDashOffset))); public static readonly DependencyProperty StrokeDashOffsetProperty = Shape.StrokeDashOffsetProperty.AddOwner( - typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.DashStyle = new DashStyle(((MapPath)o).StrokeDashArray, (double)e.NewValue))); + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.DashStyle = new DashStyle(((MapPolyline)o).StrokeDashArray, (double)e.NewValue))); public static readonly DependencyProperty StrokeDashCapProperty = Shape.StrokeDashCapProperty.AddOwner( - typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.DashCap = (PenLineCap)e.NewValue)); + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.DashCap = (PenLineCap)e.NewValue)); public static readonly DependencyProperty StrokeStartLineCapProperty = Shape.StrokeStartLineCapProperty.AddOwner( - typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.StartLineCap = (PenLineCap)e.NewValue)); + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.StartLineCap = (PenLineCap)e.NewValue)); public static readonly DependencyProperty StrokeEndLineCapProperty = Shape.StrokeEndLineCapProperty.AddOwner( - typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.EndLineCap = (PenLineCap)e.NewValue)); + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.EndLineCap = (PenLineCap)e.NewValue)); public static readonly DependencyProperty StrokeLineJoinProperty = Shape.StrokeLineJoinProperty.AddOwner( - typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.LineJoin = (PenLineJoin)e.NewValue)); + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.LineJoin = (PenLineJoin)e.NewValue)); public static readonly DependencyProperty StrokeMiterLimitProperty = Shape.StrokeMiterLimitProperty.AddOwner( - typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).drawing.Pen.MiterLimit = (double)e.NewValue)); + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).drawing.Pen.MiterLimit = (double)e.NewValue)); public static readonly DependencyProperty StrokeThicknessProperty = Shape.StrokeThicknessProperty.AddOwner( - typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).UpdatePenThickness())); + typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).UpdatePenThickness())); public static readonly DependencyProperty TransformStrokeProperty = DependencyProperty.Register( - "TransformStroke", typeof(bool), typeof(MapPath), new FrameworkPropertyMetadata((o, e) => ((MapPath)o).UpdatePenThickness())); + "TransformStroke", typeof(bool), typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).UpdatePenThickness())); - private readonly DrawingVisual visual = new DrawingVisual(); - private readonly GeometryDrawing drawing = new GeometryDrawing(); + public static readonly DependencyProperty LocationsProperty = DependencyProperty.Register( + "Locations", typeof(LocationCollection), typeof(MapPolyline), new FrameworkPropertyMetadata((o, e) => ((MapPolyline)o).UpdateGeometry())); - public MapPath() + protected readonly DrawingVisual visual = new DrawingVisual(); + protected readonly GeometryDrawing drawing = new GeometryDrawing(); + + public MapPolyline() { - drawing.Brush = Fill; drawing.Pen = new Pen(Stroke, StrokeThickness); using (DrawingContext drawingContext = visual.RenderOpen()) @@ -59,18 +63,6 @@ namespace MapControl Loaded += (o, e) => UpdateGeometry(); } - public Geometry Data - { - get { return (Geometry)GetValue(DataProperty); } - set { SetValue(DataProperty, value); } - } - - public Brush Fill - { - get { return (Brush)GetValue(FillProperty); } - set { SetValue(FillProperty, value); } - } - public Brush Stroke { get { return (Brush)GetValue(StrokeProperty); } @@ -131,6 +123,12 @@ namespace MapControl set { SetValue(TransformStrokeProperty, value); } } + public LocationCollection Locations + { + get { return (LocationCollection)GetValue(LocationsProperty); } + set { SetValue(LocationsProperty, value); } + } + public double TransformedStrokeThickness { get { return drawing.Pen.Thickness; } @@ -162,23 +160,26 @@ namespace MapControl { double scale = 1d; - if (TransformStroke && Data != null) + if (TransformStroke) { - Point center = Data.Bounds.Location + (Vector)Data.Bounds.Size / 2d; - scale = parentMap.GetMapScale(center) * Map.MeterPerDegree; + scale = parentMap.CenterScale * Map.MeterPerDegree; } drawing.Pen.Thickness = scale * StrokeThickness; } - private void UpdateGeometry() + protected virtual void UpdateGeometry() + { + UpdateGeometry(false); + } + + protected void UpdateGeometry(bool closed) { Map parentMap = MapPanel.GetParentMap(this); - if (parentMap != null && Data != null) + if (parentMap != null && Locations != null && Locations.Count > 0) { - drawing.Geometry = parentMap.MapTransform.Transform(Data); - drawing.Geometry.Transform = parentMap.ViewTransform; + drawing.Geometry = CreateGeometry(parentMap, Locations, closed); OnViewTransformChanged(parentMap); } else @@ -196,5 +197,25 @@ namespace MapControl OnViewTransformChanged(parentMap); } } + + private Geometry CreateGeometry(Map parentMap, LocationCollection locations, bool closed) + { + StreamGeometry geometry = new StreamGeometry + { + Transform = parentMap.ViewportTransform + }; + + using (StreamGeometryContext sgc = geometry.Open()) + { + sgc.BeginFigure(parentMap.MapTransform.Transform(locations.First()), closed, closed); + + if (Locations.Count > 1) + { + sgc.PolyLineTo(parentMap.MapTransform.Transform(locations.Skip(1)), true, true); + } + } + + return geometry; + } } } diff --git a/MapControl/MapStreamGeometry.cs b/MapControl/MapStreamGeometry.cs deleted file mode 100644 index 5866c84e..00000000 --- a/MapControl/MapStreamGeometry.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Windows; -using System.Windows.Media; - -namespace MapControl -{ - public static class MapStreamGeometry - { - public static MapStreamGeometryContext Open(this StreamGeometry mapGeometry, MapTransform transform) - { - return new MapStreamGeometryContext(mapGeometry.Open(), transform); - } - } - - public class MapStreamGeometryContext : IDisposable - { - StreamGeometryContext context; - MapTransform transform; - - public MapStreamGeometryContext(StreamGeometryContext context, MapTransform transform) - { - this.context = context; - this.transform = transform; - } - - void IDisposable.Dispose() - { - context.Close(); - } - - public void Close() - { - context.Close(); - } - - public void BeginFigure(Point startPoint, bool isFilled, bool isClosed) - { - context.BeginFigure(transform.Transform(startPoint), isFilled, isClosed); - } - - public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection, bool isStroked, bool isSmoothJoin) - { - double yScale = transform.RelativeScale(point); - - if (rotationAngle == 0d) - { - size.Height *= yScale; - } - else - { - double sinR = Math.Sin(rotationAngle * Math.PI / 180d); - double cosR = Math.Cos(rotationAngle * Math.PI / 180d); - - size.Width *= Math.Sqrt(yScale * yScale * sinR * sinR + cosR * cosR); - size.Height *= Math.Sqrt(yScale * yScale * cosR * cosR + sinR * sinR); - } - - context.ArcTo(transform.Transform(point), size, rotationAngle, isLargeArc, sweepDirection, isStroked, isSmoothJoin); - } - - public void LineTo(Point point, bool isStroked, bool isSmoothJoin) - { - context.LineTo(transform.Transform(point), isStroked, isSmoothJoin); - } - - public void QuadraticBezierTo(Point point1, Point point2, bool isStroked, bool isSmoothJoin) - { - context.QuadraticBezierTo(transform.Transform(point1), transform.Transform(point2), isStroked, isSmoothJoin); - } - - public void BezierTo(Point point1, Point point2, Point point3, bool isStroked, bool isSmoothJoin) - { - context.BezierTo(transform.Transform(point1), transform.Transform(point2), transform.Transform(point3), isStroked, isSmoothJoin); - } - - public void PolyLineTo(IList points, bool isStroked, bool isSmoothJoin) - { - context.PolyLineTo(TransformPoints(points), isStroked, isSmoothJoin); - } - - public void PolyQuadraticBezierTo(IList points, bool isStroked, bool isSmoothJoin) - { - context.PolyQuadraticBezierTo(TransformPoints(points), isStroked, isSmoothJoin); - } - - public void PolyBezierTo(IList points, bool isStroked, bool isSmoothJoin) - { - context.PolyBezierTo(TransformPoints(points), isStroked, isSmoothJoin); - } - - private IList TransformPoints(IList points) - { - Point[] transformedPoints = new Point[points.Count]; - - for (int i = 0; i < transformedPoints.Length; i++) - { - transformedPoints[i] = transform.Transform(points[i]); - } - - return transformedPoints; - } - } -} diff --git a/MapControl/MapTransform.cs b/MapControl/MapTransform.cs index bb0d7680..f0797c1c 100644 --- a/MapControl/MapTransform.cs +++ b/MapControl/MapTransform.cs @@ -1,4 +1,10 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Collections.Generic; +using System.Linq; using System.Windows; using System.Windows.Media; @@ -9,7 +15,7 @@ namespace MapControl /// are transformed to cartesian coordinates with origin at latitude = 0 and longitude = 0. /// Longitude values are transformed identically to x values in the interval [-180 .. 180]. /// - public abstract class MapTransform : GeneralTransform + public abstract class MapTransform { /// /// Gets the absolute value of the minimum and maximum latitude that can be transformed. @@ -17,9 +23,35 @@ namespace MapControl public abstract double MaxLatitude { get; } /// - /// Gets the point scale factor of the map projection at the specified point - /// relative to the point (0, 0). + /// Gets the scale factor of the map projection at the specified geographic location + /// relative to the scale at latitude zero. /// - public abstract double RelativeScale(Point point); + public abstract double RelativeScale(Location location); + + /// + /// Transforms a geographic location to a cartesian coordinate point. + /// + public abstract Point Transform(Location location); + + /// + /// Transforms a cartesian coordinate point to a geographic location. + /// + public abstract Location TransformBack(Point point); + + /// + /// Transforms a collection of geographic locations to a collection of cartesian coordinate points. + /// + public PointCollection Transform(IEnumerable locations) + { + return new PointCollection(locations.Select(l => Transform(l))); + } + + /// + /// Transforms a collection of cartesian coordinate points to a collections of geographic location. + /// + public LocationCollection TransformBack(IEnumerable points) + { + return new LocationCollection(points.Select(p => TransformBack(p))); + } } } diff --git a/MapControl/MapViewTransform.cs b/MapControl/MapViewTransform.cs deleted file mode 100644 index 1323fd2a..00000000 --- a/MapControl/MapViewTransform.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Media; - -namespace MapControl -{ - public class MapViewTransform : GeneralTransform - { - private readonly GeneralTransform inverse; - - public MapViewTransform() - { - MapTransform = new MercatorTransform(); - inverse = new InverseMapViewTransform(this); - } - - public MapTransform MapTransform { get; set; } - public Transform ViewTransform { get; set; } - - public override GeneralTransform Inverse - { - get { return inverse; } - } - - public override bool TryTransform(Point point, out Point result) - { - result = ViewTransform.Transform(MapTransform.Transform(point)); - return true; - } - - public override Rect TransformBounds(Rect rect) - { - return ViewTransform.TransformBounds(MapTransform.TransformBounds(rect)); - } - - protected override Freezable CreateInstanceCore() - { - return new MapViewTransform(); - } - } - - internal class InverseMapViewTransform : GeneralTransform - { - private readonly MapViewTransform inverse; - - public InverseMapViewTransform(MapViewTransform inverse) - { - this.inverse = inverse; - } - - public override GeneralTransform Inverse - { - get { return inverse; } - } - - public override bool TryTransform(Point point, out Point result) - { - result = inverse.MapTransform.Inverse.Transform(inverse.ViewTransform.Inverse.Transform(point)); - return true; - } - - public override Rect TransformBounds(Rect rect) - { - return inverse.MapTransform.Inverse.TransformBounds(inverse.ViewTransform.Inverse.TransformBounds(rect)); - } - - protected override Freezable CreateInstanceCore() - { - return new InverseMapViewTransform(inverse); - } - } -} diff --git a/MapControl/MercatorTransform.cs b/MapControl/MercatorTransform.cs index 089358ce..2fd59020 100644 --- a/MapControl/MercatorTransform.cs +++ b/MapControl/MercatorTransform.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Windows; using System.Windows.Media; @@ -10,101 +14,50 @@ namespace MapControl /// public class MercatorTransform : MapTransform { - private GeneralTransform inverse = new InverseMercatorTransform(); - - public MercatorTransform() - { - Freeze(); - } - - public override GeneralTransform Inverse - { - get { return inverse; } - } - public override double MaxLatitude { get { return 85.0511; } } - public override double RelativeScale(Point point) + public override double RelativeScale(Location location) { - if (point.Y <= -90d) + if (location.Latitude <= -90d) { return double.NegativeInfinity; } - if (point.Y >= 90d) + if (location.Latitude >= 90d) { return double.PositiveInfinity; } - return 1d / Math.Cos(point.Y * Math.PI / 180d); + return 1d / Math.Cos(location.Latitude * Math.PI / 180d); } - public override bool TryTransform(Point point, out Point result) + public override Point Transform(Location location) { - result = point; + Point result = new Point(location.Longitude, 0d); - if (point.Y <= -90d) + if (location.Latitude <= -90d) { result.Y = double.NegativeInfinity; } - else if (point.Y >= 90d) + else if (location.Latitude >= 90d) { result.Y = double.PositiveInfinity; } else { - double lat = point.Y * Math.PI / 180d; + double lat = location.Latitude * Math.PI / 180d; result.Y = (Math.Log(Math.Tan(lat) + 1d / Math.Cos(lat))) / Math.PI * 180d; } - return true; + return result; } - public override Rect TransformBounds(Rect rect) + public override Location TransformBack(Point point) { - return new Rect(Transform(rect.TopLeft), Transform(rect.BottomRight)); - } - - protected override Freezable CreateInstanceCore() - { - return new MercatorTransform(); - } - } - - /// - /// Transforms cartesian coordinates to latitude and longitude values in degrees - /// according to the inverse Mercator transform. - /// - public class InverseMercatorTransform : GeneralTransform - { - public InverseMercatorTransform() - { - Freeze(); - } - - public override GeneralTransform Inverse - { - get { return new MercatorTransform(); } - } - - public override bool TryTransform(Point point, out Point result) - { - result = point; - result.Y = Math.Atan(Math.Sinh(point.Y * Math.PI / 180d)) / Math.PI * 180d; - return true; - } - - public override Rect TransformBounds(Rect rect) - { - return new Rect(Transform(rect.TopLeft), Transform(rect.BottomRight)); - } - - protected override Freezable CreateInstanceCore() - { - return new InverseMercatorTransform(); + return new Location(Math.Atan(Math.Sinh(point.Y * Math.PI / 180d)) / Math.PI * 180d, point.X); } } } diff --git a/MapControl/Properties/AssemblyInfo.cs b/MapControl/Properties/AssemblyInfo.cs index 3da8f27b..b8a2a5c4 100644 --- a/MapControl/Properties/AssemblyInfo.cs +++ b/MapControl/Properties/AssemblyInfo.cs @@ -6,12 +6,12 @@ using System.Windows; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("MapControl")] -[assembly: AssemblyDescription("MapControl")] +[assembly: AssemblyTitle("Map Control")] +[assembly: AssemblyDescription("WPF Map Control")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Clemens Fischer")] -[assembly: AssemblyProduct("MapControl")] -[assembly: AssemblyCopyright("Copyright © Clemens Fischer 2012")] +[assembly: AssemblyProduct("WPF Map Control")] +[assembly: AssemblyCopyright("Copyright © 2012 Clemens Fischer")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/MapControl/Tile.cs b/MapControl/Tile.cs index 683eb49f..d8a66eb6 100644 --- a/MapControl/Tile.cs +++ b/MapControl/Tile.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Windows; using System.Windows.Media; using System.Windows.Media.Animation; diff --git a/MapControl/TileContainer.cs b/MapControl/TileContainer.cs index 0dfc583e..ae9284a9 100644 --- a/MapControl/TileContainer.cs +++ b/MapControl/TileContainer.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Windows; @@ -22,7 +26,7 @@ namespace MapControl private Int32Rect tileGrid; private TileLayerCollection tileLayers; private readonly DispatcherTimer updateTimer; - private readonly MatrixTransform viewTransform = new MatrixTransform(); + private readonly MatrixTransform viewportTransform = new MatrixTransform(); public TileContainer() { @@ -52,17 +56,17 @@ namespace MapControl } } - public Transform ViewTransform + public Transform ViewportTransform { - get { return viewTransform; } + get { return viewportTransform; } } - public double SetTransform(double mapZoomLevel, double mapRotation, Point mapOrigin, Point viewOrigin, Size viewSize) + public double SetTransform(double mapZoomLevel, double mapRotation, Point mapOrigin, Point viewportOrigin, Size viewportSize) { zoomLevel = mapZoomLevel; rotation = mapRotation; - size = viewSize; - origin = viewOrigin; + size = viewportSize; + origin = viewportOrigin; double scale = Math.Pow(2d, zoomLevel) * 256d / 360d; offset.X = origin.X - (180d + mapOrigin.X) * scale; @@ -72,7 +76,7 @@ namespace MapControl transform.Scale(scale, scale); transform.Translate(offset.X, offset.Y); transform.RotateAt(rotation, origin.X, origin.Y); - viewTransform.Matrix = transform; + viewportTransform.Matrix = transform; transform = GetVisualTransform(); @@ -110,7 +114,7 @@ namespace MapControl int zoom = (int)Math.Floor(zoomLevel + 1d - zoomLevelSwitchOffset); int numTiles = 1 << zoom; double mapToTileScale = (double)numTiles / 360d; - Matrix transform = viewTransform.Matrix; + Matrix transform = viewportTransform.Matrix; transform.Invert(); // view to map coordinates transform.Translate(180d, -180d); transform.Scale(mapToTileScale, -mapToTileScale); // map coordinates to tile indices diff --git a/MapControl/TileImageLoader.cs b/MapControl/TileImageLoader.cs index 8f7cc61a..f9320d54 100644 --- a/MapControl/TileImageLoader.cs +++ b/MapControl/TileImageLoader.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -10,9 +14,13 @@ using System.Windows.Threading; namespace MapControl { + /// + /// Loads map tiles by their URIs and optionally caches their image files in a folder + /// defined by the static TileCacheFolder property. + /// public class TileImageLoader : DispatcherObject { - public static string TileCacheDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapCache"); + public static string TileCacheFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl Cache"); public static TimeSpan TileCacheExpiryAge = TimeSpan.FromDays(1); private readonly Queue pendingTiles = new Queue(); @@ -45,7 +53,7 @@ namespace MapControl lock (pendingTiles) { - if (!string.IsNullOrEmpty(TileCacheDirectory) && + if (!string.IsNullOrEmpty(TileCacheFolder) && !string.IsNullOrEmpty(TileLayerName)) { List expiredTiles = new List(newTiles.Count); @@ -174,7 +182,7 @@ namespace MapControl string tilePath; - if (!string.IsNullOrEmpty(TileCacheDirectory) && + if (!string.IsNullOrEmpty(TileCacheFolder) && !string.IsNullOrEmpty(TileLayerName) && (tilePath = TilePath(tile, decoder)) != null) { @@ -213,7 +221,7 @@ namespace MapControl private string TileDirectory(Tile tile) { - return Path.Combine(TileCacheDirectory, TileLayerName, tile.ZoomLevel.ToString(), tile.XIndex.ToString()); + return Path.Combine(TileCacheFolder, TileLayerName, tile.ZoomLevel.ToString(), tile.XIndex.ToString()); } private string TilePath(Tile tile, BitmapDecoder decoder) @@ -255,7 +263,7 @@ namespace MapControl private static void TraceInformation(string format, params object[] args) { -#if DEBUG +#if TRACE System.Diagnostics.Trace.TraceInformation("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args)); #endif } diff --git a/MapControl/TileLayer.cs b/MapControl/TileLayer.cs index bb7f03dd..9f6685b1 100644 --- a/MapControl/TileLayer.cs +++ b/MapControl/TileLayer.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Collections.Generic; using System.Linq; using System.Windows; @@ -7,11 +11,17 @@ using System.Windows.Media; namespace MapControl { + /// + /// Fills a rectangular area with map tiles from a TileSource. If the IsCached property is true, + /// map tiles are cached in a folder defined by the TileImageLoader.TileCacheFolder property. + /// [ContentProperty("TileSource")] public class TileLayer : DrawingVisual { private readonly TileImageLoader tileImageLoader = new TileImageLoader(); private readonly List tiles = new List(); + private bool isCached = false; + private string name = string.Empty; private string description = string.Empty; private Int32Rect grid; private int zoomLevel; @@ -36,10 +46,24 @@ namespace MapControl set { tileImageLoader.MaxDownloads = value; } } + public bool IsCached + { + get { return isCached; } + set + { + isCached = value; + tileImageLoader.TileLayerName = isCached ? name : null; + } + } + public string Name { - get { return tileImageLoader.TileLayerName; } - set { tileImageLoader.TileLayerName = value; } + get { return name; } + set + { + name = value; + tileImageLoader.TileLayerName = isCached ? name : null; + } } public string Description @@ -136,7 +160,7 @@ namespace MapControl tiles.Sort((t1, t2) => t1.ZoomLevel - t2.ZoomLevel); - System.Diagnostics.Trace.TraceInformation("{0} Tiles: {1}", tiles.Count, string.Join(", ", tiles.Select(t => t.ZoomLevel.ToString()))); + //System.Diagnostics.Trace.TraceInformation("{0} Tiles: {1}", tiles.Count, string.Join(", ", tiles.Select(t => t.ZoomLevel.ToString()))); } private void RenderTiles() diff --git a/MapControl/TileLayerCollection.cs b/MapControl/TileLayerCollection.cs index b3ea04b1..6c374cc1 100644 --- a/MapControl/TileLayerCollection.cs +++ b/MapControl/TileLayerCollection.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.Collections.ObjectModel; using System.Windows.Media; diff --git a/MapControl/TileSource.cs b/MapControl/TileSource.cs index e420a33d..52e7c66b 100644 --- a/MapControl/TileSource.cs +++ b/MapControl/TileSource.cs @@ -1,4 +1,8 @@ -using System; +// WPF MapControl - http://wpfmapcontrol.codeplex.com/ +// Copyright © 2012 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; using System.ComponentModel; using System.Globalization; using System.Text; @@ -6,6 +10,9 @@ using System.Windows; namespace MapControl { + /// + /// Defines the URI of a map tile. + /// [TypeConverter(typeof(TileSourceTypeConverter))] public class TileSource { @@ -90,20 +97,20 @@ namespace MapControl { public override Uri GetUri(int x, int y, int zoomLevel) { - InverseMercatorTransform t = new InverseMercatorTransform(); + MercatorTransform t = new MercatorTransform(); double n = 1 << zoomLevel; double x1 = (double)x * 360d / n - 180d; double x2 = (double)(x + 1) * 360d / n - 180d; double y1 = 180d - (double)(y + 1) * 360d / n; double y2 = 180d - (double)y * 360d / n; - Point p1 = t.Transform(new Point(x1, y1)); - Point p2 = t.Transform(new Point(x2, y2)); + Location p1 = t.TransformBack(new Point(x1, y1)); + Location p2 = t.TransformBack(new Point(x2, y2)); return new Uri(UriFormat. - Replace("{w}", p1.X.ToString(CultureInfo.InvariantCulture)). - Replace("{s}", p1.Y.ToString(CultureInfo.InvariantCulture)). - Replace("{e}", p2.X.ToString(CultureInfo.InvariantCulture)). - Replace("{n}", p2.Y.ToString(CultureInfo.InvariantCulture))); + Replace("{w}", p1.Longitude.ToString(CultureInfo.InvariantCulture)). + Replace("{s}", p1.Latitude.ToString(CultureInfo.InvariantCulture)). + Replace("{e}", p2.Longitude.ToString(CultureInfo.InvariantCulture)). + Replace("{n}", p2.Latitude.ToString(CultureInfo.InvariantCulture))); } } diff --git a/TestApplication/App.xaml b/TestApplication/App.xaml new file mode 100644 index 00000000..be2ad0e5 --- /dev/null +++ b/TestApplication/App.xaml @@ -0,0 +1,7 @@ + + + + diff --git a/TestApplication/App.xaml.cs b/TestApplication/App.xaml.cs new file mode 100644 index 00000000..8da9a87e --- /dev/null +++ b/TestApplication/App.xaml.cs @@ -0,0 +1,8 @@ +using System.Windows; + +namespace MapControlTestApp +{ + public partial class App : Application + { + } +} diff --git a/TestApplication/MainWindow.xaml b/TestApplication/MainWindow.xaml new file mode 100644 index 00000000..5d148b27 --- /dev/null +++ b/TestApplication/MainWindow.xaml @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TestApplication/MainWindow.xaml.cs b/TestApplication/MainWindow.xaml.cs new file mode 100644 index 00000000..40f8774a --- /dev/null +++ b/TestApplication/MainWindow.xaml.cs @@ -0,0 +1,131 @@ +using System; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using MapControl; + +namespace MapControlTestApp +{ + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + + SampleItemCollection items = (SampleItemCollection)Resources["SampleItems"]; + items.Add( + new SamplePolyline + { + Name = "WHV - Eckwarderhörne", + Locations = LocationCollection.Parse("53.5140,8.1451 53.5123,8.1506 53.5156,8.1623 53.5276,8.1757 53.5491,8.1852 53.5495,8.1877 53.5426,8.1993 53.5184,8.2219 53.5182,8.2386 53.5195,8.2387") + }); + items.Add( + new SamplePolygon + { + Name = "JadeWeserPort", + Locations = LocationCollection.Parse("53.5978,8.1212 53.6018,8.1494 53.5859,8.1554 53.5852,8.1531 53.5841,8.1539 53.5802,8.1392 53.5826,8.1309 53.5867,8.1317") + }); + items.Add( + new SamplePushpin + { + Name = "WHV - Eckwarderhörne", + Location = new Location(53.5495, 8.1877) + }); + items.Add( + new SamplePushpin + { + Name = "JadeWeserPort", + Location = new Location(53.5914, 8.14) + }); + items.Add( + new SamplePushpin + { + Name = "Kurhaus Dangast", + Location = new Location(53.447, 8.1114) + }); + items.Add( + new SamplePushpin + { + Name = "Eckwarderhörne", + Location = new Location(53.5207, 8.2323) + }); + items.Add( + new SamplePoint + { + Name = "Steinbake Leitdamm", + Location = new Location(53.51217, 8.16603) + }); + items.Add( + new SamplePoint + { + Name = "Buhne 2", + Location = new Location(53.50926, 8.15815) + }); + items.Add( + new SamplePoint + { + Name = "Buhne 4", + Location = new Location(53.50468, 8.15343) + }); + items.Add( + new SamplePoint + { + Name = "Buhne 6", + Location = new Location(53.50092, 8.15267) + }); + items.Add( + new SamplePoint + { + Name = "Buhne 8", + Location = new Location(53.49871, 8.15321) + }); + items.Add( + new SamplePoint + { + Name = "Buhne 10", + Location = new Location(53.49350, 8.15563) + }); + items.Add( + new SampleShape + { + Name = "N 53°30' E 8°12'", + Location = new Location(53.5, 8.2), + RadiusX = 200d, // meters + RadiusY = 300d, // meters + Rotation = 30d + }); + } + + private void MapManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e) + { + e.TranslationBehavior.DesiredDeceleration = 0.001; + } + + private void MapMouseLeave(object sender, MouseEventArgs e) + { + mouseLocation.Text = string.Empty; + } + + private void MapMouseMove(object sender, MouseEventArgs e) + { + mouseLocation.Text = map.ViewportPointToLocation(e.GetPosition(map)).ToString(); + } + + private void SeamarksClick(object sender, RoutedEventArgs e) + { + TileLayer seamarks = (TileLayer)Resources["SeamarksTileLayer"]; + CheckBox checkBox = (CheckBox)sender; + + if ((bool)checkBox.IsChecked) + { + map.TileLayers.Add(seamarks); + } + else + { + map.TileLayers.Remove(seamarks); + } + } + } +} diff --git a/TestApplication/MapBackgroundConverter.cs b/TestApplication/MapBackgroundConverter.cs new file mode 100644 index 00000000..26ecf976 --- /dev/null +++ b/TestApplication/MapBackgroundConverter.cs @@ -0,0 +1,28 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; +using MapControl; + +namespace MapControlTestApp +{ + class MapBackgroundConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + MapBackground mapBackground = (MapBackground)value; + + if (parameter as string == "Foreground") + { + return mapBackground == MapBackground.Light ? Brushes.Black : Brushes.White; + } + + return mapBackground == MapBackground.Light ? Brushes.White : Brushes.Black; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/TestApplication/Properties/AssemblyInfo.cs b/TestApplication/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..da1180b2 --- /dev/null +++ b/TestApplication/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +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("TestApplication")] +[assembly: AssemblyDescription("WPF Map Control Test Application")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Clemens Fischer")] +[assembly: AssemblyProduct("WPF Map Control")] +[assembly: AssemblyCopyright("Copyright © 2012 Clemens Fischer")] +[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)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// 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")] diff --git a/TestApplication/SampleItems.cs b/TestApplication/SampleItems.cs new file mode 100644 index 00000000..acf41317 --- /dev/null +++ b/TestApplication/SampleItems.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Controls; +using MapControl; + +namespace MapControlTestApp +{ + class SampleItem + { + public string Name { get; set; } + } + + class SamplePoint : SampleItem + { + public Location Location { get; set; } + } + + class SamplePushpin : SamplePoint + { + } + + class SampleShape : SamplePoint + { + public double RadiusX { get; set; } + public double RadiusY { get; set; } + public double Rotation { get; set; } + } + + class SamplePolyline : SampleItem + { + public LocationCollection Locations { get; set; } + } + + class SamplePolygon : SampleItem + { + public LocationCollection Locations { get; set; } + } + + class SampleItemCollection : ObservableCollection + { + } + + class SampleItemStyleSelector : StyleSelector + { + public override Style SelectStyle(object item, DependencyObject container) + { + if (item is SamplePolyline) + { + return Application.Current.Windows[0].Resources["SamplePolylineItemStyle"] as Style; + } + + if (item is SamplePolygon) + { + return Application.Current.Windows[0].Resources["SamplePolygonItemStyle"] as Style; + } + + if (item is SampleShape) + { + return Application.Current.Windows[0].Resources["SampleShapeItemStyle"] as Style; + } + + if (item is SamplePushpin) + { + return Application.Current.Windows[0].Resources["SamplePushpinItemStyle"] as Style; + } + + return Application.Current.Windows[0].Resources["SamplePointItemStyle"] as Style; + } + } +} diff --git a/TestApplication/TestApplication.csproj b/TestApplication/TestApplication.csproj new file mode 100644 index 00000000..07180684 --- /dev/null +++ b/TestApplication/TestApplication.csproj @@ -0,0 +1,87 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {CCBCDAE5-E68F-43A8-930A-0749E476D29D} + WinExe + Properties + MapControlTestApp + MapControlTestApp + v4.0 + Client + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + none + true + bin\Release\ + TRACE + prompt + 4 + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + + + + {06481252-2310-414A-B9FC-D5739FDF6BD3} + MapControl + + + + + \ No newline at end of file