@lugg/maps 1.0.0-beta.19 → 1.0.0-beta.20

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.
@@ -49,6 +49,7 @@ import com.luggmaps.extensions.findViewByTag
49
49
  import java.net.URL
50
50
  import kotlin.math.atan
51
51
  import kotlin.math.ln
52
+ import kotlin.math.log2
52
53
  import kotlin.math.pow
53
54
  import kotlin.math.sin
54
55
  import kotlin.math.sinh
@@ -866,13 +867,11 @@ class GoogleMapProvider(private val context: Context) :
866
867
  return
867
868
  }
868
869
 
869
- polylineView.polyline?.width = polylineView.strokeWidth.dpToPx()
870
- polylineView.polyline?.zIndex = polylineView.zIndex
871
-
872
870
  polylineAnimators[polylineView]?.apply {
873
871
  coordinates = polylineView.coordinates
874
872
  strokeColors = polylineView.strokeColors
875
873
  strokeWidth = polylineView.strokeWidth.dpToPx()
874
+ zIndex = polylineView.zIndex
876
875
  animatedOptions = polylineView.animatedOptions
877
876
  animated = polylineView.animated && !staticMode
878
877
  update()
@@ -902,6 +901,7 @@ class GoogleMapProvider(private val context: Context) :
902
901
  coordinates = polylineView.coordinates
903
902
  strokeColors = polylineView.strokeColors
904
903
  strokeWidth = polylineView.strokeWidth.dpToPx()
904
+ zIndex = polylineView.zIndex
905
905
  animatedOptions = polylineView.animatedOptions
906
906
  // Static maps render once; show the full polyline instead of animating
907
907
  animated = polylineView.animated && !staticMode
@@ -1203,6 +1203,17 @@ class GoogleMapProvider(private val context: Context) :
1203
1203
  // region Commands
1204
1204
 
1205
1205
  override fun moveCamera(latitude: Double, longitude: Double, zoom: Double, duration: Int) {
1206
+ if (staticMode) {
1207
+ // Static maps don't animate; re-center at the final camera
1208
+ initialLatitude = latitude
1209
+ initialLongitude = longitude
1210
+ if (zoom > 0) initialZoom = zoom.toFloat()
1211
+ val map = googleMap ?: return
1212
+ map.moveCamera(CameraUpdateFactory.newLatLngZoom(staticCameraTarget(), initialZoom))
1213
+ positionLiveMarkers()
1214
+ return
1215
+ }
1216
+
1206
1217
  val map = googleMap ?: return
1207
1218
  val position = LatLng(latitude, longitude)
1208
1219
  val targetZoom = if (zoom > 0) zoom.toFloat() else map.cameraPosition.zoom
@@ -1232,15 +1243,20 @@ class GoogleMapProvider(private val context: Context) :
1232
1243
  val latLongs = coordinates.filterIsInstance<LatLng>()
1233
1244
  if (latLongs.isEmpty()) return
1234
1245
 
1235
- val boundsBuilder = LatLngBounds.Builder()
1236
- latLongs.forEach { boundsBuilder.include(it) }
1237
- val bounds = boundsBuilder.build()
1238
-
1239
1246
  val top = edgeInsetsTop.toFloat().dpToPx().toInt()
1240
1247
  val left = edgeInsetsLeft.toFloat().dpToPx().toInt()
1241
1248
  val bottom = edgeInsetsBottom.toFloat().dpToPx().toInt()
1242
1249
  val right = edgeInsetsRight.toFloat().dpToPx().toInt()
1243
1250
 
1251
+ if (staticMode) {
1252
+ fitStaticCoordinates(latLongs, top, left, bottom, right)
1253
+ return
1254
+ }
1255
+
1256
+ val boundsBuilder = LatLngBounds.Builder()
1257
+ latLongs.forEach { boundsBuilder.include(it) }
1258
+ val bounds = boundsBuilder.build()
1259
+
1244
1260
  val combined = combinedEdgeInsets()
1245
1261
  map.setPadding(
1246
1262
  combined.left + left,
@@ -1341,14 +1357,79 @@ class GoogleMapProvider(private val context: Context) :
1341
1357
  val offsetY = (edgeInsets.top - edgeInsets.bottom) / 2.0
1342
1358
  if (offsetX == 0.0 && offsetY == 0.0) return LatLng(initialLatitude, initialLongitude)
1343
1359
 
1344
- // Web mercator world size in px at the initial zoom
1345
- val worldSize = 256f.dpToPx().toDouble() * 2.0.pow(initialZoom.toDouble())
1346
- val sinLat = sin(Math.toRadians(initialLatitude))
1347
- val x = (initialLongitude + 180.0) / 360.0 - offsetX / worldSize
1348
- val y = 0.5 - ln((1.0 + sinLat) / (1.0 - sinLat)) / (4.0 * Math.PI) - offsetY / worldSize
1349
- return LatLng(Math.toDegrees(atan(sinh(Math.PI * (1.0 - 2.0 * y)))), x * 360.0 - 180.0)
1360
+ val worldSize = mercatorWorldSize(initialZoom)
1361
+ return latLngFromMercator(
1362
+ mercatorX(initialLongitude) - offsetX / worldSize,
1363
+ mercatorY(initialLatitude) - offsetY / worldSize
1364
+ )
1365
+ }
1366
+
1367
+ // A static camera can't fit with map padding (lite mode misrenders it);
1368
+ // compute the fitted camera in mercator space instead, shifting the
1369
+ // center for asymmetric padding like edge insets
1370
+ private fun fitStaticCoordinates(
1371
+ coordinates: List<LatLng>,
1372
+ top: Int,
1373
+ left: Int,
1374
+ bottom: Int,
1375
+ right: Int
1376
+ ) {
1377
+ val map = googleMap ?: return
1378
+ val wrapper = wrapperView ?: return
1379
+ if (wrapper.width == 0 || wrapper.height == 0) return
1380
+
1381
+ var minX = Double.MAX_VALUE
1382
+ var minY = Double.MAX_VALUE
1383
+ var maxX = -Double.MAX_VALUE
1384
+ var maxY = -Double.MAX_VALUE
1385
+ for (coordinate in coordinates) {
1386
+ val x = mercatorX(coordinate.longitude)
1387
+ val y = mercatorY(coordinate.latitude)
1388
+ minX = minOf(minX, x)
1389
+ maxX = maxOf(maxX, x)
1390
+ minY = minOf(minY, y)
1391
+ maxY = maxOf(maxY, y)
1392
+ }
1393
+
1394
+ val insets = combinedEdgeInsets()
1395
+ val availWidth = (wrapper.width - insets.left - insets.right - left - right).coerceAtLeast(1)
1396
+ val availHeight = (wrapper.height - insets.top - insets.bottom - top - bottom).coerceAtLeast(1)
1397
+
1398
+ // World size in px at which the bounds fit the padded viewport;
1399
+ // infinite for coincident coordinates - keep the current zoom then
1400
+ val fitWorldSize = minOf(availWidth / (maxX - minX), availHeight / (maxY - minY))
1401
+ if (fitWorldSize.isFinite()) {
1402
+ initialZoom = log2(fitWorldSize / 256f.dpToPx())
1403
+ .toFloat()
1404
+ .coerceIn(map.minZoomLevel, map.maxZoomLevel)
1405
+ }
1406
+
1407
+ val worldSize = mercatorWorldSize(initialZoom)
1408
+ val center = latLngFromMercator(
1409
+ (minX + maxX) / 2.0 - (left - right) / 2.0 / worldSize,
1410
+ (minY + maxY) / 2.0 - (top - bottom) / 2.0 / worldSize
1411
+ )
1412
+ initialLatitude = center.latitude
1413
+ initialLongitude = center.longitude
1414
+
1415
+ map.moveCamera(CameraUpdateFactory.newLatLngZoom(staticCameraTarget(), initialZoom))
1416
+ positionLiveMarkers()
1350
1417
  }
1351
1418
 
1419
+ // Web mercator world space in [0, 1]
1420
+ private fun mercatorX(longitude: Double): Double = (longitude + 180.0) / 360.0
1421
+
1422
+ private fun mercatorY(latitude: Double): Double {
1423
+ val sinLat = sin(Math.toRadians(latitude))
1424
+ return 0.5 - ln((1.0 + sinLat) / (1.0 - sinLat)) / (4.0 * Math.PI)
1425
+ }
1426
+
1427
+ private fun latLngFromMercator(x: Double, y: Double): LatLng =
1428
+ LatLng(Math.toDegrees(atan(sinh(Math.PI * (1.0 - 2.0 * y)))), x * 360.0 - 180.0)
1429
+
1430
+ // World size in px at the given zoom
1431
+ private fun mercatorWorldSize(zoom: Float): Double = 256f.dpToPx().toDouble() * 2.0.pow(zoom.toDouble())
1432
+
1352
1433
  private fun applyWatermarkTranslation(insets: EdgeInsets, duration: Int = 0) {
1353
1434
  val view = mapView ?: return
1354
1435
  view.findViewByTag("GoogleWatermark")?.let { watermark ->
@@ -16,12 +16,19 @@ class PolylineAnimator {
16
16
  companion object {
17
17
  private const val MAX_ANIMATION_SPANS = 16
18
18
  private const val MAX_GRADIENT_SPANS = 512
19
+
20
+ // Cap polyline updates to ~60fps; every update crosses Binder into the
21
+ // maps renderer, and 120Hz displays double that churn (CONSUMER-APP-44Y)
22
+ private const val MIN_UPDATE_INTERVAL_NANOS = 16_000_000L
19
23
  }
20
24
 
21
25
  var polyline: Polyline? = null
22
26
  var coordinates: List<LatLng> = emptyList()
23
27
  set(value) {
24
- if (field === value) return
28
+ // Structural equality: Fabric delivers a new list instance on every
29
+ // commit, so identity checks never match and the full route would be
30
+ // re-parceled across Binder even when unchanged
31
+ if (field == value) return
25
32
  field = value
26
33
  dirty = true
27
34
  if (animated && animator != null) {
@@ -30,11 +37,22 @@ class PolylineAnimator {
30
37
  }
31
38
  var strokeColors: List<Int> = listOf(Color.BLACK)
32
39
  set(value) {
33
- if (field === value) return
40
+ if (field == value) return
34
41
  field = value
35
42
  dirty = true
36
43
  }
37
44
  var strokeWidth: Float = 1f
45
+ set(value) {
46
+ if (field == value) return
47
+ field = value
48
+ polyline?.width = value
49
+ }
50
+ var zIndex: Float = 0f
51
+ set(value) {
52
+ if (field == value) return
53
+ field = value
54
+ polyline?.zIndex = value
55
+ }
38
56
  var animatedOptions: AnimatedOptions = AnimatedOptions()
39
57
  set(value) {
40
58
  if (field == value) return
@@ -58,6 +76,8 @@ class PolylineAnimator {
58
76
 
59
77
  private var animator: ValueAnimator? = null
60
78
  private var animationProgress: Float = 0f
79
+ private var lastUpdateNanos: Long = 0L
80
+ private var lastAppliedColor: Int? = null
61
81
  private var cumulativeDistances: FloatArray = floatArrayOf()
62
82
  private var totalLength: Float = 0f
63
83
  private var dirty: Boolean = true
@@ -91,8 +111,11 @@ class PolylineAnimator {
91
111
 
92
112
  if (strokeColors.size > 1) {
93
113
  applyGradientSpans(poly)
114
+ lastAppliedColor = null
94
115
  } else {
95
- poly.color = strokeColors.firstOrNull() ?: Color.BLACK
116
+ val color = strokeColors.firstOrNull() ?: Color.BLACK
117
+ poly.color = color
118
+ lastAppliedColor = color
96
119
  }
97
120
  }
98
121
 
@@ -111,6 +134,9 @@ class PolylineAnimator {
111
134
  interpolator = LinearInterpolator()
112
135
  addUpdateListener { animation ->
113
136
  animationProgress = animation.animatedValue as Float
137
+ val now = System.nanoTime()
138
+ if (now - lastUpdateNanos < MIN_UPDATE_INTERVAL_NANOS) return@addUpdateListener
139
+ lastUpdateNanos = now
114
140
  updateAnimatedPolyline()
115
141
  }
116
142
  start()
@@ -182,6 +208,7 @@ class PolylineAnimator {
182
208
  private fun stopAnimation() {
183
209
  animator?.cancel()
184
210
  animator = null
211
+ lastUpdateNanos = 0L
185
212
  }
186
213
 
187
214
  fun pause() {
@@ -222,6 +249,7 @@ class PolylineAnimator {
222
249
  if (headDist <= tailDist) {
223
250
  val point = coordinates.firstOrNull() ?: LatLng(0.0, 0.0)
224
251
  poly.color = strokeColors.firstOrNull() ?: Color.BLACK
252
+ lastAppliedColor = null
225
253
  poly.points = listOf(point, point)
226
254
  return
227
255
  }
@@ -246,12 +274,13 @@ class PolylineAnimator {
246
274
 
247
275
  if (reusablePoints.size < 2) return
248
276
 
249
- // Clear spans before setting points to prevent IndexOutOfBoundsException
250
- // when the new points list is shorter than what existing spans reference
251
- poly.color = strokeColors.firstOrNull() ?: Color.BLACK
252
- poly.points = reusablePoints
253
-
254
277
  if (strokeColors.size > 1) {
278
+ // Clear spans before setting points to prevent IndexOutOfBoundsException
279
+ // when the new points list is shorter than what existing spans reference
280
+ poly.color = strokeColors.firstOrNull() ?: Color.BLACK
281
+ lastAppliedColor = null
282
+ poly.points = reusablePoints
283
+
255
284
  val segmentCount = reusablePoints.size - 1
256
285
  val spanCount = min(segmentCount, MAX_ANIMATION_SPANS)
257
286
  val segmentsPerSpan = segmentCount.toDouble() / spanCount
@@ -263,6 +292,13 @@ class PolylineAnimator {
263
292
  reusableSpans.add(StyleSpan(StrokeStyle.colorBuilder(color).build(), segmentsPerSpan))
264
293
  }
265
294
  poly.setSpans(reusableSpans)
295
+ } else {
296
+ val color = strokeColors.firstOrNull() ?: Color.BLACK
297
+ if (lastAppliedColor != color) {
298
+ poly.color = color
299
+ lastAppliedColor = color
300
+ }
301
+ poly.points = reusablePoints
266
302
  }
267
303
  }
268
304
 
@@ -248,18 +248,26 @@ static NSCache<NSString *, UIImage *> *StaticSnapshotCache(void) {
248
248
  : -(NSInteger)_theme;
249
249
 
250
250
  // Camera is part of the key so a reused staticKey with a different
251
- // location/zoom can't serve a stale snapshot
251
+ // location/zoom can't serve a stale snapshot. The provider's camera (when
252
+ // available) reflects imperative camera changes, so a re-rendered
253
+ // snapshot is keyed on what it actually shows.
252
254
  const auto &viewProps =
253
255
  *std::static_pointer_cast<LuggMapViewProps const>(_props);
256
+ double latitude = viewProps.initialCoordinate.latitude;
257
+ double longitude = viewProps.initialCoordinate.longitude;
258
+ double zoom = viewProps.initialZoom;
259
+ if ([_provider isKindOfClass:[StaticMapProviderBase class]]) {
260
+ StaticMapProviderBase *staticProvider = (StaticMapProviderBase *)_provider;
261
+ latitude = staticProvider.coordinate.latitude;
262
+ longitude = staticProvider.coordinate.longitude;
263
+ zoom = staticProvider.zoom;
264
+ }
254
265
 
255
266
  return [NSString
256
267
  stringWithFormat:@"%@|%d|%@|%d|%ld|%.0fx%.0f|%.6f,%.6f,%.2f|%@",
257
268
  _staticKey, (int)_providerType, _mapId, (int)_mapType,
258
- (long)style, size.width, size.height,
259
- viewProps.initialCoordinate.latitude,
260
- viewProps.initialCoordinate.longitude,
261
- viewProps.initialZoom,
262
- NSStringFromUIEdgeInsets(_edgeInsets)];
269
+ (long)style, size.width, size.height, latitude,
270
+ longitude, zoom, NSStringFromUIEdgeInsets(_edgeInsets)];
263
271
  }
264
272
 
265
273
  - (void)startStaticContent {
@@ -322,14 +330,6 @@ static NSCache<NSString *, UIImage *> *StaticSnapshotCache(void) {
322
330
  _provider = google;
323
331
  }
324
332
 
325
- if (_staticMode) {
326
- NSString *cacheKey = [self staticSnapshotCacheKey];
327
- if (cacheKey) {
328
- ((StaticMapProviderBase *)_provider).cachedBaseImage =
329
- [StaticSnapshotCache() objectForKey:cacheKey];
330
- }
331
- }
332
-
333
333
  _provider.delegate = self;
334
334
  _provider.staticMode = _staticMode;
335
335
 
@@ -341,6 +341,16 @@ static NSCache<NSString *, UIImage *> *StaticSnapshotCache(void) {
341
341
  initialCoordinate:coordinate
342
342
  initialZoom:viewProps.initialZoom];
343
343
 
344
+ // After initializeMapInView so the cache key reads the provider's camera;
345
+ // the base render is async and picks up the cached image
346
+ if (_staticMode) {
347
+ NSString *cacheKey = [self staticSnapshotCacheKey];
348
+ if (cacheKey) {
349
+ ((StaticMapProviderBase *)_provider).cachedBaseImage =
350
+ [StaticSnapshotCache() objectForKey:cacheKey];
351
+ }
352
+ }
353
+
344
354
  // Apply cached props after map view is created
345
355
  [self applyProps];
346
356
  [_provider setEdgeInsets:_edgeInsets oldEdgeInsets:UIEdgeInsetsZero];
@@ -74,6 +74,19 @@ static MKMapRect LuggStaticFittedMapRect(CLLocationCoordinate2D center,
74
74
  return pin;
75
75
  }
76
76
 
77
+ // Inverse of LuggStaticFittedMapRect: the pre-fit rect is square in map
78
+ // points with side latitudeDelta * worldHeight / (360 * cos(lat)), and the
79
+ // aspect fit scales it to the view's short side
80
+ - (double)zoomForMapPointsPerPoint:(double)mapPointsPerPoint {
81
+ CGSize size = self.projectedSize;
82
+ double side = mapPointsPerPoint * MIN(size.width, size.height);
83
+ double latitudeDelta = side * 360.0 *
84
+ cos(self.coordinate.latitude * M_PI / 180.0) /
85
+ MKMapSizeWorld.height;
86
+ double zoom = 0.5 + log2(360.0 / latitudeDelta);
87
+ return MAX(MIN(zoom, 28.0), 0.0);
88
+ }
89
+
77
90
  - (UITraitCollection *)snapshotTraitCollection {
78
91
  UITraitCollection *base = self.wrapperView.traitCollection;
79
92
  UIUserInterfaceStyle style;
@@ -74,6 +74,12 @@ static void EnqueueStaticMapView(NSString *mapId, GMSMapView *mapView) {
74
74
  [[UIImageView alloc] initWithImage:[GMSMarker markerImageWithColor:nil]];
75
75
  }
76
76
 
77
+ // Inverse of mapRectForSize
78
+ - (double)zoomForMapPointsPerPoint:(double)mapPointsPerPoint {
79
+ double zoom = log2(MKMapSizeWorld.width / (256.0 * mapPointsPerPoint));
80
+ return MAX(MIN(zoom, (double)kGMSMaxZoomLevel), (double)kGMSMinZoomLevel);
81
+ }
82
+
77
83
  - (void)renderBaseMap {
78
84
  if (_warmupMapView) {
79
85
  // Retry a swap that couldn't run (e.g. view was off-window)
@@ -56,6 +56,11 @@ CGPoint LuggStaticPointForCoordinate(MKMapRect mapRect, CGSize size,
56
56
  /// Overlay view shown for markers without a custom child view.
57
57
  - (UIView *)newDefaultMarkerOverlayView;
58
58
 
59
+ /// Zoom level at which the base map renders at the given scale (map points
60
+ /// per view point) for the current coordinate and view size; the inverse of
61
+ /// mapRectForSize. Used by fitCoordinates.
62
+ - (double)zoomForMapPointsPerPoint:(double)mapPointsPerPoint;
63
+
59
64
  /// Starts rendering the base map for the current projection (guaranteed
60
65
  /// projectionReady, no cached image). Called after mount (once initial
61
66
  /// props have applied), after a resize, and from resumeAnimations to retry
@@ -386,6 +386,25 @@ static NSInteger shapeZIndex(UIView *shape) {
386
386
  [self renderBaseMapIfNeeded];
387
387
  }
388
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
+
389
408
  - (void)renderBaseMapIfNeeded {
390
409
  if (_baseRenderDone || !_projectionReady || !_wrapperView)
391
410
  return;
@@ -436,6 +455,10 @@ static NSInteger shapeZIndex(UIView *shape) {
436
455
  return [[UIView alloc] init];
437
456
  }
438
457
 
458
+ - (double)zoomForMapPointsPerPoint:(double)mapPointsPerPoint {
459
+ return _zoom;
460
+ }
461
+
439
462
  - (void)renderBaseMap {
440
463
  }
441
464
 
@@ -758,12 +781,17 @@ static NSInteger shapeZIndex(UIView *shape) {
758
781
 
759
782
  #pragma mark - Commands
760
783
 
761
- // Static maps render once with their initial camera
784
+ // Static maps don't animate; commands re-render once at the final camera
762
785
 
763
786
  - (void)moveCamera:(double)latitude
764
787
  longitude:(double)longitude
765
788
  zoom:(double)zoom
766
789
  duration:(double)duration {
790
+ _coordinate = CLLocationCoordinate2DMake(latitude, longitude);
791
+ if (zoom > 0) {
792
+ _zoom = zoom;
793
+ }
794
+ [self rerenderBaseMap];
767
795
  }
768
796
 
769
797
  - (void)fitCoordinates:(NSArray *)coordinates
@@ -772,6 +800,46 @@ static NSInteger shapeZIndex(UIView *shape) {
772
800
  edgeInsetsBottom:(double)edgeInsetsBottom
773
801
  edgeInsetsRight:(double)edgeInsetsRight
774
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];
775
843
  }
776
844
 
777
845
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lugg/maps",
3
- "version": "1.0.0-beta.19",
3
+ "version": "1.0.0-beta.20",
4
4
  "description": "Universal maps for React Native.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",