diff --git a/MapControl/Map.cs b/MapControl/Map.cs index 1d03449e..5a142f10 100644 --- a/MapControl/Map.cs +++ b/MapControl/Map.cs @@ -3,6 +3,8 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; +using System.Collections.Specialized; +using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; @@ -41,7 +43,7 @@ namespace MapControl public static readonly DependencyProperty TileLayersProperty = DependencyProperty.Register( "TileLayers", typeof(TileLayerCollection), typeof(Map), new FrameworkPropertyMetadata( - (o, e) => ((Map)o).TileLayersPropertyChanged((TileLayerCollection)e.NewValue), + (o, e) => ((Map)o).TileLayersPropertyChanged((TileLayerCollection)e.OldValue, (TileLayerCollection)e.NewValue), (o, v) => ((Map)o).CoerceTileLayersProperty((TileLayerCollection)v))); public static readonly DependencyProperty MainTileLayerProperty = DependencyProperty.Register( @@ -122,14 +124,9 @@ namespace MapControl } /// - /// Raised when the ViewTransform property has changed. + /// Raised when the current viewport has changed. /// - public event EventHandler ViewTransformChanged; - - /// - /// Raised when the TileLayers property has changed. - /// - public event EventHandler TileLayersChanged; + public event Action ViewportChanged; public double MinZoomLevel { get; set; } public double MaxZoomLevel { get; set; } @@ -258,7 +255,7 @@ namespace MapControl } /// - /// Gets or sets the map rotation angle in degrees. + /// Gets or sets the map heading, or clockwise rotation angle in degrees. /// public double Heading { @@ -381,7 +378,7 @@ namespace MapControl if (transformOrigin != null) { viewportOrigin += translation; - UpdateViewTransform(); + UpdateTransform(); } else { @@ -404,7 +401,7 @@ namespace MapControl Heading = (((Heading + rotation) % 360d) + 360d) % 360d; ZoomLevel += Math.Log(scale, 2d); updateTransform = true; - UpdateViewTransform(); + UpdateTransform(); } TranslateMap(translation); @@ -448,7 +445,7 @@ namespace MapControl base.OnRenderSizeChanged(sizeInfo); ResetTransformOrigin(); - UpdateViewTransform(); + UpdateTransform(); } protected override void OnRender(DrawingContext drawingContext) @@ -456,21 +453,89 @@ namespace MapControl drawingContext.DrawRectangle(Background, null, new Rect(RenderSize)); } - protected override void OnViewTransformChanged(Map map) + protected override void OnViewportChanged() { - base.OnViewTransformChanged(map); + base.OnViewportChanged(); - if (ViewTransformChanged != null) + if (ViewportChanged != null) { - ViewTransformChanged(this, EventArgs.Empty); + ViewportChanged(); } } - protected internal virtual void OnTileLayersChanged() + private void TileLayerCollectionChanged(object sender, NotifyCollectionChangedEventArgs eventArgs) { - if (tileContainer.TileLayers != null && - tileContainer.TileLayers.Count > 0 && - tileContainer.TileLayers[0].HasDarkBackground) + switch (eventArgs.Action) + { + case NotifyCollectionChangedAction.Add: + tileContainer.AddTileLayers(eventArgs.NewStartingIndex, eventArgs.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Remove: + tileContainer.RemoveTileLayers(eventArgs.OldStartingIndex, eventArgs.OldItems.Cast()); + break; + + case NotifyCollectionChangedAction.Move: + case NotifyCollectionChangedAction.Replace: + tileContainer.RemoveTileLayers(eventArgs.OldStartingIndex, eventArgs.OldItems.Cast()); + tileContainer.AddTileLayers(eventArgs.NewStartingIndex, eventArgs.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Reset: + tileContainer.ClearTileLayers(); + if (eventArgs.NewItems != null) + { + tileContainer.AddTileLayers(0, eventArgs.NewItems.Cast()); + } + break; + } + + UpdateMainTileLayer(); + } + + private void TileLayersPropertyChanged(TileLayerCollection oldTileLayers, TileLayerCollection newTileLayers) + { + tileContainer.ClearTileLayers(); + + if (oldTileLayers != null) + { + oldTileLayers.CollectionChanged -= TileLayerCollectionChanged; + } + + if (newTileLayers != null) + { + newTileLayers.CollectionChanged += TileLayerCollectionChanged; + tileContainer.AddTileLayers(0, newTileLayers); + } + + UpdateMainTileLayer(); + } + + private TileLayerCollection CoerceTileLayersProperty(TileLayerCollection tileLayers) + { + if (tileLayers == null) + { + tileLayers = new TileLayerCollection(); + } + + return tileLayers; + } + + private void MainTileLayerPropertyChanged(TileLayer mainTileLayer) + { + if (mainTileLayer != null) + { + if (TileLayers.Count == 0) + { + TileLayers.Add(mainTileLayer); + } + else if (TileLayers[0] != mainTileLayer) + { + TileLayers[0] = mainTileLayer; + } + } + + if (mainTileLayer != null && mainTileLayer.HasDarkBackground) { if (DarkForeground != null) { @@ -494,63 +559,34 @@ namespace MapControl Background = LightBackground; } } - - if (TileLayersChanged != null) - { - TileLayersChanged(this, EventArgs.Empty); - } - } - - private void TileLayersPropertyChanged(TileLayerCollection tileLayers) - { - if (tileLayers != null) - { - tileContainer.TileLayers = tileLayers; - MainTileLayer = tileLayers.Count > 0 ? tileLayers[0] : null; - } - } - - private TileLayerCollection CoerceTileLayersProperty(TileLayerCollection tileLayers) - { - if (tileLayers == null) - { - tileLayers = new TileLayerCollection(); - } - - return tileLayers; - } - - private void MainTileLayerPropertyChanged(TileLayer mainTileLayer) - { - if (mainTileLayer != null) - { - if (tileContainer.TileLayers.Count == 0) - { - tileContainer.TileLayers.Add(mainTileLayer); - } - else if (tileContainer.TileLayers[0] != mainTileLayer) - { - tileContainer.TileLayers[0] = mainTileLayer; - } - } } private TileLayer CoerceMainTileLayerProperty(TileLayer mainTileLayer) { - if (mainTileLayer == null && tileContainer.TileLayers.Count > 0) + if (mainTileLayer == null && TileLayers.Count > 0) { - mainTileLayer = tileContainer.TileLayers[0]; + mainTileLayer = TileLayers[0]; } return mainTileLayer; } + private void UpdateMainTileLayer() + { + TileLayer mainTileLayer = TileLayers.FirstOrDefault(); + + if (MainTileLayer != mainTileLayer) + { + MainTileLayer = mainTileLayer; + } + } + private void CenterPropertyChanged(Location center) { if (updateTransform) { ResetTransformOrigin(); - UpdateViewTransform(); + UpdateTransform(); } if (centerAnimation == null) @@ -600,7 +636,7 @@ namespace MapControl { if (updateTransform) { - UpdateViewTransform(); + UpdateTransform(); } if (zoomLevelAnimation == null) @@ -649,7 +685,7 @@ namespace MapControl { if (updateTransform) { - UpdateViewTransform(); + UpdateTransform(); } if (headingAnimation == null) @@ -704,20 +740,20 @@ namespace MapControl return ((heading % 360d) + 360d) % 360d; } - private void UpdateViewTransform() + private void UpdateTransform() { double scale; if (transformOrigin != null) { - scale = tileContainer.SetTransform(ZoomLevel, Heading, MapTransform.Transform(transformOrigin), viewportOrigin, RenderSize); + scale = tileContainer.SetViewportTransform(ZoomLevel, Heading, MapTransform.Transform(transformOrigin), viewportOrigin, RenderSize); updateTransform = false; Center = ViewportPointToLocation(new Point(RenderSize.Width / 2d, RenderSize.Height / 2d)); updateTransform = true; } else { - scale = tileContainer.SetTransform(ZoomLevel, Heading, MapTransform.Transform(Center), viewportOrigin, RenderSize); + scale = tileContainer.SetViewportTransform(ZoomLevel, Heading, MapTransform.Transform(Center), viewportOrigin, RenderSize); } scale *= MapTransform.RelativeScale(Center) / MeterPerDegree; // Pixels per meter at center latitude @@ -728,7 +764,7 @@ namespace MapControl rotateTransform.Angle = Heading; scaleRotateTransform.Matrix = scaleTransform.Value * rotateTransform.Value; - OnViewTransformChanged(this); + OnViewportChanged(); } } } diff --git a/MapControl/MapElement.cs b/MapControl/MapElement.cs index 3366c188..d31e28ab 100644 --- a/MapControl/MapElement.cs +++ b/MapControl/MapElement.cs @@ -7,16 +7,17 @@ using System.Windows; namespace MapControl { - internal interface INotifyParentMapChanged - { - void ParentMapChanged(Map oldParentMap, Map newParentMap); - } - /// /// Base class for child elements of a MapPanel. /// - public abstract class MapElement : FrameworkElement, INotifyParentMapChanged + public abstract class MapElement : FrameworkElement { + static MapElement() + { + MapPanel.ParentMapProperty.OverrideMetadata(typeof(MapElement), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, ParentMapPropertyChanged)); + } + protected MapElement() { HorizontalAlignment = HorizontalAlignment.Stretch; @@ -28,23 +29,26 @@ namespace MapControl get { return MapPanel.GetParentMap(this); } } - protected abstract void OnViewTransformChanged(Map parentMap); + protected abstract void OnViewportChanged(); - private void OnViewTransformChanged(object sender, EventArgs eventArgs) + private static void ParentMapPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs eventArgs) { - OnViewTransformChanged((Map)sender); - } + MapElement mapElement = obj as MapElement; - void INotifyParentMapChanged.ParentMapChanged(Map oldParentMap, Map newParentMap) - { - if (oldParentMap != null) + if (mapElement != null) { - oldParentMap.ViewTransformChanged -= OnViewTransformChanged; - } + Map oldParentMap = eventArgs.OldValue as Map; + Map newParentMap = eventArgs.NewValue as Map; - if (newParentMap != null) - { - newParentMap.ViewTransformChanged += OnViewTransformChanged; + if (oldParentMap != null) + { + oldParentMap.ViewportChanged -= mapElement.OnViewportChanged; + } + + if (newParentMap != null) + { + newParentMap.ViewportChanged += mapElement.OnViewportChanged; + } } } } diff --git a/MapControl/MapGraticule.cs b/MapControl/MapGraticule.cs index 981df69d..4b0be759 100644 --- a/MapControl/MapGraticule.cs +++ b/MapControl/MapGraticule.cs @@ -125,8 +125,9 @@ namespace MapControl return visual; } - protected override void OnViewTransformChanged(Map parentMap) + protected override void OnViewportChanged() { + Map 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); @@ -195,7 +196,7 @@ namespace MapControl private void UpdateBrush() { pen.Brush = null; - OnViewTransformChanged(ParentMap); + OnViewportChanged(); } private static string CoordinateString(double value, string format, string hemispheres) diff --git a/MapControl/MapPanel.cs b/MapControl/MapPanel.cs index d6dfa99e..8ccda801 100644 --- a/MapControl/MapPanel.cs +++ b/MapControl/MapPanel.cs @@ -15,7 +15,7 @@ namespace MapControl /// coordinates. IsInsideMapBounds indicates if the viewport coordinates are located /// inside the visible part of the map. /// - public class MapPanel : Panel, INotifyParentMapChanged + public class MapPanel : Panel { public static readonly DependencyProperty ParentMapProperty = DependencyProperty.RegisterAttached( "ParentMap", typeof(Map), typeof(MapPanel), @@ -102,8 +102,10 @@ namespace MapControl return finalSize; } - protected virtual void OnViewTransformChanged(Map parentMap) + protected virtual void OnViewportChanged() { + Map parentMap = ParentMap; + foreach (UIElement element in InternalChildren) { Location location = GetLocation(element); @@ -115,31 +117,24 @@ namespace MapControl } } - private void OnViewTransformChanged(object sender, EventArgs eventArgs) - { - OnViewTransformChanged((Map)sender); - } - - void INotifyParentMapChanged.ParentMapChanged(Map oldParentMap, Map newParentMap) - { - if (oldParentMap != null && oldParentMap != this) - { - oldParentMap.ViewTransformChanged -= OnViewTransformChanged; - } - - if (newParentMap != null && newParentMap != this) - { - newParentMap.ViewTransformChanged += OnViewTransformChanged; - } - } - private static void ParentMapPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs eventArgs) { - INotifyParentMapChanged notifyChanged = obj as INotifyParentMapChanged; + MapPanel mapPanel = obj as MapPanel; - if (notifyChanged != null) + if (mapPanel != null) { - notifyChanged.ParentMapChanged(eventArgs.OldValue as Map, eventArgs.NewValue as Map); + Map oldParentMap = eventArgs.OldValue as Map; + Map newParentMap = eventArgs.NewValue as Map; + + if (oldParentMap != null && oldParentMap != mapPanel) + { + oldParentMap.ViewportChanged -= mapPanel.OnViewportChanged; + } + + if (newParentMap != null && newParentMap != mapPanel) + { + newParentMap.ViewportChanged += mapPanel.OnViewportChanged; + } } } diff --git a/MapControl/MapPolyline.cs b/MapControl/MapPolyline.cs index 6e532a13..33d58400 100644 --- a/MapControl/MapPolyline.cs +++ b/MapControl/MapPolyline.cs @@ -156,13 +156,13 @@ namespace MapControl AddVisualChild(visual); } - protected override void OnViewTransformChanged(Map parentMap) + protected override void OnViewportChanged() { double scale = 1d; if (TransformStroke) { - scale = parentMap.CenterScale * Map.MeterPerDegree; + scale = ParentMap.CenterScale * Map.MeterPerDegree; } drawing.Pen.Thickness = scale * StrokeThickness; @@ -175,12 +175,10 @@ namespace MapControl protected void UpdateGeometry(bool closed) { - Map parentMap = MapPanel.GetParentMap(this); - - if (parentMap != null && Locations != null && Locations.Count > 0) + if (ParentMap != null && Locations != null && Locations.Count > 0) { - drawing.Geometry = CreateGeometry(parentMap, Locations, closed); - OnViewTransformChanged(parentMap); + drawing.Geometry = CreateGeometry(Locations, closed); + OnViewportChanged(); } else { @@ -190,28 +188,26 @@ namespace MapControl private void UpdatePenThickness() { - Map parentMap = MapPanel.GetParentMap(this); - - if (parentMap != null) + if (ParentMap != null) { - OnViewTransformChanged(parentMap); + OnViewportChanged(); } } - private Geometry CreateGeometry(Map parentMap, LocationCollection locations, bool closed) + private Geometry CreateGeometry(LocationCollection locations, bool closed) { StreamGeometry geometry = new StreamGeometry { - Transform = parentMap.ViewportTransform + Transform = ParentMap.ViewportTransform }; using (StreamGeometryContext sgc = geometry.Open()) { - sgc.BeginFigure(parentMap.MapTransform.Transform(locations.First()), closed, closed); + sgc.BeginFigure(ParentMap.MapTransform.Transform(locations.First()), closed, closed); if (Locations.Count > 1) { - sgc.PolyLineTo(parentMap.MapTransform.Transform(locations.Skip(1)), true, true); + sgc.PolyLineTo(ParentMap.MapTransform.Transform(locations.Skip(1)), true, true); } } diff --git a/MapControl/TileContainer.cs b/MapControl/TileContainer.cs index 05a212d7..b5d47574 100644 --- a/MapControl/TileContainer.cs +++ b/MapControl/TileContainer.cs @@ -3,8 +3,7 @@ // Licensed under the Microsoft Public License (Ms-PL) using System; -using System.Collections; -using System.Collections.Specialized; +using System.Collections.Generic; using System.Windows; using System.Windows.Media; using System.Windows.Threading; @@ -23,44 +22,56 @@ namespace MapControl private double zoomLevel; private int tileZoomLevel; private Int32Rect tileGrid; - private TileLayerCollection tileLayers; private readonly DispatcherTimer updateTimer; - private readonly MatrixTransform viewportTransform = new MatrixTransform(); + + public readonly MatrixTransform ViewportTransform = new MatrixTransform(); public TileContainer() { updateTimer = new DispatcherTimer(TimeSpan.FromSeconds(0.5), DispatcherPriority.Background, UpdateTiles, Dispatcher); } - public TileLayerCollection TileLayers + public void AddTileLayers(int index, IEnumerable tileLayers) { - get { return tileLayers; } - set + Matrix transform = GetVisualTransform(); + + foreach (TileLayer tileLayer in tileLayers) { - if (tileLayers != null) + if (string.IsNullOrEmpty(tileLayer.Name)) { - tileLayers.CollectionChanged -= TileLayersChanged; + throw new ArgumentException("TileLayer.Name property must not be null or empty."); } - tileLayers = value; - ClearChildren(); - - if (tileLayers != null) - { - tileLayers.CollectionChanged += TileLayersChanged; - AddChildren(0, tileLayers); - } - - ((Map)VisualParent).OnTileLayersChanged(); + Children.Insert(index++, tileLayer); + tileLayer.TransformMatrix = transform; + tileLayer.UpdateTiles(tileZoomLevel, tileGrid); } } - public Transform ViewportTransform + public void RemoveTileLayers(int index, IEnumerable tileLayers) { - get { return viewportTransform; } + int count = 0; + + foreach (TileLayer tileLayer in tileLayers) + { + tileLayer.ClearTiles(); + count++; + } + + Children.RemoveRange(index, count); } - public double SetTransform(double mapZoomLevel, double mapRotation, Point mapOrigin, Point viewportOrigin, Size viewportSize) + public void ClearTileLayers() + { + foreach (TileLayer tileLayer in Children) + { + tileLayer.ClearTiles(); + } + + Children.Clear(); + } + + public double SetViewportTransform(double mapZoomLevel, double mapRotation, Point mapOrigin, Point viewportOrigin, Size viewportSize) { zoomLevel = mapZoomLevel; rotation = mapRotation; @@ -75,16 +86,13 @@ namespace MapControl transform.Scale(scale, scale); transform.Translate(offset.X, offset.Y); transform.RotateAt(rotation, origin.X, origin.Y); - viewportTransform.Matrix = transform; + ViewportTransform.Matrix = transform; transform = GetVisualTransform(); - if (tileLayers != null) + foreach (TileLayer tileLayer in Children) { - foreach (TileLayer tileLayer in tileLayers) - { - tileLayer.TransformMatrix = transform; - } + tileLayer.TransformMatrix = transform; } updateTimer.IsEnabled = true; @@ -113,7 +121,7 @@ namespace MapControl int zoom = (int)Math.Floor(zoomLevel + 1d - zoomLevelSwitchOffset); int numTiles = 1 << zoom; double mapToTileScale = (double)numTiles / 360d; - Matrix transform = viewportTransform.Matrix; + Matrix transform = ViewportTransform.Matrix; transform.Invert(); // view to map coordinates transform.Translate(180d, -180d); transform.Scale(mapToTileScale, -mapToTileScale); // map coordinates to tile indices @@ -142,77 +150,12 @@ namespace MapControl tileGrid = grid; transform = GetVisualTransform(); - if (tileLayers != null) + foreach (TileLayer tileLayer in Children) { - foreach (TileLayer tileLayer in tileLayers) - { - tileLayer.TransformMatrix = transform; - tileLayer.UpdateTiles(tileZoomLevel, tileGrid); - } + tileLayer.TransformMatrix = transform; + tileLayer.UpdateTiles(tileZoomLevel, tileGrid); } } } - - private void TileLayersChanged(object sender, NotifyCollectionChangedEventArgs eventArgs) - { - switch (eventArgs.Action) - { - case NotifyCollectionChangedAction.Add: - AddChildren(eventArgs.NewStartingIndex, eventArgs.NewItems); - break; - - case NotifyCollectionChangedAction.Remove: - RemoveChildren(eventArgs.OldStartingIndex, eventArgs.OldItems); - break; - - case NotifyCollectionChangedAction.Move: - case NotifyCollectionChangedAction.Replace: - RemoveChildren(eventArgs.OldStartingIndex, eventArgs.OldItems); - AddChildren(eventArgs.NewStartingIndex, eventArgs.NewItems); - break; - - case NotifyCollectionChangedAction.Reset: - ClearChildren(); - if (eventArgs.NewItems != null) - { - AddChildren(0, eventArgs.NewItems); - } - break; - } - - ((Map)VisualParent).OnTileLayersChanged(); - } - - private void AddChildren(int index, IList layers) - { - Matrix transform = GetVisualTransform(); - - foreach (TileLayer tileLayer in layers) - { - Children.Insert(index++, tileLayer); - tileLayer.TransformMatrix = transform; - tileLayer.UpdateTiles(tileZoomLevel, tileGrid); - } - } - - private void RemoveChildren(int index, IList layers) - { - foreach (TileLayer tileLayer in layers) - { - tileLayer.ClearTiles(); - } - - Children.RemoveRange(index, layers.Count); - } - - private void ClearChildren() - { - foreach (TileLayer tileLayer in Children) - { - tileLayer.ClearTiles(); - } - - Children.Clear(); - } } } diff --git a/MapControl/TileLayer.cs b/MapControl/TileLayer.cs index 9ebd4db0..6fa1c99b 100644 --- a/MapControl/TileLayer.cs +++ b/MapControl/TileLayer.cs @@ -27,7 +27,6 @@ namespace MapControl tileImageLoader = new TileImageLoader(this); VisualEdgeMode = EdgeMode.Aliased; VisualTransform = new MatrixTransform(); - Name = string.Empty; MinZoomLevel = 1; MaxZoomLevel = 18; MaxParallelDownloads = 8; @@ -59,7 +58,7 @@ namespace MapControl tileImageLoader.CancelGetTiles(); - if (VisualParent != null && TileSource != null) + if (TileSource != null) { SelectTiles(); RenderTiles(); @@ -76,11 +75,11 @@ namespace MapControl private void SelectTiles() { - TileContainer tileContainer = VisualParent as TileContainer; int maxZoomLevel = Math.Min(zoomLevel, MaxZoomLevel); int minZoomLevel = maxZoomLevel; + ContainerVisual parent = Parent as ContainerVisual; - if (tileContainer != null && tileContainer.TileLayers.IndexOf(this) == 0) + if (parent != null && parent.Children.IndexOf(this) == 0) { minZoomLevel = MinZoomLevel; } diff --git a/SampleApps/SampleApplication/MainWindow.xaml b/SampleApps/SampleApplication/MainWindow.xaml index 6010075d..06dfa6ac 100644 --- a/SampleApps/SampleApplication/MainWindow.xaml +++ b/SampleApps/SampleApplication/MainWindow.xaml @@ -11,6 +11,7 @@ + @@ -64,6 +65,7 @@