@lugg/maps 1.0.0-beta.0 → 1.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +4 -1
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/com/luggmaps/LuggCalloutView.kt +79 -0
  4. package/android/src/main/java/com/luggmaps/LuggCalloutViewManager.kt +39 -0
  5. package/android/src/main/java/com/luggmaps/LuggCircleView.kt +85 -0
  6. package/android/src/main/java/com/luggmaps/LuggCircleViewManager.kt +79 -0
  7. package/android/src/main/java/com/luggmaps/LuggGroundOverlayView.kt +81 -0
  8. package/android/src/main/java/com/luggmaps/LuggGroundOverlayViewManager.kt +82 -0
  9. package/android/src/main/java/com/luggmaps/LuggMapView.kt +49 -0
  10. package/android/src/main/java/com/luggmaps/LuggMapViewManager.kt +31 -0
  11. package/android/src/main/java/com/luggmaps/LuggMapWrapperView.kt +0 -11
  12. package/android/src/main/java/com/luggmaps/LuggMarkerView.kt +99 -51
  13. package/android/src/main/java/com/luggmaps/LuggMarkerViewManager.kt +13 -0
  14. package/android/src/main/java/com/luggmaps/LuggPackage.kt +11 -1
  15. package/android/src/main/java/com/luggmaps/LuggPolylineView.kt +1 -22
  16. package/android/src/main/java/com/luggmaps/LuggTileOverlayView.kt +92 -0
  17. package/android/src/main/java/com/luggmaps/LuggTileOverlayViewManager.kt +80 -0
  18. package/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +628 -18
  19. package/android/src/main/java/com/luggmaps/core/MapProviderDelegate.kt +14 -0
  20. package/android/src/main/java/com/luggmaps/core/PolylineAnimator.kt +12 -7
  21. package/android/src/main/java/com/luggmaps/events/CirclePressEvent.kt +12 -0
  22. package/android/src/main/java/com/luggmaps/events/GroundOverlayPressEvent.kt +12 -0
  23. package/android/src/main/java/com/luggmaps/events/TileOverlayPressEvent.kt +12 -0
  24. package/android/src/main/java/com/luggmaps/extensions/ViewExtensions.kt +11 -0
  25. package/ios/LuggCalloutView.h +23 -0
  26. package/ios/LuggCalloutView.mm +155 -0
  27. package/ios/LuggCircleView.h +31 -0
  28. package/ios/LuggCircleView.mm +133 -0
  29. package/ios/LuggGroundOverlayView.h +30 -0
  30. package/ios/LuggGroundOverlayView.mm +125 -0
  31. package/ios/LuggMapView.mm +84 -2
  32. package/ios/LuggMarkerView.h +5 -0
  33. package/ios/LuggMarkerView.mm +40 -2
  34. package/ios/LuggTileOverlayView.h +31 -0
  35. package/ios/LuggTileOverlayView.mm +130 -0
  36. package/ios/core/AppleMapProvider.mm +838 -34
  37. package/ios/core/GMSPolylineAnimator.m +6 -3
  38. package/ios/core/GoogleMapProvider.mm +506 -19
  39. package/ios/core/LuggAnnotationView.h +12 -0
  40. package/ios/core/LuggAnnotationView.mm +55 -0
  41. package/ios/core/MapProviderDelegate.h +20 -1
  42. package/ios/events/CirclePressEvent.h +20 -0
  43. package/ios/events/GroundOverlayPressEvent.h +20 -0
  44. package/ios/events/TileOverlayPressEvent.h +20 -0
  45. package/lib/module/MapProvider.web.js +7 -2
  46. package/lib/module/MapProvider.web.js.map +1 -1
  47. package/lib/module/MapView.js +12 -1
  48. package/lib/module/MapView.js.map +1 -1
  49. package/lib/module/MapView.web.js +36 -20
  50. package/lib/module/MapView.web.js.map +1 -1
  51. package/lib/module/components/Circle.js +46 -0
  52. package/lib/module/components/Circle.js.map +1 -0
  53. package/lib/module/components/Circle.types.js +4 -0
  54. package/lib/module/components/Circle.types.js.map +1 -0
  55. package/lib/module/components/Circle.web.js +79 -0
  56. package/lib/module/components/Circle.web.js.map +1 -0
  57. package/lib/module/components/GeoJson.js +3 -3
  58. package/lib/module/components/GeoJson.js.map +1 -1
  59. package/lib/module/components/GroundOverlay.js +45 -0
  60. package/lib/module/components/GroundOverlay.js.map +1 -0
  61. package/lib/module/components/GroundOverlay.types.js +4 -0
  62. package/lib/module/components/GroundOverlay.types.js.map +1 -0
  63. package/lib/module/components/GroundOverlay.web.js +77 -0
  64. package/lib/module/components/GroundOverlay.web.js.map +1 -0
  65. package/lib/module/components/Marker.js +41 -6
  66. package/lib/module/components/Marker.js.map +1 -1
  67. package/lib/module/components/Marker.web.js +103 -26
  68. package/lib/module/components/Marker.web.js.map +1 -1
  69. package/lib/module/components/Polygon.web.js +3 -3
  70. package/lib/module/components/Polygon.web.js.map +1 -1
  71. package/lib/module/components/Polyline.web.js +6 -12
  72. package/lib/module/components/Polyline.web.js.map +1 -1
  73. package/lib/module/components/TileOverlay.js +44 -0
  74. package/lib/module/components/TileOverlay.js.map +1 -0
  75. package/lib/module/components/TileOverlay.types.js +4 -0
  76. package/lib/module/components/TileOverlay.types.js.map +1 -0
  77. package/lib/module/components/TileOverlay.web.js +69 -0
  78. package/lib/module/components/TileOverlay.web.js.map +1 -0
  79. package/lib/module/components/index.js +3 -0
  80. package/lib/module/components/index.js.map +1 -1
  81. package/lib/module/components/index.web.js +3 -0
  82. package/lib/module/components/index.web.js.map +1 -1
  83. package/lib/module/fabric/LuggCalloutViewNativeComponent.ts +20 -0
  84. package/lib/module/fabric/LuggCircleViewNativeComponent.ts +25 -0
  85. package/lib/module/fabric/LuggGroundOverlayViewNativeComponent.ts +29 -0
  86. package/lib/module/fabric/LuggMapViewNativeComponent.ts +13 -5
  87. package/lib/module/fabric/LuggMarkerViewNativeComponent.ts +14 -2
  88. package/lib/module/fabric/LuggTileOverlayViewNativeComponent.ts +30 -0
  89. package/lib/module/index.js +3 -0
  90. package/lib/module/index.js.map +1 -1
  91. package/lib/module/index.web.js +3 -0
  92. package/lib/module/index.web.js.map +1 -1
  93. package/lib/typescript/src/MapProvider.web.d.ts +9 -2
  94. package/lib/typescript/src/MapProvider.web.d.ts.map +1 -1
  95. package/lib/typescript/src/MapView.d.ts +1 -1
  96. package/lib/typescript/src/MapView.d.ts.map +1 -1
  97. package/lib/typescript/src/MapView.types.d.ts +36 -1
  98. package/lib/typescript/src/MapView.types.d.ts.map +1 -1
  99. package/lib/typescript/src/MapView.web.d.ts +1 -1
  100. package/lib/typescript/src/MapView.web.d.ts.map +1 -1
  101. package/lib/typescript/src/components/Circle.d.ts +10 -0
  102. package/lib/typescript/src/components/Circle.d.ts.map +1 -0
  103. package/lib/typescript/src/components/Circle.types.d.ts +33 -0
  104. package/lib/typescript/src/components/Circle.types.d.ts.map +1 -0
  105. package/lib/typescript/src/components/Circle.web.d.ts +3 -0
  106. package/lib/typescript/src/components/Circle.web.d.ts.map +1 -0
  107. package/lib/typescript/src/components/GeoJson.d.ts +2 -1
  108. package/lib/typescript/src/components/GeoJson.d.ts.map +1 -1
  109. package/lib/typescript/src/components/GeoJson.types.d.ts +11 -1
  110. package/lib/typescript/src/components/GeoJson.types.d.ts.map +1 -1
  111. package/lib/typescript/src/components/GroundOverlay.d.ts +10 -0
  112. package/lib/typescript/src/components/GroundOverlay.d.ts.map +1 -0
  113. package/lib/typescript/src/components/GroundOverlay.types.d.ts +35 -0
  114. package/lib/typescript/src/components/GroundOverlay.types.d.ts.map +1 -0
  115. package/lib/typescript/src/components/GroundOverlay.web.d.ts +3 -0
  116. package/lib/typescript/src/components/GroundOverlay.web.d.ts.map +1 -0
  117. package/lib/typescript/src/components/Marker.d.ts +6 -3
  118. package/lib/typescript/src/components/Marker.d.ts.map +1 -1
  119. package/lib/typescript/src/components/Marker.types.d.ts +40 -2
  120. package/lib/typescript/src/components/Marker.types.d.ts.map +1 -1
  121. package/lib/typescript/src/components/Marker.web.d.ts +3 -2
  122. package/lib/typescript/src/components/Marker.web.d.ts.map +1 -1
  123. package/lib/typescript/src/components/Polygon.web.d.ts +1 -1
  124. package/lib/typescript/src/components/Polygon.web.d.ts.map +1 -1
  125. package/lib/typescript/src/components/Polyline.web.d.ts +1 -1
  126. package/lib/typescript/src/components/Polyline.web.d.ts.map +1 -1
  127. package/lib/typescript/src/components/TileOverlay.d.ts +10 -0
  128. package/lib/typescript/src/components/TileOverlay.d.ts.map +1 -0
  129. package/lib/typescript/src/components/TileOverlay.types.d.ts +36 -0
  130. package/lib/typescript/src/components/TileOverlay.types.d.ts.map +1 -0
  131. package/lib/typescript/src/components/TileOverlay.web.d.ts +3 -0
  132. package/lib/typescript/src/components/TileOverlay.web.d.ts.map +1 -0
  133. package/lib/typescript/src/components/index.d.ts +7 -1
  134. package/lib/typescript/src/components/index.d.ts.map +1 -1
  135. package/lib/typescript/src/components/index.web.d.ts +7 -1
  136. package/lib/typescript/src/components/index.web.d.ts.map +1 -1
  137. package/lib/typescript/src/fabric/LuggCalloutViewNativeComponent.d.ts +13 -0
  138. package/lib/typescript/src/fabric/LuggCalloutViewNativeComponent.d.ts.map +1 -0
  139. package/lib/typescript/src/fabric/LuggCircleViewNativeComponent.d.ts +18 -0
  140. package/lib/typescript/src/fabric/LuggCircleViewNativeComponent.d.ts.map +1 -0
  141. package/lib/typescript/src/fabric/LuggGroundOverlayViewNativeComponent.d.ts +21 -0
  142. package/lib/typescript/src/fabric/LuggGroundOverlayViewNativeComponent.d.ts.map +1 -0
  143. package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts +10 -5
  144. package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts.map +1 -1
  145. package/lib/typescript/src/fabric/LuggMarkerViewNativeComponent.d.ts +8 -1
  146. package/lib/typescript/src/fabric/LuggMarkerViewNativeComponent.d.ts.map +1 -1
  147. package/lib/typescript/src/fabric/LuggTileOverlayViewNativeComponent.d.ts +21 -0
  148. package/lib/typescript/src/fabric/LuggTileOverlayViewNativeComponent.d.ts.map +1 -0
  149. package/lib/typescript/src/index.d.ts +10 -4
  150. package/lib/typescript/src/index.d.ts.map +1 -1
  151. package/lib/typescript/src/index.web.d.ts +9 -3
  152. package/lib/typescript/src/index.web.d.ts.map +1 -1
  153. package/lib/typescript/src/types.d.ts +0 -4
  154. package/lib/typescript/src/types.d.ts.map +1 -1
  155. package/package.json +6 -2
  156. package/src/MapProvider.web.tsx +15 -3
  157. package/src/MapView.tsx +12 -1
  158. package/src/MapView.types.ts +118 -1
  159. package/src/MapView.web.tsx +379 -322
  160. package/src/components/Circle.tsx +52 -0
  161. package/src/components/Circle.types.ts +33 -0
  162. package/src/components/Circle.web.tsx +95 -0
  163. package/src/components/GeoJson.tsx +3 -3
  164. package/src/components/GeoJson.types.ts +12 -1
  165. package/src/components/GroundOverlay.tsx +47 -0
  166. package/src/components/GroundOverlay.types.ts +36 -0
  167. package/src/components/GroundOverlay.web.tsx +81 -0
  168. package/src/components/Marker.tsx +59 -5
  169. package/src/components/Marker.types.ts +42 -2
  170. package/src/components/Marker.web.tsx +235 -99
  171. package/src/components/Polygon.web.tsx +93 -89
  172. package/src/components/Polyline.web.tsx +235 -235
  173. package/src/components/TileOverlay.tsx +44 -0
  174. package/src/components/TileOverlay.types.ts +37 -0
  175. package/src/components/TileOverlay.web.tsx +97 -0
  176. package/src/components/index.ts +11 -0
  177. package/src/components/index.web.ts +10 -1
  178. package/src/fabric/LuggCalloutViewNativeComponent.ts +20 -0
  179. package/src/fabric/LuggCircleViewNativeComponent.ts +25 -0
  180. package/src/fabric/LuggGroundOverlayViewNativeComponent.ts +29 -0
  181. package/src/fabric/LuggMapViewNativeComponent.ts +13 -5
  182. package/src/fabric/LuggMarkerViewNativeComponent.ts +14 -2
  183. package/src/fabric/LuggTileOverlayViewNativeComponent.ts +30 -0
  184. package/src/index.ts +14 -1
  185. package/src/index.web.ts +17 -1
  186. package/src/types.ts +0 -5
@@ -1,8 +1,19 @@
1
1
  #import "AppleMapProvider.h"
2
+
3
+ using facebook::react::LuggMapViewInsetAdjustment;
4
+ using facebook::react::LuggMapViewMapType;
5
+ using facebook::react::LuggMapViewPoiFilterMode;
6
+ using facebook::react::LuggMapViewTheme;
7
+
8
+ #import "../LuggCalloutView.h"
9
+ #import "../LuggCircleView.h"
10
+ #import "../LuggGroundOverlayView.h"
2
11
  #import "../LuggMarkerView.h"
3
12
  #import "../LuggPolygonView.h"
4
13
  #import "../LuggPolylineView.h"
14
+ #import "../LuggTileOverlayView.h"
5
15
  #import "../extensions/MKMapView+Zoom.h"
16
+ #import "LuggAnnotationView.h"
6
17
  #import "MKPolylineAnimator.h"
7
18
 
8
19
  @interface AppleMarkerAnnotation : NSObject <MKAnnotation>
@@ -11,20 +22,100 @@
11
22
  @property(nonatomic, copy, nullable) NSString *subtitle;
12
23
  @property(nonatomic, strong) LuggMarkerView *markerView;
13
24
  @property(nonatomic, weak) MKAnnotationView *annotationView;
25
+ @property(nonatomic, copy, nullable) dispatch_block_t pendingScaleAnimation;
14
26
  @end
15
27
 
16
28
  @implementation AppleMarkerAnnotation
17
29
  @end
18
30
 
31
+ @interface GroundImageOverlay : NSObject <MKOverlay>
32
+ @property(nonatomic, assign) CLLocationCoordinate2D coordinate;
33
+ @property(nonatomic, assign) MKMapRect boundingMapRect;
34
+ @property(nonatomic, strong) UIImage *image;
35
+ @property(nonatomic, assign) CGFloat opacity;
36
+ @end
37
+
38
+ @implementation GroundImageOverlay
39
+ @end
40
+
41
+ @interface BoundedTileOverlay : MKTileOverlay
42
+ @property(nonatomic, assign) CLLocationCoordinate2D southwest;
43
+ @property(nonatomic, assign) CLLocationCoordinate2D northeast;
44
+ @end
45
+
46
+ @implementation BoundedTileOverlay
47
+
48
+ static double tileToLat(NSInteger y, NSInteger z) {
49
+ double n = M_PI - 2.0 * M_PI * y / pow(2.0, z);
50
+ return 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n)));
51
+ }
52
+
53
+ static double tileToLng(NSInteger x, NSInteger z) {
54
+ return x / pow(2.0, z) * 360.0 - 180.0;
55
+ }
56
+
57
+ - (void)loadTileAtPath:(MKTileOverlayPath)path
58
+ result:(void (^)(NSData *_Nullable, NSError *_Nullable))result {
59
+ double tileSW_lat = tileToLat(path.y + 1, path.z);
60
+ double tileNE_lat = tileToLat(path.y, path.z);
61
+ double tileSW_lng = tileToLng(path.x, path.z);
62
+ double tileNE_lng = tileToLng(path.x + 1, path.z);
63
+
64
+ if (tileNE_lat < self.southwest.latitude ||
65
+ tileSW_lat > self.northeast.latitude ||
66
+ tileNE_lng < self.southwest.longitude ||
67
+ tileSW_lng > self.northeast.longitude) {
68
+ result(nil, nil);
69
+ return;
70
+ }
71
+
72
+ [super loadTileAtPath:path result:result];
73
+ }
74
+
75
+ @end
76
+
77
+ @interface GroundImageOverlayRenderer : MKOverlayRenderer
78
+ @property(nonatomic, strong) UIImage *image;
79
+ @property(nonatomic, assign) CGFloat overlayOpacity;
80
+ @end
81
+
82
+ @implementation GroundImageOverlayRenderer
83
+
84
+ - (void)drawMapRect:(MKMapRect)mapRect
85
+ zoomScale:(MKZoomScale)zoomScale
86
+ inContext:(CGContextRef)context {
87
+ MKMapRect overlayRect = self.overlay.boundingMapRect;
88
+ CGRect drawRect = [self rectForMapRect:overlayRect];
89
+
90
+ CGContextSaveGState(context);
91
+ CGContextSetAlpha(context, self.overlayOpacity);
92
+
93
+ // Flip the context for UIImage drawing
94
+ CGContextTranslateCTM(context, drawRect.origin.x,
95
+ drawRect.origin.y + drawRect.size.height);
96
+ CGContextScaleCTM(context, 1.0, -1.0);
97
+
98
+ CGRect imageRect =
99
+ CGRectMake(0, 0, drawRect.size.width, drawRect.size.height);
100
+ CGContextDrawImage(context, imageRect, self.image.CGImage);
101
+
102
+ CGContextRestoreGState(context);
103
+ }
104
+
105
+ @end
106
+
19
107
  @implementation LuggAppleMapViewContent
20
108
  @end
21
109
 
22
110
  @interface AppleMapProvider () <
23
- LuggMarkerViewDelegate, LuggPolylineViewDelegate, LuggPolygonViewDelegate,
111
+ LuggMarkerViewDelegate, LuggCalloutViewDelegate, LuggPolylineViewDelegate,
112
+ LuggPolygonViewDelegate, LuggCircleViewDelegate,
113
+ LuggGroundOverlayViewDelegate, LuggTileOverlayViewDelegate,
24
114
  UIGestureRecognizerDelegate>
25
115
  @end
26
116
 
27
117
  @implementation AppleMapProvider {
118
+ UIView *_wrapperView;
28
119
  LuggAppleMapViewContent *_mapView;
29
120
  BOOL _isMapReady;
30
121
  BOOL _isDragging;
@@ -32,14 +123,25 @@
32
123
  double _maxZoom;
33
124
  NSMapTable<id<MKOverlay>, LuggPolylineView *> *_overlayToPolylineMap;
34
125
  NSMapTable<id<MKOverlay>, LuggPolygonView *> *_overlayToPolygonMap;
126
+ NSMapTable<id<MKOverlay>, LuggCircleView *> *_overlayToCircleMap;
127
+ NSMapTable<id<MKOverlay>, LuggGroundOverlayView *>
128
+ *_overlayToGroundOverlayMap;
129
+ NSMapTable<id<MKOverlay>, LuggTileOverlayView *> *_overlayToTileOverlayMap;
35
130
  UITapGestureRecognizer *_tapGesture;
36
131
  UILongPressGestureRecognizer *_longPressGesture;
132
+ LuggMarkerView *_activeNonBubbledMarker;
133
+ BOOL _isReselectingAnnotation;
134
+ BOOL _isProgrammaticSelect;
37
135
  // Edge insets animation
38
136
  CADisplayLink *_edgeInsetsDisplayLink;
39
137
  UIEdgeInsets _edgeInsetsFrom;
40
138
  UIEdgeInsets _edgeInsetsTo;
139
+ UIEdgeInsets _edgeInsetsCurrent;
41
140
  CFTimeInterval _edgeInsetsAnimationStart;
42
141
  CFTimeInterval _edgeInsetsAnimationDuration;
142
+ BOOL _poiEnabled;
143
+ LuggMapViewPoiFilterMode _poiFilterMode;
144
+ NSArray<NSString *> *_poiFilterCategories;
43
145
  }
44
146
 
45
147
  @synthesize delegate = _delegate;
@@ -48,6 +150,12 @@
48
150
  if (self = [super init]) {
49
151
  _overlayToPolylineMap = [NSMapTable strongToWeakObjectsMapTable];
50
152
  _overlayToPolygonMap = [NSMapTable strongToWeakObjectsMapTable];
153
+ _overlayToCircleMap = [NSMapTable strongToWeakObjectsMapTable];
154
+ _overlayToGroundOverlayMap = [NSMapTable strongToWeakObjectsMapTable];
155
+ _overlayToTileOverlayMap = [NSMapTable strongToWeakObjectsMapTable];
156
+ _poiEnabled = YES;
157
+ _poiFilterMode = LuggMapViewPoiFilterMode::Including;
158
+ _poiFilterCategories = @[];
51
159
  }
52
160
  return self;
53
161
  }
@@ -91,6 +199,7 @@
91
199
 
92
200
  [_tapGesture requireGestureRecognizerToFail:_longPressGesture];
93
201
 
202
+ _wrapperView = wrapperView;
94
203
  [wrapperView addSubview:_mapView];
95
204
 
96
205
  MKCoordinateRegion region = [_mapView regionForCenterCoordinate:coordinate
@@ -103,6 +212,7 @@
103
212
  }
104
213
 
105
214
  - (void)destroy {
215
+ [self dismissNonBubbledCallout];
106
216
  [self stopEdgeInsetsAnimation];
107
217
  if (_tapGesture) {
108
218
  [_mapView removeGestureRecognizer:_tapGesture];
@@ -139,15 +249,181 @@
139
249
  _mapView.showsUserLocation = enabled;
140
250
  }
141
251
 
142
- - (void)setTheme:(NSInteger)theme {
252
+ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
253
+ static NSDictionary<NSString *, MKPointOfInterestCategory> *map;
254
+ static dispatch_once_t onceToken;
255
+ dispatch_once(&onceToken, ^{
256
+ NSMutableDictionary<NSString *, MKPointOfInterestCategory> *m =
257
+ [NSMutableDictionary dictionaryWithDictionary:@{
258
+ @"airport" : MKPointOfInterestCategoryAirport,
259
+ @"amusement-park" : MKPointOfInterestCategoryAmusementPark,
260
+ @"aquarium" : MKPointOfInterestCategoryAquarium,
261
+ @"atm" : MKPointOfInterestCategoryATM,
262
+ @"bakery" : MKPointOfInterestCategoryBakery,
263
+ @"bank" : MKPointOfInterestCategoryBank,
264
+ @"beach" : MKPointOfInterestCategoryBeach,
265
+ @"brewery" : MKPointOfInterestCategoryBrewery,
266
+ @"cafe" : MKPointOfInterestCategoryCafe,
267
+ @"campground" : MKPointOfInterestCategoryCampground,
268
+ @"car-rental" : MKPointOfInterestCategoryCarRental,
269
+ @"ev-charger" : MKPointOfInterestCategoryEVCharger,
270
+ @"fire-station" : MKPointOfInterestCategoryFireStation,
271
+ @"fitness-center" : MKPointOfInterestCategoryFitnessCenter,
272
+ @"food-market" : MKPointOfInterestCategoryFoodMarket,
273
+ @"gas-station" : MKPointOfInterestCategoryGasStation,
274
+ @"hospital" : MKPointOfInterestCategoryHospital,
275
+ @"hotel" : MKPointOfInterestCategoryHotel,
276
+ @"laundry" : MKPointOfInterestCategoryLaundry,
277
+ @"library" : MKPointOfInterestCategoryLibrary,
278
+ @"marina" : MKPointOfInterestCategoryMarina,
279
+ @"movie-theater" : MKPointOfInterestCategoryMovieTheater,
280
+ @"museum" : MKPointOfInterestCategoryMuseum,
281
+ @"national-park" : MKPointOfInterestCategoryNationalPark,
282
+ @"nightlife" : MKPointOfInterestCategoryNightlife,
283
+ @"park" : MKPointOfInterestCategoryPark,
284
+ @"parking" : MKPointOfInterestCategoryParking,
285
+ @"pharmacy" : MKPointOfInterestCategoryPharmacy,
286
+ @"police" : MKPointOfInterestCategoryPolice,
287
+ @"post-office" : MKPointOfInterestCategoryPostOffice,
288
+ @"public-transport" : MKPointOfInterestCategoryPublicTransport,
289
+ @"restaurant" : MKPointOfInterestCategoryRestaurant,
290
+ @"restroom" : MKPointOfInterestCategoryRestroom,
291
+ @"school" : MKPointOfInterestCategorySchool,
292
+ @"stadium" : MKPointOfInterestCategoryStadium,
293
+ @"store" : MKPointOfInterestCategoryStore,
294
+ @"theater" : MKPointOfInterestCategoryTheater,
295
+ @"university" : MKPointOfInterestCategoryUniversity,
296
+ @"winery" : MKPointOfInterestCategoryWinery,
297
+ @"zoo" : MKPointOfInterestCategoryZoo,
298
+ }];
299
+ if (@available(iOS 18.0, *)) {
300
+ [m addEntriesFromDictionary:@{
301
+ @"animal-service" : MKPointOfInterestCategoryAnimalService,
302
+ @"automotive-repair" : MKPointOfInterestCategoryAutomotiveRepair,
303
+ @"baseball" : MKPointOfInterestCategoryBaseball,
304
+ @"basketball" : MKPointOfInterestCategoryBasketball,
305
+ @"beauty" : MKPointOfInterestCategoryBeauty,
306
+ @"bowling" : MKPointOfInterestCategoryBowling,
307
+ @"castle" : MKPointOfInterestCategoryCastle,
308
+ @"convention-center" : MKPointOfInterestCategoryConventionCenter,
309
+ @"distillery" : MKPointOfInterestCategoryDistillery,
310
+ @"fairground" : MKPointOfInterestCategoryFairground,
311
+ @"fishing" : MKPointOfInterestCategoryFishing,
312
+ @"fortress" : MKPointOfInterestCategoryFortress,
313
+ @"go-kart" : MKPointOfInterestCategoryGoKart,
314
+ @"golf" : MKPointOfInterestCategoryGolf,
315
+ @"hiking" : MKPointOfInterestCategoryHiking,
316
+ @"kayaking" : MKPointOfInterestCategoryKayaking,
317
+ @"landmark" : MKPointOfInterestCategoryLandmark,
318
+ @"mailbox" : MKPointOfInterestCategoryMailbox,
319
+ @"mini-golf" : MKPointOfInterestCategoryMiniGolf,
320
+ @"music-venue" : MKPointOfInterestCategoryMusicVenue,
321
+ @"national-monument" : MKPointOfInterestCategoryNationalMonument,
322
+ @"planetarium" : MKPointOfInterestCategoryPlanetarium,
323
+ @"rock-climbing" : MKPointOfInterestCategoryRockClimbing,
324
+ @"rv-park" : MKPointOfInterestCategoryRVPark,
325
+ @"skate-park" : MKPointOfInterestCategorySkatePark,
326
+ @"skating" : MKPointOfInterestCategorySkating,
327
+ @"skiing" : MKPointOfInterestCategorySkiing,
328
+ @"soccer" : MKPointOfInterestCategorySoccer,
329
+ @"spa" : MKPointOfInterestCategorySpa,
330
+ @"surfing" : MKPointOfInterestCategorySurfing,
331
+ @"swimming" : MKPointOfInterestCategorySwimming,
332
+ @"tennis" : MKPointOfInterestCategoryTennis,
333
+ @"volleyball" : MKPointOfInterestCategoryVolleyball,
334
+ }];
335
+ }
336
+ map = [m copy];
337
+ });
338
+ return map[string];
339
+ }
340
+
341
+ - (void)applyPoiFilter {
342
+ if (!_poiEnabled) {
343
+ _mapView.pointOfInterestFilter =
344
+ MKPointOfInterestFilter.filterExcludingAllCategories;
345
+ return;
346
+ }
347
+ if (_poiFilterCategories.count > 0) {
348
+ NSMutableArray<MKPointOfInterestCategory> *categories =
349
+ [NSMutableArray array];
350
+ for (NSString *name in _poiFilterCategories) {
351
+ MKPointOfInterestCategory category = poiCategoryFromString(name);
352
+ if (category) {
353
+ [categories addObject:category];
354
+ }
355
+ }
356
+ if (categories.count > 0) {
357
+ if (_poiFilterMode == LuggMapViewPoiFilterMode::Excluding) {
358
+ _mapView.pointOfInterestFilter = [[MKPointOfInterestFilter alloc]
359
+ initExcludingCategories:categories];
360
+ } else {
361
+ _mapView.pointOfInterestFilter = [[MKPointOfInterestFilter alloc]
362
+ initIncludingCategories:categories];
363
+ }
364
+ return;
365
+ }
366
+ }
367
+ _mapView.pointOfInterestFilter = nil;
368
+ }
369
+
370
+ - (void)setInsetAdjustment:(LuggMapViewInsetAdjustment)insetAdjustment {
371
+ BOOL automatic = (insetAdjustment == LuggMapViewInsetAdjustment::Automatic);
372
+ if (_mapView.insetsLayoutMarginsFromSafeArea == automatic)
373
+ return;
374
+
375
+ CLLocationCoordinate2D center = _mapView.centerCoordinate;
376
+ _mapView.insetsLayoutMarginsFromSafeArea = automatic;
377
+
378
+ dispatch_async(dispatch_get_main_queue(), ^{
379
+ [self->_mapView setCenterCoordinate:center animated:NO];
380
+ });
381
+ }
382
+
383
+ - (void)setPoiEnabled:(BOOL)enabled {
384
+ _poiEnabled = enabled;
385
+ [self applyPoiFilter];
386
+ }
387
+
388
+ - (void)setPoiFilterMode:(LuggMapViewPoiFilterMode)mode {
389
+ _poiFilterMode = mode;
390
+ [self applyPoiFilter];
391
+ }
392
+
393
+ - (void)setPoiFilterCategories:(NSArray<NSString *> *)categories {
394
+ _poiFilterCategories = categories;
395
+ [self applyPoiFilter];
396
+ }
397
+
398
+ - (void)setMapType:(LuggMapViewMapType)mapType {
399
+ switch (mapType) {
400
+ case LuggMapViewMapType::Satellite:
401
+ _mapView.mapType = MKMapTypeSatellite;
402
+ break;
403
+ case LuggMapViewMapType::Terrain:
404
+ _mapView.mapType = MKMapTypeStandard;
405
+ break;
406
+ case LuggMapViewMapType::Hybrid:
407
+ _mapView.mapType = MKMapTypeHybrid;
408
+ break;
409
+ case LuggMapViewMapType::MutedStandard:
410
+ _mapView.mapType = MKMapTypeMutedStandard;
411
+ break;
412
+ default:
413
+ _mapView.mapType = MKMapTypeStandard;
414
+ break;
415
+ }
416
+ }
417
+
418
+ - (void)setTheme:(LuggMapViewTheme)theme {
143
419
  switch (theme) {
144
- case 1: // Dark
420
+ case LuggMapViewTheme::Dark:
145
421
  _mapView.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
146
422
  break;
147
- case 0: // Light
423
+ case LuggMapViewTheme::Light:
148
424
  _mapView.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
149
425
  break;
150
- default: // System
426
+ default:
151
427
  _mapView.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
152
428
  break;
153
429
  }
@@ -174,6 +450,7 @@
174
450
  CGFloat deltaY = newOffsetY - oldOffsetY;
175
451
 
176
452
  _mapView.layoutMargins = edgeInsets;
453
+ _edgeInsetsCurrent = edgeInsets;
177
454
 
178
455
  if (deltaX != 0 || deltaY != 0) {
179
456
  CLLocationCoordinate2D currentCenter = _mapView.centerCoordinate;
@@ -224,7 +501,7 @@
224
501
  _edgeInsetsFrom.right +
225
502
  (_edgeInsetsTo.right - _edgeInsetsFrom.right) * t);
226
503
 
227
- [self setEdgeInsets:current oldEdgeInsets:_mapView.layoutMargins];
504
+ [self setEdgeInsets:current oldEdgeInsets:_edgeInsetsCurrent];
228
505
 
229
506
  if (progress >= 1.0) {
230
507
  [self stopEdgeInsetsAnimation];
@@ -307,6 +584,55 @@
307
584
  return nil;
308
585
  }
309
586
 
587
+ - (LuggCircleView *)hitTestCircleAtPoint:(CGPoint)point {
588
+ CLLocationCoordinate2D tapCoordinate = [_mapView convertPoint:point
589
+ toCoordinateFromView:_mapView];
590
+ CLLocation *tapLocation =
591
+ [[CLLocation alloc] initWithLatitude:tapCoordinate.latitude
592
+ longitude:tapCoordinate.longitude];
593
+
594
+ NSArray<id<MKOverlay>> *overlays = _mapView.overlays;
595
+ for (NSInteger i = overlays.count - 1; i >= 0; i--) {
596
+ id<MKOverlay> overlay = overlays[i];
597
+ if (![overlay isKindOfClass:[MKCircle class]])
598
+ continue;
599
+
600
+ LuggCircleView *circleView = [_overlayToCircleMap objectForKey:overlay];
601
+ if (!circleView || !circleView.tappable)
602
+ continue;
603
+
604
+ CLLocation *center =
605
+ [[CLLocation alloc] initWithLatitude:circleView.center.latitude
606
+ longitude:circleView.center.longitude];
607
+ CLLocationDistance distance = [tapLocation distanceFromLocation:center];
608
+ if (distance <= circleView.radius)
609
+ return circleView;
610
+ }
611
+ return nil;
612
+ }
613
+
614
+ - (LuggGroundOverlayView *)hitTestGroundOverlayAtPoint:(CGPoint)point {
615
+ CLLocationCoordinate2D tapCoordinate = [_mapView convertPoint:point
616
+ toCoordinateFromView:_mapView];
617
+
618
+ NSArray<id<MKOverlay>> *overlays = _mapView.overlays;
619
+ for (NSInteger i = overlays.count - 1; i >= 0; i--) {
620
+ id<MKOverlay> overlay = overlays[i];
621
+ if (![overlay isKindOfClass:[GroundImageOverlay class]])
622
+ continue;
623
+
624
+ LuggGroundOverlayView *view =
625
+ [_overlayToGroundOverlayMap objectForKey:overlay];
626
+ if (!view || !view.tappable)
627
+ continue;
628
+
629
+ MKMapPoint mapPoint = MKMapPointForCoordinate(tapCoordinate);
630
+ if (MKMapRectContainsPoint(overlay.boundingMapRect, mapPoint))
631
+ return view;
632
+ }
633
+ return nil;
634
+ }
635
+
310
636
  - (BOOL)hitTestAnnotationAtPoint:(CGPoint)point {
311
637
  for (id<MKAnnotation> annotation in _mapView.annotations) {
312
638
  MKAnnotationView *view = [_mapView viewForAnnotation:annotation];
@@ -319,15 +645,41 @@
319
645
  return NO;
320
646
  }
321
647
 
648
+ - (BOOL)hitTestCalloutAtPoint:(CGPoint)point {
649
+ if (!_activeNonBubbledMarker)
650
+ return NO;
651
+
652
+ UIView *contentView = _activeNonBubbledMarker.calloutView.contentView;
653
+ CGPoint local = [contentView convertPoint:point fromView:_mapView];
654
+ return [contentView pointInside:local withEvent:nil];
655
+ }
656
+
322
657
  - (void)handleTap:(UITapGestureRecognizer *)gesture {
323
658
  if (gesture.state != UIGestureRecognizerStateEnded)
324
659
  return;
325
660
 
326
661
  CGPoint point = [gesture locationInView:_mapView];
662
+ if ([self hitTestCalloutAtPoint:point])
663
+ return;
664
+
665
+ [self dismissNonBubbledCallout];
327
666
 
328
667
  if ([self hitTestAnnotationAtPoint:point])
329
668
  return;
330
669
 
670
+ LuggGroundOverlayView *groundOverlayView =
671
+ [self hitTestGroundOverlayAtPoint:point];
672
+ if (groundOverlayView) {
673
+ [groundOverlayView emitPressEvent];
674
+ return;
675
+ }
676
+
677
+ LuggCircleView *circleView = [self hitTestCircleAtPoint:point];
678
+ if (circleView) {
679
+ [circleView emitPressEvent];
680
+ return;
681
+ }
682
+
331
683
  LuggPolygonView *polygonView = [self hitTestPolygonAtPoint:point];
332
684
  if (polygonView) {
333
685
  [polygonView emitPressEvent];
@@ -390,6 +742,7 @@
390
742
  longitude:mapView.centerCoordinate.longitude
391
743
  zoom:mapView.zoomLevel
392
744
  gesture:_isDragging];
745
+ [self positionNonBubbledCallout];
393
746
  }
394
747
 
395
748
  - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
@@ -423,22 +776,23 @@
423
776
  }
424
777
 
425
778
  if (!markerView.hasCustomView) {
426
- MKMarkerAnnotationView *markerAnnotationView =
427
- [[MKMarkerAnnotationView alloc] initWithAnnotation:annotation
428
- reuseIdentifier:nil];
779
+ LuggMarkerAnnotationView *markerAnnotationView =
780
+ [[LuggMarkerAnnotationView alloc] initWithAnnotation:annotation
781
+ reuseIdentifier:nil];
429
782
  markerAnnotationView.canShowCallout = YES;
430
783
  markerAnnotationView.displayPriority = MKFeatureDisplayPriorityRequired;
431
784
  markerAnnotationView.layer.zPosition = markerView.zIndex;
432
785
  markerAnnotationView.zPriority = markerView.zIndex;
433
786
  markerAnnotationView.draggable = markerView.draggable;
787
+ [self applyCalloutView:markerView annotationView:markerAnnotationView];
434
788
  [self addCenterTapGesture:markerAnnotationView];
435
789
  markerAnnotation.annotationView = markerAnnotationView;
436
790
  return markerAnnotationView;
437
791
  }
438
792
 
439
- MKAnnotationView *annotationView =
440
- [[MKAnnotationView alloc] initWithAnnotation:annotation
441
- reuseIdentifier:nil];
793
+ LuggAnnotationView *annotationView =
794
+ [[LuggAnnotationView alloc] initWithAnnotation:annotation
795
+ reuseIdentifier:nil];
442
796
  annotationView.canShowCallout = YES;
443
797
  annotationView.displayPriority = MKFeatureDisplayPriorityRequired;
444
798
  annotationView.layer.zPosition = markerView.zIndex;
@@ -446,6 +800,8 @@
446
800
  annotationView.draggable = markerView.draggable;
447
801
  [self addCenterTapGesture:annotationView];
448
802
 
803
+ [self applyCalloutView:markerView annotationView:annotationView];
804
+
449
805
  if (!markerView.rasterize) {
450
806
  UIView *iconView = markerView.iconView;
451
807
  [iconView removeFromSuperview];
@@ -501,6 +857,37 @@
501
857
  return renderer;
502
858
  }
503
859
 
860
+ if ([overlay isKindOfClass:[MKCircle class]]) {
861
+ LuggCircleView *circleView = [_overlayToCircleMap objectForKey:overlay];
862
+ MKCircle *circle = (MKCircle *)overlay;
863
+
864
+ MKCircleRenderer *renderer =
865
+ [[MKCircleRenderer alloc] initWithCircle:circle];
866
+ if (circleView) {
867
+ renderer.fillColor = circleView.fillColor;
868
+ renderer.strokeColor = circleView.strokeColor;
869
+ renderer.lineWidth = circleView.strokeWidth;
870
+ circleView.renderer = renderer;
871
+ }
872
+ return renderer;
873
+ }
874
+
875
+ if ([overlay isKindOfClass:[GroundImageOverlay class]]) {
876
+ GroundImageOverlay *groundOverlay = (GroundImageOverlay *)overlay;
877
+ GroundImageOverlayRenderer *renderer =
878
+ [[GroundImageOverlayRenderer alloc] initWithOverlay:overlay];
879
+ renderer.image = groundOverlay.image;
880
+ renderer.overlayOpacity = groundOverlay.opacity;
881
+ return renderer;
882
+ }
883
+
884
+ if ([overlay isKindOfClass:[MKTileOverlay class]]) {
885
+ MKTileOverlay *tileOverlay = (MKTileOverlay *)overlay;
886
+ MKTileOverlayRenderer *renderer =
887
+ [[MKTileOverlayRenderer alloc] initWithTileOverlay:tileOverlay];
888
+ return renderer;
889
+ }
890
+
504
891
  return nil;
505
892
  }
506
893
 
@@ -513,9 +900,104 @@
513
900
  LuggMarkerView *markerView = annotation.markerView;
514
901
 
515
902
  if (markerView) {
516
- CGPoint point = [_mapView convertCoordinate:markerView.coordinate
517
- toPointToView:_mapView];
518
- [markerView emitPressEventWithPoint:point];
903
+ LuggCalloutView *calloutView = markerView.calloutView;
904
+ if (calloutView && !calloutView.bubbled && calloutView.hasCustomContent) {
905
+ [self showNonBubbledCallout:markerView];
906
+ }
907
+ }
908
+ }
909
+
910
+ - (void)mapView:(MKMapView *)mapView
911
+ didDeselectAnnotationView:(MKAnnotationView *)view {
912
+ if (![view.annotation isKindOfClass:[AppleMarkerAnnotation class]])
913
+ return;
914
+
915
+ AppleMarkerAnnotation *annotation = (AppleMarkerAnnotation *)view.annotation;
916
+ LuggMarkerView *markerView = annotation.markerView;
917
+
918
+ if (markerView && _activeNonBubbledMarker == markerView &&
919
+ !_isReselectingAnnotation) {
920
+ _isReselectingAnnotation = YES;
921
+ [_mapView selectAnnotation:annotation animated:NO];
922
+ _isReselectingAnnotation = NO;
923
+ }
924
+ }
925
+
926
+ - (void)showNonBubbledCallout:(LuggMarkerView *)markerView {
927
+ [self dismissNonBubbledCallout];
928
+
929
+ LuggCalloutView *calloutView = markerView.calloutView;
930
+ UIView *contentView = calloutView.contentView;
931
+ [contentView removeFromSuperview];
932
+
933
+ contentView.userInteractionEnabled = YES;
934
+ contentView.hidden = YES;
935
+ calloutView.delegate = self;
936
+
937
+ AppleMarkerAnnotation *annotation =
938
+ (AppleMarkerAnnotation *)markerView.marker;
939
+ MKAnnotationView *annotationView = annotation.annotationView;
940
+ if (annotationView) {
941
+ annotationView.clipsToBounds = NO;
942
+ [annotationView addSubview:contentView];
943
+ } else {
944
+ [_wrapperView addSubview:contentView];
945
+ }
946
+
947
+ _activeNonBubbledMarker = markerView;
948
+ [self positionNonBubbledCallout];
949
+ }
950
+
951
+ - (void)calloutViewDidUpdate:(LuggCalloutView *)calloutView {
952
+ [self positionNonBubbledCallout];
953
+ }
954
+
955
+ - (void)positionNonBubbledCallout {
956
+ if (!_activeNonBubbledMarker)
957
+ return;
958
+
959
+ LuggCalloutView *calloutView = _activeNonBubbledMarker.calloutView;
960
+ UIView *contentView = calloutView.contentView;
961
+ CGSize contentSize = contentView.bounds.size;
962
+ if (contentSize.width <= 0 || contentSize.height <= 0)
963
+ return;
964
+
965
+ CGPoint offset = calloutView.offset;
966
+
967
+ AppleMarkerAnnotation *annotation =
968
+ (AppleMarkerAnnotation *)_activeNonBubbledMarker.marker;
969
+ MKAnnotationView *annotationView = annotation.annotationView;
970
+
971
+ if (annotationView && contentView.superview == annotationView) {
972
+ CGPoint center =
973
+ CGPointMake(annotationView.bounds.size.width / 2.0 + offset.x,
974
+ -contentSize.height / 2.0 + offset.y);
975
+ contentView.center = center;
976
+ } else {
977
+ CGPoint point =
978
+ [_mapView convertCoordinate:_activeNonBubbledMarker.coordinate
979
+ toPointToView:_wrapperView];
980
+ contentView.center = CGPointMake(
981
+ point.x + offset.x, point.y - contentSize.height / 2.0 + offset.y);
982
+ }
983
+
984
+ contentView.hidden = NO;
985
+ }
986
+
987
+ - (void)dismissNonBubbledCallout {
988
+ if (!_activeNonBubbledMarker)
989
+ return;
990
+
991
+ LuggMarkerView *markerView = _activeNonBubbledMarker;
992
+ _activeNonBubbledMarker = nil;
993
+
994
+ markerView.calloutView.delegate = nil;
995
+ [markerView.calloutView.contentView removeFromSuperview];
996
+
997
+ AppleMarkerAnnotation *annotation =
998
+ (AppleMarkerAnnotation *)markerView.marker;
999
+ if (annotation) {
1000
+ [_mapView deselectAnnotation:annotation animated:NO];
519
1001
  }
520
1002
  }
521
1003
 
@@ -570,7 +1052,74 @@
570
1052
  if ([view.annotation isKindOfClass:[AppleMarkerAnnotation class]]) {
571
1053
  AppleMarkerAnnotation *annotation =
572
1054
  (AppleMarkerAnnotation *)view.annotation;
573
- [_mapView setCenterCoordinate:annotation.coordinate animated:YES];
1055
+ LuggMarkerView *markerView = annotation.markerView;
1056
+
1057
+ if (markerView) {
1058
+ CGPoint point = [_mapView convertCoordinate:markerView.coordinate
1059
+ toPointToView:_mapView];
1060
+ [markerView emitPressEventWithPoint:point];
1061
+
1062
+ if (markerView.centerOnPress) {
1063
+ [_mapView setCenterCoordinate:annotation.coordinate animated:YES];
1064
+ }
1065
+ }
1066
+ }
1067
+ }
1068
+
1069
+ - (void)applyCalloutView:(LuggMarkerView *)markerView
1070
+ annotationView:(MKAnnotationView *)annotationView {
1071
+ LuggCalloutView *calloutView = markerView.calloutView;
1072
+ if (!calloutView) {
1073
+ annotationView.detailCalloutAccessoryView = nil;
1074
+ annotationView.rightCalloutAccessoryView = nil;
1075
+ return;
1076
+ }
1077
+
1078
+ annotationView.rightCalloutAccessoryView = nil;
1079
+
1080
+ if (!calloutView.bubbled) {
1081
+ annotationView.canShowCallout = NO;
1082
+ annotationView.detailCalloutAccessoryView = nil;
1083
+ return;
1084
+ }
1085
+
1086
+ annotationView.detailCalloutAccessoryView =
1087
+ calloutView.hasCustomContent ? calloutView.contentView : nil;
1088
+ }
1089
+
1090
+ - (void)showCalloutForMarkerView:(LuggMarkerView *)markerView {
1091
+ LuggCalloutView *calloutView = markerView.calloutView;
1092
+ _isProgrammaticSelect = YES;
1093
+
1094
+ if (calloutView && calloutView.hasCustomContent) {
1095
+ [self dismissNonBubbledCallout];
1096
+ AppleMarkerAnnotation *annotation =
1097
+ (AppleMarkerAnnotation *)markerView.marker;
1098
+ if (!calloutView.bubbled) {
1099
+ [self showNonBubbledCallout:markerView];
1100
+ } else if (annotation) {
1101
+ [_mapView selectAnnotation:annotation animated:YES];
1102
+ }
1103
+ _isProgrammaticSelect = NO;
1104
+ return;
1105
+ }
1106
+
1107
+ if (markerView.title.length > 0) {
1108
+ AppleMarkerAnnotation *annotation =
1109
+ (AppleMarkerAnnotation *)markerView.marker;
1110
+ if (annotation) {
1111
+ [_mapView selectAnnotation:annotation animated:YES];
1112
+ }
1113
+ }
1114
+ _isProgrammaticSelect = NO;
1115
+ }
1116
+
1117
+ - (void)hideCalloutForMarkerView:(LuggMarkerView *)markerView {
1118
+ [self dismissNonBubbledCallout];
1119
+ AppleMarkerAnnotation *annotation =
1120
+ (AppleMarkerAnnotation *)markerView.marker;
1121
+ if (annotation) {
1122
+ [_mapView deselectAnnotation:annotation animated:YES];
574
1123
  }
575
1124
  }
576
1125
 
@@ -600,6 +1149,7 @@
600
1149
  annotationView.draggable = markerView.draggable;
601
1150
  annotationView.layer.zPosition = markerView.zIndex;
602
1151
  annotationView.zPriority = markerView.zIndex;
1152
+ [self applyCalloutView:markerView annotationView:annotationView];
603
1153
  }
604
1154
 
605
1155
  [self updateAnnotationViewFrame:annotation];
@@ -617,6 +1167,24 @@
617
1167
  [self syncPolygonView:polygonView];
618
1168
  }
619
1169
 
1170
+ #pragma mark - CircleViewDelegate
1171
+
1172
+ - (void)circleViewDidUpdate:(LuggCircleView *)circleView {
1173
+ [self syncCircleView:circleView];
1174
+ }
1175
+
1176
+ #pragma mark - GroundOverlayViewDelegate
1177
+
1178
+ - (void)groundOverlayViewDidUpdate:(LuggGroundOverlayView *)groundOverlayView {
1179
+ [self syncGroundOverlayView:groundOverlayView];
1180
+ }
1181
+
1182
+ #pragma mark - TileOverlayViewDelegate
1183
+
1184
+ - (void)tileOverlayViewDidUpdate:(LuggTileOverlayView *)tileOverlayView {
1185
+ [self syncTileOverlayView:tileOverlayView];
1186
+ }
1187
+
620
1188
  #pragma mark - Marker Management
621
1189
 
622
1190
  - (void)addMarkerView:(LuggMarkerView *)markerView {
@@ -640,6 +1208,10 @@
640
1208
  (AppleMarkerAnnotation *)markerView.marker;
641
1209
 
642
1210
  if (annotation) {
1211
+ if (annotation.pendingScaleAnimation) {
1212
+ dispatch_block_cancel(annotation.pendingScaleAnimation);
1213
+ annotation.pendingScaleAnimation = nil;
1214
+ }
643
1215
  annotation.annotationView.transform = CGAffineTransformIdentity;
644
1216
  annotation.markerView = nil;
645
1217
  annotation.annotationView = nil;
@@ -658,31 +1230,65 @@
658
1230
  annotationView.transform = CGAffineTransformIdentity;
659
1231
 
660
1232
  UIView *iconView = markerView.iconView;
661
- CGRect frame = iconView.frame;
662
- if (frame.size.width <= 0 || frame.size.height <= 0)
1233
+ iconView.transform = CGAffineTransformIdentity;
1234
+
1235
+ // Clean up iconView from in-progress rasterize animation
1236
+ if (markerView.rasterize && [iconView superview] == annotationView) {
1237
+ [iconView removeFromSuperview];
1238
+ iconView.layer.anchorPoint = CGPointMake(0.5, 0.5);
1239
+ }
1240
+
1241
+ CGSize size = iconView.bounds.size;
1242
+ if (size.width <= 0 || size.height <= 0)
663
1243
  return;
664
1244
 
665
1245
  CGFloat scale = markerView.scale;
666
1246
  CGPoint anchor = markerView.anchor;
1247
+ CGFloat scaledWidth = size.width * scale;
1248
+ CGFloat scaledHeight = size.height * scale;
667
1249
 
668
- if (markerView.rasterize) {
669
- annotationView.image = [markerView createScaledIconImage];
670
- } else {
671
- iconView.layer.anchorPoint = anchor;
672
- iconView.transform = CGAffineTransformMakeScale(scale, scale);
673
- iconView.frame =
674
- CGRectMake(frame.size.width * (0.5 - anchor.x) * (scale - 1),
675
- frame.size.height * (0.5 - anchor.y) * (scale - 1),
676
- frame.size.width, frame.size.height);
677
- }
678
-
679
- annotationView.bounds =
680
- CGRectMake(0, 0, frame.size.width * scale, frame.size.height * scale);
681
- annotationView.centerOffset =
682
- CGPointMake(frame.size.width * scale * (anchor.x - 0.5),
683
- -frame.size.height * scale * (anchor.y - 0.5));
1250
+ AppleMarkerAnnotation *annotation =
1251
+ (AppleMarkerAnnotation *)markerView.marker;
1252
+
1253
+ // Cancel any pending rasterize
1254
+ if (annotation.pendingScaleAnimation) {
1255
+ dispatch_block_cancel(annotation.pendingScaleAnimation);
1256
+ annotation.pendingScaleAnimation = nil;
1257
+ }
1258
+
1259
+ // Use live iconView during rapid updates (skip expensive rasterize per frame)
1260
+ if (markerView.rasterize && [iconView superview] != annotationView) {
1261
+ annotationView.image = nil;
1262
+ [annotationView addSubview:iconView];
1263
+ }
1264
+
1265
+ iconView.layer.anchorPoint = anchor;
1266
+ iconView.bounds = CGRectMake(0, 0, size.width, size.height);
1267
+ iconView.center =
1268
+ CGPointMake(scaledWidth * anchor.x, scaledHeight * anchor.y);
1269
+ iconView.transform = CGAffineTransformMakeScale(scale, scale);
1270
+
1271
+ annotationView.bounds = CGRectMake(0, 0, scaledWidth, scaledHeight);
1272
+ annotationView.centerOffset = CGPointMake(scaledWidth * (0.5 - anchor.x),
1273
+ scaledHeight * (0.5 - anchor.y));
684
1274
  annotationView.transform =
685
1275
  CGAffineTransformMakeRotation(markerView.rotate * M_PI / 180.0);
1276
+
1277
+ // Debounce: rasterize once updates settle
1278
+ if (markerView.rasterize) {
1279
+ dispatch_block_t rasterizeBlock =
1280
+ dispatch_block_create((dispatch_block_flags_t)0, ^{
1281
+ annotation.pendingScaleAnimation = nil;
1282
+ annotationView.image = [markerView createScaledIconImage];
1283
+ [iconView removeFromSuperview];
1284
+ [markerView resetIconViewTransform];
1285
+ });
1286
+
1287
+ annotation.pendingScaleAnimation = rasterizeBlock;
1288
+ dispatch_after(
1289
+ dispatch_time(DISPATCH_TIME_NOW, (int64_t)(150 * NSEC_PER_MSEC)),
1290
+ dispatch_get_main_queue(), rasterizeBlock);
1291
+ }
686
1292
  }
687
1293
 
688
1294
  - (void)updateAnnotationViewFrame:(AppleMarkerAnnotation *)annotation {
@@ -902,6 +1508,196 @@
902
1508
  return [interiorPolygons copy];
903
1509
  }
904
1510
 
1511
+ #pragma mark - Circle Management
1512
+
1513
+ - (void)addCircleView:(LuggCircleView *)circleView {
1514
+ circleView.delegate = self;
1515
+ [self addCircleOverlayToMap:circleView];
1516
+ }
1517
+
1518
+ - (void)removeCircleView:(LuggCircleView *)circleView {
1519
+ circleView.delegate = nil;
1520
+ MKCircle *circle = (MKCircle *)circleView.circle;
1521
+ if (circle) {
1522
+ [_overlayToCircleMap removeObjectForKey:circle];
1523
+ [_mapView removeOverlay:circle];
1524
+ circleView.circle = nil;
1525
+ }
1526
+ }
1527
+
1528
+ - (void)syncCircleView:(LuggCircleView *)circleView {
1529
+ if (!_mapView)
1530
+ return;
1531
+
1532
+ MKCircle *oldCircle = (MKCircle *)circleView.circle;
1533
+
1534
+ MKCircle *newCircle = [MKCircle circleWithCenterCoordinate:circleView.center
1535
+ radius:circleView.radius];
1536
+
1537
+ circleView.circle = newCircle;
1538
+ [_overlayToCircleMap setObject:circleView forKey:newCircle];
1539
+
1540
+ MKCircleRenderer *renderer = (MKCircleRenderer *)circleView.renderer;
1541
+ if (renderer && oldCircle) {
1542
+ [_overlayToCircleMap removeObjectForKey:oldCircle];
1543
+ [_mapView removeOverlay:oldCircle];
1544
+ [self insertOverlay:newCircle withZIndex:circleView.zIndex];
1545
+ circleView.renderer = nil;
1546
+ return;
1547
+ }
1548
+
1549
+ if (oldCircle) {
1550
+ [_overlayToCircleMap removeObjectForKey:oldCircle];
1551
+ [_mapView removeOverlay:oldCircle];
1552
+ }
1553
+ [self insertOverlay:newCircle withZIndex:circleView.zIndex];
1554
+ }
1555
+
1556
+ - (void)addCircleOverlayToMap:(LuggCircleView *)circleView {
1557
+ if (!_mapView)
1558
+ return;
1559
+
1560
+ MKCircle *circle = [MKCircle circleWithCenterCoordinate:circleView.center
1561
+ radius:circleView.radius];
1562
+
1563
+ circleView.circle = circle;
1564
+ [_overlayToCircleMap setObject:circleView forKey:circle];
1565
+ [self insertOverlay:circle withZIndex:circleView.zIndex];
1566
+ }
1567
+
1568
+ #pragma mark - Ground Overlay Management
1569
+
1570
+ - (void)addGroundOverlayView:(LuggGroundOverlayView *)groundOverlayView {
1571
+ groundOverlayView.delegate = self;
1572
+ [self syncGroundOverlayView:groundOverlayView];
1573
+ }
1574
+
1575
+ - (void)removeGroundOverlayView:(LuggGroundOverlayView *)groundOverlayView {
1576
+ groundOverlayView.delegate = nil;
1577
+ GroundImageOverlay *overlay = (GroundImageOverlay *)groundOverlayView.overlay;
1578
+ if (overlay) {
1579
+ [_overlayToGroundOverlayMap removeObjectForKey:overlay];
1580
+ [_mapView removeOverlay:overlay];
1581
+ groundOverlayView.overlay = nil;
1582
+ }
1583
+ }
1584
+
1585
+ - (void)syncGroundOverlayView:(LuggGroundOverlayView *)groundOverlayView {
1586
+ if (!_mapView)
1587
+ return;
1588
+
1589
+ NSString *imageUri = groundOverlayView.imageUri;
1590
+ if (imageUri.length == 0)
1591
+ return;
1592
+
1593
+ // Remove old overlay
1594
+ GroundImageOverlay *oldOverlay =
1595
+ (GroundImageOverlay *)groundOverlayView.overlay;
1596
+ if (oldOverlay) {
1597
+ [_overlayToGroundOverlayMap removeObjectForKey:oldOverlay];
1598
+ [_mapView removeOverlay:oldOverlay];
1599
+ groundOverlayView.overlay = nil;
1600
+ }
1601
+
1602
+ NSURL *url = [NSURL URLWithString:imageUri];
1603
+ if (!url)
1604
+ return;
1605
+
1606
+ __weak __typeof(self) weakSelf = self;
1607
+ __weak LuggGroundOverlayView *weakOverlayView = groundOverlayView;
1608
+ dispatch_async(
1609
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
1610
+ NSData *data = [NSData dataWithContentsOfURL:url];
1611
+ UIImage *image = data ? [UIImage imageWithData:data] : nil;
1612
+ dispatch_async(dispatch_get_main_queue(), ^{
1613
+ [weakSelf addGroundOverlayToMap:weakOverlayView image:image];
1614
+ });
1615
+ });
1616
+ }
1617
+
1618
+ - (void)addGroundOverlayToMap:(LuggGroundOverlayView *)groundOverlayView
1619
+ image:(UIImage *)image {
1620
+ if (!_mapView || !groundOverlayView || !image)
1621
+ return;
1622
+
1623
+ MKMapPoint sw = MKMapPointForCoordinate(groundOverlayView.southwest);
1624
+ MKMapPoint ne = MKMapPointForCoordinate(groundOverlayView.northeast);
1625
+
1626
+ MKMapRect mapRect = MKMapRectMake(MIN(sw.x, ne.x), MIN(sw.y, ne.y),
1627
+ fabs(ne.x - sw.x), fabs(ne.y - sw.y));
1628
+
1629
+ CLLocationCoordinate2D center =
1630
+ CLLocationCoordinate2DMake((groundOverlayView.northeast.latitude +
1631
+ groundOverlayView.southwest.latitude) /
1632
+ 2.0,
1633
+ (groundOverlayView.northeast.longitude +
1634
+ groundOverlayView.southwest.longitude) /
1635
+ 2.0);
1636
+
1637
+ GroundImageOverlay *overlay = [[GroundImageOverlay alloc] init];
1638
+ overlay.coordinate = center;
1639
+ overlay.boundingMapRect = mapRect;
1640
+ overlay.image = image;
1641
+ overlay.opacity = groundOverlayView.opacity;
1642
+
1643
+ groundOverlayView.overlay = overlay;
1644
+ [_overlayToGroundOverlayMap setObject:groundOverlayView forKey:overlay];
1645
+ [self insertOverlay:overlay withZIndex:groundOverlayView.zIndex];
1646
+ }
1647
+
1648
+ #pragma mark - Tile Overlay Management
1649
+
1650
+ - (void)addTileOverlayView:(LuggTileOverlayView *)tileOverlayView {
1651
+ tileOverlayView.delegate = self;
1652
+ [self syncTileOverlayView:tileOverlayView];
1653
+ }
1654
+
1655
+ - (void)removeTileOverlayView:(LuggTileOverlayView *)tileOverlayView {
1656
+ tileOverlayView.delegate = nil;
1657
+ MKTileOverlay *overlay = (MKTileOverlay *)tileOverlayView.overlay;
1658
+ if (overlay) {
1659
+ [_overlayToTileOverlayMap removeObjectForKey:overlay];
1660
+ [_mapView removeOverlay:overlay];
1661
+ tileOverlayView.overlay = nil;
1662
+ }
1663
+ }
1664
+
1665
+ - (void)syncTileOverlayView:(LuggTileOverlayView *)tileOverlayView {
1666
+ if (!_mapView)
1667
+ return;
1668
+
1669
+ NSString *urlTemplate = tileOverlayView.urlTemplate;
1670
+ if (urlTemplate.length == 0)
1671
+ return;
1672
+
1673
+ // Remove old overlay
1674
+ MKTileOverlay *oldOverlay = (MKTileOverlay *)tileOverlayView.overlay;
1675
+ if (oldOverlay) {
1676
+ [_overlayToTileOverlayMap removeObjectForKey:oldOverlay];
1677
+ [_mapView removeOverlay:oldOverlay];
1678
+ tileOverlayView.overlay = nil;
1679
+ }
1680
+
1681
+ MKTileOverlay *tileOverlay;
1682
+ if (tileOverlayView.hasBounds) {
1683
+ BoundedTileOverlay *bounded =
1684
+ [[BoundedTileOverlay alloc] initWithURLTemplate:urlTemplate];
1685
+ bounded.southwest = tileOverlayView.southwest;
1686
+ bounded.northeast = tileOverlayView.northeast;
1687
+ tileOverlay = bounded;
1688
+ } else {
1689
+ tileOverlay = [[MKTileOverlay alloc] initWithURLTemplate:urlTemplate];
1690
+ }
1691
+
1692
+ tileOverlay.tileSize =
1693
+ CGSizeMake(tileOverlayView.tileSize, tileOverlayView.tileSize);
1694
+ tileOverlay.canReplaceMapContent = NO;
1695
+
1696
+ tileOverlayView.overlay = tileOverlay;
1697
+ [_overlayToTileOverlayMap setObject:tileOverlayView forKey:tileOverlay];
1698
+ [_mapView addOverlay:tileOverlay level:MKOverlayLevelAboveRoads];
1699
+ }
1700
+
905
1701
  - (void)insertOverlay:(id<MKOverlay>)overlay withZIndex:(NSInteger)zIndex {
906
1702
  if (zIndex == 0) {
907
1703
  [_mapView addOverlay:overlay];
@@ -917,10 +1713,18 @@
917
1713
  [_overlayToPolylineMap objectForKey:overlays[i]];
918
1714
  LuggPolygonView *existingPolygonView =
919
1715
  [_overlayToPolygonMap objectForKey:overlays[i]];
1716
+ LuggCircleView *existingCircleView =
1717
+ [_overlayToCircleMap objectForKey:overlays[i]];
1718
+ LuggGroundOverlayView *existingGroundOverlayView =
1719
+ [_overlayToGroundOverlayMap objectForKey:overlays[i]];
920
1720
  if (existingPolylineView) {
921
1721
  existingZIndex = existingPolylineView.zIndex;
922
1722
  } else if (existingPolygonView) {
923
1723
  existingZIndex = existingPolygonView.zIndex;
1724
+ } else if (existingCircleView) {
1725
+ existingZIndex = existingCircleView.zIndex;
1726
+ } else if (existingGroundOverlayView) {
1727
+ existingZIndex = existingGroundOverlayView.zIndex;
924
1728
  } else {
925
1729
  continue;
926
1730
  }