diff --git a/MapControl/MapItem.cs b/MapControl/MapItem.cs index df8fb8e7..052b8da0 100644 --- a/MapControl/MapItem.cs +++ b/MapControl/MapItem.cs @@ -43,16 +43,9 @@ namespace MapControl IsEnabledChanged += IsEnabledPropertyChanged; } - public event RoutedEventHandler Selected + public Map ParentMap { - add { AddHandler(SelectedEvent, value); } - remove { RemoveHandler(SelectedEvent, value); } - } - - public event RoutedEventHandler Unselected - { - add { AddHandler(UnselectedEvent, value); } - remove { RemoveHandler(UnselectedEvent, value); } + get { return MapPanel.GetParentMap(this); } } public bool IsSelected @@ -66,9 +59,16 @@ namespace MapControl get { return MapItemsControl.GetIsCurrent(this); } } - public Map ParentMap + public event RoutedEventHandler Selected { - get { return MapPanel.GetParentMap(this); } + add { AddHandler(SelectedEvent, value); } + remove { RemoveHandler(SelectedEvent, value); } + } + + public event RoutedEventHandler Unselected + { + add { AddHandler(UnselectedEvent, value); } + remove { RemoveHandler(UnselectedEvent, value); } } protected override void OnMouseEnter(MouseEventArgs e) diff --git a/MapControl/MapItemsControl.cs b/MapControl/MapItemsControl.cs index cacd1a50..24ad37aa 100644 --- a/MapControl/MapItemsControl.cs +++ b/MapControl/MapItemsControl.cs @@ -40,6 +40,11 @@ namespace MapControl Items.CurrentChanged += OnCurrentItemChanged; } + public Map ParentMap + { + get { return MapPanel.GetParentMap(this); } + } + public SelectionMode SelectionMode { get { return (SelectionMode)GetValue(SelectionModeProperty); } @@ -74,7 +79,7 @@ namespace MapControl public IList GetItemsInGeometry(Geometry geometry) { - return GetItemsInGeometry(geometry, new ArrayList(Items.Count), Items.Count); + return GetItemsInGeometry(geometry, new ArrayList()); } protected override bool IsItemItsOwnContainerOverride(object item) @@ -111,15 +116,39 @@ namespace MapControl { e.Handled = true; UIElement container = (UIElement)sender; + UIElement selectedContainer; - if (SelectionMode == SelectionMode.Extended && - (Keyboard.Modifiers & (ModifierKeys.Control | ModifierKeys.Shift)) == 0) + if (SelectionMode != SelectionMode.Extended || (Keyboard.Modifiers & ModifierKeys.Control) != 0) + { + Selector.SetIsSelected(container, !Selector.GetIsSelected(container)); + } + else if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0) { SelectedItem = GetItem(container); } - else + else if ((selectedContainer = GetContainer(SelectedItem)) != null) { - Selector.SetIsSelected(container, !Selector.GetIsSelected(container)); + ViewportPosition p1 = MapPanel.GetViewportPosition(selectedContainer); + ViewportPosition p2 = MapPanel.GetViewportPosition(container); + + if (p1 != null && p2 != null) + { + Rect rect = new Rect(p1.Position, p2.Position); + + BeginUpdateSelectedItems(); + SelectedItems.Clear(); + SelectedItems.Add(SelectedItem); + + foreach (object item in Items) + { + if (item != SelectedItem && IsItemInRect(item, rect)) + { + SelectedItems.Add(item); + } + } + + EndUpdateSelectedItems(); + } } } @@ -161,46 +190,68 @@ namespace MapControl { if (SelectionMode == SelectionMode.Single) { - IList items = GetItemsInGeometry(geometry, new ArrayList(1), 1); - SelectedItem = items.Count > 0 ? items[0] : null; + SelectedItem = GetFirstItemInGeometry(geometry); } else { BeginUpdateSelectedItems(); - GetItemsInGeometry(geometry, SelectedItems, Items.Count); + SelectedItems.Clear(); + GetItemsInGeometry(geometry, SelectedItems); EndUpdateSelectedItems(); } } } - private IList GetItemsInGeometry(Geometry geometry, IList items, int maxItems) + private object GetFirstItemInGeometry(Geometry geometry) { - items.Clear(); - if (!geometry.IsEmpty()) { foreach (object item in Items) { - UIElement container = GetContainer(item); - - if (container != null) + if (IsItemInGeometry(item, geometry)) { - ViewportPosition viewportPosition = MapPanel.GetViewportPosition(container); + return item; + } + } + } - if (viewportPosition != null && geometry.FillContains(viewportPosition.Position)) - { - items.Add(item); + return null; + } - if (items.Count >= maxItems) - { - break; - } - } + private IList GetItemsInGeometry(Geometry geometry, IList items) + { + if (!geometry.IsEmpty()) + { + foreach (object item in Items) + { + if (IsItemInGeometry(item, geometry)) + { + items.Add(item); } } } return items; } + + private bool IsItemInGeometry(object item, Geometry geometry) + { + UIElement container = GetContainer(item); + ViewportPosition viewportPosition; + + return container != null + && (viewportPosition = MapPanel.GetViewportPosition(container)) != null + && geometry.FillContains(viewportPosition.Position); + } + + private bool IsItemInRect(object item, Rect rect) + { + UIElement container = GetContainer(item); + ViewportPosition viewportPosition; + + return container != null + && (viewportPosition = MapPanel.GetViewportPosition(container)) != null + && rect.Contains(viewportPosition.Position); + } } } diff --git a/MapControl/Pushpin.cs b/MapControl/Pushpin.cs index 4c48ad07..e2bf7812 100644 --- a/MapControl/Pushpin.cs +++ b/MapControl/Pushpin.cs @@ -23,6 +23,11 @@ namespace MapControl typeof(Pushpin), new FrameworkPropertyMetadata(typeof(Pushpin))); } + public Map ParentMap + { + get { return MapPanel.GetParentMap(this); } + } + public Location Location { get { return (Location)GetValue(LocationProperty); } diff --git a/MapControl/TileImageLoader.cs b/MapControl/TileImageLoader.cs index 111a8cc6..d1eb3e76 100644 --- a/MapControl/TileImageLoader.cs +++ b/MapControl/TileImageLoader.cs @@ -19,10 +19,11 @@ namespace MapControl /// /// Loads map tile images by their URIs and optionally caches the images in an ObjectCache. /// - public class TileImageLoader : DispatcherObject + public class TileImageLoader { private readonly TileLayer tileLayer; private readonly ConcurrentQueue pendingTiles = new ConcurrentQueue(); + private int downloadThreadCount; /// /// Default Name of an ObjectCache instance that is assigned to the Cache property. @@ -90,7 +91,7 @@ namespace MapControl newTiles.ForEach(tile => { - Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => tile.Image = imageTileSource.GetImage(tile.XIndex, tile.Y, tile.ZoomLevel))); + tileLayer.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => tile.Image = imageTileSource.GetImage(tile.XIndex, tile.Y, tile.ZoomLevel))); }); } else @@ -106,19 +107,19 @@ namespace MapControl newTiles.ForEach(tile => { string key = CacheKey(tile); - byte[] imageBuffer = Cache.Get(key) as byte[]; + byte[] buffer = Cache.Get(key) as byte[]; - if (imageBuffer == null) + if (buffer == null) { pendingTiles.Enqueue(tile); } - else if (!CreateTileImage(tile, imageBuffer)) + else if (!CreateTileImage(tile, buffer)) { // got corrupted buffer from cache Cache.Remove(key); pendingTiles.Enqueue(tile); } - else if (IsCacheOutdated(imageBuffer)) + else if (IsCacheOutdated(buffer)) { // update cached image outdatedTiles.Add(tile); @@ -128,28 +129,31 @@ namespace MapControl outdatedTiles.ForEach(tile => pendingTiles.Enqueue(tile)); } - int numDownloads = Math.Min(pendingTiles.Count, tileLayer.MaxParallelDownloads); - - while (--numDownloads >= 0) + while (downloadThreadCount < Math.Min(pendingTiles.Count, tileLayer.MaxParallelDownloads)) { + Interlocked.Increment(ref downloadThreadCount); + ThreadPool.QueueUserWorkItem(DownloadTiles); } } } + private void DownloadTiles(object o) { Tile tile; while (pendingTiles.TryDequeue(out tile)) { tile.Uri = tileLayer.TileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel); - byte[] imageBuffer = DownloadImage(tile); + byte[] buffer = DownloadImage(tile.Uri); - if (imageBuffer != null && CreateTileImage(tile, imageBuffer) && Cache != null) + if (buffer != null && CreateTileImage(tile, buffer) && Cache != null) { - Cache.Set(CacheKey(tile), imageBuffer, new CacheItemPolicy { SlidingExpiration = CacheExpiration }); + Cache.Set(CacheKey(tile), buffer, new CacheItemPolicy { SlidingExpiration = CacheExpiration }); } } + + Interlocked.Decrement(ref downloadThreadCount); } private string CacheKey(Tile tile) @@ -157,18 +161,39 @@ namespace MapControl return string.Format("{0}/{1}/{2}/{3}", tileLayer.Name, tile.ZoomLevel, tile.XIndex, tile.Y); } - private byte[] DownloadImage(Tile tile) + private bool CreateTileImage(Tile tile, byte[] buffer) + { + BitmapImage bitmap = new BitmapImage(); + + try + { + using (Stream stream = new MemoryStream(buffer, 8, buffer.Length - 8, false)) + { + bitmap.BeginInit(); + bitmap.CacheOption = BitmapCacheOption.OnLoad; + bitmap.StreamSource = stream; + bitmap.EndInit(); + bitmap.Freeze(); + } + } + catch (Exception ex) + { + Trace.TraceWarning("Creating tile image failed: {0}", ex.Message); + return false; + } + + tileLayer.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => tile.Image = bitmap)); + return true; + } + + private static byte[] DownloadImage(Uri uri) { - HttpWebRequest request = null; byte[] buffer = null; try { - TraceInformation("{0} - Requesting", tile.Uri); - - request = (HttpWebRequest)WebRequest.Create(tile.Uri); + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); request.UserAgent = typeof(TileImageLoader).ToString(); - request.KeepAlive = true; using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) using (Stream responseStream = response.GetResponseStream()) @@ -185,67 +210,36 @@ namespace MapControl } } - TraceInformation("{0} - Completed", tile.Uri); + Trace.TraceInformation("Downloaded {0}", uri); } catch (WebException ex) { if (ex.Status == WebExceptionStatus.ProtocolError) { - TraceInformation("{0} - {1}", tile.Uri, ((HttpWebResponse)ex.Response).StatusCode); + HttpStatusCode statusCode = ((HttpWebResponse)ex.Response).StatusCode; + if (statusCode != HttpStatusCode.NotFound) + { + Trace.TraceInformation("Downloading {0} failed: {1}", uri, ex.Message); + } } else { - TraceWarning("{0} - {1}", tile.Uri, ex.Status); + Trace.TraceWarning("Downloading {0} failed with {1}: {2}", uri, ex.Status, ex.Message); } } catch (Exception ex) { - TraceWarning("{0} - {1}", tile.Uri, ex.Message); + Trace.TraceWarning("Downloading {0} failed: {1}", uri, ex.Message); } return buffer; } - private bool IsCacheOutdated(byte[] imageBuffer) + private static bool IsCacheOutdated(byte[] buffer) { - long creationTime = BitConverter.ToInt64(imageBuffer, 0); + long creationTime = BitConverter.ToInt64(buffer, 0); return DateTime.FromBinary(creationTime) + CacheUpdateAge < DateTime.UtcNow; } - - private bool CreateTileImage(Tile tile, byte[] imageBuffer) - { - BitmapImage bitmap = new BitmapImage(); - - try - { - using (Stream stream = new MemoryStream(imageBuffer, 8, imageBuffer.Length - 8, false)) - { - bitmap.BeginInit(); - bitmap.CacheOption = BitmapCacheOption.OnLoad; - bitmap.StreamSource = stream; - bitmap.EndInit(); - bitmap.Freeze(); - } - } - catch (Exception ex) - { - TraceWarning("Creating tile image failed: {0}", ex.Message); - return false; - } - - Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() => tile.Image = bitmap)); - return true; - } - - private static void TraceWarning(string format, params object[] args) - { - Trace.TraceWarning("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args)); - } - - private static void TraceInformation(string format, params object[] args) - { - //Trace.TraceInformation("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args)); - } } } diff --git a/MapControl/TileLayer.cs b/MapControl/TileLayer.cs index 6fa1c99b..b2ebc1e4 100644 --- a/MapControl/TileLayer.cs +++ b/MapControl/TileLayer.cs @@ -133,7 +133,8 @@ namespace MapControl drawingContext.DrawRectangle(tile.Brush, null, tileRect); //if (tile.ZoomLevel == zoomLevel) - // drawingContext.DrawText(new FormattedText(string.Format("{0}-{1}-{2}", tile.ZoomLevel, tile.X, tile.Y), System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface("Segoe UI"), 14, Brushes.Black), tileRect.TopLeft); + // drawingContext.DrawText(new FormattedText(string.Format("{0}-{1}-{2}", tile.ZoomLevel, tile.X, tile.Y), + // System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface("Segoe UI"), 14, Brushes.Black), tileRect.TopLeft); }); } }