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

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 +27 -2
  4. package/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +110 -5
  5. package/ios/LuggMapView.mm +153 -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 +207 -0
  17. package/ios/core/MKPolylineAnimator.m +67 -43
  18. package/ios/core/MapProviderDelegate.h +6 -0
  19. package/ios/core/StaticMapProviderBase.h +70 -0
  20. package/ios/core/StaticMapProviderBase.mm +777 -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,777 @@
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
+
352
+ // Edge insets shift the visible center like a live map: the coordinate
353
+ // lands at the center of the inset viewport instead of the view center
354
+ CGFloat offsetX = (_edgeInsets.left - _edgeInsets.right) / 2.0;
355
+ CGFloat offsetY = (_edgeInsets.top - _edgeInsets.bottom) / 2.0;
356
+ _mapRect.origin.x -= offsetX * _mapRect.size.width / size.width;
357
+ _mapRect.origin.y -= offsetY * _mapRect.size.height / size.height;
358
+
359
+ _projectionReady = YES;
360
+
361
+ _shapeOverlayView.mapRect = _mapRect;
362
+ _shapeOverlayView.projectionReady = YES;
363
+ [_shapeOverlayView setNeedsDisplay];
364
+
365
+ for (LuggMarkerView *markerView in _markerViews) {
366
+ [self positionMarkerOverlay:markerView];
367
+ }
368
+ }
369
+
370
+ // A static map can't autoresize like a live map; when the wrapper gets its
371
+ // first real size (or resizes later) recompute the projection and
372
+ // (re)render the base map at the new size
373
+ - (void)wrapperDidLayout {
374
+ CGSize size = _wrapperView.bounds.size;
375
+ if (size.width <= 0 || size.height <= 0 ||
376
+ CGSizeEqualToSize(size, _projectedSize))
377
+ return;
378
+
379
+ [self updateProjection];
380
+
381
+ // Any in-flight or completed base render targets the old size
382
+ [self cancelBaseRender];
383
+ _baseRenderDone = NO;
384
+ self.cachedBaseImage = nil;
385
+
386
+ [self renderBaseMapIfNeeded];
387
+ }
388
+
389
+ - (void)renderBaseMapIfNeeded {
390
+ if (_baseRenderDone || !_projectionReady || !_wrapperView)
391
+ return;
392
+
393
+ if (self.cachedBaseImage) {
394
+ [self displayBaseImage:self.cachedBaseImage fromCache:YES];
395
+ return;
396
+ }
397
+
398
+ [self renderBaseMap];
399
+ }
400
+
401
+ - (void)displayBaseImage:(UIImage *)image fromCache:(BOOL)fromCache {
402
+ if (!_baseImageView) {
403
+ _baseImageView = [[UIImageView alloc] initWithFrame:_wrapperView.bounds];
404
+ _baseImageView.autoresizingMask =
405
+ UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
406
+ [_wrapperView insertSubview:_baseImageView atIndex:0];
407
+ }
408
+ _baseImageView.image = image;
409
+ _baseRenderDone = YES;
410
+ [self revealOverlays];
411
+
412
+ if (!fromCache) {
413
+ [_delegate mapProviderDidCaptureStaticImage:image];
414
+ }
415
+ [_delegate mapProviderDidFinishStaticSnapshot];
416
+ [_delegate mapProviderDidReady];
417
+ }
418
+
419
+ - (void)revealOverlays {
420
+ if (_overlaysRevealed)
421
+ return;
422
+ _overlaysRevealed = YES;
423
+ _shapeOverlayView.hidden = NO;
424
+ for (LuggMarkerView *markerView in _markerViews) {
425
+ [self positionMarkerOverlay:markerView];
426
+ }
427
+ }
428
+
429
+ #pragma mark - Subclass hooks
430
+
431
+ - (MKMapRect)mapRectForSize:(CGSize)size {
432
+ return MKMapRectNull;
433
+ }
434
+
435
+ - (UIView *)newDefaultMarkerOverlayView {
436
+ return [[UIView alloc] init];
437
+ }
438
+
439
+ - (void)renderBaseMap {
440
+ }
441
+
442
+ - (void)cancelBaseRender {
443
+ }
444
+
445
+ #pragma mark - Props
446
+
447
+ - (void)setMapType:(LuggMapViewMapType)mapType {
448
+ _mapType = mapType;
449
+ }
450
+
451
+ - (void)setTheme:(LuggMapViewTheme)theme {
452
+ _theme = theme;
453
+ }
454
+
455
+ // Interaction and camera props don't apply to a static map
456
+ - (void)setInsetAdjustment:(LuggMapViewInsetAdjustment)insetAdjustment {
457
+ }
458
+ - (void)setZoomEnabled:(BOOL)enabled {
459
+ }
460
+ - (void)setScrollEnabled:(BOOL)enabled {
461
+ }
462
+ - (void)setRotateEnabled:(BOOL)enabled {
463
+ }
464
+ - (void)setPitchEnabled:(BOOL)enabled {
465
+ }
466
+ - (void)setCompassEnabled:(BOOL)enabled {
467
+ }
468
+ - (void)setUserLocationEnabled:(BOOL)enabled {
469
+ }
470
+ - (void)setMinZoom:(double)minZoom {
471
+ }
472
+ - (void)setMaxZoom:(double)maxZoom {
473
+ }
474
+ - (void)setPoiEnabled:(BOOL)enabled {
475
+ }
476
+ - (void)setPoiFilterMode:(LuggMapViewPoiFilterMode)mode {
477
+ }
478
+ - (void)setPoiFilterCategories:(NSArray<NSString *> *)categories {
479
+ }
480
+ - (void)setEdgeInsets:(UIEdgeInsets)edgeInsets
481
+ oldEdgeInsets:(UIEdgeInsets)oldEdgeInsets {
482
+ if (UIEdgeInsetsEqualToEdgeInsets(_edgeInsets, edgeInsets))
483
+ return;
484
+ _edgeInsets = edgeInsets;
485
+
486
+ if (!_projectionReady)
487
+ return;
488
+
489
+ [self updateProjection];
490
+
491
+ // Any in-flight or completed base render targets the old projection. The
492
+ // initial prop set lands before the first render, so the cached image
493
+ // (keyed on insets) stays valid.
494
+ [self cancelBaseRender];
495
+ if (_baseRenderDone) {
496
+ _baseRenderDone = NO;
497
+ self.cachedBaseImage = nil;
498
+ }
499
+ __weak StaticMapProviderBase *weakSelf = self;
500
+ dispatch_async(dispatch_get_main_queue(), ^{
501
+ [weakSelf renderBaseMapIfNeeded];
502
+ });
503
+ }
504
+
505
+ // Static maps don't animate; apply the final insets directly
506
+ - (void)setEdgeInsets:(UIEdgeInsets)edgeInsets
507
+ oldEdgeInsets:(UIEdgeInsets)oldEdgeInsets
508
+ duration:(double)duration {
509
+ [self setEdgeInsets:edgeInsets oldEdgeInsets:oldEdgeInsets];
510
+ }
511
+
512
+ #pragma mark - Markers
513
+
514
+ // Markers are live views over the base map (like Android lite mode's live
515
+ // markers), so async content such as remote images shows up when it loads
516
+
517
+ - (void)addMarkerView:(LuggMarkerView *)markerView {
518
+ markerView.delegate = self;
519
+ if (![_markerViews containsObject:markerView]) {
520
+ [_markerViews addObject:markerView];
521
+ }
522
+ [self syncMarkerView:markerView];
523
+ }
524
+
525
+ - (void)removeMarkerView:(LuggMarkerView *)markerView {
526
+ markerView.delegate = nil;
527
+ [_markerViews removeObject:markerView];
528
+
529
+ UIView *pin = [_defaultMarkerOverlays objectForKey:markerView];
530
+ if (pin) {
531
+ [pin removeFromSuperview];
532
+ [_defaultMarkerOverlays removeObjectForKey:markerView];
533
+ }
534
+
535
+ UIView *iconView = markerView.iconView;
536
+ if (iconView.superview == _wrapperView) {
537
+ [iconView removeFromSuperview];
538
+ }
539
+ [markerView resetIconViewTransform];
540
+ }
541
+
542
+ - (void)syncMarkerView:(LuggMarkerView *)markerView {
543
+ if (!_wrapperView)
544
+ return;
545
+
546
+ UIView *pin = [_defaultMarkerOverlays objectForKey:markerView];
547
+
548
+ if (markerView.hasCustomView) {
549
+ if (pin) {
550
+ [pin removeFromSuperview];
551
+ [_defaultMarkerOverlays removeObjectForKey:markerView];
552
+ }
553
+ UIView *iconView = markerView.iconView;
554
+ if (iconView.superview != _wrapperView) {
555
+ [iconView removeFromSuperview];
556
+ iconView.userInteractionEnabled = NO;
557
+ // Hidden until the base map displays; positionMarkerOverlay shows it
558
+ iconView.hidden = YES;
559
+ [_wrapperView addSubview:iconView];
560
+ }
561
+ } else {
562
+ UIView *iconView = markerView.iconView;
563
+ if (iconView.superview == _wrapperView) {
564
+ [iconView removeFromSuperview];
565
+ [markerView resetIconViewTransform];
566
+ }
567
+ if (!pin) {
568
+ pin = [self newDefaultMarkerOverlayView];
569
+ pin.userInteractionEnabled = NO;
570
+ pin.hidden = YES;
571
+ [_wrapperView addSubview:pin];
572
+ [_defaultMarkerOverlays setObject:pin forKey:markerView];
573
+ }
574
+ }
575
+
576
+ [self positionMarkerOverlay:markerView];
577
+ }
578
+
579
+ - (void)positionMarkerOverlay:(LuggMarkerView *)markerView {
580
+ if (!_projectionReady)
581
+ return;
582
+
583
+ CGPoint point = LuggStaticPointForCoordinate(_mapRect, _projectedSize,
584
+ markerView.coordinate);
585
+
586
+ if (markerView.hasCustomView) {
587
+ UIView *iconView = markerView.iconView;
588
+ CGSize size = iconView.bounds.size;
589
+ if (size.width <= 0 || size.height <= 0) {
590
+ // Not laid out yet; markerViewDidLayout re-runs this
591
+ return;
592
+ }
593
+ CGFloat scale = markerView.scale;
594
+ CGFloat radians = markerView.rotate * M_PI / 180.0;
595
+ iconView.layer.anchorPoint = markerView.anchor;
596
+ iconView.transform =
597
+ CGAffineTransformConcat(CGAffineTransformMakeScale(scale, scale),
598
+ CGAffineTransformMakeRotation(radians));
599
+ // center positions the layer's anchorPoint, so the marker's anchor
600
+ // lands exactly on the projected coordinate
601
+ iconView.center = point;
602
+ iconView.layer.zPosition = markerView.zIndex;
603
+ iconView.hidden = !_overlaysRevealed;
604
+ } else {
605
+ UIView *pin = [_defaultMarkerOverlays objectForKey:markerView];
606
+ pin.layer.anchorPoint = CGPointMake(0.5, 1.0);
607
+ pin.center = point;
608
+ pin.layer.zPosition = markerView.zIndex;
609
+ pin.hidden = !_overlaysRevealed;
610
+ }
611
+ }
612
+
613
+ #pragma mark - LuggMarkerViewDelegate
614
+
615
+ - (void)markerViewDidLayout:(LuggMarkerView *)markerView {
616
+ [self syncMarkerView:markerView];
617
+ }
618
+
619
+ - (void)markerViewDidUpdate:(LuggMarkerView *)markerView {
620
+ [self syncMarkerView:markerView];
621
+ }
622
+
623
+ #pragma mark - Shapes
624
+
625
+ - (void)addPolylineView:(LuggPolylineView *)polylineView {
626
+ polylineView.delegate = self;
627
+ if (![_shapeOverlayView.polylineViews containsObject:polylineView]) {
628
+ [_shapeOverlayView.polylineViews addObject:polylineView];
629
+ }
630
+ [_shapeOverlayView setNeedsDisplay];
631
+ }
632
+
633
+ - (void)removePolylineView:(LuggPolylineView *)polylineView {
634
+ polylineView.delegate = nil;
635
+ [_shapeOverlayView.polylineViews removeObject:polylineView];
636
+ [_shapeOverlayView setNeedsDisplay];
637
+ }
638
+
639
+ - (void)syncPolylineView:(LuggPolylineView *)polylineView {
640
+ [_shapeOverlayView setNeedsDisplay];
641
+ }
642
+
643
+ - (void)polylineViewDidUpdate:(LuggPolylineView *)polylineView {
644
+ [_shapeOverlayView setNeedsDisplay];
645
+ }
646
+
647
+ - (void)addPolygonView:(LuggPolygonView *)polygonView {
648
+ polygonView.delegate = self;
649
+ if (![_shapeOverlayView.polygonViews containsObject:polygonView]) {
650
+ [_shapeOverlayView.polygonViews addObject:polygonView];
651
+ }
652
+ [_shapeOverlayView setNeedsDisplay];
653
+ }
654
+
655
+ - (void)removePolygonView:(LuggPolygonView *)polygonView {
656
+ polygonView.delegate = nil;
657
+ [_shapeOverlayView.polygonViews removeObject:polygonView];
658
+ [_shapeOverlayView setNeedsDisplay];
659
+ }
660
+
661
+ - (void)syncPolygonView:(LuggPolygonView *)polygonView {
662
+ [_shapeOverlayView setNeedsDisplay];
663
+ }
664
+
665
+ - (void)polygonViewDidUpdate:(LuggPolygonView *)polygonView {
666
+ [_shapeOverlayView setNeedsDisplay];
667
+ }
668
+
669
+ - (void)addCircleView:(LuggCircleView *)circleView {
670
+ circleView.delegate = self;
671
+ if (![_shapeOverlayView.circleViews containsObject:circleView]) {
672
+ [_shapeOverlayView.circleViews addObject:circleView];
673
+ }
674
+ [_shapeOverlayView setNeedsDisplay];
675
+ }
676
+
677
+ - (void)removeCircleView:(LuggCircleView *)circleView {
678
+ circleView.delegate = nil;
679
+ [_shapeOverlayView.circleViews removeObject:circleView];
680
+ [_shapeOverlayView setNeedsDisplay];
681
+ }
682
+
683
+ - (void)syncCircleView:(LuggCircleView *)circleView {
684
+ [_shapeOverlayView setNeedsDisplay];
685
+ }
686
+
687
+ - (void)circleViewDidUpdate:(LuggCircleView *)circleView {
688
+ [_shapeOverlayView setNeedsDisplay];
689
+ }
690
+
691
+ - (void)addGroundOverlayView:(LuggGroundOverlayView *)groundOverlayView {
692
+ groundOverlayView.delegate = self;
693
+ if (![_shapeOverlayView.groundOverlayViews
694
+ containsObject:groundOverlayView]) {
695
+ [_shapeOverlayView.groundOverlayViews addObject:groundOverlayView];
696
+ }
697
+ [self loadGroundOverlayImage:groundOverlayView];
698
+ }
699
+
700
+ - (void)removeGroundOverlayView:(LuggGroundOverlayView *)groundOverlayView {
701
+ groundOverlayView.delegate = nil;
702
+ [_shapeOverlayView.groundOverlayViews removeObject:groundOverlayView];
703
+ [_shapeOverlayView.groundOverlayImages removeObjectForKey:groundOverlayView];
704
+ [_shapeOverlayView setNeedsDisplay];
705
+ }
706
+
707
+ - (void)syncGroundOverlayView:(LuggGroundOverlayView *)groundOverlayView {
708
+ [self loadGroundOverlayImage:groundOverlayView];
709
+ }
710
+
711
+ - (void)groundOverlayViewDidUpdate:(LuggGroundOverlayView *)groundOverlayView {
712
+ [self loadGroundOverlayImage:groundOverlayView];
713
+ }
714
+
715
+ - (void)loadGroundOverlayImage:(LuggGroundOverlayView *)groundOverlayView {
716
+ NSURL *url = [NSURL URLWithString:groundOverlayView.imageUri];
717
+ if (!url)
718
+ return;
719
+
720
+ __weak StaticMapProviderBase *weakSelf = self;
721
+ __weak LuggGroundOverlayView *weakOverlayView = groundOverlayView;
722
+ dispatch_async(
723
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
724
+ NSData *data = [NSData dataWithContentsOfURL:url];
725
+ UIImage *image = data ? [UIImage imageWithData:data] : nil;
726
+ dispatch_async(dispatch_get_main_queue(), ^{
727
+ StaticMapProviderBase *strongSelf = weakSelf;
728
+ LuggGroundOverlayView *overlayView = weakOverlayView;
729
+ if (!strongSelf || !overlayView || !image)
730
+ return;
731
+ LuggStaticShapeOverlayView *shapeView = strongSelf->_shapeOverlayView;
732
+ if (![shapeView.groundOverlayViews containsObject:overlayView])
733
+ return;
734
+ [shapeView.groundOverlayImages setObject:image forKey:overlayView];
735
+ [shapeView setNeedsDisplay];
736
+ });
737
+ });
738
+ }
739
+
740
+ // Tile overlays need a live map to fetch and compose tiles; unsupported on
741
+ // static maps
742
+ - (void)addTileOverlayView:(LuggTileOverlayView *)tileOverlayView {
743
+ }
744
+ - (void)removeTileOverlayView:(LuggTileOverlayView *)tileOverlayView {
745
+ }
746
+ - (void)syncTileOverlayView:(LuggTileOverlayView *)tileOverlayView {
747
+ }
748
+
749
+ #pragma mark - Lifecycle
750
+
751
+ - (void)pauseAnimations {
752
+ }
753
+
754
+ - (void)resumeAnimations {
755
+ // Retry a failed or cancelled base render (e.g. device back online)
756
+ [self renderBaseMapIfNeeded];
757
+ }
758
+
759
+ #pragma mark - Commands
760
+
761
+ // Static maps render once with their initial camera
762
+
763
+ - (void)moveCamera:(double)latitude
764
+ longitude:(double)longitude
765
+ zoom:(double)zoom
766
+ duration:(double)duration {
767
+ }
768
+
769
+ - (void)fitCoordinates:(NSArray *)coordinates
770
+ edgeInsetsTop:(double)edgeInsetsTop
771
+ edgeInsetsLeft:(double)edgeInsetsLeft
772
+ edgeInsetsBottom:(double)edgeInsetsBottom
773
+ edgeInsetsRight:(double)edgeInsetsRight
774
+ duration:(double)duration {
775
+ }
776
+
777
+ @end