@lugg/maps 0.2.0-alpha.3 → 0.2.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/luggmaps/LuggGoogleMapView.kt +0 -1
- package/android/src/main/java/com/luggmaps/core/PolylineAnimator.kt +89 -43
- package/ios/LuggAppleMapView.mm +5 -1
- package/ios/LuggGoogleMapView.mm +3 -4
- package/ios/core/GMSPolylineAnimator.m +85 -36
- package/ios/core/MKPolylineAnimator.m +63 -32
- package/package.json +1 -1
|
@@ -311,7 +311,6 @@ class LuggGoogleMapView(private val reactContext: ThemedReactContext) :
|
|
|
311
311
|
.position(position)
|
|
312
312
|
.title(markerView.title)
|
|
313
313
|
.snippet(markerView.description)
|
|
314
|
-
.collisionBehavior(CollisionBehavior.REQUIRED)
|
|
315
314
|
|
|
316
315
|
Log.d(TAG, "adding marker: ${markerView.name} customview: ${markerView.hasCustomView}")
|
|
317
316
|
if (markerView.hasCustomView) {
|
|
@@ -2,18 +2,24 @@ package com.luggmaps.core
|
|
|
2
2
|
|
|
3
3
|
import android.animation.ValueAnimator
|
|
4
4
|
import android.graphics.Color
|
|
5
|
+
import android.location.Location
|
|
5
6
|
import android.view.animation.LinearInterpolator
|
|
6
7
|
import com.google.android.gms.maps.model.LatLng
|
|
7
8
|
import com.google.android.gms.maps.model.Polyline
|
|
8
9
|
import com.google.android.gms.maps.model.StrokeStyle
|
|
9
10
|
import com.google.android.gms.maps.model.StyleSpan
|
|
10
11
|
import kotlin.math.floor
|
|
11
|
-
import kotlin.math.max
|
|
12
12
|
import kotlin.math.min
|
|
13
13
|
|
|
14
14
|
class PolylineAnimator {
|
|
15
15
|
var polyline: Polyline? = null
|
|
16
16
|
var coordinates: List<LatLng> = emptyList()
|
|
17
|
+
set(value) {
|
|
18
|
+
field = value
|
|
19
|
+
if (animated && animator != null) {
|
|
20
|
+
computeCumulativeDistances()
|
|
21
|
+
}
|
|
22
|
+
}
|
|
17
23
|
var strokeColors: List<Int> = listOf(Color.BLACK)
|
|
18
24
|
var strokeWidth: Float = 1f
|
|
19
25
|
|
|
@@ -31,6 +37,8 @@ class PolylineAnimator {
|
|
|
31
37
|
|
|
32
38
|
private var animator: ValueAnimator? = null
|
|
33
39
|
private var animationProgress: Float = 0f
|
|
40
|
+
private var cumulativeDistances: FloatArray = floatArrayOf()
|
|
41
|
+
private var totalLength: Float = 0f
|
|
34
42
|
|
|
35
43
|
fun update() {
|
|
36
44
|
if (animated) return
|
|
@@ -50,8 +58,10 @@ class PolylineAnimator {
|
|
|
50
58
|
private fun startAnimation() {
|
|
51
59
|
if (animator != null) return
|
|
52
60
|
|
|
61
|
+
computeCumulativeDistances()
|
|
62
|
+
|
|
53
63
|
animator = ValueAnimator.ofFloat(0f, 2.15f).apply {
|
|
54
|
-
duration =
|
|
64
|
+
duration = 2150
|
|
55
65
|
repeatCount = ValueAnimator.INFINITE
|
|
56
66
|
interpolator = LinearInterpolator()
|
|
57
67
|
addUpdateListener { animation ->
|
|
@@ -62,6 +72,58 @@ class PolylineAnimator {
|
|
|
62
72
|
}
|
|
63
73
|
}
|
|
64
74
|
|
|
75
|
+
private fun computeCumulativeDistances() {
|
|
76
|
+
if (coordinates.size < 2) {
|
|
77
|
+
cumulativeDistances = floatArrayOf(0f)
|
|
78
|
+
totalLength = 0f
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
val distances = FloatArray(coordinates.size)
|
|
83
|
+
distances[0] = 0f
|
|
84
|
+
var total = 0f
|
|
85
|
+
|
|
86
|
+
for (i in 1 until coordinates.size) {
|
|
87
|
+
val prev = coordinates[i - 1]
|
|
88
|
+
val curr = coordinates[i]
|
|
89
|
+
val results = FloatArray(1)
|
|
90
|
+
Location.distanceBetween(prev.latitude, prev.longitude, curr.latitude, curr.longitude, results)
|
|
91
|
+
total += results[0]
|
|
92
|
+
distances[i] = total
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
cumulativeDistances = distances
|
|
96
|
+
totalLength = total
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private fun indexForDistance(distance: Float): Int {
|
|
100
|
+
for (i in 1 until cumulativeDistances.size) {
|
|
101
|
+
if (cumulativeDistances[i] >= distance) {
|
|
102
|
+
return i - 1
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return (cumulativeDistances.size - 2).coerceAtLeast(0)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private fun coordinateAtDistance(distance: Float): LatLng {
|
|
109
|
+
if (distance <= 0f) return coordinates.first()
|
|
110
|
+
if (distance >= totalLength) return coordinates.last()
|
|
111
|
+
|
|
112
|
+
val idx = indexForDistance(distance)
|
|
113
|
+
val segStart = cumulativeDistances[idx]
|
|
114
|
+
val segEnd = cumulativeDistances[idx + 1]
|
|
115
|
+
val segLength = segEnd - segStart
|
|
116
|
+
|
|
117
|
+
val t = if (segLength > 0) (distance - segStart) / segLength else 0f
|
|
118
|
+
val c1 = coordinates[idx]
|
|
119
|
+
val c2 = coordinates[idx + 1]
|
|
120
|
+
|
|
121
|
+
return LatLng(
|
|
122
|
+
c1.latitude + (c2.latitude - c1.latitude) * t,
|
|
123
|
+
c1.longitude + (c2.longitude - c1.longitude) * t
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
65
127
|
private fun stopAnimation() {
|
|
66
128
|
animator?.cancel()
|
|
67
129
|
animator = null
|
|
@@ -69,71 +131,55 @@ class PolylineAnimator {
|
|
|
69
131
|
|
|
70
132
|
private fun updateAnimatedPolyline() {
|
|
71
133
|
val poly = polyline ?: return
|
|
72
|
-
if (coordinates.size < 2) {
|
|
134
|
+
if (coordinates.size < 2 || totalLength <= 0f) {
|
|
73
135
|
poly.points = coordinates
|
|
74
136
|
return
|
|
75
137
|
}
|
|
76
138
|
|
|
77
|
-
val segmentCount = coordinates.size - 1
|
|
78
139
|
val progress = min(animationProgress, 2f)
|
|
79
140
|
|
|
80
|
-
val
|
|
81
|
-
val
|
|
141
|
+
val headDist: Float
|
|
142
|
+
val tailDist: Float
|
|
82
143
|
|
|
83
144
|
if (progress <= 1f) {
|
|
84
|
-
|
|
85
|
-
|
|
145
|
+
tailDist = 0f
|
|
146
|
+
headDist = progress * totalLength
|
|
86
147
|
} else {
|
|
87
148
|
val shrinkProgress = progress - 1f
|
|
88
|
-
|
|
89
|
-
|
|
149
|
+
tailDist = shrinkProgress * totalLength
|
|
150
|
+
headDist = totalLength
|
|
90
151
|
}
|
|
91
152
|
|
|
92
|
-
if (
|
|
153
|
+
if (headDist <= tailDist) {
|
|
93
154
|
poly.setSpans(emptyList())
|
|
94
155
|
poly.points = listOf(coordinates.firstOrNull() ?: LatLng(0.0, 0.0))
|
|
95
156
|
return
|
|
96
157
|
}
|
|
97
158
|
|
|
98
|
-
val
|
|
99
|
-
val
|
|
100
|
-
val
|
|
159
|
+
val visibleLength = headDist - tailDist
|
|
160
|
+
val startIndex = indexForDistance(tailDist)
|
|
161
|
+
val endIndex = indexForDistance(headDist)
|
|
101
162
|
|
|
102
163
|
val points = mutableListOf<LatLng>()
|
|
103
164
|
val spans = mutableListOf<StyleSpan>()
|
|
104
165
|
|
|
105
|
-
|
|
106
|
-
var coord = coordinates[i]
|
|
107
|
-
|
|
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
|
-
}
|
|
166
|
+
points.add(coordinateAtDistance(tailDist))
|
|
117
167
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
}
|
|
168
|
+
for (i in (startIndex + 1)..endIndex) {
|
|
169
|
+
points.add(coordinates[i])
|
|
170
|
+
}
|
|
127
171
|
|
|
128
|
-
|
|
172
|
+
val endCoord = coordinateAtDistance(headDist)
|
|
173
|
+
val lastAdded = points.lastOrNull()
|
|
174
|
+
if (lastAdded == null || endCoord.latitude != lastAdded.latitude || endCoord.longitude != lastAdded.longitude) {
|
|
175
|
+
points.add(endCoord)
|
|
176
|
+
}
|
|
129
177
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
spans.add(StyleSpan(StrokeStyle.colorBuilder(color).build()))
|
|
136
|
-
}
|
|
178
|
+
for (i in 0 until (points.size - 1)) {
|
|
179
|
+
val segMidDist = tailDist + visibleLength * (i + 0.5f) / (points.size - 1)
|
|
180
|
+
val gradientPos = (segMidDist - tailDist) / visibleLength
|
|
181
|
+
val color = colorAtGradientPosition(gradientPos)
|
|
182
|
+
spans.add(StyleSpan(StrokeStyle.colorBuilder(color).build()))
|
|
137
183
|
}
|
|
138
184
|
|
|
139
185
|
poly.points = points
|
package/ios/LuggAppleMapView.mm
CHANGED
|
@@ -316,6 +316,9 @@ using namespace luggmaps::events;
|
|
|
316
316
|
// Build new polyline from coordinates
|
|
317
317
|
NSArray<CLLocation *> *coordinates = polylineView.coordinates;
|
|
318
318
|
if (coordinates.count == 0) {
|
|
319
|
+
if (renderer) {
|
|
320
|
+
renderer.animated = NO;
|
|
321
|
+
}
|
|
319
322
|
if (oldPolyline) {
|
|
320
323
|
[_mapView removeOverlay:oldPolyline];
|
|
321
324
|
polylineView.polyline = nil;
|
|
@@ -337,6 +340,8 @@ using namespace luggmaps::events;
|
|
|
337
340
|
|
|
338
341
|
// If we have an existing renderer, update it in place
|
|
339
342
|
if (renderer && oldPolyline) {
|
|
343
|
+
[_mapView removeOverlay:oldPolyline];
|
|
344
|
+
[_mapView addOverlay:newPolyline];
|
|
340
345
|
[renderer updatePolyline:newPolyline];
|
|
341
346
|
renderer.lineWidth = polylineView.strokeWidth;
|
|
342
347
|
renderer.strokeColor = polylineView.strokeColors.firstObject;
|
|
@@ -442,7 +447,6 @@ using namespace luggmaps::events;
|
|
|
442
447
|
reuseIdentifier:nil];
|
|
443
448
|
annotationView.canShowCallout = YES;
|
|
444
449
|
annotationView.displayPriority = MKFeatureDisplayPriorityRequired;
|
|
445
|
-
annotationView.collisionMode = MKAnnotationViewCollisionModeNone;
|
|
446
450
|
|
|
447
451
|
UIView *iconView = markerView.iconView;
|
|
448
452
|
[iconView removeFromSuperview];
|
package/ios/LuggGoogleMapView.mm
CHANGED
|
@@ -242,14 +242,14 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
242
242
|
marker.position = markerView.coordinate;
|
|
243
243
|
marker.title = markerView.title;
|
|
244
244
|
marker.snippet = markerView.markerDescription;
|
|
245
|
-
marker.groundAnchor = markerView.anchor;
|
|
246
|
-
|
|
247
245
|
if (markerView.hasCustomView) {
|
|
248
246
|
UIView *iconView = markerView.iconView;
|
|
249
247
|
[iconView removeFromSuperview];
|
|
250
248
|
marker.iconView = iconView;
|
|
249
|
+
marker.groundAnchor = markerView.anchor;
|
|
251
250
|
} else {
|
|
252
251
|
marker.iconView = nil;
|
|
252
|
+
marker.groundAnchor = CGPointMake(0.5, 1);
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
|
|
@@ -277,13 +277,12 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID";
|
|
|
277
277
|
marker.position = markerView.coordinate;
|
|
278
278
|
marker.title = markerView.title;
|
|
279
279
|
marker.snippet = markerView.markerDescription;
|
|
280
|
-
marker.collisionBehavior = GMSCollisionBehaviorRequired;
|
|
281
280
|
|
|
282
281
|
if (markerView.hasCustomView) {
|
|
283
282
|
marker.iconView = iconView;
|
|
283
|
+
marker.groundAnchor = markerView.anchor;
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
marker.groundAnchor = markerView.anchor;
|
|
287
286
|
marker.map = _mapView;
|
|
288
287
|
|
|
289
288
|
markerView.marker = marker;
|
|
@@ -4,12 +4,21 @@
|
|
|
4
4
|
@implementation GMSPolylineAnimator {
|
|
5
5
|
CADisplayLink *_displayLink;
|
|
6
6
|
CGFloat _animationProgress;
|
|
7
|
+
NSArray<NSNumber *> *_cumulativeDistances;
|
|
8
|
+
CGFloat _totalLength;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
- (void)dealloc {
|
|
10
12
|
[self stopAnimation];
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
- (void)setCoordinates:(NSArray<CLLocation *> *)coordinates {
|
|
16
|
+
[super setCoordinates:coordinates];
|
|
17
|
+
if (_animated && _displayLink) {
|
|
18
|
+
[self computeCumulativeDistances];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
- (void)setAnimated:(BOOL)animated {
|
|
14
23
|
if (_animated == animated) {
|
|
15
24
|
return;
|
|
@@ -28,18 +37,35 @@
|
|
|
28
37
|
if (_displayLink) {
|
|
29
38
|
return;
|
|
30
39
|
}
|
|
40
|
+
[self computeCumulativeDistances];
|
|
31
41
|
_animationProgress = 0;
|
|
32
42
|
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationTick:)];
|
|
33
43
|
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
|
34
44
|
}
|
|
35
45
|
|
|
46
|
+
- (void)computeCumulativeDistances {
|
|
47
|
+
NSMutableArray<NSNumber *> *distances = [NSMutableArray array];
|
|
48
|
+
CGFloat total = 0;
|
|
49
|
+
[distances addObject:@(0)];
|
|
50
|
+
|
|
51
|
+
for (NSUInteger i = 1; i < self.coordinates.count; i++) {
|
|
52
|
+
CLLocation *prev = self.coordinates[i - 1];
|
|
53
|
+
CLLocation *curr = self.coordinates[i];
|
|
54
|
+
total += [prev distanceFromLocation:curr];
|
|
55
|
+
[distances addObject:@(total)];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_cumulativeDistances = [distances copy];
|
|
59
|
+
_totalLength = total;
|
|
60
|
+
}
|
|
61
|
+
|
|
36
62
|
- (void)stopAnimation {
|
|
37
63
|
[_displayLink invalidate];
|
|
38
64
|
_displayLink = nil;
|
|
39
65
|
}
|
|
40
66
|
|
|
41
67
|
- (void)animationTick:(CADisplayLink *)displayLink {
|
|
42
|
-
CGFloat speed = displayLink.duration / 1.
|
|
68
|
+
CGFloat speed = displayLink.duration / 1.0;
|
|
43
69
|
_animationProgress += speed;
|
|
44
70
|
|
|
45
71
|
if (_animationProgress >= 2.15) {
|
|
@@ -71,63 +97,86 @@
|
|
|
71
97
|
}
|
|
72
98
|
}
|
|
73
99
|
|
|
100
|
+
- (NSUInteger)indexForDistance:(CGFloat)distance {
|
|
101
|
+
for (NSUInteger i = 1; i < _cumulativeDistances.count; i++) {
|
|
102
|
+
if (_cumulativeDistances[i].doubleValue >= distance) {
|
|
103
|
+
return i - 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return _cumulativeDistances.count - 2;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
- (CLLocationCoordinate2D)coordinateAtDistance:(CGFloat)distance {
|
|
110
|
+
if (distance <= 0) {
|
|
111
|
+
return self.coordinates.firstObject.coordinate;
|
|
112
|
+
}
|
|
113
|
+
if (distance >= _totalLength) {
|
|
114
|
+
return self.coordinates.lastObject.coordinate;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
NSUInteger idx = [self indexForDistance:distance];
|
|
118
|
+
CGFloat segStart = _cumulativeDistances[idx].doubleValue;
|
|
119
|
+
CGFloat segEnd = _cumulativeDistances[idx + 1].doubleValue;
|
|
120
|
+
CGFloat segLength = segEnd - segStart;
|
|
121
|
+
|
|
122
|
+
CGFloat t = (segLength > 0) ? (distance - segStart) / segLength : 0;
|
|
123
|
+
CLLocationCoordinate2D c1 = self.coordinates[idx].coordinate;
|
|
124
|
+
CLLocationCoordinate2D c2 = self.coordinates[idx + 1].coordinate;
|
|
125
|
+
|
|
126
|
+
return CLLocationCoordinate2DMake(c1.latitude + (c2.latitude - c1.latitude) * t,
|
|
127
|
+
c1.longitude + (c2.longitude - c1.longitude) * t);
|
|
128
|
+
}
|
|
129
|
+
|
|
74
130
|
- (void)updateAnimatedPolyline {
|
|
75
|
-
if (!_polyline || self.coordinates.count < 2) {
|
|
131
|
+
if (!_polyline || self.coordinates.count < 2 || _totalLength <= 0) {
|
|
76
132
|
return;
|
|
77
133
|
}
|
|
78
134
|
|
|
79
|
-
NSUInteger segmentCount = self.coordinates.count - 1;
|
|
80
135
|
CGFloat progress = MIN(_animationProgress, 2.0);
|
|
81
|
-
CGFloat
|
|
136
|
+
CGFloat headDist, tailDist;
|
|
82
137
|
|
|
83
138
|
if (progress <= 1.0) {
|
|
84
|
-
|
|
85
|
-
|
|
139
|
+
tailDist = 0;
|
|
140
|
+
headDist = progress * _totalLength;
|
|
86
141
|
} else {
|
|
87
142
|
CGFloat shrinkProgress = progress - 1.0;
|
|
88
|
-
|
|
89
|
-
|
|
143
|
+
tailDist = shrinkProgress * _totalLength;
|
|
144
|
+
headDist = _totalLength;
|
|
90
145
|
}
|
|
91
146
|
|
|
92
|
-
if (
|
|
147
|
+
if (headDist <= tailDist) {
|
|
93
148
|
_polyline.path = [GMSMutablePath path];
|
|
94
149
|
return;
|
|
95
150
|
}
|
|
96
151
|
|
|
97
|
-
|
|
98
|
-
NSUInteger
|
|
99
|
-
|
|
152
|
+
CGFloat visibleLength = headDist - tailDist;
|
|
153
|
+
NSUInteger startIndex = [self indexForDistance:tailDist];
|
|
154
|
+
NSUInteger endIndex = [self indexForDistance:headDist];
|
|
100
155
|
|
|
101
156
|
GMSMutablePath *path = [GMSMutablePath path];
|
|
102
157
|
NSMutableArray<GMSStyleSpan *> *spans = [NSMutableArray array];
|
|
103
158
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (i == startIndex && tailPos > (CGFloat)startIndex) {
|
|
108
|
-
CGFloat t = tailPos - (CGFloat)startIndex;
|
|
109
|
-
CLLocationCoordinate2D nextCoord = self.coordinates[i + 1].coordinate;
|
|
110
|
-
coord.latitude = coord.latitude + (nextCoord.latitude - coord.latitude) * t;
|
|
111
|
-
coord.longitude = coord.longitude + (nextCoord.longitude - coord.longitude) * t;
|
|
112
|
-
}
|
|
159
|
+
CLLocationCoordinate2D startCoord = [self coordinateAtDistance:tailDist];
|
|
160
|
+
[path addCoordinate:startCoord];
|
|
113
161
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
coord.latitude = prevCoord.latitude + (coord.latitude - prevCoord.latitude) * t;
|
|
118
|
-
coord.longitude = prevCoord.longitude + (coord.longitude - prevCoord.longitude) * t;
|
|
119
|
-
}
|
|
162
|
+
for (NSUInteger i = startIndex + 1; i <= endIndex; i++) {
|
|
163
|
+
[path addCoordinate:self.coordinates[i].coordinate];
|
|
164
|
+
}
|
|
120
165
|
|
|
121
|
-
|
|
166
|
+
CLLocationCoordinate2D endCoord = [self coordinateAtDistance:headDist];
|
|
167
|
+
CLLocationCoordinate2D lastAdded =
|
|
168
|
+
(endIndex < self.coordinates.count) ? self.coordinates[endIndex].coordinate : endCoord;
|
|
169
|
+
if (endCoord.latitude != lastAdded.latitude || endCoord.longitude != lastAdded.longitude) {
|
|
170
|
+
[path addCoordinate:endCoord];
|
|
171
|
+
}
|
|
122
172
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
173
|
+
NSUInteger pathCount = path.count;
|
|
174
|
+
for (NSUInteger i = 0; i < pathCount - 1; i++) {
|
|
175
|
+
CGFloat segMidDist = tailDist + visibleLength * ((CGFloat)i + 0.5) / (CGFloat)(pathCount - 1);
|
|
176
|
+
CGFloat gradientPos = (segMidDist - tailDist) / visibleLength;
|
|
177
|
+
UIColor *color = [self colorAtGradientPosition:gradientPos];
|
|
178
|
+
GMSStrokeStyle *style = [GMSStrokeStyle solidColor:color];
|
|
179
|
+
[spans addObject:[GMSStyleSpan spanWithStyle:style]];
|
|
131
180
|
}
|
|
132
181
|
|
|
133
182
|
_polyline.path = path;
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
@implementation MKPolylineAnimator {
|
|
5
5
|
MKPolyline *_polyline;
|
|
6
6
|
CADisplayLink *_displayLink;
|
|
7
|
-
CGFloat _animationProgress;
|
|
7
|
+
CGFloat _animationProgress;
|
|
8
|
+
NSArray<NSNumber *> *_cumulativeDistances;
|
|
9
|
+
CGFloat _totalLength;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
- (id)initWithPolyline:(MKPolyline *)polyline {
|
|
@@ -38,23 +40,47 @@
|
|
|
38
40
|
if (_displayLink) {
|
|
39
41
|
return;
|
|
40
42
|
}
|
|
43
|
+
[self computeCumulativeDistances];
|
|
41
44
|
_animationProgress = 0;
|
|
42
45
|
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationTick:)];
|
|
43
46
|
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
- (void)computeCumulativeDistances {
|
|
50
|
+
NSMutableArray<NSNumber *> *distances = [NSMutableArray array];
|
|
51
|
+
CGFloat total = 0;
|
|
52
|
+
[distances addObject:@(0)];
|
|
53
|
+
|
|
54
|
+
for (NSUInteger i = 1; i < _polyline.pointCount; i++) {
|
|
55
|
+
MKMapPoint p1 = _polyline.points[i - 1];
|
|
56
|
+
MKMapPoint p2 = _polyline.points[i];
|
|
57
|
+
total += MKMetersBetweenMapPoints(p1, p2);
|
|
58
|
+
[distances addObject:@(total)];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_cumulativeDistances = [distances copy];
|
|
62
|
+
_totalLength = total;
|
|
63
|
+
}
|
|
64
|
+
|
|
46
65
|
- (void)stopAnimation {
|
|
47
66
|
[_displayLink invalidate];
|
|
48
67
|
_displayLink = nil;
|
|
49
68
|
}
|
|
50
69
|
|
|
70
|
+
- (NSUInteger)indexForDistance:(CGFloat)distance {
|
|
71
|
+
for (NSUInteger i = 1; i < _cumulativeDistances.count; i++) {
|
|
72
|
+
if (_cumulativeDistances[i].doubleValue >= distance) {
|
|
73
|
+
return i - 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return _cumulativeDistances.count - 2;
|
|
77
|
+
}
|
|
78
|
+
|
|
51
79
|
- (void)animationTick:(CADisplayLink *)displayLink {
|
|
52
|
-
|
|
53
|
-
CGFloat speed = displayLink.duration / 1.75;
|
|
80
|
+
CGFloat speed = displayLink.duration / 1.0;
|
|
54
81
|
_animationProgress += speed;
|
|
55
82
|
|
|
56
|
-
|
|
57
|
-
if (_animationProgress >= 2.15) { // 2.0 + 0.15 pause (~300ms at this speed)
|
|
83
|
+
if (_animationProgress >= 2.15) {
|
|
58
84
|
_animationProgress = 0;
|
|
59
85
|
}
|
|
60
86
|
|
|
@@ -67,6 +93,9 @@
|
|
|
67
93
|
_polyline = polyline;
|
|
68
94
|
[self invalidatePath];
|
|
69
95
|
[self createPath];
|
|
96
|
+
if (_animated) {
|
|
97
|
+
[self computeCumulativeDistances];
|
|
98
|
+
}
|
|
70
99
|
[self setNeedsDisplay];
|
|
71
100
|
}
|
|
72
101
|
|
|
@@ -135,62 +164,64 @@
|
|
|
135
164
|
}
|
|
136
165
|
|
|
137
166
|
// Snake animation: grow from start, then shrink from start
|
|
138
|
-
if (_animated && _polyline.pointCount > 1) {
|
|
167
|
+
if (_animated && _polyline.pointCount > 1 && _totalLength > 0) {
|
|
139
168
|
CGFloat progress = MIN(_animationProgress, 2.0);
|
|
140
|
-
CGFloat
|
|
169
|
+
CGFloat headDist, tailDist;
|
|
141
170
|
|
|
142
171
|
if (progress <= 1.0) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
headPos = progress * segmentCount;
|
|
172
|
+
tailDist = 0;
|
|
173
|
+
headDist = progress * _totalLength;
|
|
146
174
|
} else {
|
|
147
|
-
// Phase 2: shrink from start
|
|
148
175
|
CGFloat shrinkProgress = progress - 1.0;
|
|
149
|
-
|
|
150
|
-
|
|
176
|
+
tailDist = shrinkProgress * _totalLength;
|
|
177
|
+
headDist = _totalLength;
|
|
151
178
|
}
|
|
152
179
|
|
|
153
|
-
if (
|
|
180
|
+
if (headDist <= tailDist) {
|
|
154
181
|
return;
|
|
155
182
|
}
|
|
156
183
|
|
|
157
|
-
|
|
158
|
-
NSUInteger
|
|
159
|
-
|
|
184
|
+
CGFloat visibleLength = headDist - tailDist;
|
|
185
|
+
NSUInteger startIndex = [self indexForDistance:tailDist];
|
|
186
|
+
NSUInteger endIndex = [self indexForDistance:headDist];
|
|
187
|
+
|
|
188
|
+
for (NSUInteger i = startIndex; i <= endIndex && i < segmentCount; i++) {
|
|
189
|
+
CGFloat segStartDist = _cumulativeDistances[i].doubleValue;
|
|
190
|
+
CGFloat segEndDist = _cumulativeDistances[i + 1].doubleValue;
|
|
191
|
+
|
|
192
|
+
if (segEndDist <= tailDist || segStartDist >= headDist) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
160
195
|
|
|
161
|
-
for (NSUInteger i = startIndex; i < endIndex && i < segmentCount; i++) {
|
|
162
196
|
CGPoint segStart = [self pointForMapPoint:_polyline.points[i]];
|
|
163
197
|
CGPoint segEnd = [self pointForMapPoint:_polyline.points[i + 1]];
|
|
164
198
|
|
|
165
199
|
CGPoint drawStart = segStart;
|
|
166
200
|
CGPoint drawEnd = segEnd;
|
|
167
|
-
CGFloat
|
|
168
|
-
CGFloat
|
|
201
|
+
CGFloat drawStartDist = segStartDist;
|
|
202
|
+
CGFloat drawEndDist = segEndDist;
|
|
203
|
+
CGFloat segLength = segEndDist - segStartDist;
|
|
169
204
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
CGFloat t = tailPos - segStartPos;
|
|
205
|
+
if (segStartDist < tailDist && segLength > 0) {
|
|
206
|
+
CGFloat t = (tailDist - segStartDist) / segLength;
|
|
173
207
|
drawStart.x = segStart.x + (segEnd.x - segStart.x) * t;
|
|
174
208
|
drawStart.y = segStart.y + (segEnd.y - segStart.y) * t;
|
|
175
|
-
|
|
209
|
+
drawStartDist = tailDist;
|
|
176
210
|
}
|
|
177
211
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
CGFloat t = headPos - (CGFloat)i;
|
|
212
|
+
if (segEndDist > headDist && segLength > 0) {
|
|
213
|
+
CGFloat t = (headDist - segStartDist) / segLength;
|
|
181
214
|
drawEnd.x = segStart.x + (segEnd.x - segStart.x) * t;
|
|
182
215
|
drawEnd.y = segStart.y + (segEnd.y - segStart.y) * t;
|
|
183
|
-
|
|
216
|
+
drawEndDist = headDist;
|
|
184
217
|
}
|
|
185
218
|
|
|
186
|
-
|
|
187
|
-
CGFloat
|
|
188
|
-
CGFloat gradientEnd = (segEndPos - tailPos) / visibleLength;
|
|
219
|
+
CGFloat gradientStart = (drawStartDist - tailDist) / visibleLength;
|
|
220
|
+
CGFloat gradientEnd = (drawEndDist - tailDist) / visibleLength;
|
|
189
221
|
|
|
190
222
|
UIColor *startColor = [self colorAtGradientPosition:gradientStart];
|
|
191
223
|
UIColor *endColor = [self colorAtGradientPosition:gradientEnd];
|
|
192
224
|
|
|
193
|
-
// Draw gradient line segment
|
|
194
225
|
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
195
226
|
NSArray *colors = @[(__bridge id)startColor.CGColor, (__bridge id)endColor.CGColor];
|
|
196
227
|
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, NULL);
|