@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.
- package/README.md +15 -100
- package/android/src/main/java/com/luggmaps/LuggCalloutView.kt +5 -0
- package/android/src/main/java/com/luggmaps/LuggMapView.kt +56 -2
- package/android/src/main/java/com/luggmaps/LuggMapViewManager.kt +18 -0
- package/android/src/main/java/com/luggmaps/LuggMapWrapperView.kt +36 -2
- package/android/src/main/java/com/luggmaps/LuggMarkerView.kt +7 -2
- package/android/src/main/java/com/luggmaps/LuggPolylineView.kt +2 -1
- package/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +332 -42
- package/android/src/main/java/com/luggmaps/core/MapProviderDelegate.kt +6 -0
- package/android/src/main/java/com/luggmaps/core/PolylineAnimator.kt +61 -10
- package/ios/LuggMapView.mm +240 -10
- package/ios/LuggMapWrapperView.h +5 -0
- package/ios/LuggMapWrapperView.mm +18 -0
- package/ios/LuggMarkerView.mm +2 -0
- package/ios/core/AppleMapProvider.h +8 -0
- package/ios/core/AppleMapProvider.mm +63 -47
- package/ios/core/AppleStaticMapProvider.h +14 -0
- package/ios/core/AppleStaticMapProvider.mm +233 -0
- package/ios/core/GMSPolylineAnimator.m +40 -15
- package/ios/core/GoogleMapProvider.h +10 -0
- package/ios/core/GoogleMapProvider.mm +67 -54
- package/ios/core/GoogleStaticMapProvider.h +21 -0
- package/ios/core/GoogleStaticMapProvider.mm +217 -0
- package/ios/core/MKPolylineAnimator.m +67 -43
- package/ios/core/MapEdgeInsets.h +18 -0
- package/ios/core/MapProviderDelegate.h +11 -0
- package/ios/core/StaticMapProviderBase.h +80 -0
- package/ios/core/StaticMapProviderBase.mm +847 -0
- package/ios/events/CameraIdleEvent.h +4 -2
- package/ios/events/CameraMoveEvent.h +4 -2
- package/ios/events/CirclePressEvent.h +4 -2
- package/ios/events/GroundOverlayPressEvent.h +4 -2
- package/ios/events/LongPressEvent.h +4 -2
- package/ios/events/MarkerDragEvent.h +12 -6
- package/ios/events/MarkerPressEvent.h +4 -2
- package/ios/events/PolygonPressEvent.h +4 -2
- package/ios/events/PressEvent.h +4 -2
- package/ios/events/ReadyEvent.h +4 -2
- package/ios/events/TileOverlayPressEvent.h +4 -2
- package/ios/extensions/MKMapView+Zoom.h +20 -0
- package/ios/extensions/MKMapView+Zoom.m +28 -18
- package/ios/extensions/UIView+Snapshot.h +16 -0
- package/ios/extensions/UIView+Snapshot.m +27 -0
- package/lib/module/MapView.js +12 -0
- package/lib/module/MapView.js.map +1 -1
- package/lib/module/MapView.web.js +29 -10
- package/lib/module/MapView.web.js.map +1 -1
- package/lib/module/fabric/LuggMapViewNativeComponent.ts +10 -1
- package/lib/typescript/src/MapView.d.ts +1 -0
- package/lib/typescript/src/MapView.d.ts.map +1 -1
- package/lib/typescript/src/MapView.types.d.ts +32 -0
- package/lib/typescript/src/MapView.types.d.ts.map +1 -1
- package/lib/typescript/src/MapView.web.d.ts.map +1 -1
- package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts +4 -0
- package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts.map +1 -1
- package/package.json +24 -14
- package/src/MapView.tsx +14 -0
- package/src/MapView.types.ts +32 -0
- package/src/MapView.web.tsx +39 -14
- package/src/fabric/LuggMapViewNativeComponent.ts +10 -1
- package/plugin/build/withMapsAndroid.d.ts +0 -5
- package/plugin/build/withMapsAndroid.js +0 -15
- package/plugin/build/withMapsIOS.d.ts +0 -5
- package/plugin/build/withMapsIOS.js +0 -27
|
@@ -16,18 +16,43 @@ class PolylineAnimator {
|
|
|
16
16
|
companion object {
|
|
17
17
|
private const val MAX_ANIMATION_SPANS = 16
|
|
18
18
|
private const val MAX_GRADIENT_SPANS = 512
|
|
19
|
+
|
|
20
|
+
// Cap polyline updates to ~60fps; every update crosses Binder into the
|
|
21
|
+
// maps renderer, and 120Hz displays double that churn (CONSUMER-APP-44Y)
|
|
22
|
+
private const val MIN_UPDATE_INTERVAL_NANOS = 16_000_000L
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
var polyline: Polyline? = null
|
|
22
26
|
var coordinates: List<LatLng> = emptyList()
|
|
23
27
|
set(value) {
|
|
28
|
+
// Structural equality: Fabric delivers a new list instance on every
|
|
29
|
+
// commit, so identity checks never match and the full route would be
|
|
30
|
+
// re-parceled across Binder even when unchanged
|
|
31
|
+
if (field == value) return
|
|
24
32
|
field = value
|
|
33
|
+
dirty = true
|
|
25
34
|
if (animated && animator != null) {
|
|
26
35
|
computeCumulativeDistances()
|
|
27
36
|
}
|
|
28
37
|
}
|
|
29
38
|
var strokeColors: List<Int> = listOf(Color.BLACK)
|
|
39
|
+
set(value) {
|
|
40
|
+
if (field == value) return
|
|
41
|
+
field = value
|
|
42
|
+
dirty = true
|
|
43
|
+
}
|
|
30
44
|
var strokeWidth: Float = 1f
|
|
45
|
+
set(value) {
|
|
46
|
+
if (field == value) return
|
|
47
|
+
field = value
|
|
48
|
+
polyline?.width = value
|
|
49
|
+
}
|
|
50
|
+
var zIndex: Float = 0f
|
|
51
|
+
set(value) {
|
|
52
|
+
if (field == value) return
|
|
53
|
+
field = value
|
|
54
|
+
polyline?.zIndex = value
|
|
55
|
+
}
|
|
31
56
|
var animatedOptions: AnimatedOptions = AnimatedOptions()
|
|
32
57
|
set(value) {
|
|
33
58
|
if (field == value) return
|
|
@@ -51,8 +76,11 @@ class PolylineAnimator {
|
|
|
51
76
|
|
|
52
77
|
private var animator: ValueAnimator? = null
|
|
53
78
|
private var animationProgress: Float = 0f
|
|
79
|
+
private var lastUpdateNanos: Long = 0L
|
|
80
|
+
private var lastAppliedColor: Int? = null
|
|
54
81
|
private var cumulativeDistances: FloatArray = floatArrayOf()
|
|
55
82
|
private var totalLength: Float = 0f
|
|
83
|
+
private var dirty: Boolean = true
|
|
56
84
|
|
|
57
85
|
// Reusable collections to avoid per-frame allocations
|
|
58
86
|
private val reusablePoints = ArrayList<LatLng>()
|
|
@@ -73,16 +101,21 @@ class PolylineAnimator {
|
|
|
73
101
|
|
|
74
102
|
fun update() {
|
|
75
103
|
if (animated) return
|
|
104
|
+
if (!dirty) return
|
|
76
105
|
|
|
77
106
|
val poly = polyline ?: return
|
|
78
107
|
if (coordinates.size < 2) return
|
|
79
108
|
|
|
109
|
+
dirty = false
|
|
80
110
|
poly.points = coordinates
|
|
81
111
|
|
|
82
112
|
if (strokeColors.size > 1) {
|
|
83
|
-
poly
|
|
113
|
+
applyGradientSpans(poly)
|
|
114
|
+
lastAppliedColor = null
|
|
84
115
|
} else {
|
|
85
|
-
|
|
116
|
+
val color = strokeColors.firstOrNull() ?: Color.BLACK
|
|
117
|
+
poly.color = color
|
|
118
|
+
lastAppliedColor = color
|
|
86
119
|
}
|
|
87
120
|
}
|
|
88
121
|
|
|
@@ -101,6 +134,9 @@ class PolylineAnimator {
|
|
|
101
134
|
interpolator = LinearInterpolator()
|
|
102
135
|
addUpdateListener { animation ->
|
|
103
136
|
animationProgress = animation.animatedValue as Float
|
|
137
|
+
val now = System.nanoTime()
|
|
138
|
+
if (now - lastUpdateNanos < MIN_UPDATE_INTERVAL_NANOS) return@addUpdateListener
|
|
139
|
+
lastUpdateNanos = now
|
|
104
140
|
updateAnimatedPolyline()
|
|
105
141
|
}
|
|
106
142
|
start()
|
|
@@ -172,6 +208,7 @@ class PolylineAnimator {
|
|
|
172
208
|
private fun stopAnimation() {
|
|
173
209
|
animator?.cancel()
|
|
174
210
|
animator = null
|
|
211
|
+
lastUpdateNanos = 0L
|
|
175
212
|
}
|
|
176
213
|
|
|
177
214
|
fun pause() {
|
|
@@ -210,8 +247,10 @@ class PolylineAnimator {
|
|
|
210
247
|
}
|
|
211
248
|
|
|
212
249
|
if (headDist <= tailDist) {
|
|
213
|
-
|
|
250
|
+
val point = coordinates.firstOrNull() ?: LatLng(0.0, 0.0)
|
|
214
251
|
poly.color = strokeColors.firstOrNull() ?: Color.BLACK
|
|
252
|
+
lastAppliedColor = null
|
|
253
|
+
poly.points = listOf(point, point)
|
|
215
254
|
return
|
|
216
255
|
}
|
|
217
256
|
|
|
@@ -235,11 +274,13 @@ class PolylineAnimator {
|
|
|
235
274
|
|
|
236
275
|
if (reusablePoints.size < 2) return
|
|
237
276
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
277
|
+
if (strokeColors.size > 1) {
|
|
278
|
+
// Clear spans before setting points to prevent IndexOutOfBoundsException
|
|
279
|
+
// when the new points list is shorter than what existing spans reference
|
|
241
280
|
poly.color = strokeColors.firstOrNull() ?: Color.BLACK
|
|
242
|
-
|
|
281
|
+
lastAppliedColor = null
|
|
282
|
+
poly.points = reusablePoints
|
|
283
|
+
|
|
243
284
|
val segmentCount = reusablePoints.size - 1
|
|
244
285
|
val spanCount = min(segmentCount, MAX_ANIMATION_SPANS)
|
|
245
286
|
val segmentsPerSpan = segmentCount.toDouble() / spanCount
|
|
@@ -251,18 +292,28 @@ class PolylineAnimator {
|
|
|
251
292
|
reusableSpans.add(StyleSpan(StrokeStyle.colorBuilder(color).build(), segmentsPerSpan))
|
|
252
293
|
}
|
|
253
294
|
poly.setSpans(reusableSpans)
|
|
295
|
+
} else {
|
|
296
|
+
val color = strokeColors.firstOrNull() ?: Color.BLACK
|
|
297
|
+
if (lastAppliedColor != color) {
|
|
298
|
+
poly.color = color
|
|
299
|
+
lastAppliedColor = color
|
|
300
|
+
}
|
|
301
|
+
poly.points = reusablePoints
|
|
254
302
|
}
|
|
255
303
|
}
|
|
256
304
|
|
|
257
|
-
private fun
|
|
305
|
+
private fun applyGradientSpans(poly: Polyline) {
|
|
258
306
|
val segmentCount = coordinates.size - 1
|
|
259
307
|
val spanCount = min(segmentCount, MAX_GRADIENT_SPANS)
|
|
260
308
|
val segmentsPerSpan = segmentCount.toDouble() / spanCount
|
|
261
|
-
|
|
309
|
+
|
|
310
|
+
reusableSpans.clear()
|
|
311
|
+
for (i in 0 until spanCount) {
|
|
262
312
|
val position = (i + 0.5f) / spanCount
|
|
263
313
|
val color = colorAtGradientPosition(position)
|
|
264
|
-
StyleSpan(StrokeStyle.colorBuilder(color).build(), segmentsPerSpan)
|
|
314
|
+
reusableSpans.add(StyleSpan(StrokeStyle.colorBuilder(color).build(), segmentsPerSpan))
|
|
265
315
|
}
|
|
316
|
+
poly.setSpans(reusableSpans)
|
|
266
317
|
}
|
|
267
318
|
|
|
268
319
|
private fun colorAtGradientPosition(position: Float): Int {
|
package/ios/LuggMapView.mm
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
#import "LuggPolylineView.h"
|
|
8
8
|
#import "LuggTileOverlayView.h"
|
|
9
9
|
#import "core/AppleMapProvider.h"
|
|
10
|
+
#import "core/AppleStaticMapProvider.h"
|
|
10
11
|
#import "core/GoogleMapProvider.h"
|
|
12
|
+
#import "core/GoogleStaticMapProvider.h"
|
|
11
13
|
#import "core/MapProviderDelegate.h"
|
|
12
14
|
#import "events/CameraIdleEvent.h"
|
|
13
15
|
#import "events/CameraMoveEvent.h"
|
|
@@ -25,6 +27,19 @@
|
|
|
25
27
|
using namespace facebook::react;
|
|
26
28
|
using namespace luggmaps::events;
|
|
27
29
|
|
|
30
|
+
// Static snapshots cached across view recycling, keyed by the user-provided
|
|
31
|
+
// staticKey plus the map settings that affect appearance.
|
|
32
|
+
static NSCache<NSString *, UIImage *> *StaticSnapshotCache(void) {
|
|
33
|
+
static NSCache<NSString *, UIImage *> *cache;
|
|
34
|
+
static dispatch_once_t onceToken;
|
|
35
|
+
dispatch_once(&onceToken, ^{
|
|
36
|
+
cache = [[NSCache alloc] init];
|
|
37
|
+
cache.countLimit = 100;
|
|
38
|
+
cache.totalCostLimit = 64 * 1024 * 1024; // bytes
|
|
39
|
+
});
|
|
40
|
+
return cache;
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
@interface LuggMapView () <RCTLuggMapViewViewProtocol, MapProviderDelegate>
|
|
29
44
|
@end
|
|
30
45
|
|
|
@@ -39,6 +54,10 @@ using namespace luggmaps::events;
|
|
|
39
54
|
BOOL _scrollEnabled;
|
|
40
55
|
BOOL _rotateEnabled;
|
|
41
56
|
BOOL _pitchEnabled;
|
|
57
|
+
BOOL _compassEnabled;
|
|
58
|
+
BOOL _staticMode;
|
|
59
|
+
NSString *_staticKey;
|
|
60
|
+
BOOL _staticSnapshotDone;
|
|
42
61
|
BOOL _userLocationEnabled;
|
|
43
62
|
LuggMapViewMapType _mapType;
|
|
44
63
|
LuggMapViewTheme _theme;
|
|
@@ -68,6 +87,9 @@ using namespace luggmaps::events;
|
|
|
68
87
|
_scrollEnabled = YES;
|
|
69
88
|
_rotateEnabled = YES;
|
|
70
89
|
_pitchEnabled = YES;
|
|
90
|
+
_compassEnabled = YES;
|
|
91
|
+
_staticMode = NO;
|
|
92
|
+
_staticKey = @"";
|
|
71
93
|
_userLocationEnabled = NO;
|
|
72
94
|
_poiEnabled = NO;
|
|
73
95
|
_poiFilterMode = LuggMapViewPoiFilterMode::Including;
|
|
@@ -168,17 +190,118 @@ using namespace luggmaps::events;
|
|
|
168
190
|
[super didMoveToWindow];
|
|
169
191
|
if (self.window) {
|
|
170
192
|
if (!_provider && _mapWrapperView) {
|
|
171
|
-
|
|
193
|
+
if (_staticMode) {
|
|
194
|
+
[self startStaticContent];
|
|
195
|
+
} else {
|
|
196
|
+
[self initializeProvider];
|
|
197
|
+
}
|
|
172
198
|
}
|
|
173
199
|
[_provider resumeAnimations];
|
|
174
200
|
} else {
|
|
201
|
+
// A static map mid-warmup may hold an expensive live map view. Destroy
|
|
202
|
+
// it off-window and re-render on return instead of letting paused maps
|
|
203
|
+
// pile up across screen switches and all resume at once
|
|
204
|
+
if (_staticMode && _provider && !_staticSnapshotDone) {
|
|
205
|
+
[_provider destroy];
|
|
206
|
+
_provider = nil;
|
|
207
|
+
_initialized = NO;
|
|
208
|
+
}
|
|
175
209
|
[_provider pauseAnimations];
|
|
176
210
|
}
|
|
177
211
|
}
|
|
178
212
|
|
|
213
|
+
// Shown while the map warms up, until the live map or snapshot covers it
|
|
214
|
+
- (void)applyStaticPlaceholderBackground {
|
|
215
|
+
const auto &viewProps =
|
|
216
|
+
*std::static_pointer_cast<LuggMapViewProps const>(_props);
|
|
217
|
+
if (viewProps.backgroundColor) {
|
|
218
|
+
// A user-provided background acts as the placeholder
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
switch (_theme) {
|
|
223
|
+
case LuggMapViewTheme::Dark:
|
|
224
|
+
_mapWrapperView.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
|
|
225
|
+
break;
|
|
226
|
+
case LuggMapViewTheme::Light:
|
|
227
|
+
_mapWrapperView.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
|
|
228
|
+
break;
|
|
229
|
+
default:
|
|
230
|
+
_mapWrapperView.overrideUserInterfaceStyle =
|
|
231
|
+
UIUserInterfaceStyleUnspecified;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
_mapWrapperView.backgroundColor = UIColor.secondarySystemBackgroundColor;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
- (nullable NSString *)staticSnapshotCacheKey {
|
|
238
|
+
if (_staticKey.length == 0 || !_mapWrapperView)
|
|
239
|
+
return nil;
|
|
240
|
+
|
|
241
|
+
CGSize size = _mapWrapperView.bounds.size;
|
|
242
|
+
if (size.width <= 0 || size.height <= 0)
|
|
243
|
+
return nil;
|
|
244
|
+
|
|
245
|
+
// 'system' theme snapshots depend on the current appearance
|
|
246
|
+
NSInteger style = _theme == LuggMapViewTheme::System
|
|
247
|
+
? (NSInteger)self.traitCollection.userInterfaceStyle
|
|
248
|
+
: -(NSInteger)_theme;
|
|
249
|
+
|
|
250
|
+
// Camera is part of the key so a reused staticKey with a different
|
|
251
|
+
// location/zoom can't serve a stale snapshot. The provider's camera (when
|
|
252
|
+
// available) reflects imperative camera changes, so a re-rendered
|
|
253
|
+
// snapshot is keyed on what it actually shows.
|
|
254
|
+
const auto &viewProps =
|
|
255
|
+
*std::static_pointer_cast<LuggMapViewProps const>(_props);
|
|
256
|
+
double latitude = viewProps.initialCoordinate.latitude;
|
|
257
|
+
double longitude = viewProps.initialCoordinate.longitude;
|
|
258
|
+
double zoom = viewProps.initialZoom;
|
|
259
|
+
if ([_provider isKindOfClass:[StaticMapProviderBase class]]) {
|
|
260
|
+
StaticMapProviderBase *staticProvider = (StaticMapProviderBase *)_provider;
|
|
261
|
+
latitude = staticProvider.coordinate.latitude;
|
|
262
|
+
longitude = staticProvider.coordinate.longitude;
|
|
263
|
+
zoom = staticProvider.zoom;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return [NSString
|
|
267
|
+
stringWithFormat:@"%@|%d|%@|%d|%ld|%.0fx%.0f|%.6f,%.6f,%.2f|%@",
|
|
268
|
+
_staticKey, (int)_providerType, _mapId, (int)_mapType,
|
|
269
|
+
(long)style, size.width, size.height, latitude,
|
|
270
|
+
longitude, zoom, NSStringFromUIEdgeInsets(_edgeInsets)];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
- (void)startStaticContent {
|
|
274
|
+
[self applyStaticPlaceholderBackground];
|
|
275
|
+
|
|
276
|
+
// Both static providers render fully async: the placeholder shows until
|
|
277
|
+
// the snapshotter (Apple) or a brief live-map warmup (Google) delivers
|
|
278
|
+
// the base map, which reveals together with the marker/shape overlays
|
|
279
|
+
[self initializeProvider];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// staticKey identifies the map's content; when it changes (e.g. a recycled
|
|
283
|
+
// list row bound to a new item), any live warmup or displayed snapshot is
|
|
284
|
+
// stale and the static lifecycle restarts. Without this, an in-flight
|
|
285
|
+
// warmup caches the old item's map under the new key.
|
|
286
|
+
- (void)resetStaticContent {
|
|
287
|
+
_staticSnapshotDone = NO;
|
|
288
|
+
|
|
289
|
+
if (_provider) {
|
|
290
|
+
[_provider destroy];
|
|
291
|
+
_provider = nil;
|
|
292
|
+
_initialized = NO;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (self.window) {
|
|
296
|
+
[self startStaticContent];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
179
300
|
- (void)prepareForRecycle {
|
|
180
301
|
[super prepareForRecycle];
|
|
181
302
|
|
|
303
|
+
_staticSnapshotDone = NO;
|
|
304
|
+
|
|
182
305
|
[_provider destroy];
|
|
183
306
|
_provider = nil;
|
|
184
307
|
_mapWrapperView = nil;
|
|
@@ -188,14 +311,28 @@ using namespace luggmaps::events;
|
|
|
188
311
|
#pragma mark - Provider Initialization
|
|
189
312
|
|
|
190
313
|
- (void)initializeProvider {
|
|
191
|
-
if (_provider || !_mapWrapperView)
|
|
192
|
-
return;
|
|
193
|
-
|
|
194
314
|
const auto &viewProps =
|
|
195
315
|
*std::static_pointer_cast<LuggMapViewProps const>(_props);
|
|
316
|
+
[self
|
|
317
|
+
initializeProviderWithCoordinate:CLLocationCoordinate2DMake(
|
|
318
|
+
viewProps.initialCoordinate.latitude,
|
|
319
|
+
viewProps.initialCoordinate
|
|
320
|
+
.longitude)
|
|
321
|
+
zoom:viewProps.initialZoom];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
- (void)initializeProviderWithCoordinate:(CLLocationCoordinate2D)coordinate
|
|
325
|
+
zoom:(double)zoom {
|
|
326
|
+
if (_provider || !_mapWrapperView)
|
|
327
|
+
return;
|
|
196
328
|
|
|
197
329
|
if (_providerType == LuggMapViewProvider::Apple) {
|
|
198
|
-
_provider = [[
|
|
330
|
+
_provider = _staticMode ? [[AppleStaticMapProvider alloc] init]
|
|
331
|
+
: [[AppleMapProvider alloc] init];
|
|
332
|
+
} else if (_staticMode) {
|
|
333
|
+
GoogleStaticMapProvider *google = [[GoogleStaticMapProvider alloc] init];
|
|
334
|
+
google.mapId = _mapId;
|
|
335
|
+
_provider = google;
|
|
199
336
|
} else {
|
|
200
337
|
GoogleMapProvider *google = [[GoogleMapProvider alloc] init];
|
|
201
338
|
google.mapId = _mapId;
|
|
@@ -203,14 +340,21 @@ using namespace luggmaps::events;
|
|
|
203
340
|
}
|
|
204
341
|
|
|
205
342
|
_provider.delegate = self;
|
|
206
|
-
|
|
207
|
-
CLLocationCoordinate2D coordinate =
|
|
208
|
-
CLLocationCoordinate2DMake(viewProps.initialCoordinate.latitude,
|
|
209
|
-
viewProps.initialCoordinate.longitude);
|
|
343
|
+
_provider.staticMode = _staticMode;
|
|
210
344
|
|
|
211
345
|
[_provider initializeMapInView:_mapWrapperView
|
|
212
346
|
initialCoordinate:coordinate
|
|
213
|
-
initialZoom:
|
|
347
|
+
initialZoom:zoom];
|
|
348
|
+
|
|
349
|
+
// After initializeMapInView so the cache key reads the provider's camera;
|
|
350
|
+
// the base render is async and picks up the cached image
|
|
351
|
+
if (_staticMode) {
|
|
352
|
+
NSString *cacheKey = [self staticSnapshotCacheKey];
|
|
353
|
+
if (cacheKey) {
|
|
354
|
+
((StaticMapProviderBase *)_provider).cachedBaseImage =
|
|
355
|
+
[StaticSnapshotCache() objectForKey:cacheKey];
|
|
356
|
+
}
|
|
357
|
+
}
|
|
214
358
|
|
|
215
359
|
// Apply cached props after map view is created
|
|
216
360
|
[self applyProps];
|
|
@@ -242,6 +386,19 @@ using namespace luggmaps::events;
|
|
|
242
386
|
ReadyEvent::emit<LuggMapViewEventEmitter>(_eventEmitter);
|
|
243
387
|
}
|
|
244
388
|
|
|
389
|
+
- (void)mapProviderDidFinishStaticSnapshot {
|
|
390
|
+
_staticSnapshotDone = YES;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
- (void)mapProviderDidCaptureStaticImage:(UIImage *)image {
|
|
394
|
+
NSString *key = [self staticSnapshotCacheKey];
|
|
395
|
+
if (key) {
|
|
396
|
+
NSUInteger cost = (NSUInteger)(image.size.width * image.scale *
|
|
397
|
+
image.size.height * image.scale * 4);
|
|
398
|
+
[StaticSnapshotCache() setObject:image forKey:key cost:cost];
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
245
402
|
- (void)mapProviderDidMoveCamera:(double)latitude
|
|
246
403
|
longitude:(double)longitude
|
|
247
404
|
zoom:(double)zoom
|
|
@@ -281,6 +438,7 @@ using namespace luggmaps::events;
|
|
|
281
438
|
[_provider setScrollEnabled:_scrollEnabled];
|
|
282
439
|
[_provider setRotateEnabled:_rotateEnabled];
|
|
283
440
|
[_provider setPitchEnabled:_pitchEnabled];
|
|
441
|
+
[_provider setCompassEnabled:_compassEnabled];
|
|
284
442
|
[_provider setUserLocationEnabled:_userLocationEnabled];
|
|
285
443
|
[_provider setMapType:_mapType];
|
|
286
444
|
[_provider setTheme:_theme];
|
|
@@ -300,6 +458,12 @@ using namespace luggmaps::events;
|
|
|
300
458
|
*std::static_pointer_cast<LuggMapViewProps const>(_props);
|
|
301
459
|
|
|
302
460
|
_providerType = newViewProps.provider;
|
|
461
|
+
_staticMode = newViewProps.staticMode;
|
|
462
|
+
|
|
463
|
+
NSString *newStaticKey =
|
|
464
|
+
[NSString stringWithUTF8String:newViewProps.staticKey.c_str()];
|
|
465
|
+
BOOL staticKeyChanged = ![newStaticKey isEqualToString:_staticKey];
|
|
466
|
+
_staticKey = newStaticKey;
|
|
303
467
|
|
|
304
468
|
NSString *newMapId =
|
|
305
469
|
[NSString stringWithUTF8String:newViewProps.mapId.c_str()];
|
|
@@ -323,6 +487,10 @@ using namespace luggmaps::events;
|
|
|
323
487
|
_pitchEnabled = newViewProps.pitchEnabled;
|
|
324
488
|
[_provider setPitchEnabled:_pitchEnabled];
|
|
325
489
|
}
|
|
490
|
+
if (newViewProps.compassEnabled != prevViewProps.compassEnabled) {
|
|
491
|
+
_compassEnabled = newViewProps.compassEnabled;
|
|
492
|
+
[_provider setCompassEnabled:_compassEnabled];
|
|
493
|
+
}
|
|
326
494
|
if (newViewProps.userLocationEnabled != prevViewProps.userLocationEnabled) {
|
|
327
495
|
_userLocationEnabled = newViewProps.userLocationEnabled;
|
|
328
496
|
[_provider setUserLocationEnabled:_userLocationEnabled];
|
|
@@ -374,6 +542,11 @@ using namespace luggmaps::events;
|
|
|
374
542
|
}
|
|
375
543
|
|
|
376
544
|
[super updateProps:props oldProps:oldProps];
|
|
545
|
+
|
|
546
|
+
// After super so the snapshot cache key sees the new camera props
|
|
547
|
+
if (staticKeyChanged && _staticMode && _mapWrapperView) {
|
|
548
|
+
[self resetStaticContent];
|
|
549
|
+
}
|
|
377
550
|
}
|
|
378
551
|
|
|
379
552
|
#pragma mark - Commands
|
|
@@ -414,6 +587,63 @@ using namespace luggmaps::events;
|
|
|
414
587
|
duration:duration];
|
|
415
588
|
}
|
|
416
589
|
|
|
590
|
+
// Loads the map again, e.g. to recover from missing tiles. A static map
|
|
591
|
+
// drops its cached snapshot and renders the base map again in place.
|
|
592
|
+
// Neither SDK can reload a live map's tiles, so the native map view is
|
|
593
|
+
// recreated at the current camera (coordinate and zoom; heading and pitch
|
|
594
|
+
// reset) with children re-added.
|
|
595
|
+
- (void)reload {
|
|
596
|
+
if (_staticMode) {
|
|
597
|
+
_staticSnapshotDone = NO;
|
|
598
|
+
NSString *cacheKey = [self staticSnapshotCacheKey];
|
|
599
|
+
if (cacheKey) {
|
|
600
|
+
[StaticSnapshotCache() removeObjectForKey:cacheKey];
|
|
601
|
+
}
|
|
602
|
+
if ([_provider isKindOfClass:[StaticMapProviderBase class]]) {
|
|
603
|
+
[(StaticMapProviderBase *)_provider rerenderBaseMap];
|
|
604
|
+
}
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (!_provider)
|
|
609
|
+
return;
|
|
610
|
+
|
|
611
|
+
CLLocationCoordinate2D coordinate = _provider.coordinate;
|
|
612
|
+
double zoom = _provider.zoom;
|
|
613
|
+
// Children keep their native marker/overlay objects from the old map;
|
|
614
|
+
// detach them so the new provider creates fresh ones on the flush
|
|
615
|
+
[self removeChildrenFromProvider];
|
|
616
|
+
[_provider destroy];
|
|
617
|
+
_provider = nil;
|
|
618
|
+
_initialized = NO;
|
|
619
|
+
[self initializeProviderWithCoordinate:coordinate zoom:zoom];
|
|
620
|
+
if (_providerType == LuggMapViewProvider::Apple) {
|
|
621
|
+
// The captured camera already includes the previous inset offset.
|
|
622
|
+
[_provider moveCamera:coordinate.latitude
|
|
623
|
+
longitude:coordinate.longitude
|
|
624
|
+
zoom:zoom
|
|
625
|
+
duration:0];
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
- (void)removeChildrenFromProvider {
|
|
630
|
+
for (UIView *subview in self.subviews) {
|
|
631
|
+
if ([subview isKindOfClass:[LuggMarkerView class]]) {
|
|
632
|
+
[_provider removeMarkerView:(LuggMarkerView *)subview];
|
|
633
|
+
} else if ([subview isKindOfClass:[LuggPolylineView class]]) {
|
|
634
|
+
[_provider removePolylineView:(LuggPolylineView *)subview];
|
|
635
|
+
} else if ([subview isKindOfClass:[LuggPolygonView class]]) {
|
|
636
|
+
[_provider removePolygonView:(LuggPolygonView *)subview];
|
|
637
|
+
} else if ([subview isKindOfClass:[LuggCircleView class]]) {
|
|
638
|
+
[_provider removeCircleView:(LuggCircleView *)subview];
|
|
639
|
+
} else if ([subview isKindOfClass:[LuggGroundOverlayView class]]) {
|
|
640
|
+
[_provider removeGroundOverlayView:(LuggGroundOverlayView *)subview];
|
|
641
|
+
} else if ([subview isKindOfClass:[LuggTileOverlayView class]]) {
|
|
642
|
+
[_provider removeTileOverlayView:(LuggTileOverlayView *)subview];
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
417
647
|
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
|
|
418
648
|
RCTLuggMapViewHandleCommand(self, commandName, args);
|
|
419
649
|
}
|
package/ios/LuggMapWrapperView.h
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
NS_ASSUME_NONNULL_BEGIN
|
|
5
5
|
|
|
6
6
|
@interface LuggMapWrapperView : RCTViewComponentView
|
|
7
|
+
|
|
8
|
+
/// Called after layout; lets the static snapshotter start (or restart)
|
|
9
|
+
/// once the wrapper has a real size, since a snapshot can't autoresize
|
|
10
|
+
@property(nonatomic, copy, nullable) void (^layoutHandler)(void);
|
|
11
|
+
|
|
7
12
|
@end
|
|
8
13
|
|
|
9
14
|
NS_ASSUME_NONNULL_END
|
|
@@ -29,6 +29,24 @@ using namespace facebook::react;
|
|
|
29
29
|
return self;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
- (void)layoutSubviews {
|
|
33
|
+
[super layoutSubviews];
|
|
34
|
+
if (self.layoutHandler) {
|
|
35
|
+
self.layoutHandler();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
- (void)prepareForRecycle {
|
|
40
|
+
[super prepareForRecycle];
|
|
41
|
+
|
|
42
|
+
// Static placeholder styling is applied outside of props
|
|
43
|
+
// (LuggMapView applyStaticPlaceholderBackground) and would otherwise
|
|
44
|
+
// leak into the next map that reuses this recycled view
|
|
45
|
+
self.backgroundColor = nil;
|
|
46
|
+
self.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
|
|
47
|
+
self.layoutHandler = nil;
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
Class<RCTComponentViewProtocol> LuggMapWrapperViewCls(void) {
|
|
33
51
|
return LuggMapWrapperView.class;
|
|
34
52
|
}
|
package/ios/LuggMarkerView.mm
CHANGED
|
@@ -287,6 +287,8 @@ using namespace luggmaps::events;
|
|
|
287
287
|
_iconView.layer.anchorPoint = CGPointMake(0.5, 0.5);
|
|
288
288
|
_iconView.frame = CGRectMake(0, 0, _iconView.bounds.size.width,
|
|
289
289
|
_iconView.bounds.size.height);
|
|
290
|
+
// Static providers hide the icon view until positioned
|
|
291
|
+
_iconView.hidden = NO;
|
|
290
292
|
}
|
|
291
293
|
|
|
292
294
|
- (void)showCallout {
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
NS_ASSUME_NONNULL_BEGIN
|
|
5
5
|
|
|
6
|
+
/// POI filter for the given props, shared by the live map view and the
|
|
7
|
+
/// static snapshotter. Returns nil for "show everything".
|
|
8
|
+
MKPointOfInterestFilter *_Nullable LuggPointOfInterestFilter(
|
|
9
|
+
BOOL poiEnabled, facebook::react::LuggMapViewPoiFilterMode filterMode,
|
|
10
|
+
NSArray<NSString *> *filterCategories);
|
|
11
|
+
|
|
12
|
+
MKMapType LuggMKMapTypeFromMapType(facebook::react::LuggMapViewMapType mapType);
|
|
13
|
+
|
|
6
14
|
@interface LuggAppleMapViewContent : MKMapView
|
|
7
15
|
@end
|
|
8
16
|
|