diff --git a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java index 248c7664b..474e524d1 100644 --- a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java +++ b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java @@ -3,11 +3,13 @@ import android.animation.Animator; import android.location.Location; import android.os.SystemClock; +import android.util.SparseArray; +import android.view.animation.LinearInterpolator; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.Size; import androidx.annotation.VisibleForTesting; -import android.util.SparseArray; -import android.view.animation.LinearInterpolator; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.geometry.LatLng; @@ -33,6 +35,8 @@ import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_TILT; import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_ZOOM; import static com.mapbox.mapboxsdk.location.Utils.immediateAnimation; +import static com.mapbox.mapboxsdk.location.Utils.normalize; +import static com.mapbox.mapboxsdk.location.Utils.shortestRotation; final class LocationAnimatorCoordinator { @@ -74,6 +78,12 @@ void updateAnimatorListenerHolders(@NonNull Set listener void feedNewLocation(@NonNull Location newLocation, @NonNull CameraPosition currentCameraPosition, boolean isGpsNorth) { + feedNewLocation(new Location[] {newLocation}, currentCameraPosition, isGpsNorth, false); + } + + void feedNewLocation(@NonNull @Size(min = 1) Location[] newLocations, + @NonNull CameraPosition currentCameraPosition, boolean isGpsNorth, boolean lookAheadUpdate) { + Location newLocation = newLocations[newLocations.length - 1]; if (previousLocation == null) { previousLocation = newLocation; locationUpdateTimestamp = SystemClock.elapsedRealtime() - TRANSITION_ANIMATION_DURATION_MS; @@ -82,16 +92,23 @@ void feedNewLocation(@NonNull Location newLocation, @NonNull CameraPosition curr LatLng previousLayerLatLng = getPreviousLayerLatLng(); float previousLayerBearing = getPreviousLayerGpsBearing(); LatLng previousCameraLatLng = currentCameraPosition.target; - float previousCameraBearing = (float) currentCameraPosition.bearing; + float previousCameraBearing = normalize((float) currentCameraPosition.bearing); - LatLng targetLatLng = new LatLng(newLocation); - float targetLayerBearing = newLocation.getBearing(); - float targetCameraBearing = newLocation.getBearing(); - targetCameraBearing = checkGpsNorth(isGpsNorth, targetCameraBearing); + // generate targets for layer + LatLng[] latLngValues = getLatLngValues(previousLayerLatLng, newLocations); + Float[] bearingValues = getBearingValues(previousLayerBearing, newLocations); + updateLayerAnimators(latLngValues, bearingValues); - updateLayerAnimators(previousLayerLatLng, targetLatLng, previousLayerBearing, targetLayerBearing); - updateCameraAnimators(previousCameraLatLng, previousCameraBearing, targetLatLng, targetCameraBearing); + // replace the animation start with the camera's previous value + latLngValues[0] = previousCameraLatLng; + if (isGpsNorth) { + bearingValues = new Float[] {previousCameraBearing, 0f}; + } else { + bearingValues[0] = previousCameraBearing; + } + updateCameraAnimators(latLngValues, bearingValues); + LatLng targetLatLng = new LatLng(newLocation); boolean snap = immediateAnimation(projection, previousCameraLatLng, targetLatLng) || immediateAnimation(projection, previousLayerLatLng, targetLatLng); @@ -102,6 +119,15 @@ void feedNewLocation(@NonNull Location newLocation, @NonNull CameraPosition curr if (previousUpdateTimeStamp == 0) { animationDuration = 0; + } else if (lookAheadUpdate) { + long currentTimestamp = System.currentTimeMillis(); + if (currentTimestamp > newLocation.getTime()) { + animationDuration = 0; + Logger.e("LocationAnimatorCoordinator", + "Lookahead enabled, but the target location's timestamp is smaller than current timestamp"); + } else { + animationDuration = newLocation.getTime() - currentTimestamp; + } } else { animationDuration = (long) ((locationUpdateTimestamp - previousUpdateTimeStamp) * durationMultiplier) /* make animation slightly longer with durationMultiplier, defaults to 1.1f */; @@ -207,23 +233,35 @@ private float getPreviousAccuracyRadius() { return previousRadius; } - private void updateLayerAnimators(LatLng previousLatLng, LatLng targetLatLng, - float previousBearing, float targetBearing) { - createNewLatLngAnimator(ANIMATOR_LAYER_LATLNG, previousLatLng, targetLatLng); + private LatLng[] getLatLngValues(LatLng previousLatLng, Location[] targetLocations) { + LatLng[] latLngs = new LatLng[targetLocations.length + 1]; + latLngs[0] = previousLatLng; + for (int i = 1; i < latLngs.length; i++) { + latLngs[i] = new LatLng(targetLocations[i - 1]); + } + return latLngs; + } + + private Float[] getBearingValues(Float previousBearing, Location[] targetLocations) { + Float[] bearings = new Float[targetLocations.length + 1]; // Because Location bearing values are normalized to [0, 360] // we need to do the same for the previous bearing value to determine the shortest path - previousBearing = Utils.normalize(previousBearing); - float normalizedLayerBearing = Utils.shortestRotation(targetBearing, previousBearing); - createNewFloatAnimator(ANIMATOR_LAYER_GPS_BEARING, previousBearing, normalizedLayerBearing); + bearings[0] = normalize(previousBearing); + for (int i = 1; i < bearings.length; i++) { + bearings[i] = shortestRotation(targetLocations[i - 1].getBearing(), bearings[i - 1]); + } + return bearings; } - private void updateCameraAnimators(LatLng previousCameraLatLng, float previousCameraBearing, - LatLng targetLatLng, float targetBearing) { - createNewLatLngAnimator(ANIMATOR_CAMERA_LATLNG, previousCameraLatLng, targetLatLng); + private void updateLayerAnimators(LatLng[] latLngValues, Float[] bearingValues) { + createNewLatLngAnimator(ANIMATOR_LAYER_LATLNG, latLngValues); + createNewFloatAnimator(ANIMATOR_LAYER_GPS_BEARING, bearingValues); + } - float normalizedCameraBearing = Utils.shortestRotation(targetBearing, previousCameraBearing); - createNewFloatAnimator(ANIMATOR_CAMERA_GPS_BEARING, previousCameraBearing, normalizedCameraBearing); + private void updateCameraAnimators(LatLng[] latLngValues, Float[] bearingValues) { + createNewLatLngAnimator(ANIMATOR_CAMERA_LATLNG, latLngValues); + createNewFloatAnimator(ANIMATOR_CAMERA_GPS_BEARING, bearingValues); } private void updateCompassAnimators(float targetCompassBearing, float previousLayerBearing, @@ -241,36 +279,45 @@ private void updateAccuracyAnimators(float targetAccuracyRadius, float previousA private void updateZoomAnimator(float targetZoomLevel, float previousZoomLevel, @Nullable MapboxMap.CancelableCallback cancelableCallback) { - createNewCameraAdapterAnimator(ANIMATOR_ZOOM, previousZoomLevel, targetZoomLevel, cancelableCallback); + createNewCameraAdapterAnimator(ANIMATOR_ZOOM, new Float[] {previousZoomLevel, targetZoomLevel}, cancelableCallback); } private void updateTiltAnimator(float targetTilt, float previousTiltLevel, @Nullable MapboxMap.CancelableCallback cancelableCallback) { - createNewCameraAdapterAnimator(ANIMATOR_TILT, previousTiltLevel, targetTilt, cancelableCallback); + createNewCameraAdapterAnimator(ANIMATOR_TILT, new Float[] {previousTiltLevel, targetTilt}, cancelableCallback); } private void createNewLatLngAnimator(@MapboxAnimator.Type int animatorType, LatLng previous, LatLng target) { + createNewLatLngAnimator(animatorType, new LatLng[] {previous, target}); + } + + private void createNewLatLngAnimator(@MapboxAnimator.Type int animatorType, LatLng[] values) { cancelAnimator(animatorType); MapboxAnimator.AnimationsValueChangeListener listener = listeners.get(animatorType); if (listener != null) { - animatorArray.put(animatorType, animatorProvider.latLngAnimator(previous, target, listener, maxAnimationFps)); + animatorArray.put(animatorType, animatorProvider.latLngAnimator(values, listener, maxAnimationFps)); } } private void createNewFloatAnimator(@MapboxAnimator.Type int animatorType, float previous, float target) { + createNewFloatAnimator(animatorType, new Float[] {previous, target}); + } + + private void createNewFloatAnimator(@MapboxAnimator.Type int animatorType, @NonNull @Size(min = 2) Float[] values) { cancelAnimator(animatorType); MapboxAnimator.AnimationsValueChangeListener listener = listeners.get(animatorType); if (listener != null) { - animatorArray.put(animatorType, animatorProvider.floatAnimator(previous, target, listener, maxAnimationFps)); + animatorArray.put(animatorType, animatorProvider.floatAnimator(values, listener, maxAnimationFps)); } } - private void createNewCameraAdapterAnimator(@MapboxAnimator.Type int animatorType, float previous, float target, + private void createNewCameraAdapterAnimator(@MapboxAnimator.Type int animatorType, + @NonNull @Size(min = 2) Float[] values, @Nullable MapboxMap.CancelableCallback cancelableCallback) { cancelAnimator(animatorType); MapboxAnimator.AnimationsValueChangeListener listener = listeners.get(animatorType); if (listener != null) { - animatorArray.put(animatorType, animatorProvider.cameraAnimator(previous, target, listener, cancelableCallback)); + animatorArray.put(animatorType, animatorProvider.cameraAnimator(values, listener, cancelableCallback)); } } diff --git a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java index 9df7a1456..0f7dfded0 100644 --- a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java +++ b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java @@ -6,12 +6,13 @@ import android.location.Location; import android.os.Looper; import android.os.SystemClock; +import android.view.WindowManager; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresPermission; import androidx.annotation.StyleRes; import androidx.annotation.VisibleForTesting; -import android.view.WindowManager; import com.mapbox.android.core.location.LocationEngine; import com.mapbox.android.core.location.LocationEngineCallback; @@ -889,6 +890,32 @@ public void forceLocationUpdate(@Nullable Location location) { updateLocation(location, false); } + /** + * Use to either force a location update or to manually control when the user location gets + * updated. + *

+ * This method can be used to provide the list of locations where the last one is the target location + * and the rest are intermediate points used as the animation path. + * The puck and the camera will be animated between each of the points linearly until reaching the target. + * + * @param locations where the location icon is placed on the map + * @param lookAheadUpdate If set to true, the last location's timestamp has to be greater than current timestamp and + * should represent the time at which the animation should actually reach this position, + * cutting out the time interpolation delay. + */ + public void forceLocationUpdate(@Nullable List locations, boolean lookAheadUpdate) { + checkActivationState(); + if (locations != null && locations.size() >= 1) { + updateLocation( + locations.get(locations.size() - 1), // target location + locations.subList(0, locations.size() - 1), // intermediate locations + false, + lookAheadUpdate); + } else { + updateLocation(null, false); + } + } + /** * Set max FPS at which location animators can output updates. The throttling will only impact the location puck * and camera tracking smooth animations. @@ -1341,6 +1368,11 @@ private void updateMapWithOptions(@NonNull LocationComponentOptions options) { * @param location the latest user location */ private void updateLocation(@Nullable final Location location, boolean fromLastLocation) { + updateLocation(location, null, fromLastLocation, false); + } + + private void updateLocation(@Nullable final Location location, @Nullable List intermediatePoints, + boolean fromLastLocation, boolean lookAheadUpdate) { if (location == null) { return; } else if (!isLayerReady) { @@ -1362,11 +1394,28 @@ private void updateLocation(@Nullable final Location location, boolean fromLastL } CameraPosition currentCameraPosition = mapboxMap.getCameraPosition(); boolean isGpsNorth = getCameraMode() == CameraMode.TRACKING_GPS_NORTH; - locationAnimatorCoordinator.feedNewLocation(location, currentCameraPosition, isGpsNorth); + if (intermediatePoints != null) { + locationAnimatorCoordinator.feedNewLocation( + getTargetLocationWithIntermediates(location, intermediatePoints), + currentCameraPosition, + isGpsNorth, + lookAheadUpdate); + } else { + locationAnimatorCoordinator.feedNewLocation(location, currentCameraPosition, isGpsNorth); + } updateAccuracyRadius(location, false); lastLocation = location; } + private Location[] getTargetLocationWithIntermediates(Location location, List intermediatePoints) { + Location[] locations = new Location[intermediatePoints.size() + 1]; + locations[locations.length - 1] = location; + for (int i = 0; i < intermediatePoints.size(); i++) { + locations[i] = intermediatePoints.get(i); + } + return locations; + } + private void showLocationLayerIfHidden() { boolean isLocationLayerHidden = locationLayerController.isHidden(); if (isEnabled && isComponentStarted && isLocationLayerHidden) { diff --git a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxAnimator.java b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxAnimator.java index 7038367e3..f9a213241 100644 --- a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxAnimator.java +++ b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxAnimator.java @@ -4,8 +4,10 @@ import android.animation.AnimatorListenerAdapter; import android.animation.TypeEvaluator; import android.animation.ValueAnimator; + import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import androidx.annotation.Size; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -48,13 +50,13 @@ abstract class MapboxAnimator extends ValueAnimator implements ValueAnimator. private final double minUpdateInterval; private long timeElapsed; - MapboxAnimator(@NonNull K previous, @NonNull K target, @NonNull AnimationsValueChangeListener updateListener, + MapboxAnimator(@NonNull @Size(min = 2) K[] values, @NonNull AnimationsValueChangeListener updateListener, int maxAnimationFps) { minUpdateInterval = 1E9 / maxAnimationFps; - setObjectValues(previous, target); + setObjectValues((Object[]) values); setEvaluator(provideEvaluator()); this.updateListener = updateListener; - this.target = target; + this.target = values[values.length - 1]; addUpdateListener(this); addListener(new AnimatorListener()); } diff --git a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxAnimatorProvider.java b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxAnimatorProvider.java index 7782d8822..c1109b959 100644 --- a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxAnimatorProvider.java +++ b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxAnimatorProvider.java @@ -20,21 +20,19 @@ public static MapboxAnimatorProvider getInstance() { return INSTANCE; } - MapboxLatLngAnimator latLngAnimator(LatLng previous, LatLng target, - MapboxAnimator.AnimationsValueChangeListener updateListener, + MapboxLatLngAnimator latLngAnimator(LatLng[] values, MapboxAnimator.AnimationsValueChangeListener updateListener, int maxAnimationFps) { - return new MapboxLatLngAnimator(previous, target, updateListener, maxAnimationFps); + return new MapboxLatLngAnimator(values, updateListener, maxAnimationFps); } - MapboxFloatAnimator floatAnimator(Float previous, Float target, - MapboxAnimator.AnimationsValueChangeListener updateListener, + MapboxFloatAnimator floatAnimator(Float[] values, MapboxAnimator.AnimationsValueChangeListener updateListener, int maxAnimationFps) { - return new MapboxFloatAnimator(previous, target, updateListener, maxAnimationFps); + return new MapboxFloatAnimator(values, updateListener, maxAnimationFps); } - MapboxCameraAnimatorAdapter cameraAnimator(Float previous, Float target, + MapboxCameraAnimatorAdapter cameraAnimator(Float[] values, MapboxAnimator.AnimationsValueChangeListener updateListener, @Nullable MapboxMap.CancelableCallback cancelableCallback) { - return new MapboxCameraAnimatorAdapter(previous, target, updateListener, cancelableCallback); + return new MapboxCameraAnimatorAdapter(values, updateListener, cancelableCallback); } } diff --git a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxCameraAnimatorAdapter.java b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxCameraAnimatorAdapter.java index 52ab7c4fc..a3b9fff42 100644 --- a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxCameraAnimatorAdapter.java +++ b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxCameraAnimatorAdapter.java @@ -2,7 +2,10 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.Size; import com.mapbox.mapboxsdk.maps.MapboxMap; @@ -10,10 +13,10 @@ class MapboxCameraAnimatorAdapter extends MapboxFloatAnimator { @Nullable private final MapboxMap.CancelableCallback cancelableCallback; - MapboxCameraAnimatorAdapter(Float previous, Float target, + MapboxCameraAnimatorAdapter(@NonNull @Size(min = 2) Float[] values, AnimationsValueChangeListener updateListener, @Nullable MapboxMap.CancelableCallback cancelableCallback) { - super(previous, target, updateListener, Integer.MAX_VALUE); + super(values, updateListener, Integer.MAX_VALUE); this.cancelableCallback = cancelableCallback; addListener(new MapboxAnimatorListener()); } diff --git a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxFloatAnimator.java b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxFloatAnimator.java index 94f405b69..cad5fc983 100644 --- a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxFloatAnimator.java +++ b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxFloatAnimator.java @@ -2,11 +2,14 @@ import android.animation.FloatEvaluator; import android.animation.TypeEvaluator; + import androidx.annotation.NonNull; +import androidx.annotation.Size; class MapboxFloatAnimator extends MapboxAnimator { - MapboxFloatAnimator(Float previous, Float target, AnimationsValueChangeListener updateListener, int maxAnimationFps) { - super(previous, target, updateListener, maxAnimationFps); + MapboxFloatAnimator(@NonNull @Size(min = 2) Float[] values, + @NonNull AnimationsValueChangeListener updateListener, int maxAnimationFps) { + super(values, updateListener, maxAnimationFps); } @NonNull diff --git a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxLatLngAnimator.java b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxLatLngAnimator.java index 524847036..a32bf63ef 100644 --- a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxLatLngAnimator.java +++ b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxLatLngAnimator.java @@ -1,15 +1,16 @@ package com.mapbox.mapboxsdk.location; import android.animation.TypeEvaluator; + import androidx.annotation.NonNull; import com.mapbox.mapboxsdk.geometry.LatLng; class MapboxLatLngAnimator extends MapboxAnimator { - MapboxLatLngAnimator(LatLng previous, LatLng target, AnimationsValueChangeListener updateListener, + MapboxLatLngAnimator(@NonNull LatLng[] values, @NonNull AnimationsValueChangeListener updateListener, int maxAnimationFps) { - super(previous, target, updateListener, maxAnimationFps); + super(values, updateListener, maxAnimationFps); } @NonNull diff --git a/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinatorTest.kt b/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinatorTest.kt index 1b927d213..bd741e5b9 100644 --- a/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinatorTest.kt +++ b/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinatorTest.kt @@ -56,34 +56,32 @@ class LocationAnimatorCoordinatorTest { // workaround https://github.com/mockk/mockk/issues/229#issuecomment-457816131 registerInstanceFactory { AnimationsValueChangeListener {} } registerInstanceFactory { AnimationsValueChangeListener {} } - val previousFloatSlot = slot() - val targetFloatSlot = slot() + val floatsSlot = slot>() val listenerSlot = slot>() val maxFpsSlot = slot() every { - animatorProvider.floatAnimator(capture(previousFloatSlot), capture(targetFloatSlot), capture(listenerSlot), capture(maxFpsSlot)) + animatorProvider.floatAnimator(capture(floatsSlot), capture(listenerSlot), capture(maxFpsSlot)) } answers { - MapboxFloatAnimator(previousFloatSlot.captured, targetFloatSlot.captured, listenerSlot.captured, maxFpsSlot.captured) + MapboxFloatAnimator(floatsSlot.captured, listenerSlot.captured, maxFpsSlot.captured) } - val previousLatLngSlot = slot() - val targetLatLngSlot = slot() + val latLngsSlot = slot>() every { - animatorProvider.latLngAnimator(capture(previousLatLngSlot), capture(targetLatLngSlot), capture(listenerSlot), capture(maxFpsSlot)) + animatorProvider.latLngAnimator(capture(latLngsSlot), capture(listenerSlot), capture(maxFpsSlot)) } answers { - MapboxLatLngAnimator(previousLatLngSlot.captured, targetLatLngSlot.captured, listenerSlot.captured, maxFpsSlot.captured) + MapboxLatLngAnimator(latLngsSlot.captured, listenerSlot.captured, maxFpsSlot.captured) } val callback = slot() every { - animatorProvider.cameraAnimator(capture(previousFloatSlot), capture(targetFloatSlot), capture(listenerSlot), capture(callback)) + animatorProvider.cameraAnimator(capture(floatsSlot), capture(listenerSlot), capture(callback)) } answers { - MapboxCameraAnimatorAdapter(previousFloatSlot.captured, targetFloatSlot.captured, listenerSlot.captured, callback.captured) + MapboxCameraAnimatorAdapter(floatsSlot.captured, listenerSlot.captured, callback.captured) } every { - animatorProvider.cameraAnimator(capture(previousFloatSlot), capture(targetFloatSlot), capture(listenerSlot), null) + animatorProvider.cameraAnimator(capture(floatsSlot), capture(listenerSlot), null) } answers { - MapboxCameraAnimatorAdapter(previousFloatSlot.captured, targetFloatSlot.captured, listenerSlot.captured, null) + MapboxCameraAnimatorAdapter(floatsSlot.captured, listenerSlot.captured, null) } } @@ -106,16 +104,112 @@ class LocationAnimatorCoordinatorTest { locationAnimatorCoordinator.feedNewLocation(location, cameraPosition, false) val cameraLatLngTarget = locationAnimatorCoordinator.animatorArray[ANIMATOR_CAMERA_LATLNG]?.target as LatLng - assertEquals(cameraLatLngTarget.latitude, cameraLatLngTarget.latitude) + assertEquals(location.latitude, cameraLatLngTarget.latitude) + assertEquals(location.longitude, cameraLatLngTarget.longitude) val layerLatLngTarget = locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_LATLNG]?.target as LatLng - assertEquals(layerLatLngTarget.latitude, layerLatLngTarget.latitude) + assertEquals(location.latitude, layerLatLngTarget.latitude) + assertEquals(location.longitude, layerLatLngTarget.longitude) + + val cameraBearingTarget = locationAnimatorCoordinator.animatorArray[ANIMATOR_CAMERA_GPS_BEARING]?.target as Float + assertEquals(location.bearing, cameraBearingTarget) + + val layerBearingTarget = locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_GPS_BEARING]?.target as Float + assertEquals(location.bearing, layerBearingTarget) + } + + @Test + fun feedNewLocation_animatorValue_multiplePoints() { + val previousLocation = Location("") + previousLocation.latitude = 0.0 + previousLocation.longitude = 0.0 + previousLocation.bearing = 0f + + val locationInter = Location("") + locationInter.latitude = 51.1 + locationInter.longitude = 17.1 + locationInter.bearing = 35f + val location = Location("") + location.latitude = 51.2 + location.longitude = 17.2 + location.bearing = 36f + locationAnimatorCoordinator.feedNewLocation(arrayOf(locationInter, location), cameraPosition, false, false) + + val cameraLatLngTarget = locationAnimatorCoordinator.animatorArray[ANIMATOR_CAMERA_LATLNG]?.target as LatLng + assertEquals(location.latitude, cameraLatLngTarget.latitude) + assertEquals(location.longitude, cameraLatLngTarget.longitude) + + val layerLatLngTarget = locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_LATLNG]?.target as LatLng + assertEquals(location.latitude, layerLatLngTarget.latitude) + assertEquals(location.longitude, layerLatLngTarget.longitude) val cameraBearingTarget = locationAnimatorCoordinator.animatorArray[ANIMATOR_CAMERA_GPS_BEARING]?.target as Float assertEquals(location.bearing, cameraBearingTarget) val layerBearingTarget = locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_GPS_BEARING]?.target as Float assertEquals(location.bearing, layerBearingTarget) + + verify { + animatorProvider.latLngAnimator( + arrayOf( + LatLng(previousLocation.latitude, previousLocation.longitude), + LatLng(locationInter.latitude, locationInter.longitude), + LatLng(location.latitude, location.longitude) + ), any(), any() + ) + } + verify { + animatorProvider.floatAnimator( + arrayOf(previousLocation.bearing, locationInter.bearing, location.bearing), any(), any() + ) + } + } + + @Test + fun feedNewLocation_animatorValue_multiplePoints_animationDuration() { + every { projection.getMetersPerPixelAtLatitude(any()) } answers { 10000.0 } // disable snap + val locationInter = Location("") + locationInter.latitude = 51.1 + locationInter.longitude = 17.1 + locationInter.bearing = 35f + val location = Location("") + location.latitude = 51.2 + location.longitude = 17.2 + location.bearing = 36f + locationAnimatorCoordinator.feedNewLocation(arrayOf(locationInter, location), cameraPosition, false, false) + + verify { + animatorSetProvider.startAnimation(eq(listOf( + locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_LATLNG], + locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_GPS_BEARING], + locationAnimatorCoordinator.animatorArray[ANIMATOR_CAMERA_LATLNG], + locationAnimatorCoordinator.animatorArray[ANIMATOR_CAMERA_GPS_BEARING] + )), any(), 0) + } + } + + @Test + fun feedNewLocation_animatorValue_multiplePoints_animationDuration_lookAhead() { + every { projection.getMetersPerPixelAtLatitude(any()) } answers { 10000.0 } // disable snap + val locationInter = Location("") + locationInter.latitude = 51.1 + locationInter.longitude = 17.1 + locationInter.bearing = 35f + val location = Location("") + location.latitude = 51.2 + location.longitude = 17.2 + location.bearing = 36f + location.time = System.currentTimeMillis() + 2000 + locationAnimatorCoordinator.feedNewLocation(arrayOf(locationInter, location), cameraPosition, false, true) + + verify { + animatorSetProvider.startAnimation(eq(listOf( + locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_LATLNG], + locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_GPS_BEARING], + locationAnimatorCoordinator.animatorArray[ANIMATOR_CAMERA_LATLNG], + locationAnimatorCoordinator.animatorArray[ANIMATOR_CAMERA_GPS_BEARING] + )), any(), more(1500L)) + } } @Test @@ -508,8 +602,8 @@ class LocationAnimatorCoordinatorTest { fun maxFps_givenToAnimator() { locationAnimatorCoordinator.setMaxAnimationFps(5) locationAnimatorCoordinator.feedNewLocation(Location(""), cameraPosition, false) - verify { animatorProvider.latLngAnimator(any(), any(), any(), 5) } - verify { animatorProvider.floatAnimator(any(), any(), any(), 5) } + verify { animatorProvider.latLngAnimator(any(), any(), 5) } + verify { animatorProvider.floatAnimator(any(), any(), 5) } } private fun getListenerHoldersSet(vararg animatorTypes: Int): Set { diff --git a/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/MapboxAnimatorTest.kt b/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/MapboxAnimatorTest.kt index 7c126c783..0c5b770b4 100644 --- a/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/MapboxAnimatorTest.kt +++ b/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/MapboxAnimatorTest.kt @@ -17,7 +17,7 @@ class MapboxAnimatorTest { every { valueAnimator.animatedValue } answers { 5f } val listener = mockk>() every { listener.onNewAnimationValue(any()) } answers {} - val mapboxAnimator = MapboxFloatAnimator(0f, 10f, listener, Int.MAX_VALUE) + val mapboxAnimator = MapboxFloatAnimator(floatArrayOf(0f, 10f).toTypedArray(), listener, Int.MAX_VALUE) for (i in 0 until 5) mapboxAnimator.onAnimationUpdate(valueAnimator) @@ -31,7 +31,7 @@ class MapboxAnimatorTest { every { valueAnimator.animatedValue } answers { 5f } val listener = mockk>() every { listener.onNewAnimationValue(any()) } answers {} - val mapboxAnimator = MapboxFloatAnimator(0f, 10f, listener, 5) + val mapboxAnimator = MapboxFloatAnimator(floatArrayOf(0f, 10f).toTypedArray(), listener, 5) for (i in 0 until 5) { mapboxAnimator.onAnimationUpdate(valueAnimator) diff --git a/vendor/mapbox-gl-native b/vendor/mapbox-gl-native index 236afc039..3ea459233 160000 --- a/vendor/mapbox-gl-native +++ b/vendor/mapbox-gl-native @@ -1 +1 @@ -Subproject commit 236afc03958cc729b3b8121a1c5a72660f0e9fa2 +Subproject commit 3ea459233a60ee737d0b637582f94024f3763797