@lugg/maps 1.0.0-beta.16 → 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 (41) 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/GMSPolylineAnimator.m +40 -15
  14. package/ios/core/GoogleMapProvider.h +10 -0
  15. package/ios/core/GoogleMapProvider.mm +47 -41
  16. package/ios/core/GoogleStaticMapProvider.h +21 -0
  17. package/ios/core/GoogleStaticMapProvider.mm +204 -0
  18. package/ios/core/MKPolylineAnimator.m +67 -43
  19. package/ios/core/MapProviderDelegate.h +6 -0
  20. package/ios/core/StaticMapProviderBase.h +69 -0
  21. package/ios/core/StaticMapProviderBase.mm +745 -0
  22. package/ios/extensions/MKMapView+Zoom.h +14 -0
  23. package/ios/extensions/MKMapView+Zoom.m +17 -15
  24. package/ios/extensions/UIView+Snapshot.h +16 -0
  25. package/ios/extensions/UIView+Snapshot.m +27 -0
  26. package/lib/module/MapView.js +4 -0
  27. package/lib/module/MapView.js.map +1 -1
  28. package/lib/module/MapView.web.js +8 -5
  29. package/lib/module/MapView.web.js.map +1 -1
  30. package/lib/module/fabric/LuggMapViewNativeComponent.ts +2 -0
  31. package/lib/typescript/src/MapView.d.ts.map +1 -1
  32. package/lib/typescript/src/MapView.types.d.ts +18 -0
  33. package/lib/typescript/src/MapView.types.d.ts.map +1 -1
  34. package/lib/typescript/src/MapView.web.d.ts.map +1 -1
  35. package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts +2 -0
  36. package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/MapView.tsx +4 -0
  39. package/src/MapView.types.ts +18 -0
  40. package/src/MapView.web.tsx +14 -10
  41. package/src/fabric/LuggMapViewNativeComponent.ts +2 -0
@@ -35,8 +35,9 @@
35
35
  NSArray<NSNumber *> *_cumulativeDistances;
36
36
  CGFloat _totalLength;
37
37
  CGColorSpaceRef _colorSpace;
38
- RGBAComponents *_colorCache;
39
- NSUInteger _colorCacheCount;
38
+ // Immutable snapshot read by background draws; rebuilt (never mutated)
39
+ // on the main thread
40
+ NSData *_colorCacheData;
40
41
  }
41
42
 
42
43
  - (id)initWithPolyline:(MKPolyline *)polyline {
@@ -61,7 +62,6 @@
61
62
 
62
63
  - (void)dealloc {
63
64
  [self stopAnimation];
64
- free(_colorCache);
65
65
  if (_colorSpace) {
66
66
  CGColorSpaceRelease(_colorSpace);
67
67
  _colorSpace = NULL;
@@ -123,20 +123,25 @@
123
123
  _displayLink.paused = NO;
124
124
  }
125
125
 
126
- - (NSUInteger)indexForDistance:(CGFloat)distance {
126
+ - (NSUInteger)indexForDistance:(CGFloat)distance
127
+ inDistances:(NSArray<NSNumber *> *)distances {
128
+ if (distances.count < 2) {
129
+ return 0;
130
+ }
131
+
127
132
  NSUInteger left = 0;
128
- NSUInteger right = _cumulativeDistances.count - 1;
133
+ NSUInteger right = distances.count - 1;
129
134
 
130
135
  while (left < right) {
131
136
  NSUInteger mid = (left + right + 1) / 2;
132
- if (_cumulativeDistances[mid].doubleValue <= distance) {
137
+ if (distances[mid].doubleValue <= distance) {
133
138
  left = mid;
134
139
  } else {
135
140
  right = mid - 1;
136
141
  }
137
142
  }
138
143
 
139
- return MIN(left, _cumulativeDistances.count - 2);
144
+ return MIN(left, distances.count - 2);
140
145
  }
141
146
 
142
147
  - (CGFloat)applyEasing:(CGFloat)t {
@@ -208,44 +213,52 @@
208
213
  }
209
214
 
210
215
  - (void)rebuildColorCache {
211
- free(_colorCache);
212
- _colorCache = NULL;
213
- _colorCacheCount = _strokeColors.count;
214
- if (_colorCacheCount == 0) return;
215
-
216
- _colorCache = (RGBAComponents *)malloc(sizeof(RGBAComponents) * _colorCacheCount);
217
- for (NSUInteger i = 0; i < _colorCacheCount; i++) {
218
- [_strokeColors[i] getRed:&_colorCache[i].r
219
- green:&_colorCache[i].g
220
- blue:&_colorCache[i].b
221
- alpha:&_colorCache[i].a];
216
+ NSUInteger count = _strokeColors.count;
217
+ if (count == 0) {
218
+ _colorCacheData = nil;
219
+ return;
220
+ }
221
+
222
+ NSMutableData *data =
223
+ [NSMutableData dataWithLength:sizeof(RGBAComponents) * count];
224
+ RGBAComponents *cache = (RGBAComponents *)data.mutableBytes;
225
+ for (NSUInteger i = 0; i < count; i++) {
226
+ [_strokeColors[i] getRed:&cache[i].r
227
+ green:&cache[i].g
228
+ blue:&cache[i].b
229
+ alpha:&cache[i].a];
222
230
  }
231
+ _colorCacheData = data;
223
232
  }
224
233
 
225
234
  - (void)colorAtGradientPosition:(CGFloat)position rgba:(RGBAComponents *)out {
226
- if (_colorCacheCount == 0) {
235
+ NSData *cacheData = _colorCacheData;
236
+ const RGBAComponents *cache = (const RGBAComponents *)cacheData.bytes;
237
+ NSUInteger count = cacheData.length / sizeof(RGBAComponents);
238
+
239
+ if (count == 0) {
227
240
  CGFloat r, g, b, a;
228
241
  [self.strokeColor getRed:&r green:&g blue:&b alpha:&a];
229
242
  *out = (RGBAComponents){r, g, b, a};
230
243
  return;
231
244
  }
232
- if (_colorCacheCount == 1) {
233
- *out = _colorCache[0];
245
+ if (count == 1) {
246
+ *out = cache[0];
234
247
  return;
235
248
  }
236
249
 
237
250
  position = MAX(0, MIN(1, position));
238
- CGFloat scaledPos = position * (_colorCacheCount - 1);
251
+ CGFloat scaledPos = position * (count - 1);
239
252
  NSUInteger index = (NSUInteger)scaledPos;
240
253
  CGFloat t = scaledPos - index;
241
254
 
242
- if (index >= _colorCacheCount - 1) {
243
- *out = _colorCache[_colorCacheCount - 1];
255
+ if (index >= count - 1) {
256
+ *out = cache[count - 1];
244
257
  return;
245
258
  }
246
259
 
247
- RGBAComponents c1 = _colorCache[index];
248
- RGBAComponents c2 = _colorCache[index + 1];
260
+ RGBAComponents c1 = cache[index];
261
+ RGBAComponents c2 = cache[index + 1];
249
262
  out->r = c1.r + (c2.r - c1.r) * t;
250
263
  out->g = c1.g + (c2.g - c1.g) * t;
251
264
  out->b = c1.b + (c2.b - c1.b) * t;
@@ -272,13 +285,20 @@
272
285
  CGContextSetLineCap(context, self.lineCap);
273
286
  CGContextSetLineJoin(context, self.lineJoin);
274
287
 
275
- NSUInteger segmentCount = _polyline.pointCount - 1;
276
- if (segmentCount == 0) {
288
+ // drawMapRect: runs on MapKit's background render queues while
289
+ // updatePolyline: swaps these ivars on the main thread; snapshot them so
290
+ // one frame draws from a consistent pair
291
+ MKPolyline *polyline = _polyline;
292
+ NSArray<NSNumber *> *distances = _cumulativeDistances;
293
+ CGFloat totalLength = _totalLength;
294
+
295
+ if (polyline.pointCount < 2) {
277
296
  return;
278
297
  }
298
+ NSUInteger segmentCount = polyline.pointCount - 1;
279
299
 
280
300
  // Snake animation: grow from start, then shrink from start
281
- if (_animated && _polyline.pointCount > 1 && _totalLength > 0) {
301
+ if (_animated && distances.count > 1 && totalLength > 0) {
282
302
  CGFloat trailLength = MAX(0.01, MIN(1.0, _animatedOptions.trailLength));
283
303
  CGFloat maxProgress = (trailLength < 1.0) ? 1.0 : 2.0;
284
304
  CGFloat progress = MIN(_animationProgress, maxProgress);
@@ -287,15 +307,15 @@
287
307
  CGFloat headDist, tailDist;
288
308
 
289
309
  if (trailLength < 1.0) {
290
- headDist = easedProgress * _totalLength;
291
- tailDist = MAX(0, headDist - _totalLength * trailLength);
310
+ headDist = easedProgress * totalLength;
311
+ tailDist = MAX(0, headDist - totalLength * trailLength);
292
312
  } else if (easedProgress <= 1.0) {
293
313
  tailDist = 0;
294
- headDist = easedProgress * _totalLength;
314
+ headDist = easedProgress * totalLength;
295
315
  } else {
296
316
  CGFloat shrinkProgress = easedProgress - 1.0;
297
- tailDist = shrinkProgress * _totalLength;
298
- headDist = _totalLength;
317
+ tailDist = shrinkProgress * totalLength;
318
+ headDist = totalLength;
299
319
  }
300
320
 
301
321
  if (headDist <= tailDist) {
@@ -303,19 +323,23 @@
303
323
  }
304
324
 
305
325
  CGFloat visibleLength = headDist - tailDist;
306
- NSUInteger startIndex = [self indexForDistance:tailDist];
307
- NSUInteger endIndex = [self indexForDistance:headDist];
326
+ NSUInteger startIndex = [self indexForDistance:tailDist inDistances:distances];
327
+ NSUInteger endIndex = [self indexForDistance:headDist inDistances:distances];
328
+
329
+ // distances may lag behind the polyline (or vice versa) for a frame;
330
+ // never index past either
331
+ NSUInteger maxSegment = MIN(segmentCount, distances.count - 1);
308
332
 
309
- for (NSUInteger i = startIndex; i <= endIndex && i < segmentCount; i++) {
310
- CGFloat segStartDist = _cumulativeDistances[i].doubleValue;
311
- CGFloat segEndDist = _cumulativeDistances[i + 1].doubleValue;
333
+ for (NSUInteger i = startIndex; i <= endIndex && i < maxSegment; i++) {
334
+ CGFloat segStartDist = distances[i].doubleValue;
335
+ CGFloat segEndDist = distances[i + 1].doubleValue;
312
336
 
313
337
  if (segEndDist <= tailDist || segStartDist >= headDist) {
314
338
  continue;
315
339
  }
316
340
 
317
- CGPoint segStart = [self pointForMapPoint:_polyline.points[i]];
318
- CGPoint segEnd = [self pointForMapPoint:_polyline.points[i + 1]];
341
+ CGPoint segStart = [self pointForMapPoint:polyline.points[i]];
342
+ CGPoint segEnd = [self pointForMapPoint:polyline.points[i + 1]];
319
343
 
320
344
  CGPoint drawStart = segStart;
321
345
  CGPoint drawEnd = segEnd;
@@ -368,8 +392,8 @@
368
392
  // Static gradient rendering
369
393
  if (_strokeColors && _strokeColors.count > 1) {
370
394
  for (NSUInteger i = 0; i < segmentCount; i++) {
371
- CGPoint startPoint = [self pointForMapPoint:_polyline.points[i]];
372
- CGPoint endPoint = [self pointForMapPoint:_polyline.points[i + 1]];
395
+ CGPoint startPoint = [self pointForMapPoint:polyline.points[i]];
396
+ CGPoint endPoint = [self pointForMapPoint:polyline.points[i + 1]];
373
397
 
374
398
  CGFloat gradientStart = (CGFloat)i / segmentCount;
375
399
  CGFloat gradientEnd = (CGFloat)(i + 1) / segmentCount;
@@ -13,6 +13,8 @@ NS_ASSUME_NONNULL_BEGIN
13
13
 
14
14
  @protocol MapProviderDelegate <NSObject>
15
15
  - (void)mapProviderDidReady;
16
+ - (void)mapProviderDidFinishStaticSnapshot;
17
+ - (void)mapProviderDidCaptureStaticImage:(UIImage *)image;
16
18
  - (void)mapProviderDidMoveCamera:(double)latitude
17
19
  longitude:(double)longitude
18
20
  zoom:(double)zoom
@@ -36,6 +38,10 @@ NS_ASSUME_NONNULL_BEGIN
36
38
  @property(nonatomic, weak, nullable) id<MapProviderDelegate> delegate;
37
39
  @property(nonatomic, readonly) BOOL isMapReady;
38
40
 
41
+ // Non-interactive static map. Must be set before initializeMapInView.
42
+ // Once fully rendered, the live map is replaced with a snapshot image.
43
+ @property(nonatomic, assign) BOOL staticMode;
44
+
39
45
  - (void)initializeMapInView:(UIView *)wrapperView
40
46
  initialCoordinate:(CLLocationCoordinate2D)coordinate
41
47
  initialZoom:(double)zoom;
@@ -0,0 +1,69 @@
1
+ #import "MapProviderDelegate.h"
2
+ #import <MapKit/MapKit.h>
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ /// Projects a coordinate into view points for a static map covering mapRect
7
+ CGPoint LuggStaticPointForCoordinate(MKMapRect mapRect, CGSize size,
8
+ CLLocationCoordinate2D coordinate);
9
+
10
+ /**
11
+ * Shared behavior for static (non-interactive) map providers - the iOS
12
+ * equivalent of Android's lite mode. The base map renders asynchronously
13
+ * (subclass-specific), so static maps keep loading while the user scrolls
14
+ * without blocking the main thread.
15
+ *
16
+ * Markers are live views positioned over the base map via a mercator
17
+ * projection (mirroring Android's live markers on lite maps), so async
18
+ * marker content (e.g. remote images) still updates when it loads.
19
+ * Polylines, polygons, circles and ground overlays are drawn into a
20
+ * single CoreGraphics overlay view. All overlays stay hidden over the
21
+ * placeholder and reveal together with the base map. Tile overlays are
22
+ * not supported.
23
+ */
24
+ @interface StaticMapProviderBase : NSObject <MapProvider>
25
+
26
+ /// Previously captured base map for the same cache key; when set, the base
27
+ /// render is skipped and the image is shown immediately.
28
+ @property(nonatomic, strong, nullable) UIImage *cachedBaseImage;
29
+
30
+ // State for subclasses
31
+ @property(nonatomic, readonly, nullable) UIView *wrapperView;
32
+ @property(nonatomic, readonly) CLLocationCoordinate2D coordinate;
33
+ @property(nonatomic, readonly) double zoom;
34
+ @property(nonatomic, readonly) MKMapRect mapRect;
35
+ @property(nonatomic, readonly) CGSize projectedSize;
36
+ @property(nonatomic, readonly) BOOL projectionReady;
37
+ @property(nonatomic, readonly) facebook::react::LuggMapViewMapType mapType;
38
+ @property(nonatomic, readonly) facebook::react::LuggMapViewTheme theme;
39
+
40
+ /// Shows the base image below the overlays, emits the static snapshot
41
+ /// delegate callbacks and marks the base render done.
42
+ - (void)displayBaseImage:(UIImage *)image fromCache:(BOOL)fromCache;
43
+
44
+ /// Shows the marker and shape overlays (idempotent). Called automatically
45
+ /// when the base image displays; subclasses whose base map is already
46
+ /// visible earlier (e.g. a live warmup map) can call it sooner.
47
+ - (void)revealOverlays;
48
+
49
+ #pragma mark - Subclass hooks
50
+
51
+ /// The map rect the base render covers for the given view size; must match
52
+ /// the SDK's camera framing so overlays align with the base map exactly.
53
+ - (MKMapRect)mapRectForSize:(CGSize)size;
54
+
55
+ /// Overlay view shown for markers without a custom child view.
56
+ - (UIView *)newDefaultMarkerOverlayView;
57
+
58
+ /// Starts rendering the base map for the current projection (guaranteed
59
+ /// projectionReady, no cached image). Called after mount (once initial
60
+ /// props have applied), after a resize, and from resumeAnimations to retry
61
+ /// a failed or cancelled render (e.g. device back online).
62
+ - (void)renderBaseMap;
63
+
64
+ /// Cancels an in-flight base render (resize or teardown).
65
+ - (void)cancelBaseRender;
66
+
67
+ @end
68
+
69
+ NS_ASSUME_NONNULL_END