[android] merge LocationLayerPlugin to Maps SDK
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.mapbox.mapboxsdk.plugins.locationlayer;
|
||||
|
||||
interface OnCameraMoveInvalidateListener {
|
||||
|
||||
void onInvalidateCameraMove();
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Contains the Mapbox Location layer plugin.
|
||||
*/
|
||||
package com.mapbox.mapboxsdk.plugins.locationlayer;
|
||||
Reference in New Issue
Block a user