@lugg/maps 0.2.0-alpha.2 → 0.2.0-alpha.21

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.
Files changed (96) hide show
  1. package/README.md +17 -4
  2. package/android/src/main/java/com/luggmaps/LuggGoogleMapView.kt +31 -37
  3. package/android/src/main/java/com/luggmaps/LuggMapWrapperView.kt +6 -5
  4. package/android/src/main/java/com/luggmaps/LuggMarkerView.kt +136 -14
  5. package/android/src/main/java/com/luggmaps/LuggMarkerViewManager.kt +21 -0
  6. package/android/src/main/java/com/luggmaps/LuggPolylineView.kt +16 -0
  7. package/android/src/main/java/com/luggmaps/LuggPolylineViewManager.kt +22 -0
  8. package/android/src/main/java/com/luggmaps/core/PolylineAnimator.kt +157 -52
  9. package/ios/LuggAppleMapView.mm +161 -46
  10. package/ios/LuggGoogleMapView.mm +52 -22
  11. package/ios/LuggMarkerView.h +9 -0
  12. package/ios/LuggMarkerView.mm +79 -0
  13. package/ios/LuggPolylineView.h +4 -0
  14. package/ios/LuggPolylineView.mm +23 -0
  15. package/ios/core/GMSPolylineAnimator.h +3 -0
  16. package/ios/core/GMSPolylineAnimator.m +164 -41
  17. package/ios/core/MKPolylineAnimator.h +4 -0
  18. package/ios/core/MKPolylineAnimator.m +162 -43
  19. package/ios/core/PolylineAnimatorBase.h +14 -0
  20. package/ios/core/PolylineAnimatorBase.m +33 -0
  21. package/ios/extensions/MKMapView+Zoom.h +2 -0
  22. package/ios/extensions/MKMapView+Zoom.m +14 -4
  23. package/lib/module/MapProvider.js +13 -0
  24. package/lib/module/MapProvider.js.map +1 -0
  25. package/lib/module/MapProvider.types.js +4 -0
  26. package/lib/module/MapProvider.types.js.map +1 -0
  27. package/lib/module/MapProvider.web.js +20 -0
  28. package/lib/module/MapProvider.web.js.map +1 -0
  29. package/lib/module/MapView.js +2 -2
  30. package/lib/module/MapView.js.map +1 -1
  31. package/lib/module/MapView.web.js +272 -0
  32. package/lib/module/MapView.web.js.map +1 -0
  33. package/lib/module/components/Marker.js +10 -1
  34. package/lib/module/components/Marker.js.map +1 -1
  35. package/lib/module/components/Marker.web.js +33 -0
  36. package/lib/module/components/Marker.web.js.map +1 -0
  37. package/lib/module/components/Polyline.js +8 -3
  38. package/lib/module/components/Polyline.js.map +1 -1
  39. package/lib/module/components/Polyline.web.js +229 -0
  40. package/lib/module/components/Polyline.web.js.map +1 -0
  41. package/lib/module/components/index.js +2 -2
  42. package/lib/module/components/index.js.map +1 -1
  43. package/lib/module/components/index.web.js +5 -0
  44. package/lib/module/components/index.web.js.map +1 -0
  45. package/lib/module/fabric/LuggMarkerViewNativeComponent.ts +7 -1
  46. package/lib/module/fabric/LuggPolylineViewNativeComponent.ts +8 -0
  47. package/lib/module/index.js +3 -2
  48. package/lib/module/index.js.map +1 -1
  49. package/lib/module/index.web.js +6 -0
  50. package/lib/module/index.web.js.map +1 -0
  51. package/lib/typescript/src/MapProvider.d.ts +8 -0
  52. package/lib/typescript/src/MapProvider.d.ts.map +1 -0
  53. package/lib/typescript/src/MapProvider.types.d.ts +16 -0
  54. package/lib/typescript/src/MapProvider.types.d.ts.map +1 -0
  55. package/lib/typescript/src/MapProvider.web.d.ts +11 -0
  56. package/lib/typescript/src/MapProvider.web.d.ts.map +1 -0
  57. package/lib/typescript/src/MapView.d.ts +1 -1
  58. package/lib/typescript/src/MapView.d.ts.map +1 -1
  59. package/lib/typescript/src/MapView.types.d.ts +2 -2
  60. package/lib/typescript/src/MapView.types.d.ts.map +1 -1
  61. package/lib/typescript/src/MapView.web.d.ts +3 -0
  62. package/lib/typescript/src/MapView.web.d.ts.map +1 -0
  63. package/lib/typescript/src/components/Marker.d.ts +21 -0
  64. package/lib/typescript/src/components/Marker.d.ts.map +1 -1
  65. package/lib/typescript/src/components/Marker.web.d.ts +3 -0
  66. package/lib/typescript/src/components/Marker.web.d.ts.map +1 -0
  67. package/lib/typescript/src/components/Polyline.d.ts +32 -0
  68. package/lib/typescript/src/components/Polyline.d.ts.map +1 -1
  69. package/lib/typescript/src/components/Polyline.web.d.ts +3 -0
  70. package/lib/typescript/src/components/Polyline.web.d.ts.map +1 -0
  71. package/lib/typescript/src/components/index.web.d.ts +5 -0
  72. package/lib/typescript/src/components/index.web.d.ts.map +1 -0
  73. package/lib/typescript/src/fabric/LuggMarkerViewNativeComponent.d.ts +4 -1
  74. package/lib/typescript/src/fabric/LuggMarkerViewNativeComponent.d.ts.map +1 -1
  75. package/lib/typescript/src/fabric/LuggPolylineViewNativeComponent.d.ts +7 -0
  76. package/lib/typescript/src/fabric/LuggPolylineViewNativeComponent.d.ts.map +1 -1
  77. package/lib/typescript/src/index.d.ts +3 -1
  78. package/lib/typescript/src/index.d.ts.map +1 -1
  79. package/lib/typescript/src/index.web.d.ts +7 -0
  80. package/lib/typescript/src/index.web.d.ts.map +1 -0
  81. package/package.json +15 -2
  82. package/src/MapProvider.tsx +10 -0
  83. package/src/MapProvider.types.ts +16 -0
  84. package/src/MapProvider.web.tsx +14 -0
  85. package/src/MapView.tsx +2 -2
  86. package/src/MapView.types.ts +2 -2
  87. package/src/MapView.web.tsx +337 -0
  88. package/src/components/Marker.tsx +37 -3
  89. package/src/components/Marker.web.tsx +33 -0
  90. package/src/components/Polyline.tsx +38 -1
  91. package/src/components/Polyline.web.tsx +287 -0
  92. package/src/components/index.web.ts +4 -0
  93. package/src/fabric/LuggMarkerViewNativeComponent.ts +7 -1
  94. package/src/fabric/LuggPolylineViewNativeComponent.ts +8 -0
  95. package/src/index.ts +8 -1
  96. package/src/index.web.ts +17 -0
@@ -1,21 +1,37 @@
1
1
  package com.luggmaps.core
2
2
 
3
+ import android.animation.TimeInterpolator
3
4
  import android.animation.ValueAnimator
4
5
  import android.graphics.Color
6
+ import android.location.Location
5
7
  import android.view.animation.LinearInterpolator
6
8
  import com.google.android.gms.maps.model.LatLng
7
9
  import com.google.android.gms.maps.model.Polyline
8
10
  import com.google.android.gms.maps.model.StrokeStyle
9
11
  import com.google.android.gms.maps.model.StyleSpan
12
+ import com.luggmaps.AnimatedOptions
10
13
  import kotlin.math.floor
11
- import kotlin.math.max
12
14
  import kotlin.math.min
13
15
 
14
16
  class PolylineAnimator {
15
17
  var polyline: Polyline? = null
16
18
  var coordinates: List<LatLng> = emptyList()
19
+ set(value) {
20
+ field = value
21
+ if (animated && animator != null) {
22
+ computeCumulativeDistances()
23
+ }
24
+ }
17
25
  var strokeColors: List<Int> = listOf(Color.BLACK)
18
26
  var strokeWidth: Float = 1f
27
+ var animatedOptions: AnimatedOptions = AnimatedOptions()
28
+ set(value) {
29
+ if (field == value) return
30
+ field = value
31
+ if (animated) {
32
+ restartAnimation()
33
+ }
34
+ }
19
35
 
20
36
  var animated: Boolean = false
21
37
  set(value) {
@@ -31,6 +47,30 @@ class PolylineAnimator {
31
47
 
32
48
  private var animator: ValueAnimator? = null
33
49
  private var animationProgress: Float = 0f
50
+ private var cumulativeDistances: FloatArray = floatArrayOf()
51
+ private var totalLength: Float = 0f
52
+
53
+ // Reusable collections to avoid per-frame allocations
54
+ private val reusablePoints = ArrayList<LatLng>()
55
+ private val reusableSpans = ArrayList<StyleSpan>()
56
+
57
+ private fun restartAnimation() {
58
+ stopAnimation()
59
+ startAnimation()
60
+ }
61
+
62
+ private fun getInterpolator(): TimeInterpolator =
63
+ when (animatedOptions.easing) {
64
+ "easeIn" -> TimeInterpolator { t -> t * t }
65
+
66
+ "easeOut" -> TimeInterpolator { t -> t * (2 - t) }
67
+
68
+ "easeInOut" -> TimeInterpolator { t ->
69
+ if (t < 0.5f) 2 * t * t else -1 + (4 - 2 * t) * t
70
+ }
71
+
72
+ else -> LinearInterpolator()
73
+ }
34
74
 
35
75
  fun update() {
36
76
  if (animated) return
@@ -50,10 +90,16 @@ class PolylineAnimator {
50
90
  private fun startAnimation() {
51
91
  if (animator != null) return
52
92
 
53
- animator = ValueAnimator.ofFloat(0f, 2.15f).apply {
54
- duration = 3650 // ~1.75s per phase * 2 + pause
93
+ computeCumulativeDistances()
94
+
95
+ val trailLength = animatedOptions.trailLength.coerceIn(0.01f, 1f)
96
+ val endValue = if (trailLength < 1f) 1f else 2.15f
97
+
98
+ animator = ValueAnimator.ofFloat(0f, endValue).apply {
99
+ duration = animatedOptions.duration
100
+ startDelay = animatedOptions.delay
55
101
  repeatCount = ValueAnimator.INFINITE
56
- interpolator = LinearInterpolator()
102
+ interpolator = getInterpolator()
57
103
  addUpdateListener { animation ->
58
104
  animationProgress = animation.animatedValue as Float
59
105
  updateAnimatedPolyline()
@@ -62,83 +108,142 @@ class PolylineAnimator {
62
108
  }
63
109
  }
64
110
 
111
+ private fun computeCumulativeDistances() {
112
+ if (coordinates.size < 2) {
113
+ cumulativeDistances = floatArrayOf(0f)
114
+ totalLength = 0f
115
+ return
116
+ }
117
+
118
+ val distances = FloatArray(coordinates.size)
119
+ distances[0] = 0f
120
+ var total = 0f
121
+
122
+ for (i in 1 until coordinates.size) {
123
+ val prev = coordinates[i - 1]
124
+ val curr = coordinates[i]
125
+ val results = FloatArray(1)
126
+ Location.distanceBetween(prev.latitude, prev.longitude, curr.latitude, curr.longitude, results)
127
+ total += results[0]
128
+ distances[i] = total
129
+ }
130
+
131
+ cumulativeDistances = distances
132
+ totalLength = total
133
+ }
134
+
135
+ private fun indexForDistance(distance: Float): Int {
136
+ var left = 0
137
+ var right = cumulativeDistances.size - 1
138
+
139
+ while (left < right) {
140
+ val mid = (left + right + 1) / 2
141
+ if (cumulativeDistances[mid] <= distance) {
142
+ left = mid
143
+ } else {
144
+ right = mid - 1
145
+ }
146
+ }
147
+
148
+ return left.coerceAtMost(cumulativeDistances.size - 2).coerceAtLeast(0)
149
+ }
150
+
151
+ private fun coordinateAtDistance(distance: Float): LatLng {
152
+ if (distance <= 0f) return coordinates.first()
153
+ if (distance >= totalLength) return coordinates.last()
154
+
155
+ val idx = indexForDistance(distance)
156
+ val segStart = cumulativeDistances[idx]
157
+ val segEnd = cumulativeDistances[idx + 1]
158
+ val segLength = segEnd - segStart
159
+
160
+ val t = if (segLength > 0) (distance - segStart) / segLength else 0f
161
+ val c1 = coordinates[idx]
162
+ val c2 = coordinates[idx + 1]
163
+
164
+ // Reuse existing coordinate if no interpolation needed
165
+ if (t == 0f) return c1
166
+
167
+ return LatLng(
168
+ c1.latitude + (c2.latitude - c1.latitude) * t,
169
+ c1.longitude + (c2.longitude - c1.longitude) * t
170
+ )
171
+ }
172
+
65
173
  private fun stopAnimation() {
66
174
  animator?.cancel()
67
175
  animator = null
68
176
  }
69
177
 
178
+ fun pause() {
179
+ animator?.pause()
180
+ }
181
+
182
+ fun resume() {
183
+ animator?.resume()
184
+ }
185
+
70
186
  private fun updateAnimatedPolyline() {
71
187
  val poly = polyline ?: return
72
- if (coordinates.size < 2) {
188
+ if (coordinates.size < 2 || totalLength <= 0f) {
73
189
  poly.points = coordinates
74
190
  return
75
191
  }
76
192
 
77
- val segmentCount = coordinates.size - 1
78
- val progress = min(animationProgress, 2f)
193
+ val trailLength = animatedOptions.trailLength.coerceIn(0.01f, 1f)
194
+ val progress = min(animationProgress, if (trailLength < 1f) 1f else 2f)
79
195
 
80
- val headPos: Float
81
- val tailPos: Float
196
+ val headDist: Float
197
+ val tailDist: Float
82
198
 
83
- if (progress <= 1f) {
84
- tailPos = 0f
85
- headPos = progress * segmentCount
199
+ if (trailLength < 1f) {
200
+ headDist = progress * totalLength
201
+ tailDist = maxOf(0f, headDist - totalLength * trailLength)
202
+ } else if (progress <= 1f) {
203
+ tailDist = 0f
204
+ headDist = progress * totalLength
86
205
  } else {
87
206
  val shrinkProgress = progress - 1f
88
- tailPos = shrinkProgress * segmentCount
89
- headPos = segmentCount.toFloat()
207
+ tailDist = shrinkProgress * totalLength
208
+ headDist = totalLength
90
209
  }
91
210
 
92
- if (headPos <= tailPos || coordinates.isEmpty()) {
211
+ if (headDist <= tailDist) {
93
212
  poly.setSpans(emptyList())
94
213
  poly.points = listOf(coordinates.firstOrNull() ?: LatLng(0.0, 0.0))
95
214
  return
96
215
  }
97
216
 
98
- val startIndex = floor(tailPos).toInt()
99
- val endIndex = kotlin.math.ceil(headPos.toDouble()).toInt()
100
- val visibleLength = headPos - tailPos
217
+ val visibleLength = headDist - tailDist
218
+ val startIndex = indexForDistance(tailDist)
219
+ val endIndex = indexForDistance(headDist)
101
220
 
102
- val points = mutableListOf<LatLng>()
103
- val spans = mutableListOf<StyleSpan>()
221
+ reusablePoints.clear()
222
+ reusableSpans.clear()
104
223
 
105
- for (i in startIndex..minOf(endIndex, coordinates.size - 1)) {
106
- var coord = coordinates[i]
224
+ reusablePoints.add(coordinateAtDistance(tailDist))
107
225
 
108
- // Interpolate tail
109
- if (i == startIndex && tailPos > startIndex.toFloat() && i + 1 < coordinates.size) {
110
- val t = tailPos - startIndex
111
- val next = coordinates[i + 1]
112
- coord = LatLng(
113
- coord.latitude + (next.latitude - coord.latitude) * t,
114
- coord.longitude + (next.longitude - coord.longitude) * t
115
- )
116
- }
117
-
118
- // Interpolate head
119
- if (i == endIndex && headPos < endIndex.toFloat() && i > 0) {
120
- val t = headPos - (endIndex - 1)
121
- val prev = coordinates[i - 1]
122
- coord = LatLng(
123
- prev.latitude + (coordinates[i].latitude - prev.latitude) * t,
124
- prev.longitude + (coordinates[i].longitude - prev.longitude) * t
125
- )
126
- }
226
+ for (i in (startIndex + 1)..endIndex) {
227
+ reusablePoints.add(coordinates[i])
228
+ }
127
229
 
128
- points.add(coord)
230
+ val endCoord = coordinateAtDistance(headDist)
231
+ val lastAdded = reusablePoints.lastOrNull()
232
+ if (lastAdded == null || endCoord.latitude != lastAdded.latitude || endCoord.longitude != lastAdded.longitude) {
233
+ reusablePoints.add(endCoord)
234
+ }
129
235
 
130
- if (i < endIndex && i < segmentCount) {
131
- val segStartPos = max(i.toFloat(), tailPos)
132
- val segEndPos = min((i + 1).toFloat(), headPos)
133
- val gradientMid = ((segStartPos + segEndPos) / 2f - tailPos) / visibleLength
134
- val color = colorAtGradientPosition(gradientMid)
135
- spans.add(StyleSpan(StrokeStyle.colorBuilder(color).build()))
136
- }
236
+ val pointCount = reusablePoints.size
237
+ for (i in 0 until (pointCount - 1)) {
238
+ val segMidDist = tailDist + visibleLength * (i + 0.5f) / (pointCount - 1)
239
+ val gradientPos = (segMidDist - tailDist) / visibleLength
240
+ val color = colorAtGradientPosition(gradientPos)
241
+ reusableSpans.add(StyleSpan(StrokeStyle.colorBuilder(color).build()))
137
242
  }
138
243
 
139
- poly.points = points
140
- if (spans.isNotEmpty()) {
141
- poly.setSpans(spans)
244
+ poly.points = reusablePoints
245
+ if (reusableSpans.isNotEmpty()) {
246
+ poly.setSpans(reusableSpans)
142
247
  }
143
248
  }
144
249
 
@@ -44,6 +44,7 @@ using namespace luggmaps::events;
44
44
  BOOL _isDragging;
45
45
  double _minZoom;
46
46
  double _maxZoom;
47
+ NSMapTable<id<MKOverlay>, LuggPolylineView *> *_overlayToPolylineMap;
47
48
  }
48
49
 
49
50
  + (ComponentDescriptorProvider)componentDescriptorProvider {
@@ -56,6 +57,7 @@ using namespace luggmaps::events;
56
57
  static const auto defaultProps =
57
58
  std::make_shared<const LuggAppleMapViewProps>();
58
59
  _props = defaultProps;
60
+ _overlayToPolylineMap = [NSMapTable strongToWeakObjectsMapTable];
59
61
  }
60
62
 
61
63
  return self;
@@ -101,16 +103,19 @@ using namespace luggmaps::events;
101
103
  (AppleMarkerAnnotation *)markerView.marker;
102
104
 
103
105
  if (annotation) {
106
+ annotation.annotationView.transform = CGAffineTransformIdentity;
104
107
  annotation.markerView = nil;
105
108
  annotation.annotationView = nil;
106
109
  [_mapView removeAnnotation:annotation];
107
110
  markerView.marker = nil;
108
111
  }
112
+ [markerView resetIconViewTransform];
109
113
  } else if ([childComponentView isKindOfClass:[LuggPolylineView class]]) {
110
114
  LuggPolylineView *polylineView = (LuggPolylineView *)childComponentView;
111
115
  polylineView.delegate = nil;
112
116
  MKPolyline *polyline = (MKPolyline *)polylineView.polyline;
113
117
  if (polyline) {
118
+ [_overlayToPolylineMap removeObjectForKey:polyline];
114
119
  [_mapView removeOverlay:polyline];
115
120
  polylineView.polyline = nil;
116
121
  }
@@ -167,10 +172,13 @@ using namespace luggmaps::events;
167
172
 
168
173
  [_mapWrapperView addSubview:_mapView];
169
174
 
170
- [self setCameraWithLatitude:viewProps.initialCoordinate.latitude
171
- longitude:viewProps.initialCoordinate.longitude
172
- zoom:viewProps.initialZoom
173
- animated:NO];
175
+ CLLocationCoordinate2D center =
176
+ CLLocationCoordinate2DMake(viewProps.initialCoordinate.latitude,
177
+ viewProps.initialCoordinate.longitude);
178
+ MKCoordinateRegion region =
179
+ [_mapView regionForCenterCoordinate:center
180
+ zoomLevel:viewProps.initialZoom];
181
+ [_mapView setRegion:region animated:NO];
174
182
 
175
183
  // Add annotations for any markers that were mounted before map was ready
176
184
  for (UIView *subview in self.subviews) {
@@ -230,6 +238,8 @@ using namespace luggmaps::events;
230
238
 
231
239
  - (void)updateProps:(Props::Shared const &)props
232
240
  oldProps:(Props::Shared const &)oldProps {
241
+ const auto &oldViewProps =
242
+ *std::static_pointer_cast<LuggAppleMapViewProps const>(oldProps);
233
243
  const auto &newViewProps =
234
244
  *std::static_pointer_cast<LuggAppleMapViewProps const>(props);
235
245
 
@@ -239,9 +249,49 @@ using namespace luggmaps::events;
239
249
  _mapView.rotateEnabled = newViewProps.rotateEnabled;
240
250
  _mapView.pitchEnabled = newViewProps.pitchEnabled;
241
251
  _mapView.showsUserLocation = newViewProps.userLocationEnabled;
242
- _mapView.layoutMargins = UIEdgeInsetsMake(
243
- newViewProps.padding.top, newViewProps.padding.left,
244
- newViewProps.padding.bottom, newViewProps.padding.right);
252
+
253
+ // Check if padding changed
254
+ BOOL paddingChanged =
255
+ oldViewProps.padding.top != newViewProps.padding.top ||
256
+ oldViewProps.padding.left != newViewProps.padding.left ||
257
+ oldViewProps.padding.bottom != newViewProps.padding.bottom ||
258
+ oldViewProps.padding.right != newViewProps.padding.right;
259
+
260
+ if (paddingChanged) {
261
+ // Calculate the offset difference to keep visual center stable
262
+ CGFloat oldOffsetX =
263
+ (oldViewProps.padding.left - oldViewProps.padding.right) / 2.0;
264
+ CGFloat oldOffsetY =
265
+ (oldViewProps.padding.top - oldViewProps.padding.bottom) / 2.0;
266
+ CGFloat newOffsetX =
267
+ (newViewProps.padding.left - newViewProps.padding.right) / 2.0;
268
+ CGFloat newOffsetY =
269
+ (newViewProps.padding.top - newViewProps.padding.bottom) / 2.0;
270
+
271
+ CGFloat deltaX = newOffsetX - oldOffsetX;
272
+ CGFloat deltaY = newOffsetY - oldOffsetY;
273
+
274
+ // Apply new padding first
275
+ _mapView.layoutMargins = UIEdgeInsetsMake(
276
+ newViewProps.padding.top, newViewProps.padding.left,
277
+ newViewProps.padding.bottom, newViewProps.padding.right);
278
+
279
+ // Convert pixel offset to coordinate offset
280
+ if (deltaX != 0 || deltaY != 0) {
281
+ CLLocationCoordinate2D currentCenter = _mapView.centerCoordinate;
282
+ CGPoint centerPoint = [_mapView convertCoordinate:currentCenter
283
+ toPointToView:_mapView];
284
+ CGPoint newPoint =
285
+ CGPointMake(centerPoint.x - deltaX, centerPoint.y - deltaY);
286
+ CLLocationCoordinate2D newCenter = [_mapView convertPoint:newPoint
287
+ toCoordinateFromView:_mapView];
288
+ [_mapView setCenterCoordinate:newCenter animated:NO];
289
+ }
290
+ } else {
291
+ _mapView.layoutMargins = UIEdgeInsetsMake(
292
+ newViewProps.padding.top, newViewProps.padding.left,
293
+ newViewProps.padding.bottom, newViewProps.padding.right);
294
+ }
245
295
 
246
296
  _minZoom = newViewProps.minZoom;
247
297
  _maxZoom = newViewProps.maxZoom;
@@ -253,6 +303,38 @@ using namespace luggmaps::events;
253
303
 
254
304
  #pragma mark - Annotation Helpers
255
305
 
306
+ - (void)applyMarkerStyle:(LuggMarkerView *)markerView
307
+ annotationView:(MKAnnotationView *)annotationView {
308
+ annotationView.transform = CGAffineTransformIdentity;
309
+
310
+ UIView *iconView = markerView.iconView;
311
+ CGRect frame = iconView.frame;
312
+ if (frame.size.width <= 0 || frame.size.height <= 0)
313
+ return;
314
+
315
+ CGFloat scale = markerView.scale;
316
+ CGPoint anchor = markerView.anchor;
317
+
318
+ if (markerView.rasterize) {
319
+ annotationView.image = [markerView createScaledIconImage];
320
+ } else {
321
+ iconView.layer.anchorPoint = anchor;
322
+ iconView.transform = CGAffineTransformMakeScale(scale, scale);
323
+ iconView.frame =
324
+ CGRectMake(frame.size.width * (0.5 - anchor.x) * (scale - 1),
325
+ frame.size.height * (0.5 - anchor.y) * (scale - 1),
326
+ frame.size.width, frame.size.height);
327
+ }
328
+
329
+ annotationView.bounds =
330
+ CGRectMake(0, 0, frame.size.width * scale, frame.size.height * scale);
331
+ annotationView.centerOffset =
332
+ CGPointMake(frame.size.width * scale * (anchor.x - 0.5),
333
+ -frame.size.height * scale * (anchor.y - 0.5));
334
+ annotationView.transform =
335
+ CGAffineTransformMakeRotation(markerView.rotate * M_PI / 180.0);
336
+ }
337
+
256
338
  - (void)updateAnnotationViewFrame:(AppleMarkerAnnotation *)annotation {
257
339
  MKAnnotationView *annotationView = annotation.annotationView;
258
340
  LuggMarkerView *markerView = annotation.markerView;
@@ -261,16 +343,7 @@ using namespace luggmaps::events;
261
343
  return;
262
344
  }
263
345
 
264
- UIView *iconView = markerView.iconView;
265
- CGRect frame = iconView.frame;
266
- if (frame.size.width > 0 && frame.size.height > 0) {
267
- annotationView.frame = frame;
268
-
269
- CGPoint anchor = markerView.anchor;
270
- annotationView.centerOffset =
271
- CGPointMake(frame.size.width * (anchor.x - 0.5),
272
- -frame.size.height * (anchor.y - 0.5));
273
- }
346
+ [self applyMarkerStyle:markerView annotationView:annotationView];
274
347
  }
275
348
 
276
349
  #pragma mark - PolylineViewDelegate
@@ -302,7 +375,8 @@ using namespace luggmaps::events;
302
375
  free(coords);
303
376
 
304
377
  polylineView.polyline = polyline;
305
- [_mapView addOverlay:polyline];
378
+ [_overlayToPolylineMap setObject:polylineView forKey:polyline];
379
+ [self insertOverlay:polyline withZIndex:polylineView.zIndex];
306
380
  }
307
381
 
308
382
  - (void)syncPolylineView:(LuggPolylineView *)polylineView {
@@ -316,7 +390,11 @@ using namespace luggmaps::events;
316
390
  // Build new polyline from coordinates
317
391
  NSArray<CLLocation *> *coordinates = polylineView.coordinates;
318
392
  if (coordinates.count == 0) {
393
+ if (renderer) {
394
+ renderer.animated = NO;
395
+ }
319
396
  if (oldPolyline) {
397
+ [_overlayToPolylineMap removeObjectForKey:oldPolyline];
320
398
  [_mapView removeOverlay:oldPolyline];
321
399
  polylineView.polyline = nil;
322
400
  polylineView.renderer = nil;
@@ -334,35 +412,54 @@ using namespace luggmaps::events;
334
412
  free(coords);
335
413
 
336
414
  polylineView.polyline = newPolyline;
415
+ [_overlayToPolylineMap setObject:polylineView forKey:newPolyline];
337
416
 
338
417
  // If we have an existing renderer, update it in place
339
418
  if (renderer && oldPolyline) {
419
+ [_overlayToPolylineMap removeObjectForKey:oldPolyline];
420
+ [_mapView removeOverlay:oldPolyline];
421
+ [self insertOverlay:newPolyline withZIndex:polylineView.zIndex];
340
422
  [renderer updatePolyline:newPolyline];
341
423
  renderer.lineWidth = polylineView.strokeWidth;
342
424
  renderer.strokeColor = polylineView.strokeColors.firstObject;
343
425
  renderer.strokeColors =
344
426
  polylineView.strokeColors.count > 1 ? polylineView.strokeColors : nil;
427
+ renderer.animatedOptions = polylineView.animatedOptions;
345
428
  renderer.animated = polylineView.animated;
346
429
  return;
347
430
  }
348
431
 
349
432
  // Otherwise do full add
350
433
  if (oldPolyline) {
434
+ [_overlayToPolylineMap removeObjectForKey:oldPolyline];
351
435
  [_mapView removeOverlay:oldPolyline];
352
436
  }
353
- [_mapView addOverlay:newPolyline];
437
+ [self insertOverlay:newPolyline withZIndex:polylineView.zIndex];
354
438
  }
355
439
 
356
- - (LuggPolylineView *)findPolylineViewForOverlay:(id<MKOverlay>)overlay {
357
- for (UIView *subview in self.subviews) {
358
- if ([subview isKindOfClass:[LuggPolylineView class]]) {
359
- LuggPolylineView *polylineView = (LuggPolylineView *)subview;
360
- if (polylineView.polyline == overlay) {
361
- return polylineView;
362
- }
440
+ - (void)insertOverlay:(id<MKOverlay>)overlay withZIndex:(NSInteger)zIndex {
441
+ if (zIndex == 0) {
442
+ [_mapView addOverlay:overlay];
443
+ return;
444
+ }
445
+
446
+ NSArray<id<MKOverlay>> *overlays = _mapView.overlays;
447
+ NSInteger insertIndex = overlays.count;
448
+
449
+ for (NSInteger i = 0; i < overlays.count; i++) {
450
+ LuggPolylineView *existingPolylineView =
451
+ [self findPolylineViewForOverlay:overlays[i]];
452
+ if (existingPolylineView && existingPolylineView.zIndex > zIndex) {
453
+ insertIndex = i;
454
+ break;
363
455
  }
364
456
  }
365
- return nil;
457
+
458
+ [_mapView insertOverlay:overlay atIndex:insertIndex];
459
+ }
460
+
461
+ - (LuggPolylineView *)findPolylineViewForOverlay:(id<MKOverlay>)overlay {
462
+ return [_overlayToPolylineMap objectForKey:overlay];
366
463
  }
367
464
 
368
465
  #pragma mark - MarkerViewDelegate
@@ -388,6 +485,12 @@ using namespace luggmaps::events;
388
485
  annotation.title = markerView.title;
389
486
  annotation.subtitle = markerView.markerDescription;
390
487
 
488
+ MKAnnotationView *annotationView = annotation.annotationView;
489
+ if (annotationView) {
490
+ annotationView.layer.zPosition = markerView.zIndex;
491
+ annotationView.zPriority = markerView.zIndex;
492
+ }
493
+
391
494
  [self updateAnnotationViewFrame:annotation];
392
495
  }
393
496
 
@@ -395,6 +498,15 @@ using namespace luggmaps::events;
395
498
 
396
499
  - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated {
397
500
  _isDragging = [self isUserInteracting:mapView];
501
+ if (_isDragging) {
502
+ for (UIView *subview in self.subviews) {
503
+ if ([subview isKindOfClass:[LuggPolylineView class]]) {
504
+ MKPolylineAnimator *renderer =
505
+ (MKPolylineAnimator *)((LuggPolylineView *)subview).renderer;
506
+ [renderer pause];
507
+ }
508
+ }
509
+ }
398
510
  }
399
511
 
400
512
  - (BOOL)isUserInteracting:(MKMapView *)mapView {
@@ -418,6 +530,15 @@ using namespace luggmaps::events;
418
530
  - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
419
531
  BOOL wasDragging = _isDragging;
420
532
  _isDragging = NO;
533
+ if (wasDragging) {
534
+ for (UIView *subview in self.subviews) {
535
+ if ([subview isKindOfClass:[LuggPolylineView class]]) {
536
+ MKPolylineAnimator *renderer =
537
+ (MKPolylineAnimator *)((LuggPolylineView *)subview).renderer;
538
+ [renderer resume];
539
+ }
540
+ }
541
+ }
421
542
  CameraIdleEvent{mapView.centerCoordinate.latitude,
422
543
  mapView.centerCoordinate.longitude, mapView.zoomLevel,
423
544
  static_cast<bool>(wasDragging)}
@@ -442,25 +563,16 @@ using namespace luggmaps::events;
442
563
  reuseIdentifier:nil];
443
564
  annotationView.canShowCallout = YES;
444
565
  annotationView.displayPriority = MKFeatureDisplayPriorityRequired;
445
- annotationView.collisionMode = MKAnnotationViewCollisionModeNone;
446
-
447
- UIView *iconView = markerView.iconView;
448
- [iconView removeFromSuperview];
449
- [annotationView addSubview:iconView];
566
+ annotationView.layer.zPosition = markerView.zIndex;
567
+ annotationView.zPriority = markerView.zIndex;
450
568
 
451
- // Set frame and centerOffset based on iconView
452
- CGRect frame = iconView.frame;
453
- if (frame.size.width > 0 && frame.size.height > 0) {
454
- annotationView.frame =
455
- CGRectMake(0, 0, frame.size.width, frame.size.height);
456
- iconView.frame = CGRectMake(0, 0, frame.size.width, frame.size.height);
457
-
458
- CGPoint anchor = markerView.anchor;
459
- annotationView.centerOffset =
460
- CGPointMake(frame.size.width * (anchor.x - 0.5),
461
- -frame.size.height * (anchor.y - 0.5));
569
+ if (!markerView.rasterize) {
570
+ UIView *iconView = markerView.iconView;
571
+ [iconView removeFromSuperview];
572
+ [annotationView addSubview:iconView];
462
573
  }
463
574
 
575
+ [self applyMarkerStyle:markerView annotationView:annotationView];
464
576
  markerAnnotation.annotationView = annotationView;
465
577
 
466
578
  return annotationView;
@@ -482,6 +594,7 @@ using namespace luggmaps::events;
482
594
  if (colors.count > 1) {
483
595
  renderer.strokeColors = colors;
484
596
  }
597
+ renderer.animatedOptions = polylineView.animatedOptions;
485
598
  renderer.animated = polylineView.animated;
486
599
  polylineView.renderer = renderer;
487
600
  return renderer;
@@ -504,16 +617,18 @@ using namespace luggmaps::events;
504
617
  return;
505
618
  }
506
619
 
620
+ double targetZoom = zoom > 0 ? zoom : _mapView.zoomLevel;
621
+
507
622
  if (duration < 0) {
508
623
  [self setCameraWithLatitude:latitude
509
624
  longitude:longitude
510
- zoom:zoom
625
+ zoom:targetZoom
511
626
  animated:YES];
512
627
  } else if (duration > 0) {
513
628
  CLLocationCoordinate2D center =
514
629
  CLLocationCoordinate2DMake(latitude, longitude);
515
630
  MKCoordinateRegion region = [_mapView regionForCenterCoordinate:center
516
- zoomLevel:zoom];
631
+ zoomLevel:targetZoom];
517
632
  [UIView animateWithDuration:duration / 1000.0
518
633
  animations:^{
519
634
  [self->_mapView setRegion:region animated:NO];
@@ -521,7 +636,7 @@ using namespace luggmaps::events;
521
636
  } else {
522
637
  [self setCameraWithLatitude:latitude
523
638
  longitude:longitude
524
- zoom:zoom
639
+ zoom:targetZoom
525
640
  animated:NO];
526
641
  }
527
642
  }