@lugg/maps 1.0.0-beta.9 → 1.0.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 +41 -0
- package/android/src/main/java/com/luggmaps/LuggMapViewManager.kt +14 -0
- package/android/src/main/java/com/luggmaps/LuggMapWrapperView.kt +27 -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 +307 -41
- package/android/src/main/java/com/luggmaps/core/MapProviderDelegate.kt +1 -0
- package/android/src/main/java/com/luggmaps/core/PolylineAnimator.kt +61 -10
- package/ios/LuggMapView.mm +170 -2
- 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 +48 -31
- package/ios/core/AppleStaticMapProvider.h +14 -0
- package/ios/core/AppleStaticMapProvider.mm +159 -0
- package/ios/core/GMSPolylineAnimator.m +40 -15
- package/ios/core/GoogleMapProvider.h +10 -0
- package/ios/core/GoogleMapProvider.mm +56 -44
- package/ios/core/GoogleStaticMapProvider.h +21 -0
- package/ios/core/GoogleStaticMapProvider.mm +213 -0
- package/ios/core/MKPolylineAnimator.m +67 -43
- package/ios/core/MapProviderDelegate.h +7 -0
- package/ios/core/StaticMapProviderBase.h +75 -0
- package/ios/core/StaticMapProviderBase.mm +845 -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 +7 -0
- package/lib/module/MapView.js.map +1 -1
- package/lib/module/MapView.web.js +9 -4
- package/lib/module/MapView.web.js.map +1 -1
- package/lib/module/fabric/LuggMapViewNativeComponent.ts +3 -0
- package/lib/typescript/src/MapView.d.ts.map +1 -1
- package/lib/typescript/src/MapView.types.d.ts +24 -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 +3 -0
- package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts.map +1 -1
- package/package.json +19 -12
- package/src/MapView.tsx +7 -0
- package/src/MapView.types.ts +24 -0
- package/src/MapView.web.tsx +15 -9
- package/src/fabric/LuggMapViewNativeComponent.ts +3 -0
- 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;
|
|
@@ -195,7 +318,12 @@ using namespace luggmaps::events;
|
|
|
195
318
|
*std::static_pointer_cast<LuggMapViewProps const>(_props);
|
|
196
319
|
|
|
197
320
|
if (_providerType == LuggMapViewProvider::Apple) {
|
|
198
|
-
_provider = [[
|
|
321
|
+
_provider = _staticMode ? [[AppleStaticMapProvider alloc] init]
|
|
322
|
+
: [[AppleMapProvider alloc] init];
|
|
323
|
+
} else if (_staticMode) {
|
|
324
|
+
GoogleStaticMapProvider *google = [[GoogleStaticMapProvider alloc] init];
|
|
325
|
+
google.mapId = _mapId;
|
|
326
|
+
_provider = google;
|
|
199
327
|
} else {
|
|
200
328
|
GoogleMapProvider *google = [[GoogleMapProvider alloc] init];
|
|
201
329
|
google.mapId = _mapId;
|
|
@@ -203,6 +331,7 @@ using namespace luggmaps::events;
|
|
|
203
331
|
}
|
|
204
332
|
|
|
205
333
|
_provider.delegate = self;
|
|
334
|
+
_provider.staticMode = _staticMode;
|
|
206
335
|
|
|
207
336
|
CLLocationCoordinate2D coordinate =
|
|
208
337
|
CLLocationCoordinate2DMake(viewProps.initialCoordinate.latitude,
|
|
@@ -212,6 +341,16 @@ using namespace luggmaps::events;
|
|
|
212
341
|
initialCoordinate:coordinate
|
|
213
342
|
initialZoom:viewProps.initialZoom];
|
|
214
343
|
|
|
344
|
+
// After initializeMapInView so the cache key reads the provider's camera;
|
|
345
|
+
// the base render is async and picks up the cached image
|
|
346
|
+
if (_staticMode) {
|
|
347
|
+
NSString *cacheKey = [self staticSnapshotCacheKey];
|
|
348
|
+
if (cacheKey) {
|
|
349
|
+
((StaticMapProviderBase *)_provider).cachedBaseImage =
|
|
350
|
+
[StaticSnapshotCache() objectForKey:cacheKey];
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
215
354
|
// Apply cached props after map view is created
|
|
216
355
|
[self applyProps];
|
|
217
356
|
[_provider setEdgeInsets:_edgeInsets oldEdgeInsets:UIEdgeInsetsZero];
|
|
@@ -242,6 +381,19 @@ using namespace luggmaps::events;
|
|
|
242
381
|
ReadyEvent::emit<LuggMapViewEventEmitter>(_eventEmitter);
|
|
243
382
|
}
|
|
244
383
|
|
|
384
|
+
- (void)mapProviderDidFinishStaticSnapshot {
|
|
385
|
+
_staticSnapshotDone = YES;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
- (void)mapProviderDidCaptureStaticImage:(UIImage *)image {
|
|
389
|
+
NSString *key = [self staticSnapshotCacheKey];
|
|
390
|
+
if (key) {
|
|
391
|
+
NSUInteger cost = (NSUInteger)(image.size.width * image.scale *
|
|
392
|
+
image.size.height * image.scale * 4);
|
|
393
|
+
[StaticSnapshotCache() setObject:image forKey:key cost:cost];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
245
397
|
- (void)mapProviderDidMoveCamera:(double)latitude
|
|
246
398
|
longitude:(double)longitude
|
|
247
399
|
zoom:(double)zoom
|
|
@@ -281,6 +433,7 @@ using namespace luggmaps::events;
|
|
|
281
433
|
[_provider setScrollEnabled:_scrollEnabled];
|
|
282
434
|
[_provider setRotateEnabled:_rotateEnabled];
|
|
283
435
|
[_provider setPitchEnabled:_pitchEnabled];
|
|
436
|
+
[_provider setCompassEnabled:_compassEnabled];
|
|
284
437
|
[_provider setUserLocationEnabled:_userLocationEnabled];
|
|
285
438
|
[_provider setMapType:_mapType];
|
|
286
439
|
[_provider setTheme:_theme];
|
|
@@ -300,6 +453,12 @@ using namespace luggmaps::events;
|
|
|
300
453
|
*std::static_pointer_cast<LuggMapViewProps const>(_props);
|
|
301
454
|
|
|
302
455
|
_providerType = newViewProps.provider;
|
|
456
|
+
_staticMode = newViewProps.staticMode;
|
|
457
|
+
|
|
458
|
+
NSString *newStaticKey =
|
|
459
|
+
[NSString stringWithUTF8String:newViewProps.staticKey.c_str()];
|
|
460
|
+
BOOL staticKeyChanged = ![newStaticKey isEqualToString:_staticKey];
|
|
461
|
+
_staticKey = newStaticKey;
|
|
303
462
|
|
|
304
463
|
NSString *newMapId =
|
|
305
464
|
[NSString stringWithUTF8String:newViewProps.mapId.c_str()];
|
|
@@ -323,6 +482,10 @@ using namespace luggmaps::events;
|
|
|
323
482
|
_pitchEnabled = newViewProps.pitchEnabled;
|
|
324
483
|
[_provider setPitchEnabled:_pitchEnabled];
|
|
325
484
|
}
|
|
485
|
+
if (newViewProps.compassEnabled != prevViewProps.compassEnabled) {
|
|
486
|
+
_compassEnabled = newViewProps.compassEnabled;
|
|
487
|
+
[_provider setCompassEnabled:_compassEnabled];
|
|
488
|
+
}
|
|
326
489
|
if (newViewProps.userLocationEnabled != prevViewProps.userLocationEnabled) {
|
|
327
490
|
_userLocationEnabled = newViewProps.userLocationEnabled;
|
|
328
491
|
[_provider setUserLocationEnabled:_userLocationEnabled];
|
|
@@ -374,6 +537,11 @@ using namespace luggmaps::events;
|
|
|
374
537
|
}
|
|
375
538
|
|
|
376
539
|
[super updateProps:props oldProps:oldProps];
|
|
540
|
+
|
|
541
|
+
// After super so the snapshot cache key sees the new camera props
|
|
542
|
+
if (staticKeyChanged && _staticMode && _mapWrapperView) {
|
|
543
|
+
[self resetStaticContent];
|
|
544
|
+
}
|
|
377
545
|
}
|
|
378
546
|
|
|
379
547
|
#pragma mark - Commands
|
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
|
|
|
@@ -145,6 +145,7 @@ static double tileToLng(NSInteger x, NSInteger z) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
@synthesize delegate = _delegate;
|
|
148
|
+
@synthesize staticMode = _staticMode;
|
|
148
149
|
|
|
149
150
|
- (instancetype)init {
|
|
150
151
|
if (self = [super init]) {
|
|
@@ -182,6 +183,10 @@ static double tileToLng(NSInteger x, NSInteger z) {
|
|
|
182
183
|
_mapView.delegate = self;
|
|
183
184
|
_mapView.insetsLayoutMarginsFromSafeArea = NO;
|
|
184
185
|
|
|
186
|
+
if (_staticMode) {
|
|
187
|
+
_mapView.userInteractionEnabled = NO;
|
|
188
|
+
}
|
|
189
|
+
|
|
185
190
|
[self applyZoomRange];
|
|
186
191
|
|
|
187
192
|
_tapGesture =
|
|
@@ -212,6 +217,11 @@ static double tileToLng(NSInteger x, NSInteger z) {
|
|
|
212
217
|
}
|
|
213
218
|
|
|
214
219
|
- (void)destroy {
|
|
220
|
+
[self destroyMapView];
|
|
221
|
+
_isMapReady = NO;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
- (void)destroyMapView {
|
|
215
225
|
[self dismissNonBubbledCallout];
|
|
216
226
|
[self stopEdgeInsetsAnimation];
|
|
217
227
|
if (_tapGesture) {
|
|
@@ -222,9 +232,9 @@ static double tileToLng(NSInteger x, NSInteger z) {
|
|
|
222
232
|
[_mapView removeGestureRecognizer:_longPressGesture];
|
|
223
233
|
_longPressGesture = nil;
|
|
224
234
|
}
|
|
235
|
+
_mapView.delegate = nil;
|
|
225
236
|
[_mapView removeFromSuperview];
|
|
226
237
|
_mapView = nil;
|
|
227
|
-
_isMapReady = NO;
|
|
228
238
|
}
|
|
229
239
|
|
|
230
240
|
#pragma mark - Props
|
|
@@ -245,6 +255,10 @@ static double tileToLng(NSInteger x, NSInteger z) {
|
|
|
245
255
|
_mapView.pitchEnabled = enabled;
|
|
246
256
|
}
|
|
247
257
|
|
|
258
|
+
- (void)setCompassEnabled:(BOOL)enabled {
|
|
259
|
+
_mapView.showsCompass = enabled;
|
|
260
|
+
}
|
|
261
|
+
|
|
248
262
|
- (void)setUserLocationEnabled:(BOOL)enabled {
|
|
249
263
|
_mapView.showsUserLocation = enabled;
|
|
250
264
|
}
|
|
@@ -338,33 +352,36 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
338
352
|
return map[string];
|
|
339
353
|
}
|
|
340
354
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return;
|
|
355
|
+
MKPointOfInterestFilter *_Nullable LuggPointOfInterestFilter(
|
|
356
|
+
BOOL poiEnabled, LuggMapViewPoiFilterMode filterMode,
|
|
357
|
+
NSArray<NSString *> *filterCategories) {
|
|
358
|
+
if (!poiEnabled) {
|
|
359
|
+
return MKPointOfInterestFilter.filterExcludingAllCategories;
|
|
346
360
|
}
|
|
347
|
-
if (
|
|
361
|
+
if (filterCategories.count > 0) {
|
|
348
362
|
NSMutableArray<MKPointOfInterestCategory> *categories =
|
|
349
363
|
[NSMutableArray array];
|
|
350
|
-
for (NSString *name in
|
|
364
|
+
for (NSString *name in filterCategories) {
|
|
351
365
|
MKPointOfInterestCategory category = poiCategoryFromString(name);
|
|
352
366
|
if (category) {
|
|
353
367
|
[categories addObject:category];
|
|
354
368
|
}
|
|
355
369
|
}
|
|
356
370
|
if (categories.count > 0) {
|
|
357
|
-
if (
|
|
358
|
-
|
|
371
|
+
if (filterMode == LuggMapViewPoiFilterMode::Excluding) {
|
|
372
|
+
return [[MKPointOfInterestFilter alloc]
|
|
359
373
|
initExcludingCategories:categories];
|
|
360
|
-
} else {
|
|
361
|
-
_mapView.pointOfInterestFilter = [[MKPointOfInterestFilter alloc]
|
|
362
|
-
initIncludingCategories:categories];
|
|
363
374
|
}
|
|
364
|
-
return
|
|
375
|
+
return
|
|
376
|
+
[[MKPointOfInterestFilter alloc] initIncludingCategories:categories];
|
|
365
377
|
}
|
|
366
378
|
}
|
|
367
|
-
|
|
379
|
+
return nil;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
- (void)applyPoiFilter {
|
|
383
|
+
_mapView.pointOfInterestFilter = LuggPointOfInterestFilter(
|
|
384
|
+
_poiEnabled, _poiFilterMode, _poiFilterCategories);
|
|
368
385
|
}
|
|
369
386
|
|
|
370
387
|
- (void)setInsetAdjustment:(LuggMapViewInsetAdjustment)insetAdjustment {
|
|
@@ -395,26 +412,25 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
395
412
|
[self applyPoiFilter];
|
|
396
413
|
}
|
|
397
414
|
|
|
398
|
-
|
|
415
|
+
MKMapType LuggMKMapTypeFromMapType(LuggMapViewMapType mapType) {
|
|
399
416
|
switch (mapType) {
|
|
400
417
|
case LuggMapViewMapType::Satellite:
|
|
401
|
-
|
|
402
|
-
break;
|
|
418
|
+
return MKMapTypeSatellite;
|
|
403
419
|
case LuggMapViewMapType::Terrain:
|
|
404
|
-
|
|
405
|
-
break;
|
|
420
|
+
return MKMapTypeStandard;
|
|
406
421
|
case LuggMapViewMapType::Hybrid:
|
|
407
|
-
|
|
408
|
-
break;
|
|
422
|
+
return MKMapTypeHybrid;
|
|
409
423
|
case LuggMapViewMapType::MutedStandard:
|
|
410
|
-
|
|
411
|
-
break;
|
|
424
|
+
return MKMapTypeMutedStandard;
|
|
412
425
|
default:
|
|
413
|
-
|
|
414
|
-
break;
|
|
426
|
+
return MKMapTypeStandard;
|
|
415
427
|
}
|
|
416
428
|
}
|
|
417
429
|
|
|
430
|
+
- (void)setMapType:(LuggMapViewMapType)mapType {
|
|
431
|
+
_mapView.mapType = LuggMKMapTypeFromMapType(mapType);
|
|
432
|
+
}
|
|
433
|
+
|
|
418
434
|
- (void)setTheme:(LuggMapViewTheme)theme {
|
|
419
435
|
switch (theme) {
|
|
420
436
|
case LuggMapViewTheme::Dark:
|
|
@@ -832,7 +848,8 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
832
848
|
renderer.strokeColors = colors;
|
|
833
849
|
}
|
|
834
850
|
renderer.animatedOptions = polylineView.animatedOptions;
|
|
835
|
-
|
|
851
|
+
// Static maps snapshot an arbitrary frame; render the full polyline
|
|
852
|
+
renderer.animated = polylineView.animated && !_staticMode;
|
|
836
853
|
polylineView.renderer = renderer;
|
|
837
854
|
return renderer;
|
|
838
855
|
}
|
|
@@ -1361,7 +1378,7 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
1361
1378
|
renderer.strokeColors =
|
|
1362
1379
|
polylineView.strokeColors.count > 1 ? polylineView.strokeColors : nil;
|
|
1363
1380
|
renderer.animatedOptions = polylineView.animatedOptions;
|
|
1364
|
-
renderer.animated = polylineView.animated;
|
|
1381
|
+
renderer.animated = polylineView.animated && !_staticMode;
|
|
1365
1382
|
return;
|
|
1366
1383
|
}
|
|
1367
1384
|
|
|
@@ -1774,11 +1791,11 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
1774
1791
|
} else if (duration > 0) {
|
|
1775
1792
|
CLLocationCoordinate2D center =
|
|
1776
1793
|
CLLocationCoordinate2DMake(latitude, longitude);
|
|
1777
|
-
|
|
1778
|
-
|
|
1794
|
+
MKMapCamera *camera = [_mapView cameraForCenterCoordinate:center
|
|
1795
|
+
zoomLevel:targetZoom];
|
|
1779
1796
|
[UIView animateWithDuration:duration / 1000.0
|
|
1780
1797
|
animations:^{
|
|
1781
|
-
[self->_mapView
|
|
1798
|
+
[self->_mapView setCamera:camera animated:NO];
|
|
1782
1799
|
}];
|
|
1783
1800
|
} else {
|
|
1784
1801
|
[_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
|