summaryrefslogtreecommitdiff
path: root/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java')
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java771
1 files changed, 771 insertions, 0 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java
new file mode 100644
index 0000000000..98d66b9307
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java
@@ -0,0 +1,771 @@
+package com.mapbox.mapboxsdk.maps.widgets;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.location.Location;
+import android.os.Build;
+import android.os.SystemClock;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.mapbox.mapboxsdk.R;
+import com.mapbox.mapboxsdk.camera.CameraPosition;
+import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
+import com.mapbox.mapboxsdk.constants.MyBearingTracking;
+import com.mapbox.mapboxsdk.constants.MyLocationTracking;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.location.LocationListener;
+import com.mapbox.mapboxsdk.location.LocationService;
+import com.mapbox.mapboxsdk.maps.MapboxMap;
+import com.mapbox.mapboxsdk.maps.Projection;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This view shows the user's location, as determined from GPS, on the map
+ * as a dot annotation.
+ */
+
+public final class UserLocationView extends View {
+
+ private MapboxMap mMapboxMap;
+ private Projection mProjection;
+
+ private boolean mShowMarker;
+ private boolean mShowDirection;
+ private boolean mShowAccuracy;
+ private boolean mStaleMarker;
+
+ private PointF mMarkerScreenPoint;
+ private Matrix mMarkerScreenMatrix;
+
+ private Paint mAccuracyPaintFill;
+ private Paint mAccuracyPaintStroke;
+ private Path mAccuracyPath;
+ private RectF mAccuracyBounds;
+
+ private Drawable mUserLocationDrawable;
+ private RectF mUserLocationDrawableBoundsF;
+ private Rect mUserLocationDrawableBounds;
+
+ private Drawable mUserLocationBearingDrawable;
+ private RectF mUserLocationBearingDrawableBoundsF;
+ private Rect mUserLocationBearingDrawableBounds;
+
+ private Drawable mUserLocationStaleDrawable;
+ private RectF mUserLocationStaleDrawableBoundsF;
+ private Rect mUserLocationStaleDrawableBounds;
+
+ private Rect mDirtyRect;
+ private RectF mDirtyRectF;
+
+ private LatLng mMarkerCoordinate;
+ private ValueAnimator mMarkerCoordinateAnimator;
+ private float mGpsMarkerDirection;
+ private float mCompassMarkerDirection;
+ private ObjectAnimator mMarkerDirectionAnimator;
+ private float mMarkerAccuracy;
+ private ObjectAnimator mMarkerAccuracyAnimator;
+
+ private LatLng mCurrentMapViewCoordinate;
+ private double mCurrentBearing;
+ private boolean mPaused = false;
+ private Location mUserLocation;
+ private UserLocationListener mUserLocationListener;
+
+ private MapboxMap.OnMyLocationChangeListener mOnMyLocationChangeListener;
+
+ @MyLocationTracking.Mode
+ private int mMyLocationTrackingMode;
+
+ @MyBearingTracking.Mode
+ private int mMyBearingTrackingMode;
+
+ // Compass data
+ private MyBearingListener mBearingChangeListener;
+
+ public UserLocationView(Context context) {
+ super(context);
+ initialize(context);
+ }
+
+ public UserLocationView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize(context);
+ }
+
+ public UserLocationView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize(context);
+ }
+
+ private void initialize(Context context) {
+ // View configuration
+ setEnabled(false);
+ setWillNotDraw(false);
+
+ // Layout params
+ ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ setLayoutParams(lp);
+
+ // Setup sensors
+ mBearingChangeListener = new MyBearingListener(context);
+
+ // Setup the custom paint
+ Resources resources = context.getResources();
+ int accuracyColor = ContextCompat.getColor(context,R.color.my_location_ring);
+
+ float density = resources.getDisplayMetrics().density;
+ mMarkerCoordinate = new LatLng(0.0, 0.0);
+ mMarkerScreenPoint = new PointF();
+ mMarkerScreenMatrix = new Matrix();
+
+ mAccuracyPaintFill = new Paint();
+ mAccuracyPaintFill.setAntiAlias(true);
+ mAccuracyPaintFill.setStyle(Paint.Style.FILL);
+ mAccuracyPaintFill.setColor(accuracyColor);
+ mAccuracyPaintFill.setAlpha((int) (255 * 0.25f));
+
+ mAccuracyPaintStroke = new Paint();
+ mAccuracyPaintStroke.setAntiAlias(true);
+ mAccuracyPaintStroke.setStyle(Paint.Style.STROKE);
+ mAccuracyPaintStroke.setStrokeWidth(0.5f * density);
+ mAccuracyPaintStroke.setColor(accuracyColor);
+ mAccuracyPaintStroke.setAlpha((int) (255 * 0.5f));
+
+ mAccuracyPath = new Path();
+ mAccuracyBounds = new RectF();
+
+ mUserLocationDrawable = ContextCompat.getDrawable(getContext(), R.drawable.my_location);
+ mUserLocationDrawableBounds = new Rect(
+ -mUserLocationDrawable.getIntrinsicWidth() / 2,
+ -mUserLocationDrawable.getIntrinsicHeight() / 2,
+ mUserLocationDrawable.getIntrinsicWidth() / 2,
+ mUserLocationDrawable.getIntrinsicHeight() / 2);
+ mUserLocationDrawableBoundsF = new RectF(
+ -mUserLocationDrawable.getIntrinsicWidth() / 2,
+ -mUserLocationDrawable.getIntrinsicHeight() / 2,
+ mUserLocationDrawable.getIntrinsicWidth() / 2,
+ mUserLocationDrawable.getIntrinsicHeight() / 2);
+ mUserLocationDrawable.setBounds(mUserLocationDrawableBounds);
+
+ mUserLocationBearingDrawable = ContextCompat.getDrawable(getContext(), R.drawable.my_location_bearing);
+ mUserLocationBearingDrawableBounds = new Rect(
+ -mUserLocationBearingDrawable.getIntrinsicWidth() / 2,
+ -mUserLocationBearingDrawable.getIntrinsicHeight() / 2,
+ mUserLocationBearingDrawable.getIntrinsicWidth() / 2,
+ mUserLocationBearingDrawable.getIntrinsicHeight() / 2);
+ mUserLocationBearingDrawableBoundsF = new RectF(
+ -mUserLocationBearingDrawable.getIntrinsicWidth() / 2,
+ -mUserLocationBearingDrawable.getIntrinsicHeight() / 2,
+ mUserLocationBearingDrawable.getIntrinsicWidth() / 2,
+ mUserLocationBearingDrawable.getIntrinsicHeight() / 2);
+ mUserLocationBearingDrawable.setBounds(mUserLocationBearingDrawableBounds);
+
+ mUserLocationStaleDrawable = ContextCompat.getDrawable(getContext(), R.drawable.my_location_stale);
+ mUserLocationStaleDrawableBounds = new Rect(
+ -mUserLocationStaleDrawable.getIntrinsicWidth() / 2,
+ -mUserLocationStaleDrawable.getIntrinsicHeight() / 2,
+ mUserLocationStaleDrawable.getIntrinsicWidth() / 2,
+ mUserLocationStaleDrawable.getIntrinsicHeight() / 2);
+ mUserLocationStaleDrawableBoundsF = new RectF(
+ -mUserLocationStaleDrawable.getIntrinsicWidth() / 2,
+ -mUserLocationStaleDrawable.getIntrinsicHeight() / 2,
+ mUserLocationStaleDrawable.getIntrinsicWidth() / 2,
+ mUserLocationStaleDrawable.getIntrinsicHeight() / 2);
+ mUserLocationStaleDrawable.setBounds(mUserLocationStaleDrawableBounds);
+ }
+
+ public void setMapboxMap(MapboxMap mapboxMap) {
+ mMapboxMap = mapboxMap;
+ mProjection = mapboxMap.getProjection();
+ }
+
+ public void onStart() {
+ if (mMyBearingTrackingMode == MyBearingTracking.COMPASS) {
+ mBearingChangeListener.onStart(getContext());
+ }
+ }
+
+ public void onStop() {
+ mBearingChangeListener.onStop();
+ cancelAnimations();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (!mShowMarker) {
+ return;
+ }
+
+ canvas.concat(mMarkerScreenMatrix);
+
+ Drawable dotDrawable = mShowDirection ? mUserLocationBearingDrawable : mUserLocationDrawable;
+ dotDrawable = mStaleMarker ? mUserLocationStaleDrawable : dotDrawable;
+ // IMPORTANT also update in update()
+ RectF dotBounds = mShowDirection ? mUserLocationBearingDrawableBoundsF : mUserLocationDrawableBoundsF;
+ dotBounds = mStaleMarker ? mUserLocationStaleDrawableBoundsF : dotBounds;
+
+ boolean willDraw = true;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN || !canvas.isHardwareAccelerated()) {
+ willDraw = mShowAccuracy && !mStaleMarker && !canvas.quickReject(mAccuracyPath, Canvas.EdgeType.AA);
+ }
+ willDraw |= !canvas.quickReject(dotBounds, Canvas.EdgeType.AA);
+
+ if (willDraw) {
+ if (mShowAccuracy && !mStaleMarker) {
+ canvas.drawPath(mAccuracyPath, mAccuracyPaintFill);
+ canvas.drawPath(mAccuracyPath, mAccuracyPaintStroke);
+ }
+ dotDrawable.draw(canvas);
+ }
+ }
+
+ public void setMyLocationTrackingMode(@MyLocationTracking.Mode int myLocationTrackingMode) {
+ mMyLocationTrackingMode = myLocationTrackingMode;
+
+ if (myLocationTrackingMode != MyLocationTracking.TRACKING_NONE && mUserLocation != null) {
+ // center map directly if we have a location fix
+ mMarkerCoordinate = new LatLng(mUserLocation.getLatitude(), mUserLocation.getLongitude());
+ mMapboxMap.moveCamera(CameraUpdateFactory.newLatLng(new LatLng(mUserLocation)));
+
+ // center view directly
+ mMarkerScreenMatrix.reset();
+ mMarkerScreenPoint = getMarkerScreenPoint();
+ mMarkerScreenMatrix.setTranslate(mMarkerScreenPoint.x, mMarkerScreenPoint.y);
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+ toggleGps(enabled);
+ }
+
+ public void update() {
+ if (isEnabled() && mShowMarker) {
+ setVisibility(View.VISIBLE);
+
+ mStaleMarker = isStale(mUserLocation);
+
+ // compute new marker position
+ // TODO add JNI method that takes existing pointf
+ if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
+ mMarkerScreenPoint = getMarkerScreenPoint();
+ mMarkerScreenMatrix.reset();
+ mMarkerScreenMatrix.setTranslate(
+ mMarkerScreenPoint.x,
+ mMarkerScreenPoint.y);
+
+ } else if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW) {
+ float bearing;
+ if (mShowDirection) {
+ bearing = mMyBearingTrackingMode == MyBearingTracking.COMPASS ? mBearingChangeListener.getCompassBearing() : mUserLocation.getBearing();
+ } else {
+ bearing = mMapboxMap.getCameraPosition().bearing;
+ }
+
+ if (mCurrentMapViewCoordinate == null) {
+ mCurrentMapViewCoordinate = mMapboxMap.getCameraPosition().target;
+ }
+
+ // only update if there is an actual change
+ if ((!mCurrentMapViewCoordinate.equals(mMarkerCoordinate)) || (!(mCurrentBearing == bearing))) {
+ CameraPosition cameraPosition = new CameraPosition.Builder()
+ .target(mMarkerCoordinate)
+ .bearing(bearing)
+ .build();
+ mMapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), 300, null);
+ mMarkerScreenMatrix.reset();
+ mMarkerScreenPoint = getMarkerScreenPoint();
+ mMarkerScreenMatrix.setTranslate(mMarkerScreenPoint.x, mMarkerScreenPoint.y);
+
+ // set values for next check for actual change
+ mCurrentMapViewCoordinate = mMarkerCoordinate;
+ mCurrentBearing = bearing;
+ }
+ }
+
+ // rotate so arrow in points to bearing
+ if (mShowDirection) {
+ if (mMyBearingTrackingMode == MyBearingTracking.COMPASS && mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
+ mMarkerScreenMatrix.preRotate(mCompassMarkerDirection + mMapboxMap.getCameraPosition().bearing);
+ } else if (mMyBearingTrackingMode == MyBearingTracking.GPS) {
+ if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
+ mMarkerScreenMatrix.preRotate(mGpsMarkerDirection + mMapboxMap.getCameraPosition().bearing);
+ } else {
+ mMarkerScreenMatrix.preRotate(mGpsMarkerDirection);
+ }
+ }
+ }
+
+ // adjust accuracy circle
+ if (mShowAccuracy && !mStaleMarker) {
+ mAccuracyPath.reset();
+ mAccuracyPath.addCircle(0.0f, 0.0f,
+ (float) (mMarkerAccuracy / mMapboxMap.getProjection().getMetersPerPixelAtLatitude(
+ mMarkerCoordinate.getLatitude())),
+ Path.Direction.CW);
+
+ mAccuracyPath.computeBounds(mAccuracyBounds, false);
+ mAccuracyBounds.inset(-1.0f, -1.0f);
+ }
+
+ // invalidate changed pixels
+ if (mDirtyRect == null) {
+ mDirtyRect = new Rect();
+ mDirtyRectF = new RectF();
+ } else {
+ // the old marker location
+ invalidate(mDirtyRect);
+ }
+
+ RectF dotBounds = mShowDirection ? mUserLocationBearingDrawableBoundsF : mUserLocationDrawableBoundsF;
+ dotBounds = mStaleMarker ? mUserLocationStaleDrawableBoundsF : dotBounds;
+ RectF largerBounds = mShowAccuracy && !mStaleMarker && mAccuracyBounds.contains(dotBounds)
+ ? mAccuracyBounds : dotBounds;
+ mMarkerScreenMatrix.mapRect(mDirtyRectF, largerBounds);
+ mDirtyRectF.roundOut(mDirtyRect);
+ invalidate(mDirtyRect); // the new marker location
+ } else {
+ setVisibility(View.INVISIBLE);
+ }
+ }
+
+ public Location getLocation() {
+ return mUserLocation;
+ }
+
+ /**
+ * Enabled / Disable GPS location updates along with updating the UI
+ *
+ * @param enableGps true if GPS is to be enabled, false if GPS is to be disabled
+ */
+ private void toggleGps(boolean enableGps) {
+
+ LocationService locationService = LocationService.getInstance(getContext());
+
+ if (enableGps) {
+ // Set an initial location if one available
+ Location lastLocation = locationService.getLastLocation();
+ if (lastLocation != null) {
+ setLocation(lastLocation);
+ }
+
+ if (mUserLocationListener == null) {
+ mUserLocationListener = new UserLocationListener(this);
+ }
+
+ // Register for Location Updates
+ locationService.addLocationListener(mUserLocationListener);
+ } else {
+ // Disable location and user dot
+ setLocation(null);
+
+ // Deregister for Location Updates
+ locationService.removeLocationListener(mUserLocationListener);
+ }
+
+ locationService.toggleGPS(enableGps);
+ }
+
+ public void setMyBearingTrackingMode(@MyBearingTracking.Mode int myBearingTrackingMode) {
+ mMyBearingTrackingMode = myBearingTrackingMode;
+
+ if (myBearingTrackingMode == MyBearingTracking.COMPASS) {
+ mShowAccuracy = false;
+ mShowDirection = true;
+ mBearingChangeListener.onStart(getContext());
+ } else {
+ mBearingChangeListener.onStop();
+ if (myBearingTrackingMode == MyBearingTracking.GPS) {
+ mShowDirection = (mUserLocation != null) && mUserLocation.hasBearing();
+ } else {
+ mShowDirection = false;
+ }
+ }
+ update();
+ }
+
+ private class MyBearingListener implements SensorEventListener {
+
+ private SensorManager mSensorManager;
+ private Sensor mAccelerometer;
+ private Sensor mMagnetometer;
+ private float[] mLastAccelerometer = new float[3];
+ private float[] mLastMagnetometer = new float[3];
+ private boolean mLastAccelerometerSet = false;
+ private boolean mLastMagnetometerSet = false;
+ private float[] mR = new float[9];
+ private float[] mOrientation = new float[3];
+ private float mCurrentDegree = 0f;
+
+ // Controls the sensor update rate in milliseconds
+ private static final int UPDATE_RATE_MS = 300;
+
+ // Compass data
+ private float mCompassBearing;
+ private long mCompassUpdateNextTimestamp = 0;
+
+ public MyBearingListener(Context context) {
+ mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+ }
+
+ public void onStart(Context context) {
+ mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
+ mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME);
+ }
+
+ public void onStop() {
+ mSensorManager.unregisterListener(this, mAccelerometer);
+ mSensorManager.unregisterListener(this, mMagnetometer);
+ }
+
+ public float getCompassBearing() {
+ return mCurrentDegree;
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (mPaused) {
+ return;
+ }
+
+ long currentTime = SystemClock.elapsedRealtime();
+ if (currentTime < mCompassUpdateNextTimestamp) {
+ return;
+ }
+
+ if (event.sensor == mAccelerometer) {
+ System.arraycopy(event.values, 0, mLastAccelerometer, 0, event.values.length);
+ mLastAccelerometerSet = true;
+ } else if (event.sensor == mMagnetometer) {
+ System.arraycopy(event.values, 0, mLastMagnetometer, 0, event.values.length);
+ mLastMagnetometerSet = true;
+ }
+
+ if (mLastAccelerometerSet && mLastMagnetometerSet) {
+ SensorManager.getRotationMatrix(mR, null, mLastAccelerometer, mLastMagnetometer);
+ SensorManager.getOrientation(mR, mOrientation);
+ float azimuthInRadians = mOrientation[0];
+
+ mCompassBearing = (float) (Math.toDegrees(azimuthInRadians) + 360) % 360;
+ if (mCompassBearing < 0) {
+ // only allow positive degrees
+ mCompassBearing += 360;
+ }
+
+ if (mCompassBearing > mCurrentDegree + 15 || mCompassBearing < mCurrentDegree - 15) {
+ mCurrentDegree = mCompassBearing;
+ setCompass(mCurrentDegree);
+ }
+ }
+ mCompassUpdateNextTimestamp = currentTime + UPDATE_RATE_MS;
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // TODO add accuracy to the equiation
+ }
+ }
+
+ private static class UserLocationListener implements LocationListener {
+
+ private WeakReference<UserLocationView> mUserLocationView;
+
+ public UserLocationListener(UserLocationView userLocationView) {
+ mUserLocationView = new WeakReference<>(userLocationView);
+ }
+
+
+ /**
+ * Callback method for receiving location updates from LocationService.
+ *
+ * @param location The new Location data
+ */
+ @Override
+ public void onLocationChanged(Location location) {
+ UserLocationView locationView = mUserLocationView.get();
+ if (locationView != null && !locationView.isPaused()) {
+ locationView.setLocation(location);
+ }
+ }
+ }
+
+ private boolean isStale(Location location) {
+ if (location != null && mMyBearingTrackingMode != MyBearingTracking.COMPASS) {
+ long ageInNanos;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ ageInNanos = SystemClock.elapsedRealtimeNanos() -
+ location.getElapsedRealtimeNanos();
+ } else {
+ ageInNanos = (System.currentTimeMillis() - location.getTime()) * 1000 * 1000;
+ }
+ final long oneMinuteInNanos = 60L * 1000 * 1000 * 1000;
+ return ageInNanos > oneMinuteInNanos;
+ } else {
+ return false;
+ }
+ }
+
+ // Handles location updates from GPS
+ private void setLocation(Location location) {
+ // if null we should hide the marker
+ if (location == null) {
+ mShowMarker = false;
+ mShowDirection = false;
+ mShowAccuracy = false;
+
+ cancelAnimations();
+
+ mUserLocation = null;
+ return;
+ }
+
+ if (mMarkerCoordinateAnimator != null) {
+ mMarkerCoordinateAnimator.end();
+ mMarkerCoordinateAnimator = null;
+ }
+
+ if (mMarkerDirectionAnimator != null) {
+ mMarkerDirectionAnimator.end();
+ mMarkerDirectionAnimator = null;
+ }
+
+ if (mMarkerAccuracyAnimator != null) {
+ mMarkerAccuracyAnimator.end();
+ mMarkerAccuracyAnimator = null;
+ }
+
+ mShowMarker = true;
+
+ LatLng previousCoordinate;
+ if (mUserLocation == null) {
+ previousCoordinate = new LatLng(location);
+ } else {
+ previousCoordinate = new LatLng(mUserLocation);
+ }
+
+ if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
+ // moving marker above map
+ mMarkerCoordinateAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mMarkerCoordinateAnimator.setDuration(1000);
+ mMarkerCoordinateAnimator.addUpdateListener(new MarkerCoordinateAnimatorListener(
+ previousCoordinate, new LatLng(location)
+ ));
+ mMarkerCoordinateAnimator.start();
+ mMarkerCoordinate = new LatLng(location);
+ } else {
+ // moving map under the tracker
+ mMarkerCoordinate = new LatLng(location);
+ }
+
+ if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE && mMyBearingTrackingMode == MyBearingTracking.GPS) {
+ // show GPS direction
+ mShowDirection = location.hasBearing();
+ if (mShowDirection) {
+ if (mUserLocation != null && mUserLocation.hasBearing()) {
+ mGpsMarkerDirection = mUserLocation.getBearing();
+ }
+ float oldDir = mGpsMarkerDirection;
+ float newDir = location.getBearing();
+ float diff = oldDir - newDir;
+ if (diff > 180.0f) {
+ newDir += 360.0f;
+ } else if (diff < -180.0f) {
+ newDir -= 360.f;
+ }
+ mMarkerDirectionAnimator = ObjectAnimator.ofFloat(this, "direction", oldDir, newDir);
+ mMarkerDirectionAnimator.setDuration(1000);
+ mMarkerDirectionAnimator.start();
+ }
+ } else if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW && mMyBearingTrackingMode == MyBearingTracking.GPS) {
+ // always show north & rotate map below
+ mShowDirection = true;
+ mGpsMarkerDirection = 0;
+ }
+
+ mShowAccuracy = location.hasAccuracy();
+ if (mShowAccuracy) {
+ if (mUserLocation != null && mUserLocation.hasAccuracy()) {
+ mMarkerAccuracy = mUserLocation.getAccuracy();
+ }
+ mMarkerAccuracyAnimator = ObjectAnimator.ofFloat(this, "accuracy", location.getAccuracy());
+ mMarkerAccuracyAnimator.setDuration(1000);
+ mMarkerAccuracyAnimator.start();
+ }
+
+ mUserLocation = location;
+ updateOnNextFrame();
+
+ if (mOnMyLocationChangeListener != null) {
+ mOnMyLocationChangeListener.onMyLocationChange(location);
+ }
+ }
+
+ // handles compass sensor updates
+ private void setCompass(float bearing) {
+ if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
+ // animate marker
+ mShowDirection = true;
+ float oldDir = mCompassMarkerDirection;
+ float newDir = bearing;
+ float diff = oldDir - newDir;
+ if (diff > 180.0f) {
+ newDir += 360.0f;
+ } else if (diff < -180.0f) {
+ newDir -= 360.f;
+ }
+ mMarkerDirectionAnimator = ObjectAnimator.ofFloat(this, "direction", oldDir, newDir);
+ mMarkerDirectionAnimator.setDuration(1000);
+ mMarkerDirectionAnimator.start();
+ mCompassMarkerDirection = bearing;
+
+ } else if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW) {
+ cancelAnimations();
+ if (mMyBearingTrackingMode == MyBearingTracking.COMPASS) {
+ // always show north & change map direction
+ mShowDirection = true;
+ mGpsMarkerDirection = 0;
+ mCompassMarkerDirection = 0;
+ update();
+ }
+ }
+ }
+
+ void updateOnNextFrame() {
+ mMapboxMap.invalidate();
+ }
+
+ /**
+ * Called from MapView.onPause()
+ */
+ public void pause() {
+ mPaused = true;
+ toggleGps(false);
+ }
+
+ /**
+ * Called from MapView.onResume()
+ */
+ public void resume() {
+ mPaused = false;
+ if (isEnabled()) {
+ toggleGps(true);
+ }
+ }
+
+ public void setOnMyLocationChangeListener(@Nullable MapboxMap.OnMyLocationChangeListener listener) {
+ mOnMyLocationChangeListener = listener;
+ }
+
+ // public for animator only
+ public float getDirection() {
+ if (mMyBearingTrackingMode == MyBearingTracking.COMPASS) {
+ return mCompassMarkerDirection;
+ }
+ return mGpsMarkerDirection;
+ }
+
+ // public for animator only
+ public void setDirection(float direction) {
+ if (mMyBearingTrackingMode == MyBearingTracking.COMPASS) {
+ mCompassMarkerDirection = direction % 360.0f;
+ } else {
+ mGpsMarkerDirection = direction % 360.0f;
+ }
+ updateOnNextFrame();
+ }
+
+ // public for animator only
+ public float getAccuracy() {
+ return mMarkerAccuracy;
+ }
+
+ // public for animator only
+ public void setAccuracy(float accuracy) {
+ mMarkerAccuracy = accuracy;
+ updateOnNextFrame();
+ }
+
+ private class MarkerCoordinateAnimatorListener implements ValueAnimator.AnimatorUpdateListener {
+
+ private double mFromLat;
+ private double mFromLng;
+ private double mToLat;
+ private double mToLng;
+
+ private MarkerCoordinateAnimatorListener(LatLng from, LatLng to) {
+ mFromLat = from.getLatitude();
+ mFromLng = from.getLongitude();
+ mToLat = to.getLatitude();
+ mToLng = to.getLongitude();
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float frac = animation.getAnimatedFraction();
+ double latitude = mFromLat + (mToLat - mFromLat) * frac;
+ double longitude = mFromLng + (mToLng - mFromLng) * frac;
+ mMarkerCoordinate.setLatitude(latitude);
+ mMarkerCoordinate.setLongitude(longitude);
+ updateOnNextFrame();
+ }
+ }
+
+ public void cancelAnimations() {
+ if (mMarkerCoordinateAnimator != null) {
+ mMarkerCoordinateAnimator.cancel();
+ mMarkerCoordinateAnimator = null;
+ }
+
+ if (mMarkerDirectionAnimator != null) {
+ mMarkerDirectionAnimator.cancel();
+ mMarkerDirectionAnimator = null;
+ }
+
+ if (mMarkerAccuracyAnimator != null) {
+ mMarkerAccuracyAnimator.cancel();
+ mMarkerAccuracyAnimator = null;
+ }
+ }
+
+ public boolean isPaused() {
+ return mPaused;
+ }
+
+ public PointF getMarkerScreenPoint() {
+ if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) {
+ mMarkerScreenPoint = mProjection.toScreenLocation(mMarkerCoordinate);
+ } else {
+ int[] contentPadding = mMapboxMap.getPadding();
+ mMarkerScreenPoint = new PointF(((getMeasuredWidth() + contentPadding[0] - contentPadding[2]) / 2)
+ , ((getMeasuredHeight() - contentPadding[3] + contentPadding[1]) / 2));
+ }
+ return mMarkerScreenPoint;
+ }
+}