@lugg/maps 1.0.0-beta.16 → 1.0.0-beta.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +20 -2
- package/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +29 -3
- package/ios/LuggMapView.mm +152 -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/GMSPolylineAnimator.m +40 -15
- 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 +204 -0
- package/ios/core/MKPolylineAnimator.m +67 -43
- package/ios/core/MapProviderDelegate.h +6 -0
- package/ios/core/StaticMapProviderBase.h +69 -0
- package/ios/core/StaticMapProviderBase.mm +745 -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
|
@@ -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
|
|
@@ -148,13 +148,25 @@ static const NSUInteger kMaxGradientSpans = 512;
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
GMSMutablePath *path = [GMSMutablePath path];
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
CLLocationCoordinate2D lastCoord = self.coordinates.firstObject.coordinate;
|
|
152
|
+
[path addCoordinate:lastCoord];
|
|
153
|
+
for (NSUInteger i = 1; i < self.coordinates.count; i++) {
|
|
154
|
+
CLLocationCoordinate2D coord = self.coordinates[i].coordinate;
|
|
155
|
+
if (coord.latitude == lastCoord.latitude && coord.longitude == lastCoord.longitude) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
[path addCoordinate:coord];
|
|
159
|
+
lastCoord = coord;
|
|
153
160
|
}
|
|
161
|
+
|
|
162
|
+
// Clear stale spans before swapping the path so Google Maps never rebuilds
|
|
163
|
+
// span models against a path with fewer segments (GMSModelStyleBuilder
|
|
164
|
+
// couldNotInstantiate -> EXC_BAD_ACCESS). Reassign spans once the new path is set.
|
|
165
|
+
_polyline.spans = nil;
|
|
154
166
|
_polyline.path = path;
|
|
155
167
|
|
|
156
|
-
if (self.strokeColors.count > 1) {
|
|
157
|
-
_polyline.spans = [self
|
|
168
|
+
if (path.count > 1 && self.strokeColors.count > 1) {
|
|
169
|
+
_polyline.spans = [self createGradientSpansForSegmentCount:path.count - 1];
|
|
158
170
|
} else {
|
|
159
171
|
_polyline.strokeColor = self.strokeColors.firstObject ?: [UIColor blackColor];
|
|
160
172
|
}
|
|
@@ -222,35 +234,44 @@ static const NSUInteger kMaxGradientSpans = 512;
|
|
|
222
234
|
}
|
|
223
235
|
|
|
224
236
|
if (headDist <= tailDist) {
|
|
237
|
+
_polyline.spans = nil;
|
|
225
238
|
_polyline.path = [GMSMutablePath path];
|
|
226
239
|
return;
|
|
227
240
|
}
|
|
228
241
|
|
|
229
|
-
CGFloat visibleLength = headDist - tailDist;
|
|
230
242
|
NSUInteger startIndex = [self indexForDistance:tailDist];
|
|
231
243
|
NSUInteger endIndex = [self indexForDistance:headDist];
|
|
232
244
|
|
|
233
245
|
GMSMutablePath *path = [GMSMutablePath path];
|
|
234
|
-
NSMutableArray<GMSStyleSpan *> *spans = [NSMutableArray array];
|
|
235
246
|
|
|
236
|
-
CLLocationCoordinate2D
|
|
237
|
-
[path addCoordinate:
|
|
247
|
+
CLLocationCoordinate2D lastCoord = [self coordinateAtDistance:tailDist];
|
|
248
|
+
[path addCoordinate:lastCoord];
|
|
238
249
|
|
|
239
250
|
for (NSUInteger i = startIndex + 1; i <= endIndex; i++) {
|
|
240
|
-
|
|
251
|
+
CLLocationCoordinate2D coord = self.coordinates[i].coordinate;
|
|
252
|
+
if (coord.latitude == lastCoord.latitude && coord.longitude == lastCoord.longitude) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
[path addCoordinate:coord];
|
|
256
|
+
lastCoord = coord;
|
|
241
257
|
}
|
|
242
258
|
|
|
243
259
|
CLLocationCoordinate2D endCoord = [self coordinateAtDistance:headDist];
|
|
244
|
-
|
|
245
|
-
(endIndex < self.coordinates.count) ? self.coordinates[endIndex].coordinate : endCoord;
|
|
246
|
-
if (endCoord.latitude != lastAdded.latitude || endCoord.longitude != lastAdded.longitude) {
|
|
260
|
+
if (endCoord.latitude != lastCoord.latitude || endCoord.longitude != lastCoord.longitude) {
|
|
247
261
|
[path addCoordinate:endCoord];
|
|
248
262
|
}
|
|
249
263
|
|
|
250
264
|
NSUInteger pathCount = path.count;
|
|
251
|
-
NSUInteger segmentCount = pathCount - 1;
|
|
265
|
+
NSUInteger segmentCount = (pathCount > 1) ? pathCount - 1 : 0;
|
|
266
|
+
|
|
267
|
+
if (segmentCount == 0) {
|
|
268
|
+
_polyline.spans = nil;
|
|
269
|
+
_polyline.path = path;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
252
272
|
|
|
253
273
|
if (self.strokeColors.count <= 1) {
|
|
274
|
+
_polyline.spans = nil;
|
|
254
275
|
_polyline.path = path;
|
|
255
276
|
_polyline.strokeColor = self.strokeColors.firstObject ?: [UIColor blackColor];
|
|
256
277
|
return;
|
|
@@ -259,6 +280,7 @@ static const NSUInteger kMaxGradientSpans = 512;
|
|
|
259
280
|
NSUInteger spanCount = MIN(segmentCount, kMaxAnimationSpans);
|
|
260
281
|
double segmentsPerSpan = (double)segmentCount / spanCount;
|
|
261
282
|
|
|
283
|
+
NSMutableArray<GMSStyleSpan *> *spans = [NSMutableArray array];
|
|
262
284
|
for (NSUInteger i = 0; i < spanCount; i++) {
|
|
263
285
|
CGFloat gradientPos = ((CGFloat)i + 0.5) / spanCount;
|
|
264
286
|
UIColor *color = [self colorAtGradientPosition:gradientPos];
|
|
@@ -266,13 +288,16 @@ static const NSUInteger kMaxGradientSpans = 512;
|
|
|
266
288
|
[spans addObject:[GMSStyleSpan spanWithStyle:style segments:segmentsPerSpan]];
|
|
267
289
|
}
|
|
268
290
|
|
|
291
|
+
// Clear stale spans before swapping the path so Google Maps never rebuilds
|
|
292
|
+
// span models against a path with fewer segments (GMSModelStyleBuilder
|
|
293
|
+
// couldNotInstantiate -> EXC_BAD_ACCESS). Reassign spans once the new path is set.
|
|
294
|
+
_polyline.spans = nil;
|
|
269
295
|
_polyline.path = path;
|
|
270
296
|
_polyline.spans = spans;
|
|
271
297
|
}
|
|
272
298
|
|
|
273
|
-
- (NSArray<GMSStyleSpan *> *)
|
|
299
|
+
- (NSArray<GMSStyleSpan *> *)createGradientSpansForSegmentCount:(NSUInteger)segmentCount {
|
|
274
300
|
NSMutableArray<GMSStyleSpan *> *spans = [NSMutableArray array];
|
|
275
|
-
NSUInteger segmentCount = self.coordinates.count - 1;
|
|
276
301
|
NSUInteger spanCount = MIN(segmentCount, kMaxGradientSpans);
|
|
277
302
|
double segmentsPerSpan = (double)segmentCount / spanCount;
|
|
278
303
|
|
|
@@ -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,204 @@
|
|
|
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
|
+
mapView.mapType = LuggGMSMapTypeFromMapType(self.mapType);
|
|
131
|
+
mapView.overrideUserInterfaceStyle = LuggInterfaceStyleFromTheme(self.theme);
|
|
132
|
+
|
|
133
|
+
_warmupMapView = mapView;
|
|
134
|
+
[wrapperView insertSubview:mapView atIndex:0];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
- (void)destroyWarmupMapView {
|
|
138
|
+
if (!_warmupMapView)
|
|
139
|
+
return;
|
|
140
|
+
_warmupMapView.delegate = nil;
|
|
141
|
+
[_warmupMapView removeFromSuperview];
|
|
142
|
+
EnqueueStaticMapView([self poolKey], _warmupMapView);
|
|
143
|
+
_warmupMapView = nil;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Once tiles are fully rendered, swap the live map with its image and
|
|
147
|
+
// release the map view. The image render and map teardown are expensive,
|
|
148
|
+
// so they run outside scroll tracking; the live map keeps displaying (and
|
|
149
|
+
// loading tiles) until then.
|
|
150
|
+
- (void)scheduleSwap {
|
|
151
|
+
if (_swapScheduled || !_warmupMapView || !_tilesRendered ||
|
|
152
|
+
!_warmupMapView.window)
|
|
153
|
+
return;
|
|
154
|
+
|
|
155
|
+
_swapScheduled = YES;
|
|
156
|
+
__weak GoogleStaticMapProvider *weakSelf = self;
|
|
157
|
+
[[NSRunLoop mainRunLoop] performInModes:@[ NSDefaultRunLoopMode ]
|
|
158
|
+
block:^{
|
|
159
|
+
GoogleStaticMapProvider *strongSelf =
|
|
160
|
+
weakSelf;
|
|
161
|
+
if (!strongSelf)
|
|
162
|
+
return;
|
|
163
|
+
strongSelf->_swapScheduled = NO;
|
|
164
|
+
[strongSelf performSwap];
|
|
165
|
+
}];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
- (void)performSwap {
|
|
169
|
+
// Tiles can invalidate (or the view leave the window) between scheduling
|
|
170
|
+
// and this runloop pass; the next tile callback or resumeAnimations
|
|
171
|
+
// retries
|
|
172
|
+
if (!_warmupMapView || !_tilesRendered || !_warmupMapView.window)
|
|
173
|
+
return;
|
|
174
|
+
|
|
175
|
+
UIImageView *imageView = [_warmupMapView lugg_snapshotImageView];
|
|
176
|
+
if (!imageView)
|
|
177
|
+
return;
|
|
178
|
+
|
|
179
|
+
[self displayBaseImage:imageView.image fromCache:NO];
|
|
180
|
+
[self destroyWarmupMapView];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#pragma mark - GMSMapViewDelegate
|
|
184
|
+
|
|
185
|
+
- (void)mapViewDidStartTileRendering:(GMSMapView *)mapView {
|
|
186
|
+
_tilesRendered = NO;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
- (void)mapViewDidFinishTileRendering:(GMSMapView *)mapView {
|
|
190
|
+
_tilesRendered = YES;
|
|
191
|
+
// The visible warmup map is fully rendered; show the overlays now
|
|
192
|
+
// instead of waiting for the swap, which is deferred while scrolling
|
|
193
|
+
[self revealOverlays];
|
|
194
|
+
[self scheduleSwap];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
- (void)mapViewSnapshotReady:(GMSMapView *)mapView {
|
|
198
|
+
// Stable per the SDK: tiles loaded, labels and overlays rendered
|
|
199
|
+
_tilesRendered = YES;
|
|
200
|
+
[self revealOverlays];
|
|
201
|
+
[self scheduleSwap];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@end
|