@lugg/maps 0.2.0-alpha.2 → 0.2.0-alpha.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.
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 +154 -42
  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
  }
@@ -230,6 +235,8 @@ using namespace luggmaps::events;
230
235
 
231
236
  - (void)updateProps:(Props::Shared const &)props
232
237
  oldProps:(Props::Shared const &)oldProps {
238
+ const auto &oldViewProps =
239
+ *std::static_pointer_cast<LuggAppleMapViewProps const>(oldProps);
233
240
  const auto &newViewProps =
234
241
  *std::static_pointer_cast<LuggAppleMapViewProps const>(props);
235
242
 
@@ -239,9 +246,49 @@ using namespace luggmaps::events;
239
246
  _mapView.rotateEnabled = newViewProps.rotateEnabled;
240
247
  _mapView.pitchEnabled = newViewProps.pitchEnabled;
241
248
  _mapView.showsUserLocation = newViewProps.userLocationEnabled;
242
- _mapView.layoutMargins = UIEdgeInsetsMake(
243
- newViewProps.padding.top, newViewProps.padding.left,
244
- newViewProps.padding.bottom, newViewProps.padding.right);
249
+
250
+ // Check if padding changed
251
+ BOOL paddingChanged =
252
+ oldViewProps.padding.top != newViewProps.padding.top ||
253
+ oldViewProps.padding.left != newViewProps.padding.left ||
254
+ oldViewProps.padding.bottom != newViewProps.padding.bottom ||
255
+ oldViewProps.padding.right != newViewProps.padding.right;
256
+
257
+ if (paddingChanged) {
258
+ // Calculate the offset difference to keep visual center stable
259
+ CGFloat oldOffsetX =
260
+ (oldViewProps.padding.left - oldViewProps.padding.right) / 2.0;
261
+ CGFloat oldOffsetY =
262
+ (oldViewProps.padding.top - oldViewProps.padding.bottom) / 2.0;
263
+ CGFloat newOffsetX =
264
+ (newViewProps.padding.left - newViewProps.padding.right) / 2.0;
265
+ CGFloat newOffsetY =
266
+ (newViewProps.padding.top - newViewProps.padding.bottom) / 2.0;
267
+
268
+ CGFloat deltaX = newOffsetX - oldOffsetX;
269
+ CGFloat deltaY = newOffsetY - oldOffsetY;
270
+
271
+ // Apply new padding first
272
+ _mapView.layoutMargins = UIEdgeInsetsMake(
273
+ newViewProps.padding.top, newViewProps.padding.left,
274
+ newViewProps.padding.bottom, newViewProps.padding.right);
275
+
276
+ // Convert pixel offset to coordinate offset
277
+ if (deltaX != 0 || deltaY != 0) {
278
+ CLLocationCoordinate2D currentCenter = _mapView.centerCoordinate;
279
+ CGPoint centerPoint = [_mapView convertCoordinate:currentCenter
280
+ toPointToView:_mapView];
281
+ CGPoint newPoint =
282
+ CGPointMake(centerPoint.x - deltaX, centerPoint.y - deltaY);
283
+ CLLocationCoordinate2D newCenter = [_mapView convertPoint:newPoint
284
+ toCoordinateFromView:_mapView];
285
+ [_mapView setCenterCoordinate:newCenter animated:NO];
286
+ }
287
+ } else {
288
+ _mapView.layoutMargins = UIEdgeInsetsMake(
289
+ newViewProps.padding.top, newViewProps.padding.left,
290
+ newViewProps.padding.bottom, newViewProps.padding.right);
291
+ }
245
292
 
246
293
  _minZoom = newViewProps.minZoom;
247
294
  _maxZoom = newViewProps.maxZoom;
@@ -253,6 +300,38 @@ using namespace luggmaps::events;
253
300
 
254
301
  #pragma mark - Annotation Helpers
255
302
 
303
+ - (void)applyMarkerStyle:(LuggMarkerView *)markerView
304
+ annotationView:(MKAnnotationView *)annotationView {
305
+ annotationView.transform = CGAffineTransformIdentity;
306
+
307
+ UIView *iconView = markerView.iconView;
308
+ CGRect frame = iconView.frame;
309
+ if (frame.size.width <= 0 || frame.size.height <= 0)
310
+ return;
311
+
312
+ CGFloat scale = markerView.scale;
313
+ CGPoint anchor = markerView.anchor;
314
+
315
+ if (markerView.rasterize) {
316
+ annotationView.image = [markerView createScaledIconImage];
317
+ } else {
318
+ iconView.layer.anchorPoint = anchor;
319
+ iconView.transform = CGAffineTransformMakeScale(scale, scale);
320
+ iconView.frame =
321
+ CGRectMake(frame.size.width * (0.5 - anchor.x) * (scale - 1),
322
+ frame.size.height * (0.5 - anchor.y) * (scale - 1),
323
+ frame.size.width, frame.size.height);
324
+ }
325
+
326
+ annotationView.bounds =
327
+ CGRectMake(0, 0, frame.size.width * scale, frame.size.height * scale);
328
+ annotationView.centerOffset =
329
+ CGPointMake(frame.size.width * scale * (anchor.x - 0.5),
330
+ -frame.size.height * scale * (anchor.y - 0.5));
331
+ annotationView.transform =
332
+ CGAffineTransformMakeRotation(markerView.rotate * M_PI / 180.0);
333
+ }
334
+
256
335
  - (void)updateAnnotationViewFrame:(AppleMarkerAnnotation *)annotation {
257
336
  MKAnnotationView *annotationView = annotation.annotationView;
258
337
  LuggMarkerView *markerView = annotation.markerView;
@@ -261,16 +340,7 @@ using namespace luggmaps::events;
261
340
  return;
262
341
  }
263
342
 
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
- }
343
+ [self applyMarkerStyle:markerView annotationView:annotationView];
274
344
  }
275
345
 
276
346
  #pragma mark - PolylineViewDelegate
@@ -302,7 +372,8 @@ using namespace luggmaps::events;
302
372
  free(coords);
303
373
 
304
374
  polylineView.polyline = polyline;
305
- [_mapView addOverlay:polyline];
375
+ [_overlayToPolylineMap setObject:polylineView forKey:polyline];
376
+ [self insertOverlay:polyline withZIndex:polylineView.zIndex];
306
377
  }
307
378
 
308
379
  - (void)syncPolylineView:(LuggPolylineView *)polylineView {
@@ -316,7 +387,11 @@ using namespace luggmaps::events;
316
387
  // Build new polyline from coordinates
317
388
  NSArray<CLLocation *> *coordinates = polylineView.coordinates;
318
389
  if (coordinates.count == 0) {
390
+ if (renderer) {
391
+ renderer.animated = NO;
392
+ }
319
393
  if (oldPolyline) {
394
+ [_overlayToPolylineMap removeObjectForKey:oldPolyline];
320
395
  [_mapView removeOverlay:oldPolyline];
321
396
  polylineView.polyline = nil;
322
397
  polylineView.renderer = nil;
@@ -334,35 +409,54 @@ using namespace luggmaps::events;
334
409
  free(coords);
335
410
 
336
411
  polylineView.polyline = newPolyline;
412
+ [_overlayToPolylineMap setObject:polylineView forKey:newPolyline];
337
413
 
338
414
  // If we have an existing renderer, update it in place
339
415
  if (renderer && oldPolyline) {
416
+ [_overlayToPolylineMap removeObjectForKey:oldPolyline];
417
+ [_mapView removeOverlay:oldPolyline];
418
+ [self insertOverlay:newPolyline withZIndex:polylineView.zIndex];
340
419
  [renderer updatePolyline:newPolyline];
341
420
  renderer.lineWidth = polylineView.strokeWidth;
342
421
  renderer.strokeColor = polylineView.strokeColors.firstObject;
343
422
  renderer.strokeColors =
344
423
  polylineView.strokeColors.count > 1 ? polylineView.strokeColors : nil;
424
+ renderer.animatedOptions = polylineView.animatedOptions;
345
425
  renderer.animated = polylineView.animated;
346
426
  return;
347
427
  }
348
428
 
349
429
  // Otherwise do full add
350
430
  if (oldPolyline) {
431
+ [_overlayToPolylineMap removeObjectForKey:oldPolyline];
351
432
  [_mapView removeOverlay:oldPolyline];
352
433
  }
353
- [_mapView addOverlay:newPolyline];
434
+ [self insertOverlay:newPolyline withZIndex:polylineView.zIndex];
354
435
  }
355
436
 
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
- }
437
+ - (void)insertOverlay:(id<MKOverlay>)overlay withZIndex:(NSInteger)zIndex {
438
+ if (zIndex == 0) {
439
+ [_mapView addOverlay:overlay];
440
+ return;
441
+ }
442
+
443
+ NSArray<id<MKOverlay>> *overlays = _mapView.overlays;
444
+ NSInteger insertIndex = overlays.count;
445
+
446
+ for (NSInteger i = 0; i < overlays.count; i++) {
447
+ LuggPolylineView *existingPolylineView =
448
+ [self findPolylineViewForOverlay:overlays[i]];
449
+ if (existingPolylineView && existingPolylineView.zIndex > zIndex) {
450
+ insertIndex = i;
451
+ break;
363
452
  }
364
453
  }
365
- return nil;
454
+
455
+ [_mapView insertOverlay:overlay atIndex:insertIndex];
456
+ }
457
+
458
+ - (LuggPolylineView *)findPolylineViewForOverlay:(id<MKOverlay>)overlay {
459
+ return [_overlayToPolylineMap objectForKey:overlay];
366
460
  }
367
461
 
368
462
  #pragma mark - MarkerViewDelegate
@@ -388,6 +482,12 @@ using namespace luggmaps::events;
388
482
  annotation.title = markerView.title;
389
483
  annotation.subtitle = markerView.markerDescription;
390
484
 
485
+ MKAnnotationView *annotationView = annotation.annotationView;
486
+ if (annotationView) {
487
+ annotationView.layer.zPosition = markerView.zIndex;
488
+ annotationView.zPriority = markerView.zIndex;
489
+ }
490
+
391
491
  [self updateAnnotationViewFrame:annotation];
392
492
  }
393
493
 
@@ -395,6 +495,15 @@ using namespace luggmaps::events;
395
495
 
396
496
  - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated {
397
497
  _isDragging = [self isUserInteracting:mapView];
498
+ if (_isDragging) {
499
+ for (UIView *subview in self.subviews) {
500
+ if ([subview isKindOfClass:[LuggPolylineView class]]) {
501
+ MKPolylineAnimator *renderer =
502
+ (MKPolylineAnimator *)((LuggPolylineView *)subview).renderer;
503
+ [renderer pause];
504
+ }
505
+ }
506
+ }
398
507
  }
399
508
 
400
509
  - (BOOL)isUserInteracting:(MKMapView *)mapView {
@@ -418,6 +527,15 @@ using namespace luggmaps::events;
418
527
  - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
419
528
  BOOL wasDragging = _isDragging;
420
529
  _isDragging = NO;
530
+ if (wasDragging) {
531
+ for (UIView *subview in self.subviews) {
532
+ if ([subview isKindOfClass:[LuggPolylineView class]]) {
533
+ MKPolylineAnimator *renderer =
534
+ (MKPolylineAnimator *)((LuggPolylineView *)subview).renderer;
535
+ [renderer resume];
536
+ }
537
+ }
538
+ }
421
539
  CameraIdleEvent{mapView.centerCoordinate.latitude,
422
540
  mapView.centerCoordinate.longitude, mapView.zoomLevel,
423
541
  static_cast<bool>(wasDragging)}
@@ -442,25 +560,16 @@ using namespace luggmaps::events;
442
560
  reuseIdentifier:nil];
443
561
  annotationView.canShowCallout = YES;
444
562
  annotationView.displayPriority = MKFeatureDisplayPriorityRequired;
445
- annotationView.collisionMode = MKAnnotationViewCollisionModeNone;
563
+ annotationView.layer.zPosition = markerView.zIndex;
564
+ annotationView.zPriority = markerView.zIndex;
446
565
 
447
- UIView *iconView = markerView.iconView;
448
- [iconView removeFromSuperview];
449
- [annotationView addSubview:iconView];
450
-
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));
566
+ if (!markerView.rasterize) {
567
+ UIView *iconView = markerView.iconView;
568
+ [iconView removeFromSuperview];
569
+ [annotationView addSubview:iconView];
462
570
  }
463
571
 
572
+ [self applyMarkerStyle:markerView annotationView:annotationView];
464
573
  markerAnnotation.annotationView = annotationView;
465
574
 
466
575
  return annotationView;
@@ -482,6 +591,7 @@ using namespace luggmaps::events;
482
591
  if (colors.count > 1) {
483
592
  renderer.strokeColors = colors;
484
593
  }
594
+ renderer.animatedOptions = polylineView.animatedOptions;
485
595
  renderer.animated = polylineView.animated;
486
596
  polylineView.renderer = renderer;
487
597
  return renderer;
@@ -504,16 +614,18 @@ using namespace luggmaps::events;
504
614
  return;
505
615
  }
506
616
 
617
+ double targetZoom = zoom > 0 ? zoom : _mapView.zoomLevel;
618
+
507
619
  if (duration < 0) {
508
620
  [self setCameraWithLatitude:latitude
509
621
  longitude:longitude
510
- zoom:zoom
622
+ zoom:targetZoom
511
623
  animated:YES];
512
624
  } else if (duration > 0) {
513
625
  CLLocationCoordinate2D center =
514
626
  CLLocationCoordinate2DMake(latitude, longitude);
515
627
  MKCoordinateRegion region = [_mapView regionForCenterCoordinate:center
516
- zoomLevel:zoom];
628
+ zoomLevel:targetZoom];
517
629
  [UIView animateWithDuration:duration / 1000.0
518
630
  animations:^{
519
631
  [self->_mapView setRegion:region animated:NO];
@@ -521,7 +633,7 @@ using namespace luggmaps::events;
521
633
  } else {
522
634
  [self setCameraWithLatitude:latitude
523
635
  longitude:longitude
524
- zoom:zoom
636
+ zoom:targetZoom
525
637
  animated:NO];
526
638
  }
527
639
  }