diff --git a/app/src/androidTest/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerTest.kt b/app/src/androidTest/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerTest.kt index 26bda75fd..f4d521559 100644 --- a/app/src/androidTest/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerTest.kt +++ b/app/src/androidTest/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerTest.kt @@ -15,7 +15,9 @@ import android.support.test.rule.ActivityTestRule import android.support.test.rule.GrantPermissionRule import android.support.test.rule.GrantPermissionRule.grant import android.support.test.runner.AndroidJUnit4 +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory import com.mapbox.mapboxsdk.constants.Style +import com.mapbox.mapboxsdk.geometry.LatLng import com.mapbox.mapboxsdk.maps.MapboxMap import com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.* import com.mapbox.mapboxsdk.plugins.locationlayer.modes.RenderMode @@ -30,6 +32,7 @@ import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.notNullValue import org.hamcrest.Matchers.equalTo import org.junit.After +import org.junit.Assert.assertEquals import org.junit.Assert.assertThat import org.junit.Before import org.junit.Rule @@ -56,6 +59,7 @@ class LocationLayerTest { val initLocation = Location("test") initLocation.latitude = 15.0 initLocation.longitude = 17.0 + initLocation.accuracy = 2000f initLocation } @@ -259,6 +263,62 @@ class LocationLayerTest { onView(withId(R.id.content)).check(matches(isDisplayed())) } + @Test + fun accuracy_visibleWithNewLocation() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + mapboxMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(location), 16.0)) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + assertEquals(Utils.calculateZoomLevelRadius(mapboxMap, location) /*meters projected to radius on zoom 16*/, + mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0] + .getNumberProperty(PROPERTY_ACCURACY_RADIUS).toFloat(), 0.1f) + } + } + executePluginTest(pluginAction) + } + + @Test + fun accuracy_visibleWhenCameraEased() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + mapboxMap.easeCamera(CameraUpdateFactory.newLatLngZoom(LatLng(location), 16.0), 300) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY + 300) + + assertEquals(Utils.calculateZoomLevelRadius(mapboxMap, location) /*meters projected to radius on zoom 16*/, + mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0] + .getNumberProperty(PROPERTY_ACCURACY_RADIUS).toFloat(), 0.1f) + } + } + executePluginTest(pluginAction) + } + + @Test + fun accuracy_visibleWhenCameraMoved() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + mapboxMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(location), 16.0)) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY + 300) + + assertEquals(Utils.calculateZoomLevelRadius(mapboxMap, location) /*meters projected to radius on zoom 16*/, + mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0] + .getNumberProperty(PROPERTY_ACCURACY_RADIUS).toFloat(), 0.1f) + } + } + executePluginTest(pluginAction) + } + @After fun afterTest() { Timber.e("@After: unregister idle resource") diff --git a/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayer.java b/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayer.java index c527b3fc9..7d689b5b8 100644 --- a/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayer.java +++ b/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayer.java @@ -48,6 +48,7 @@ import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_SHADOW_ICON_OFFSET; import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.SHADOW_ICON; import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.SHADOW_LAYER; +import static com.mapbox.mapboxsdk.plugins.locationlayer.Utils.calculateZoomLevelRadius; import static com.mapbox.mapboxsdk.plugins.locationlayer.Utils.generateShadow; import static com.mapbox.mapboxsdk.plugins.locationlayer.Utils.getBitmapFromDrawable; import static com.mapbox.mapboxsdk.plugins.locationlayer.Utils.getDrawable; @@ -263,20 +264,11 @@ private void setBearingProperty(String propertyId, float bearing) { void updateAccuracyRadius(Location location) { if (renderMode == RenderMode.COMPASS || renderMode == RenderMode.NORMAL) { - locationFeature.addNumberProperty(PROPERTY_ACCURACY_RADIUS, calculateZoomLevelRadius(location)); + locationFeature.addNumberProperty(PROPERTY_ACCURACY_RADIUS, calculateZoomLevelRadius(mapboxMap, location)); refreshSource(); } } - private float calculateZoomLevelRadius(Location location) { - if (location == null) { - return 0; - } - double metersPerPixel = mapboxMap.getProjection().getMetersPerPixelAtLatitude( - location.getLatitude()); - return (float) (location.getAccuracy() * (1 / metersPerPixel)); - } - void updateForegroundOffset(double tilt) { JsonArray foregroundJsonArray = new JsonArray(); foregroundJsonArray.add(0f); diff --git a/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerPlugin.java b/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerPlugin.java index 8235f92e5..761c676ac 100644 --- a/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerPlugin.java +++ b/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerPlugin.java @@ -20,6 +20,7 @@ import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapView.OnMapChangedListener; import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraIdleListener; import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveListener; import com.mapbox.mapboxsdk.maps.MapboxMap.OnMapClickListener; import com.mapbox.mapboxsdk.plugins.locationlayer.modes.CameraMode; @@ -72,6 +73,11 @@ public final class LocationLayerPlugin implements LifecycleObserver { private LocationLayerAnimator locationLayerAnimator; + /** + * Holds last location which is being returned in the {@link #getLastKnownLocation()} + * when there is no {@link #locationEngine} set or when the last location returned by the engine is null. + */ + private Location lastLocation; private CameraPosition lastCameraPosition; /** @@ -358,7 +364,11 @@ public LocationEngine getLocationEngine() { @Nullable @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}) public Location getLastKnownLocation() { - return locationEngine != null ? locationEngine.getLastLocation() : null; + Location location = locationEngine != null ? locationEngine.getLastLocation() : null; + if (location == null) { + location = lastLocation; + } + return location; } /** @@ -508,6 +518,7 @@ void onLocationLayerStart() { } if (mapboxMap != null) { mapboxMap.addOnCameraMoveListener(onCameraMoveListener); + mapboxMap.addOnCameraIdleListener(onCameraIdleListener); } if (options.enableStaleState()) { staleStateManager.onStart(); @@ -530,6 +541,7 @@ void onLocationLayerStop() { } if (mapboxMap != null) { mapboxMap.removeOnCameraMoveListener(onCameraMoveListener); + mapboxMap.removeOnCameraIdleListener(onCameraIdleListener); } } @@ -593,14 +605,19 @@ private void updateMapWithOptions(final LocationLayerOptions options) { * @since 0.1.0 */ private void updateLocation(final Location location) { - if (location == null || !isLocationLayerStarted) { + if (location == null) { + return; + } else if (!isLocationLayerStarted) { + lastLocation = location; return; } + staleStateManager.updateLatestLocationTime(); CameraPosition currentCameraPosition = mapboxMap.getCameraPosition(); boolean isGpsNorth = getCameraMode() == CameraMode.TRACKING_GPS_NORTH; locationLayerAnimator.feedNewLocation(location, currentCameraPosition, isGpsNorth); locationLayer.updateAccuracyRadius(location); + lastLocation = location; } private void updateCompassHeading(float heading) { @@ -646,13 +663,19 @@ private void updateLayerOffsets(boolean forceUpdate) { } private OnCameraMoveListener onCameraMoveListener = new OnCameraMoveListener() { - @Override public void onCameraMove() { updateLayerOffsets(false); } }; + private OnCameraIdleListener onCameraIdleListener = new OnCameraIdleListener() { + @Override + public void onCameraIdle() { + updateLayerOffsets(false); + } + }; + private OnMapClickListener onMapClickListener = new OnMapClickListener() { @Override public void onMapClick(@NonNull LatLng point) { diff --git a/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/Utils.java b/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/Utils.java index 4e627a869..5b341e6e6 100644 --- a/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/Utils.java +++ b/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/Utils.java @@ -6,12 +6,15 @@ import android.graphics.PorterDuff; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.location.Location; import android.os.Build; import android.support.annotation.ColorInt; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; +import com.mapbox.mapboxsdk.maps.MapboxMap; + public final class Utils { private Utils() { @@ -77,6 +80,15 @@ static Drawable getDrawable(@NonNull Context context, @DrawableRes int drawableR return drawable; } + static float calculateZoomLevelRadius(MapboxMap mapboxMap, Location location) { + if (location == null) { + return 0; + } + double metersPerPixel = mapboxMap.getProjection().getMetersPerPixelAtLatitude( + location.getLatitude()); + return (float) (location.getAccuracy() * (1 / metersPerPixel)); + } + /** * Casts the value to an even integer. */