[android] merge LocationLayerPlugin to Maps SDK

This commit is contained in:
Łukasz Paczos
2018-08-28 17:06:21 +02:00
parent e70fe05756
commit 5d000d090f
68 changed files with 90148 additions and 2 deletions

View File

@@ -0,0 +1,24 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.ValueAnimator;
import java.util.List;
class CameraCompassBearingAnimator extends PluginFloatAnimator<PluginAnimator.OnCameraAnimationsValuesChangeListener> {
CameraCompassBearingAnimator(Float previous, Float target,
List<OnCameraAnimationsValuesChangeListener> updateListeners) {
super(previous, target, updateListeners);
}
@Override
int provideAnimatorType() {
return ANIMATOR_CAMERA_COMPASS_BEARING;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
for (OnCameraAnimationsValuesChangeListener listener : updateListeners) {
listener.onNewCompassBearingValue((Float) animation.getAnimatedValue());
}
}
}

View File

@@ -0,0 +1,23 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.ValueAnimator;
import java.util.List;
class CameraGpsBearingAnimator extends PluginFloatAnimator<PluginAnimator.OnCameraAnimationsValuesChangeListener> {
CameraGpsBearingAnimator(Float previous, Float target, List<OnCameraAnimationsValuesChangeListener> updateListeners) {
super(previous, target, updateListeners);
}
@Override
int provideAnimatorType() {
return ANIMATOR_CAMERA_GPS_BEARING;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
for (OnCameraAnimationsValuesChangeListener listener : updateListeners) {
listener.onNewGpsBearingValue((Float) animation.getAnimatedValue());
}
}
}

View File

@@ -0,0 +1,25 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.ValueAnimator;
import com.mapbox.mapboxsdk.geometry.LatLng;
import java.util.List;
class CameraLatLngAnimator extends PluginLatLngAnimator<PluginAnimator.OnCameraAnimationsValuesChangeListener> {
CameraLatLngAnimator(LatLng previous, LatLng target, List<OnCameraAnimationsValuesChangeListener> updateListeners) {
super(previous, target, updateListeners);
}
@Override
int provideAnimatorType() {
return ANIMATOR_CAMERA_LATLNG;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
for (OnCameraAnimationsValuesChangeListener listener : updateListeners) {
listener.onNewLatLngValue((LatLng) animation.getAnimatedValue());
}
}
}

View File

@@ -0,0 +1,62 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.support.annotation.NonNull;
/**
* Interface defining the source of compass heading data that is
* consumed by the {@link LocationLayerPlugin} when in compass related
* {@link com.mapbox.mapboxsdk.plugins.locationlayer.modes.RenderMode} or
* {@link com.mapbox.mapboxsdk.plugins.locationlayer.modes.CameraMode}s.
*/
public interface CompassEngine {
/**
* Adds a {@link CompassListener} that can be used to
* receive heading and state changes.
*
* @param compassListener to be added
*/
void addCompassListener(@NonNull CompassListener compassListener);
/**
* Removes a {@link CompassListener} that can be used to
* receive heading and state changes.
*
* @param compassListener to be removed
*/
void removeCompassListener(@NonNull CompassListener compassListener);
/**
* Returns the last heading value produced and pushed via
* a compass listener.
*
* @return last heading value
*/
float getLastHeading();
/**
* Provides the last know accuracy status from the sensor manager.
* <p>
* An integer value which is identical to the {@code SensorManager} class constants:
* <ul>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_NO_CONTACT}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_UNRELIABLE}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_LOW}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_MEDIUM}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_HIGH}</li>
* </ul>
*
* @return last accuracy status
*/
int getLastAccuracySensorStatus();
/**
* Lifecycle method that can be used for adding or releasing resources.
*/
void onStart();
/**
* Lifecycle method that can be used for adding or releasing resources.
*/
void onStop();
}

View File

@@ -0,0 +1,33 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
/**
* Callbacks related to the compass
*/
public interface CompassListener {
/**
* Callback's invoked when a new compass update occurs. You can listen into the compass updates
* using {@link LocationLayerPlugin#addCompassListener(CompassListener)} and implementing these
* callbacks. Note that this interface is also used internally to to update the UI chevron/arrow.
*
* @param userHeading the new compass heading
*/
void onCompassChanged(float userHeading);
/**
* This gets invoked when the compass accuracy status changes from one value to another. It
* provides an integer value which is identical to the {@code SensorManager} class constants:
* <ul>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_NO_CONTACT}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_UNRELIABLE}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_LOW}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_MEDIUM}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_HIGH}</li>
* </ul>
*
* @param compassStatus the new accuracy of this sensor, one of
* {@code SensorManager.SENSOR_STATUS_*}
*/
void onCompassAccuracyChange(int compassStatus);
}

View File

@@ -0,0 +1,19 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.TypeEvaluator;
import com.mapbox.mapboxsdk.geometry.LatLng;
class LatLngEvaluator implements TypeEvaluator<LatLng> {
private final LatLng latLng = new LatLng();
@Override
public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) {
latLng.setLatitude(startValue.getLatitude()
+ ((endValue.getLatitude() - startValue.getLatitude()) * fraction));
latLng.setLongitude(startValue.getLongitude()
+ ((endValue.getLongitude() - startValue.getLongitude()) * fraction));
return latLng;
}
}

View File

@@ -0,0 +1,24 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.ValueAnimator;
import java.util.List;
class LayerAccuracyAnimator extends PluginFloatAnimator<PluginAnimator.OnLayerAnimationsValuesChangeListener> {
LayerAccuracyAnimator(Float previous, Float target, List<OnLayerAnimationsValuesChangeListener> updateListeners) {
super(previous, target, updateListeners);
}
@Override
int provideAnimatorType() {
return ANIMATOR_LAYER_ACCURACY;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
for (OnLayerAnimationsValuesChangeListener listener : updateListeners) {
listener.onNewAccuracyRadiusValue((Float) animation.getAnimatedValue());
}
}
}

View File

@@ -0,0 +1,33 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.v4.content.ContextCompat;
import com.mapbox.mapboxsdk.R;
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;
class LayerBitmapProvider {
private final Context context;
LayerBitmapProvider(Context context) {
this.context = context;
}
Bitmap generateBitmap(@DrawableRes int drawableRes, @ColorInt Integer tintColor) {
Drawable drawable = getDrawable(context, drawableRes, tintColor);
return getBitmapFromDrawable(drawable);
}
Bitmap generateShadowBitmap(LocationLayerOptions options) {
Drawable shadowDrawable = ContextCompat.getDrawable(context, R.drawable.mapbox_user_icon_shadow);
return generateShadow(shadowDrawable, options.elevation());
}
}

View File

@@ -0,0 +1,24 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.ValueAnimator;
import java.util.List;
class LayerCompassBearingAnimator extends PluginFloatAnimator<PluginAnimator.OnLayerAnimationsValuesChangeListener> {
LayerCompassBearingAnimator(Float previous,
Float target, List<OnLayerAnimationsValuesChangeListener> updateListeners) {
super(previous, target, updateListeners);
}
@Override
int provideAnimatorType() {
return ANIMATOR_LAYER_COMPASS_BEARING;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
for (OnLayerAnimationsValuesChangeListener listener : updateListeners) {
listener.onNewCompassBearingValue((Float) animation.getAnimatedValue());
}
}
}

View File

@@ -0,0 +1,26 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.Point;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_COMPASS_BEARING;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_GPS_BEARING;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_LOCATION_STALE;
class LayerFeatureProvider {
@NonNull
Feature generateLocationFeature(@Nullable Feature locationFeature, LocationLayerOptions options) {
if (locationFeature != null) {
return locationFeature;
}
locationFeature = Feature.fromGeometry(Point.fromLngLat(0.0, 0.0));
locationFeature.addNumberProperty(PROPERTY_GPS_BEARING, 0f);
locationFeature.addNumberProperty(PROPERTY_COMPASS_BEARING, 0f);
locationFeature.addBooleanProperty(PROPERTY_LOCATION_STALE, options.enableStaleState());
return locationFeature;
}
}

View File

@@ -0,0 +1,23 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.ValueAnimator;
import java.util.List;
class LayerGpsBearingAnimator extends PluginFloatAnimator<PluginAnimator.OnLayerAnimationsValuesChangeListener> {
LayerGpsBearingAnimator(Float previous, Float target, List<OnLayerAnimationsValuesChangeListener> updateListeners) {
super(previous, target, updateListeners);
}
@Override
int provideAnimatorType() {
return ANIMATOR_LAYER_GPS_BEARING;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
for (OnLayerAnimationsValuesChangeListener listener : updateListeners) {
listener.onNewGpsBearingValue((Float) animation.getAnimatedValue());
}
}
}

View File

@@ -0,0 +1,25 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.ValueAnimator;
import com.mapbox.mapboxsdk.geometry.LatLng;
import java.util.List;
class LayerLatLngAnimator extends PluginLatLngAnimator<PluginAnimator.OnLayerAnimationsValuesChangeListener> {
LayerLatLngAnimator(LatLng previous, LatLng target, List<OnLayerAnimationsValuesChangeListener> updateListeners) {
super(previous, target, updateListeners);
}
@Override
int provideAnimatorType() {
return ANIMATOR_LAYER_LATLNG;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
for (OnLayerAnimationsValuesChangeListener listener : updateListeners) {
listener.onNewLatLngValue((LatLng) animation.getAnimatedValue());
}
}
}

View File

@@ -0,0 +1,107 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import com.mapbox.geojson.Feature;
import com.mapbox.mapboxsdk.style.layers.CircleLayer;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.layers.Property;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonOptions;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.ACCURACY_LAYER;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.BACKGROUND_LAYER;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.BEARING_LAYER;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.FOREGROUND_LAYER;
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_BACKGROUND_ICON;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_BACKGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_BEARING_ICON;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_COMPASS_BEARING;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_FOREGROUND_ICON;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_FOREGROUND_ICON_OFFSET;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_FOREGROUND_STALE_ICON;
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.style.expressions.Expression.get;
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.layers.Property.ICON_ROTATION_ALIGNMENT_MAP;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleOpacity;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circlePitchAlignment;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleRadius;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleStrokeColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconOffset;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconRotate;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconRotationAlignment;
class LayerSourceProvider {
private static final String EMPTY_STRING = "";
GeoJsonSource generateSource(Feature locationFeature) {
return new GeoJsonSource(
LOCATION_SOURCE,
locationFeature,
new GeoJsonOptions().withMaxZoom(16)
);
}
Layer generateLayer(String layerId) {
SymbolLayer layer = new SymbolLayer(layerId, LOCATION_SOURCE);
layer.setProperties(
iconAllowOverlap(true),
iconIgnorePlacement(true),
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(EMPTY_STRING),
stop(FOREGROUND_LAYER, switchCase(
get(PROPERTY_LOCATION_STALE), get(PROPERTY_FOREGROUND_STALE_ICON),
get(PROPERTY_FOREGROUND_ICON))),
stop(BACKGROUND_LAYER, switchCase(
get(PROPERTY_LOCATION_STALE), get(PROPERTY_BACKGROUND_STALE_ICON),
get(PROPERTY_BACKGROUND_ICON))),
stop(SHADOW_LAYER, literal(SHADOW_ICON)),
stop(BEARING_LAYER, get(PROPERTY_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))
)
)
);
return layer;
}
Layer generateAccuracyLayer() {
return new CircleLayer(ACCURACY_LAYER, LOCATION_SOURCE)
.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)
);
}
}

View File

@@ -0,0 +1,398 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
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.maps.MapboxMap;
import com.mapbox.mapboxsdk.plugins.locationlayer.modes.RenderMode;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import java.util.ArrayList;
import java.util.List;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.ACCURACY_LAYER;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.BACKGROUND_ICON;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.BACKGROUND_LAYER;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.BACKGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.BEARING_ICON;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.BEARING_LAYER;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.FOREGROUND_ICON;
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_BACKGROUND_ICON;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_BACKGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_BEARING_ICON;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_COMPASS_BEARING;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_FOREGROUND_ICON;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_FOREGROUND_ICON_OFFSET;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.PROPERTY_FOREGROUND_STALE_ICON;
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.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.colorToRgbaString;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.visibility;
final class LocationLayer implements PluginAnimator.OnLayerAnimationsValuesChangeListener {
@RenderMode.Mode
private int renderMode;
private final MapboxMap mapboxMap;
private final LayerSourceProvider layerSourceProvider;
private final LayerBitmapProvider bitmapProvider;
private LocationLayerOptions options;
private final List<String> layerMap = new ArrayList<>();
private Feature locationFeature;
private GeoJsonSource locationSource;
private boolean isHidden = true;
LocationLayer(MapboxMap mapboxMap, LayerSourceProvider layerSourceProvider,
LayerFeatureProvider featureProvider, LayerBitmapProvider bitmapProvider,
LocationLayerOptions options) {
this.mapboxMap = mapboxMap;
this.layerSourceProvider = layerSourceProvider;
this.bitmapProvider = bitmapProvider;
this.locationFeature = featureProvider.generateLocationFeature(locationFeature, options);
initializeComponents(options);
setRenderMode(RenderMode.NORMAL);
}
void initializeComponents(LocationLayerOptions options) {
addLocationSource();
addLayers(options.layerBelow());
applyStyle(options);
if (isHidden) {
hide();
} else {
show();
}
}
void applyStyle(@NonNull LocationLayerOptions options) {
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);
}
void setRenderMode(@RenderMode.Mode int renderMode) {
this.renderMode = renderMode;
if (!isHidden) {
boolean isStale = locationFeature.getBooleanProperty(PROPERTY_LOCATION_STALE);
switch (renderMode) {
case RenderMode.NORMAL:
styleForeground(options);
setLayerVisibility(SHADOW_LAYER, true);
setLayerVisibility(FOREGROUND_LAYER, true);
setLayerVisibility(BACKGROUND_LAYER, true);
setLayerVisibility(ACCURACY_LAYER, !isStale);
setLayerVisibility(BEARING_LAYER, false);
break;
case RenderMode.COMPASS:
styleForeground(options);
setLayerVisibility(SHADOW_LAYER, true);
setLayerVisibility(FOREGROUND_LAYER, true);
setLayerVisibility(BACKGROUND_LAYER, true);
setLayerVisibility(ACCURACY_LAYER, !isStale);
setLayerVisibility(BEARING_LAYER, true);
break;
case RenderMode.GPS:
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;
}
determineIconsSource(options);
}
}
int getRenderMode() {
return renderMode;
}
//
// Layer action
//
void show() {
isHidden = false;
setRenderMode(renderMode);
}
void hide() {
isHidden = true;
for (String layerId : layerMap) {
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);
}
}
private void setLayerVisibility(String layerId, boolean visible) {
Layer layer = mapboxMap.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(String idBelowLayer) {
addSymbolLayer(BEARING_LAYER, idBelowLayer);
addSymbolLayer(FOREGROUND_LAYER, BEARING_LAYER);
addSymbolLayer(BACKGROUND_LAYER, FOREGROUND_LAYER);
addSymbolLayer(SHADOW_LAYER, BACKGROUND_LAYER);
addAccuracyLayer();
}
private void addSymbolLayer(String layerId, 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) {
mapboxMap.addLayerBelow(layer, idBelowLayer);
layerMap.add(layer.getId());
}
private void setBearingProperty(String propertyId, float bearing) {
locationFeature.addNumberProperty(propertyId, bearing);
refreshSource();
}
private void updateAccuracyRadius(float accuracy) {
if (renderMode == RenderMode.COMPASS || renderMode == RenderMode.NORMAL) {
locationFeature.addNumberProperty(PROPERTY_ACCURACY_RADIUS, accuracy);
refreshSource();
}
}
//
// Source actions
//
private void addLocationSource() {
locationSource = layerSourceProvider.generateSource(locationFeature);
mapboxMap.addSource(locationSource);
}
private void refreshSource() {
GeoJsonSource source = mapboxMap.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(LocationLayerOptions options) {
Bitmap backgroundBitmap = bitmapProvider.generateBitmap(
options.backgroundDrawable(), options.backgroundTintColor()
);
Bitmap backgroundStaleBitmap = bitmapProvider.generateBitmap(
options.backgroundDrawableStale(), options.backgroundStaleTintColor()
);
mapboxMap.addImage(BACKGROUND_ICON, backgroundBitmap);
mapboxMap.addImage(BACKGROUND_STALE_ICON, backgroundStaleBitmap);
}
private void styleShadow(LocationLayerOptions options) {
mapboxMap.addImage(SHADOW_ICON, bitmapProvider.generateShadowBitmap(options));
}
private void styleBearing(LocationLayerOptions options) {
Bitmap bearingBitmap = bitmapProvider.generateBitmap(options.bearingDrawable(), options.bearingTintColor());
mapboxMap.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(LocationLayerOptions 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()
);
}
mapboxMap.addImage(FOREGROUND_ICON, foregroundBitmap);
mapboxMap.addImage(FOREGROUND_STALE_ICON, foregroundBitmapStale);
}
private void styleScaling(LocationLayerOptions options) {
for (String layerId : layerMap) {
Layer layer = mapboxMap.getLayer(layerId);
if (layer != null && layer instanceof SymbolLayer) {
layer.setProperties(
iconSize(
interpolate(linear(), zoom(),
stop(options.minZoom(), options.minZoomIconScale()),
stop(options.maxZoom(), options.maxZoomIconScale())
)
)
);
}
}
}
private void determineIconsSource(LocationLayerOptions 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();
}
private String buildIconString(@Nullable String bitmapName, @NonNull String drawableName) {
if (bitmapName != null) {
return bitmapName;
}
return drawableName;
}
void setLocationsStale(boolean isStale) {
// If options has stale state disabled, just return here.
if (!options.enableStaleState()) {
return;
}
locationFeature.addBooleanProperty(PROPERTY_LOCATION_STALE, isStale);
refreshSource();
if (renderMode != RenderMode.GPS) {
setLayerVisibility(ACCURACY_LAYER, !isStale);
}
}
//
// Map click event
//
boolean onMapClick(LatLng point) {
PointF screenLoc = mapboxMap.getProjection().toScreenLocation(point);
List<Feature> features = mapboxMap.queryRenderedFeatures(screenLoc,
BACKGROUND_LAYER,
FOREGROUND_LAYER,
BEARING_LAYER
);
return !features.isEmpty();
}
@Override
public void onNewLatLngValue(LatLng latLng) {
Point point = Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude());
setLocationPoint(point);
}
@Override
public void onNewGpsBearingValue(float gpsBearing) {
if (renderMode == RenderMode.GPS) {
setBearingProperty(PROPERTY_GPS_BEARING, gpsBearing);
}
}
@Override
public void onNewCompassBearingValue(float compassBearing) {
if (renderMode == RenderMode.COMPASS) {
setBearingProperty(PROPERTY_COMPASS_BEARING, compassBearing);
}
}
@Override
public void onNewAccuracyRadiusValue(float accuracyRadiusValue) {
updateAccuracyRadius(accuracyRadiusValue);
}
}

View File

@@ -0,0 +1,263 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.content.Context;
import android.graphics.PointF;
import android.view.MotionEvent;
import com.mapbox.android.gestures.AndroidGesturesManager;
import com.mapbox.android.gestures.MoveGestureDetector;
import com.mapbox.android.gestures.RotateGestureDetector;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.plugins.locationlayer.modes.CameraMode;
import java.util.List;
import java.util.Set;
final class LocationLayerCamera implements PluginAnimator.OnCameraAnimationsValuesChangeListener {
@CameraMode.Mode
private int cameraMode;
private final MapboxMap mapboxMap;
private final OnCameraTrackingChangedListener internalCameraTrackingChangedListener;
private LocationLayerOptions options;
private boolean adjustFocalPoint;
private final MoveGestureDetector moveGestureDetector;
private final OnCameraMoveInvalidateListener onCameraMoveInvalidateListener;
LocationLayerCamera(
Context context,
MapboxMap mapboxMap,
OnCameraTrackingChangedListener internalCameraTrackingChangedListener,
LocationLayerOptions options,
OnCameraMoveInvalidateListener onCameraMoveInvalidateListener) {
this.mapboxMap = mapboxMap;
mapboxMap.setGesturesManager(
new PluginsGesturesManager(context), true, true);
moveGestureDetector = mapboxMap.getGesturesManager().getMoveGestureDetector();
mapboxMap.addOnMoveListener(onMoveListener);
mapboxMap.addOnRotateListener(onRotateListener);
mapboxMap.addOnFlingListener(onFlingListener);
this.internalCameraTrackingChangedListener = internalCameraTrackingChangedListener;
this.onCameraMoveInvalidateListener = onCameraMoveInvalidateListener;
initializeOptions(options);
}
// Package private for testing purposes
LocationLayerCamera(MapboxMap mapboxMap,
MoveGestureDetector moveGestureDetector,
OnCameraTrackingChangedListener internalCameraTrackingChangedListener,
OnCameraMoveInvalidateListener onCameraMoveInvalidateListener) {
this.mapboxMap = mapboxMap;
this.moveGestureDetector = moveGestureDetector;
this.internalCameraTrackingChangedListener = internalCameraTrackingChangedListener;
this.onCameraMoveInvalidateListener = onCameraMoveInvalidateListener;
}
void initializeOptions(LocationLayerOptions options) {
this.options = options;
}
void setCameraMode(@CameraMode.Mode int cameraMode) {
final boolean wasTracking = isLocationTracking();
this.cameraMode = cameraMode;
mapboxMap.cancelTransitions();
adjustGesturesThresholds();
notifyCameraTrackingChangeListener(wasTracking);
}
int getCameraMode() {
return cameraMode;
}
private void setBearing(float bearing) {
mapboxMap.moveCamera(CameraUpdateFactory.bearingTo(bearing));
onCameraMoveInvalidateListener.onInvalidateCameraMove();
}
private void setLatLng(LatLng latLng) {
mapboxMap.moveCamera(CameraUpdateFactory.newLatLng(latLng));
onCameraMoveInvalidateListener.onInvalidateCameraMove();
}
private void setZoom(float zoom) {
mapboxMap.moveCamera(CameraUpdateFactory.zoomTo(zoom));
onCameraMoveInvalidateListener.onInvalidateCameraMove();
}
private void setTilt(float tilt) {
mapboxMap.moveCamera(CameraUpdateFactory.tiltTo(tilt));
onCameraMoveInvalidateListener.onInvalidateCameraMove();
}
@Override
public void onNewLatLngValue(LatLng latLng) {
if (cameraMode == CameraMode.TRACKING
|| cameraMode == CameraMode.TRACKING_COMPASS
|| cameraMode == CameraMode.TRACKING_GPS
|| cameraMode == CameraMode.TRACKING_GPS_NORTH) {
setLatLng(latLng);
if (adjustFocalPoint) {
PointF focalPoint = mapboxMap.getProjection().toScreenLocation(latLng);
mapboxMap.getUiSettings().setFocalPoint(focalPoint);
adjustFocalPoint = false;
}
}
}
@Override
public void onNewGpsBearingValue(float gpsBearing) {
boolean trackingNorth = cameraMode == CameraMode.TRACKING_GPS_NORTH
&& mapboxMap.getCameraPosition().bearing != 0;
if (cameraMode == CameraMode.TRACKING_GPS
|| cameraMode == CameraMode.NONE_GPS
|| trackingNorth) {
setBearing(gpsBearing);
}
}
@Override
public void onNewCompassBearingValue(float compassBearing) {
if (cameraMode == CameraMode.TRACKING_COMPASS
|| cameraMode == CameraMode.NONE_COMPASS) {
setBearing(compassBearing);
}
}
@Override
public void onNewZoomValue(float zoom) {
setZoom(zoom);
}
@Override
public void onNewTiltValue(float tilt) {
setTilt(tilt);
}
private void adjustGesturesThresholds() {
if (isLocationTracking()) {
adjustFocalPoint = true;
moveGestureDetector.setMoveThreshold(options.trackingInitialMoveThreshold());
} else {
moveGestureDetector.setMoveThreshold(0f);
}
}
private boolean isLocationTracking() {
return cameraMode == CameraMode.TRACKING
|| cameraMode == CameraMode.TRACKING_COMPASS
|| cameraMode == CameraMode.TRACKING_GPS
|| cameraMode == CameraMode.TRACKING_GPS_NORTH;
}
private boolean isBearingTracking() {
return cameraMode == CameraMode.NONE_COMPASS
|| cameraMode == CameraMode.TRACKING_COMPASS
|| cameraMode == CameraMode.NONE_GPS
|| cameraMode == CameraMode.TRACKING_GPS
|| cameraMode == CameraMode.TRACKING_GPS_NORTH;
}
private void notifyCameraTrackingChangeListener(boolean wasTracking) {
internalCameraTrackingChangedListener.onCameraTrackingChanged(cameraMode);
if (wasTracking && !isLocationTracking()) {
mapboxMap.getUiSettings().setFocalPoint(null);
internalCameraTrackingChangedListener.onCameraTrackingDismissed();
}
}
private MapboxMap.OnMoveListener onMoveListener = new MapboxMap.OnMoveListener() {
private boolean interrupt;
@Override
public void onMoveBegin(MoveGestureDetector detector) {
if (detector.getPointersCount() > 1
&& detector.getMoveThreshold() != options.trackingMultiFingerMoveThreshold()
&& isLocationTracking()) {
detector.setMoveThreshold(options.trackingMultiFingerMoveThreshold());
interrupt = true;
}
}
@Override
public void onMove(MoveGestureDetector detector) {
if (interrupt) {
detector.interrupt();
return;
}
setCameraMode(CameraMode.NONE);
}
@Override
public void onMoveEnd(MoveGestureDetector detector) {
if (!interrupt && isLocationTracking()) {
moveGestureDetector.setMoveThreshold(options.trackingInitialMoveThreshold());
}
interrupt = false;
}
};
private MapboxMap.OnRotateListener onRotateListener = new MapboxMap.OnRotateListener() {
@Override
public void onRotateBegin(RotateGestureDetector detector) {
if (isBearingTracking()) {
setCameraMode(CameraMode.NONE);
}
}
@Override
public void onRotate(RotateGestureDetector detector) {
// no implementation
}
@Override
public void onRotateEnd(RotateGestureDetector detector) {
// no implementation
}
};
private MapboxMap.OnFlingListener onFlingListener = new MapboxMap.OnFlingListener() {
@Override
public void onFling() {
setCameraMode(CameraMode.NONE);
}
};
private class PluginsGesturesManager extends AndroidGesturesManager {
public PluginsGesturesManager(Context context) {
super(context);
}
public PluginsGesturesManager(Context context, boolean applyDefaultThresholds) {
super(context, applyDefaultThresholds);
}
public PluginsGesturesManager(Context context, Set<Integer>[] exclusiveGestures) {
super(context, exclusiveGestures);
}
public PluginsGesturesManager(Context context, List<Set<Integer>> exclusiveGestures,
boolean applyDefaultThresholds) {
super(context, exclusiveGestures, applyDefaultThresholds);
}
@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
if (motionEvent != null) {
int action = motionEvent.getActionMasked();
if (action == MotionEvent.ACTION_UP) {
adjustGesturesThresholds();
}
}
return super.onTouchEvent(motionEvent);
}
}
}

View File

@@ -0,0 +1,267 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Surface;
import android.view.WindowManager;
import java.util.ArrayList;
import java.util.List;
import timber.log.Timber;
/**
* This manager class handles compass events such as starting the tracking of device bearing, or
* when a new compass update occurs.
*/
class LocationLayerCompassEngine implements CompassEngine, SensorEventListener {
// The rate sensor events will be delivered at. As the Android documentation states, this is only
// a hint to the system and the events might actually be received faster or slower then this
// specified rate. Since the minimum Android API levels about 9, we are able to set this value
// ourselves rather than using one of the provided constants which deliver updates too quickly for
// our use case. The default is set to 100ms
private static final int SENSOR_DELAY_MICROS = 100 * 1000;
// Filtering coefficient 0 < ALPHA < 1
private static final float ALPHA = 0.45f;
private final WindowManager windowManager;
private final SensorManager sensorManager;
private final List<CompassListener> compassListeners = new ArrayList<>();
// Not all devices have a compassSensor
@Nullable
private Sensor compassSensor;
@Nullable
private Sensor gravitySensor;
@Nullable
private Sensor magneticFieldSensor;
private float[] truncatedRotationVectorValue = new float[4];
private float[] rotationMatrix = new float[9];
private float[] rotationVectorValue;
private float lastHeading;
private int lastAccuracySensorStatus;
private long compassUpdateNextTimestamp;
private float[] gravityValues = new float[3];
private float[] magneticValues = new float[3];
/**
* Construct a new instance of the this class. A internal compass listeners needed to separate it
* from the cleared list of public listeners.
*/
LocationLayerCompassEngine(WindowManager windowManager, SensorManager sensorManager) {
this.windowManager = windowManager;
this.sensorManager = sensorManager;
compassSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
if (compassSensor == null) {
if (isGyroscopeAvailable()) {
Timber.d("Rotation vector sensor not supported on device, falling back to orientation.");
compassSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
} else {
Timber.d("Rotation vector sensor not supported on device, falling back to accelerometer and magnetic field.");
gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magneticFieldSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
}
}
@Override
public void addCompassListener(@NonNull CompassListener compassListener) {
if (compassListeners.isEmpty()) {
onStart();
}
compassListeners.add(compassListener);
}
@Override
public void removeCompassListener(@NonNull CompassListener compassListener) {
compassListeners.remove(compassListener);
if (compassListeners.isEmpty()) {
onStop();
}
}
@Override
public int getLastAccuracySensorStatus() {
return lastAccuracySensorStatus;
}
@Override
public float getLastHeading() {
return lastHeading;
}
@Override
public void onStart() {
registerSensorListeners();
}
@Override
public void onStop() {
unregisterSensorListeners();
}
@Override
public void onSensorChanged(SensorEvent event) {
// check when the last time the compass was updated, return if too soon.
long currentTime = SystemClock.elapsedRealtime();
if (currentTime < compassUpdateNextTimestamp) {
return;
}
if (lastAccuracySensorStatus == SensorManager.SENSOR_STATUS_UNRELIABLE) {
Timber.d("Compass sensor is unreliable, device calibration is needed.");
return;
}
if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
rotationVectorValue = getRotationVectorFromSensorEvent(event);
updateOrientation();
// Update the compassUpdateNextTimestamp
compassUpdateNextTimestamp = currentTime + LocationLayerConstants.COMPASS_UPDATE_RATE_MS;
} else if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
notifyCompassChangeListeners((event.values[0] + 360) % 360);
} else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
gravityValues = lowPassFilter(getRotationVectorFromSensorEvent(event), gravityValues);
updateOrientation();
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
magneticValues = lowPassFilter(getRotationVectorFromSensorEvent(event), magneticValues);
updateOrientation();
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
if (lastAccuracySensorStatus != accuracy) {
for (CompassListener compassListener : compassListeners) {
compassListener.onCompassAccuracyChange(accuracy);
}
lastAccuracySensorStatus = accuracy;
}
}
private boolean isGyroscopeAvailable() {
return sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null;
}
@SuppressWarnings("SuspiciousNameCombination")
private void updateOrientation() {
if (rotationVectorValue != null) {
SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVectorValue);
} else {
// Get rotation matrix given the gravity and geomagnetic matrices
SensorManager.getRotationMatrix(rotationMatrix, null, gravityValues, magneticValues);
}
final int worldAxisForDeviceAxisX;
final int worldAxisForDeviceAxisY;
// Remap the axes as if the device screen was the instrument panel,
// and adjust the rotation matrix for the device orientation.
switch (windowManager.getDefaultDisplay().getRotation()) {
case Surface.ROTATION_90:
worldAxisForDeviceAxisX = SensorManager.AXIS_Z;
worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_X;
break;
case Surface.ROTATION_180:
worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_X;
worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_Z;
break;
case Surface.ROTATION_270:
worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_Z;
worldAxisForDeviceAxisY = SensorManager.AXIS_X;
break;
case Surface.ROTATION_0:
default:
worldAxisForDeviceAxisX = SensorManager.AXIS_X;
worldAxisForDeviceAxisY = SensorManager.AXIS_Z;
break;
}
float[] adjustedRotationMatrix = new float[9];
SensorManager.remapCoordinateSystem(rotationMatrix, worldAxisForDeviceAxisX,
worldAxisForDeviceAxisY, adjustedRotationMatrix);
// Transform rotation matrix into azimuth/pitch/roll
float[] orientation = new float[3];
SensorManager.getOrientation(adjustedRotationMatrix, orientation);
// The x-axis is all we care about here.
notifyCompassChangeListeners((float) Math.toDegrees(orientation[0]));
}
private void notifyCompassChangeListeners(float heading) {
for (CompassListener compassListener : compassListeners) {
compassListener.onCompassChanged(heading);
}
lastHeading = heading;
}
private void registerSensorListeners() {
if (isCompassSensorAvailable()) {
// Does nothing if the sensors already registered.
sensorManager.registerListener(this, compassSensor, SENSOR_DELAY_MICROS);
} else {
sensorManager.registerListener(this, gravitySensor, SENSOR_DELAY_MICROS);
sensorManager.registerListener(this, magneticFieldSensor, SENSOR_DELAY_MICROS);
}
}
private void unregisterSensorListeners() {
if (isCompassSensorAvailable()) {
sensorManager.unregisterListener(this, compassSensor);
} else {
sensorManager.unregisterListener(this, gravitySensor);
sensorManager.unregisterListener(this, magneticFieldSensor);
}
}
private boolean isCompassSensorAvailable() {
return compassSensor != null;
}
/**
* Helper function, that filters newValues, considering previous values
*
* @param newValues array of float, that contains new data
* @param smoothedValues array of float, that contains previous state
* @return float filtered array of float
*/
private float[] lowPassFilter(float[] newValues, float[] smoothedValues) {
if (smoothedValues == null) {
return newValues;
}
for (int i = 0; i < newValues.length; i++) {
smoothedValues[i] = smoothedValues[i] + ALPHA * (newValues[i] - smoothedValues[i]);
}
return smoothedValues;
}
/**
* Pulls out the rotation vector from a SensorEvent, with a maximum length
* vector of four elements to avoid potential compatibility issues.
*
* @param event the sensor event
* @return the events rotation vector, potentially truncated
*/
@NonNull
private float[] getRotationVectorFromSensorEvent(@NonNull SensorEvent event) {
if (event.values.length > 4) {
// On some Samsung devices SensorManager.getRotationMatrixFromVector
// appears to throw an exception if rotation vector has length > 4.
// For the purposes of this class the first 4 values of the
// rotation vector are sufficient (see crbug.com/335298 for details).
// Only affects Android 4.3
System.arraycopy(event.values, 0, truncatedRotationVectorValue, 0, 4);
return truncatedRotationVectorValue;
} else {
return event.values;
}
}
}

View File

@@ -0,0 +1,60 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
/**
* Contains all the constants being used for the Location layer.
*/
final class LocationLayerConstants {
// Controls the compass update rate in milliseconds
static final int COMPASS_UPDATE_RATE_MS = 500;
// Sets the transition animation duration when switching camera modes.
static final long TRANSITION_ANIMATION_DURATION_MS = 750;
// Sets the max allowed time for the location icon animation from one LatLng to another.
static final long MAX_ANIMATION_DURATION_MS = 2000;
// Sets the duration of change of accuracy radius when a different value is provided.
static final long ACCURACY_RADIUS_ANIMATION_DURATION = 250;
// Default animation duration for zooming while tracking.
static final long DEFAULT_TRACKING_ZOOM_ANIMATION_DURATION = 750;
// Default animation duration for tilting while tracking.
static final long DEFAULT_TRACKING_TILT_ANIMATION_DURATION = 1250;
// 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";
static final String PROPERTY_FOREGROUND_ICON = "mapbox-property-foreground-icon";
static final String PROPERTY_BACKGROUND_ICON = "mapbox-property-background-icon";
static final String PROPERTY_FOREGROUND_STALE_ICON = "mapbox-property-foreground-stale-icon";
static final String PROPERTY_BACKGROUND_STALE_ICON = "mapbox-property-background-stale-icon";
static final String PROPERTY_BEARING_ICON = "mapbox-property-shadow-icon";
// Layers
static final String SHADOW_LAYER = "mapbox-location-shadow";
static final String FOREGROUND_LAYER = "mapbox-location-layer";
static final String BACKGROUND_LAYER = "mapbox-location-stroke-layer";
static final String ACCURACY_LAYER = "mapbox-location-accuracy-layer";
static final String BEARING_LAYER = "mapbox-location-bearing-layer";
// Icons
static final String FOREGROUND_ICON = "mapbox-location-icon";
static final String BACKGROUND_ICON = "mapbox-location-stroke-icon";
static final String FOREGROUND_STALE_ICON = "mapbox-location-stale-icon";
static final String BACKGROUND_STALE_ICON = "mapbox-location-background-stale-icon";
static final String SHADOW_ICON = "mapbox-location-shadow-icon";
static final String BEARING_ICON = "mapbox-location-bearing-icon";
private LocationLayerConstants() {
// Class should not be initialized
}
}

View File

@@ -0,0 +1,966 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.annotation.SuppressLint;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.OnLifecycleEvent;
import android.content.Context;
import android.hardware.SensorManager;
import android.location.Location;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresPermission;
import android.support.annotation.StyleRes;
import android.support.v7.app.AppCompatDelegate;
import android.view.WindowManager;
import com.mapbox.android.core.location.LocationEngine;
import com.mapbox.android.core.location.LocationEngineListener;
import com.mapbox.android.core.location.LocationEnginePriority;
import com.mapbox.android.core.location.LocationEngineProvider;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdate;
import com.mapbox.mapboxsdk.geometry.LatLng;
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;
import com.mapbox.mapboxsdk.plugins.locationlayer.modes.RenderMode;
import java.util.concurrent.CopyOnWriteArrayList;
import timber.log.Timber;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.DEFAULT_TRACKING_TILT_ANIMATION_DURATION;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.DEFAULT_TRACKING_ZOOM_ANIMATION_DURATION;
/**
* The Location layer plugin provides location awareness to your mobile application. Enabling this
* plugin provides a contextual experience to your users by showing an icon representing the users
* current location. A few different modes are offered to provide the right context to your users at
* the correct time. {@link RenderMode#NORMAL} simply shows the users location on the map
* represented as a dot. {@link RenderMode#COMPASS} mode allows you to display an arrow icon
* (by default) that points in the direction the device is pointing in.
* {@link RenderMode#GPS} can be used in conjunction with our Navigation SDK to
* display a larger icon (customized with {@link LocationLayerOptions#gpsDrawable()}) we call the user puck.
* <p>
* This plugin also offers the ability to set a map camera behavior for tracking the user
* location. These different {@link CameraMode}s will track, stop tracking the location based on the
* mode set with {@link LocationLayerPlugin#setCameraMode(int)}.
* <p>
* Lastly, {@link LocationLayerPlugin#setLocationLayerEnabled(boolean)} can be used
* to disable the Location Layer but keep the instance around till the activity is destroyed.
* <p>
* Using this plugin requires you to request permission beforehand manually or using
* {@link com.mapbox.android.core.permissions.PermissionsManager}. Either
* {@code ACCESS_COARSE_LOCATION} or {@code ACCESS_FINE_LOCATION} permissions can be requested and
* this plugin work as expected.
* <p>
* When instantiating the plugin for the first time, the map's max/min zoom levels will be set to
* {@link LocationLayerOptions#MAX_ZOOM_DEFAULT} and {@link LocationLayerOptions#MIN_ZOOM_DEFAULT} respectively.
* You can adjust the zoom range with {@link LocationLayerOptions#maxZoom()} and {@link LocationLayerOptions#minZoom()}.
*/
public final class LocationLayerPlugin implements LifecycleObserver {
private final MapboxMap mapboxMap;
private final MapView mapView;
private LocationLayerOptions options;
private LocationEngine locationEngine;
private CompassEngine compassEngine;
private boolean usingInternalLocationEngine;
private LocationLayer locationLayer;
private LocationLayerCamera locationLayerCamera;
private PluginAnimatorCoordinator pluginAnimatorCoordinator;
/**
* 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;
/**
* Indicates that the plugin is enabled and should be displaying location if Mapbox components are available and
* the lifecycle is in a resumed state.
*/
private boolean isEnabled;
/**
* Indicated that plugin's lifecycle {@link #onStart()} method has been called or the plugin is initialized..
* This allows Mapbox components enter started state and display data, and adds state safety for methods like
* {@link #setLocationLayerEnabled(boolean)}
* <p>
* Initialized in a started state because the plugin can be instantiated after lifecycle's onStart() and
* the developer might not register the lifecycle observer but call lifecycle methods manually instead.
*/
private boolean isPluginStarted = true;
/**
* Indicates if Mapbox components are ready to be interacted with. This can differ from {@link #isPluginStarted}
* if the Mapbox style is being reloaded.
*/
private boolean isLocationLayerStarted;
private StaleStateManager staleStateManager;
private final CopyOnWriteArrayList<OnLocationStaleListener> onLocationStaleListeners
= new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<OnLocationLayerClickListener> onLocationLayerClickListeners
= new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<OnLocationLayerLongClickListener> onLocationLayerLongClickListeners
= new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<OnCameraTrackingChangedListener> onCameraTrackingChangedListeners
= new CopyOnWriteArrayList<>();
/**
* Construct a LocationLayerPlugin
* <p>
* <strong>Note</strong>: This constructor will initialize and use an internal {@link LocationEngine}.
* </p>
*
* @param mapView the MapView to apply the LocationLayerPlugin to
* @param mapboxMap the MapboxMap to apply the LocationLayerPlugin with
*/
public LocationLayerPlugin(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
this.mapView = mapView;
options = LocationLayerOptions.createFromAttributes(mapView.getContext(), R.style.mapbox_LocationLayer);
initializeLocationEngine();
initialize();
}
/**
* Construct a LocationLayerPlugin
* <p>
* <strong>Note</strong>: This constructor will initialize and use an internal {@link LocationEngine}.
* </p>
*
* @param mapView the MapView to apply the LocationLayerPlugin to
* @param mapboxMap the MapboxMap to apply the LocationLayerPlugin with
* @param options to customize the user location icons inside your apps
*/
public LocationLayerPlugin(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap,
@NonNull LocationLayerOptions options) {
this.mapboxMap = mapboxMap;
this.mapView = mapView;
this.options = options;
initializeLocationEngine();
initialize();
}
/**
* Construct a LocationLayerPlugin
* <p>
* <strong>Note</strong>: This constructor will initialize and use an internal {@link LocationEngine}.
* </p>
*
* @param mapView the MapView to apply the LocationLayerPlugin to
* @param mapboxMap the MapboxMap to apply the LocationLayerPlugin with
*/
public LocationLayerPlugin(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap,
@StyleRes int styleRes) {
this.mapboxMap = mapboxMap;
this.mapView = mapView;
this.options = LocationLayerOptions.createFromAttributes(mapView.getContext(), styleRes);
initializeLocationEngine();
initialize();
}
/**
* Construct a LocationLayerPlugin
*
* @param mapView the MapView to apply the LocationLayerPlugin to
* @param mapboxMap the MapboxMap to apply the LocationLayerPlugin with
* @param locationEngine the {@link LocationEngine} this plugin should use to update
*/
public LocationLayerPlugin(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap,
@Nullable LocationEngine locationEngine) {
this(mapView, mapboxMap, locationEngine,
LocationLayerOptions.createFromAttributes(mapView.getContext(),
R.style.mapbox_LocationLayer));
}
/**
* Construct a LocationLayerPlugin
*
* @param mapView the MapView to apply the LocationLayerPlugin to
* @param mapboxMap the MapboxMap to apply the LocationLayerPlugin with
* @param locationEngine the {@link LocationEngine} this plugin should use to update
* @param styleRes customize the user location icons inside your apps {@code style.xml}
*/
public LocationLayerPlugin(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap,
@Nullable LocationEngine locationEngine, @StyleRes int styleRes) {
this(mapView, mapboxMap, locationEngine,
LocationLayerOptions.createFromAttributes(mapView.getContext(), styleRes));
}
/**
* Construct a LocationLayerPlugin
*
* @param mapView the MapView to apply the LocationLayerPlugin to
* @param mapboxMap the MapboxMap to apply the LocationLayerPlugin with
* @param locationEngine the {@link LocationEngine} this plugin should use to update
* @param options to customize the user location icons inside your apps
*/
public LocationLayerPlugin(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap,
@Nullable LocationEngine locationEngine,
@NonNull LocationLayerOptions options) {
this.locationEngine = locationEngine;
this.mapboxMap = mapboxMap;
this.mapView = mapView;
this.options = options;
initialize();
}
/**
* This method will show or hide the location icon and enable or disable the camera
* tracking the location.
*
* @param isEnabled true to show layers and enable camera, false otherwise
*/
public void setLocationLayerEnabled(boolean isEnabled) {
if (isEnabled) {
enableLocationLayerPlugin();
} else {
disableLocationLayerPlugin();
}
}
/**
* Returns whether the plugin is enabled, meaning that location can be displayed and camera modes can be used.
*
* @return true if the plugin is enabled, false otherwise
*/
public boolean isLocationLayerEnabled() {
return isEnabled;
}
/**
* Sets the camera mode, which determines how the map camera will track the rendered location.
* <p>
* <ul>
* <li>{@link CameraMode#NONE}: No camera tracking</li>
* <li>{@link CameraMode#NONE_COMPASS}: Camera does not track location, but does track compass bearing</li>
* <li>{@link CameraMode#NONE_GPS}: Camera does not track location, but does track GPS bearing</li>
* <li>{@link CameraMode#TRACKING}: Camera tracks the user location</li>
* <li>{@link CameraMode#TRACKING_COMPASS}: Camera tracks the user location, with bearing provided by a compass</li>
* <li>{@link CameraMode#TRACKING_GPS}: Camera tracks the user location, with normalized bearing</li>
* <li>{@link CameraMode#TRACKING_GPS_NORTH}: Camera tracks the user location, with bearing always set to north</li>
* </ul>
*
* @param cameraMode one of the modes found in {@link CameraMode}
*/
public void setCameraMode(@CameraMode.Mode int cameraMode) {
boolean isGpsNorth = cameraMode == CameraMode.TRACKING_GPS_NORTH;
pluginAnimatorCoordinator.resetAllCameraAnimations(mapboxMap.getCameraPosition(), isGpsNorth);
locationLayerCamera.setCameraMode(cameraMode);
}
/**
* Provides the current camera mode being used to track
* the location or compass updates.
*
* @return the current camera mode
*/
@CameraMode.Mode
public int getCameraMode() {
return locationLayerCamera.getCameraMode();
}
/**
* Sets the render mode, which determines how the location updates will be rendered on the map.
* <p>
* <ul>
* <li>{@link RenderMode#NORMAL}: Shows user location, bearing ignored</li>
* <li>{@link RenderMode#COMPASS}: Shows user location with bearing considered from compass</li>
* <li>{@link RenderMode#GPS}: Shows user location with bearing considered from location</li>
* </ul>
*
* @param renderMode one of the modes found in {@link RenderMode}
*/
public void setRenderMode(@RenderMode.Mode int renderMode) {
locationLayer.setRenderMode(renderMode);
updateLayerOffsets(true);
}
/**
* Provides the current render mode being used to show
* the location and/or compass updates on the map.
*
* @return the current render mode
*/
@RenderMode.Mode
public int getRenderMode() {
return locationLayer.getRenderMode();
}
/**
* Returns the current location options being used.
*
* @return the current {@link LocationLayerOptions}
*/
public LocationLayerOptions getLocationLayerOptions() {
return options;
}
/**
* Apply a new LocationLayer style with a style resource.
*
* @param styleRes a XML style overriding some or all the options
*/
public void applyStyle(@StyleRes int styleRes) {
applyStyle(LocationLayerOptions.createFromAttributes(mapView.getContext(), styleRes));
}
/**
* Apply a new LocationLayer style with location layer options.
*
* @param options to update the current style
*/
public void applyStyle(LocationLayerOptions options) {
this.options = options;
locationLayer.applyStyle(options);
if (!options.enableStaleState()) {
staleStateManager.onStop();
}
staleStateManager.setDelayTime(options.staleStateTimeout());
updateMapWithOptions(options);
}
/**
* Zooms to the desired zoom level.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
* If you are not using any of {@link CameraMode} modes,
* use one of {@link MapboxMap#moveCamera(CameraUpdate)},
* {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
*
* @param zoomLevel The desired zoom level.
* @param animationDuration The zoom animation duration.
* @param callback The callback with finish/cancel information
*/
public void zoomWhileTracking(double zoomLevel, long animationDuration,
@Nullable MapboxMap.CancelableCallback callback) {
if (!isLocationLayerStarted) {
return;
} else if (getCameraMode() == CameraMode.NONE) {
Timber.e("%s%s",
"LocationLayerPlugin#zoomWhileTracking method can only be used",
" when a camera mode other than CameraMode#NONE is engaged.");
return;
}
pluginAnimatorCoordinator.feedNewZoomLevel(zoomLevel, mapboxMap.getCameraPosition(), animationDuration, callback);
}
/**
* Zooms to the desired zoom level.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
* If you are not using any of {@link CameraMode} modes,
* use one of {@link MapboxMap#moveCamera(CameraUpdate)},
* {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
*
* @param zoomLevel The desired zoom level.
* @param animationDuration The zoom animation duration.
*/
public void zoomWhileTracking(double zoomLevel, long animationDuration) {
zoomWhileTracking(zoomLevel, animationDuration, null);
}
/**
* Zooms to the desired zoom level.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
* If you are not using any of {@link CameraMode} modes,
* use one of {@link MapboxMap#moveCamera(CameraUpdate)},
* {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
*
* @param zoomLevel The desired zoom level.
*/
public void zoomWhileTracking(double zoomLevel) {
zoomWhileTracking(zoomLevel, DEFAULT_TRACKING_ZOOM_ANIMATION_DURATION, null);
}
/**
* Cancels animation started by {@link #zoomWhileTracking(double, long, MapboxMap.CancelableCallback)}.
*/
public void cancelZoomWhileTrackingAnimation() {
pluginAnimatorCoordinator.cancelZoomAnimation();
}
/**
* Tilts the camera.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
* If you are not using any of {@link CameraMode} modes,
* use one of {@link MapboxMap#moveCamera(CameraUpdate)},
* {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
*
* @param tilt The desired camera tilt.
* @param animationDuration The tilt animation duration.
* @param callback The callback with finish/cancel information
*/
public void tiltWhileTracking(double tilt, long animationDuration,
@Nullable MapboxMap.CancelableCallback callback) {
if (!isLocationLayerStarted) {
return;
} else if (getCameraMode() == CameraMode.NONE) {
Timber.e("%s%s",
"LocationLayerPlugin#tiltWhileTracking method can only be used",
" when a camera mode other than CameraMode#NONE is engaged.");
return;
}
pluginAnimatorCoordinator.feedNewTilt(tilt, mapboxMap.getCameraPosition(), animationDuration, callback);
}
/**
* Tilts the camera.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
* If you are not using any of {@link CameraMode} modes,
* use one of {@link MapboxMap#moveCamera(CameraUpdate)},
* {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
*
* @param tilt The desired camera tilt.
* @param animationDuration The tilt animation duration.
*/
public void tiltWhileTracking(double tilt, long animationDuration) {
tiltWhileTracking(tilt, animationDuration, null);
}
/**
* Tilts the camera.
* This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
* If you are not using any of {@link CameraMode} modes,
* use one of {@link MapboxMap#moveCamera(CameraUpdate)},
* {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
*
* @param tilt The desired camera tilt.
*/
public void tiltWhileTracking(double tilt) {
tiltWhileTracking(tilt, DEFAULT_TRACKING_TILT_ANIMATION_DURATION, null);
}
/**
* Cancels animation started by {@link #tiltWhileTracking(double, long, MapboxMap.CancelableCallback)}.
*/
public void cancelTiltWhileTrackingAnimation() {
pluginAnimatorCoordinator.cancelTiltAnimation();
}
/**
* Use to either force a location update or to manually control when the user location gets
* updated.
*
* @param location where the location icon is placed on the map
*/
public void forceLocationUpdate(@Nullable Location location) {
updateLocation(location, false);
}
/**
* Set the location engine to update the current user location.
* <p>
* If {@code null} is passed in, all updates will occur through the
* {@link LocationLayerPlugin#forceLocationUpdate(Location)} method.
*
* @param locationEngine a {@link LocationEngine} this plugin should use to handle updates
*/
public void setLocationEngine(@Nullable LocationEngine locationEngine) {
if (this.locationEngine != null) {
// If internal location engines being used, extra steps need to be taken to deconstruct the
// instance.
if (usingInternalLocationEngine) {
this.locationEngine.removeLocationUpdates();
this.locationEngine.deactivate();
usingInternalLocationEngine = false;
}
this.locationEngine.removeLocationEngineListener(locationEngineListener);
this.locationEngine = null;
}
if (locationEngine != null) {
this.locationEngine = locationEngine;
if (isEnabled) {
this.locationEngine.addLocationEngineListener(locationEngineListener);
}
}
}
/**
* Returns the current {@link LocationEngine} being used for updating the user location layer.
*
* @return the {@link LocationEngine} being used to update the user location layer
*/
@Nullable
public LocationEngine getLocationEngine() {
return locationEngine;
}
/**
* Sets the compass engine used to provide compass heading values.
*
* @param compassEngine to be used
*/
public void setCompassEngine(@NonNull CompassEngine compassEngine) {
this.compassEngine.removeCompassListener(compassListener);
this.compassEngine = compassEngine;
compassEngine.addCompassListener(compassListener);
}
/**
* Returns the compass engine used to provide compass heading values.
*
* @return compass engine currently being used
*/
@NonNull
public CompassEngine getCompassEngine() {
return compassEngine;
}
/**
* Get the last know location of the location layer plugin.
*
* @return the last known location
*/
@Nullable
@RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})
public Location getLastKnownLocation() {
Location location = locationEngine != null ? locationEngine.getLastLocation() : null;
if (location == null) {
location = lastLocation;
}
return location;
}
/**
* Return the last known {@link CompassEngine} accuracy status of the location layer plugin.
* <p>
* The last known accuracy of the compass sensor, one of SensorManager.SENSOR_STATUS_*
*
* @return the last know compass accuracy bearing
*/
public float getLastKnownCompassAccuracyStatus() {
return compassEngine.getLastAccuracySensorStatus();
}
/**
* Add a compass listener to get heading updates every second. Once the first listener gets added,
* the sensor gets initiated and starts returning values.
*
* @param compassListener a {@link CompassListener} for listening into compass heading and
* accuracy changes
*/
public void addCompassListener(@NonNull CompassListener compassListener) {
compassEngine.addCompassListener(compassListener);
}
/**
* Remove a compass listener.
*
* @param compassListener the {@link CompassListener} which you'd like to remove from the listener
* list.
*/
public void removeCompassListener(@NonNull CompassListener compassListener) {
compassEngine.removeCompassListener(compassListener);
}
/**
* Adds a listener that gets invoked when the user clicks the location layer.
*
* @param listener The location layer click listener that is invoked when the
* location layer is clicked
*/
public void addOnLocationClickListener(@NonNull OnLocationLayerClickListener listener) {
onLocationLayerClickListeners.add(listener);
}
/**
* Removes the passed listener from the current list of location click listeners.
*
* @param listener to be removed
*/
public void removeOnLocationClickListener(@NonNull OnLocationLayerClickListener listener) {
onLocationLayerClickListeners.remove(listener);
}
/**
* Adds a listener that gets invoked when the user long clicks the location layer.
*
* @param listener The location layer click listener that is invoked when the
* location layer is clicked
*/
public void addOnLocationLongClickListener(@NonNull OnLocationLayerLongClickListener listener) {
onLocationLayerLongClickListeners.add(listener);
}
/**
* Removes the passed listener from the current list of location long click listeners.
*
* @param listener to be removed
*/
public void removeOnLocationLongClickListener(@NonNull OnLocationLayerLongClickListener listener) {
onLocationLayerLongClickListeners.remove(listener);
}
/**
* Adds a listener that gets invoked when camera tracking state changes.
*
* @param listener Listener that gets invoked when camera tracking state changes.
*/
public void addOnCameraTrackingChangedListener(@NonNull OnCameraTrackingChangedListener listener) {
onCameraTrackingChangedListeners.add(listener);
}
/**
* Removes a listener that gets invoked when camera tracking state changes.
*
* @param listener Listener that gets invoked when camera tracking state changes.
*/
public void removeOnCameraTrackingChangedListener(@NonNull OnCameraTrackingChangedListener listener) {
onCameraTrackingChangedListeners.remove(listener);
}
/**
* Adds the passed listener that gets invoked when user updates have stopped long enough for the last update
* to be considered stale.
* <p>
* This timeout is set by {@link LocationLayerOptions#staleStateTimeout()}.
*
* @param listener invoked when last update is considered stale
*/
public void addOnLocationStaleListener(@NonNull OnLocationStaleListener listener) {
onLocationStaleListeners.add(listener);
}
/**
* Removes the passed listener from the current list of stale listeners.
*
* @param listener to be removed from the list
*/
public void removeOnLocationStaleListener(@NonNull OnLocationStaleListener listener) {
onLocationStaleListeners.remove(listener);
}
/**
* You must call this method from the parent's Activity#onStart() or Fragment#onStart()
*/
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStart() {
if (mapView.isDestroyed()) {
Timber.e("You are calling plugins #onStart after the map was destroyed. Re-create the plugin before using it.");
return;
}
isPluginStarted = true;
onLocationLayerStart();
}
/**
* You must call this method from the parent's Activity#onStop() or Fragment#onStop().
*/
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onStop() {
onLocationLayerStop();
isPluginStarted = false;
}
@SuppressLint("MissingPermission")
private void onLocationLayerStart() {
if (!isPluginStarted) {
return;
}
if (!isLocationLayerStarted) {
isLocationLayerStarted = true;
if (mapboxMap != null) {
mapboxMap.addOnCameraMoveListener(onCameraMoveListener);
mapboxMap.addOnCameraIdleListener(onCameraIdleListener);
}
if (options.enableStaleState()) {
staleStateManager.onStart();
}
compassEngine.onStart();
}
if (isEnabled) {
if (locationEngine != null) {
locationEngine.addLocationEngineListener(locationEngineListener);
if (locationEngine.isConnected() && usingInternalLocationEngine) {
locationEngine.requestLocationUpdates();
}
}
setCameraMode(locationLayerCamera.getCameraMode());
setLastLocation();
setLastCompassHeading();
}
}
private void onLocationLayerStop() {
if (!isLocationLayerStarted || !isPluginStarted) {
return;
}
isLocationLayerStarted = false;
locationLayer.hide();
staleStateManager.onStop();
compassEngine.onStop();
pluginAnimatorCoordinator.cancelAllAnimations();
if (locationEngine != null) {
if (usingInternalLocationEngine) {
locationEngine.removeLocationUpdates();
}
locationEngine.removeLocationEngineListener(locationEngineListener);
}
if (mapboxMap != null) {
mapboxMap.removeOnCameraMoveListener(onCameraMoveListener);
mapboxMap.removeOnCameraIdleListener(onCameraIdleListener);
}
}
private void initialize() {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
mapView.addOnMapChangedListener(onMapChangedListener);
mapboxMap.addOnMapClickListener(onMapClickListener);
mapboxMap.addOnMapLongClickListener(onMapLongClickListener);
LayerSourceProvider sourceProvider = new LayerSourceProvider();
LayerFeatureProvider featureProvider = new LayerFeatureProvider();
LayerBitmapProvider bitmapProvider = new LayerBitmapProvider(mapView.getContext());
locationLayer = new LocationLayer(mapboxMap, sourceProvider, featureProvider, bitmapProvider, options);
locationLayerCamera = new LocationLayerCamera(
mapView.getContext(), mapboxMap, cameraTrackingChangedListener, options, onCameraMoveInvalidateListener);
pluginAnimatorCoordinator = new PluginAnimatorCoordinator();
pluginAnimatorCoordinator.addLayerListener(locationLayer);
pluginAnimatorCoordinator.addCameraListener(locationLayerCamera);
Context context = mapView.getContext();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
compassEngine = new LocationLayerCompassEngine(windowManager, sensorManager);
compassEngine.addCompassListener(compassListener);
staleStateManager = new StaleStateManager(onLocationStaleListener, options.staleStateTimeout());
updateMapWithOptions(options);
enableLocationLayerPlugin();
setRenderMode(RenderMode.NORMAL);
setCameraMode(CameraMode.NONE);
}
private void initializeLocationEngine() {
usingInternalLocationEngine = true;
locationEngine = new LocationEngineProvider(mapView.getContext()).obtainBestLocationEngineAvailable();
locationEngine.setPriority(LocationEnginePriority.HIGH_ACCURACY);
locationEngine.setFastestInterval(1000);
locationEngine.addLocationEngineListener(locationEngineListener);
locationEngine.activate();
}
private void enableLocationLayerPlugin() {
isEnabled = true;
onLocationLayerStart();
}
private void disableLocationLayerPlugin() {
isEnabled = false;
onLocationLayerStop();
}
private void updateMapWithOptions(final LocationLayerOptions options) {
mapboxMap.setPadding(
options.padding()[0], options.padding()[1], options.padding()[2], options.padding()[3]
);
mapboxMap.setMaxZoomPreference(options.maxZoom());
mapboxMap.setMinZoomPreference(options.minZoom());
}
/**
* Updates the user location icon.
*
* @param location the latest user location
*/
private void updateLocation(final Location location, boolean fromLastLocation) {
if (location == null) {
return;
} else if (!isLocationLayerStarted) {
lastLocation = location;
return;
}
if (isEnabled && isPluginStarted) {
locationLayer.show();
}
if (!fromLastLocation) {
staleStateManager.updateLatestLocationTime();
}
CameraPosition currentCameraPosition = mapboxMap.getCameraPosition();
boolean isGpsNorth = getCameraMode() == CameraMode.TRACKING_GPS_NORTH;
pluginAnimatorCoordinator.feedNewLocation(location, currentCameraPosition, isGpsNorth);
updateAccuracyRadius(location, false);
lastLocation = location;
}
private void updateCompassHeading(float heading) {
pluginAnimatorCoordinator.feedNewCompassBearing(heading, mapboxMap.getCameraPosition());
}
/**
* If the locationEngine contains a last location value, we use it for the initial location layer
* position.
*/
@SuppressLint("MissingPermission")
private void setLastLocation() {
updateLocation(getLastKnownLocation(), true);
}
private void setLastCompassHeading() {
updateCompassHeading(compassEngine.getLastHeading());
}
@SuppressLint("MissingPermission")
private void updateLayerOffsets(boolean forceUpdate) {
CameraPosition position = mapboxMap.getCameraPosition();
if (lastCameraPosition == null || forceUpdate) {
lastCameraPosition = position;
locationLayer.updateForegroundBearing((float) position.bearing);
locationLayer.updateForegroundOffset(position.tilt);
updateAccuracyRadius(getLastKnownLocation(), true);
return;
}
if (position.bearing != lastCameraPosition.bearing) {
locationLayer.updateForegroundBearing((float) position.bearing);
}
if (position.tilt != lastCameraPosition.tilt) {
locationLayer.updateForegroundOffset(position.tilt);
}
if (position.zoom != lastCameraPosition.zoom) {
updateAccuracyRadius(getLastKnownLocation(), true);
}
lastCameraPosition = position;
}
private void updateAccuracyRadius(Location location, boolean noAnimation) {
pluginAnimatorCoordinator.feedNewAccuracyRadius(Utils.calculateZoomLevelRadius(mapboxMap, location), noAnimation);
}
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) {
if (!onLocationLayerClickListeners.isEmpty() && locationLayer.onMapClick(point)) {
for (OnLocationLayerClickListener listener : onLocationLayerClickListeners) {
listener.onLocationLayerClick();
}
}
}
};
private MapboxMap.OnMapLongClickListener onMapLongClickListener = new MapboxMap.OnMapLongClickListener() {
@Override
public void onMapLongClick(@NonNull LatLng point) {
if (!onLocationLayerLongClickListeners.isEmpty() && locationLayer.onMapClick(point)) {
for (OnLocationLayerLongClickListener listener : onLocationLayerLongClickListeners) {
listener.onLocationLayerLongClick();
}
}
}
};
private OnLocationStaleListener onLocationStaleListener = new OnLocationStaleListener() {
@Override
public void onStaleStateChange(boolean isStale) {
locationLayer.setLocationsStale(isStale);
for (OnLocationStaleListener listener : onLocationStaleListeners) {
listener.onStaleStateChange(isStale);
}
}
};
private OnMapChangedListener onMapChangedListener = new OnMapChangedListener() {
@SuppressLint("MissingPermission")
@Override
public void onMapChanged(int change) {
if (change == MapView.WILL_START_LOADING_MAP) {
onLocationLayerStop();
} else if (change == MapView.DID_FINISH_LOADING_STYLE) {
locationLayer.initializeComponents(options);
locationLayerCamera.initializeOptions(options);
onLocationLayerStart();
}
}
};
private OnCameraMoveInvalidateListener onCameraMoveInvalidateListener = new OnCameraMoveInvalidateListener() {
@Override
public void onInvalidateCameraMove() {
onCameraMoveListener.onCameraMove();
}
};
private CompassListener compassListener = new CompassListener() {
@Override
public void onCompassChanged(float userHeading) {
updateCompassHeading(userHeading);
}
@Override
public void onCompassAccuracyChange(int compassStatus) {
// Currently don't handle this inside SDK
}
};
private LocationEngineListener locationEngineListener = new LocationEngineListener() {
@Override
@SuppressWarnings( {"MissingPermission"})
public void onConnected() {
if (usingInternalLocationEngine && isLocationLayerStarted && isEnabled) {
locationEngine.requestLocationUpdates();
}
}
@Override
public void onLocationChanged(Location location) {
updateLocation(location, false);
}
};
private OnCameraTrackingChangedListener cameraTrackingChangedListener = new OnCameraTrackingChangedListener() {
@Override
public void onCameraTrackingDismissed() {
for (OnCameraTrackingChangedListener listener : onCameraTrackingChangedListeners) {
listener.onCameraTrackingDismissed();
}
}
@Override
public void onCameraTrackingChanged(int currentMode) {
pluginAnimatorCoordinator.cancelZoomAnimation();
pluginAnimatorCoordinator.cancelTiltAnimation();
for (OnCameraTrackingChangedListener listener : onCameraTrackingChangedListeners) {
listener.onCameraTrackingChanged(currentMode);
}
}
};
}

View File

@@ -0,0 +1,38 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import java.util.List;
abstract class MapboxCameraAnimatorAdapter extends
PluginFloatAnimator<PluginAnimator.OnCameraAnimationsValuesChangeListener> {
private final MapboxMap.CancelableCallback cancelableCallback;
MapboxCameraAnimatorAdapter(Float previous, Float target,
List<OnCameraAnimationsValuesChangeListener> updateListeners,
@Nullable MapboxMap.CancelableCallback cancelableCallback) {
super(previous, target, updateListeners);
this.cancelableCallback = cancelableCallback;
addListener(new MapboxAnimatorListener());
}
private final class MapboxAnimatorListener extends AnimatorListenerAdapter {
@Override
public void onAnimationCancel(Animator animation) {
if (cancelableCallback != null) {
cancelableCallback.onCancel();
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (cancelableCallback != null) {
cancelableCallback.onFinish();
}
}
}
}

View File

@@ -0,0 +1,7 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
interface OnCameraMoveInvalidateListener {
void onInvalidateCameraMove();
}

View File

@@ -0,0 +1,21 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import com.mapbox.mapboxsdk.plugins.locationlayer.modes.CameraMode;
/**
* Listener that gets invoked when camera tracking state changes.
*/
public interface OnCameraTrackingChangedListener {
/**
* Invoked whenever camera tracking is broken.
* This callback gets invoked just after {@link #onCameraTrackingChanged(int)}, if needed.
*/
void onCameraTrackingDismissed();
/**
* Invoked on every {@link CameraMode} change.
*
* @param currentMode current active {@link CameraMode}.
*/
void onCameraTrackingChanged(@CameraMode.Mode int currentMode);
}

View File

@@ -0,0 +1,11 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
/**
* The Location Layer Plugin exposes an API for listening to when the user clicks on the location
* layer icon visible on the map. when this event occurs, the {@link #onLocationLayerClick()} method
* gets invoked.
*/
public interface OnLocationLayerClickListener {
void onLocationLayerClick();
}

View File

@@ -0,0 +1,11 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
/**
* The Location Layer Plugin exposes an API for listening to when the user long clicks on the location
* layer icon visible on the map. when this event occurs, the {@link #onLocationLayerLongClick()} method
* gets invoked.
*/
public interface OnLocationLayerLongClickListener {
void onLocationLayerLongClick();
}

View File

@@ -0,0 +1,13 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
/**
* Listener that can be added as a callback when the last location update
* is considered stale.
* <p>
* The time from the last location update that determines if a location update
* is stale or not is provided by {@link LocationLayerOptions#staleStateTimeout()}.
*/
public interface OnLocationStaleListener {
void onStaleStateChange(boolean isStale);
}

View File

@@ -0,0 +1,92 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.support.annotation.IntDef;
import com.mapbox.mapboxsdk.geometry.LatLng;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* Abstract class for all of the plugin animators.
*
* @param <K> Data type that will be animated.
* @param <L> Listener of animation updates.
*/
abstract class PluginAnimator<K, L> extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener {
@Retention(RetentionPolicy.SOURCE)
@IntDef( {
ANIMATOR_LAYER_LATLNG,
ANIMATOR_CAMERA_LATLNG,
ANIMATOR_LAYER_GPS_BEARING,
ANIMATOR_LAYER_COMPASS_BEARING,
ANIMATOR_CAMERA_GPS_BEARING,
ANIMATOR_CAMERA_COMPASS_BEARING,
ANIMATOR_LAYER_ACCURACY,
ANIMATOR_ZOOM,
ANIMATOR_TILT
})
@interface Type {
}
static final int ANIMATOR_LAYER_LATLNG = 0;
static final int ANIMATOR_CAMERA_LATLNG = 1;
static final int ANIMATOR_LAYER_GPS_BEARING = 2;
static final int ANIMATOR_LAYER_COMPASS_BEARING = 3;
static final int ANIMATOR_CAMERA_GPS_BEARING = 4;
static final int ANIMATOR_CAMERA_COMPASS_BEARING = 5;
static final int ANIMATOR_LAYER_ACCURACY = 6;
static final int ANIMATOR_ZOOM = 7;
static final int ANIMATOR_TILT = 8;
private final int animatorType = provideAnimatorType();
final List<L> updateListeners;
private final K target;
PluginAnimator(K previous, K target, List<L> updateListeners) {
setObjectValues(previous, target);
setEvaluator(provideEvaluator());
this.updateListeners = updateListeners;
this.target = target;
addUpdateListener(this);
}
K getTarget() {
return target;
}
@Type
int getAnimatorType() {
return animatorType;
}
@Type
abstract int provideAnimatorType();
abstract TypeEvaluator provideEvaluator();
interface OnLayerAnimationsValuesChangeListener {
void onNewLatLngValue(LatLng latLng);
void onNewGpsBearingValue(float gpsBearing);
void onNewCompassBearingValue(float compassBearing);
void onNewAccuracyRadiusValue(float accuracyRadiusValue);
}
interface OnCameraAnimationsValuesChangeListener {
void onNewLatLngValue(LatLng latLng);
void onNewGpsBearingValue(float gpsBearing);
void onNewCompassBearingValue(float compassBearing);
void onNewZoomValue(float zoom);
void onNewTiltValue(float tilt);
}
}

View File

@@ -0,0 +1,375 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.annotation.SuppressLint;
import android.location.Location;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.animation.LinearInterpolator;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.ACCURACY_RADIUS_ANIMATION_DURATION;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.COMPASS_UPDATE_RATE_MS;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.MAX_ANIMATION_DURATION_MS;
import static com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.TRANSITION_ANIMATION_DURATION_MS;
import static com.mapbox.mapboxsdk.plugins.locationlayer.PluginAnimator.ANIMATOR_CAMERA_COMPASS_BEARING;
import static com.mapbox.mapboxsdk.plugins.locationlayer.PluginAnimator.ANIMATOR_CAMERA_GPS_BEARING;
import static com.mapbox.mapboxsdk.plugins.locationlayer.PluginAnimator.ANIMATOR_CAMERA_LATLNG;
import static com.mapbox.mapboxsdk.plugins.locationlayer.PluginAnimator.ANIMATOR_LAYER_ACCURACY;
import static com.mapbox.mapboxsdk.plugins.locationlayer.PluginAnimator.ANIMATOR_LAYER_COMPASS_BEARING;
import static com.mapbox.mapboxsdk.plugins.locationlayer.PluginAnimator.ANIMATOR_LAYER_GPS_BEARING;
import static com.mapbox.mapboxsdk.plugins.locationlayer.PluginAnimator.ANIMATOR_LAYER_LATLNG;
import static com.mapbox.mapboxsdk.plugins.locationlayer.PluginAnimator.ANIMATOR_TILT;
import static com.mapbox.mapboxsdk.plugins.locationlayer.PluginAnimator.ANIMATOR_ZOOM;
final class PluginAnimatorCoordinator {
@SuppressLint("UseSparseArrays")
final Map<Integer, PluginAnimator> animatorMap = new HashMap<>();
final List<PluginAnimator.OnLayerAnimationsValuesChangeListener> layerListeners = new ArrayList<>();
final List<PluginAnimator.OnCameraAnimationsValuesChangeListener> cameraListeners = new ArrayList<>();
private Location previousLocation;
private float previousAccuracyRadius = -1;
private float previousCompassBearing = -1;
private long locationUpdateTimestamp = -1;
void addLayerListener(PluginAnimator.OnLayerAnimationsValuesChangeListener listener) {
layerListeners.add(listener);
}
void removeLayerListener(PluginAnimator.OnLayerAnimationsValuesChangeListener listener) {
layerListeners.remove(listener);
}
void addCameraListener(PluginAnimator.OnCameraAnimationsValuesChangeListener listener) {
cameraListeners.add(listener);
}
void removeCameraListener(PluginAnimator.OnCameraAnimationsValuesChangeListener listener) {
cameraListeners.remove(listener);
}
void feedNewLocation(@NonNull Location newLocation, @NonNull CameraPosition currentCameraPosition,
boolean isGpsNorth) {
if (previousLocation == null) {
previousLocation = newLocation;
locationUpdateTimestamp = SystemClock.elapsedRealtime() - TRANSITION_ANIMATION_DURATION_MS;
}
LatLng previousLayerLatLng = getPreviousLayerLatLng();
float previousLayerBearing = getPreviousLayerGpsBearing();
LatLng previousCameraLatLng = currentCameraPosition.target;
float previousCameraBearing = (float) currentCameraPosition.bearing;
LatLng targetLatLng = new LatLng(newLocation);
float targetLayerBearing = newLocation.getBearing();
float targetCameraBearing = newLocation.getBearing();
targetCameraBearing = checkGpsNorth(isGpsNorth, targetCameraBearing);
updateLayerAnimators(previousLayerLatLng, targetLatLng, previousLayerBearing, targetLayerBearing);
updateCameraAnimators(previousCameraLatLng, previousCameraBearing, targetLatLng, targetCameraBearing);
playLocationAnimators(getAnimationDuration());
previousLocation = newLocation;
}
void feedNewCompassBearing(float targetCompassBearing, @NonNull CameraPosition currentCameraPosition) {
if (previousCompassBearing < 0) {
previousCompassBearing = targetCompassBearing;
}
float previousLayerBearing = getPreviousLayerCompassBearing();
float previousCameraBearing = (float) currentCameraPosition.bearing;
updateCompassAnimators(targetCompassBearing, previousLayerBearing, previousCameraBearing);
playCompassAnimators(COMPASS_UPDATE_RATE_MS);
previousCompassBearing = targetCompassBearing;
}
void feedNewAccuracyRadius(float targetAccuracyRadius, boolean noAnimation) {
if (previousAccuracyRadius < 0) {
previousAccuracyRadius = targetAccuracyRadius;
}
float previousAccuracyRadius = getPreviousAccuracyRadius();
updateAccuracyAnimators(targetAccuracyRadius, previousAccuracyRadius);
playAccuracyAnimator(noAnimation ? 0 : ACCURACY_RADIUS_ANIMATION_DURATION);
this.previousAccuracyRadius = targetAccuracyRadius;
}
void feedNewZoomLevel(double targetZoomLevel, @NonNull CameraPosition currentCameraPosition, long animationDuration,
@Nullable MapboxMap.CancelableCallback callback) {
updateZoomAnimator((float) targetZoomLevel, (float) currentCameraPosition.zoom, callback);
playZoomAnimator(animationDuration);
}
void feedNewTilt(double targetTilt, @NonNull CameraPosition currentCameraPosition, long animationDuration,
@Nullable MapboxMap.CancelableCallback callback) {
updateTiltAnimator((float) targetTilt, (float) currentCameraPosition.tilt, callback);
playTiltAnimator(animationDuration);
}
private LatLng getPreviousLayerLatLng() {
LatLng previousLatLng;
PluginAnimator latLngAnimator = animatorMap.get(ANIMATOR_LAYER_LATLNG);
if (latLngAnimator != null) {
previousLatLng = (LatLng) latLngAnimator.getAnimatedValue();
} else {
previousLatLng = new LatLng(previousLocation);
}
return previousLatLng;
}
private float getPreviousLayerGpsBearing() {
LayerGpsBearingAnimator animator = (LayerGpsBearingAnimator) animatorMap.get(ANIMATOR_LAYER_GPS_BEARING);
float previousBearing;
if (animator != null) {
previousBearing = (float) animator.getAnimatedValue();
} else {
previousBearing = previousLocation.getBearing();
}
return previousBearing;
}
private float getPreviousLayerCompassBearing() {
LayerCompassBearingAnimator animator =
(LayerCompassBearingAnimator) animatorMap.get(ANIMATOR_LAYER_COMPASS_BEARING);
float previousBearing;
if (animator != null) {
previousBearing = (float) animator.getAnimatedValue();
} else {
previousBearing = previousCompassBearing;
}
return previousBearing;
}
private float getPreviousAccuracyRadius() {
LayerAccuracyAnimator animator = (LayerAccuracyAnimator) animatorMap.get(ANIMATOR_LAYER_ACCURACY);
float previousRadius;
if (animator != null) {
previousRadius = (float) animator.getAnimatedValue();
} else {
previousRadius = previousAccuracyRadius;
}
return previousRadius;
}
private void updateLayerAnimators(LatLng previousLatLng, LatLng targetLatLng,
float previousBearing, float targetBearing) {
createNewAnimator(ANIMATOR_LAYER_LATLNG, new LayerLatLngAnimator(previousLatLng, targetLatLng, layerListeners));
float normalizedLayerBearing = Utils.shortestRotation(targetBearing, previousBearing);
createNewAnimator(ANIMATOR_LAYER_GPS_BEARING,
new LayerGpsBearingAnimator(previousBearing, normalizedLayerBearing, layerListeners));
}
private void updateCameraAnimators(LatLng previousCameraLatLng, float previousCameraBearing,
LatLng targetLatLng, float targetBearing) {
createNewAnimator(ANIMATOR_CAMERA_LATLNG,
new CameraLatLngAnimator(previousCameraLatLng, targetLatLng, cameraListeners));
float normalizedCameraBearing = Utils.shortestRotation(targetBearing, previousCameraBearing);
createNewAnimator(ANIMATOR_CAMERA_GPS_BEARING,
new CameraGpsBearingAnimator(previousCameraBearing, normalizedCameraBearing, cameraListeners));
}
private void updateCompassAnimators(float targetCompassBearing, float previousLayerBearing,
float previousCameraBearing) {
float normalizedLayerBearing = Utils.shortestRotation(targetCompassBearing, previousLayerBearing);
createNewAnimator(ANIMATOR_LAYER_COMPASS_BEARING,
new LayerCompassBearingAnimator(previousLayerBearing, normalizedLayerBearing, layerListeners));
float normalizedCameraBearing = Utils.shortestRotation(targetCompassBearing, previousCameraBearing);
createNewAnimator(ANIMATOR_CAMERA_COMPASS_BEARING,
new CameraCompassBearingAnimator(previousCameraBearing, normalizedCameraBearing, cameraListeners));
}
private void updateAccuracyAnimators(float targetAccuracyRadius, float previousAccuracyRadius) {
createNewAnimator(ANIMATOR_LAYER_ACCURACY,
new LayerAccuracyAnimator(previousAccuracyRadius, targetAccuracyRadius, layerListeners));
}
private void updateZoomAnimator(float targetZoomLevel, float previousZoomLevel,
@Nullable MapboxMap.CancelableCallback cancelableCallback) {
createNewAnimator(ANIMATOR_ZOOM,
new ZoomAnimator(previousZoomLevel, targetZoomLevel, cameraListeners, cancelableCallback));
}
private void updateTiltAnimator(float targetTilt, float previousTiltLevel,
@Nullable MapboxMap.CancelableCallback cancelableCallback) {
createNewAnimator(ANIMATOR_TILT,
new TiltAnimator(previousTiltLevel, targetTilt, cameraListeners, cancelableCallback));
}
private long getAnimationDuration() {
long previousUpdateTimeStamp = locationUpdateTimestamp;
locationUpdateTimestamp = SystemClock.elapsedRealtime();
long animationDuration;
if (previousUpdateTimeStamp == 0) {
animationDuration = 0;
} else {
animationDuration = (long) ((locationUpdateTimestamp - previousUpdateTimeStamp) * 1.1f)
/*make animation slightly longer*/;
}
animationDuration = Math.min(animationDuration, MAX_ANIMATION_DURATION_MS);
return animationDuration;
}
private float checkGpsNorth(boolean isGpsNorth, float targetCameraBearing) {
if (isGpsNorth) {
targetCameraBearing = 0;
}
return targetCameraBearing;
}
private void playLocationAnimators(long duration) {
List<Animator> locationAnimators = new ArrayList<>();
locationAnimators.add(animatorMap.get(ANIMATOR_LAYER_LATLNG));
locationAnimators.add(animatorMap.get(ANIMATOR_LAYER_GPS_BEARING));
locationAnimators.add(animatorMap.get(ANIMATOR_CAMERA_LATLNG));
locationAnimators.add(animatorMap.get(ANIMATOR_CAMERA_GPS_BEARING));
AnimatorSet locationAnimatorSet = new AnimatorSet();
locationAnimatorSet.playTogether(locationAnimators);
locationAnimatorSet.setInterpolator(new LinearInterpolator());
locationAnimatorSet.setDuration(duration);
locationAnimatorSet.start();
}
private void playCompassAnimators(long duration) {
List<Animator> compassAnimators = new ArrayList<>();
compassAnimators.add(animatorMap.get(ANIMATOR_LAYER_COMPASS_BEARING));
compassAnimators.add(animatorMap.get(ANIMATOR_CAMERA_COMPASS_BEARING));
AnimatorSet compassAnimatorSet = new AnimatorSet();
compassAnimatorSet.playTogether(compassAnimators);
compassAnimatorSet.setDuration(duration);
compassAnimatorSet.start();
}
private void playAccuracyAnimator(long duration) {
PluginAnimator animator = animatorMap.get(ANIMATOR_LAYER_ACCURACY);
animator.setDuration(duration);
animator.start();
}
private void playZoomAnimator(long duration) {
PluginAnimator animator = animatorMap.get(ANIMATOR_ZOOM);
animator.setDuration(duration);
animator.start();
}
private void playTiltAnimator(long duration) {
PluginAnimator animator = animatorMap.get(ANIMATOR_TILT);
animator.setDuration(duration);
animator.start();
}
private void playCameraLocationAnimators(long duration) {
List<Animator> locationAnimators = new ArrayList<>();
locationAnimators.add(animatorMap.get(ANIMATOR_CAMERA_LATLNG));
locationAnimators.add(animatorMap.get(ANIMATOR_CAMERA_GPS_BEARING));
AnimatorSet locationAnimatorSet = new AnimatorSet();
locationAnimatorSet.playTogether(locationAnimators);
locationAnimatorSet.setInterpolator(new LinearInterpolator());
locationAnimatorSet.setDuration(duration);
locationAnimatorSet.start();
}
void resetAllCameraAnimations(CameraPosition currentCameraPosition, boolean isGpsNorth) {
resetCameraCompassAnimation(currentCameraPosition);
resetCameraLocationAnimations(currentCameraPosition, isGpsNorth);
playCameraLocationAnimators(TRANSITION_ANIMATION_DURATION_MS);
}
private void resetCameraLocationAnimations(CameraPosition currentCameraPosition, boolean isGpsNorth) {
resetCameraLatLngAnimation(currentCameraPosition);
resetCameraGpsBearingAnimation(currentCameraPosition, isGpsNorth);
}
private void resetCameraLatLngAnimation(CameraPosition currentCameraPosition) {
CameraLatLngAnimator animator = (CameraLatLngAnimator) animatorMap.get(ANIMATOR_CAMERA_LATLNG);
if (animator == null) {
return;
}
LatLng currentTarget = animator.getTarget();
LatLng previousCameraTarget = currentCameraPosition.target;
createNewAnimator(ANIMATOR_CAMERA_LATLNG,
new CameraLatLngAnimator(previousCameraTarget, currentTarget, cameraListeners));
}
private void resetCameraGpsBearingAnimation(CameraPosition currentCameraPosition, boolean isGpsNorth) {
CameraGpsBearingAnimator animator = (CameraGpsBearingAnimator) animatorMap.get(ANIMATOR_CAMERA_GPS_BEARING);
if (animator == null) {
return;
}
float currentTargetBearing = animator.getTarget();
currentTargetBearing = checkGpsNorth(isGpsNorth, currentTargetBearing);
float previousCameraBearing = (float) currentCameraPosition.bearing;
float normalizedCameraBearing = Utils.shortestRotation(currentTargetBearing, previousCameraBearing);
createNewAnimator(ANIMATOR_CAMERA_GPS_BEARING,
new CameraGpsBearingAnimator(previousCameraBearing, normalizedCameraBearing, cameraListeners));
}
private void resetCameraCompassAnimation(CameraPosition currentCameraPosition) {
CameraCompassBearingAnimator animator =
(CameraCompassBearingAnimator) animatorMap.get(ANIMATOR_CAMERA_COMPASS_BEARING);
if (animator == null) {
return;
}
float currentTargetBearing = animator.getTarget();
float previousCameraBearing = (float) currentCameraPosition.bearing;
float normalizedCameraBearing = Utils.shortestRotation(currentTargetBearing, previousCameraBearing);
createNewAnimator(ANIMATOR_CAMERA_COMPASS_BEARING,
new CameraCompassBearingAnimator(previousCameraBearing, normalizedCameraBearing, cameraListeners));
}
private void createNewAnimator(@PluginAnimator.Type int animatorType, PluginAnimator animator) {
cancelAnimator(animatorType);
animatorMap.put(animatorType, animator);
}
void cancelZoomAnimation() {
cancelAnimator(ANIMATOR_ZOOM);
}
void cancelTiltAnimation() {
cancelAnimator(ANIMATOR_TILT);
}
void cancelAllAnimations() {
for (@PluginAnimator.Type int animatorType : animatorMap.keySet()) {
cancelAnimator(animatorType);
}
}
private void cancelAnimator(@PluginAnimator.Type int animatorType) {
PluginAnimator animator = animatorMap.get(animatorType);
if (animator != null) {
animator.cancel();
animator.removeAllUpdateListeners();
animator.removeAllListeners();
animatorMap.put(animatorType, null);
}
}
}

View File

@@ -0,0 +1,17 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.FloatEvaluator;
import android.animation.TypeEvaluator;
import java.util.List;
abstract class PluginFloatAnimator<L> extends PluginAnimator<Float, L> {
PluginFloatAnimator(Float previous, Float target, List<L> updateListeners) {
super(previous, target, updateListeners);
}
@Override
TypeEvaluator provideEvaluator() {
return new FloatEvaluator();
}
}

View File

@@ -0,0 +1,19 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.TypeEvaluator;
import com.mapbox.mapboxsdk.geometry.LatLng;
import java.util.List;
abstract class PluginLatLngAnimator<L> extends PluginAnimator<LatLng, L> {
PluginLatLngAnimator(LatLng previous, LatLng target, List<L> updateListeners) {
super(previous, target, updateListeners);
}
@Override
TypeEvaluator provideEvaluator() {
return new LatLngEvaluator();
}
}

View File

@@ -0,0 +1,64 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.os.Handler;
/**
* Class controls the location layer stale state when the {@link android.location.Location} hasn't
* been updated in 'x' amount of time. {@link LocationLayerOptions#staleStateTimeout()} can be used to
* control the amount of time before the locations considered stale.
* {@link LocationLayerOptions#enableStaleState()} is available for disabling this behaviour.
*/
class StaleStateManager {
private final OnLocationStaleListener innerOnLocationStaleListeners;
private final Handler handler;
private boolean isStale = true;
private long delayTime;
StaleStateManager(OnLocationStaleListener innerListener, long delayTime) {
innerOnLocationStaleListeners = innerListener;
this.delayTime = delayTime;
handler = new Handler();
innerOnLocationStaleListeners.onStaleStateChange(true);
}
private Runnable staleStateRunnable = new Runnable() {
@Override
public void run() {
isStale = true;
innerOnLocationStaleListeners.onStaleStateChange(true);
}
};
boolean isStale() {
return isStale;
}
void updateLatestLocationTime() {
if (isStale) {
isStale = false;
innerOnLocationStaleListeners.onStaleStateChange(false);
}
postTheCallback();
}
void setDelayTime(long delayTime) {
this.delayTime = delayTime;
postTheCallback();
}
void onStart() {
if (!isStale) {
postTheCallback();
}
}
void onStop() {
handler.removeCallbacksAndMessages(null);
}
private void postTheCallback() {
handler.removeCallbacksAndMessages(null);
handler.postDelayed(staleStateRunnable, delayTime);
}
}

View File

@@ -0,0 +1,27 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.ValueAnimator;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import java.util.List;
class TiltAnimator extends MapboxCameraAnimatorAdapter {
TiltAnimator(Float previous, Float target, List<OnCameraAnimationsValuesChangeListener> updateListeners,
@Nullable MapboxMap.CancelableCallback cancelableCallback) {
super(previous, target, updateListeners, cancelableCallback);
}
@Override
int provideAnimatorType() {
return ANIMATOR_TILT;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
for (OnCameraAnimationsValuesChangeListener listener : updateListeners) {
listener.onNewTiltValue((Float) animation.getAnimatedValue());
}
}
}

View File

@@ -0,0 +1,102 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
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.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import com.mapbox.mapboxsdk.maps.MapboxMap;
public final class Utils {
private Utils() {
// Class should not be initialized
}
/**
* Util for finding the shortest path from the current icon rotated degree to the new degree.
*
* @param magneticHeading the new position of the rotation
* @param previousMagneticHeading the current position of the rotation
* @return the shortest degree of rotation possible
*/
public static float shortestRotation(float magneticHeading, float previousMagneticHeading) {
double diff = previousMagneticHeading - magneticHeading;
if (diff > 180.0f) {
magneticHeading += 360.0f;
} else if (diff < -180.0f) {
magneticHeading -= 360.f;
}
return magneticHeading;
}
static Bitmap getBitmapFromDrawable(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
} else {
// width and height are equal for all assets since they are ovals.
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
static Bitmap generateShadow(Drawable drawable, float elevation) {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
bitmap = Bitmap.createScaledBitmap(bitmap,
toEven(width + elevation), toEven(height + elevation), false);
return bitmap;
}
static Drawable getDrawable(@NonNull Context context, @DrawableRes int drawableRes,
@ColorInt Integer tintColor) {
Drawable drawable = ContextCompat.getDrawable(context, drawableRes);
if (tintColor == null) {
return drawable;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
drawable.setTint(tintColor);
} else {
drawable.mutate().setColorFilter(tintColor, PorterDuff.Mode.SRC_IN);
}
return drawable;
}
static float calculateZoomLevelRadius(@NonNull MapboxMap mapboxMap, @Nullable 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.
*/
private static int toEven(float value) {
int i = (int) (value + .5f);
if (i % 2 == 1) {
return i - 1;
}
return i;
}
}

View File

@@ -0,0 +1,29 @@
package com.mapbox.mapboxsdk.plugins.locationlayer;
import android.animation.ValueAnimator;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import java.util.List;
class ZoomAnimator extends MapboxCameraAnimatorAdapter {
ZoomAnimator(Float previous, Float target, List<OnCameraAnimationsValuesChangeListener> updateListeners,
@Nullable MapboxMap.CancelableCallback cancelableCallback) {
super(previous, target, updateListeners, cancelableCallback);
}
@Override
int provideAnimatorType() {
return ANIMATOR_ZOOM;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
for (OnCameraAnimationsValuesChangeListener listener : updateListeners) {
listener.onNewZoomValue((Float) animation.getAnimatedValue());
}
}
}

View File

@@ -0,0 +1,66 @@
package com.mapbox.mapboxsdk.plugins.locationlayer.modes;
import android.location.Location;
import android.support.annotation.IntDef;
import com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerPlugin;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Contains the variety of camera modes which determine how the camera will track
* the user location.
*/
public final class CameraMode {
private CameraMode() {
// Class should not be initialized
}
/**
* Determine the camera tracking behavior in the {@link LocationLayerPlugin}.
*/
@IntDef( {NONE, NONE_COMPASS, NONE_GPS, TRACKING, TRACKING_COMPASS, TRACKING_GPS, TRACKING_GPS_NORTH})
@Retention(RetentionPolicy.SOURCE)
public @interface Mode {
}
/**
* No camera tracking.
*/
public static final int NONE = 0x00000008;
/**
* Camera does not track location, but does track compass bearing.
*/
public static final int NONE_COMPASS = 0x00000010;
/**
* Camera does not track location, but does track GPS {@link Location} bearing.
*/
public static final int NONE_GPS = 0x00000016;
/**
* Camera tracks the user location.
*/
public static final int TRACKING = 0x00000018;
/**
* Camera tracks the user location, with bearing
* provided by a compass.
*/
public static final int TRACKING_COMPASS = 0x00000020;
/**
* Camera tracks the user location, with bearing
* provided by a normalized {@link Location#getBearing()}.
*/
public static final int TRACKING_GPS = 0x00000022;
/**
* Camera tracks the user location, with bearing
* always set to north (0).
*/
public static final int TRACKING_GPS_NORTH = 0x00000024;
}

View File

@@ -0,0 +1,45 @@
package com.mapbox.mapboxsdk.plugins.locationlayer.modes;
import android.support.annotation.IntDef;
import com.mapbox.mapboxsdk.plugins.locationlayer.CompassEngine;
import com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerPlugin;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Contains the variety of ways the user location can be rendered on the map.
*/
public final class RenderMode {
private RenderMode() {
// Class should not be initialized
}
/**
* One of these constants should be used with {@link LocationLayerPlugin#setRenderMode(int)}.
* mode can be switched at anytime by calling the {@code setLocationLayerMode} method passing
* in the new mode you'd like the location layer to be in.
*/
@IntDef( {COMPASS, GPS, NORMAL})
@Retention(RetentionPolicy.SOURCE)
public @interface Mode {
}
/**
* Basic tracking is enabled, bearing ignored.
*/
public static final int NORMAL = 0x00000012;
/**
* Tracking the user location with bearing considered
* from a {@link CompassEngine}.
*/
public static final int COMPASS = 0x00000004;
/**
* Tracking the user location with bearing considered from {@link android.location.Location}.
*/
public static final int GPS = 0x00000008;
}

View File

@@ -0,0 +1,4 @@
/**
* Contains the Mapbox Location layer plugin.
*/
package com.mapbox.mapboxsdk.plugins.locationlayer;