@lugg/maps 1.0.0-beta.17 → 1.0.0-beta.19
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/android/src/main/java/com/luggmaps/LuggMapView.kt +33 -0
- package/android/src/main/java/com/luggmaps/LuggMapViewManager.kt +9 -0
- package/android/src/main/java/com/luggmaps/LuggMapWrapperView.kt +27 -2
- package/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +110 -5
- package/ios/LuggMapView.mm +153 -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 +41 -28
- package/ios/core/AppleStaticMapProvider.h +14 -0
- package/ios/core/AppleStaticMapProvider.mm +146 -0
- package/ios/core/GoogleMapProvider.h +10 -0
- package/ios/core/GoogleMapProvider.mm +47 -41
- package/ios/core/GoogleStaticMapProvider.h +21 -0
- package/ios/core/GoogleStaticMapProvider.mm +207 -0
- package/ios/core/MKPolylineAnimator.m +67 -43
- package/ios/core/MapProviderDelegate.h +6 -0
- package/ios/core/StaticMapProviderBase.h +70 -0
- package/ios/core/StaticMapProviderBase.mm +777 -0
- package/ios/extensions/MKMapView+Zoom.h +14 -0
- package/ios/extensions/MKMapView+Zoom.m +17 -15
- package/ios/extensions/UIView+Snapshot.h +16 -0
- package/ios/extensions/UIView+Snapshot.m +27 -0
- package/lib/module/MapView.js +4 -0
- package/lib/module/MapView.js.map +1 -1
- package/lib/module/MapView.web.js +8 -5
- package/lib/module/MapView.web.js.map +1 -1
- package/lib/module/fabric/LuggMapViewNativeComponent.ts +2 -0
- package/lib/typescript/src/MapView.d.ts.map +1 -1
- package/lib/typescript/src/MapView.types.d.ts +18 -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 +2 -0
- package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/MapView.tsx +4 -0
- package/src/MapView.types.ts +18 -0
- package/src/MapView.web.tsx +14 -10
- package/src/fabric/LuggMapViewNativeComponent.ts +2 -0
|
@@ -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
|
|
@@ -342,33 +352,36 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
342
352
|
return map[string];
|
|
343
353
|
}
|
|
344
354
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
return;
|
|
355
|
+
MKPointOfInterestFilter *_Nullable LuggPointOfInterestFilter(
|
|
356
|
+
BOOL poiEnabled, LuggMapViewPoiFilterMode filterMode,
|
|
357
|
+
NSArray<NSString *> *filterCategories) {
|
|
358
|
+
if (!poiEnabled) {
|
|
359
|
+
return MKPointOfInterestFilter.filterExcludingAllCategories;
|
|
350
360
|
}
|
|
351
|
-
if (
|
|
361
|
+
if (filterCategories.count > 0) {
|
|
352
362
|
NSMutableArray<MKPointOfInterestCategory> *categories =
|
|
353
363
|
[NSMutableArray array];
|
|
354
|
-
for (NSString *name in
|
|
364
|
+
for (NSString *name in filterCategories) {
|
|
355
365
|
MKPointOfInterestCategory category = poiCategoryFromString(name);
|
|
356
366
|
if (category) {
|
|
357
367
|
[categories addObject:category];
|
|
358
368
|
}
|
|
359
369
|
}
|
|
360
370
|
if (categories.count > 0) {
|
|
361
|
-
if (
|
|
362
|
-
|
|
371
|
+
if (filterMode == LuggMapViewPoiFilterMode::Excluding) {
|
|
372
|
+
return [[MKPointOfInterestFilter alloc]
|
|
363
373
|
initExcludingCategories:categories];
|
|
364
|
-
} else {
|
|
365
|
-
_mapView.pointOfInterestFilter = [[MKPointOfInterestFilter alloc]
|
|
366
|
-
initIncludingCategories:categories];
|
|
367
374
|
}
|
|
368
|
-
return
|
|
375
|
+
return
|
|
376
|
+
[[MKPointOfInterestFilter alloc] initIncludingCategories:categories];
|
|
369
377
|
}
|
|
370
378
|
}
|
|
371
|
-
|
|
379
|
+
return nil;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
- (void)applyPoiFilter {
|
|
383
|
+
_mapView.pointOfInterestFilter = LuggPointOfInterestFilter(
|
|
384
|
+
_poiEnabled, _poiFilterMode, _poiFilterCategories);
|
|
372
385
|
}
|
|
373
386
|
|
|
374
387
|
- (void)setInsetAdjustment:(LuggMapViewInsetAdjustment)insetAdjustment {
|
|
@@ -399,26 +412,25 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
399
412
|
[self applyPoiFilter];
|
|
400
413
|
}
|
|
401
414
|
|
|
402
|
-
|
|
415
|
+
MKMapType LuggMKMapTypeFromMapType(LuggMapViewMapType mapType) {
|
|
403
416
|
switch (mapType) {
|
|
404
417
|
case LuggMapViewMapType::Satellite:
|
|
405
|
-
|
|
406
|
-
break;
|
|
418
|
+
return MKMapTypeSatellite;
|
|
407
419
|
case LuggMapViewMapType::Terrain:
|
|
408
|
-
|
|
409
|
-
break;
|
|
420
|
+
return MKMapTypeStandard;
|
|
410
421
|
case LuggMapViewMapType::Hybrid:
|
|
411
|
-
|
|
412
|
-
break;
|
|
422
|
+
return MKMapTypeHybrid;
|
|
413
423
|
case LuggMapViewMapType::MutedStandard:
|
|
414
|
-
|
|
415
|
-
break;
|
|
424
|
+
return MKMapTypeMutedStandard;
|
|
416
425
|
default:
|
|
417
|
-
|
|
418
|
-
break;
|
|
426
|
+
return MKMapTypeStandard;
|
|
419
427
|
}
|
|
420
428
|
}
|
|
421
429
|
|
|
430
|
+
- (void)setMapType:(LuggMapViewMapType)mapType {
|
|
431
|
+
_mapView.mapType = LuggMKMapTypeFromMapType(mapType);
|
|
432
|
+
}
|
|
433
|
+
|
|
422
434
|
- (void)setTheme:(LuggMapViewTheme)theme {
|
|
423
435
|
switch (theme) {
|
|
424
436
|
case LuggMapViewTheme::Dark:
|
|
@@ -836,7 +848,8 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
836
848
|
renderer.strokeColors = colors;
|
|
837
849
|
}
|
|
838
850
|
renderer.animatedOptions = polylineView.animatedOptions;
|
|
839
|
-
|
|
851
|
+
// Static maps snapshot an arbitrary frame; render the full polyline
|
|
852
|
+
renderer.animated = polylineView.animated && !_staticMode;
|
|
840
853
|
polylineView.renderer = renderer;
|
|
841
854
|
return renderer;
|
|
842
855
|
}
|
|
@@ -1365,7 +1378,7 @@ static MKPointOfInterestCategory poiCategoryFromString(NSString *string) {
|
|
|
1365
1378
|
renderer.strokeColors =
|
|
1366
1379
|
polylineView.strokeColors.count > 1 ? polylineView.strokeColors : nil;
|
|
1367
1380
|
renderer.animatedOptions = polylineView.animatedOptions;
|
|
1368
|
-
renderer.animated = polylineView.animated;
|
|
1381
|
+
renderer.animated = polylineView.animated && !_staticMode;
|
|
1369
1382
|
return;
|
|
1370
1383
|
}
|
|
1371
1384
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#import "StaticMapProviderBase.h"
|
|
2
|
+
|
|
3
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Static Apple map rendered with MKMapSnapshotter. The base map renders
|
|
7
|
+
* fully asynchronously (no live MKMapView is ever created), so static maps
|
|
8
|
+
* keep loading while the user scrolls without blocking the main thread.
|
|
9
|
+
*/
|
|
10
|
+
@interface AppleStaticMapProvider : StaticMapProviderBase
|
|
11
|
+
|
|
12
|
+
@end
|
|
13
|
+
|
|
14
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#import "AppleStaticMapProvider.h"
|
|
2
|
+
|
|
3
|
+
using facebook::react::LuggMapViewPoiFilterMode;
|
|
4
|
+
using facebook::react::LuggMapViewTheme;
|
|
5
|
+
|
|
6
|
+
#import "../extensions/MKMapView+Zoom.h"
|
|
7
|
+
#import "AppleMapProvider.h"
|
|
8
|
+
#import "LuggAnnotationView.h"
|
|
9
|
+
|
|
10
|
+
// Standard MKMarkerAnnotationView balloon size; standalone (off-map) views
|
|
11
|
+
// don't size themselves
|
|
12
|
+
static const CGSize kDefaultMarkerSize = {28, 33};
|
|
13
|
+
|
|
14
|
+
// The rect the snapshot renders: the region for the requested zoom, expanded
|
|
15
|
+
// on the short axis to the view's aspect ratio - the same fit MKMapView
|
|
16
|
+
// applies in setRegion, so static and live maps frame alike. Passing this
|
|
17
|
+
// exact rect to MKMapSnapshotter makes the projection exact.
|
|
18
|
+
static MKMapRect LuggStaticFittedMapRect(CLLocationCoordinate2D center,
|
|
19
|
+
double zoom, CGSize size) {
|
|
20
|
+
MKCoordinateSpan span = LuggCoordinateSpanForZoomLevel(MIN(zoom, 28), center);
|
|
21
|
+
MKMapPoint topLeft = MKMapPointForCoordinate(CLLocationCoordinate2DMake(
|
|
22
|
+
MIN(center.latitude + span.latitudeDelta / 2, 90.0),
|
|
23
|
+
center.longitude - span.longitudeDelta / 2));
|
|
24
|
+
MKMapPoint bottomRight = MKMapPointForCoordinate(CLLocationCoordinate2DMake(
|
|
25
|
+
MAX(center.latitude - span.latitudeDelta / 2, -90.0),
|
|
26
|
+
center.longitude + span.longitudeDelta / 2));
|
|
27
|
+
MKMapRect rect =
|
|
28
|
+
MKMapRectMake(topLeft.x, topLeft.y, bottomRight.x - topLeft.x,
|
|
29
|
+
bottomRight.y - topLeft.y);
|
|
30
|
+
|
|
31
|
+
double viewAspect = size.width / size.height;
|
|
32
|
+
double rectAspect = rect.size.width / rect.size.height;
|
|
33
|
+
if (rectAspect > viewAspect) {
|
|
34
|
+
double height = rect.size.width / viewAspect;
|
|
35
|
+
rect.origin.y -= (height - rect.size.height) / 2;
|
|
36
|
+
rect.size.height = height;
|
|
37
|
+
} else {
|
|
38
|
+
double width = rect.size.height * viewAspect;
|
|
39
|
+
rect.origin.x -= (width - rect.size.width) / 2;
|
|
40
|
+
rect.size.width = width;
|
|
41
|
+
}
|
|
42
|
+
return rect;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@implementation AppleStaticMapProvider {
|
|
46
|
+
MKMapSnapshotter *_snapshotter;
|
|
47
|
+
|
|
48
|
+
BOOL _poiEnabled;
|
|
49
|
+
LuggMapViewPoiFilterMode _poiFilterMode;
|
|
50
|
+
NSArray<NSString *> *_poiFilterCategories;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
- (instancetype)init {
|
|
54
|
+
if (self = [super init]) {
|
|
55
|
+
_poiEnabled = YES;
|
|
56
|
+
_poiFilterMode = LuggMapViewPoiFilterMode::Including;
|
|
57
|
+
_poiFilterCategories = @[];
|
|
58
|
+
}
|
|
59
|
+
return self;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#pragma mark - StaticMapProviderBase
|
|
63
|
+
|
|
64
|
+
- (MKMapRect)mapRectForSize:(CGSize)size {
|
|
65
|
+
return LuggStaticFittedMapRect(self.coordinate, self.zoom, size);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
- (UIView *)newDefaultMarkerOverlayView {
|
|
69
|
+
MKAnnotationView *pin =
|
|
70
|
+
[[LuggMarkerAnnotationView alloc] initWithAnnotation:nil
|
|
71
|
+
reuseIdentifier:nil];
|
|
72
|
+
pin.bounds =
|
|
73
|
+
CGRectMake(0, 0, kDefaultMarkerSize.width, kDefaultMarkerSize.height);
|
|
74
|
+
return pin;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
- (UITraitCollection *)snapshotTraitCollection {
|
|
78
|
+
UITraitCollection *base = self.wrapperView.traitCollection;
|
|
79
|
+
UIUserInterfaceStyle style;
|
|
80
|
+
switch (self.theme) {
|
|
81
|
+
case LuggMapViewTheme::Dark:
|
|
82
|
+
style = UIUserInterfaceStyleDark;
|
|
83
|
+
break;
|
|
84
|
+
case LuggMapViewTheme::Light:
|
|
85
|
+
style = UIUserInterfaceStyleLight;
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
return base;
|
|
89
|
+
}
|
|
90
|
+
return [UITraitCollection traitCollectionWithTraitsFromCollections:@[
|
|
91
|
+
base, [UITraitCollection traitCollectionWithUserInterfaceStyle:style]
|
|
92
|
+
]];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
- (void)renderBaseMap {
|
|
96
|
+
if (_snapshotter)
|
|
97
|
+
return;
|
|
98
|
+
|
|
99
|
+
MKMapSnapshotOptions *options = [[MKMapSnapshotOptions alloc] init];
|
|
100
|
+
options.mapRect = self.mapRect;
|
|
101
|
+
options.size = self.projectedSize;
|
|
102
|
+
options.mapType = LuggMKMapTypeFromMapType(self.mapType);
|
|
103
|
+
options.pointOfInterestFilter = LuggPointOfInterestFilter(
|
|
104
|
+
_poiEnabled, _poiFilterMode, _poiFilterCategories);
|
|
105
|
+
options.traitCollection = [self snapshotTraitCollection];
|
|
106
|
+
|
|
107
|
+
MKMapSnapshotter *snapshotter =
|
|
108
|
+
[[MKMapSnapshotter alloc] initWithOptions:options];
|
|
109
|
+
_snapshotter = snapshotter;
|
|
110
|
+
|
|
111
|
+
__weak AppleStaticMapProvider *weakSelf = self;
|
|
112
|
+
[snapshotter startWithQueue:dispatch_get_main_queue()
|
|
113
|
+
completionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
|
|
114
|
+
AppleStaticMapProvider *strongSelf = weakSelf;
|
|
115
|
+
if (!strongSelf || strongSelf->_snapshotter != snapshotter)
|
|
116
|
+
return;
|
|
117
|
+
strongSelf->_snapshotter = nil;
|
|
118
|
+
if (error || !snapshot) {
|
|
119
|
+
// Keep the placeholder; resumeAnimations retries (e.g. back
|
|
120
|
+
// online)
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
[strongSelf displayBaseImage:snapshot.image fromCache:NO];
|
|
124
|
+
}];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
- (void)cancelBaseRender {
|
|
128
|
+
[_snapshotter cancel];
|
|
129
|
+
_snapshotter = nil;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#pragma mark - Props
|
|
133
|
+
|
|
134
|
+
- (void)setPoiEnabled:(BOOL)enabled {
|
|
135
|
+
_poiEnabled = enabled;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
- (void)setPoiFilterMode:(LuggMapViewPoiFilterMode)mode {
|
|
139
|
+
_poiFilterMode = mode;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
- (void)setPoiFilterCategories:(NSArray<NSString *> *)categories {
|
|
143
|
+
_poiFilterCategories = categories;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@end
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
|
|
4
4
|
NS_ASSUME_NONNULL_BEGIN
|
|
5
5
|
|
|
6
|
+
/// Resolves a mapId prop value ("" or "DEMO_MAP_ID" -> demo map ID),
|
|
7
|
+
/// shared by the live and static Google providers.
|
|
8
|
+
GMSMapID *LuggGMSMapIDFromString(NSString *_Nullable mapId);
|
|
9
|
+
|
|
10
|
+
GMSMapViewType
|
|
11
|
+
LuggGMSMapTypeFromMapType(facebook::react::LuggMapViewMapType mapType);
|
|
12
|
+
|
|
13
|
+
UIUserInterfaceStyle
|
|
14
|
+
LuggInterfaceStyleFromTheme(facebook::react::LuggMapViewTheme theme);
|
|
15
|
+
|
|
6
16
|
@interface GoogleMapProvider : NSObject <MapProvider, GMSMapViewDelegate>
|
|
7
17
|
|
|
8
18
|
@property(nonatomic, copy, nullable) NSString *mapId;
|
|
@@ -17,6 +17,39 @@ using facebook::react::LuggMapViewTheme;
|
|
|
17
17
|
|
|
18
18
|
static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
19
19
|
|
|
20
|
+
GMSMapID *LuggGMSMapIDFromString(NSString *_Nullable mapId) {
|
|
21
|
+
if (mapId.length == 0 || [mapId isEqualToString:kDemoMapId]) {
|
|
22
|
+
return [GMSMapID demoMapID];
|
|
23
|
+
}
|
|
24
|
+
return [GMSMapID mapIDWithIdentifier:mapId];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
GMSMapViewType
|
|
28
|
+
LuggGMSMapTypeFromMapType(facebook::react::LuggMapViewMapType mapType) {
|
|
29
|
+
switch (mapType) {
|
|
30
|
+
case LuggMapViewMapType::Satellite:
|
|
31
|
+
return kGMSTypeSatellite;
|
|
32
|
+
case LuggMapViewMapType::Terrain:
|
|
33
|
+
return kGMSTypeTerrain;
|
|
34
|
+
case LuggMapViewMapType::Hybrid:
|
|
35
|
+
return kGMSTypeHybrid;
|
|
36
|
+
default:
|
|
37
|
+
return kGMSTypeNormal;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
UIUserInterfaceStyle
|
|
42
|
+
LuggInterfaceStyleFromTheme(facebook::react::LuggMapViewTheme theme) {
|
|
43
|
+
switch (theme) {
|
|
44
|
+
case LuggMapViewTheme::Dark:
|
|
45
|
+
return UIUserInterfaceStyleDark;
|
|
46
|
+
case LuggMapViewTheme::Light:
|
|
47
|
+
return UIUserInterfaceStyleLight;
|
|
48
|
+
default:
|
|
49
|
+
return UIUserInterfaceStyleUnspecified;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
20
53
|
@interface GoogleMapProvider () <
|
|
21
54
|
LuggMarkerViewDelegate, LuggCalloutViewDelegate, LuggPolylineViewDelegate,
|
|
22
55
|
LuggPolygonViewDelegate, LuggCircleViewDelegate,
|
|
@@ -53,6 +86,7 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
53
86
|
}
|
|
54
87
|
|
|
55
88
|
@synthesize delegate = _delegate;
|
|
89
|
+
@synthesize staticMode = _staticMode;
|
|
56
90
|
|
|
57
91
|
- (instancetype)init {
|
|
58
92
|
if (self = [super init]) {
|
|
@@ -89,13 +123,6 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
89
123
|
if (_mapView)
|
|
90
124
|
return;
|
|
91
125
|
|
|
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
126
|
GMSCameraPosition *camera =
|
|
100
127
|
[GMSCameraPosition cameraWithLatitude:coordinate.latitude
|
|
101
128
|
longitude:coordinate.longitude
|
|
@@ -104,7 +131,7 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
104
131
|
GMSMapViewOptions *options = [[GMSMapViewOptions alloc] init];
|
|
105
132
|
options.frame = wrapperView.bounds;
|
|
106
133
|
options.camera = camera;
|
|
107
|
-
options.mapID =
|
|
134
|
+
options.mapID = LuggGMSMapIDFromString(_mapId);
|
|
108
135
|
|
|
109
136
|
_mapView = [[GMSMapView alloc] initWithOptions:options];
|
|
110
137
|
_mapView.autoresizingMask =
|
|
@@ -142,10 +169,18 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
142
169
|
[_circleToViewMap removeAllObjects];
|
|
143
170
|
[_markerToViewMap removeAllObjects];
|
|
144
171
|
[_groundOverlayToViewMap removeAllObjects];
|
|
145
|
-
[
|
|
172
|
+
[self destroyMapView];
|
|
173
|
+
_isMapReady = NO;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
- (void)destroyMapView {
|
|
177
|
+
if (!_mapView)
|
|
178
|
+
return;
|
|
179
|
+
|
|
180
|
+
_mapView.delegate = nil;
|
|
146
181
|
[_mapView removeFromSuperview];
|
|
182
|
+
[_mapView clear];
|
|
147
183
|
_mapView = nil;
|
|
148
|
-
_isMapReady = NO;
|
|
149
184
|
}
|
|
150
185
|
|
|
151
186
|
#pragma mark - Props
|
|
@@ -191,23 +226,7 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
191
226
|
}
|
|
192
227
|
|
|
193
228
|
- (void)setMapType:(LuggMapViewMapType)mapType {
|
|
194
|
-
|
|
195
|
-
return;
|
|
196
|
-
|
|
197
|
-
switch (mapType) {
|
|
198
|
-
case LuggMapViewMapType::Satellite:
|
|
199
|
-
_mapView.mapType = kGMSTypeSatellite;
|
|
200
|
-
break;
|
|
201
|
-
case LuggMapViewMapType::Terrain:
|
|
202
|
-
_mapView.mapType = kGMSTypeTerrain;
|
|
203
|
-
break;
|
|
204
|
-
case LuggMapViewMapType::Hybrid:
|
|
205
|
-
_mapView.mapType = kGMSTypeHybrid;
|
|
206
|
-
break;
|
|
207
|
-
default:
|
|
208
|
-
_mapView.mapType = kGMSTypeNormal;
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
229
|
+
_mapView.mapType = LuggGMSMapTypeFromMapType(mapType);
|
|
211
230
|
}
|
|
212
231
|
|
|
213
232
|
- (void)setTheme:(LuggMapViewTheme)theme {
|
|
@@ -216,20 +235,7 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
216
235
|
}
|
|
217
236
|
|
|
218
237
|
- (void)applyTheme {
|
|
219
|
-
|
|
220
|
-
return;
|
|
221
|
-
|
|
222
|
-
switch (_theme) {
|
|
223
|
-
case LuggMapViewTheme::Dark:
|
|
224
|
-
_mapView.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
|
|
225
|
-
break;
|
|
226
|
-
case LuggMapViewTheme::Light:
|
|
227
|
-
_mapView.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
|
|
228
|
-
break;
|
|
229
|
-
default:
|
|
230
|
-
_mapView.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
238
|
+
_mapView.overrideUserInterfaceStyle = LuggInterfaceStyleFromTheme(_theme);
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
- (void)setMinZoom:(double)minZoom {
|
|
@@ -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,207 @@
|
|
|
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 _tilesRendered;
|
|
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
|
+
- (void)renderBaseMap {
|
|
78
|
+
if (_warmupMapView) {
|
|
79
|
+
// Retry a swap that couldn't run (e.g. view was off-window)
|
|
80
|
+
[self scheduleSwap];
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
[self startWarmup];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
- (void)cancelBaseRender {
|
|
87
|
+
[self destroyWarmupMapView];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#pragma mark - Warmup
|
|
91
|
+
|
|
92
|
+
// "" resolves to the demo map ID (see LuggGMSMapIDFromString), so both
|
|
93
|
+
// share a pool bucket
|
|
94
|
+
- (NSString *)poolKey {
|
|
95
|
+
return self.mapId.length > 0 ? self.mapId : @"DEMO_MAP_ID";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
- (void)startWarmup {
|
|
99
|
+
UIView *wrapperView = self.wrapperView;
|
|
100
|
+
if (!wrapperView)
|
|
101
|
+
return;
|
|
102
|
+
|
|
103
|
+
_tilesRendered = NO;
|
|
104
|
+
|
|
105
|
+
GMSMapView *mapView = DequeueStaticMapView([self poolKey]);
|
|
106
|
+
if (mapView) {
|
|
107
|
+
mapView.frame = wrapperView.bounds;
|
|
108
|
+
[mapView moveCamera:[GMSCameraUpdate setTarget:self.coordinate
|
|
109
|
+
zoom:(float)self.zoom]];
|
|
110
|
+
} else {
|
|
111
|
+
GMSCameraPosition *camera =
|
|
112
|
+
[GMSCameraPosition cameraWithLatitude:self.coordinate.latitude
|
|
113
|
+
longitude:self.coordinate.longitude
|
|
114
|
+
zoom:(float)self.zoom];
|
|
115
|
+
|
|
116
|
+
GMSMapViewOptions *options = [[GMSMapViewOptions alloc] init];
|
|
117
|
+
options.frame = wrapperView.bounds;
|
|
118
|
+
options.camera = camera;
|
|
119
|
+
options.mapID = LuggGMSMapIDFromString(self.mapId);
|
|
120
|
+
|
|
121
|
+
mapView = [[GMSMapView alloc] initWithOptions:options];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
mapView.autoresizingMask =
|
|
125
|
+
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
126
|
+
mapView.delegate = self;
|
|
127
|
+
mapView.userInteractionEnabled = NO;
|
|
128
|
+
mapView.preferredFrameRate = kGMSFrameRateConservative;
|
|
129
|
+
mapView.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorNever;
|
|
130
|
+
// Frames the camera like the inset-shifted projection (and clears stale
|
|
131
|
+
// padding on pooled views)
|
|
132
|
+
mapView.padding = self.edgeInsets;
|
|
133
|
+
mapView.mapType = LuggGMSMapTypeFromMapType(self.mapType);
|
|
134
|
+
mapView.overrideUserInterfaceStyle = LuggInterfaceStyleFromTheme(self.theme);
|
|
135
|
+
|
|
136
|
+
_warmupMapView = mapView;
|
|
137
|
+
[wrapperView insertSubview:mapView atIndex:0];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
- (void)destroyWarmupMapView {
|
|
141
|
+
if (!_warmupMapView)
|
|
142
|
+
return;
|
|
143
|
+
_warmupMapView.delegate = nil;
|
|
144
|
+
[_warmupMapView removeFromSuperview];
|
|
145
|
+
EnqueueStaticMapView([self poolKey], _warmupMapView);
|
|
146
|
+
_warmupMapView = nil;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Once tiles are fully rendered, swap the live map with its image and
|
|
150
|
+
// release the map view. The image render and map teardown are expensive,
|
|
151
|
+
// so they run outside scroll tracking; the live map keeps displaying (and
|
|
152
|
+
// loading tiles) until then.
|
|
153
|
+
- (void)scheduleSwap {
|
|
154
|
+
if (_swapScheduled || !_warmupMapView || !_tilesRendered ||
|
|
155
|
+
!_warmupMapView.window)
|
|
156
|
+
return;
|
|
157
|
+
|
|
158
|
+
_swapScheduled = YES;
|
|
159
|
+
__weak GoogleStaticMapProvider *weakSelf = self;
|
|
160
|
+
[[NSRunLoop mainRunLoop] performInModes:@[ NSDefaultRunLoopMode ]
|
|
161
|
+
block:^{
|
|
162
|
+
GoogleStaticMapProvider *strongSelf =
|
|
163
|
+
weakSelf;
|
|
164
|
+
if (!strongSelf)
|
|
165
|
+
return;
|
|
166
|
+
strongSelf->_swapScheduled = NO;
|
|
167
|
+
[strongSelf performSwap];
|
|
168
|
+
}];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
- (void)performSwap {
|
|
172
|
+
// Tiles can invalidate (or the view leave the window) between scheduling
|
|
173
|
+
// and this runloop pass; the next tile callback or resumeAnimations
|
|
174
|
+
// retries
|
|
175
|
+
if (!_warmupMapView || !_tilesRendered || !_warmupMapView.window)
|
|
176
|
+
return;
|
|
177
|
+
|
|
178
|
+
UIImageView *imageView = [_warmupMapView lugg_snapshotImageView];
|
|
179
|
+
if (!imageView)
|
|
180
|
+
return;
|
|
181
|
+
|
|
182
|
+
[self displayBaseImage:imageView.image fromCache:NO];
|
|
183
|
+
[self destroyWarmupMapView];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#pragma mark - GMSMapViewDelegate
|
|
187
|
+
|
|
188
|
+
- (void)mapViewDidStartTileRendering:(GMSMapView *)mapView {
|
|
189
|
+
_tilesRendered = NO;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
- (void)mapViewDidFinishTileRendering:(GMSMapView *)mapView {
|
|
193
|
+
_tilesRendered = YES;
|
|
194
|
+
// The visible warmup map is fully rendered; show the overlays now
|
|
195
|
+
// instead of waiting for the swap, which is deferred while scrolling
|
|
196
|
+
[self revealOverlays];
|
|
197
|
+
[self scheduleSwap];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
- (void)mapViewSnapshotReady:(GMSMapView *)mapView {
|
|
201
|
+
// Stable per the SDK: tiles loaded, labels and overlays rendered
|
|
202
|
+
_tilesRendered = YES;
|
|
203
|
+
[self revealOverlays];
|
|
204
|
+
[self scheduleSwap];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@end
|