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 71d463678..aac2e8a9d 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 @@ -9,8 +9,9 @@ import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; import com.mapbox.geojson.Feature; -import com.mapbox.geojson.FeatureCollection; import com.mapbox.geojson.Point; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapView; @@ -37,14 +38,26 @@ import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.FOREGROUND_LAYER; import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.FOREGROUND_STALE_ICON; import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.LOCATION_SOURCE; +import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_ACCURACY_ALPHA; +import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_ACCURACY_COLOR; +import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_ACCURACY_RADIUS; +import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_COMPASS_BEARING; +import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_FOREGROUND_ICON_OFFSET; +import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_GPS_BEARING; +import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_LOCATION_STALE; +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.generateShadow; import static com.mapbox.mapboxsdk.plugins.locationlayer.Utils.getBitmapFromDrawable; import static com.mapbox.mapboxsdk.plugins.locationlayer.Utils.getDrawable; import static com.mapbox.mapboxsdk.style.expressions.Expression.exponential; +import static com.mapbox.mapboxsdk.style.expressions.Expression.get; import static com.mapbox.mapboxsdk.style.expressions.Expression.interpolate; +import static com.mapbox.mapboxsdk.style.expressions.Expression.literal; +import static com.mapbox.mapboxsdk.style.expressions.Expression.match; import static com.mapbox.mapboxsdk.style.expressions.Expression.stop; +import static com.mapbox.mapboxsdk.style.expressions.Expression.switchCase; import static com.mapbox.mapboxsdk.style.expressions.Expression.zoom; import static com.mapbox.mapboxsdk.style.layers.Property.ICON_ROTATION_ALIGNMENT_MAP; import static com.mapbox.mapboxsdk.style.layers.Property.NONE; @@ -67,14 +80,15 @@ final class LocationLayer implements LocationLayerAnimator.OnLayerAnimationsValu @RenderMode.Mode private int renderMode; - private boolean isStale; private final MapboxMap mapboxMap; private LocationLayerOptions options; private Context context; private final Map layerMap = new HashMap<>(); - private final Map sourceMap = new HashMap<>(); + private Feature locationFeature = Feature.fromGeometry(Point.fromLngLat(0, 0)); + private GeoJsonSource locationSource; + private boolean isSourceInitialized; LocationLayer(MapView mapView, MapboxMap mapboxMap, LocationLayerOptions options) { this.mapboxMap = mapboxMap; @@ -84,57 +98,53 @@ final class LocationLayer implements LocationLayerAnimator.OnLayerAnimationsValu } void initializeComponents(LocationLayerOptions options) { - addLocationSource(); + prepareLocationSource(); addLayers(); applyStyle(options); } void applyStyle(@NonNull LocationLayerOptions options) { this.options = options; - styleForeground(options, isStale); float elevation = options.elevation(); // Only add icon elevation if the values greater than 0. if (elevation > 0) { - styleShadow(ContextCompat.getDrawable(context, R.drawable.mapbox_user_icon_shadow)); + styleShadow(options); } - styleForeground( - getDrawable(context, options.foregroundDrawable(), options.foregroundTintColor()), - getDrawable(context, options.foregroundDrawableStale(), options.foregroundStaleTintColor()), - isStale); - styleBackground( - getDrawable(context, options.backgroundDrawable(), options.backgroundTintColor()), - getDrawable(context, options.backgroundDrawableStale(), options.backgroundStaleTintColor()), - isStale); - styleBearing( - getDrawable(context, options.bearingDrawable(), options.bearingTintColor())); + styleForeground(options); + styleBackground(options); + styleBearing(options); styleAccuracy(options.accuracyAlpha(), options.accuracyColor()); } void setRenderMode(@RenderMode.Mode int renderMode) { this.renderMode = renderMode; - hide(); + boolean isStale = locationFeature.getBooleanProperty(PROPERTY_LOCATION_STALE); switch (renderMode) { case RenderMode.NORMAL: - styleForeground(options, isStale); + styleForeground(options); setLayerVisibility(SHADOW_LAYER, true); setLayerVisibility(FOREGROUND_LAYER, true); setLayerVisibility(BACKGROUND_LAYER, true); - setLayerVisibility(ACCURACY_LAYER, true); + setLayerVisibility(ACCURACY_LAYER, !isStale); + setLayerVisibility(BEARING_LAYER, false); break; case RenderMode.COMPASS: - styleForeground(options, isStale); + styleForeground(options); setLayerVisibility(SHADOW_LAYER, true); setLayerVisibility(FOREGROUND_LAYER, true); setLayerVisibility(BACKGROUND_LAYER, true); - setLayerVisibility(ACCURACY_LAYER, true); + setLayerVisibility(ACCURACY_LAYER, !isStale); setLayerVisibility(BEARING_LAYER, true); break; case RenderMode.GPS: - styleForeground(options, isStale); + styleForeground(options); + setLayerVisibility(SHADOW_LAYER, false); setLayerVisibility(FOREGROUND_LAYER, true); setLayerVisibility(BACKGROUND_LAYER, true); + setLayerVisibility(ACCURACY_LAYER, false); + setLayerVisibility(BEARING_LAYER, false); break; default: break; @@ -154,13 +164,17 @@ void show() { } void hide() { - for (Layer layer : layerMap.values()) { - layer.setProperties(visibility(NONE)); + for (String layerId : layerMap.keySet()) { + setLayerVisibility(layerId, false); } } - void setLayerVisibility(String layerId, boolean visible) { - layerMap.get(layerId).setProperties(visibility(visible ? VISIBLE : NONE)); + private void setLayerVisibility(String layerId, boolean visible) { + Layer layer = layerMap.get(layerId); + String targetVisibility = visible ? VISIBLE : NONE; + if (!layer.getVisibility().value.equals(targetVisibility)) { + layer.setProperties(visibility(visible ? VISIBLE : NONE)); + } } private void addLayers() { @@ -182,13 +196,46 @@ private void addSymbolLayer(String layerId, String beforeLayerId) { stop(18f, 1.2f) ) ), - iconRotationAlignment(ICON_ROTATION_ALIGNMENT_MAP)); + iconRotationAlignment(ICON_ROTATION_ALIGNMENT_MAP), + iconRotate( + match(literal(layerId), literal(0f), + stop(FOREGROUND_LAYER, get(PROPERTY_GPS_BEARING)), + stop(BACKGROUND_LAYER, get(PROPERTY_GPS_BEARING)), + stop(SHADOW_LAYER, get(PROPERTY_GPS_BEARING)), + stop(BEARING_LAYER, get(PROPERTY_COMPASS_BEARING)) + ) + ), + iconImage( + match(literal(layerId), literal(""), + stop(FOREGROUND_LAYER, switchCase( + get(PROPERTY_LOCATION_STALE), literal(FOREGROUND_STALE_ICON), + literal(FOREGROUND_ICON))), + stop(BACKGROUND_LAYER, switchCase( + get(PROPERTY_LOCATION_STALE), literal(BACKGROUND_STALE_ICON), + literal(BACKGROUND_ICON))), + stop(SHADOW_LAYER, literal(SHADOW_ICON)), + stop(BEARING_LAYER, literal(BEARING_ICON)) + ) + ), + iconOffset( + match(literal(layerId), literal(new Float[] {0f, 0f}), + stop(literal(FOREGROUND_LAYER), get(PROPERTY_FOREGROUND_ICON_OFFSET)), + stop(literal(SHADOW_LAYER), get(PROPERTY_SHADOW_ICON_OFFSET)) + ) + ) + ); addLayerToMap(layer, beforeLayerId); } private void addAccuracyLayer() { CircleLayer locationAccuracyLayer = new CircleLayer(ACCURACY_LAYER, LOCATION_SOURCE) - .withProperties(circleRadius(0f)); + .withProperties( + circleRadius(get(PROPERTY_ACCURACY_RADIUS)), + circleColor(get(PROPERTY_ACCURACY_COLOR)), + circleOpacity(get(PROPERTY_ACCURACY_ALPHA)), + circleStrokeColor(get(PROPERTY_ACCURACY_COLOR)), + circlePitchAlignment(Property.CIRCLE_PITCH_ALIGNMENT_MAP) + ); addLayerToMap(locationAccuracyLayer, BACKGROUND_LAYER); } @@ -201,31 +248,18 @@ private void addLayerToMap(Layer layer, @Nullable String idBelowLayer) { layerMap.put(layer.getId(), layer); } - void setLayerBearing(String layerId, float bearing) { - layerMap.get(layerId).setProperties(iconRotate(bearing)); + private void setBearingProperty(String propertyId, float bearing) { + locationFeature.addNumberProperty(propertyId, bearing); + refreshSource(); } void updateAccuracyRadius(Location location) { - CircleLayer accuracyLayer = (CircleLayer) layerMap.get(ACCURACY_LAYER); - if (accuracyLayer != null && (renderMode == RenderMode.COMPASS || renderMode == RenderMode.NORMAL)) { - accuracyLayer.setProperties( - circleRadius(calculateZoomLevelRadius(location)) - ); + if (renderMode == RenderMode.COMPASS || renderMode == RenderMode.NORMAL) { + locationFeature.addNumberProperty(PROPERTY_ACCURACY_RADIUS, calculateZoomLevelRadius(location)); + refreshSource(); } } - void updateForegroundOffset(double tilt) { - layerMap.get(FOREGROUND_LAYER).setProperties( - iconOffset(new Float[] {0f, (float) (-0.05 * tilt)})); - layerMap.get(SHADOW_LAYER).setProperties( - iconOffset(new Float[] {0f, (float) (0.05 * tilt)})); - } - - void updateForegroundBearing(float bearing) { - layerMap.get(FOREGROUND_LAYER).setProperties(iconRotate(bearing)); - layerMap.get(SHADOW_LAYER).setProperties(iconRotate(bearing)); - } - private float calculateZoomLevelRadius(Location location) { if (location == null) { return 0; @@ -235,89 +269,119 @@ private float calculateZoomLevelRadius(Location location) { return (float) (location.getAccuracy() * (1 / metersPerPixel)); } + void updateForegroundOffset(double tilt) { + JsonArray foregroundJsonArray = new JsonArray(); + foregroundJsonArray.add(0f); + foregroundJsonArray.add((float) (-0.05 * tilt)); + locationFeature.addProperty(PROPERTY_FOREGROUND_ICON_OFFSET, foregroundJsonArray); + + JsonArray backgroundJsonArray = new JsonArray(); + backgroundJsonArray.add(0f); + backgroundJsonArray.add((float) (0.05 * tilt)); + locationFeature.addProperty(PROPERTY_SHADOW_ICON_OFFSET, backgroundJsonArray); + + refreshSource(); + } + + void updateForegroundBearing(float bearing) { + if (renderMode != RenderMode.GPS) { + setBearingProperty(PROPERTY_GPS_BEARING, bearing); + } + } + // // Source actions // - private void addLocationSource() { - FeatureCollection emptyFeature = FeatureCollection.fromFeatures(new Feature[] {}); - GeoJsonSource locationSource = new GeoJsonSource( + private void prepareLocationSource() { + locationFeature.addNumberProperty(PROPERTY_GPS_BEARING, 0f); + locationFeature.addNumberProperty(PROPERTY_COMPASS_BEARING, 0f); + locationFeature.addBooleanProperty(PROPERTY_LOCATION_STALE, false); + + locationSource = new GeoJsonSource( LOCATION_SOURCE, - emptyFeature, - new GeoJsonOptions().withMaxZoom(16)); + locationFeature, + new GeoJsonOptions().withMaxZoom(16) + ); + } + + private void addLocationSource() { mapboxMap.addSource(locationSource); - sourceMap.put(LOCATION_SOURCE, locationSource); + isSourceInitialized = true; + } + + private void refreshSource() { + locationSource.setGeoJson(locationFeature); } private void setLocationPoint(Point locationPoint) { - sourceMap.get(LOCATION_SOURCE).setGeoJson(locationPoint); + JsonObject properties = locationFeature.properties(); + if (properties != null) { + locationFeature = Feature.fromGeometry(locationPoint, properties); + locationSource.setGeoJson(locationFeature); + } + + if (!isSourceInitialized) { + addLocationSource(); + } } // // Styling // - private void styleBackground(Drawable backgroundDrawable, Drawable backgroundDrawableStale, boolean isStale) { + private void styleBackground(LocationLayerOptions options) { + Drawable backgroundDrawable = + getDrawable(context, options.backgroundDrawable(), options.backgroundTintColor()); + Drawable backgroundDrawableStale = + getDrawable(context, options.backgroundDrawableStale(), options.backgroundStaleTintColor()); + mapboxMap.addImage(BACKGROUND_ICON, getBitmapFromDrawable(backgroundDrawable)); mapboxMap.addImage(BACKGROUND_STALE_ICON, getBitmapFromDrawable(backgroundDrawableStale)); - layerMap.get(BACKGROUND_LAYER).setProperties( - iconImage(isStale ? BACKGROUND_STALE_ICON : BACKGROUND_ICON)); } - private void styleShadow(Drawable shadowDrawable) { + private void styleShadow(LocationLayerOptions options) { + Drawable shadowDrawable = ContextCompat.getDrawable(context, R.drawable.mapbox_user_icon_shadow); mapboxMap.addImage(SHADOW_ICON, generateShadow(shadowDrawable, options.elevation())); - layerMap.get(SHADOW_LAYER).setProperties(iconImage(SHADOW_ICON)); } - private void styleBearing(Drawable bearingDrawable) { + private void styleBearing(LocationLayerOptions options) { + Drawable bearingDrawable = getDrawable(context, options.bearingDrawable(), options.bearingTintColor()); mapboxMap.addImage(BEARING_ICON, getBitmapFromDrawable(bearingDrawable)); - layerMap.get(BEARING_LAYER).setProperties( - iconImage(BEARING_ICON)); } private void styleAccuracy(float accuracyAlpha, @ColorInt int accuracyColor) { - layerMap.get(ACCURACY_LAYER).setProperties( - circleColor(accuracyColor), - circleOpacity(accuracyAlpha), - circlePitchAlignment(Property.CIRCLE_PITCH_ALIGNMENT_MAP), - circleStrokeColor(accuracyColor) - ); + locationFeature.addNumberProperty(PROPERTY_ACCURACY_ALPHA, accuracyAlpha); + locationFeature.addNumberProperty(PROPERTY_ACCURACY_COLOR, accuracyColor); + refreshSource(); } - private void styleForeground(Drawable foregroundDrawable, Drawable foregroundDrawableStale, boolean isStale) { + private void styleForeground(Drawable foregroundDrawable, Drawable foregroundDrawableStale) { mapboxMap.addImage(FOREGROUND_ICON, getBitmapFromDrawable(foregroundDrawable)); mapboxMap.addImage(FOREGROUND_STALE_ICON, getBitmapFromDrawable(foregroundDrawableStale)); - - layerMap.get(FOREGROUND_LAYER).setProperties( - iconImage(isStale - ? FOREGROUND_STALE_ICON : FOREGROUND_ICON), - iconRotate(90f)); } - private void styleForeground(@NonNull LocationLayerOptions options, boolean isStale) { + private void styleForeground(@NonNull LocationLayerOptions options) { if (renderMode == RenderMode.GPS) { - styleForegroundGPS(isStale); + styleForegroundGPS(options); } else { styleForeground( getDrawable(context, options.foregroundDrawable(), options.foregroundTintColor()), - getDrawable(context, options.foregroundDrawableStale(), options.foregroundStaleTintColor()), - isStale); + getDrawable(context, options.foregroundDrawableStale(), options.foregroundStaleTintColor())); } } - private void styleForegroundGPS(boolean isStale) { + private void styleForegroundGPS(LocationLayerOptions options) { styleForeground( getDrawable(context, options.gpsDrawable(), options.foregroundTintColor()), - getDrawable(context, options.gpsDrawable(), options.foregroundStaleTintColor()), - isStale); + getDrawable(context, options.gpsDrawable(), options.foregroundStaleTintColor())); } void setLocationsStale(boolean isStale) { - this.isStale = isStale; - layerMap.get(FOREGROUND_LAYER).setProperties(iconImage(isStale ? FOREGROUND_STALE_ICON : FOREGROUND_ICON)); - layerMap.get(BACKGROUND_LAYER).setProperties(iconImage(isStale ? BACKGROUND_STALE_ICON : BACKGROUND_ICON)); + locationFeature.addBooleanProperty(PROPERTY_LOCATION_STALE, isStale); + refreshSource(); if (renderMode != RenderMode.GPS) { - layerMap.get(ACCURACY_LAYER).setProperties(visibility(isStale ? NONE : VISIBLE)); + setLayerVisibility(ACCURACY_LAYER, !isStale); } } @@ -344,15 +408,14 @@ public void onNewLatLngValue(LatLng latLng) { @Override public void onNewGpsBearingValue(float gpsBearing) { if (renderMode == RenderMode.GPS) { - setLayerBearing(LocationLayerConstants.FOREGROUND_LAYER, gpsBearing); - setLayerBearing(LocationLayerConstants.BACKGROUND_LAYER, gpsBearing); + setBearingProperty(PROPERTY_GPS_BEARING, gpsBearing); } } @Override public void onNewCompassBearingValue(float compassBearing) { if (renderMode == RenderMode.COMPASS) { - setLayerBearing(BEARING_LAYER, compassBearing); + setBearingProperty(PROPERTY_COMPASS_BEARING, compassBearing); } } } \ No newline at end of file diff --git a/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerConstants.java b/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerConstants.java index ab1a07b43..71a5fc398 100644 --- a/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerConstants.java +++ b/plugin-locationlayer/src/main/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerConstants.java @@ -18,6 +18,14 @@ final class LocationLayerConstants { // Sources static final String LOCATION_SOURCE = "mapbox-location-source"; + static final String PROPERTY_GPS_BEARING = "mapbox-property-gps-bearing"; + static final String PROPERTY_COMPASS_BEARING = "mapbox-property-compass-bearing"; + static final String PROPERTY_LOCATION_STALE = "mapbox-property-location-stale"; + static final String PROPERTY_ACCURACY_RADIUS = "mapbox-property-accuracy-radius"; + static final String PROPERTY_ACCURACY_COLOR = "mapbox-property-accuracy-color"; + static final String PROPERTY_ACCURACY_ALPHA = "mapbox-property-accuracy-alpha"; + static final String PROPERTY_FOREGROUND_ICON_OFFSET = "mapbox-property-foreground-icon-offset"; + static final String PROPERTY_SHADOW_ICON_OFFSET = "mapbox-property-shadow-icon-offset"; // Layers static final String SHADOW_LAYER = "mapbox-location-shadow"; 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 912949f48..b9baf7884 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 @@ -65,6 +65,8 @@ public final class LocationLayerPlugin implements LifecycleObserver { private LocationLayerAnimator locationLayerAnimator; + private CameraPosition lastCameraPosition; + private boolean isEnabled; private StaleStateManager staleStateManager; private final CopyOnWriteArrayList onLocationStaleListeners @@ -189,6 +191,7 @@ public int getCameraMode() { */ public void setRenderMode(@RenderMode.Mode int renderMode) { locationLayer.setRenderMode(renderMode); + updateLayerOffsets(true); } /** @@ -485,7 +488,7 @@ private void disableLocationLayerPlugin() { private void updateMapWithOptions(final LocationLayerOptions options) { mapboxMap.setPadding( - options.padding()[0], options.padding()[1],options.padding()[2], options.padding()[3] + options.padding()[0], options.padding()[1], options.padding()[2], options.padding()[3] ); mapboxMap.setMaxZoomPreference(options.maxZoom()); @@ -528,19 +531,33 @@ private void setLastCompassHeading() { updateCompassHeading(compassManager.getLastHeading()); } - private OnCameraMoveListener onCameraMoveListener = new OnCameraMoveListener() { + private void updateLayerOffsets(boolean forceUpdate) { + CameraPosition position = mapboxMap.getCameraPosition(); + if (lastCameraPosition == null || forceUpdate) { + lastCameraPosition = position; + locationLayer.updateForegroundBearing((float) position.bearing); + locationLayer.updateForegroundOffset(position.tilt); + locationLayer.updateAccuracyRadius(getLastKnownLocation()); + return; + } - private CameraPosition lastCameraPosition; + if (position.bearing != lastCameraPosition.bearing) { + locationLayer.updateForegroundBearing((float) position.bearing); + } + if (position.tilt != lastCameraPosition.tilt) { + locationLayer.updateForegroundOffset(position.tilt); + } + if (position.zoom != lastCameraPosition.zoom) { + locationLayer.updateAccuracyRadius(getLastKnownLocation()); + } + lastCameraPosition = position; + } + + private OnCameraMoveListener onCameraMoveListener = new OnCameraMoveListener() { @Override public void onCameraMove() { - CameraPosition position = mapboxMap.getCameraPosition(); - if (position.equals(lastCameraPosition)) { - locationLayer.updateAccuracyRadius(getLastKnownLocation()); - locationLayer.updateForegroundOffset(position.tilt); - locationLayer.updateForegroundBearing((float) position.bearing); - } - lastCameraPosition = position; + updateLayerOffsets(false); } };