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