1 /* 2 * This file is part of the "GKMap". 3 * GKMap project borrowed from GMap.NET (by radioman). 4 * 5 * Copyright (C) 2009-2018 by radioman (email@radioman.lt). 6 * This program is licensed under the FLAT EARTH License. 7 */ 8 9 using System; 10 using System.Collections.Concurrent; 11 using System.Collections.Generic; 12 using System.ComponentModel; 13 using System.Diagnostics; 14 using System.Threading; 15 using System.Threading.Tasks; 16 using GKMap.MapObjects; 17 using GKMap.MapProviders; 18 19 namespace GKMap 20 { 21 /// <summary> 22 /// internal map control core 23 /// </summary> 24 internal class MapCore : IDisposable 25 { 26 private BackgroundWorker fInvalidator; 27 private bool? fIsRunningOnMono; 28 private DateTime fLastTileLoadStart = DateTime.Now; 29 private DateTime fLastTileLoadEnd = DateTime.Now; 30 private bool fLazyEvents = true; 31 private RectLatLng? fLazySetZoomToFitRect; 32 private int fLoadWaitCount; 33 private GSize fMinOfTiles; 34 private GSize fMaxOfTiles; 35 private bool fMouseIn; 36 private PointLatLng fPosition; 37 private GPoint fPositionPixel; 38 private GMapProvider fProvider; 39 private GSize fSizeOfMapArea; 40 private readonly int fThreadPoolSize = 4; 41 private readonly BlockingCollection<LoadTask> fTileLoadQueue4 = new BlockingCollection<LoadTask>(new ConcurrentStack<LoadTask>()); 42 private readonly object fTileLoadQueue4Lock = new object(); 43 private List<Task> fTileLoadQueue4Tasks; 44 private IMapControl fView; 45 private int fZoom; 46 47 private volatile int okZoom; 48 private volatile int skipOverZoom; 49 50 private static int fInstances; 51 52 public GRect TileRect; 53 public GPoint RenderOffset; 54 public GPoint CenterTileXYLocation; 55 public GPoint CenterTileXYLocationLast; 56 public GPoint DragPoint; 57 public GPoint CompensationOffset; 58 59 public GPoint MouseDown; 60 public GPoint MouseCurrent; 61 public GPoint MouseLastZoom; 62 63 public bool InvertedMouseWheelZooming { get; set; } 64 65 /// <summary> 66 /// is user dragging map 67 /// </summary> 68 public bool IsDragging { get; set; } 69 70 /// <summary> 71 /// return true if running on mono 72 /// </summary> 73 /// <returns></returns> 74 public bool IsRunningOnMono 75 { 76 get { 77 if (!fIsRunningOnMono.HasValue) { 78 try { 79 fIsRunningOnMono = (Type.GetType("Mono.Runtime") != null); 80 return fIsRunningOnMono.Value; 81 } catch { 82 } 83 } else { 84 return fIsRunningOnMono.Value; 85 } 86 return false; 87 } 88 } 89 90 public TileMatrix Matrix { get; private set; } 91 92 public bool MouseIn 93 { 94 get { return fMouseIn; } 95 set { fMouseIn = value; } 96 } 97 98 public AutoResetEvent RefreshEvent { get; private set; } 99 100 public List<DrawTile> TileDrawingList { get; private set; } 101 102 public RWLock TileDrawingListLock { get; private set; } 103 104 public bool UpdatingBounds { get; private set; } 105 106 107 internal Dictionary<LoadTask, Exception> FailedLoads = new Dictionary<LoadTask, Exception>(new LoadTaskComparer()); 108 internal readonly object FailedLoadsLock = new object(); 109 internal volatile bool IsStarted; 110 internal readonly object InvalidationLock = new object(); 111 internal DateTime LastInvalidation = DateTime.Now; 112 internal bool MouseWheelZooming = false; 113 internal int MaxZoom = 2; 114 internal int MinZoom = 2; 115 internal int Width; 116 internal int Height; 117 internal bool ZoomToArea = true; 118 119 120 /// <summary> 121 /// current marker position 122 /// </summary> 123 public PointLatLng Position 124 { 125 get { 126 return fPosition; 127 } 128 set { 129 fPosition = value; 130 fPositionPixel = fProvider.Projection.FromLatLngToPixel(value, Zoom); 131 132 if (IsStarted) { 133 if (!IsDragging) { 134 GoToCurrentPosition(); 135 } 136 137 if (OnCurrentPositionChanged != null) 138 OnCurrentPositionChanged(fPosition); 139 } 140 } 141 } 142 143 public GMapProvider Provider 144 { 145 get { 146 return fProvider; 147 } 148 set { 149 if (fProvider == null || !fProvider.Equals(value)) { 150 bool diffProjection = (fProvider == null || fProvider.Projection != value.Projection); 151 152 fProvider = value; 153 154 if (!fProvider.IsInitialized) { 155 fProvider.IsInitialized = true; 156 fProvider.OnInitialized(); 157 } 158 159 if (fProvider.Projection != null && diffProjection) { 160 TileRect = new GRect(GPoint.Empty, fProvider.Projection.TileSize); 161 fMinOfTiles = fProvider.Projection.GetTileMatrixMinXY(Zoom); 162 fMaxOfTiles = fProvider.Projection.GetTileMatrixMaxXY(Zoom); 163 fPositionPixel = fProvider.Projection.FromLatLngToPixel(Position, Zoom); 164 } 165 166 if (IsStarted) { 167 CancelAsyncTasks(); 168 if (diffProjection) { 169 OnMapSizeChanged(Width, Height); 170 } 171 ReloadMap(); 172 173 if (MinZoom < fProvider.MinZoom) { 174 MinZoom = fProvider.MinZoom; 175 } 176 177 ZoomToArea = true; 178 179 if (fProvider.Area.HasValue && !fProvider.Area.Value.Contains(Position)) { 180 SetZoomToFitRect(fProvider.Area.Value); 181 ZoomToArea = false; 182 } 183 184 if (OnMapTypeChanged != null) { 185 OnMapTypeChanged(value); 186 } 187 } 188 } 189 } 190 } 191 192 /// <summary> 193 /// gets current map view top/left coordinate, width in Lng, height in Lat 194 /// </summary> 195 /// <returns></returns> 196 public RectLatLng ViewArea 197 { 198 get { 199 if (fProvider.Projection != null) { 200 var p = FromLocalToLatLng(0, 0); 201 var p2 = FromLocalToLatLng(Width, Height); 202 203 return RectLatLng.FromLTRB(p.Lng, p.Lat, p2.Lng, p2.Lat); 204 } 205 return RectLatLng.Empty; 206 } 207 } 208 209 /// <summary> 210 /// map zoom 211 /// </summary> 212 public int Zoom 213 { 214 get { 215 return fZoom; 216 } 217 set { 218 if (fZoom != value && !IsDragging) { 219 fZoom = value; 220 221 fMinOfTiles = fProvider.Projection.GetTileMatrixMinXY(value); 222 fMaxOfTiles = fProvider.Projection.GetTileMatrixMaxXY(value); 223 224 fPositionPixel = fProvider.Projection.FromLatLngToPixel(Position, value); 225 226 if (IsStarted) { 227 CancelAsyncTasks(); 228 229 Matrix.ClearLevelsBelow(fZoom - LevelsKeepInMemory); 230 Matrix.ClearLevelsAbove(fZoom + LevelsKeepInMemory); 231 232 lock (FailedLoads) { 233 FailedLoads.Clear(); 234 } 235 236 GoToCurrentPositionOnZoom(); 237 UpdateBounds(); 238 239 if (OnMapZoomChanged != null) { 240 OnMapZoomChanged(); 241 } 242 } 243 } 244 } 245 } 246 247 /// <summary> 248 /// retry count to get tile 249 /// </summary> 250 public int RetryLoadTile = 0; 251 252 /// <summary> 253 /// how many levels of tiles are staying decompressed in memory 254 /// </summary> 255 public int LevelsKeepInMemory = 5; 256 257 258 /// <summary> 259 /// occurs when current position is changed 260 /// </summary> 261 public event PositionChanged OnCurrentPositionChanged; 262 263 /// <summary> 264 /// occurs when tile set load is complete 265 /// </summary> 266 public event TileLoadComplete OnTileLoadComplete; 267 268 /// <summary> 269 /// occurs when tile set is starting to load 270 /// </summary> 271 public event TileLoadStart OnTileLoadStart; 272 273 /// <summary> 274 /// occurs on map drag 275 /// </summary> 276 public event MapDrag OnMapDrag; 277 278 /// <summary> 279 /// occurs on map zoom changed 280 /// </summary> 281 public event MapZoomChanged OnMapZoomChanged; 282 283 /// <summary> 284 /// occurs on map type changed 285 /// </summary> 286 public event MapTypeChanged OnMapTypeChanged; 287 288 /// <summary> 289 /// occurs on mouse enters marker area 290 /// </summary> 291 public event MarkerEnter OnMarkerEnter; 292 293 /// <summary> 294 /// occurs on mouse leaves marker area 295 /// </summary> 296 public event MarkerLeave OnMarkerLeave; 297 298 /// <summary> 299 /// occurs on mouse enters Polygon area 300 /// </summary> 301 public event PolygonEnter OnPolygonEnter; 302 303 /// <summary> 304 /// occurs on mouse leaves Polygon area 305 /// </summary> 306 public event PolygonLeave OnPolygonLeave; 307 308 /// <summary> 309 /// occurs on mouse enters route area 310 /// </summary> 311 public event RouteEnter OnRouteEnter; 312 313 /// <summary> 314 /// occurs on mouse leaves route area 315 /// </summary> 316 public event RouteLeave OnRouteLeave; 317 318 MapCore(IMapControl view)319 public MapCore(IMapControl view) 320 { 321 fView = view; 322 323 Provider = EmptyProvider.Instance; 324 RefreshEvent = new AutoResetEvent(false); 325 TileDrawingListLock = new RWLock(); 326 TileDrawingList = new List<DrawTile>(); 327 Matrix = new TileMatrix(); 328 } 329 330 /// <summary> 331 /// sets zoom to max to fit rect 332 /// </summary> 333 /// <param name="rect"></param> 334 /// <returns></returns> SetZoomToFitRect(RectLatLng rect)335 public bool SetZoomToFitRect(RectLatLng rect) 336 { 337 if (fLazyEvents) { 338 fLazySetZoomToFitRect = rect; 339 } else { 340 int maxZoom = GetMaxZoomToFitRect(rect); 341 if (maxZoom > 0) { 342 PointLatLng center = new PointLatLng(rect.Lat - (rect.HeightLat / 2), rect.Lng + (rect.WidthLng / 2)); 343 fView.Position = center; 344 345 if (maxZoom > MaxZoom) { 346 maxZoom = MaxZoom; 347 } 348 349 if (Zoom != maxZoom) { 350 fView.Zoom = maxZoom; 351 } 352 353 return true; 354 } 355 } 356 return false; 357 } 358 ResetZoomToFitRect()359 public void ResetZoomToFitRect() 360 { 361 if (fLazyEvents) { 362 fLazyEvents = false; 363 364 if (fLazySetZoomToFitRect.HasValue) { 365 SetZoomToFitRect(fLazySetZoomToFitRect.Value); 366 fLazySetZoomToFitRect = null; 367 } 368 } 369 } 370 OnMapOpen()371 public BackgroundWorker OnMapOpen() 372 { 373 if (!IsStarted) { 374 int x = Interlocked.Increment(ref fInstances); 375 Debug.WriteLine("OnMapOpen: " + x); 376 377 IsStarted = true; 378 379 if (x == 1) { 380 GMaps.Instance.NoMapInstances = false; 381 } 382 383 GoToCurrentPosition(); 384 385 fInvalidator = new BackgroundWorker(); 386 fInvalidator.WorkerSupportsCancellation = true; 387 fInvalidator.WorkerReportsProgress = true; 388 fInvalidator.DoWork += InvalidatorWatch; 389 fInvalidator.RunWorkerAsync(); 390 } 391 return fInvalidator; 392 } 393 OnMapClose()394 public void OnMapClose() 395 { 396 Dispose(); 397 } 398 InvalidatorWatch(object sender, DoWorkEventArgs e)399 private void InvalidatorWatch(object sender, DoWorkEventArgs e) 400 { 401 var w = sender as BackgroundWorker; 402 403 TimeSpan span = TimeSpan.FromMilliseconds(111); 404 int spanMs = (int)span.TotalMilliseconds; 405 bool skiped = false; 406 407 while (RefreshEvent != null && (!skiped && RefreshEvent.WaitOne() || (RefreshEvent.WaitOne(spanMs, false) || true))) { 408 if (w.CancellationPending) 409 break; 410 411 var now = DateTime.Now; 412 TimeSpan delta; 413 lock (InvalidationLock) { 414 delta = now - LastInvalidation; 415 } 416 417 if (delta > span) { 418 lock (InvalidationLock) { 419 LastInvalidation = now; 420 } 421 skiped = false; 422 423 w.ReportProgress(1); 424 Debug.WriteLine("Invalidate delta: " + (int)delta.TotalMilliseconds + "ms"); 425 } else { 426 skiped = true; 427 } 428 } 429 } 430 UpdateCenterTileXYLocation()431 public void UpdateCenterTileXYLocation() 432 { 433 PointLatLng center = FromLocalToLatLng(Width / 2, Height / 2); 434 GPoint centerPixel = fProvider.Projection.FromLatLngToPixel(center, Zoom); 435 CenterTileXYLocation = fProvider.Projection.FromPixelToTileXY(centerPixel); 436 } 437 OnMapSizeChanged(int width, int height)438 public void OnMapSizeChanged(int width, int height) 439 { 440 this.Width = width; 441 this.Height = height; 442 443 fSizeOfMapArea.Width = 1 + (Width / fProvider.Projection.TileSize.Width) / 2; 444 fSizeOfMapArea.Height = 1 + (Height / fProvider.Projection.TileSize.Height) / 2; 445 446 Debug.WriteLine("OnMapSizeChanged, w: " + width + ", h: " + height + ", size: " + fSizeOfMapArea); 447 448 if (IsStarted) { 449 UpdateBounds(); 450 GoToCurrentPosition(); 451 } 452 } 453 454 /// <summary> 455 /// gets lat/lng from local control coordinates 456 /// </summary> 457 /// <param name="x"></param> 458 /// <param name="y"></param> 459 /// <returns></returns> FromLocalToLatLng(long x, long y)460 public PointLatLng FromLocalToLatLng(long x, long y) 461 { 462 GPoint p = new GPoint(x, y); 463 p.OffsetNegative(RenderOffset); 464 p.Offset(CompensationOffset); 465 466 return fProvider.Projection.FromPixelToLatLng(p, Zoom); 467 } 468 469 /// <summary> 470 /// return local coordinates from lat/lng 471 /// </summary> 472 /// <param name="latlng"></param> 473 /// <returns></returns> FromLatLngToLocal(PointLatLng latlng)474 public GPoint FromLatLngToLocal(PointLatLng latlng) 475 { 476 GPoint pLocal = fProvider.Projection.FromLatLngToPixel(latlng, Zoom); 477 pLocal.Offset(RenderOffset); 478 pLocal.OffsetNegative(CompensationOffset); 479 return pLocal; 480 } 481 482 /// <summary> 483 /// gets max zoom level to fit rectangle 484 /// </summary> 485 /// <param name="rect"></param> 486 /// <returns></returns> GetMaxZoomToFitRect(RectLatLng rect)487 public int GetMaxZoomToFitRect(RectLatLng rect) 488 { 489 int zoom = MinZoom; 490 491 if (rect.HeightLat == 0 || rect.WidthLng == 0) { 492 zoom = MaxZoom / 2; 493 } else { 494 for (int i = zoom; i <= MaxZoom; i++) { 495 GPoint p1 = fProvider.Projection.FromLatLngToPixel(rect.LocationTopLeft, i); 496 GPoint p2 = fProvider.Projection.FromLatLngToPixel(rect.LocationRightBottom, i); 497 498 if (((p2.X - p1.X) <= Width + 10) && (p2.Y - p1.Y) <= Height + 10) { 499 zoom = i; 500 } else { 501 break; 502 } 503 } 504 } 505 506 return zoom; 507 } 508 509 /// <summary> 510 /// initiates map dragging 511 /// </summary> 512 /// <param name="pt"></param> BeginDrag(GPoint pt)513 public void BeginDrag(GPoint pt) 514 { 515 DragPoint.X = pt.X - RenderOffset.X; 516 DragPoint.Y = pt.Y - RenderOffset.Y; 517 IsDragging = true; 518 519 Debug.WriteLine("IsDragging = " + IsDragging); 520 } 521 522 /// <summary> 523 /// ends map dragging 524 /// </summary> EndDrag()525 public void EndDrag() 526 { 527 IsDragging = false; 528 MouseDown = GPoint.Empty; 529 RefreshEvent.Set(); 530 531 Debug.WriteLine("IsDragging = " + IsDragging); 532 } 533 534 /// <summary> 535 /// reloads map 536 /// </summary> ReloadMap()537 public void ReloadMap() 538 { 539 if (IsStarted) { 540 Debug.WriteLine("------------------"); 541 542 okZoom = 0; 543 skipOverZoom = 0; 544 545 CancelAsyncTasks(); 546 547 Matrix.ClearAllLevels(); 548 549 lock (FailedLoads) { 550 FailedLoads.Clear(); 551 } 552 553 RefreshEvent.Set(); 554 555 UpdateBounds(); 556 } else { 557 throw new Exception("Please, do not call ReloadMap before form is loaded, it's useless"); 558 } 559 } 560 561 /// <summary> 562 /// moves current position into map center 563 /// </summary> GoToCurrentPosition()564 public void GoToCurrentPosition() 565 { 566 CompensationOffset = fPositionPixel; 567 568 // reset stuff 569 RenderOffset = GPoint.Empty; 570 DragPoint = GPoint.Empty; 571 572 var d = new GPoint(Width / 2, Height / 2); 573 Drag(d); 574 } 575 576 /// <summary> 577 /// moves current position into map center 578 /// </summary> GoToCurrentPositionOnZoom()579 internal void GoToCurrentPositionOnZoom() 580 { 581 CompensationOffset = fPositionPixel; 582 583 // reset stuff 584 RenderOffset = GPoint.Empty; 585 DragPoint = GPoint.Empty; 586 587 // goto location and centering 588 if (!MouseWheelZooming) { 589 // use current map center 590 MouseLastZoom = GPoint.Empty; 591 } 592 593 GPoint pt = new GPoint(-(fPositionPixel.X - Width / 2), -(fPositionPixel.Y - Height / 2)); 594 pt.Offset(CompensationOffset); 595 RenderOffset.X = pt.X - DragPoint.X; 596 RenderOffset.Y = pt.Y - DragPoint.Y; 597 598 UpdateCenterTileXYLocation(); 599 } 600 601 /// <summary> 602 /// drag map by offset in pixels 603 /// </summary> 604 /// <param name="offset"></param> DragOffset(GPoint offset)605 public void DragOffset(GPoint offset) 606 { 607 RenderOffset.Offset(offset); 608 609 UpdateCenterTileXYLocation(); 610 611 if (CenterTileXYLocation != CenterTileXYLocationLast) { 612 CenterTileXYLocationLast = CenterTileXYLocation; 613 UpdateBounds(); 614 } 615 616 IsDragging = true; 617 Position = FromLocalToLatLng(Width / 2, Height / 2); 618 IsDragging = false; 619 620 if (OnMapDrag != null) { 621 OnMapDrag(); 622 } 623 624 ForceUpdateOverlays(); 625 } 626 627 /// <summary> 628 /// drag map 629 /// </summary> 630 /// <param name="pt"></param> Drag(GPoint pt)631 public void Drag(GPoint pt) 632 { 633 RenderOffset.X = pt.X - DragPoint.X; 634 RenderOffset.Y = pt.Y - DragPoint.Y; 635 636 UpdateCenterTileXYLocation(); 637 638 if (CenterTileXYLocation != CenterTileXYLocationLast) { 639 CenterTileXYLocationLast = CenterTileXYLocation; 640 UpdateBounds(); 641 } 642 643 if (IsDragging) { 644 Position = FromLocalToLatLng(Width / 2, Height / 2); 645 646 if (OnMapDrag != null) { 647 OnMapDrag(); 648 } 649 } 650 } 651 652 /// <summary> 653 /// cancels tile loaders and bounds checker 654 /// </summary> CancelAsyncTasks()655 public void CancelAsyncTasks() 656 { 657 if (IsStarted) { 658 } 659 } 660 AddLoadTask(LoadTask t)661 private void AddLoadTask(LoadTask t) 662 { 663 lock (fTileLoadQueue4Lock) { 664 if (fTileLoadQueue4Tasks == null) { 665 fTileLoadQueue4Tasks = new List<Task>(); 666 667 while (fTileLoadQueue4Tasks.Count < fThreadPoolSize) { 668 Debug.WriteLine("creating ProcessLoadTask: " + fTileLoadQueue4Tasks.Count); 669 670 fTileLoadQueue4Tasks.Add(Task.Factory.StartNew(delegate { 671 string ctid = "ProcessLoadTask[" + Thread.CurrentThread.ManagedThreadId + "]"; 672 Thread.CurrentThread.Name = ctid; 673 674 Debug.WriteLine(ctid + ": started"); 675 do { 676 if (fTileLoadQueue4.Count == 0) { 677 Debug.WriteLine(ctid + ": ready"); 678 679 if (Interlocked.Increment(ref fLoadWaitCount) >= fThreadPoolSize) { 680 Interlocked.Exchange(ref fLoadWaitCount, 0); 681 OnLoadComplete(ctid); 682 } 683 } 684 ProcessLoadTask(fTileLoadQueue4.Take(), ctid); 685 } while (!fTileLoadQueue4.IsAddingCompleted); 686 687 Debug.WriteLine(ctid + ": exit"); 688 689 }, TaskCreationOptions.LongRunning)); 690 } 691 } 692 } 693 fTileLoadQueue4.Add(t); 694 } 695 ProcessLoadTask(LoadTask task, string ctid)696 private void ProcessLoadTask(LoadTask task, string ctid) 697 { 698 try { 699 if (Matrix == null) 700 return; 701 702 var m = Matrix.GetTileWithReadLock(task.Zoom, task.Pos); 703 if (!m.NotEmpty) { 704 Debug.WriteLine(ctid + " - try load: " + task); 705 706 Tile t = new Tile(task.Zoom, task.Pos); 707 708 var providerOverlays = fProvider.Overlays; 709 710 if (providerOverlays != null) foreach (var tl in providerOverlays) { 711 int retry = 0; 712 do { 713 PureImage img = null; 714 Exception ex = null; 715 716 if (task.Zoom >= fProvider.MinZoom && (!fProvider.MaxZoom.HasValue || task.Zoom <= fProvider.MaxZoom)) { 717 if (skipOverZoom == 0 || task.Zoom <= skipOverZoom) { 718 // tile number inversion(BottomLeft -> TopLeft) 719 if (tl.InvertedAxisY) { 720 img = GMaps.Instance.GetImageFrom(tl, new GPoint(task.Pos.X, fMaxOfTiles.Height - task.Pos.Y), task.Zoom, out ex); 721 } else { 722 img = GMaps.Instance.GetImageFrom(tl, task.Pos, task.Zoom, out ex); 723 } 724 } 725 } 726 727 if (img != null && ex == null) { 728 if (okZoom < task.Zoom) { 729 okZoom = task.Zoom; 730 skipOverZoom = 0; 731 Debug.WriteLine("skipOverZoom disabled, okZoom: " + okZoom); 732 } 733 } else if (ex != null) { 734 if ((skipOverZoom != okZoom) && (task.Zoom > okZoom)) { 735 if (ex.Message.Contains("(404) Not Found")) { 736 skipOverZoom = okZoom; 737 Debug.WriteLine("skipOverZoom enabled: " + skipOverZoom); 738 } 739 } 740 } 741 742 // check for parent tiles if not found 743 if (img == null && okZoom > 0 && fProvider.Projection is MercatorProjection) { 744 int zoomOffset = task.Zoom > okZoom ? task.Zoom - okZoom : 1; 745 long Ix = 0; 746 GPoint parentTile = GPoint.Empty; 747 748 while (img == null && zoomOffset < task.Zoom) { 749 Ix = (long)Math.Pow(2, zoomOffset); 750 parentTile = new GPoint((task.Pos.X / Ix), (task.Pos.Y / Ix)); 751 img = GMaps.Instance.GetImageFrom(tl, parentTile, task.Zoom - zoomOffset++, out ex); 752 } 753 754 if (img != null) { 755 // offsets in quadrant 756 long xoff = Math.Abs(task.Pos.X - (parentTile.X * Ix)); 757 long yoff = Math.Abs(task.Pos.Y - (parentTile.Y * Ix)); 758 759 img.IsParent = true; 760 img.Ix = Ix; 761 img.Xoff = xoff; 762 img.Yoff = yoff; 763 } 764 } 765 766 if (img != null) { 767 Debug.WriteLine(ctid + " - tile loaded: " + img.Data.Length / 1024 + "KB, " + task); 768 t.AddOverlay(img); 769 break; 770 } else { 771 if (ex != null) { 772 lock (FailedLoads) { 773 if (!FailedLoads.ContainsKey(task)) { 774 FailedLoads.Add(task, ex); 775 } 776 } 777 } 778 779 if (RetryLoadTile > 0) { 780 Debug.WriteLine(ctid + " - ProcessLoadTask: " + task + " -> empty tile, retry " + retry); 781 Thread.Sleep(1111); 782 } 783 } 784 } 785 while (++retry < RetryLoadTile); 786 } 787 788 if (t.HasAnyOverlays && IsStarted) { 789 Matrix.SetTile(t); 790 } else { 791 t.Dispose(); 792 } 793 } 794 } catch (Exception ex) { 795 Debug.WriteLine(ctid + " - ProcessLoadTask: " + ex); 796 } finally { 797 if (RefreshEvent != null) { 798 RefreshEvent.Set(); 799 } 800 } 801 } 802 OnLoadComplete(string ctid)803 private void OnLoadComplete(string ctid) 804 { 805 fLastTileLoadEnd = DateTime.Now; 806 long lastTileLoadTimeMs = (long)(fLastTileLoadEnd - fLastTileLoadStart).TotalMilliseconds; 807 808 #region -- clear stuff-- 809 if (IsStarted) { 810 TileDrawingListLock.AcquireReaderLock(); 811 try { 812 Matrix.ClearLevelAndPointsNotIn(Zoom, TileDrawingList); 813 } finally { 814 TileDrawingListLock.ReleaseReaderLock(); 815 } 816 } 817 #endregion 818 819 #if UseGC 820 GC.Collect(); 821 GC.WaitForPendingFinalizers(); 822 GC.Collect(); 823 #endif 824 Debug.WriteLine(ctid + " - OnTileLoadComplete: " + lastTileLoadTimeMs + "ms"); 825 826 if (OnTileLoadComplete != null) { 827 OnTileLoadComplete(lastTileLoadTimeMs); 828 } 829 } 830 831 /// <summary> 832 /// updates map bounds 833 /// </summary> UpdateBounds()834 private void UpdateBounds() 835 { 836 if (!IsStarted || fProvider.Equals(EmptyProvider.Instance)) { 837 return; 838 } 839 840 UpdatingBounds = true; 841 842 TileDrawingListLock.AcquireWriterLock(); 843 try { 844 #region -- find tiles around -- 845 TileDrawingList.Clear(); 846 847 for (long i = -fSizeOfMapArea.Width, countI = fSizeOfMapArea.Width; i <= countI; i++) { 848 for (long j = -fSizeOfMapArea.Height, countJ = fSizeOfMapArea.Height; j <= countJ; j++) { 849 GPoint p = CenterTileXYLocation; 850 p.X += i; 851 p.Y += j; 852 853 if (p.X >= fMinOfTiles.Width && p.Y >= fMinOfTiles.Height && p.X <= fMaxOfTiles.Width && p.Y <= fMaxOfTiles.Height) { 854 DrawTile dt = new DrawTile( 855 p, new GPoint(p.X * TileRect.Width, p.Y * TileRect.Height), 856 (CenterTileXYLocation.X - p.X) * (CenterTileXYLocation.X - p.X) + (CenterTileXYLocation.Y - p.Y) * (CenterTileXYLocation.Y - p.Y) 857 ); 858 859 if (!TileDrawingList.Contains(dt)) { 860 TileDrawingList.Add(dt); 861 } 862 } 863 } 864 } 865 866 TileDrawingList.Sort(); 867 #endregion 868 } finally { 869 TileDrawingListLock.ReleaseWriterLock(); 870 } 871 872 Interlocked.Exchange(ref fLoadWaitCount, 0); 873 874 TileDrawingListLock.AcquireReaderLock(); 875 try { 876 foreach (DrawTile p in TileDrawingList) { 877 LoadTask task = new LoadTask(p.PosXY, Zoom); 878 AddLoadTask(task); 879 } 880 } finally { 881 TileDrawingListLock.ReleaseReaderLock(); 882 } 883 884 fLastTileLoadStart = DateTime.Now; 885 Debug.WriteLine("OnTileLoadStart - at zoom " + Zoom + ", time: " + fLastTileLoadStart.TimeOfDay); 886 UpdatingBounds = false; 887 888 if (OnTileLoadStart != null) { 889 OnTileLoadStart(); 890 } 891 } 892 ~MapCore()893 ~MapCore() 894 { 895 Dispose(false); 896 } 897 Dispose(bool disposing)898 void Dispose(bool disposing) 899 { 900 if (IsStarted) { 901 if (fInvalidator != null) { 902 fInvalidator.CancelAsync(); 903 fInvalidator.DoWork -= InvalidatorWatch; 904 fInvalidator.Dispose(); 905 fInvalidator = null; 906 } 907 908 if (RefreshEvent != null) { 909 RefreshEvent.Set(); 910 RefreshEvent.Close(); 911 RefreshEvent = null; 912 } 913 914 int x = Interlocked.Decrement(ref fInstances); 915 Debug.WriteLine("OnMapClose: " + x); 916 917 CancelAsyncTasks(); 918 IsStarted = false; 919 920 if (Matrix != null) { 921 Matrix.Dispose(); 922 Matrix = null; 923 } 924 925 if (FailedLoads != null) { 926 lock (FailedLoadsLock) { 927 FailedLoads.Clear(); 928 } 929 FailedLoads = null; 930 } 931 932 TileDrawingListLock.AcquireWriterLock(); 933 try { 934 TileDrawingList.Clear(); 935 } finally { 936 TileDrawingListLock.ReleaseWriterLock(); 937 } 938 939 if (TileDrawingListLock != null) { 940 TileDrawingListLock.Dispose(); 941 TileDrawingListLock = null; 942 TileDrawingList = null; 943 } 944 945 if (x == 0) { 946 #if DEBUG 947 GMaps.Instance.CancelTileCaching(); 948 #endif 949 GMaps.Instance.NoMapInstances = true; 950 GMaps.Instance.WaitForCache.Set(); 951 } 952 } 953 } 954 Dispose()955 public void Dispose() 956 { 957 Dispose(true); 958 GC.SuppressFinalize(this); 959 } 960 ProcessOverlaysMouseMove(int eX, int eY)961 internal void ProcessOverlaysMouseMove(int eX, int eY) 962 { 963 GPoint rp = new GPoint(eX, eY); 964 rp.OffsetNegative(RenderOffset); 965 966 for (int i = fView.Overlays.Count - 1; i >= 0; i--) { 967 MapOverlay o = fView.Overlays[i]; 968 if (o == null || !o.IsVisible || !o.IsHitTestVisible) 969 continue; 970 971 foreach (MapMarker m in o.Markers) { 972 if (!m.IsVisible || !m.IsHitTestVisible) 973 continue; 974 975 if (m.IsInside((int)rp.X, (int)rp.Y)) { 976 if (!m.IsMouseOver) { 977 fView.SetCursorHandOnEnter(); 978 m.IsMouseOver = true; 979 fView.IsMouseOverMarker = true; 980 981 var markerEnter = (MarkerEnter)OnMarkerEnter; 982 if (markerEnter != null) { 983 markerEnter(m); 984 } 985 986 fView.Invalidate(); 987 } 988 } else if (m.IsMouseOver) { 989 m.IsMouseOver = false; 990 fView.IsMouseOverMarker = false; 991 fView.RestoreCursorOnLeave(); 992 993 var markerLeave = (MarkerLeave)OnMarkerLeave; 994 if (markerLeave != null) { 995 markerLeave(m); 996 } 997 998 fView.Invalidate(); 999 } 1000 } 1001 1002 foreach (MapRoute m in o.Routes) { 1003 if (!m.IsVisible || !m.IsHitTestVisible) 1004 continue; 1005 1006 if (m.IsInside((int)rp.X, (int)rp.Y)) { 1007 if (!m.IsMouseOver) { 1008 fView.SetCursorHandOnEnter(); 1009 m.IsMouseOver = true; 1010 fView.IsMouseOverRoute = true; 1011 1012 var routeEnter = (RouteEnter)OnRouteEnter; 1013 if (routeEnter != null) { 1014 routeEnter(m); 1015 } 1016 1017 fView.Invalidate(); 1018 } 1019 } else { 1020 if (m.IsMouseOver) { 1021 m.IsMouseOver = false; 1022 fView.IsMouseOverRoute = false; 1023 fView.RestoreCursorOnLeave(); 1024 1025 var routeLeave = (RouteLeave)OnRouteLeave; 1026 if (routeLeave != null) { 1027 routeLeave(m); 1028 } 1029 1030 fView.Invalidate(); 1031 } 1032 } 1033 } 1034 1035 foreach (MapPolygon m in o.Polygons) { 1036 if (!m.IsVisible || !m.IsHitTestVisible) 1037 continue; 1038 1039 if (m.IsInside((int)rp.X, (int)rp.Y)) { 1040 if (!m.IsMouseOver) { 1041 fView.SetCursorHandOnEnter(); 1042 m.IsMouseOver = true; 1043 fView.IsMouseOverPolygon = true; 1044 1045 var polygonEnter = (PolygonEnter)OnPolygonEnter; 1046 if (polygonEnter != null) { 1047 polygonEnter(m); 1048 } 1049 1050 fView.Invalidate(); 1051 } 1052 } else { 1053 if (m.IsMouseOver) { 1054 m.IsMouseOver = false; 1055 fView.IsMouseOverPolygon = false; 1056 fView.RestoreCursorOnLeave(); 1057 1058 var polygonLeave = (PolygonLeave)OnPolygonLeave; 1059 if (polygonLeave != null) { 1060 polygonLeave(m); 1061 } 1062 1063 fView.Invalidate(); 1064 } 1065 } 1066 } 1067 } 1068 } 1069 1070 /// <summary> 1071 /// updates markers local position 1072 /// </summary> 1073 /// <param name="marker"></param> UpdateMarkerLocalPosition(MapMarker marker)1074 public void UpdateMarkerLocalPosition(MapMarker marker) 1075 { 1076 GPoint p = FromLatLngToLocal(marker.Position); 1077 p.OffsetNegative(RenderOffset); 1078 marker.LocalPosition = new GPoint((int)(p.X + marker.Offset.X), (int)(p.Y + marker.Offset.Y)); 1079 } 1080 1081 /// <summary> 1082 /// updates routes local position 1083 /// </summary> 1084 /// <param name="route"></param> UpdateRouteLocalPosition(MapRoute route)1085 public void UpdateRouteLocalPosition(MapRoute route) 1086 { 1087 route.LocalPoints.Clear(); 1088 1089 for (int i = 0; i < route.Points.Count; i++) { 1090 GPoint p = FromLatLngToLocal(route.Points[i]); 1091 p.OffsetNegative(RenderOffset); 1092 route.LocalPoints.Add(p); 1093 } 1094 route.UpdateGraphicsPath(); 1095 } 1096 1097 /// <summary> 1098 /// updates polygons local position 1099 /// </summary> 1100 /// <param name="polygon"></param> UpdatePolygonLocalPosition(MapPolygon polygon)1101 public void UpdatePolygonLocalPosition(MapPolygon polygon) 1102 { 1103 polygon.LocalPoints.Clear(); 1104 1105 for (int i = 0; i < polygon.Points.Count; i++) { 1106 GPoint p = FromLatLngToLocal(polygon.Points[i]); 1107 p.OffsetNegative(RenderOffset); 1108 polygon.LocalPoints.Add(p); 1109 } 1110 polygon.UpdateGraphicsPath(); 1111 } 1112 1113 /// <summary> 1114 /// set current position using keywords 1115 /// </summary> 1116 /// <param name="keys"></param> 1117 /// <returns>true if successful</returns> SetPositionByKeywords(string keys)1118 public GeocoderStatusCode SetPositionByKeywords(string keys) 1119 { 1120 var status = GeocoderStatusCode.Unknown; 1121 1122 var gp = Provider as IGeocodingProvider; 1123 if (gp == null) { 1124 gp = GMapProviders.OpenStreetMap; 1125 } 1126 1127 if (gp != null) { 1128 var pt = gp.GetPoint(keys, out status); 1129 if (status == GeocoderStatusCode.Success && pt.HasValue) { 1130 Position = pt.Value; 1131 } 1132 } 1133 1134 return status; 1135 } 1136 1137 /// <summary> 1138 /// gets rect of route or polygon 1139 /// </summary> 1140 /// <param name="figure"></param> 1141 /// <returns></returns> GetRectOfFigure(MapFigure figure)1142 public RectLatLng? GetRectOfFigure(MapFigure figure) 1143 { 1144 RectLatLng? ret = null; 1145 1146 double left = double.MaxValue; 1147 double top = double.MinValue; 1148 double right = double.MinValue; 1149 double bottom = double.MaxValue; 1150 1151 if (figure.HasLines) { 1152 foreach (PointLatLng p in figure.Points) { 1153 // left 1154 if (p.Lng < left) { 1155 left = p.Lng; 1156 } 1157 1158 // top 1159 if (p.Lat > top) { 1160 top = p.Lat; 1161 } 1162 1163 // right 1164 if (p.Lng > right) { 1165 right = p.Lng; 1166 } 1167 1168 // bottom 1169 if (p.Lat < bottom) { 1170 bottom = p.Lat; 1171 } 1172 } 1173 ret = RectLatLng.FromLTRB(left, top, right, bottom); 1174 } 1175 return ret; 1176 } 1177 1178 /// <summary> 1179 /// gets rectangle with all objects inside 1180 /// </summary> 1181 /// <param name="overlayId">overlay id or null to check all except zoomInsignificant</param> 1182 /// <returns></returns> GetRectOfAllMarkers(string overlayId)1183 public RectLatLng? GetRectOfAllMarkers(string overlayId) 1184 { 1185 RectLatLng? ret = null; 1186 1187 double left = double.MaxValue; 1188 double top = double.MinValue; 1189 double right = double.MinValue; 1190 double bottom = double.MaxValue; 1191 1192 foreach (MapOverlay o in fView.Overlays) { 1193 if (((overlayId == null && o.IsZoomSignificant) || o.Id == overlayId) && o.IsVisible && o.Markers.Count > 0) { 1194 foreach (MapMarker m in o.Markers) { 1195 if (!m.IsVisible) continue; 1196 1197 // left 1198 if (m.Position.Lng < left) { 1199 left = m.Position.Lng; 1200 } 1201 1202 // top 1203 if (m.Position.Lat > top) { 1204 top = m.Position.Lat; 1205 } 1206 1207 // right 1208 if (m.Position.Lng > right) { 1209 right = m.Position.Lng; 1210 } 1211 1212 // bottom 1213 if (m.Position.Lat < bottom) { 1214 bottom = m.Position.Lat; 1215 } 1216 } 1217 } 1218 } 1219 1220 if (left != double.MaxValue && right != double.MinValue && top != double.MinValue && bottom != double.MaxValue) { 1221 ret = RectLatLng.FromLTRB(left, top, right, bottom); 1222 } 1223 1224 return ret; 1225 } 1226 1227 /// <summary> 1228 /// gets rectangle with all objects inside 1229 /// </summary> 1230 /// <param name="overlayId">overlay id or null to check all except zoomInsignificant</param> 1231 /// <returns></returns> GetRectOfAllRoutes(string overlayId)1232 public RectLatLng? GetRectOfAllRoutes(string overlayId) 1233 { 1234 RectLatLng? ret = null; 1235 1236 double left = double.MaxValue; 1237 double top = double.MinValue; 1238 double right = double.MinValue; 1239 double bottom = double.MaxValue; 1240 1241 foreach (MapOverlay o in fView.Overlays) { 1242 if (((overlayId == null && o.IsZoomSignificant) || o.Id == overlayId) && o.IsVisible && o.Routes.Count > 0) { 1243 foreach (MapRoute route in o.Routes) { 1244 if (!route.IsVisible || !route.HasLines) continue; 1245 1246 foreach (PointLatLng p in route.Points) { 1247 // left 1248 if (p.Lng < left) { 1249 left = p.Lng; 1250 } 1251 1252 // top 1253 if (p.Lat > top) { 1254 top = p.Lat; 1255 } 1256 1257 // right 1258 if (p.Lng > right) { 1259 right = p.Lng; 1260 } 1261 1262 // bottom 1263 if (p.Lat < bottom) { 1264 bottom = p.Lat; 1265 } 1266 } 1267 } 1268 } 1269 } 1270 1271 if (left != double.MaxValue && right != double.MinValue && top != double.MinValue && bottom != double.MaxValue) { 1272 ret = RectLatLng.FromLTRB(left, top, right, bottom); 1273 } 1274 1275 return ret; 1276 } 1277 ProcessMouseWheel(int eX, int eY, int eDelta)1278 public void ProcessMouseWheel(int eX, int eY, int eDelta) 1279 { 1280 if (fMouseIn && !IsDragging) { 1281 if (MouseLastZoom.X != eX && MouseLastZoom.Y != eY) { 1282 fView.Position = FromLocalToLatLng(eX, eY); 1283 MouseLastZoom.X = eX; 1284 MouseLastZoom.Y = eY; 1285 } 1286 1287 fView.SetMousePositionToMapCenter(); 1288 1289 MouseWheelZooming = true; 1290 1291 if (eDelta > 0) { 1292 if (!InvertedMouseWheelZooming) { 1293 fView.Zoom = ((int)Zoom) + 1; 1294 } else { 1295 fView.Zoom = ((int)(Zoom + 0.99)) - 1; 1296 } 1297 } else if (eDelta < 0) { 1298 if (!InvertedMouseWheelZooming) { 1299 fView.Zoom = ((int)(Zoom + 0.99)) - 1; 1300 } else { 1301 fView.Zoom = ((int)Zoom) + 1; 1302 } 1303 } 1304 1305 MouseWheelZooming = false; 1306 } 1307 } 1308 ProcessMouseClick(int eX, int eY, EventArgs e)1309 public void ProcessMouseClick(int eX, int eY, EventArgs e) 1310 { 1311 if (IsDragging) return; 1312 1313 GPoint rp = new GPoint(eX, eY); 1314 rp.OffsetNegative(RenderOffset); 1315 1316 for (int i = fView.Overlays.Count - 1; i >= 0; i--) { 1317 MapOverlay o = fView.Overlays[i]; 1318 if (o == null || !o.IsVisible || !o.IsHitTestVisible) continue; 1319 1320 foreach (MapMarker m in o.Markers) { 1321 if (m.IsVisible && m.IsHitTestVisible && m.IsInside((int)rp.X, (int)rp.Y)) { 1322 fView.DoMouseClick(m, e); 1323 break; 1324 } 1325 } 1326 1327 foreach (MapRoute m in o.Routes) { 1328 if (m.IsVisible && m.IsHitTestVisible && m.IsInside((int)rp.X, (int)rp.Y)) { 1329 fView.DoMouseClick(m, e); 1330 break; 1331 } 1332 } 1333 1334 foreach (MapPolygon m in o.Polygons) { 1335 if (m.IsVisible && m.IsHitTestVisible && m.IsInsideLatLng(FromLocalToLatLng(eX, eY))) { 1336 fView.DoMouseClick(m, e); 1337 break; 1338 } 1339 } 1340 } 1341 } 1342 ProcessMouseDoubleClick(int eX, int eY, EventArgs e)1343 public void ProcessMouseDoubleClick(int eX, int eY, EventArgs e) 1344 { 1345 if (IsDragging) return; 1346 1347 GPoint rp = new GPoint(eX, eY); 1348 rp.OffsetNegative(RenderOffset); 1349 1350 for (int i = fView.Overlays.Count - 1; i >= 0; i--) { 1351 MapOverlay o = fView.Overlays[i]; 1352 if (o == null || !o.IsVisible || !o.IsHitTestVisible) continue; 1353 1354 foreach (MapMarker m in o.Markers) { 1355 if (m.IsVisible && m.IsHitTestVisible && m.IsInside((int)rp.X, (int)rp.Y)) { 1356 fView.DoMouseDoubleClick(m, e); 1357 break; 1358 } 1359 } 1360 1361 foreach (MapRoute m in o.Routes) { 1362 if (m.IsVisible && m.IsHitTestVisible && m.IsInside((int)rp.X, (int)rp.Y)) { 1363 fView.DoMouseDoubleClick(m, e); 1364 break; 1365 } 1366 } 1367 1368 foreach (MapPolygon m in o.Polygons) { 1369 if (m.IsVisible && m.IsHitTestVisible && m.IsInsideLatLng(FromLocalToLatLng(eX, eY))) { 1370 fView.DoMouseDoubleClick(m, e); 1371 break; 1372 } 1373 } 1374 } 1375 } 1376 1377 /// <summary> 1378 /// update objects when map is dragged/zoomed 1379 /// </summary> ForceUpdateOverlays()1380 public void ForceUpdateOverlays() 1381 { 1382 try { 1383 fView.HoldInvalidation = true; 1384 1385 foreach (var o in fView.Overlays) { 1386 if (o.IsVisible) { 1387 o.ForceUpdate(); 1388 } 1389 } 1390 } finally { 1391 fView.Refresh(); 1392 } 1393 } 1394 1395 /// <summary> 1396 /// sets to max zoom to fit all markers and centers them in map 1397 /// </summary> 1398 /// <param name="overlayId">overlay id or null to check all</param> 1399 /// <returns></returns> ZoomAndCenterMarkers(string overlayId)1400 public bool ZoomAndCenterMarkers(string overlayId) 1401 { 1402 RectLatLng? rect = GetRectOfAllMarkers(overlayId); 1403 if (rect.HasValue) { 1404 return SetZoomToFitRect(rect.Value); 1405 } 1406 return false; 1407 } 1408 1409 /// <summary> 1410 /// zooms and centers all route 1411 /// </summary> 1412 /// <param name="overlayId">overlay id or null to check all</param> 1413 /// <returns></returns> ZoomAndCenterRoutes(string overlayId)1414 public bool ZoomAndCenterRoutes(string overlayId) 1415 { 1416 RectLatLng? rect = GetRectOfAllRoutes(overlayId); 1417 if (rect.HasValue) { 1418 return SetZoomToFitRect(rect.Value); 1419 } 1420 return false; 1421 } 1422 1423 /// <summary> 1424 /// zooms and centers route 1425 /// </summary> 1426 /// <param name="figure"></param> 1427 /// <returns></returns> ZoomAndCenterFigure(MapFigure figure)1428 public bool ZoomAndCenterFigure(MapFigure figure) 1429 { 1430 RectLatLng? rect = GetRectOfFigure(figure); 1431 if (rect.HasValue) { 1432 return SetZoomToFitRect(rect.Value); 1433 } 1434 return false; 1435 } 1436 DrawMap(object g)1437 public void DrawMap(object g) 1438 { 1439 if (UpdatingBounds || Equals(fProvider, EmptyProvider.Instance) || fProvider == null) { 1440 Debug.WriteLine("Core.updatingBounds"); 1441 return; 1442 } 1443 1444 TileDrawingListLock.AcquireReaderLock(); 1445 Matrix.EnterReadLock(); 1446 1447 try { 1448 foreach (var tilePoint in TileDrawingList) { 1449 TileRect.Location = tilePoint.PosPixel; 1450 TileRect.OffsetNegative(CompensationOffset); 1451 1452 bool found = false; 1453 1454 Tile t = Matrix.GetTileWithNoLock(Zoom, tilePoint.PosXY); 1455 if (t.NotEmpty) { 1456 // render tile 1457 foreach (var pureImage in t.Overlays) { 1458 fView.DrawTile(g, pureImage, ref found); 1459 } 1460 } else if (fProvider.Projection is MercatorProjection) { 1461 // filling empty tiles using lower level images 1462 int zoomOffset = 1; 1463 Tile parentTile = Tile.Empty; 1464 long Ix = 0; 1465 1466 while (!parentTile.NotEmpty && zoomOffset < Zoom && zoomOffset <= LevelsKeepInMemory) { 1467 Ix = (long)Math.Pow(2, zoomOffset); 1468 parentTile = Matrix.GetTileWithNoLock(Zoom - zoomOffset++, 1469 new GPoint((int)(tilePoint.PosXY.X / Ix), (int)(tilePoint.PosXY.Y / Ix))); 1470 } 1471 1472 if (parentTile.NotEmpty) { 1473 long xoff = Math.Abs(tilePoint.PosXY.X - (parentTile.Pos.X * Ix)); 1474 long yoff = Math.Abs(tilePoint.PosXY.Y - (parentTile.Pos.Y * Ix)); 1475 1476 // render tile 1477 foreach (var pureImage in parentTile.Overlays) { 1478 fView.DrawLowerTile(g, pureImage, Ix, xoff, yoff, ref found); 1479 } 1480 } 1481 } 1482 1483 // add text if tile is missing 1484 if (!found) { 1485 lock (FailedLoads) { 1486 var lt = new LoadTask(tilePoint.PosXY, Zoom); 1487 if (FailedLoads.ContainsKey(lt)) { 1488 var ex = FailedLoads[lt]; 1489 fView.DrawMissingTile(g, ex); 1490 } 1491 } 1492 } 1493 1494 fView.ShowTileGridLines(g, tilePoint); 1495 } 1496 } finally { 1497 Matrix.LeaveReadLock(); 1498 TileDrawingListLock.ReleaseReaderLock(); 1499 } 1500 } 1501 } 1502 } 1503