@lugg/maps 1.0.0-beta.9 → 1.1.0

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 (64) hide show
  1. package/README.md +15 -100
  2. package/android/src/main/java/com/luggmaps/LuggCalloutView.kt +5 -0
  3. package/android/src/main/java/com/luggmaps/LuggMapView.kt +56 -2
  4. package/android/src/main/java/com/luggmaps/LuggMapViewManager.kt +18 -0
  5. package/android/src/main/java/com/luggmaps/LuggMapWrapperView.kt +36 -2
  6. package/android/src/main/java/com/luggmaps/LuggMarkerView.kt +7 -2
  7. package/android/src/main/java/com/luggmaps/LuggPolylineView.kt +2 -1
  8. package/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +332 -42
  9. package/android/src/main/java/com/luggmaps/core/MapProviderDelegate.kt +6 -0
  10. package/android/src/main/java/com/luggmaps/core/PolylineAnimator.kt +61 -10
  11. package/ios/LuggMapView.mm +240 -10
  12. package/ios/LuggMapWrapperView.h +5 -0
  13. package/ios/LuggMapWrapperView.mm +18 -0
  14. package/ios/LuggMarkerView.mm +2 -0
  15. package/ios/core/AppleMapProvider.h +8 -0
  16. package/ios/core/AppleMapProvider.mm +63 -47
  17. package/ios/core/AppleStaticMapProvider.h +14 -0
  18. package/ios/core/AppleStaticMapProvider.mm +233 -0
  19. package/ios/core/GMSPolylineAnimator.m +40 -15
  20. package/ios/core/GoogleMapProvider.h +10 -0
  21. package/ios/core/GoogleMapProvider.mm +67 -54
  22. package/ios/core/GoogleStaticMapProvider.h +21 -0
  23. package/ios/core/GoogleStaticMapProvider.mm +217 -0
  24. package/ios/core/MKPolylineAnimator.m +67 -43
  25. package/ios/core/MapEdgeInsets.h +18 -0
  26. package/ios/core/MapProviderDelegate.h +11 -0
  27. package/ios/core/StaticMapProviderBase.h +80 -0
  28. package/ios/core/StaticMapProviderBase.mm +847 -0
  29. package/ios/events/CameraIdleEvent.h +4 -2
  30. package/ios/events/CameraMoveEvent.h +4 -2
  31. package/ios/events/CirclePressEvent.h +4 -2
  32. package/ios/events/GroundOverlayPressEvent.h +4 -2
  33. package/ios/events/LongPressEvent.h +4 -2
  34. package/ios/events/MarkerDragEvent.h +12 -6
  35. package/ios/events/MarkerPressEvent.h +4 -2
  36. package/ios/events/PolygonPressEvent.h +4 -2
  37. package/ios/events/PressEvent.h +4 -2
  38. package/ios/events/ReadyEvent.h +4 -2
  39. package/ios/events/TileOverlayPressEvent.h +4 -2
  40. package/ios/extensions/MKMapView+Zoom.h +20 -0
  41. package/ios/extensions/MKMapView+Zoom.m +28 -18
  42. package/ios/extensions/UIView+Snapshot.h +16 -0
  43. package/ios/extensions/UIView+Snapshot.m +27 -0
  44. package/lib/module/MapView.js +12 -0
  45. package/lib/module/MapView.js.map +1 -1
  46. package/lib/module/MapView.web.js +29 -10
  47. package/lib/module/MapView.web.js.map +1 -1
  48. package/lib/module/fabric/LuggMapViewNativeComponent.ts +10 -1
  49. package/lib/typescript/src/MapView.d.ts +1 -0
  50. package/lib/typescript/src/MapView.d.ts.map +1 -1
  51. package/lib/typescript/src/MapView.types.d.ts +32 -0
  52. package/lib/typescript/src/MapView.types.d.ts.map +1 -1
  53. package/lib/typescript/src/MapView.web.d.ts.map +1 -1
  54. package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts +4 -0
  55. package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts.map +1 -1
  56. package/package.json +24 -14
  57. package/src/MapView.tsx +14 -0
  58. package/src/MapView.types.ts +32 -0
  59. package/src/MapView.web.tsx +39 -14
  60. package/src/fabric/LuggMapViewNativeComponent.ts +10 -1
  61. package/plugin/build/withMapsAndroid.d.ts +0 -5
  62. package/plugin/build/withMapsAndroid.js +0 -15
  63. package/plugin/build/withMapsIOS.d.ts +0 -5
  64. package/plugin/build/withMapsIOS.js +0 -27
@@ -15,6 +15,7 @@ using facebook::react::LuggMapViewTheme;
15
15
  #import "../extensions/MKMapView+Zoom.h"
16
16
  #import "LuggAnnotationView.h"
17
17
  #import "MKPolylineAnimator.h"
18
+ #import "MapEdgeInsets.h"
18
19
 
19
20
  @interface AppleMarkerAnnotation : NSObject <MKAnnotation>
20
21
  @property(nonatomic, assign) CLLocationCoordinate2D coordinate;
@@ -145,6 +146,7 @@ static double tileToLng(NSInteger x, NSInteger z) {
145
146
  }
146
147
 
147
148
  @synthesize delegate = _delegate;
149
+ @synthesize staticMode = _staticMode;
148
150
 
149
151
  - (instancetype)init {
150
152
  if (self = [super init]) {
@@ -182,6 +184,10 @@ static double tileToLng(NSInteger x, NSInteger z) {
182
184
  _mapView.delegate = self;
183
185
  _mapView.insetsLayoutMarginsFromSafeArea = NO;
184
186
 
187
+ if (_staticMode) {
188
+ _mapView.userInteractionEnabled = NO;
189
+ }
190
+
185
191
  [self applyZoomRange];
186
192
 
187
193
  _tapGesture =
@@ -212,6 +218,19 @@ static double tileToLng(NSInteger x, NSInteger z) {
212
218
  }
213
219
 
214
220
  - (void)destroy {
221
+ [self destroyMapView];
222
+ _isMapReady = NO;
223
+ }
224
+
225
+ - (CLLocationCoordinate2D)coordinate {
226
+ return _mapView.centerCoordinate;
227
+ }
228
+
229
+ - (double)zoom {
230
+ return _mapView.zoomLevel;
231
+ }
232
+
233
+ - (void)destroyMapView {
215
234
  [self dismissNonBubbledCallout];
216
235
  [self stopEdgeInsetsAnimation];
217
236
  if (_tapGesture) {
@@ -222,9 +241,9 @@ static double tileToLng(NSInteger x, NSInteger z) {
222
241
  [_mapView removeGestureRecognizer:_longPressGesture];
223
242
  _longPressGesture = nil;
224
243
  }
244
+ _mapView.delegate = nil;
225
245
  [_mapView removeFromSuperview];
226
246
  _mapView = nil;
227
- _isMapReady = NO;
228
247
  }
229
248
 
230
249
  #pragma mark - Props
@@ -245,6 +264,10 @@ static double tileToLng(NSInteger x, NSInteger z) {
245
264
  _mapView.pitchEnabled = enabled;
246
265
  }
247
266
 
267
+ - (void)setCompassEnabled:(BOOL)enabled {
268
+ _mapView.showsCompass = enabled;
269
+ }
270
+
248
271
  - (void)setUserLocationEnabled:(BOOL)enabled {
249
272
  _mapView.showsUserLocation = enabled;
250
273
  }
@@ -338,33 +361,36 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
338
361
  return map[string];
339
362
  }
340
363
 
341
- - (void)applyPoiFilter {
342
- if (!_poiEnabled) {
343
- _mapView.pointOfInterestFilter =
344
- MKPointOfInterestFilter.filterExcludingAllCategories;
345
- return;
364
+ MKPointOfInterestFilter *_Nullable LuggPointOfInterestFilter(
365
+ BOOL poiEnabled, LuggMapViewPoiFilterMode filterMode,
366
+ NSArray<NSString *> *filterCategories) {
367
+ if (!poiEnabled) {
368
+ return MKPointOfInterestFilter.filterExcludingAllCategories;
346
369
  }
347
- if (_poiFilterCategories.count > 0) {
370
+ if (filterCategories.count > 0) {
348
371
  NSMutableArray<MKPointOfInterestCategory> *categories =
349
372
  [NSMutableArray array];
350
- for (NSString *name in _poiFilterCategories) {
373
+ for (NSString *name in filterCategories) {
351
374
  MKPointOfInterestCategory category = poiCategoryFromString(name);
352
375
  if (category) {
353
376
  [categories addObject:category];
354
377
  }
355
378
  }
356
379
  if (categories.count > 0) {
357
- if (_poiFilterMode == LuggMapViewPoiFilterMode::Excluding) {
358
- _mapView.pointOfInterestFilter = [[MKPointOfInterestFilter alloc]
380
+ if (filterMode == LuggMapViewPoiFilterMode::Excluding) {
381
+ return [[MKPointOfInterestFilter alloc]
359
382
  initExcludingCategories:categories];
360
- } else {
361
- _mapView.pointOfInterestFilter = [[MKPointOfInterestFilter alloc]
362
- initIncludingCategories:categories];
363
383
  }
364
- return;
384
+ return
385
+ [[MKPointOfInterestFilter alloc] initIncludingCategories:categories];
365
386
  }
366
387
  }
367
- _mapView.pointOfInterestFilter = nil;
388
+ return nil;
389
+ }
390
+
391
+ - (void)applyPoiFilter {
392
+ _mapView.pointOfInterestFilter = LuggPointOfInterestFilter(
393
+ _poiEnabled, _poiFilterMode, _poiFilterCategories);
368
394
  }
369
395
 
370
396
  - (void)setInsetAdjustment:(LuggMapViewInsetAdjustment)insetAdjustment {
@@ -395,26 +421,25 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
395
421
  [self applyPoiFilter];
396
422
  }
397
423
 
398
- - (void)setMapType:(LuggMapViewMapType)mapType {
424
+ MKMapType LuggMKMapTypeFromMapType(LuggMapViewMapType mapType) {
399
425
  switch (mapType) {
400
426
  case LuggMapViewMapType::Satellite:
401
- _mapView.mapType = MKMapTypeSatellite;
402
- break;
427
+ return MKMapTypeSatellite;
403
428
  case LuggMapViewMapType::Terrain:
404
- _mapView.mapType = MKMapTypeStandard;
405
- break;
429
+ return MKMapTypeStandard;
406
430
  case LuggMapViewMapType::Hybrid:
407
- _mapView.mapType = MKMapTypeHybrid;
408
- break;
431
+ return MKMapTypeHybrid;
409
432
  case LuggMapViewMapType::MutedStandard:
410
- _mapView.mapType = MKMapTypeMutedStandard;
411
- break;
433
+ return MKMapTypeMutedStandard;
412
434
  default:
413
- _mapView.mapType = MKMapTypeStandard;
414
- break;
435
+ return MKMapTypeStandard;
415
436
  }
416
437
  }
417
438
 
439
+ - (void)setMapType:(LuggMapViewMapType)mapType {
440
+ _mapView.mapType = LuggMKMapTypeFromMapType(mapType);
441
+ }
442
+
418
443
  - (void)setTheme:(LuggMapViewTheme)theme {
419
444
  switch (theme) {
420
445
  case LuggMapViewTheme::Dark:
@@ -441,13 +466,11 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
441
466
 
442
467
  - (void)setEdgeInsets:(UIEdgeInsets)edgeInsets
443
468
  oldEdgeInsets:(UIEdgeInsets)oldEdgeInsets {
444
- CGFloat oldOffsetX = (oldEdgeInsets.left - oldEdgeInsets.right) / 2.0;
445
- CGFloat oldOffsetY = (oldEdgeInsets.top - oldEdgeInsets.bottom) / 2.0;
446
- CGFloat newOffsetX = (edgeInsets.left - edgeInsets.right) / 2.0;
447
- CGFloat newOffsetY = (edgeInsets.top - edgeInsets.bottom) / 2.0;
469
+ CGPoint oldOffset = LuggMapInsetOffset(oldEdgeInsets);
470
+ CGPoint newOffset = LuggMapInsetOffset(edgeInsets);
448
471
 
449
- CGFloat deltaX = newOffsetX - oldOffsetX;
450
- CGFloat deltaY = newOffsetY - oldOffsetY;
472
+ CGFloat deltaX = newOffset.x - oldOffset.x;
473
+ CGFloat deltaY = newOffset.y - oldOffset.y;
451
474
 
452
475
  _mapView.layoutMargins = edgeInsets;
453
476
  _edgeInsetsCurrent = edgeInsets;
@@ -490,16 +513,8 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
490
513
  CFTimeInterval elapsed = CACurrentMediaTime() - _edgeInsetsAnimationStart;
491
514
  CGFloat progress = MIN(elapsed / _edgeInsetsAnimationDuration, 1.0);
492
515
 
493
- // Ease out cubic
494
- CGFloat t = 1.0 - (1.0 - progress) * (1.0 - progress) * (1.0 - progress);
495
-
496
- UIEdgeInsets current = UIEdgeInsetsMake(
497
- _edgeInsetsFrom.top + (_edgeInsetsTo.top - _edgeInsetsFrom.top) * t,
498
- _edgeInsetsFrom.left + (_edgeInsetsTo.left - _edgeInsetsFrom.left) * t,
499
- _edgeInsetsFrom.bottom +
500
- (_edgeInsetsTo.bottom - _edgeInsetsFrom.bottom) * t,
501
- _edgeInsetsFrom.right +
502
- (_edgeInsetsTo.right - _edgeInsetsFrom.right) * t);
516
+ UIEdgeInsets current =
517
+ LuggMapInsetsAtProgress(_edgeInsetsFrom, _edgeInsetsTo, progress);
503
518
 
504
519
  [self setEdgeInsets:current oldEdgeInsets:_edgeInsetsCurrent];
505
520
 
@@ -832,7 +847,8 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
832
847
  renderer.strokeColors = colors;
833
848
  }
834
849
  renderer.animatedOptions = polylineView.animatedOptions;
835
- renderer.animated = polylineView.animated;
850
+ // Static maps snapshot an arbitrary frame; render the full polyline
851
+ renderer.animated = polylineView.animated && !_staticMode;
836
852
  polylineView.renderer = renderer;
837
853
  return renderer;
838
854
  }
@@ -1361,7 +1377,7 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
1361
1377
  renderer.strokeColors =
1362
1378
  polylineView.strokeColors.count > 1 ? polylineView.strokeColors : nil;
1363
1379
  renderer.animatedOptions = polylineView.animatedOptions;
1364
- renderer.animated = polylineView.animated;
1380
+ renderer.animated = polylineView.animated && !_staticMode;
1365
1381
  return;
1366
1382
  }
1367
1383
 
@@ -1774,11 +1790,11 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
1774
1790
  } else if (duration > 0) {
1775
1791
  CLLocationCoordinate2D center =
1776
1792
  CLLocationCoordinate2DMake(latitude, longitude);
1777
- MKCoordinateRegion region = [_mapView regionForCenterCoordinate:center
1778
- zoomLevel:targetZoom];
1793
+ MKMapCamera *camera = [_mapView cameraForCenterCoordinate:center
1794
+ zoomLevel:targetZoom];
1779
1795
  [UIView animateWithDuration:duration / 1000.0
1780
1796
  animations:^{
1781
- [self->_mapView setRegion:region animated:NO];
1797
+ [self->_mapView setCamera:camera animated:NO];
1782
1798
  }];
1783
1799
  } else {
1784
1800
  [_mapView
@@ -0,0 +1,14 @@
1
+ #import "StaticMapProviderBase.h"
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ /**
6
+ * Static Apple map rendered with MKMapSnapshotter. The base map renders
7
+ * fully asynchronously (no live MKMapView is ever created), so static maps
8
+ * keep loading while the user scrolls without blocking the main thread.
9
+ */
10
+ @interface AppleStaticMapProvider : StaticMapProviderBase
11
+
12
+ @end
13
+
14
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,233 @@
1
+ #import "AppleStaticMapProvider.h"
2
+
3
+ using facebook::react::LuggMapViewPoiFilterMode;
4
+ using facebook::react::LuggMapViewTheme;
5
+
6
+ #import "../extensions/MKMapView+Zoom.h"
7
+ #import "../extensions/UIView+Snapshot.h"
8
+ #import "AppleMapProvider.h"
9
+ #import "LuggAnnotationView.h"
10
+
11
+ // Standard MKMarkerAnnotationView balloon size; standalone (off-map) views
12
+ // don't size themselves
13
+ static const CGSize kDefaultMarkerSize = {28, 33};
14
+
15
+ // The rect the snapshot renders: the region for the requested zoom, expanded
16
+ // on the short axis to the view's aspect ratio - the same fit MKMapView
17
+ // applies in setRegion, so static and live maps frame alike. Passing this
18
+ // exact rect to MKMapSnapshotter makes the projection exact.
19
+ static MKMapRect LuggStaticFittedMapRect(CLLocationCoordinate2D center,
20
+ double zoom, CGSize size) {
21
+ MKCoordinateSpan span = LuggCoordinateSpanForZoomLevel(MIN(zoom, 28), center);
22
+ MKMapPoint topLeft = MKMapPointForCoordinate(CLLocationCoordinate2DMake(
23
+ MIN(center.latitude + span.latitudeDelta / 2, 90.0),
24
+ center.longitude - span.longitudeDelta / 2));
25
+ MKMapPoint bottomRight = MKMapPointForCoordinate(CLLocationCoordinate2DMake(
26
+ MAX(center.latitude - span.latitudeDelta / 2, -90.0),
27
+ center.longitude + span.longitudeDelta / 2));
28
+ MKMapRect rect =
29
+ MKMapRectMake(topLeft.x, topLeft.y, bottomRight.x - topLeft.x,
30
+ bottomRight.y - topLeft.y);
31
+
32
+ double viewAspect = size.width / size.height;
33
+ double rectAspect = rect.size.width / rect.size.height;
34
+ if (rectAspect > viewAspect) {
35
+ double height = rect.size.width / viewAspect;
36
+ rect.origin.y -= (height - rect.size.height) / 2;
37
+ rect.size.height = height;
38
+ } else {
39
+ double width = rect.size.height * viewAspect;
40
+ rect.origin.x -= (width - rect.size.width) / 2;
41
+ rect.size.width = width;
42
+ }
43
+ return rect;
44
+ }
45
+
46
+ @interface AppleStaticMapProvider () <MKMapViewDelegate>
47
+ @end
48
+
49
+ @implementation AppleStaticMapProvider {
50
+ MKMapSnapshotter *_snapshotter;
51
+ MKMapView *_warmupMapView;
52
+ BOOL _snapshotReady;
53
+
54
+ BOOL _poiEnabled;
55
+ LuggMapViewPoiFilterMode _poiFilterMode;
56
+ NSArray<NSString *> *_poiFilterCategories;
57
+ }
58
+
59
+ - (instancetype)init {
60
+ if (self = [super init]) {
61
+ _poiEnabled = YES;
62
+ _poiFilterMode = LuggMapViewPoiFilterMode::Including;
63
+ _poiFilterCategories = @[];
64
+ }
65
+ return self;
66
+ }
67
+
68
+ #pragma mark - StaticMapProviderBase
69
+
70
+ - (MKMapRect)mapRectForSize:(CGSize)size {
71
+ return LuggStaticFittedMapRect(self.coordinate, self.zoom, size);
72
+ }
73
+
74
+ - (UIView *)newDefaultMarkerOverlayView {
75
+ MKAnnotationView *pin =
76
+ [[LuggMarkerAnnotationView alloc] initWithAnnotation:nil
77
+ reuseIdentifier:nil];
78
+ pin.bounds =
79
+ CGRectMake(0, 0, kDefaultMarkerSize.width, kDefaultMarkerSize.height);
80
+ return pin;
81
+ }
82
+
83
+ // Inverse of LuggStaticFittedMapRect: the pre-fit rect is square in map
84
+ // points with side latitudeDelta * worldHeight / (360 * cos(lat)), and the
85
+ // aspect fit scales it to the view's short side
86
+ - (double)zoomForMapPointsPerPoint:(double)mapPointsPerPoint {
87
+ CGSize size = self.projectedSize;
88
+ double side = mapPointsPerPoint * MIN(size.width, size.height);
89
+ double latitudeDelta = side * 360.0 *
90
+ cos(self.coordinate.latitude * M_PI / 180.0) /
91
+ MKMapSizeWorld.height;
92
+ double zoom = 0.5 + log2(360.0 / latitudeDelta);
93
+ return MAX(MIN(zoom, 28.0), 0.0);
94
+ }
95
+
96
+ - (UITraitCollection *)snapshotTraitCollection {
97
+ UITraitCollection *base = self.wrapperView.traitCollection;
98
+ UIUserInterfaceStyle style;
99
+ switch (self.theme) {
100
+ case LuggMapViewTheme::Dark:
101
+ style = UIUserInterfaceStyleDark;
102
+ break;
103
+ case LuggMapViewTheme::Light:
104
+ style = UIUserInterfaceStyleLight;
105
+ break;
106
+ default:
107
+ return base;
108
+ }
109
+ return [UITraitCollection traitCollectionWithTraitsFromCollections:@[
110
+ base, [UITraitCollection traitCollectionWithUserInterfaceStyle:style]
111
+ ]];
112
+ }
113
+
114
+ - (void)renderBaseMap {
115
+ // MKMapSnapshotter bakes attribution into the image without inset control.
116
+ if (!UIEdgeInsetsEqualToEdgeInsets(self.edgeInsets, UIEdgeInsetsZero)) {
117
+ [self renderInsetBaseMap];
118
+ return;
119
+ }
120
+
121
+ if (_snapshotter)
122
+ return;
123
+
124
+ MKMapSnapshotOptions *options = [[MKMapSnapshotOptions alloc] init];
125
+ options.mapRect = self.mapRect;
126
+ options.size = self.projectedSize;
127
+ options.mapType = LuggMKMapTypeFromMapType(self.mapType);
128
+ options.pointOfInterestFilter = LuggPointOfInterestFilter(
129
+ _poiEnabled, _poiFilterMode, _poiFilterCategories);
130
+ options.traitCollection = [self snapshotTraitCollection];
131
+
132
+ MKMapSnapshotter *snapshotter =
133
+ [[MKMapSnapshotter alloc] initWithOptions:options];
134
+ _snapshotter = snapshotter;
135
+
136
+ __weak AppleStaticMapProvider *weakSelf = self;
137
+ [snapshotter startWithQueue:dispatch_get_main_queue()
138
+ completionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
139
+ AppleStaticMapProvider *strongSelf = weakSelf;
140
+ if (!strongSelf || strongSelf->_snapshotter != snapshotter)
141
+ return;
142
+ strongSelf->_snapshotter = nil;
143
+ if (error || !snapshot) {
144
+ // Keep the placeholder; resumeAnimations retries (e.g. back
145
+ // online)
146
+ return;
147
+ }
148
+ [strongSelf displayBaseImage:snapshot.image fromCache:NO];
149
+ }];
150
+ }
151
+
152
+ - (void)cancelBaseRender {
153
+ [_snapshotter cancel];
154
+ _snapshotter = nil;
155
+ _warmupMapView.delegate = nil;
156
+ [_warmupMapView removeFromSuperview];
157
+ _warmupMapView = nil;
158
+ _snapshotReady = NO;
159
+ }
160
+
161
+ #pragma mark - Inset snapshots
162
+
163
+ - (void)renderInsetBaseMap {
164
+ if (_warmupMapView) {
165
+ if (!_snapshotReady || !_warmupMapView.window)
166
+ return;
167
+
168
+ UIImageView *imageView = [_warmupMapView lugg_snapshotImageView];
169
+ if (!imageView)
170
+ return;
171
+
172
+ [self displayBaseImage:imageView.image fromCache:NO];
173
+ [self cancelBaseRender];
174
+ return;
175
+ }
176
+
177
+ MKMapView *mapView =
178
+ [[MKMapView alloc] initWithFrame:self.wrapperView.bounds];
179
+ mapView.userInteractionEnabled = NO;
180
+ mapView.showsCompass = NO;
181
+ mapView.insetsLayoutMarginsFromSafeArea = NO;
182
+ mapView.layoutMargins = self.edgeInsets;
183
+ mapView.mapType = LuggMKMapTypeFromMapType(self.mapType);
184
+ mapView.pointOfInterestFilter = LuggPointOfInterestFilter(
185
+ _poiEnabled, _poiFilterMode, _poiFilterCategories);
186
+ mapView.overrideUserInterfaceStyle =
187
+ [self snapshotTraitCollection].userInterfaceStyle;
188
+ [mapView setVisibleMapRect:self.mapRect
189
+ edgePadding:UIEdgeInsetsZero
190
+ animated:NO];
191
+ _warmupMapView = mapView;
192
+ mapView.delegate = self;
193
+ [self.wrapperView insertSubview:mapView atIndex:0];
194
+ }
195
+
196
+ - (void)mapViewWillStartRenderingMap:(MKMapView *)mapView {
197
+ _snapshotReady = NO;
198
+ }
199
+
200
+ - (void)mapViewDidFinishRenderingMap:(MKMapView *)mapView
201
+ fullyRendered:(BOOL)fullyRendered {
202
+ _snapshotReady = fullyRendered;
203
+ [self revealOverlays];
204
+ if (!fullyRendered)
205
+ return;
206
+
207
+ __weak AppleStaticMapProvider *weakSelf = self;
208
+ __weak MKMapView *weakMapView = mapView;
209
+ [[NSRunLoop mainRunLoop]
210
+ performInModes:@[ NSDefaultRunLoopMode ]
211
+ block:^{
212
+ AppleStaticMapProvider *strongSelf = weakSelf;
213
+ if (strongSelf && weakMapView &&
214
+ strongSelf->_warmupMapView == weakMapView)
215
+ [strongSelf renderInsetBaseMap];
216
+ }];
217
+ }
218
+
219
+ #pragma mark - Props
220
+
221
+ - (void)setPoiEnabled:(BOOL)enabled {
222
+ _poiEnabled = enabled;
223
+ }
224
+
225
+ - (void)setPoiFilterMode:(LuggMapViewPoiFilterMode)mode {
226
+ _poiFilterMode = mode;
227
+ }
228
+
229
+ - (void)setPoiFilterCategories:(NSArray<NSString *> *)categories {
230
+ _poiFilterCategories = categories;
231
+ }
232
+
233
+ @end
@@ -148,13 +148,25 @@ static const NSUInteger kMaxGradientSpans = 512;
148
148
  }
149
149
 
150
150
  GMSMutablePath *path = [GMSMutablePath path];
151
- for (CLLocation *location in self.coordinates) {
152
- [path addCoordinate:location.coordinate];
151
+ CLLocationCoordinate2D lastCoord = self.coordinates.firstObject.coordinate;
152
+ [path addCoordinate:lastCoord];
153
+ for (NSUInteger i = 1; i < self.coordinates.count; i++) {
154
+ CLLocationCoordinate2D coord = self.coordinates[i].coordinate;
155
+ if (coord.latitude == lastCoord.latitude && coord.longitude == lastCoord.longitude) {
156
+ continue;
157
+ }
158
+ [path addCoordinate:coord];
159
+ lastCoord = coord;
153
160
  }
161
+
162
+ // Clear stale spans before swapping the path so Google Maps never rebuilds
163
+ // span models against a path with fewer segments (GMSModelStyleBuilder
164
+ // couldNotInstantiate -> EXC_BAD_ACCESS). Reassign spans once the new path is set.
165
+ _polyline.spans = nil;
154
166
  _polyline.path = path;
155
167
 
156
- if (self.strokeColors.count > 1) {
157
- _polyline.spans = [self createGradientSpans];
168
+ if (path.count > 1 && self.strokeColors.count > 1) {
169
+ _polyline.spans = [self createGradientSpansForSegmentCount:path.count - 1];
158
170
  } else {
159
171
  _polyline.strokeColor = self.strokeColors.firstObject ?: [UIColor blackColor];
160
172
  }
@@ -222,35 +234,44 @@ static const NSUInteger kMaxGradientSpans = 512;
222
234
  }
223
235
 
224
236
  if (headDist <= tailDist) {
237
+ _polyline.spans = nil;
225
238
  _polyline.path = [GMSMutablePath path];
226
239
  return;
227
240
  }
228
241
 
229
- CGFloat visibleLength = headDist - tailDist;
230
242
  NSUInteger startIndex = [self indexForDistance:tailDist];
231
243
  NSUInteger endIndex = [self indexForDistance:headDist];
232
244
 
233
245
  GMSMutablePath *path = [GMSMutablePath path];
234
- NSMutableArray<GMSStyleSpan *> *spans = [NSMutableArray array];
235
246
 
236
- CLLocationCoordinate2D startCoord = [self coordinateAtDistance:tailDist];
237
- [path addCoordinate:startCoord];
247
+ CLLocationCoordinate2D lastCoord = [self coordinateAtDistance:tailDist];
248
+ [path addCoordinate:lastCoord];
238
249
 
239
250
  for (NSUInteger i = startIndex + 1; i <= endIndex; i++) {
240
- [path addCoordinate:self.coordinates[i].coordinate];
251
+ CLLocationCoordinate2D coord = self.coordinates[i].coordinate;
252
+ if (coord.latitude == lastCoord.latitude && coord.longitude == lastCoord.longitude) {
253
+ continue;
254
+ }
255
+ [path addCoordinate:coord];
256
+ lastCoord = coord;
241
257
  }
242
258
 
243
259
  CLLocationCoordinate2D endCoord = [self coordinateAtDistance:headDist];
244
- CLLocationCoordinate2D lastAdded =
245
- (endIndex < self.coordinates.count) ? self.coordinates[endIndex].coordinate : endCoord;
246
- if (endCoord.latitude != lastAdded.latitude || endCoord.longitude != lastAdded.longitude) {
260
+ if (endCoord.latitude != lastCoord.latitude || endCoord.longitude != lastCoord.longitude) {
247
261
  [path addCoordinate:endCoord];
248
262
  }
249
263
 
250
264
  NSUInteger pathCount = path.count;
251
- NSUInteger segmentCount = pathCount - 1;
265
+ NSUInteger segmentCount = (pathCount > 1) ? pathCount - 1 : 0;
266
+
267
+ if (segmentCount == 0) {
268
+ _polyline.spans = nil;
269
+ _polyline.path = path;
270
+ return;
271
+ }
252
272
 
253
273
  if (self.strokeColors.count <= 1) {
274
+ _polyline.spans = nil;
254
275
  _polyline.path = path;
255
276
  _polyline.strokeColor = self.strokeColors.firstObject ?: [UIColor blackColor];
256
277
  return;
@@ -259,6 +280,7 @@ static const NSUInteger kMaxGradientSpans = 512;
259
280
  NSUInteger spanCount = MIN(segmentCount, kMaxAnimationSpans);
260
281
  double segmentsPerSpan = (double)segmentCount / spanCount;
261
282
 
283
+ NSMutableArray<GMSStyleSpan *> *spans = [NSMutableArray array];
262
284
  for (NSUInteger i = 0; i < spanCount; i++) {
263
285
  CGFloat gradientPos = ((CGFloat)i + 0.5) / spanCount;
264
286
  UIColor *color = [self colorAtGradientPosition:gradientPos];
@@ -266,13 +288,16 @@ static const NSUInteger kMaxGradientSpans = 512;
266
288
  [spans addObject:[GMSStyleSpan spanWithStyle:style segments:segmentsPerSpan]];
267
289
  }
268
290
 
291
+ // Clear stale spans before swapping the path so Google Maps never rebuilds
292
+ // span models against a path with fewer segments (GMSModelStyleBuilder
293
+ // couldNotInstantiate -> EXC_BAD_ACCESS). Reassign spans once the new path is set.
294
+ _polyline.spans = nil;
269
295
  _polyline.path = path;
270
296
  _polyline.spans = spans;
271
297
  }
272
298
 
273
- - (NSArray<GMSStyleSpan *> *)createGradientSpans {
299
+ - (NSArray<GMSStyleSpan *> *)createGradientSpansForSegmentCount:(NSUInteger)segmentCount {
274
300
  NSMutableArray<GMSStyleSpan *> *spans = [NSMutableArray array];
275
- NSUInteger segmentCount = self.coordinates.count - 1;
276
301
  NSUInteger spanCount = MIN(segmentCount, kMaxGradientSpans);
277
302
  double segmentsPerSpan = (double)segmentCount / spanCount;
278
303
 
@@ -3,6 +3,16 @@
3
3
 
4
4
  NS_ASSUME_NONNULL_BEGIN
5
5
 
6
+ /// Resolves a mapId prop value ("" or "DEMO_MAP_ID" -> demo map ID),
7
+ /// shared by the live and static Google providers.
8
+ GMSMapID *LuggGMSMapIDFromString(NSString *_Nullable mapId);
9
+
10
+ GMSMapViewType
11
+ LuggGMSMapTypeFromMapType(facebook::react::LuggMapViewMapType mapType);
12
+
13
+ UIUserInterfaceStyle
14
+ LuggInterfaceStyleFromTheme(facebook::react::LuggMapViewTheme theme);
15
+
6
16
  @interface GoogleMapProvider : NSObject <MapProvider, GMSMapViewDelegate>
7
17
 
8
18
  @property(nonatomic, copy, nullable) NSString *mapId;