@lugg/maps 1.0.0-beta.17 → 1.0.0-beta.18

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 (40) hide show
  1. package/android/src/main/java/com/luggmaps/LuggMapView.kt +33 -0
  2. package/android/src/main/java/com/luggmaps/LuggMapViewManager.kt +9 -0
  3. package/android/src/main/java/com/luggmaps/LuggMapWrapperView.kt +20 -2
  4. package/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +29 -3
  5. package/ios/LuggMapView.mm +152 -2
  6. package/ios/LuggMapWrapperView.h +5 -0
  7. package/ios/LuggMapWrapperView.mm +18 -0
  8. package/ios/LuggMarkerView.mm +2 -0
  9. package/ios/core/AppleMapProvider.h +8 -0
  10. package/ios/core/AppleMapProvider.mm +41 -28
  11. package/ios/core/AppleStaticMapProvider.h +14 -0
  12. package/ios/core/AppleStaticMapProvider.mm +146 -0
  13. package/ios/core/GoogleMapProvider.h +10 -0
  14. package/ios/core/GoogleMapProvider.mm +47 -41
  15. package/ios/core/GoogleStaticMapProvider.h +21 -0
  16. package/ios/core/GoogleStaticMapProvider.mm +204 -0
  17. package/ios/core/MKPolylineAnimator.m +67 -43
  18. package/ios/core/MapProviderDelegate.h +6 -0
  19. package/ios/core/StaticMapProviderBase.h +69 -0
  20. package/ios/core/StaticMapProviderBase.mm +745 -0
  21. package/ios/extensions/MKMapView+Zoom.h +14 -0
  22. package/ios/extensions/MKMapView+Zoom.m +17 -15
  23. package/ios/extensions/UIView+Snapshot.h +16 -0
  24. package/ios/extensions/UIView+Snapshot.m +27 -0
  25. package/lib/module/MapView.js +4 -0
  26. package/lib/module/MapView.js.map +1 -1
  27. package/lib/module/MapView.web.js +8 -5
  28. package/lib/module/MapView.web.js.map +1 -1
  29. package/lib/module/fabric/LuggMapViewNativeComponent.ts +2 -0
  30. package/lib/typescript/src/MapView.d.ts.map +1 -1
  31. package/lib/typescript/src/MapView.types.d.ts +18 -0
  32. package/lib/typescript/src/MapView.types.d.ts.map +1 -1
  33. package/lib/typescript/src/MapView.web.d.ts.map +1 -1
  34. package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts +2 -0
  35. package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts.map +1 -1
  36. package/package.json +1 -1
  37. package/src/MapView.tsx +4 -0
  38. package/src/MapView.types.ts +18 -0
  39. package/src/MapView.web.tsx +14 -10
  40. package/src/fabric/LuggMapViewNativeComponent.ts +2 -0
@@ -0,0 +1,745 @@
1
+ #import "StaticMapProviderBase.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 "../LuggCircleView.h"
9
+ #import "../LuggGroundOverlayView.h"
10
+ #import "../LuggMapWrapperView.h"
11
+ #import "../LuggMarkerView.h"
12
+ #import "../LuggPolygonView.h"
13
+ #import "../LuggPolylineView.h"
14
+
15
+ #pragma mark - Projection
16
+
17
+ CGPoint LuggStaticPointForCoordinate(MKMapRect mapRect, CGSize size,
18
+ CLLocationCoordinate2D coordinate) {
19
+ MKMapPoint point = MKMapPointForCoordinate(coordinate);
20
+ return CGPointMake(
21
+ (point.x - mapRect.origin.x) / mapRect.size.width * size.width,
22
+ (point.y - mapRect.origin.y) / mapRect.size.height * size.height);
23
+ }
24
+
25
+ #pragma mark - Shape overlay
26
+
27
+ // Draws polylines, polygons, circles and ground overlays over the base
28
+ // map with CoreGraphics; the base render is map tiles only
29
+ @interface LuggStaticShapeOverlayView : UIView
30
+ @property(nonatomic, assign) MKMapRect mapRect;
31
+ @property(nonatomic, assign) BOOL projectionReady;
32
+ @property(nonatomic, strong) NSMutableArray<LuggPolylineView *> *polylineViews;
33
+ @property(nonatomic, strong) NSMutableArray<LuggPolygonView *> *polygonViews;
34
+ @property(nonatomic, strong) NSMutableArray<LuggCircleView *> *circleViews;
35
+ @property(nonatomic, strong)
36
+ NSMutableArray<LuggGroundOverlayView *> *groundOverlayViews;
37
+ @property(nonatomic, strong)
38
+ NSMapTable<LuggGroundOverlayView *, UIImage *> *groundOverlayImages;
39
+ @end
40
+
41
+ @implementation LuggStaticShapeOverlayView
42
+
43
+ - (instancetype)initWithFrame:(CGRect)frame {
44
+ if (self = [super initWithFrame:frame]) {
45
+ self.opaque = NO;
46
+ self.backgroundColor = UIColor.clearColor;
47
+ self.userInteractionEnabled = NO;
48
+ self.contentMode = UIViewContentModeRedraw;
49
+ _polylineViews = [NSMutableArray array];
50
+ _polygonViews = [NSMutableArray array];
51
+ _circleViews = [NSMutableArray array];
52
+ _groundOverlayViews = [NSMutableArray array];
53
+ _groundOverlayImages = [NSMapTable weakToStrongObjectsMapTable];
54
+ }
55
+ return self;
56
+ }
57
+
58
+ static NSInteger shapeZIndex(UIView *shape) {
59
+ if ([shape isKindOfClass:[LuggPolylineView class]])
60
+ return ((LuggPolylineView *)shape).zIndex;
61
+ if ([shape isKindOfClass:[LuggPolygonView class]])
62
+ return ((LuggPolygonView *)shape).zIndex;
63
+ if ([shape isKindOfClass:[LuggCircleView class]])
64
+ return ((LuggCircleView *)shape).zIndex;
65
+ if ([shape isKindOfClass:[LuggGroundOverlayView class]])
66
+ return ((LuggGroundOverlayView *)shape).zIndex;
67
+ return 0;
68
+ }
69
+
70
+ - (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate {
71
+ return LuggStaticPointForCoordinate(_mapRect, self.bounds.size, coordinate);
72
+ }
73
+
74
+ - (void)addPath:(CGContextRef)context
75
+ ring:(NSArray<CLLocation *> *)coordinates {
76
+ for (NSUInteger i = 0; i < coordinates.count; i++) {
77
+ CGPoint point = [self pointForCoordinate:coordinates[i].coordinate];
78
+ if (i == 0) {
79
+ CGContextMoveToPoint(context, point.x, point.y);
80
+ } else {
81
+ CGContextAddLineToPoint(context, point.x, point.y);
82
+ }
83
+ }
84
+ }
85
+
86
+ - (void)drawPolyline:(LuggPolylineView *)polylineView
87
+ inContext:(CGContextRef)context {
88
+ NSArray<CLLocation *> *coordinates = polylineView.coordinates;
89
+ UIColor *color = polylineView.strokeColors.firstObject;
90
+ if (coordinates.count < 2 || !color || polylineView.strokeWidth <= 0)
91
+ return;
92
+
93
+ CGContextSaveGState(context);
94
+ [self addPath:context ring:coordinates];
95
+ CGContextSetLineWidth(context, polylineView.strokeWidth);
96
+ CGContextSetLineCap(context, kCGLineCapRound);
97
+ CGContextSetLineJoin(context, kCGLineJoinRound);
98
+ CGContextSetStrokeColorWithColor(context, color.CGColor);
99
+ CGContextStrokePath(context);
100
+ CGContextRestoreGState(context);
101
+ }
102
+
103
+ - (void)drawPolygon:(LuggPolygonView *)polygonView
104
+ inContext:(CGContextRef)context {
105
+ NSArray<CLLocation *> *coordinates = polygonView.coordinates;
106
+ if (coordinates.count < 3)
107
+ return;
108
+
109
+ CGContextSaveGState(context);
110
+
111
+ if (polygonView.fillColor) {
112
+ [self addPath:context ring:coordinates];
113
+ CGContextClosePath(context);
114
+ for (NSArray<CLLocation *> *hole in polygonView.holes) {
115
+ [self addPath:context ring:hole];
116
+ CGContextClosePath(context);
117
+ }
118
+ CGContextSetFillColorWithColor(context, polygonView.fillColor.CGColor);
119
+ CGContextEOFillPath(context);
120
+ }
121
+
122
+ if (polygonView.strokeColor && polygonView.strokeWidth > 0) {
123
+ [self addPath:context ring:coordinates];
124
+ CGContextClosePath(context);
125
+ for (NSArray<CLLocation *> *hole in polygonView.holes) {
126
+ [self addPath:context ring:hole];
127
+ CGContextClosePath(context);
128
+ }
129
+ CGContextSetLineWidth(context, polygonView.strokeWidth);
130
+ CGContextSetLineJoin(context, kCGLineJoinRound);
131
+ CGContextSetStrokeColorWithColor(context, polygonView.strokeColor.CGColor);
132
+ CGContextStrokePath(context);
133
+ }
134
+
135
+ CGContextRestoreGState(context);
136
+ }
137
+
138
+ - (void)drawCircle:(LuggCircleView *)circleView
139
+ inContext:(CGContextRef)context {
140
+ if (circleView.radius <= 0)
141
+ return;
142
+
143
+ CGPoint center = [self pointForCoordinate:circleView.center];
144
+ double pointsPerMapPoint = self.bounds.size.width / _mapRect.size.width;
145
+ double radius = circleView.radius *
146
+ MKMapPointsPerMeterAtLatitude(circleView.center.latitude) *
147
+ pointsPerMapPoint;
148
+ CGRect ellipse =
149
+ CGRectMake(center.x - radius, center.y - radius, radius * 2, radius * 2);
150
+
151
+ CGContextSaveGState(context);
152
+ if (circleView.fillColor) {
153
+ CGContextSetFillColorWithColor(context, circleView.fillColor.CGColor);
154
+ CGContextFillEllipseInRect(context, ellipse);
155
+ }
156
+ if (circleView.strokeColor && circleView.strokeWidth > 0) {
157
+ CGContextSetLineWidth(context, circleView.strokeWidth);
158
+ CGContextSetStrokeColorWithColor(context, circleView.strokeColor.CGColor);
159
+ CGContextStrokeEllipseInRect(context, ellipse);
160
+ }
161
+ CGContextRestoreGState(context);
162
+ }
163
+
164
+ - (void)drawGroundOverlay:(LuggGroundOverlayView *)groundOverlayView {
165
+ UIImage *image = [_groundOverlayImages objectForKey:groundOverlayView];
166
+ if (!image)
167
+ return;
168
+
169
+ CGPoint topLeft =
170
+ [self pointForCoordinate:CLLocationCoordinate2DMake(
171
+ groundOverlayView.northeast.latitude,
172
+ groundOverlayView.southwest.longitude)];
173
+ CGPoint bottomRight =
174
+ [self pointForCoordinate:CLLocationCoordinate2DMake(
175
+ groundOverlayView.southwest.latitude,
176
+ groundOverlayView.northeast.longitude)];
177
+ CGRect rect = CGRectMake(topLeft.x, topLeft.y, bottomRight.x - topLeft.x,
178
+ bottomRight.y - topLeft.y);
179
+ [image drawInRect:rect
180
+ blendMode:kCGBlendModeNormal
181
+ alpha:groundOverlayView.opacity];
182
+ }
183
+
184
+ - (void)drawRect:(CGRect)rect {
185
+ if (!_projectionReady)
186
+ return;
187
+
188
+ CGContextRef context = UIGraphicsGetCurrentContext();
189
+ if (!context)
190
+ return;
191
+
192
+ NSMutableArray<UIView *> *shapes = [NSMutableArray array];
193
+ [shapes addObjectsFromArray:_groundOverlayViews];
194
+ [shapes addObjectsFromArray:_polygonViews];
195
+ [shapes addObjectsFromArray:_circleViews];
196
+ [shapes addObjectsFromArray:_polylineViews];
197
+ NSArray<UIView *> *ordered =
198
+ [shapes sortedArrayWithOptions:NSSortStable
199
+ usingComparator:^NSComparisonResult(UIView *a, UIView *b) {
200
+ NSInteger za = shapeZIndex(a), zb = shapeZIndex(b);
201
+ if (za < zb)
202
+ return NSOrderedAscending;
203
+ if (za > zb)
204
+ return NSOrderedDescending;
205
+ return NSOrderedSame;
206
+ }];
207
+
208
+ for (UIView *shape in ordered) {
209
+ if ([shape isKindOfClass:[LuggGroundOverlayView class]]) {
210
+ [self drawGroundOverlay:(LuggGroundOverlayView *)shape];
211
+ } else if ([shape isKindOfClass:[LuggPolygonView class]]) {
212
+ [self drawPolygon:(LuggPolygonView *)shape inContext:context];
213
+ } else if ([shape isKindOfClass:[LuggCircleView class]]) {
214
+ [self drawCircle:(LuggCircleView *)shape inContext:context];
215
+ } else if ([shape isKindOfClass:[LuggPolylineView class]]) {
216
+ [self drawPolyline:(LuggPolylineView *)shape inContext:context];
217
+ }
218
+ }
219
+ }
220
+
221
+ @end
222
+
223
+ #pragma mark - Provider base
224
+
225
+ @interface StaticMapProviderBase () <
226
+ LuggMarkerViewDelegate, LuggPolylineViewDelegate, LuggPolygonViewDelegate,
227
+ LuggCircleViewDelegate, LuggGroundOverlayViewDelegate>
228
+ @end
229
+
230
+ @implementation StaticMapProviderBase {
231
+ BOOL _isMapReady;
232
+ BOOL _baseRenderDone;
233
+ BOOL _overlaysRevealed;
234
+
235
+ UIImageView *_baseImageView;
236
+ LuggStaticShapeOverlayView *_shapeOverlayView;
237
+ NSMutableArray<LuggMarkerView *> *_markerViews;
238
+ NSMapTable<LuggMarkerView *, UIView *> *_defaultMarkerOverlays;
239
+ }
240
+
241
+ @synthesize delegate = _delegate;
242
+ @synthesize staticMode = _staticMode;
243
+
244
+ - (instancetype)init {
245
+ if (self = [super init]) {
246
+ _markerViews = [NSMutableArray array];
247
+ _defaultMarkerOverlays = [NSMapTable weakToStrongObjectsMapTable];
248
+ _mapType = LuggMapViewMapType::Standard;
249
+ _theme = LuggMapViewTheme::System;
250
+ }
251
+ return self;
252
+ }
253
+
254
+ - (BOOL)isMapReady {
255
+ return _isMapReady;
256
+ }
257
+
258
+ #pragma mark - MapProvider
259
+
260
+ - (void)initializeMapInView:(UIView *)wrapperView
261
+ initialCoordinate:(CLLocationCoordinate2D)coordinate
262
+ initialZoom:(double)zoom {
263
+ if (_wrapperView)
264
+ return;
265
+
266
+ _wrapperView = wrapperView;
267
+ _coordinate = coordinate;
268
+ _zoom = zoom;
269
+
270
+ _shapeOverlayView =
271
+ [[LuggStaticShapeOverlayView alloc] initWithFrame:wrapperView.bounds];
272
+ _shapeOverlayView.autoresizingMask =
273
+ UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
274
+ // Overlays stay hidden over the placeholder and reveal with the base map
275
+ _shapeOverlayView.hidden = YES;
276
+ [wrapperView addSubview:_shapeOverlayView];
277
+
278
+ [self updateProjection];
279
+
280
+ __weak StaticMapProviderBase *weakSelf = self;
281
+ if ([wrapperView isKindOfClass:[LuggMapWrapperView class]]) {
282
+ ((LuggMapWrapperView *)wrapperView).layoutHandler = ^{
283
+ [weakSelf wrapperDidLayout];
284
+ };
285
+ }
286
+
287
+ _isMapReady = YES;
288
+
289
+ // Start after the current mount pass so map settings props applied right
290
+ // after this call shape the base render
291
+ dispatch_async(dispatch_get_main_queue(), ^{
292
+ [weakSelf renderBaseMapIfNeeded];
293
+ });
294
+ }
295
+
296
+ - (void)destroy {
297
+ [self cancelBaseRender];
298
+
299
+ if ([_wrapperView isKindOfClass:[LuggMapWrapperView class]]) {
300
+ ((LuggMapWrapperView *)_wrapperView).layoutHandler = nil;
301
+ }
302
+
303
+ for (LuggMarkerView *markerView in _markerViews) {
304
+ markerView.delegate = nil;
305
+ UIView *iconView = markerView.iconView;
306
+ if (iconView.superview == _wrapperView) {
307
+ [iconView removeFromSuperview];
308
+ }
309
+ [markerView resetIconViewTransform];
310
+ }
311
+ [_markerViews removeAllObjects];
312
+ for (UIView *pin in _defaultMarkerOverlays.objectEnumerator) {
313
+ [pin removeFromSuperview];
314
+ }
315
+ [_defaultMarkerOverlays removeAllObjects];
316
+
317
+ for (LuggPolylineView *view in _shapeOverlayView.polylineViews) {
318
+ view.delegate = nil;
319
+ }
320
+ for (LuggPolygonView *view in _shapeOverlayView.polygonViews) {
321
+ view.delegate = nil;
322
+ }
323
+ for (LuggCircleView *view in _shapeOverlayView.circleViews) {
324
+ view.delegate = nil;
325
+ }
326
+ for (LuggGroundOverlayView *view in _shapeOverlayView.groundOverlayViews) {
327
+ view.delegate = nil;
328
+ }
329
+ [_shapeOverlayView removeFromSuperview];
330
+ _shapeOverlayView = nil;
331
+
332
+ [_baseImageView removeFromSuperview];
333
+ _baseImageView = nil;
334
+
335
+ _baseRenderDone = NO;
336
+ _overlaysRevealed = NO;
337
+ _projectionReady = NO;
338
+ _wrapperView = nil;
339
+ _isMapReady = NO;
340
+ }
341
+
342
+ #pragma mark - Base render
343
+
344
+ - (void)updateProjection {
345
+ CGSize size = _wrapperView.bounds.size;
346
+ if (size.width <= 0 || size.height <= 0)
347
+ return;
348
+
349
+ _projectedSize = size;
350
+ _mapRect = [self mapRectForSize:size];
351
+ _projectionReady = YES;
352
+
353
+ _shapeOverlayView.mapRect = _mapRect;
354
+ _shapeOverlayView.projectionReady = YES;
355
+ [_shapeOverlayView setNeedsDisplay];
356
+
357
+ for (LuggMarkerView *markerView in _markerViews) {
358
+ [self positionMarkerOverlay:markerView];
359
+ }
360
+ }
361
+
362
+ // A static map can't autoresize like a live map; when the wrapper gets its
363
+ // first real size (or resizes later) recompute the projection and
364
+ // (re)render the base map at the new size
365
+ - (void)wrapperDidLayout {
366
+ CGSize size = _wrapperView.bounds.size;
367
+ if (size.width <= 0 || size.height <= 0 ||
368
+ CGSizeEqualToSize(size, _projectedSize))
369
+ return;
370
+
371
+ [self updateProjection];
372
+
373
+ // Any in-flight or completed base render targets the old size
374
+ [self cancelBaseRender];
375
+ _baseRenderDone = NO;
376
+ self.cachedBaseImage = nil;
377
+
378
+ [self renderBaseMapIfNeeded];
379
+ }
380
+
381
+ - (void)renderBaseMapIfNeeded {
382
+ if (_baseRenderDone || !_projectionReady || !_wrapperView)
383
+ return;
384
+
385
+ if (self.cachedBaseImage) {
386
+ [self displayBaseImage:self.cachedBaseImage fromCache:YES];
387
+ return;
388
+ }
389
+
390
+ [self renderBaseMap];
391
+ }
392
+
393
+ - (void)displayBaseImage:(UIImage *)image fromCache:(BOOL)fromCache {
394
+ if (!_baseImageView) {
395
+ _baseImageView = [[UIImageView alloc] initWithFrame:_wrapperView.bounds];
396
+ _baseImageView.autoresizingMask =
397
+ UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
398
+ [_wrapperView insertSubview:_baseImageView atIndex:0];
399
+ }
400
+ _baseImageView.image = image;
401
+ _baseRenderDone = YES;
402
+ [self revealOverlays];
403
+
404
+ if (!fromCache) {
405
+ [_delegate mapProviderDidCaptureStaticImage:image];
406
+ }
407
+ [_delegate mapProviderDidFinishStaticSnapshot];
408
+ [_delegate mapProviderDidReady];
409
+ }
410
+
411
+ - (void)revealOverlays {
412
+ if (_overlaysRevealed)
413
+ return;
414
+ _overlaysRevealed = YES;
415
+ _shapeOverlayView.hidden = NO;
416
+ for (LuggMarkerView *markerView in _markerViews) {
417
+ [self positionMarkerOverlay:markerView];
418
+ }
419
+ }
420
+
421
+ #pragma mark - Subclass hooks
422
+
423
+ - (MKMapRect)mapRectForSize:(CGSize)size {
424
+ return MKMapRectNull;
425
+ }
426
+
427
+ - (UIView *)newDefaultMarkerOverlayView {
428
+ return [[UIView alloc] init];
429
+ }
430
+
431
+ - (void)renderBaseMap {
432
+ }
433
+
434
+ - (void)cancelBaseRender {
435
+ }
436
+
437
+ #pragma mark - Props
438
+
439
+ - (void)setMapType:(LuggMapViewMapType)mapType {
440
+ _mapType = mapType;
441
+ }
442
+
443
+ - (void)setTheme:(LuggMapViewTheme)theme {
444
+ _theme = theme;
445
+ }
446
+
447
+ // Interaction and camera props don't apply to a static map
448
+ - (void)setInsetAdjustment:(LuggMapViewInsetAdjustment)insetAdjustment {
449
+ }
450
+ - (void)setZoomEnabled:(BOOL)enabled {
451
+ }
452
+ - (void)setScrollEnabled:(BOOL)enabled {
453
+ }
454
+ - (void)setRotateEnabled:(BOOL)enabled {
455
+ }
456
+ - (void)setPitchEnabled:(BOOL)enabled {
457
+ }
458
+ - (void)setCompassEnabled:(BOOL)enabled {
459
+ }
460
+ - (void)setUserLocationEnabled:(BOOL)enabled {
461
+ }
462
+ - (void)setMinZoom:(double)minZoom {
463
+ }
464
+ - (void)setMaxZoom:(double)maxZoom {
465
+ }
466
+ - (void)setPoiEnabled:(BOOL)enabled {
467
+ }
468
+ - (void)setPoiFilterMode:(LuggMapViewPoiFilterMode)mode {
469
+ }
470
+ - (void)setPoiFilterCategories:(NSArray<NSString *> *)categories {
471
+ }
472
+ - (void)setEdgeInsets:(UIEdgeInsets)edgeInsets
473
+ oldEdgeInsets:(UIEdgeInsets)oldEdgeInsets {
474
+ }
475
+ - (void)setEdgeInsets:(UIEdgeInsets)edgeInsets
476
+ oldEdgeInsets:(UIEdgeInsets)oldEdgeInsets
477
+ duration:(double)duration {
478
+ }
479
+
480
+ #pragma mark - Markers
481
+
482
+ // Markers are live views over the base map (like Android lite mode's live
483
+ // markers), so async content such as remote images shows up when it loads
484
+
485
+ - (void)addMarkerView:(LuggMarkerView *)markerView {
486
+ markerView.delegate = self;
487
+ if (![_markerViews containsObject:markerView]) {
488
+ [_markerViews addObject:markerView];
489
+ }
490
+ [self syncMarkerView:markerView];
491
+ }
492
+
493
+ - (void)removeMarkerView:(LuggMarkerView *)markerView {
494
+ markerView.delegate = nil;
495
+ [_markerViews removeObject:markerView];
496
+
497
+ UIView *pin = [_defaultMarkerOverlays objectForKey:markerView];
498
+ if (pin) {
499
+ [pin removeFromSuperview];
500
+ [_defaultMarkerOverlays removeObjectForKey:markerView];
501
+ }
502
+
503
+ UIView *iconView = markerView.iconView;
504
+ if (iconView.superview == _wrapperView) {
505
+ [iconView removeFromSuperview];
506
+ }
507
+ [markerView resetIconViewTransform];
508
+ }
509
+
510
+ - (void)syncMarkerView:(LuggMarkerView *)markerView {
511
+ if (!_wrapperView)
512
+ return;
513
+
514
+ UIView *pin = [_defaultMarkerOverlays objectForKey:markerView];
515
+
516
+ if (markerView.hasCustomView) {
517
+ if (pin) {
518
+ [pin removeFromSuperview];
519
+ [_defaultMarkerOverlays removeObjectForKey:markerView];
520
+ }
521
+ UIView *iconView = markerView.iconView;
522
+ if (iconView.superview != _wrapperView) {
523
+ [iconView removeFromSuperview];
524
+ iconView.userInteractionEnabled = NO;
525
+ // Hidden until the base map displays; positionMarkerOverlay shows it
526
+ iconView.hidden = YES;
527
+ [_wrapperView addSubview:iconView];
528
+ }
529
+ } else {
530
+ UIView *iconView = markerView.iconView;
531
+ if (iconView.superview == _wrapperView) {
532
+ [iconView removeFromSuperview];
533
+ [markerView resetIconViewTransform];
534
+ }
535
+ if (!pin) {
536
+ pin = [self newDefaultMarkerOverlayView];
537
+ pin.userInteractionEnabled = NO;
538
+ pin.hidden = YES;
539
+ [_wrapperView addSubview:pin];
540
+ [_defaultMarkerOverlays setObject:pin forKey:markerView];
541
+ }
542
+ }
543
+
544
+ [self positionMarkerOverlay:markerView];
545
+ }
546
+
547
+ - (void)positionMarkerOverlay:(LuggMarkerView *)markerView {
548
+ if (!_projectionReady)
549
+ return;
550
+
551
+ CGPoint point = LuggStaticPointForCoordinate(_mapRect, _projectedSize,
552
+ markerView.coordinate);
553
+
554
+ if (markerView.hasCustomView) {
555
+ UIView *iconView = markerView.iconView;
556
+ CGSize size = iconView.bounds.size;
557
+ if (size.width <= 0 || size.height <= 0) {
558
+ // Not laid out yet; markerViewDidLayout re-runs this
559
+ return;
560
+ }
561
+ CGFloat scale = markerView.scale;
562
+ CGFloat radians = markerView.rotate * M_PI / 180.0;
563
+ iconView.layer.anchorPoint = markerView.anchor;
564
+ iconView.transform =
565
+ CGAffineTransformConcat(CGAffineTransformMakeScale(scale, scale),
566
+ CGAffineTransformMakeRotation(radians));
567
+ // center positions the layer's anchorPoint, so the marker's anchor
568
+ // lands exactly on the projected coordinate
569
+ iconView.center = point;
570
+ iconView.layer.zPosition = markerView.zIndex;
571
+ iconView.hidden = !_overlaysRevealed;
572
+ } else {
573
+ UIView *pin = [_defaultMarkerOverlays objectForKey:markerView];
574
+ pin.layer.anchorPoint = CGPointMake(0.5, 1.0);
575
+ pin.center = point;
576
+ pin.layer.zPosition = markerView.zIndex;
577
+ pin.hidden = !_overlaysRevealed;
578
+ }
579
+ }
580
+
581
+ #pragma mark - LuggMarkerViewDelegate
582
+
583
+ - (void)markerViewDidLayout:(LuggMarkerView *)markerView {
584
+ [self syncMarkerView:markerView];
585
+ }
586
+
587
+ - (void)markerViewDidUpdate:(LuggMarkerView *)markerView {
588
+ [self syncMarkerView:markerView];
589
+ }
590
+
591
+ #pragma mark - Shapes
592
+
593
+ - (void)addPolylineView:(LuggPolylineView *)polylineView {
594
+ polylineView.delegate = self;
595
+ if (![_shapeOverlayView.polylineViews containsObject:polylineView]) {
596
+ [_shapeOverlayView.polylineViews addObject:polylineView];
597
+ }
598
+ [_shapeOverlayView setNeedsDisplay];
599
+ }
600
+
601
+ - (void)removePolylineView:(LuggPolylineView *)polylineView {
602
+ polylineView.delegate = nil;
603
+ [_shapeOverlayView.polylineViews removeObject:polylineView];
604
+ [_shapeOverlayView setNeedsDisplay];
605
+ }
606
+
607
+ - (void)syncPolylineView:(LuggPolylineView *)polylineView {
608
+ [_shapeOverlayView setNeedsDisplay];
609
+ }
610
+
611
+ - (void)polylineViewDidUpdate:(LuggPolylineView *)polylineView {
612
+ [_shapeOverlayView setNeedsDisplay];
613
+ }
614
+
615
+ - (void)addPolygonView:(LuggPolygonView *)polygonView {
616
+ polygonView.delegate = self;
617
+ if (![_shapeOverlayView.polygonViews containsObject:polygonView]) {
618
+ [_shapeOverlayView.polygonViews addObject:polygonView];
619
+ }
620
+ [_shapeOverlayView setNeedsDisplay];
621
+ }
622
+
623
+ - (void)removePolygonView:(LuggPolygonView *)polygonView {
624
+ polygonView.delegate = nil;
625
+ [_shapeOverlayView.polygonViews removeObject:polygonView];
626
+ [_shapeOverlayView setNeedsDisplay];
627
+ }
628
+
629
+ - (void)syncPolygonView:(LuggPolygonView *)polygonView {
630
+ [_shapeOverlayView setNeedsDisplay];
631
+ }
632
+
633
+ - (void)polygonViewDidUpdate:(LuggPolygonView *)polygonView {
634
+ [_shapeOverlayView setNeedsDisplay];
635
+ }
636
+
637
+ - (void)addCircleView:(LuggCircleView *)circleView {
638
+ circleView.delegate = self;
639
+ if (![_shapeOverlayView.circleViews containsObject:circleView]) {
640
+ [_shapeOverlayView.circleViews addObject:circleView];
641
+ }
642
+ [_shapeOverlayView setNeedsDisplay];
643
+ }
644
+
645
+ - (void)removeCircleView:(LuggCircleView *)circleView {
646
+ circleView.delegate = nil;
647
+ [_shapeOverlayView.circleViews removeObject:circleView];
648
+ [_shapeOverlayView setNeedsDisplay];
649
+ }
650
+
651
+ - (void)syncCircleView:(LuggCircleView *)circleView {
652
+ [_shapeOverlayView setNeedsDisplay];
653
+ }
654
+
655
+ - (void)circleViewDidUpdate:(LuggCircleView *)circleView {
656
+ [_shapeOverlayView setNeedsDisplay];
657
+ }
658
+
659
+ - (void)addGroundOverlayView:(LuggGroundOverlayView *)groundOverlayView {
660
+ groundOverlayView.delegate = self;
661
+ if (![_shapeOverlayView.groundOverlayViews
662
+ containsObject:groundOverlayView]) {
663
+ [_shapeOverlayView.groundOverlayViews addObject:groundOverlayView];
664
+ }
665
+ [self loadGroundOverlayImage:groundOverlayView];
666
+ }
667
+
668
+ - (void)removeGroundOverlayView:(LuggGroundOverlayView *)groundOverlayView {
669
+ groundOverlayView.delegate = nil;
670
+ [_shapeOverlayView.groundOverlayViews removeObject:groundOverlayView];
671
+ [_shapeOverlayView.groundOverlayImages removeObjectForKey:groundOverlayView];
672
+ [_shapeOverlayView setNeedsDisplay];
673
+ }
674
+
675
+ - (void)syncGroundOverlayView:(LuggGroundOverlayView *)groundOverlayView {
676
+ [self loadGroundOverlayImage:groundOverlayView];
677
+ }
678
+
679
+ - (void)groundOverlayViewDidUpdate:(LuggGroundOverlayView *)groundOverlayView {
680
+ [self loadGroundOverlayImage:groundOverlayView];
681
+ }
682
+
683
+ - (void)loadGroundOverlayImage:(LuggGroundOverlayView *)groundOverlayView {
684
+ NSURL *url = [NSURL URLWithString:groundOverlayView.imageUri];
685
+ if (!url)
686
+ return;
687
+
688
+ __weak StaticMapProviderBase *weakSelf = self;
689
+ __weak LuggGroundOverlayView *weakOverlayView = groundOverlayView;
690
+ dispatch_async(
691
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
692
+ NSData *data = [NSData dataWithContentsOfURL:url];
693
+ UIImage *image = data ? [UIImage imageWithData:data] : nil;
694
+ dispatch_async(dispatch_get_main_queue(), ^{
695
+ StaticMapProviderBase *strongSelf = weakSelf;
696
+ LuggGroundOverlayView *overlayView = weakOverlayView;
697
+ if (!strongSelf || !overlayView || !image)
698
+ return;
699
+ LuggStaticShapeOverlayView *shapeView = strongSelf->_shapeOverlayView;
700
+ if (![shapeView.groundOverlayViews containsObject:overlayView])
701
+ return;
702
+ [shapeView.groundOverlayImages setObject:image forKey:overlayView];
703
+ [shapeView setNeedsDisplay];
704
+ });
705
+ });
706
+ }
707
+
708
+ // Tile overlays need a live map to fetch and compose tiles; unsupported on
709
+ // static maps
710
+ - (void)addTileOverlayView:(LuggTileOverlayView *)tileOverlayView {
711
+ }
712
+ - (void)removeTileOverlayView:(LuggTileOverlayView *)tileOverlayView {
713
+ }
714
+ - (void)syncTileOverlayView:(LuggTileOverlayView *)tileOverlayView {
715
+ }
716
+
717
+ #pragma mark - Lifecycle
718
+
719
+ - (void)pauseAnimations {
720
+ }
721
+
722
+ - (void)resumeAnimations {
723
+ // Retry a failed or cancelled base render (e.g. device back online)
724
+ [self renderBaseMapIfNeeded];
725
+ }
726
+
727
+ #pragma mark - Commands
728
+
729
+ // Static maps render once with their initial camera
730
+
731
+ - (void)moveCamera:(double)latitude
732
+ longitude:(double)longitude
733
+ zoom:(double)zoom
734
+ duration:(double)duration {
735
+ }
736
+
737
+ - (void)fitCoordinates:(NSArray *)coordinates
738
+ edgeInsetsTop:(double)edgeInsetsTop
739
+ edgeInsetsLeft:(double)edgeInsetsLeft
740
+ edgeInsetsBottom:(double)edgeInsetsBottom
741
+ edgeInsetsRight:(double)edgeInsetsRight
742
+ duration:(double)duration {
743
+ }
744
+
745
+ @end