@lugg/maps 1.0.0-beta.8 → 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 +3 -23
- 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 +65 -11
- 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 +46 -18
- 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 +7 -4
- 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 +7 -4
- 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 +7 -4
- 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
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
#import "StaticMapProviderBase.h"
|
|
2
|
+
|
|
3
|
+
using facebook::react::LuggMapViewInsetAdjustment;
|
|
4
|
+
using facebook::react::LuggMapViewMapType;
|
|
5
|
+
using facebook::react::LuggMapViewPoiFilterMode;
|
|
6
|
+
using facebook::react::LuggMapViewTheme;
|
|
7
|
+
|
|
8
|
+
#import "../LuggCircleView.h"
|
|
9
|
+
#import "../LuggGroundOverlayView.h"
|
|
10
|
+
#import "../LuggMapWrapperView.h"
|
|
11
|
+
#import "../LuggMarkerView.h"
|
|
12
|
+
#import "../LuggPolygonView.h"
|
|
13
|
+
#import "../LuggPolylineView.h"
|
|
14
|
+
|
|
15
|
+
#pragma mark - Projection
|
|
16
|
+
|
|
17
|
+
CGPoint LuggStaticPointForCoordinate(MKMapRect mapRect, CGSize size,
|
|
18
|
+
CLLocationCoordinate2D coordinate) {
|
|
19
|
+
MKMapPoint point = MKMapPointForCoordinate(coordinate);
|
|
20
|
+
return CGPointMake(
|
|
21
|
+
(point.x - mapRect.origin.x) / mapRect.size.width * size.width,
|
|
22
|
+
(point.y - mapRect.origin.y) / mapRect.size.height * size.height);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#pragma mark - Shape overlay
|
|
26
|
+
|
|
27
|
+
// Draws polylines, polygons, circles and ground overlays over the base
|
|
28
|
+
// map with CoreGraphics; the base render is map tiles only
|
|
29
|
+
@interface LuggStaticShapeOverlayView : UIView
|
|
30
|
+
@property(nonatomic, assign) MKMapRect mapRect;
|
|
31
|
+
@property(nonatomic, assign) BOOL projectionReady;
|
|
32
|
+
@property(nonatomic, strong) NSMutableArray<LuggPolylineView *> *polylineViews;
|
|
33
|
+
@property(nonatomic, strong) NSMutableArray<LuggPolygonView *> *polygonViews;
|
|
34
|
+
@property(nonatomic, strong) NSMutableArray<LuggCircleView *> *circleViews;
|
|
35
|
+
@property(nonatomic, strong)
|
|
36
|
+
NSMutableArray<LuggGroundOverlayView *> *groundOverlayViews;
|
|
37
|
+
@property(nonatomic, strong)
|
|
38
|
+
NSMapTable<LuggGroundOverlayView *, UIImage *> *groundOverlayImages;
|
|
39
|
+
@end
|
|
40
|
+
|
|
41
|
+
@implementation LuggStaticShapeOverlayView
|
|
42
|
+
|
|
43
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
44
|
+
if (self = [super initWithFrame:frame]) {
|
|
45
|
+
self.opaque = NO;
|
|
46
|
+
self.backgroundColor = UIColor.clearColor;
|
|
47
|
+
self.userInteractionEnabled = NO;
|
|
48
|
+
self.contentMode = UIViewContentModeRedraw;
|
|
49
|
+
_polylineViews = [NSMutableArray array];
|
|
50
|
+
_polygonViews = [NSMutableArray array];
|
|
51
|
+
_circleViews = [NSMutableArray array];
|
|
52
|
+
_groundOverlayViews = [NSMutableArray array];
|
|
53
|
+
_groundOverlayImages = [NSMapTable weakToStrongObjectsMapTable];
|
|
54
|
+
}
|
|
55
|
+
return self;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static NSInteger shapeZIndex(UIView *shape) {
|
|
59
|
+
if ([shape isKindOfClass:[LuggPolylineView class]])
|
|
60
|
+
return ((LuggPolylineView *)shape).zIndex;
|
|
61
|
+
if ([shape isKindOfClass:[LuggPolygonView class]])
|
|
62
|
+
return ((LuggPolygonView *)shape).zIndex;
|
|
63
|
+
if ([shape isKindOfClass:[LuggCircleView class]])
|
|
64
|
+
return ((LuggCircleView *)shape).zIndex;
|
|
65
|
+
if ([shape isKindOfClass:[LuggGroundOverlayView class]])
|
|
66
|
+
return ((LuggGroundOverlayView *)shape).zIndex;
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate {
|
|
71
|
+
return LuggStaticPointForCoordinate(_mapRect, self.bounds.size, coordinate);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
- (void)addPath:(CGContextRef)context
|
|
75
|
+
ring:(NSArray<CLLocation *> *)coordinates {
|
|
76
|
+
for (NSUInteger i = 0; i < coordinates.count; i++) {
|
|
77
|
+
CGPoint point = [self pointForCoordinate:coordinates[i].coordinate];
|
|
78
|
+
if (i == 0) {
|
|
79
|
+
CGContextMoveToPoint(context, point.x, point.y);
|
|
80
|
+
} else {
|
|
81
|
+
CGContextAddLineToPoint(context, point.x, point.y);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
- (void)drawPolyline:(LuggPolylineView *)polylineView
|
|
87
|
+
inContext:(CGContextRef)context {
|
|
88
|
+
NSArray<CLLocation *> *coordinates = polylineView.coordinates;
|
|
89
|
+
UIColor *color = polylineView.strokeColors.firstObject;
|
|
90
|
+
if (coordinates.count < 2 || !color || polylineView.strokeWidth <= 0)
|
|
91
|
+
return;
|
|
92
|
+
|
|
93
|
+
CGContextSaveGState(context);
|
|
94
|
+
[self addPath:context ring:coordinates];
|
|
95
|
+
CGContextSetLineWidth(context, polylineView.strokeWidth);
|
|
96
|
+
CGContextSetLineCap(context, kCGLineCapRound);
|
|
97
|
+
CGContextSetLineJoin(context, kCGLineJoinRound);
|
|
98
|
+
CGContextSetStrokeColorWithColor(context, color.CGColor);
|
|
99
|
+
CGContextStrokePath(context);
|
|
100
|
+
CGContextRestoreGState(context);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
- (void)drawPolygon:(LuggPolygonView *)polygonView
|
|
104
|
+
inContext:(CGContextRef)context {
|
|
105
|
+
NSArray<CLLocation *> *coordinates = polygonView.coordinates;
|
|
106
|
+
if (coordinates.count < 3)
|
|
107
|
+
return;
|
|
108
|
+
|
|
109
|
+
CGContextSaveGState(context);
|
|
110
|
+
|
|
111
|
+
if (polygonView.fillColor) {
|
|
112
|
+
[self addPath:context ring:coordinates];
|
|
113
|
+
CGContextClosePath(context);
|
|
114
|
+
for (NSArray<CLLocation *> *hole in polygonView.holes) {
|
|
115
|
+
[self addPath:context ring:hole];
|
|
116
|
+
CGContextClosePath(context);
|
|
117
|
+
}
|
|
118
|
+
CGContextSetFillColorWithColor(context, polygonView.fillColor.CGColor);
|
|
119
|
+
CGContextEOFillPath(context);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (polygonView.strokeColor && polygonView.strokeWidth > 0) {
|
|
123
|
+
[self addPath:context ring:coordinates];
|
|
124
|
+
CGContextClosePath(context);
|
|
125
|
+
for (NSArray<CLLocation *> *hole in polygonView.holes) {
|
|
126
|
+
[self addPath:context ring:hole];
|
|
127
|
+
CGContextClosePath(context);
|
|
128
|
+
}
|
|
129
|
+
CGContextSetLineWidth(context, polygonView.strokeWidth);
|
|
130
|
+
CGContextSetLineJoin(context, kCGLineJoinRound);
|
|
131
|
+
CGContextSetStrokeColorWithColor(context, polygonView.strokeColor.CGColor);
|
|
132
|
+
CGContextStrokePath(context);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
CGContextRestoreGState(context);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
- (void)drawCircle:(LuggCircleView *)circleView
|
|
139
|
+
inContext:(CGContextRef)context {
|
|
140
|
+
if (circleView.radius <= 0)
|
|
141
|
+
return;
|
|
142
|
+
|
|
143
|
+
CGPoint center = [self pointForCoordinate:circleView.center];
|
|
144
|
+
double pointsPerMapPoint = self.bounds.size.width / _mapRect.size.width;
|
|
145
|
+
double radius = circleView.radius *
|
|
146
|
+
MKMapPointsPerMeterAtLatitude(circleView.center.latitude) *
|
|
147
|
+
pointsPerMapPoint;
|
|
148
|
+
CGRect ellipse =
|
|
149
|
+
CGRectMake(center.x - radius, center.y - radius, radius * 2, radius * 2);
|
|
150
|
+
|
|
151
|
+
CGContextSaveGState(context);
|
|
152
|
+
if (circleView.fillColor) {
|
|
153
|
+
CGContextSetFillColorWithColor(context, circleView.fillColor.CGColor);
|
|
154
|
+
CGContextFillEllipseInRect(context, ellipse);
|
|
155
|
+
}
|
|
156
|
+
if (circleView.strokeColor && circleView.strokeWidth > 0) {
|
|
157
|
+
CGContextSetLineWidth(context, circleView.strokeWidth);
|
|
158
|
+
CGContextSetStrokeColorWithColor(context, circleView.strokeColor.CGColor);
|
|
159
|
+
CGContextStrokeEllipseInRect(context, ellipse);
|
|
160
|
+
}
|
|
161
|
+
CGContextRestoreGState(context);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
- (void)drawGroundOverlay:(LuggGroundOverlayView *)groundOverlayView {
|
|
165
|
+
UIImage *image = [_groundOverlayImages objectForKey:groundOverlayView];
|
|
166
|
+
if (!image)
|
|
167
|
+
return;
|
|
168
|
+
|
|
169
|
+
CGPoint topLeft =
|
|
170
|
+
[self pointForCoordinate:CLLocationCoordinate2DMake(
|
|
171
|
+
groundOverlayView.northeast.latitude,
|
|
172
|
+
groundOverlayView.southwest.longitude)];
|
|
173
|
+
CGPoint bottomRight =
|
|
174
|
+
[self pointForCoordinate:CLLocationCoordinate2DMake(
|
|
175
|
+
groundOverlayView.southwest.latitude,
|
|
176
|
+
groundOverlayView.northeast.longitude)];
|
|
177
|
+
CGRect rect = CGRectMake(topLeft.x, topLeft.y, bottomRight.x - topLeft.x,
|
|
178
|
+
bottomRight.y - topLeft.y);
|
|
179
|
+
[image drawInRect:rect
|
|
180
|
+
blendMode:kCGBlendModeNormal
|
|
181
|
+
alpha:groundOverlayView.opacity];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
- (void)drawRect:(CGRect)rect {
|
|
185
|
+
if (!_projectionReady)
|
|
186
|
+
return;
|
|
187
|
+
|
|
188
|
+
CGContextRef context = UIGraphicsGetCurrentContext();
|
|
189
|
+
if (!context)
|
|
190
|
+
return;
|
|
191
|
+
|
|
192
|
+
NSMutableArray<UIView *> *shapes = [NSMutableArray array];
|
|
193
|
+
[shapes addObjectsFromArray:_groundOverlayViews];
|
|
194
|
+
[shapes addObjectsFromArray:_polygonViews];
|
|
195
|
+
[shapes addObjectsFromArray:_circleViews];
|
|
196
|
+
[shapes addObjectsFromArray:_polylineViews];
|
|
197
|
+
NSArray<UIView *> *ordered =
|
|
198
|
+
[shapes sortedArrayWithOptions:NSSortStable
|
|
199
|
+
usingComparator:^NSComparisonResult(UIView *a, UIView *b) {
|
|
200
|
+
NSInteger za = shapeZIndex(a), zb = shapeZIndex(b);
|
|
201
|
+
if (za < zb)
|
|
202
|
+
return NSOrderedAscending;
|
|
203
|
+
if (za > zb)
|
|
204
|
+
return NSOrderedDescending;
|
|
205
|
+
return NSOrderedSame;
|
|
206
|
+
}];
|
|
207
|
+
|
|
208
|
+
for (UIView *shape in ordered) {
|
|
209
|
+
if ([shape isKindOfClass:[LuggGroundOverlayView class]]) {
|
|
210
|
+
[self drawGroundOverlay:(LuggGroundOverlayView *)shape];
|
|
211
|
+
} else if ([shape isKindOfClass:[LuggPolygonView class]]) {
|
|
212
|
+
[self drawPolygon:(LuggPolygonView *)shape inContext:context];
|
|
213
|
+
} else if ([shape isKindOfClass:[LuggCircleView class]]) {
|
|
214
|
+
[self drawCircle:(LuggCircleView *)shape inContext:context];
|
|
215
|
+
} else if ([shape isKindOfClass:[LuggPolylineView class]]) {
|
|
216
|
+
[self drawPolyline:(LuggPolylineView *)shape inContext:context];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@end
|
|
222
|
+
|
|
223
|
+
#pragma mark - Provider base
|
|
224
|
+
|
|
225
|
+
@interface StaticMapProviderBase () <
|
|
226
|
+
LuggMarkerViewDelegate, LuggPolylineViewDelegate, LuggPolygonViewDelegate,
|
|
227
|
+
LuggCircleViewDelegate, LuggGroundOverlayViewDelegate>
|
|
228
|
+
@end
|
|
229
|
+
|
|
230
|
+
@implementation StaticMapProviderBase {
|
|
231
|
+
BOOL _isMapReady;
|
|
232
|
+
BOOL _baseRenderDone;
|
|
233
|
+
BOOL _overlaysRevealed;
|
|
234
|
+
|
|
235
|
+
UIImageView *_baseImageView;
|
|
236
|
+
LuggStaticShapeOverlayView *_shapeOverlayView;
|
|
237
|
+
NSMutableArray<LuggMarkerView *> *_markerViews;
|
|
238
|
+
NSMapTable<LuggMarkerView *, UIView *> *_defaultMarkerOverlays;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
@synthesize delegate = _delegate;
|
|
242
|
+
@synthesize staticMode = _staticMode;
|
|
243
|
+
|
|
244
|
+
- (instancetype)init {
|
|
245
|
+
if (self = [super init]) {
|
|
246
|
+
_markerViews = [NSMutableArray array];
|
|
247
|
+
_defaultMarkerOverlays = [NSMapTable weakToStrongObjectsMapTable];
|
|
248
|
+
_mapType = LuggMapViewMapType::Standard;
|
|
249
|
+
_theme = LuggMapViewTheme::System;
|
|
250
|
+
}
|
|
251
|
+
return self;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
- (BOOL)isMapReady {
|
|
255
|
+
return _isMapReady;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
#pragma mark - MapProvider
|
|
259
|
+
|
|
260
|
+
- (void)initializeMapInView:(UIView *)wrapperView
|
|
261
|
+
initialCoordinate:(CLLocationCoordinate2D)coordinate
|
|
262
|
+
initialZoom:(double)zoom {
|
|
263
|
+
if (_wrapperView)
|
|
264
|
+
return;
|
|
265
|
+
|
|
266
|
+
_wrapperView = wrapperView;
|
|
267
|
+
_coordinate = coordinate;
|
|
268
|
+
_zoom = zoom;
|
|
269
|
+
|
|
270
|
+
_shapeOverlayView =
|
|
271
|
+
[[LuggStaticShapeOverlayView alloc] initWithFrame:wrapperView.bounds];
|
|
272
|
+
_shapeOverlayView.autoresizingMask =
|
|
273
|
+
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
274
|
+
// Overlays stay hidden over the placeholder and reveal with the base map
|
|
275
|
+
_shapeOverlayView.hidden = YES;
|
|
276
|
+
[wrapperView addSubview:_shapeOverlayView];
|
|
277
|
+
|
|
278
|
+
[self updateProjection];
|
|
279
|
+
|
|
280
|
+
__weak StaticMapProviderBase *weakSelf = self;
|
|
281
|
+
if ([wrapperView isKindOfClass:[LuggMapWrapperView class]]) {
|
|
282
|
+
((LuggMapWrapperView *)wrapperView).layoutHandler = ^{
|
|
283
|
+
[weakSelf wrapperDidLayout];
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
_isMapReady = YES;
|
|
288
|
+
|
|
289
|
+
// Start after the current mount pass so map settings props applied right
|
|
290
|
+
// after this call shape the base render
|
|
291
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
292
|
+
[weakSelf renderBaseMapIfNeeded];
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
- (void)destroy {
|
|
297
|
+
[self cancelBaseRender];
|
|
298
|
+
|
|
299
|
+
if ([_wrapperView isKindOfClass:[LuggMapWrapperView class]]) {
|
|
300
|
+
((LuggMapWrapperView *)_wrapperView).layoutHandler = nil;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
for (LuggMarkerView *markerView in _markerViews) {
|
|
304
|
+
markerView.delegate = nil;
|
|
305
|
+
UIView *iconView = markerView.iconView;
|
|
306
|
+
if (iconView.superview == _wrapperView) {
|
|
307
|
+
[iconView removeFromSuperview];
|
|
308
|
+
}
|
|
309
|
+
[markerView resetIconViewTransform];
|
|
310
|
+
}
|
|
311
|
+
[_markerViews removeAllObjects];
|
|
312
|
+
for (UIView *pin in _defaultMarkerOverlays.objectEnumerator) {
|
|
313
|
+
[pin removeFromSuperview];
|
|
314
|
+
}
|
|
315
|
+
[_defaultMarkerOverlays removeAllObjects];
|
|
316
|
+
|
|
317
|
+
for (LuggPolylineView *view in _shapeOverlayView.polylineViews) {
|
|
318
|
+
view.delegate = nil;
|
|
319
|
+
}
|
|
320
|
+
for (LuggPolygonView *view in _shapeOverlayView.polygonViews) {
|
|
321
|
+
view.delegate = nil;
|
|
322
|
+
}
|
|
323
|
+
for (LuggCircleView *view in _shapeOverlayView.circleViews) {
|
|
324
|
+
view.delegate = nil;
|
|
325
|
+
}
|
|
326
|
+
for (LuggGroundOverlayView *view in _shapeOverlayView.groundOverlayViews) {
|
|
327
|
+
view.delegate = nil;
|
|
328
|
+
}
|
|
329
|
+
[_shapeOverlayView removeFromSuperview];
|
|
330
|
+
_shapeOverlayView = nil;
|
|
331
|
+
|
|
332
|
+
[_baseImageView removeFromSuperview];
|
|
333
|
+
_baseImageView = nil;
|
|
334
|
+
|
|
335
|
+
_baseRenderDone = NO;
|
|
336
|
+
_overlaysRevealed = NO;
|
|
337
|
+
_projectionReady = NO;
|
|
338
|
+
_wrapperView = nil;
|
|
339
|
+
_isMapReady = NO;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
#pragma mark - Base render
|
|
343
|
+
|
|
344
|
+
- (void)updateProjection {
|
|
345
|
+
CGSize size = _wrapperView.bounds.size;
|
|
346
|
+
if (size.width <= 0 || size.height <= 0)
|
|
347
|
+
return;
|
|
348
|
+
|
|
349
|
+
_projectedSize = size;
|
|
350
|
+
_mapRect = [self mapRectForSize:size];
|
|
351
|
+
|
|
352
|
+
// Edge insets shift the visible center like a live map: the coordinate
|
|
353
|
+
// lands at the center of the inset viewport instead of the view center
|
|
354
|
+
CGFloat offsetX = (_edgeInsets.left - _edgeInsets.right) / 2.0;
|
|
355
|
+
CGFloat offsetY = (_edgeInsets.top - _edgeInsets.bottom) / 2.0;
|
|
356
|
+
_mapRect.origin.x -= offsetX * _mapRect.size.width / size.width;
|
|
357
|
+
_mapRect.origin.y -= offsetY * _mapRect.size.height / size.height;
|
|
358
|
+
|
|
359
|
+
_projectionReady = YES;
|
|
360
|
+
|
|
361
|
+
_shapeOverlayView.mapRect = _mapRect;
|
|
362
|
+
_shapeOverlayView.projectionReady = YES;
|
|
363
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
364
|
+
|
|
365
|
+
for (LuggMarkerView *markerView in _markerViews) {
|
|
366
|
+
[self positionMarkerOverlay:markerView];
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// A static map can't autoresize like a live map; when the wrapper gets its
|
|
371
|
+
// first real size (or resizes later) recompute the projection and
|
|
372
|
+
// (re)render the base map at the new size
|
|
373
|
+
- (void)wrapperDidLayout {
|
|
374
|
+
CGSize size = _wrapperView.bounds.size;
|
|
375
|
+
if (size.width <= 0 || size.height <= 0 ||
|
|
376
|
+
CGSizeEqualToSize(size, _projectedSize))
|
|
377
|
+
return;
|
|
378
|
+
|
|
379
|
+
[self updateProjection];
|
|
380
|
+
|
|
381
|
+
// Any in-flight or completed base render targets the old size
|
|
382
|
+
[self cancelBaseRender];
|
|
383
|
+
_baseRenderDone = NO;
|
|
384
|
+
self.cachedBaseImage = nil;
|
|
385
|
+
|
|
386
|
+
[self renderBaseMapIfNeeded];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Camera commands invalidate any cached, displayed or in-flight base
|
|
390
|
+
// render and re-render at the new camera
|
|
391
|
+
- (void)rerenderBaseMap {
|
|
392
|
+
[self cancelBaseRender];
|
|
393
|
+
_baseRenderDone = NO;
|
|
394
|
+
self.cachedBaseImage = nil;
|
|
395
|
+
|
|
396
|
+
// Without a layout yet, the first layout renders with the new camera
|
|
397
|
+
if (!_projectionReady)
|
|
398
|
+
return;
|
|
399
|
+
|
|
400
|
+
[self updateProjection];
|
|
401
|
+
|
|
402
|
+
__weak StaticMapProviderBase *weakSelf = self;
|
|
403
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
404
|
+
[weakSelf renderBaseMapIfNeeded];
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
- (void)renderBaseMapIfNeeded {
|
|
409
|
+
if (_baseRenderDone || !_projectionReady || !_wrapperView)
|
|
410
|
+
return;
|
|
411
|
+
|
|
412
|
+
if (self.cachedBaseImage) {
|
|
413
|
+
[self displayBaseImage:self.cachedBaseImage fromCache:YES];
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
[self renderBaseMap];
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
- (void)displayBaseImage:(UIImage *)image fromCache:(BOOL)fromCache {
|
|
421
|
+
if (!_baseImageView) {
|
|
422
|
+
_baseImageView = [[UIImageView alloc] initWithFrame:_wrapperView.bounds];
|
|
423
|
+
_baseImageView.autoresizingMask =
|
|
424
|
+
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
425
|
+
[_wrapperView insertSubview:_baseImageView atIndex:0];
|
|
426
|
+
}
|
|
427
|
+
_baseImageView.image = image;
|
|
428
|
+
_baseRenderDone = YES;
|
|
429
|
+
[self revealOverlays];
|
|
430
|
+
|
|
431
|
+
if (!fromCache) {
|
|
432
|
+
[_delegate mapProviderDidCaptureStaticImage:image];
|
|
433
|
+
}
|
|
434
|
+
[_delegate mapProviderDidFinishStaticSnapshot];
|
|
435
|
+
[_delegate mapProviderDidReady];
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
- (void)revealOverlays {
|
|
439
|
+
if (_overlaysRevealed)
|
|
440
|
+
return;
|
|
441
|
+
_overlaysRevealed = YES;
|
|
442
|
+
_shapeOverlayView.hidden = NO;
|
|
443
|
+
for (LuggMarkerView *markerView in _markerViews) {
|
|
444
|
+
[self positionMarkerOverlay:markerView];
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
#pragma mark - Subclass hooks
|
|
449
|
+
|
|
450
|
+
- (MKMapRect)mapRectForSize:(CGSize)size {
|
|
451
|
+
return MKMapRectNull;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
- (UIView *)newDefaultMarkerOverlayView {
|
|
455
|
+
return [[UIView alloc] init];
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
- (double)zoomForMapPointsPerPoint:(double)mapPointsPerPoint {
|
|
459
|
+
return _zoom;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
- (void)renderBaseMap {
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
- (void)cancelBaseRender {
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
#pragma mark - Props
|
|
469
|
+
|
|
470
|
+
- (void)setMapType:(LuggMapViewMapType)mapType {
|
|
471
|
+
_mapType = mapType;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
- (void)setTheme:(LuggMapViewTheme)theme {
|
|
475
|
+
_theme = theme;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Interaction and camera props don't apply to a static map
|
|
479
|
+
- (void)setInsetAdjustment:(LuggMapViewInsetAdjustment)insetAdjustment {
|
|
480
|
+
}
|
|
481
|
+
- (void)setZoomEnabled:(BOOL)enabled {
|
|
482
|
+
}
|
|
483
|
+
- (void)setScrollEnabled:(BOOL)enabled {
|
|
484
|
+
}
|
|
485
|
+
- (void)setRotateEnabled:(BOOL)enabled {
|
|
486
|
+
}
|
|
487
|
+
- (void)setPitchEnabled:(BOOL)enabled {
|
|
488
|
+
}
|
|
489
|
+
- (void)setCompassEnabled:(BOOL)enabled {
|
|
490
|
+
}
|
|
491
|
+
- (void)setUserLocationEnabled:(BOOL)enabled {
|
|
492
|
+
}
|
|
493
|
+
- (void)setMinZoom:(double)minZoom {
|
|
494
|
+
}
|
|
495
|
+
- (void)setMaxZoom:(double)maxZoom {
|
|
496
|
+
}
|
|
497
|
+
- (void)setPoiEnabled:(BOOL)enabled {
|
|
498
|
+
}
|
|
499
|
+
- (void)setPoiFilterMode:(LuggMapViewPoiFilterMode)mode {
|
|
500
|
+
}
|
|
501
|
+
- (void)setPoiFilterCategories:(NSArray<NSString *> *)categories {
|
|
502
|
+
}
|
|
503
|
+
- (void)setEdgeInsets:(UIEdgeInsets)edgeInsets
|
|
504
|
+
oldEdgeInsets:(UIEdgeInsets)oldEdgeInsets {
|
|
505
|
+
if (UIEdgeInsetsEqualToEdgeInsets(_edgeInsets, edgeInsets))
|
|
506
|
+
return;
|
|
507
|
+
_edgeInsets = edgeInsets;
|
|
508
|
+
|
|
509
|
+
if (!_projectionReady)
|
|
510
|
+
return;
|
|
511
|
+
|
|
512
|
+
[self updateProjection];
|
|
513
|
+
|
|
514
|
+
// Any in-flight or completed base render targets the old projection. The
|
|
515
|
+
// initial prop set lands before the first render, so the cached image
|
|
516
|
+
// (keyed on insets) stays valid.
|
|
517
|
+
[self cancelBaseRender];
|
|
518
|
+
if (_baseRenderDone) {
|
|
519
|
+
_baseRenderDone = NO;
|
|
520
|
+
self.cachedBaseImage = nil;
|
|
521
|
+
}
|
|
522
|
+
__weak StaticMapProviderBase *weakSelf = self;
|
|
523
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
524
|
+
[weakSelf renderBaseMapIfNeeded];
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Static maps don't animate; apply the final insets directly
|
|
529
|
+
- (void)setEdgeInsets:(UIEdgeInsets)edgeInsets
|
|
530
|
+
oldEdgeInsets:(UIEdgeInsets)oldEdgeInsets
|
|
531
|
+
duration:(double)duration {
|
|
532
|
+
[self setEdgeInsets:edgeInsets oldEdgeInsets:oldEdgeInsets];
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
#pragma mark - Markers
|
|
536
|
+
|
|
537
|
+
// Markers are live views over the base map (like Android lite mode's live
|
|
538
|
+
// markers), so async content such as remote images shows up when it loads
|
|
539
|
+
|
|
540
|
+
- (void)addMarkerView:(LuggMarkerView *)markerView {
|
|
541
|
+
markerView.delegate = self;
|
|
542
|
+
if (![_markerViews containsObject:markerView]) {
|
|
543
|
+
[_markerViews addObject:markerView];
|
|
544
|
+
}
|
|
545
|
+
[self syncMarkerView:markerView];
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
- (void)removeMarkerView:(LuggMarkerView *)markerView {
|
|
549
|
+
markerView.delegate = nil;
|
|
550
|
+
[_markerViews removeObject:markerView];
|
|
551
|
+
|
|
552
|
+
UIView *pin = [_defaultMarkerOverlays objectForKey:markerView];
|
|
553
|
+
if (pin) {
|
|
554
|
+
[pin removeFromSuperview];
|
|
555
|
+
[_defaultMarkerOverlays removeObjectForKey:markerView];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
UIView *iconView = markerView.iconView;
|
|
559
|
+
if (iconView.superview == _wrapperView) {
|
|
560
|
+
[iconView removeFromSuperview];
|
|
561
|
+
}
|
|
562
|
+
[markerView resetIconViewTransform];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
- (void)syncMarkerView:(LuggMarkerView *)markerView {
|
|
566
|
+
if (!_wrapperView)
|
|
567
|
+
return;
|
|
568
|
+
|
|
569
|
+
UIView *pin = [_defaultMarkerOverlays objectForKey:markerView];
|
|
570
|
+
|
|
571
|
+
if (markerView.hasCustomView) {
|
|
572
|
+
if (pin) {
|
|
573
|
+
[pin removeFromSuperview];
|
|
574
|
+
[_defaultMarkerOverlays removeObjectForKey:markerView];
|
|
575
|
+
}
|
|
576
|
+
UIView *iconView = markerView.iconView;
|
|
577
|
+
if (iconView.superview != _wrapperView) {
|
|
578
|
+
[iconView removeFromSuperview];
|
|
579
|
+
iconView.userInteractionEnabled = NO;
|
|
580
|
+
// Hidden until the base map displays; positionMarkerOverlay shows it
|
|
581
|
+
iconView.hidden = YES;
|
|
582
|
+
[_wrapperView addSubview:iconView];
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
UIView *iconView = markerView.iconView;
|
|
586
|
+
if (iconView.superview == _wrapperView) {
|
|
587
|
+
[iconView removeFromSuperview];
|
|
588
|
+
[markerView resetIconViewTransform];
|
|
589
|
+
}
|
|
590
|
+
if (!pin) {
|
|
591
|
+
pin = [self newDefaultMarkerOverlayView];
|
|
592
|
+
pin.userInteractionEnabled = NO;
|
|
593
|
+
pin.hidden = YES;
|
|
594
|
+
[_wrapperView addSubview:pin];
|
|
595
|
+
[_defaultMarkerOverlays setObject:pin forKey:markerView];
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
[self positionMarkerOverlay:markerView];
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
- (void)positionMarkerOverlay:(LuggMarkerView *)markerView {
|
|
603
|
+
if (!_projectionReady)
|
|
604
|
+
return;
|
|
605
|
+
|
|
606
|
+
CGPoint point = LuggStaticPointForCoordinate(_mapRect, _projectedSize,
|
|
607
|
+
markerView.coordinate);
|
|
608
|
+
|
|
609
|
+
if (markerView.hasCustomView) {
|
|
610
|
+
UIView *iconView = markerView.iconView;
|
|
611
|
+
CGSize size = iconView.bounds.size;
|
|
612
|
+
if (size.width <= 0 || size.height <= 0) {
|
|
613
|
+
// Not laid out yet; markerViewDidLayout re-runs this
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
CGFloat scale = markerView.scale;
|
|
617
|
+
CGFloat radians = markerView.rotate * M_PI / 180.0;
|
|
618
|
+
iconView.layer.anchorPoint = markerView.anchor;
|
|
619
|
+
iconView.transform =
|
|
620
|
+
CGAffineTransformConcat(CGAffineTransformMakeScale(scale, scale),
|
|
621
|
+
CGAffineTransformMakeRotation(radians));
|
|
622
|
+
// center positions the layer's anchorPoint, so the marker's anchor
|
|
623
|
+
// lands exactly on the projected coordinate
|
|
624
|
+
iconView.center = point;
|
|
625
|
+
iconView.layer.zPosition = markerView.zIndex;
|
|
626
|
+
iconView.hidden = !_overlaysRevealed;
|
|
627
|
+
} else {
|
|
628
|
+
UIView *pin = [_defaultMarkerOverlays objectForKey:markerView];
|
|
629
|
+
pin.layer.anchorPoint = CGPointMake(0.5, 1.0);
|
|
630
|
+
pin.center = point;
|
|
631
|
+
pin.layer.zPosition = markerView.zIndex;
|
|
632
|
+
pin.hidden = !_overlaysRevealed;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
#pragma mark - LuggMarkerViewDelegate
|
|
637
|
+
|
|
638
|
+
- (void)markerViewDidLayout:(LuggMarkerView *)markerView {
|
|
639
|
+
[self syncMarkerView:markerView];
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
- (void)markerViewDidUpdate:(LuggMarkerView *)markerView {
|
|
643
|
+
[self syncMarkerView:markerView];
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
#pragma mark - Shapes
|
|
647
|
+
|
|
648
|
+
- (void)addPolylineView:(LuggPolylineView *)polylineView {
|
|
649
|
+
polylineView.delegate = self;
|
|
650
|
+
if (![_shapeOverlayView.polylineViews containsObject:polylineView]) {
|
|
651
|
+
[_shapeOverlayView.polylineViews addObject:polylineView];
|
|
652
|
+
}
|
|
653
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
- (void)removePolylineView:(LuggPolylineView *)polylineView {
|
|
657
|
+
polylineView.delegate = nil;
|
|
658
|
+
[_shapeOverlayView.polylineViews removeObject:polylineView];
|
|
659
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
- (void)syncPolylineView:(LuggPolylineView *)polylineView {
|
|
663
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
- (void)polylineViewDidUpdate:(LuggPolylineView *)polylineView {
|
|
667
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
- (void)addPolygonView:(LuggPolygonView *)polygonView {
|
|
671
|
+
polygonView.delegate = self;
|
|
672
|
+
if (![_shapeOverlayView.polygonViews containsObject:polygonView]) {
|
|
673
|
+
[_shapeOverlayView.polygonViews addObject:polygonView];
|
|
674
|
+
}
|
|
675
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
- (void)removePolygonView:(LuggPolygonView *)polygonView {
|
|
679
|
+
polygonView.delegate = nil;
|
|
680
|
+
[_shapeOverlayView.polygonViews removeObject:polygonView];
|
|
681
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
- (void)syncPolygonView:(LuggPolygonView *)polygonView {
|
|
685
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
- (void)polygonViewDidUpdate:(LuggPolygonView *)polygonView {
|
|
689
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
- (void)addCircleView:(LuggCircleView *)circleView {
|
|
693
|
+
circleView.delegate = self;
|
|
694
|
+
if (![_shapeOverlayView.circleViews containsObject:circleView]) {
|
|
695
|
+
[_shapeOverlayView.circleViews addObject:circleView];
|
|
696
|
+
}
|
|
697
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
- (void)removeCircleView:(LuggCircleView *)circleView {
|
|
701
|
+
circleView.delegate = nil;
|
|
702
|
+
[_shapeOverlayView.circleViews removeObject:circleView];
|
|
703
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
- (void)syncCircleView:(LuggCircleView *)circleView {
|
|
707
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
- (void)circleViewDidUpdate:(LuggCircleView *)circleView {
|
|
711
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
- (void)addGroundOverlayView:(LuggGroundOverlayView *)groundOverlayView {
|
|
715
|
+
groundOverlayView.delegate = self;
|
|
716
|
+
if (![_shapeOverlayView.groundOverlayViews
|
|
717
|
+
containsObject:groundOverlayView]) {
|
|
718
|
+
[_shapeOverlayView.groundOverlayViews addObject:groundOverlayView];
|
|
719
|
+
}
|
|
720
|
+
[self loadGroundOverlayImage:groundOverlayView];
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
- (void)removeGroundOverlayView:(LuggGroundOverlayView *)groundOverlayView {
|
|
724
|
+
groundOverlayView.delegate = nil;
|
|
725
|
+
[_shapeOverlayView.groundOverlayViews removeObject:groundOverlayView];
|
|
726
|
+
[_shapeOverlayView.groundOverlayImages removeObjectForKey:groundOverlayView];
|
|
727
|
+
[_shapeOverlayView setNeedsDisplay];
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
- (void)syncGroundOverlayView:(LuggGroundOverlayView *)groundOverlayView {
|
|
731
|
+
[self loadGroundOverlayImage:groundOverlayView];
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
- (void)groundOverlayViewDidUpdate:(LuggGroundOverlayView *)groundOverlayView {
|
|
735
|
+
[self loadGroundOverlayImage:groundOverlayView];
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
- (void)loadGroundOverlayImage:(LuggGroundOverlayView *)groundOverlayView {
|
|
739
|
+
NSURL *url = [NSURL URLWithString:groundOverlayView.imageUri];
|
|
740
|
+
if (!url)
|
|
741
|
+
return;
|
|
742
|
+
|
|
743
|
+
__weak StaticMapProviderBase *weakSelf = self;
|
|
744
|
+
__weak LuggGroundOverlayView *weakOverlayView = groundOverlayView;
|
|
745
|
+
dispatch_async(
|
|
746
|
+
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
747
|
+
NSData *data = [NSData dataWithContentsOfURL:url];
|
|
748
|
+
UIImage *image = data ? [UIImage imageWithData:data] : nil;
|
|
749
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
750
|
+
StaticMapProviderBase *strongSelf = weakSelf;
|
|
751
|
+
LuggGroundOverlayView *overlayView = weakOverlayView;
|
|
752
|
+
if (!strongSelf || !overlayView || !image)
|
|
753
|
+
return;
|
|
754
|
+
LuggStaticShapeOverlayView *shapeView = strongSelf->_shapeOverlayView;
|
|
755
|
+
if (![shapeView.groundOverlayViews containsObject:overlayView])
|
|
756
|
+
return;
|
|
757
|
+
[shapeView.groundOverlayImages setObject:image forKey:overlayView];
|
|
758
|
+
[shapeView setNeedsDisplay];
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Tile overlays need a live map to fetch and compose tiles; unsupported on
|
|
764
|
+
// static maps
|
|
765
|
+
- (void)addTileOverlayView:(LuggTileOverlayView *)tileOverlayView {
|
|
766
|
+
}
|
|
767
|
+
- (void)removeTileOverlayView:(LuggTileOverlayView *)tileOverlayView {
|
|
768
|
+
}
|
|
769
|
+
- (void)syncTileOverlayView:(LuggTileOverlayView *)tileOverlayView {
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
#pragma mark - Lifecycle
|
|
773
|
+
|
|
774
|
+
- (void)pauseAnimations {
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
- (void)resumeAnimations {
|
|
778
|
+
// Retry a failed or cancelled base render (e.g. device back online)
|
|
779
|
+
[self renderBaseMapIfNeeded];
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
#pragma mark - Commands
|
|
783
|
+
|
|
784
|
+
// Static maps don't animate; commands re-render once at the final camera
|
|
785
|
+
|
|
786
|
+
- (void)moveCamera:(double)latitude
|
|
787
|
+
longitude:(double)longitude
|
|
788
|
+
zoom:(double)zoom
|
|
789
|
+
duration:(double)duration {
|
|
790
|
+
_coordinate = CLLocationCoordinate2DMake(latitude, longitude);
|
|
791
|
+
if (zoom > 0) {
|
|
792
|
+
_zoom = zoom;
|
|
793
|
+
}
|
|
794
|
+
[self rerenderBaseMap];
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
- (void)fitCoordinates:(NSArray *)coordinates
|
|
798
|
+
edgeInsetsTop:(double)edgeInsetsTop
|
|
799
|
+
edgeInsetsLeft:(double)edgeInsetsLeft
|
|
800
|
+
edgeInsetsBottom:(double)edgeInsetsBottom
|
|
801
|
+
edgeInsetsRight:(double)edgeInsetsRight
|
|
802
|
+
duration:(double)duration {
|
|
803
|
+
if (coordinates.count == 0 || !_projectionReady)
|
|
804
|
+
return;
|
|
805
|
+
|
|
806
|
+
MKMapRect boundsRect = MKMapRectNull;
|
|
807
|
+
for (NSDictionary *coordinate in coordinates) {
|
|
808
|
+
MKMapPoint point = MKMapPointForCoordinate(
|
|
809
|
+
CLLocationCoordinate2DMake([coordinate[@"latitude"] doubleValue],
|
|
810
|
+
[coordinate[@"longitude"] doubleValue]));
|
|
811
|
+
boundsRect =
|
|
812
|
+
MKMapRectUnion(boundsRect, MKMapRectMake(point.x, point.y, 0, 0));
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
double availWidth =
|
|
816
|
+
MAX(_projectedSize.width - _edgeInsets.left - _edgeInsets.right -
|
|
817
|
+
edgeInsetsLeft - edgeInsetsRight,
|
|
818
|
+
1.0);
|
|
819
|
+
double availHeight =
|
|
820
|
+
MAX(_projectedSize.height - _edgeInsets.top - _edgeInsets.bottom -
|
|
821
|
+
edgeInsetsTop - edgeInsetsBottom,
|
|
822
|
+
1.0);
|
|
823
|
+
|
|
824
|
+
// Map points per view point at which the bounds fit the padded viewport
|
|
825
|
+
double scale = MAX(boundsRect.size.width / availWidth,
|
|
826
|
+
boundsRect.size.height / availHeight);
|
|
827
|
+
MKMapPoint center = MKMapPointMake(MKMapRectGetMidX(boundsRect),
|
|
828
|
+
MKMapRectGetMidY(boundsRect));
|
|
829
|
+
if (scale > 0) {
|
|
830
|
+
_coordinate = MKCoordinateForMapPoint(center);
|
|
831
|
+
_zoom = [self zoomForMapPointsPerPoint:scale];
|
|
832
|
+
} else {
|
|
833
|
+
// Coincident coordinates; keep the current zoom
|
|
834
|
+
scale = _mapRect.size.width / _projectedSize.width;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Asymmetric padding shifts the visible center, like edge insets
|
|
838
|
+
center.x -= (edgeInsetsLeft - edgeInsetsRight) / 2.0 * scale;
|
|
839
|
+
center.y -= (edgeInsetsTop - edgeInsetsBottom) / 2.0 * scale;
|
|
840
|
+
_coordinate = MKCoordinateForMapPoint(center);
|
|
841
|
+
|
|
842
|
+
[self rerenderBaseMap];
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
@end
|