@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
|
@@ -13,10 +13,44 @@ using facebook::react::LuggMapViewTheme;
|
|
|
13
13
|
#import "../LuggPolylineView.h"
|
|
14
14
|
#import "../LuggTileOverlayView.h"
|
|
15
15
|
#import "GMSPolylineAnimator.h"
|
|
16
|
+
#import "MapEdgeInsets.h"
|
|
16
17
|
#import "PolylineAnimatorBase.h"
|
|
17
18
|
|
|
18
19
|
static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
19
20
|
|
|
21
|
+
GMSMapID *LuggGMSMapIDFromString(NSString *_Nullable mapId) {
|
|
22
|
+
if (mapId.length == 0 || [mapId isEqualToString:kDemoMapId]) {
|
|
23
|
+
return [GMSMapID demoMapID];
|
|
24
|
+
}
|
|
25
|
+
return [GMSMapID mapIDWithIdentifier:mapId];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
GMSMapViewType
|
|
29
|
+
LuggGMSMapTypeFromMapType(facebook::react::LuggMapViewMapType mapType) {
|
|
30
|
+
switch (mapType) {
|
|
31
|
+
case LuggMapViewMapType::Satellite:
|
|
32
|
+
return kGMSTypeSatellite;
|
|
33
|
+
case LuggMapViewMapType::Terrain:
|
|
34
|
+
return kGMSTypeTerrain;
|
|
35
|
+
case LuggMapViewMapType::Hybrid:
|
|
36
|
+
return kGMSTypeHybrid;
|
|
37
|
+
default:
|
|
38
|
+
return kGMSTypeNormal;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
UIUserInterfaceStyle
|
|
43
|
+
LuggInterfaceStyleFromTheme(facebook::react::LuggMapViewTheme theme) {
|
|
44
|
+
switch (theme) {
|
|
45
|
+
case LuggMapViewTheme::Dark:
|
|
46
|
+
return UIUserInterfaceStyleDark;
|
|
47
|
+
case LuggMapViewTheme::Light:
|
|
48
|
+
return UIUserInterfaceStyleLight;
|
|
49
|
+
default:
|
|
50
|
+
return UIUserInterfaceStyleUnspecified;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
20
54
|
@interface GoogleMapProvider () <
|
|
21
55
|
LuggMarkerViewDelegate, LuggCalloutViewDelegate, LuggPolylineViewDelegate,
|
|
22
56
|
LuggPolygonViewDelegate, LuggCircleViewDelegate,
|
|
@@ -53,6 +87,7 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
53
87
|
}
|
|
54
88
|
|
|
55
89
|
@synthesize delegate = _delegate;
|
|
90
|
+
@synthesize staticMode = _staticMode;
|
|
56
91
|
|
|
57
92
|
- (instancetype)init {
|
|
58
93
|
if (self = [super init]) {
|
|
@@ -89,13 +124,6 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
89
124
|
if (_mapView)
|
|
90
125
|
return;
|
|
91
126
|
|
|
92
|
-
GMSMapID *gmsMapId;
|
|
93
|
-
if ([_mapId isEqualToString:kDemoMapId] || _mapId.length == 0) {
|
|
94
|
-
gmsMapId = [GMSMapID demoMapID];
|
|
95
|
-
} else {
|
|
96
|
-
gmsMapId = [GMSMapID mapIDWithIdentifier:_mapId];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
127
|
GMSCameraPosition *camera =
|
|
100
128
|
[GMSCameraPosition cameraWithLatitude:coordinate.latitude
|
|
101
129
|
longitude:coordinate.longitude
|
|
@@ -104,7 +132,7 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
104
132
|
GMSMapViewOptions *options = [[GMSMapViewOptions alloc] init];
|
|
105
133
|
options.frame = wrapperView.bounds;
|
|
106
134
|
options.camera = camera;
|
|
107
|
-
options.mapID =
|
|
135
|
+
options.mapID = LuggGMSMapIDFromString(_mapId);
|
|
108
136
|
|
|
109
137
|
_mapView = [[GMSMapView alloc] initWithOptions:options];
|
|
110
138
|
_mapView.autoresizingMask =
|
|
@@ -142,10 +170,26 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
142
170
|
[_circleToViewMap removeAllObjects];
|
|
143
171
|
[_markerToViewMap removeAllObjects];
|
|
144
172
|
[_groundOverlayToViewMap removeAllObjects];
|
|
145
|
-
[
|
|
173
|
+
[self destroyMapView];
|
|
174
|
+
_isMapReady = NO;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
- (void)destroyMapView {
|
|
178
|
+
if (!_mapView)
|
|
179
|
+
return;
|
|
180
|
+
|
|
181
|
+
_mapView.delegate = nil;
|
|
146
182
|
[_mapView removeFromSuperview];
|
|
183
|
+
[_mapView clear];
|
|
147
184
|
_mapView = nil;
|
|
148
|
-
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
- (CLLocationCoordinate2D)coordinate {
|
|
188
|
+
return _mapView.camera.target;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
- (double)zoom {
|
|
192
|
+
return _mapView.camera.zoom;
|
|
149
193
|
}
|
|
150
194
|
|
|
151
195
|
#pragma mark - Props
|
|
@@ -166,6 +210,10 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
166
210
|
_mapView.settings.tiltGestures = enabled;
|
|
167
211
|
}
|
|
168
212
|
|
|
213
|
+
- (void)setCompassEnabled:(BOOL)enabled {
|
|
214
|
+
_mapView.settings.compassButton = enabled;
|
|
215
|
+
}
|
|
216
|
+
|
|
169
217
|
- (void)setUserLocationEnabled:(BOOL)enabled {
|
|
170
218
|
_mapView.myLocationEnabled = enabled;
|
|
171
219
|
}
|
|
@@ -187,23 +235,7 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
187
235
|
}
|
|
188
236
|
|
|
189
237
|
- (void)setMapType:(LuggMapViewMapType)mapType {
|
|
190
|
-
|
|
191
|
-
return;
|
|
192
|
-
|
|
193
|
-
switch (mapType) {
|
|
194
|
-
case LuggMapViewMapType::Satellite:
|
|
195
|
-
_mapView.mapType = kGMSTypeSatellite;
|
|
196
|
-
break;
|
|
197
|
-
case LuggMapViewMapType::Terrain:
|
|
198
|
-
_mapView.mapType = kGMSTypeTerrain;
|
|
199
|
-
break;
|
|
200
|
-
case LuggMapViewMapType::Hybrid:
|
|
201
|
-
_mapView.mapType = kGMSTypeHybrid;
|
|
202
|
-
break;
|
|
203
|
-
default:
|
|
204
|
-
_mapView.mapType = kGMSTypeNormal;
|
|
205
|
-
break;
|
|
206
|
-
}
|
|
238
|
+
_mapView.mapType = LuggGMSMapTypeFromMapType(mapType);
|
|
207
239
|
}
|
|
208
240
|
|
|
209
241
|
- (void)setTheme:(LuggMapViewTheme)theme {
|
|
@@ -212,20 +244,7 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
212
244
|
}
|
|
213
245
|
|
|
214
246
|
- (void)applyTheme {
|
|
215
|
-
|
|
216
|
-
return;
|
|
217
|
-
|
|
218
|
-
switch (_theme) {
|
|
219
|
-
case LuggMapViewTheme::Dark:
|
|
220
|
-
_mapView.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
|
|
221
|
-
break;
|
|
222
|
-
case LuggMapViewTheme::Light:
|
|
223
|
-
_mapView.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
|
|
224
|
-
break;
|
|
225
|
-
default:
|
|
226
|
-
_mapView.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
247
|
+
_mapView.overrideUserInterfaceStyle = LuggInterfaceStyleFromTheme(_theme);
|
|
229
248
|
}
|
|
230
249
|
|
|
231
250
|
- (void)setMinZoom:(double)minZoom {
|
|
@@ -278,16 +297,8 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
278
297
|
CFTimeInterval elapsed = CACurrentMediaTime() - _edgeInsetsAnimationStart;
|
|
279
298
|
CGFloat progress = MIN(elapsed / _edgeInsetsAnimationDuration, 1.0);
|
|
280
299
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
UIEdgeInsets current = UIEdgeInsetsMake(
|
|
285
|
-
_edgeInsetsFrom.top + (_edgeInsetsTo.top - _edgeInsetsFrom.top) * t,
|
|
286
|
-
_edgeInsetsFrom.left + (_edgeInsetsTo.left - _edgeInsetsFrom.left) * t,
|
|
287
|
-
_edgeInsetsFrom.bottom +
|
|
288
|
-
(_edgeInsetsTo.bottom - _edgeInsetsFrom.bottom) * t,
|
|
289
|
-
_edgeInsetsFrom.right +
|
|
290
|
-
(_edgeInsetsTo.right - _edgeInsetsFrom.right) * t);
|
|
300
|
+
UIEdgeInsets current =
|
|
301
|
+
LuggMapInsetsAtProgress(_edgeInsetsFrom, _edgeInsetsTo, progress);
|
|
291
302
|
|
|
292
303
|
_mapView.padding = current;
|
|
293
304
|
|
|
@@ -1125,9 +1136,11 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
1125
1136
|
return;
|
|
1126
1137
|
|
|
1127
1138
|
float targetZoom = zoom > 0 ? (float)zoom : _mapView.camera.zoom;
|
|
1128
|
-
GMSCameraPosition *camera = [GMSCameraPosition
|
|
1129
|
-
|
|
1130
|
-
|
|
1139
|
+
GMSCameraPosition *camera = [GMSCameraPosition
|
|
1140
|
+
cameraWithTarget:CLLocationCoordinate2DMake(latitude, longitude)
|
|
1141
|
+
zoom:targetZoom
|
|
1142
|
+
bearing:_mapView.camera.bearing
|
|
1143
|
+
viewingAngle:_mapView.camera.viewingAngle];
|
|
1131
1144
|
if (duration < 0) {
|
|
1132
1145
|
[_mapView animateToCameraPosition:camera];
|
|
1133
1146
|
} else if (duration > 0) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#import "StaticMapProviderBase.h"
|
|
2
|
+
#import <GoogleMaps/GoogleMaps.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Static Google map. The Google Maps SDK has no async snapshotter, so the
|
|
8
|
+
* base map briefly warms up on a live, tiles-only GMSMapView inserted below
|
|
9
|
+
* the overlays, then is swapped with a rendered image and the map view is
|
|
10
|
+
* pooled for reuse. Markers and shapes are overlay views (shared with
|
|
11
|
+
* Apple static maps) that reveal with the base map, so the warmup map
|
|
12
|
+
* never renders its own markers. Warmup cost scales with how many static
|
|
13
|
+
* maps mount at once - bound that with list props (windowSize etc.).
|
|
14
|
+
*/
|
|
15
|
+
@interface GoogleStaticMapProvider : StaticMapProviderBase <GMSMapViewDelegate>
|
|
16
|
+
|
|
17
|
+
@property(nonatomic, copy, nullable) NSString *mapId;
|
|
18
|
+
|
|
19
|
+
@end
|
|
20
|
+
|
|
21
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#import "GoogleStaticMapProvider.h"
|
|
2
|
+
|
|
3
|
+
#import "../extensions/UIView+Snapshot.h"
|
|
4
|
+
#import "GoogleMapProvider.h"
|
|
5
|
+
|
|
6
|
+
// Static maps churn through GMSMapViews as list rows warm up, and both
|
|
7
|
+
// creating one and tearing one down are expensive main-thread operations
|
|
8
|
+
// (renderer setup/teardown). Detached map views are pooled and reused so a
|
|
9
|
+
// warmup usually only costs a camera move plus tile load. Keyed by map ID,
|
|
10
|
+
// which is fixed at GMSMapView creation.
|
|
11
|
+
static const NSUInteger kStaticMapViewPoolCapacity = 6;
|
|
12
|
+
|
|
13
|
+
static NSMutableDictionary<NSString *, NSMutableArray<GMSMapView *> *> *
|
|
14
|
+
StaticMapViewPool(void) {
|
|
15
|
+
static NSMutableDictionary<NSString *, NSMutableArray<GMSMapView *> *> *pool;
|
|
16
|
+
static dispatch_once_t onceToken;
|
|
17
|
+
dispatch_once(&onceToken, ^{
|
|
18
|
+
pool = [NSMutableDictionary dictionary];
|
|
19
|
+
[[NSNotificationCenter defaultCenter]
|
|
20
|
+
addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
|
|
21
|
+
object:nil
|
|
22
|
+
queue:[NSOperationQueue mainQueue]
|
|
23
|
+
usingBlock:^(NSNotification *notification) {
|
|
24
|
+
[pool removeAllObjects];
|
|
25
|
+
}];
|
|
26
|
+
});
|
|
27
|
+
return pool;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static GMSMapView *_Nullable DequeueStaticMapView(NSString *mapId) {
|
|
31
|
+
NSMutableArray<GMSMapView *> *views = StaticMapViewPool()[mapId];
|
|
32
|
+
GMSMapView *mapView = views.lastObject;
|
|
33
|
+
[views removeLastObject];
|
|
34
|
+
return mapView;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static void EnqueueStaticMapView(NSString *mapId, GMSMapView *mapView) {
|
|
38
|
+
NSMutableDictionary<NSString *, NSMutableArray<GMSMapView *> *> *pool =
|
|
39
|
+
StaticMapViewPool();
|
|
40
|
+
NSMutableArray<GMSMapView *> *views = pool[mapId];
|
|
41
|
+
if (!views) {
|
|
42
|
+
views = [NSMutableArray array];
|
|
43
|
+
pool[mapId] = views;
|
|
44
|
+
}
|
|
45
|
+
if (views.count >= kStaticMapViewPoolCapacity)
|
|
46
|
+
return;
|
|
47
|
+
[views addObject:mapView];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@implementation GoogleStaticMapProvider {
|
|
51
|
+
GMSMapView *_warmupMapView;
|
|
52
|
+
BOOL _snapshotReady;
|
|
53
|
+
BOOL _swapScheduled;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#pragma mark - StaticMapProviderBase
|
|
57
|
+
|
|
58
|
+
- (MKMapRect)mapRectForSize:(CGSize)size {
|
|
59
|
+
// GMSMapView shows the world 256 * 2^zoom points wide, centered on the
|
|
60
|
+
// camera target - the same web mercator MKMapPoint uses, so overlays
|
|
61
|
+
// project onto the warmed-up tiles exactly
|
|
62
|
+
double zoom =
|
|
63
|
+
MAX(MIN(self.zoom, (double)kGMSMaxZoomLevel), (double)kGMSMinZoomLevel);
|
|
64
|
+
double mapPointsPerPoint = MKMapSizeWorld.width / (256.0 * pow(2.0, zoom));
|
|
65
|
+
double width = size.width * mapPointsPerPoint;
|
|
66
|
+
double height = size.height * mapPointsPerPoint;
|
|
67
|
+
MKMapPoint center = MKMapPointForCoordinate(self.coordinate);
|
|
68
|
+
return MKMapRectMake(center.x - width / 2, center.y - height / 2, width,
|
|
69
|
+
height);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
- (UIView *)newDefaultMarkerOverlayView {
|
|
73
|
+
return
|
|
74
|
+
[[UIImageView alloc] initWithImage:[GMSMarker markerImageWithColor:nil]];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Inverse of mapRectForSize
|
|
78
|
+
- (double)zoomForMapPointsPerPoint:(double)mapPointsPerPoint {
|
|
79
|
+
double zoom = log2(MKMapSizeWorld.width / (256.0 * mapPointsPerPoint));
|
|
80
|
+
return MAX(MIN(zoom, (double)kGMSMaxZoomLevel), (double)kGMSMinZoomLevel);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
- (void)renderBaseMap {
|
|
84
|
+
if (_warmupMapView) {
|
|
85
|
+
// Retry a swap that couldn't run (e.g. view was off-window)
|
|
86
|
+
[self scheduleSwap];
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
[self startWarmup];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
- (void)cancelBaseRender {
|
|
93
|
+
[self destroyWarmupMapView];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#pragma mark - Warmup
|
|
97
|
+
|
|
98
|
+
// "" resolves to the demo map ID (see LuggGMSMapIDFromString), so both
|
|
99
|
+
// share a pool bucket
|
|
100
|
+
- (NSString *)poolKey {
|
|
101
|
+
return self.mapId.length > 0 ? self.mapId : @"DEMO_MAP_ID";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
- (void)startWarmup {
|
|
105
|
+
UIView *wrapperView = self.wrapperView;
|
|
106
|
+
if (!wrapperView)
|
|
107
|
+
return;
|
|
108
|
+
|
|
109
|
+
_snapshotReady = NO;
|
|
110
|
+
|
|
111
|
+
GMSMapView *mapView = DequeueStaticMapView([self poolKey]);
|
|
112
|
+
if (mapView) {
|
|
113
|
+
mapView.frame = wrapperView.bounds;
|
|
114
|
+
[mapView moveCamera:[GMSCameraUpdate setTarget:self.coordinate
|
|
115
|
+
zoom:(float)self.zoom]];
|
|
116
|
+
} else {
|
|
117
|
+
GMSCameraPosition *camera =
|
|
118
|
+
[GMSCameraPosition cameraWithLatitude:self.coordinate.latitude
|
|
119
|
+
longitude:self.coordinate.longitude
|
|
120
|
+
zoom:(float)self.zoom];
|
|
121
|
+
|
|
122
|
+
GMSMapViewOptions *options = [[GMSMapViewOptions alloc] init];
|
|
123
|
+
options.frame = wrapperView.bounds;
|
|
124
|
+
options.camera = camera;
|
|
125
|
+
options.mapID = LuggGMSMapIDFromString(self.mapId);
|
|
126
|
+
|
|
127
|
+
mapView = [[GMSMapView alloc] initWithOptions:options];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
mapView.autoresizingMask =
|
|
131
|
+
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
132
|
+
mapView.delegate = self;
|
|
133
|
+
mapView.userInteractionEnabled = NO;
|
|
134
|
+
mapView.preferredFrameRate = kGMSFrameRateConservative;
|
|
135
|
+
mapView.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorNever;
|
|
136
|
+
// Frames the camera like the inset-shifted projection (and clears stale
|
|
137
|
+
// padding on pooled views)
|
|
138
|
+
mapView.padding = self.edgeInsets;
|
|
139
|
+
mapView.mapType = LuggGMSMapTypeFromMapType(self.mapType);
|
|
140
|
+
mapView.overrideUserInterfaceStyle = LuggInterfaceStyleFromTheme(self.theme);
|
|
141
|
+
|
|
142
|
+
_warmupMapView = mapView;
|
|
143
|
+
[wrapperView insertSubview:mapView atIndex:0];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
- (void)destroyWarmupMapView {
|
|
147
|
+
if (!_warmupMapView)
|
|
148
|
+
return;
|
|
149
|
+
_warmupMapView.delegate = nil;
|
|
150
|
+
[_warmupMapView removeFromSuperview];
|
|
151
|
+
EnqueueStaticMapView([self poolKey], _warmupMapView);
|
|
152
|
+
_warmupMapView = nil;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Once the map is stable, swap the live map with its image and release the
|
|
156
|
+
// map view. The image render and map teardown are expensive, so they run
|
|
157
|
+
// outside scroll tracking; the live map keeps displaying (and loading
|
|
158
|
+
// tiles) until then.
|
|
159
|
+
- (void)scheduleSwap {
|
|
160
|
+
if (_swapScheduled || !_warmupMapView || !_snapshotReady ||
|
|
161
|
+
!_warmupMapView.window)
|
|
162
|
+
return;
|
|
163
|
+
|
|
164
|
+
_swapScheduled = YES;
|
|
165
|
+
__weak GoogleStaticMapProvider *weakSelf = self;
|
|
166
|
+
[[NSRunLoop mainRunLoop] performInModes:@[ NSDefaultRunLoopMode ]
|
|
167
|
+
block:^{
|
|
168
|
+
GoogleStaticMapProvider *strongSelf =
|
|
169
|
+
weakSelf;
|
|
170
|
+
if (!strongSelf)
|
|
171
|
+
return;
|
|
172
|
+
strongSelf->_swapScheduled = NO;
|
|
173
|
+
[strongSelf performSwap];
|
|
174
|
+
}];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
- (void)performSwap {
|
|
178
|
+
// Tiles can invalidate (or the view leave the window) between scheduling
|
|
179
|
+
// and this runloop pass; the next snapshotReady or resumeAnimations
|
|
180
|
+
// retries
|
|
181
|
+
if (!_warmupMapView || !_snapshotReady || !_warmupMapView.window)
|
|
182
|
+
return;
|
|
183
|
+
|
|
184
|
+
UIImageView *imageView = [_warmupMapView lugg_snapshotImageView];
|
|
185
|
+
if (!imageView)
|
|
186
|
+
return;
|
|
187
|
+
|
|
188
|
+
[self displayBaseImage:imageView.image fromCache:NO];
|
|
189
|
+
[self destroyWarmupMapView];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#pragma mark - GMSMapViewDelegate
|
|
193
|
+
|
|
194
|
+
- (void)mapViewDidStartTileRendering:(GMSMapView *)mapView {
|
|
195
|
+
_snapshotReady = NO;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fires for tiles that failed permanently too, so it can't gate the swap:
|
|
199
|
+
// capturing here cached blank tiles under the static key with no way to
|
|
200
|
+
// recover. The visible warmup map has rendered what it can; show the
|
|
201
|
+
// overlays now instead of waiting for the swap, which is deferred while
|
|
202
|
+
// scrolling
|
|
203
|
+
- (void)mapViewDidFinishTileRendering:(GMSMapView *)mapView {
|
|
204
|
+
[self revealOverlays];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Stable per the SDK: tiles loaded, labels and overlays rendered. The only
|
|
208
|
+
// signal that the render is complete, so the only one that captures and
|
|
209
|
+
// caches. Without it (e.g. offline) the live map stays until the view
|
|
210
|
+
// leaves the window, and a fresh warmup runs on return.
|
|
211
|
+
- (void)mapViewSnapshotReady:(GMSMapView *)mapView {
|
|
212
|
+
_snapshotReady = YES;
|
|
213
|
+
[self revealOverlays];
|
|
214
|
+
[self scheduleSwap];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@end
|
|
@@ -35,8 +35,9 @@
|
|
|
35
35
|
NSArray<NSNumber *> *_cumulativeDistances;
|
|
36
36
|
CGFloat _totalLength;
|
|
37
37
|
CGColorSpaceRef _colorSpace;
|
|
38
|
-
|
|
39
|
-
|
|
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 =
|
|
133
|
+
NSUInteger right = distances.count - 1;
|
|
129
134
|
|
|
130
135
|
while (left < right) {
|
|
131
136
|
NSUInteger mid = (left + right + 1) / 2;
|
|
132
|
-
if (
|
|
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,
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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 (
|
|
233
|
-
*out =
|
|
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 * (
|
|
251
|
+
CGFloat scaledPos = position * (count - 1);
|
|
239
252
|
NSUInteger index = (NSUInteger)scaledPos;
|
|
240
253
|
CGFloat t = scaledPos - index;
|
|
241
254
|
|
|
242
|
-
if (index >=
|
|
243
|
-
*out =
|
|
255
|
+
if (index >= count - 1) {
|
|
256
|
+
*out = cache[count - 1];
|
|
244
257
|
return;
|
|
245
258
|
}
|
|
246
259
|
|
|
247
|
-
RGBAComponents c1 =
|
|
248
|
-
RGBAComponents c2 =
|
|
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
|
-
|
|
276
|
-
|
|
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 &&
|
|
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 *
|
|
291
|
-
tailDist = MAX(0, headDist -
|
|
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 *
|
|
314
|
+
headDist = easedProgress * totalLength;
|
|
295
315
|
} else {
|
|
296
316
|
CGFloat shrinkProgress = easedProgress - 1.0;
|
|
297
|
-
tailDist = shrinkProgress *
|
|
298
|
-
headDist =
|
|
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 <
|
|
310
|
-
CGFloat segStartDist =
|
|
311
|
-
CGFloat segEndDist =
|
|
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:
|
|
318
|
-
CGPoint segEnd = [self pointForMapPoint:
|
|
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:
|
|
372
|
-
CGPoint endPoint = [self pointForMapPoint:
|
|
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;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#import <UIKit/UIKit.h>
|
|
4
|
+
|
|
5
|
+
static inline CGPoint LuggMapInsetOffset(UIEdgeInsets insets) {
|
|
6
|
+
return CGPointMake((insets.left - insets.right) / 2.0,
|
|
7
|
+
(insets.top - insets.bottom) / 2.0);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static inline UIEdgeInsets
|
|
11
|
+
LuggMapInsetsAtProgress(UIEdgeInsets from, UIEdgeInsets to, CGFloat progress) {
|
|
12
|
+
// Ease out cubic
|
|
13
|
+
CGFloat t = 1.0 - (1.0 - progress) * (1.0 - progress) * (1.0 - progress);
|
|
14
|
+
return UIEdgeInsetsMake(from.top + (to.top - from.top) * t,
|
|
15
|
+
from.left + (to.left - from.left) * t,
|
|
16
|
+
from.bottom + (to.bottom - from.bottom) * t,
|
|
17
|
+
from.right + (to.right - from.right) * t);
|
|
18
|
+
}
|