Files
MapLibreParticleLab/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationLayerController.java
2019-10-09 13:19:56 +02:00

464 lines
17 KiB
Java

package com.mapbox.mapboxsdk.location;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.location.modes.RenderMode;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.ACCURACY_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BACKGROUND_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BACKGROUND_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BACKGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BEARING_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BEARING_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.FOREGROUND_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.FOREGROUND_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.FOREGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.LOCATION_SOURCE;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_ACCURACY_ALPHA;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_ACCURACY_COLOR;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_ACCURACY_RADIUS;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_BACKGROUND_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_BACKGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_BEARING_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_COMPASS_BEARING;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_FOREGROUND_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_FOREGROUND_ICON_OFFSET;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_FOREGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_GPS_BEARING;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_LOCATION_STALE;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_SHADOW_ICON_OFFSET;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.SHADOW_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.SHADOW_LAYER;
import static com.mapbox.mapboxsdk.style.expressions.Expression.interpolate;
import static com.mapbox.mapboxsdk.style.expressions.Expression.linear;
import static com.mapbox.mapboxsdk.style.expressions.Expression.stop;
import static com.mapbox.mapboxsdk.style.expressions.Expression.zoom;
import static com.mapbox.mapboxsdk.style.layers.Property.NONE;
import static com.mapbox.mapboxsdk.style.layers.Property.VISIBLE;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.visibility;
import static com.mapbox.mapboxsdk.utils.ColorUtils.colorToRgbaString;
final class LocationLayerController {
@RenderMode.Mode
private int renderMode;
private final MapboxMap mapboxMap;
private Style style;
private final LayerSourceProvider layerSourceProvider;
private final LayerBitmapProvider bitmapProvider;
private LocationComponentOptions options;
private final OnRenderModeChangedListener internalRenderModeChangedListener;
@VisibleForTesting
final Set<String> layerSet = new HashSet<>();
private Feature locationFeature;
private GeoJsonSource locationSource;
private boolean isHidden = true;
private LocationComponentPositionManager positionManager;
LocationLayerController(MapboxMap mapboxMap, Style style, LayerSourceProvider layerSourceProvider,
LayerFeatureProvider featureProvider, LayerBitmapProvider bitmapProvider,
@NonNull LocationComponentOptions options,
@NonNull OnRenderModeChangedListener internalRenderModeChangedListener) {
this.mapboxMap = mapboxMap;
this.style = style;
this.layerSourceProvider = layerSourceProvider;
this.bitmapProvider = bitmapProvider;
this.locationFeature = featureProvider.generateLocationFeature(locationFeature, options);
this.internalRenderModeChangedListener = internalRenderModeChangedListener;
initializeComponents(style, options);
}
void initializeComponents(Style style, LocationComponentOptions options) {
this.style = style;
this.positionManager = new LocationComponentPositionManager(style, options.layerAbove(), options.layerBelow());
addLocationSource();
addLayers();
applyStyle(options);
if (isHidden) {
hide();
} else {
show();
}
}
void applyStyle(@NonNull LocationComponentOptions options) {
if (positionManager.update(options.layerAbove(), options.layerBelow())) {
removeLayers();
addLayers();
if (isHidden) {
hide();
}
}
this.options = options;
float elevation = options.elevation();
// Only add icon elevation if the values greater than 0.
if (elevation > 0) {
styleShadow(options);
}
styleForeground(options);
styleBackground(options);
styleBearing(options);
styleAccuracy(options.accuracyAlpha(), options.accuracyColor());
styleScaling(options);
determineIconsSource(options);
if (!isHidden) {
show();
}
}
void setRenderMode(@RenderMode.Mode int renderMode) {
if (this.renderMode == renderMode) {
return;
}
this.renderMode = renderMode;
styleForeground(options);
determineIconsSource(options);
if (!isHidden) {
show();
}
internalRenderModeChangedListener.onRenderModeChanged(renderMode);
}
int getRenderMode() {
return renderMode;
}
//
// Layer action
//
void show() {
isHidden = false;
boolean isStale = locationFeature.getBooleanProperty(PROPERTY_LOCATION_STALE);
switch (renderMode) {
case RenderMode.NORMAL:
setLayerVisibility(SHADOW_LAYER, true);
setLayerVisibility(FOREGROUND_LAYER, true);
setLayerVisibility(BACKGROUND_LAYER, true);
setLayerVisibility(ACCURACY_LAYER, !isStale);
setLayerVisibility(BEARING_LAYER, false);
break;
case RenderMode.COMPASS:
setLayerVisibility(SHADOW_LAYER, true);
setLayerVisibility(FOREGROUND_LAYER, true);
setLayerVisibility(BACKGROUND_LAYER, true);
setLayerVisibility(ACCURACY_LAYER, !isStale);
setLayerVisibility(BEARING_LAYER, true);
break;
case RenderMode.GPS:
setLayerVisibility(SHADOW_LAYER, false);
setLayerVisibility(FOREGROUND_LAYER, true);
setLayerVisibility(BACKGROUND_LAYER, true);
setLayerVisibility(ACCURACY_LAYER, false);
setLayerVisibility(BEARING_LAYER, false);
break;
default:
break;
}
}
void hide() {
isHidden = true;
for (String layerId : layerSet) {
setLayerVisibility(layerId, false);
}
}
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);
}
}
boolean isHidden() {
return isHidden;
}
boolean isConsumingCompass() {
return renderMode == RenderMode.COMPASS;
}
private void setLayerVisibility(@NonNull String layerId, boolean visible) {
Layer layer = style.getLayer(layerId);
if (layer != null) {
String targetVisibility = visible ? VISIBLE : NONE;
if (!layer.getVisibility().value.equals(targetVisibility)) {
layer.setProperties(visibility(visible ? VISIBLE : NONE));
}
}
}
private void addLayers() {
// positions the top-most reference layer
Layer layer = layerSourceProvider.generateLayer(BEARING_LAYER);
positionManager.addLayerToMap(layer);
layerSet.add(layer.getId());
// adds remaining layers while keeping the order
addSymbolLayer(FOREGROUND_LAYER, BEARING_LAYER);
addSymbolLayer(BACKGROUND_LAYER, FOREGROUND_LAYER);
addSymbolLayer(SHADOW_LAYER, BACKGROUND_LAYER);
addAccuracyLayer();
}
private void addSymbolLayer(@NonNull String layerId, @NonNull String beforeLayerId) {
Layer layer = layerSourceProvider.generateLayer(layerId);
addLayerToMap(layer, beforeLayerId);
}
private void addAccuracyLayer() {
Layer accuracyLayer = layerSourceProvider.generateAccuracyLayer();
addLayerToMap(accuracyLayer, BACKGROUND_LAYER);
}
private void addLayerToMap(Layer layer, @NonNull String idBelowLayer) {
style.addLayerBelow(layer, idBelowLayer);
layerSet.add(layer.getId());
}
private void removeLayers() {
for (String layerId : layerSet) {
style.removeLayer(layerId);
}
layerSet.clear();
}
private void setBearingProperty(@NonNull String propertyId, float bearing) {
locationFeature.addNumberProperty(propertyId, bearing);
refreshSource();
}
private void updateAccuracyRadius(float accuracy) {
locationFeature.addNumberProperty(PROPERTY_ACCURACY_RADIUS, accuracy);
refreshSource();
}
//
// Source actions
//
private void addLocationSource() {
locationSource = layerSourceProvider.generateSource(locationFeature);
style.addSource(locationSource);
}
private void refreshSource() {
GeoJsonSource source = style.getSourceAs(LOCATION_SOURCE);
if (source != null) {
locationSource.setGeoJson(locationFeature);
}
}
private void setLocationPoint(Point locationPoint) {
JsonObject properties = locationFeature.properties();
if (properties != null) {
locationFeature = Feature.fromGeometry(locationPoint, properties);
refreshSource();
}
}
//
// Styling
//
private void styleBackground(LocationComponentOptions options) {
Bitmap backgroundBitmap = bitmapProvider.generateBitmap(
options.backgroundDrawable(), options.backgroundTintColor()
);
Bitmap backgroundStaleBitmap = bitmapProvider.generateBitmap(
options.backgroundDrawableStale(), options.backgroundStaleTintColor()
);
style.addImage(BACKGROUND_ICON, backgroundBitmap);
style.addImage(BACKGROUND_STALE_ICON, backgroundStaleBitmap);
}
private void styleShadow(@NonNull LocationComponentOptions options) {
style.addImage(SHADOW_ICON, bitmapProvider.generateShadowBitmap(options));
}
private void styleBearing(LocationComponentOptions options) {
Bitmap bearingBitmap = bitmapProvider.generateBitmap(options.bearingDrawable(), options.bearingTintColor());
style.addImage(BEARING_ICON, bearingBitmap);
}
private void styleAccuracy(float accuracyAlpha, @ColorInt int accuracyColor) {
locationFeature.addNumberProperty(PROPERTY_ACCURACY_ALPHA, accuracyAlpha);
locationFeature.addStringProperty(PROPERTY_ACCURACY_COLOR, colorToRgbaString(accuracyColor));
refreshSource();
}
private void styleForeground(LocationComponentOptions options) {
Bitmap foregroundBitmap = bitmapProvider.generateBitmap(
options.foregroundDrawable(), options.foregroundTintColor()
);
Bitmap foregroundBitmapStale = bitmapProvider.generateBitmap(
options.foregroundDrawableStale(), options.foregroundStaleTintColor()
);
if (renderMode == RenderMode.GPS) {
foregroundBitmap = bitmapProvider.generateBitmap(
options.gpsDrawable(), options.foregroundTintColor()
);
foregroundBitmapStale = bitmapProvider.generateBitmap(
options.gpsDrawable(), options.foregroundStaleTintColor()
);
}
style.addImage(FOREGROUND_ICON, foregroundBitmap);
style.addImage(FOREGROUND_STALE_ICON, foregroundBitmapStale);
}
private void styleScaling(@NonNull LocationComponentOptions options) {
for (String layerId : layerSet) {
Layer layer = style.getLayer(layerId);
if (layer instanceof SymbolLayer) {
layer.setProperties(
iconSize(
interpolate(linear(), zoom(),
stop(mapboxMap.getMinZoomLevel(), options.minZoomIconScale()),
stop(mapboxMap.getMaxZoomLevel(), options.maxZoomIconScale())
)
)
);
}
}
}
private void determineIconsSource(LocationComponentOptions options) {
String foregroundIconString = buildIconString(
renderMode == RenderMode.GPS ? options.gpsName() : options.foregroundName(), FOREGROUND_ICON);
String foregroundStaleIconString = buildIconString(options.foregroundStaleName(), FOREGROUND_STALE_ICON);
String backgroundIconString = buildIconString(options.backgroundName(), BACKGROUND_ICON);
String backgroundStaleIconString = buildIconString(options.backgroundStaleName(), BACKGROUND_STALE_ICON);
String bearingIconString = buildIconString(options.bearingName(), BEARING_ICON);
locationFeature.addStringProperty(PROPERTY_FOREGROUND_ICON, foregroundIconString);
locationFeature.addStringProperty(PROPERTY_BACKGROUND_ICON, backgroundIconString);
locationFeature.addStringProperty(PROPERTY_FOREGROUND_STALE_ICON, foregroundStaleIconString);
locationFeature.addStringProperty(PROPERTY_BACKGROUND_STALE_ICON, backgroundStaleIconString);
locationFeature.addStringProperty(PROPERTY_BEARING_ICON, bearingIconString);
refreshSource();
}
@NonNull
private String buildIconString(@Nullable String bitmapName, @NonNull String drawableName) {
if (bitmapName != null) {
return bitmapName;
}
return drawableName;
}
void setLocationsStale(boolean isStale) {
locationFeature.addBooleanProperty(PROPERTY_LOCATION_STALE, isStale);
refreshSource();
if (renderMode != RenderMode.GPS) {
setLayerVisibility(ACCURACY_LAYER, !isStale);
}
}
//
// Map click event
//
boolean onMapClick(@NonNull LatLng point) {
PointF screenLoc = mapboxMap.getProjection().toScreenLocation(point);
List<Feature> features = mapboxMap.queryRenderedFeatures(screenLoc,
BACKGROUND_LAYER,
FOREGROUND_LAYER,
BEARING_LAYER
);
return !features.isEmpty();
}
private final MapboxAnimator.AnimationsValueChangeListener<LatLng> latLngValueListener =
new MapboxAnimator.AnimationsValueChangeListener<LatLng>() {
@Override
public void onNewAnimationValue(LatLng value) {
Point point = Point.fromLngLat(value.getLongitude(), value.getLatitude());
setLocationPoint(point);
}
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> gpsBearingValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@Override
public void onNewAnimationValue(Float value) {
setBearingProperty(PROPERTY_GPS_BEARING, value);
}
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> compassBearingValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@Override
public void onNewAnimationValue(Float value) {
setBearingProperty(PROPERTY_COMPASS_BEARING, value);
}
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> accuracyValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@Override
public void onNewAnimationValue(Float value) {
updateAccuracyRadius(value);
}
};
Set<AnimatorListenerHolder> getAnimationListeners() {
Set<AnimatorListenerHolder> holders = new HashSet<>();
holders.add(new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_LAYER_LATLNG, latLngValueListener));
if (renderMode == RenderMode.GPS) {
holders.add(new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_LAYER_GPS_BEARING, gpsBearingValueListener));
} else if (renderMode == RenderMode.COMPASS) {
holders.add(
new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_LAYER_COMPASS_BEARING, compassBearingValueListener));
}
if (renderMode == RenderMode.COMPASS || renderMode == RenderMode.NORMAL) {
holders.add(new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_LAYER_ACCURACY, accuracyValueListener));
}
return holders;
}
}