summaryrefslogtreecommitdiff
path: root/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java
diff options
context:
space:
mode:
Diffstat (limited to 'android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java')
-rw-r--r--android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java473
1 files changed, 473 insertions, 0 deletions
diff --git a/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java b/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java
new file mode 100644
index 0000000000..7453f4e28c
--- /dev/null
+++ b/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/views/UserLocationView.java
@@ -0,0 +1,473 @@
+package com.mapbox.mapboxsdk.views;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+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.location.Location;
+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.geometry.LatLng;
+import com.mapzen.android.lost.api.LocationListener;
+import com.mapzen.android.lost.api.LocationRequest;
+import com.mapzen.android.lost.api.LocationServices;
+import com.mapzen.android.lost.api.LostApiClient;
+
+import java.lang.ref.WeakReference;
+
+final class UserLocationView extends View {
+
+ private WeakReference<MapView> mMapView;
+
+ private static final int BLUE_COLOR = 0x39ACCBFF;
+
+ private float mDensity;
+
+ private boolean mShowMarker;
+ private boolean mShowDirection;
+ private boolean mShowAccuracy;
+
+ 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 mMarkerDirection;
+ private ObjectAnimator mMarkerDirectionAnimator;
+ private float mMarkerAccuracy;
+ private ObjectAnimator mMarkerAccuracyAnimator;
+
+ private boolean mPaused = false;
+ private LostApiClient mLocationClient;
+ private LocationRequest mLocationRequest;
+ private Location mUserLocation;
+ private MyLocationListener mLocationListener;
+
+ MapView.OnMyLocationChangeListener mOnMyLocationChangeListener;
+
+ public UserLocationView(MapView mapView, Context context) {
+ super(context);
+ initialize(mapView, context);
+ }
+
+ public UserLocationView(MapView mapView, Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize(mapView, context);
+ }
+
+ public UserLocationView(MapView mapView, Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize(mapView, context);
+ }
+
+ private void initialize(MapView mapView, Context context) {
+ mMapView = new WeakReference<MapView>(mapView);
+
+ // 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 location services
+ mLocationClient = new LostApiClient.Builder(getContext()).build();
+ mLocationRequest = LocationRequest.create()
+ .setFastestInterval(1000)
+ .setSmallestDisplacement(3.0f)
+ .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
+
+ // Setup the custom paint
+ Resources resources = context.getResources();
+ mDensity = 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(BLUE_COLOR);
+ mAccuracyPaintFill.setAlpha((int) (255 * 0.25f));
+
+ mAccuracyPaintStroke = new Paint();
+ mAccuracyPaintStroke.setAntiAlias(true);
+ mAccuracyPaintStroke.setStyle(Paint.Style.STROKE);
+ mAccuracyPaintStroke.setStrokeWidth(0.5f * mDensity);
+ mAccuracyPaintStroke.setColor(BLUE_COLOR);
+ mAccuracyPaintStroke.setAlpha((int) (255 * 0.5f));
+
+ mAccuracyPath = new Path();
+ mAccuracyBounds = new RectF();
+
+ mUserLocationDrawable = ContextCompat.getDrawable(getContext(), R.drawable.user_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.user_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.user_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);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (!mShowMarker) {
+ return;
+ }
+
+ canvas.concat(mMarkerScreenMatrix);
+
+ Drawable dotDrawable = mShowDirection ? mUserLocationBearingDrawable : mUserLocationDrawable;
+ // IMPORTANT also update in uodate()
+ RectF dotBounds = mShowDirection ? mUserLocationBearingDrawableBoundsF : mUserLocationDrawableBoundsF;
+
+ boolean willDraw;
+ willDraw = mShowAccuracy && !canvas.quickReject(mAccuracyPath, Canvas.EdgeType.AA);
+ willDraw |= !canvas.quickReject(dotBounds, Canvas.EdgeType.AA);
+
+ dotBounds.offset(
+ (int) -mMarkerScreenPoint.x,
+ (int) -mMarkerScreenPoint.y);
+
+ if (willDraw) {
+ if (mShowAccuracy) {
+ canvas.drawPath(mAccuracyPath, mAccuracyPaintFill);
+ canvas.drawPath(mAccuracyPath, mAccuracyPaintStroke);
+ }
+ dotDrawable.draw(canvas);
+ }
+ }
+
+ @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);
+
+ // compute new marker position
+ // TODO add JNI method that takes existing pointf
+ mMarkerScreenPoint = mMapView.get().toScreenLocation(mMarkerCoordinate);
+ mMarkerScreenMatrix.reset();
+ mMarkerScreenMatrix.setTranslate(
+ mMarkerScreenPoint.x,
+ mMarkerScreenPoint.y);
+
+ // rotate so arrow in points to bearing
+ if (mShowDirection) {
+ mMarkerScreenMatrix.preRotate(mMarkerDirection +
+ (float) mMapView.get().getDirection());
+ }
+
+ // adjust accuracy circle
+ if (mShowAccuracy) {
+ mAccuracyPath.reset();
+ mAccuracyPath.addCircle(0.0f, 0.0f,
+ (float) (mMarkerAccuracy / mMapView.get().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;
+ RectF largerBounds = 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) {
+ if (mLocationClient == null) {
+ return;
+ }
+
+ if (enableGps) {
+ if (!mLocationClient.isConnected()) {
+ mUserLocation = null;
+ mLocationClient.connect();
+ setLocation(LocationServices.FusedLocationApi.getLastLocation());
+ mLocationListener = new MyLocationListener();
+ LocationServices.FusedLocationApi.requestLocationUpdates(mLocationRequest, mLocationListener);
+ }
+ } else {
+ if (mLocationClient.isConnected()) {
+ LocationServices.FusedLocationApi.removeLocationUpdates(mLocationListener);
+ mLocationListener = null;
+ mLocationClient.disconnect();
+ mUserLocation = null;
+ }
+ }
+ }
+
+ private class MyLocationListener implements LocationListener {
+ @Override
+ public void onLocationChanged(Location location) {
+ if (mPaused) {
+ return;
+ }
+ setLocation(location);
+ }
+ }
+
+ // 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);
+ }
+
+ mMarkerCoordinateAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mMarkerCoordinateAnimator.setDuration(1000);
+ mMarkerCoordinateAnimator.addUpdateListener(new MarkerCoordinateAnimatorListener(
+ previousCoordinate, new LatLng(location)
+ ));
+ mMarkerCoordinateAnimator.start();
+
+ mShowDirection = location.hasBearing();
+ if (mShowDirection) {
+ if (mUserLocation != null && mUserLocation.hasBearing()) {
+ mMarkerDirection = mUserLocation.getBearing();
+ }
+ float oldDir = mMarkerDirection;
+ 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();
+ }
+
+ 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);
+ }
+ }
+
+ void updateOnNextFrame() {
+ mMapView.get().update();
+ }
+
+ public void pause() {
+ mPaused = true;
+ toggleGps(false);
+ }
+
+ public void resume() {
+ mPaused = false;
+ if (isEnabled()) {
+ toggleGps(true);
+ }
+ }
+
+ public void setOnMyLocationChangeListener(@Nullable MapView.OnMyLocationChangeListener listener) {
+ mOnMyLocationChangeListener = listener;
+ }
+
+ // public for animator only
+ public float getDirection() {
+ return mMarkerDirection;
+ }
+
+ // public for animator only
+ public void setDirection(float direction) {
+ mMarkerDirection = 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;
+ }
+ }
+}