com.mapbox.mapboxsdk.overlay.UserLocationOverlay Maven / Gradle / Ivy
package com.mapbox.mapboxsdk.overlay;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Paint.Style;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.location.Location;
import android.util.Log;
import android.view.MotionEvent;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.events.MapListener;
import com.mapbox.mapboxsdk.events.RotateEvent;
import com.mapbox.mapboxsdk.events.ScrollEvent;
import com.mapbox.mapboxsdk.events.ZoomEvent;
import com.mapbox.mapboxsdk.geometry.BoundingBox;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.overlay.Overlay.Snappable;
import com.mapbox.mapboxsdk.util.constants.UtilConstants;
import com.mapbox.mapboxsdk.views.MapController;
import com.mapbox.mapboxsdk.views.MapView;
import com.mapbox.mapboxsdk.views.safecanvas.ISafeCanvas;
import com.mapbox.mapboxsdk.views.safecanvas.SafePaint;
import com.mapbox.mapboxsdk.views.util.Projection;
import java.util.LinkedList;
/**
* @author Marc Kurtz
* @author Manuel Stahl
*/
public class UserLocationOverlay extends SafeDrawOverlay implements Snappable, MapListener {
public enum TrackingMode {
NONE, FOLLOW, FOLLOW_BEARING
}
private final SafePaint mPaint = new SafePaint();
private final SafePaint mCirclePaint = new SafePaint();
protected final MapView mMapView;
protected final Context mContext;
private final MapController mMapController;
public GpsLocationProvider mMyLocationProvider;
private final LinkedList mRunOnFirstFix = new LinkedList();
private final PointF mMapCoords = new PointF();
private Location mLocation;
private LatLng mLatLng;
private boolean mIsLocationEnabled = false;
private boolean mDrawAccuracyEnabled = true;
private TrackingMode mTrackingMode = TrackingMode.NONE;
private boolean mZoomBasedOnAccuracy = true;
private float mRequiredZoomLevel = 10;
/**
* Coordinates the feet of the person are located scaled for display density.
*/
// to avoid allocations during onDraw
private final RectF mMyLocationRect = new RectF();
private final RectF mMyLocationPreviousRect = new RectF();
private Bitmap mPersonBitmap;
private Bitmap mDirectionArrowBitmap;
private PointF mPersonHotspot;
private PointF mDirectionHotspot;
public void setDirectionArrowBitmap(Bitmap bitmap) {
mDirectionArrowBitmap = bitmap;
}
public void setPersonBitmap(Bitmap bitmap) {
mPersonBitmap = bitmap;
}
public void setDirectionArrowHotspot(PointF point) {
mDirectionHotspot = point;
}
public void setPersonHotspot(PointF point) {
mPersonHotspot = point;
}
public void setOverlayCircleColor(int newColor) {
mCirclePaint.setColor(newColor);
}
public UserLocationOverlay(GpsLocationProvider myLocationProvider, MapView mapView, int arrowId, int personId) {
mMapView = mapView;
mMapController = mapView.getController();
mContext = mapView.getContext();
mCirclePaint.setColor(0x776464FF);
mCirclePaint.setAntiAlias(true);
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
mPersonHotspot = new PointF(0.5f, 0.5f);
mDirectionHotspot = new PointF(0.5f, 0.5f);
if (personId != 0) {
mPersonBitmap = BitmapFactory.decodeResource(mContext.getResources(), personId);
}
if (arrowId != 0) {
mDirectionArrowBitmap = BitmapFactory.decodeResource(mContext.getResources(), arrowId);
}
setMyLocationProvider(myLocationProvider);
setOverlayIndex(USERLOCATIONOVERLAY_INDEX);
}
public UserLocationOverlay(GpsLocationProvider myLocationProvider, MapView mapView) {
this(myLocationProvider, mapView, R.drawable.direction_arrow, R.drawable.location_marker);
}
@Override
public void onDetach(MapView mapView) {
this.disableMyLocation();
super.onDetach(mapView);
}
/**
* If enabled, an accuracy circle will be drawn around your current position.
*
* @param drawAccuracyEnabled whether the accuracy circle will be enabled
*/
public void setDrawAccuracyEnabled(final boolean drawAccuracyEnabled) {
mDrawAccuracyEnabled = drawAccuracyEnabled;
}
/**
* If enabled, an accuracy circle will be drawn around your current position.
*
* @return true if enabled, false otherwise
*/
public boolean isDrawAccuracyEnabled() {
return mDrawAccuracyEnabled;
}
public GpsLocationProvider getMyLocationProvider() {
return mMyLocationProvider;
}
protected void setMyLocationProvider(GpsLocationProvider myLocationProvider) {
if (mMyLocationProvider != null) {
mMyLocationProvider.stopLocationProvider();
}
mMyLocationProvider = myLocationProvider;
}
protected void drawMyLocation(final ISafeCanvas canvas, final MapView mapView, final Location lastFix) {
final Rect mapBounds = new Rect(0, 0, mapView.getMeasuredWidth(), mapView.getMeasuredHeight());
final Projection projection = mapView.getProjection();
Rect rect = new Rect();
getDrawingBounds(projection, lastFix, null).round(rect);
if (!Rect.intersects(mapBounds, rect)) {
//dont draw item if offscreen
return;
}
projection.toMapPixels(mLatLng, mMapCoords);
final float mapScale = 1 / mapView.getScale();
canvas.save();
canvas.scale(mapScale, mapScale, mMapCoords.x, mMapCoords.y);
if (mDrawAccuracyEnabled) {
final float radius = lastFix.getAccuracy() / (float) Projection.groundResolution(
lastFix.getLatitude(), mapView.getZoomLevel()) * mapView.getScale();
canvas.save();
// Rotate the icon
canvas.rotate(lastFix.getBearing(), mMapCoords.x, mMapCoords.y);
// Counteract any scaling that may be happening so the icon stays the same size
mCirclePaint.setAlpha(50);
mCirclePaint.setStyle(Style.FILL);
canvas.drawCircle(mMapCoords.x, mMapCoords.y, radius, mCirclePaint);
mCirclePaint.setAlpha(150);
mCirclePaint.setStyle(Style.STROKE);
canvas.drawCircle(mMapCoords.x, mMapCoords.y, radius, mCirclePaint);
canvas.restore();
}
if (UtilConstants.DEBUGMODE) {
final float tx = (mMapCoords.x + 50);
final float ty = (mMapCoords.y - 20);
canvas.drawText("Lat: " + lastFix.getLatitude(), tx, ty + 5, mPaint);
canvas.drawText("Lon: " + lastFix.getLongitude(), tx, ty + 20, mPaint);
canvas.drawText("Alt: " + lastFix.getAltitude(), tx, ty + 35, mPaint);
canvas.drawText("Acc: " + lastFix.getAccuracy(), tx, ty + 50, mPaint);
}
if (lastFix.hasBearing()) {
canvas.save();
// Rotate the icon
canvas.rotate(lastFix.getBearing(), mMapCoords.x, mMapCoords.y);
// Draw the bitmap
canvas.translate(-mDirectionArrowBitmap.getWidth() * mDirectionHotspot.x,
-mDirectionArrowBitmap.getHeight() * mDirectionHotspot.y);
canvas.drawBitmap(mDirectionArrowBitmap, mMapCoords.x, mMapCoords.y, mPaint);
canvas.restore();
} else {
canvas.save();
// Unrotate the icon if the maps are rotated so the little man stays upright
canvas.rotate(-mMapView.getMapOrientation(), mMapCoords.x, mMapCoords.y);
// Counteract any scaling that may be happening so the icon stays the same size
canvas.translate(-mPersonBitmap.getWidth() * mPersonHotspot.x,
-mPersonBitmap.getHeight() * mPersonHotspot.y);
// Draw the bitmap
canvas.drawBitmap(mPersonBitmap, mMapCoords.x, mMapCoords.y, mPaint);
canvas.restore();
}
canvas.restore();
}
public PointF getPositionOnScreen(final Projection projection, PointF reuse) {
if (reuse == null) {
reuse = new PointF();
}
projection.toPixels(mLatLng, reuse);
return reuse;
}
public PointF getDrawingPositionOnScreen(final Projection projection, Location lastFix,
PointF reuse) {
reuse = getPositionOnScreen(projection, reuse);
if (lastFix.hasBearing()) {
reuse.offset(mPersonHotspot.x * mPersonBitmap.getWidth(),
mPersonHotspot.y * mPersonBitmap.getWidth());
} else {
reuse.offset(mDirectionHotspot.x * mDirectionArrowBitmap.getWidth(),
mDirectionHotspot.y * mDirectionArrowBitmap.getWidth());
}
return reuse;
}
protected RectF getDrawingBounds(final Projection projection, Location lastFix, RectF reuse) {
PointF positionOnScreen = getPositionOnScreen(projection, null);
return getDrawingBounds(positionOnScreen, lastFix, reuse);
}
protected RectF getDrawingBounds(PointF positionOnScreen, Location lastFix, RectF reuse) {
if (reuse == null) {
reuse = new RectF();
}
final Bitmap bitmap = lastFix.hasBearing() ? mDirectionArrowBitmap : mPersonBitmap;
final PointF scale = lastFix.hasBearing() ? mDirectionHotspot : mPersonHotspot;
//because of bearing and rotation
final int w = (int) (Math.sqrt(2) * Math.max(bitmap.getWidth(), bitmap.getHeight()));
final float x = positionOnScreen.x - scale.x * w;
final float y = positionOnScreen.y - scale.y * w;
reuse.set(x, y, x + w, y + w);
return reuse;
}
protected RectF getMyLocationMapDrawingBounds(MapView mv, Location lastFix, RectF reuse) {
mv.getProjection().toMapPixels(mLatLng, mMapCoords);
reuse = getDrawingBounds(mMapCoords, lastFix, reuse);
// Add in the accuracy circle if enabled
if (mDrawAccuracyEnabled) {
final float radius = (float) Math.ceil(
lastFix.getAccuracy() / (float) Projection.groundResolution(
lastFix.getLatitude(), mMapView.getZoomLevel())
);
RectF accuracyRect =
new RectF(mMapCoords.x - radius, mMapCoords.y - radius, mMapCoords.x + radius,
mMapCoords.y + radius);
final float strokeWidth = (float) Math.ceil(
mCirclePaint.getStrokeWidth() == 0 ? 1 : mCirclePaint.getStrokeWidth());
accuracyRect.inset(-strokeWidth, -strokeWidth);
reuse.union(accuracyRect);
}
return reuse;
}
@Override
protected void drawSafe(ISafeCanvas canvas, MapView mapView, boolean shadow) {
if (shadow) {
return;
}
if (mLocation != null && isMyLocationEnabled()) {
drawMyLocation(canvas, mapView, mLocation);
}
}
@Override
public boolean onSnapToItem(final int x, final int y, final Point snapPoint,
final MapView mapView) {
if (!isFollowLocationEnabled() && this.mLocation != null) {
snapPoint.x = (int) mMapCoords.x;
snapPoint.y = (int) mMapCoords.y;
final double xDiff = x - mMapCoords.x;
final double yDiff = y - mMapCoords.y;
final boolean snap = xDiff * xDiff + yDiff * yDiff < 64;
if (UtilConstants.DEBUGMODE) {
Log.d(TAG, "snap=" + snap);
}
return snap;
} else {
return false;
}
}
@Override
public boolean onTouchEvent(final MotionEvent event, final MapView mapView) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
this.disableFollowLocation();
}
return super.onTouchEvent(event, mapView);
}
/**
* Return a LatLng of the last known location, or null if not known.
*/
public LatLng getMyLocation() {
return mLatLng;
}
public Location getLastFix() {
return mLocation;
}
/**
* Enables "follow" functionality. The map will center on your current location and
* automatically scroll as you move. Scrolling the map in the UI will disable.
*/
public void enableFollowLocation() {
if (mTrackingMode == TrackingMode.NONE) {
mTrackingMode = TrackingMode.FOLLOW;
}
// set initial location when enabled
if (isMyLocationEnabled()) {
updateMyLocation(mMyLocationProvider.getLastKnownLocation());
}
}
/**
* Disables "follow" functionality.
*/
public void disableFollowLocation() {
mTrackingMode = TrackingMode.NONE;
}
public void setTrackingMode(TrackingMode mode) {
mTrackingMode = mode;
if (mTrackingMode != TrackingMode.NONE && isMyLocationEnabled()) {
updateMyLocation(mMyLocationProvider.getLastKnownLocation());
}
}
public void setRequiredZoom(final float zoomLevel) {
mRequiredZoomLevel = zoomLevel;
mZoomBasedOnAccuracy = false;
}
public TrackingMode getTrackingMode() {
return mTrackingMode;
}
/**
* If enabled, the map will center on your current location and automatically scroll as you
* move. Scrolling the map in the UI will disable.
*
* @return true if enabled, false otherwise
*/
public boolean isFollowLocationEnabled() {
return mTrackingMode != TrackingMode.NONE;
}
private void updateDrawingPositionRect() {
getMyLocationMapDrawingBounds(mMapView, mLocation, mMyLocationRect);
}
private void invalidate() {
if (mMapView == null) {
return; //not on map yet
}
// Get new drawing bounds
mMyLocationPreviousRect.set(mMyLocationRect);
updateDrawingPositionRect();
final RectF newRect = new RectF(mMyLocationRect);
// If we had a previous location, merge in those bounds too
newRect.union(mMyLocationPreviousRect);
// Invalidate the bounds
mMapView.post(new Runnable() {
@Override
public void run() {
mMapView.invalidateMapCoordinates(newRect);
}
});
}
public void onLocationChanged(Location location, GpsLocationProvider source) {
// If we had a previous location, let's get those bounds
if (mLocation != null && mLocation.getBearing() == location.getBearing() && mLocation.distanceTo(location) == 0) {
return;
}
updateMyLocation(location);
synchronized (mRunOnFirstFix) {
for (final Runnable runnable : mRunOnFirstFix) {
new Thread(runnable).start();
}
mRunOnFirstFix.clear();
}
}
public boolean enableMyLocation(GpsLocationProvider myLocationProvider) {
this.setMyLocationProvider(myLocationProvider);
mIsLocationEnabled = false;
return enableMyLocation();
}
public boolean goToMyPosition(final boolean animated) {
if (mLocation == null) {
return false;
}
float currentZoom = mMapView.getZoomLevel(false);
if (currentZoom <= mRequiredZoomLevel) {
double requiredZoom = mRequiredZoomLevel;
if (mZoomBasedOnAccuracy && mMapView.isLayedOut()) {
double delta = (mLocation.getAccuracy() / 110000) * 1.2; // approx. meter per degree latitude, plus some margin
final Projection projection = mMapView.getProjection();
LatLng desiredSouthWest = new LatLng(mLocation.getLatitude() - delta,
mLocation.getLongitude() - delta);
LatLng desiredNorthEast = new LatLng(mLocation.getLatitude() + delta,
mLocation.getLongitude() + delta);
float pixelRadius = Math.min(mMapView.getMeasuredWidth(), mMapView.getMeasuredHeight()) / 2;
BoundingBox currentBox = projection.getBoundingBox();
if (desiredNorthEast.getLatitude() != currentBox.getLatNorth() ||
desiredNorthEast.getLongitude() != currentBox.getLonEast() ||
desiredSouthWest.getLatitude() != currentBox.getLatSouth() ||
desiredSouthWest.getLongitude() != currentBox.getLonWest()) {
mMapView.zoomToBoundingBox(new BoundingBox(desiredNorthEast, desiredSouthWest), true, animated, true);
}
} else if (animated) {
return mMapController.setZoomAnimated((float) requiredZoom, mLatLng, true, false);
} else {
mMapController.setZoom((float) requiredZoom, mLatLng, false);
}
} else if (animated) {
return mMapController.animateTo(mLatLng);
} else {
return mMapController.goTo(mLatLng, new PointF(0, 0));
}
return true;
}
private void updateMyLocation(final Location location) {
mLocation = location;
if (mLocation == null) {
mLatLng = null;
return;
}
mLatLng = new LatLng(mLocation);
//if goToMyPosition return false, it means we are already there
//which means we have to invalidate ourselves to make sure we are redrawn
if (!isFollowLocationEnabled() || !goToMyPosition(true)) {
invalidate();
}
}
/**
* Enable receiving location updates from the provided GpsLocationProvider and show your
* location on the maps. You will likely want to call enableMyLocation() from your Activity's
* Activity.onResume() method, to enable the features of this overlay. Remember to call the
* corresponding disableMyLocation() in your Activity's Activity.onPause() method to turn off
* updates when in the background.
*/
public boolean enableMyLocation() {
if (mIsLocationEnabled) {
mMyLocationProvider.stopLocationProvider();
}
boolean result = mMyLocationProvider.startLocationProvider(this);
mIsLocationEnabled = result;
// set initial location when enabled
if (result) {
updateMyLocation(mMyLocationProvider.getLastKnownLocation());
}
return result;
}
/**
* Disable location updates
*/
public void disableMyLocation() {
mIsLocationEnabled = false;
if (mMyLocationProvider != null) {
mMyLocationProvider.stopLocationProvider();
}
// Update the screen to see changes take effect
if (mMapView != null) {
mMapView.postInvalidate();
}
}
/**
* If enabled, the map is receiving location updates and drawing your location on the map.
*
* @return true if enabled, false otherwise
*/
public boolean isMyLocationEnabled() {
return mIsLocationEnabled;
}
public boolean runOnFirstFix(final Runnable runnable) {
if (mMyLocationProvider != null && mLocation != null) {
new Thread(runnable).start();
return true;
} else {
synchronized (mRunOnFirstFix) {
mRunOnFirstFix.addLast(runnable);
}
return false;
}
}
private static final String TAG = "UserLocationOverlay";
@Override
public void onScroll(ScrollEvent event) {
if (event.getUserAction()) {
disableFollowLocation();
}
}
@Override
public void onZoom(ZoomEvent event) {
if (event.getUserAction()) {
disableFollowLocation();
}
}
@Override
public void onRotate(RotateEvent event) {
if (event.getUserAction()) {
disableFollowLocation();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy