package com.mutu.mapapi.views.overlay.mylocation;
import java.util.LinkedList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mutu.mapapi.DefaultResourceProxyImpl;
import com.mutu.mapapi.ResourceProxy;
import com.mutu.mapapi.api.IMapController;
import com.mutu.mapapi.api.IMapView;
import com.mutu.mapapi.tilesystem.TileSystem;
import com.mutu.mapapi.util.GeoPoint;
import com.mutu.mapapi.views.MapView;
import com.mutu.mapapi.views.MapView.Projection;
import com.mutu.mapapi.views.overlay.IOverlayMenuProvider;
import com.mutu.mapapi.views.overlay.SafeDrawOverlay;
import com.mutu.mapapi.views.overlay.Overlay.Snappable;
import com.mutu.mapapi.views.safecanvas.ISafeCanvas;
import com.mutu.mapapi.views.safecanvas.SafePaint;
import com.mutu.mapapi.views.util.constants.MapViewConstants;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Paint.Style;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.location.Location;
import android.util.FloatMath;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
/**
*
* @author Marc Kurtz
* @author Manuel Stahl
*
*/
public class MyLocationNewOverlay extends SafeDrawOverlay implements IMyLocationConsumer,
IOverlayMenuProvider, Snappable {
private static final Logger logger = LoggerFactory.getLogger(MyLocationNewOverlay.class);
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
protected final SafePaint mPaint = new SafePaint();
protected final SafePaint mCirclePaint = new SafePaint();
protected final Bitmap mPersonBitmap;
protected final Bitmap mDirectionArrowBitmap;
protected final MapView mMapView;
private final IMapController mMapController;
public IMyLocationProvider mMyLocationProvider;
private final LinkedList<Runnable> mRunOnFirstFix = new LinkedList<Runnable>();
private final Point mMapCoords = new Point();
private Location mLocation;
private final GeoPoint mGeoPoint = new GeoPoint(0, 0); // for reuse
private boolean mIsLocationEnabled = false;
protected boolean mIsFollowing = false; // follow location updates
protected boolean mDrawAccuracyEnabled = true;
/** Coordinates the feet of the person are located scaled for display density. */
protected final PointF mPersonHotspot;
protected final double mDirectionArrowCenterX;
protected final double mDirectionArrowCenterY;
public static final int MENU_MY_LOCATION = getSafeMenuId();
private boolean mOptionsMenuEnabled = true;
// to avoid allocations during onDraw
private final float[] mMatrixValues = new float[9];
private final Matrix mMatrix = new Matrix();
private final Rect mMyLocationRect = new Rect();
private final Rect mMyLocationPreviousRect = new Rect();
// ===========================================================
// Constructors
// ===========================================================
public MyLocationNewOverlay(Context context, MapView mapView) {
this(context, new GpsMyLocationProvider(context), mapView);
}
public MyLocationNewOverlay(Context context, IMyLocationProvider myLocationProvider,
MapView mapView) {
this(myLocationProvider, mapView, new DefaultResourceProxyImpl(context));
}
public MyLocationNewOverlay(IMyLocationProvider myLocationProvider, MapView mapView,
ResourceProxy resourceProxy) {
super(resourceProxy);
mMapView = mapView;
mMapController = mapView.getController();
mCirclePaint.setARGB(0, 100, 100, 255);
mCirclePaint.setAntiAlias(true);
mPersonBitmap = mResourceProxy.getBitmap(ResourceProxy.bitmap.person);
mDirectionArrowBitmap = mResourceProxy.getBitmap(ResourceProxy.bitmap.direction_arrow);
mDirectionArrowCenterX = mDirectionArrowBitmap.getWidth() / 2.0 - 0.5;
mDirectionArrowCenterY = mDirectionArrowBitmap.getHeight() / 2.0 - 0.5;
// Calculate position of person icon's feet, scaled to screen density
mPersonHotspot = new PointF(24.0f * mScale + 0.5f, 39.0f * mScale + 0.5f);
setMyLocationProvider(myLocationProvider);
}
@Override
public void onDetach(MapView mapView) {
this.disableMyLocation();
super.onDetach(mapView);
}
// ===========================================================
// Getter & Setter
// ===========================================================
/**
* 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 IMyLocationProvider getMyLocationProvider() {
return mMyLocationProvider;
}
protected void setMyLocationProvider(IMyLocationProvider myLocationProvider) {
if (myLocationProvider == null)
throw new RuntimeException(
"You must pass an IMyLocationProvider to setMyLocationProvider()");
if (mMyLocationProvider != null)
mMyLocationProvider.stopLocationProvider();
mMyLocationProvider = myLocationProvider;
}
public void setPersonHotspot(float x, float y) {
mPersonHotspot.set(x, y);
}
protected void drawMyLocation(final ISafeCanvas canvas, final MapView mapView,
final Location lastFix) {
final Projection pj = mapView.getProjection();
final int zoomDiff = MapViewConstants.MAXIMUM_ZOOMLEVEL - pj.getZoomLevel();
if (mDrawAccuracyEnabled) {
TileSystem tileSystem = mMapView.getTileProvider().getTileSource().getTileSystem();
final float radius = lastFix.getAccuracy()
/ (float) tileSystem.GroundResolution(lastFix.getLatitude(),
mapView.getZoomLevel());
mCirclePaint.setAlpha(50);
mCirclePaint.setStyle(Style.FILL);
canvas.drawCircle(mMapCoords.x >> zoomDiff, mMapCoords.y >> zoomDiff, radius,
mCirclePaint);
mCirclePaint.setAlpha(150);
mCirclePaint.setStyle(Style.STROKE);
canvas.drawCircle(mMapCoords.x >> zoomDiff, mMapCoords.y >> zoomDiff, radius,
mCirclePaint);
}
canvas.getMatrix(mMatrix);
mMatrix.getValues(mMatrixValues);
if (DEBUGMODE) {
final float tx = (-mMatrixValues[Matrix.MTRANS_X] + 20)
/ mMatrixValues[Matrix.MSCALE_X];
final float ty = (-mMatrixValues[Matrix.MTRANS_Y] + 90)
/ mMatrixValues[Matrix.MSCALE_Y];
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);
}
// Calculate real scale including accounting for rotation
float scaleX = (float) Math.sqrt(mMatrixValues[Matrix.MSCALE_X]
* mMatrixValues[Matrix.MSCALE_X] + mMatrixValues[Matrix.MSKEW_Y]
* mMatrixValues[Matrix.MSKEW_Y]);
float scaleY = (float) Math.sqrt(mMatrixValues[Matrix.MSCALE_Y]
* mMatrixValues[Matrix.MSCALE_Y] + mMatrixValues[Matrix.MSKEW_X]
* mMatrixValues[Matrix.MSKEW_X]);
final double x = mMapCoords.x >> zoomDiff;
final double y = mMapCoords.y >> zoomDiff;
if (lastFix.hasBearing()) {
canvas.save();
// Rotate the icon
canvas.rotate(lastFix.getBearing(), x, y);
// Counteract any scaling that may be happening so the icon stays the same size
canvas.scale(1 / scaleX, 1 / scaleY, x, y);
// Draw the bitmap
canvas.drawBitmap(mDirectionArrowBitmap, x - mDirectionArrowCenterX, y
- mDirectionArrowCenterY, mPaint);
canvas.restore();
} else {
canvas.save();
// Unrotate the icon if the maps are rotated so the little man stays upright
canvas.rotate(-mMapView.getMapOrientation(), x, y);
// Counteract any scaling that may be happening so the icon stays the same size
canvas.scale(1 / scaleX, 1 / scaleY, x, y);
// Draw the bitmap
canvas.drawBitmap(mPersonBitmap, x - mPersonHotspot.x, y - mPersonHotspot.y, mPaint);
canvas.restore();
}
}
protected Rect getMyLocationDrawingBounds(int zoomLevel, Location lastFix, Rect reuse) {
if (reuse == null)
reuse = new Rect();
final int zoomDiff = MapViewConstants.MAXIMUM_ZOOMLEVEL - zoomLevel;
final int posX = mMapCoords.x >> zoomDiff;
final int posY = mMapCoords.y >> zoomDiff;
// Start with the bitmap bounds
if (lastFix.hasBearing()) {
// Get a square bounding box around the object, and expand by the length of the diagonal
// so as to allow for extra space for rotating
int widestEdge = (int) Math.ceil(Math.max(mDirectionArrowBitmap.getWidth(),
mDirectionArrowBitmap.getHeight()) * Math.sqrt(2));
reuse.set(posX, posY, posX + widestEdge, posY + widestEdge);
reuse.offset((int) -widestEdge / 2, (int) -widestEdge / 2);
} else {
reuse.set(posX, posY, posX + mPersonBitmap.getWidth(), posY + mPersonBitmap.getHeight());
reuse.offset((int) (-mPersonHotspot.x + 0.5f), (int) (-mPersonHotspot.y + 0.5f));
}
// Add in the accuracy circle if enabled
if (mDrawAccuracyEnabled) {
TileSystem tileSystem = mMapView.getTileProvider().getTileSource().getTileSystem();
final int radius = (int) FloatMath.ceil(lastFix.getAccuracy()
/ (float) tileSystem.GroundResolution(lastFix.getLatitude(), zoomLevel));
reuse.union(posX - radius, posY - radius, posX + radius, posY + radius);
final int strokeWidth = (int) FloatMath.ceil(mCirclePaint.getStrokeWidth() == 0 ? 1
: mCirclePaint.getStrokeWidth());
reuse.inset(-strokeWidth, -strokeWidth);
}
return reuse;
}
// ===========================================================
// Methods from SuperClass/Interfaces
// ===========================================================
@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 IMapView mapView) {
if (this.mLocation != null) {
snapPoint.x = mMapCoords.x;
snapPoint.y = mMapCoords.y;
final double xDiff = x - mMapCoords.x;
final double yDiff = y - mMapCoords.y;
final boolean snap = xDiff * xDiff + yDiff * yDiff < 64;
if (DEBUGMODE) {
logger.debug("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);
}
// ===========================================================
// Menu handling methods
// ===========================================================
@Override
public void setOptionsMenuEnabled(final boolean pOptionsMenuEnabled) {
this.mOptionsMenuEnabled = pOptionsMenuEnabled;
}
@Override
public boolean isOptionsMenuEnabled() {
return this.mOptionsMenuEnabled;
}
@Override
public boolean onCreateOptionsMenu(final Menu pMenu, final int pMenuIdOffset,
final MapView pMapView) {
pMenu.add(0, MENU_MY_LOCATION + pMenuIdOffset, Menu.NONE,
mResourceProxy.getString(ResourceProxy.string.my_location))
.setIcon(mResourceProxy.getDrawable(ResourceProxy.bitmap.ic_menu_mylocation))
.setCheckable(true);
return true;
}
@Override
public boolean onPrepareOptionsMenu(final Menu pMenu, final int pMenuIdOffset,
final MapView pMapView) {
pMenu.findItem(MENU_MY_LOCATION + pMenuIdOffset).setChecked(this.isMyLocationEnabled());
return false;
}
@Override
public boolean onOptionsItemSelected(final MenuItem pItem, final int pMenuIdOffset,
final MapView pMapView) {
final int menuId = pItem.getItemId() - pMenuIdOffset;
if (menuId == MENU_MY_LOCATION) {
if (this.isMyLocationEnabled()) {
this.disableFollowLocation();
this.disableMyLocation();
} else {
this.enableFollowLocation();
this.enableMyLocation();
}
return true;
} else {
return false;
}
}
// ===========================================================
// Methods
// ===========================================================
/**
* Return a GeoPoint of the last known location, or null if not known.
*/
public GeoPoint getMyLocation() {
if (mLocation == null) {
return null;
} else {
return new GeoPoint(mLocation);
}
}
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() {
mIsFollowing = true;
// set initial location when enabled
if (isMyLocationEnabled()) {
mLocation = mMyLocationProvider.getLastKnownLocation();
if (mLocation != null) {
TileSystem tileSystem = mMapView.getTileProvider().getTileSource().getTileSystem();
tileSystem.LatLongToPixelXY(mLocation.getLatitude(), mLocation.getLongitude(),
MapViewConstants.MAXIMUM_ZOOMLEVEL, mMapCoords);
final int worldWidthSize_2 = tileSystem.MapWidthPixelSize(MapViewConstants.MAXIMUM_ZOOMLEVEL) / 2;
final int worldHeigthSize_2 = tileSystem.MapHeigthPixelSize(MapViewConstants.MAXIMUM_ZOOMLEVEL) / 2;
mMapCoords.offset(-worldWidthSize_2, -worldHeigthSize_2);
mMapController.animateTo(new GeoPoint(mLocation));
}
}
// Update the screen to see changes take effect
if (mMapView != null) {
mMapView.postInvalidate();
}
}
/**
* Disables "follow" functionality.
*/
public void disableFollowLocation() {
mIsFollowing = false;
}
/**
* 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 mIsFollowing;
}
@Override
public void onLocationChanged(Location location, IMyLocationProvider source) {
// If we had a previous location, let's get those bounds
Location oldLocation = mLocation;
if (oldLocation != null) {
this.getMyLocationDrawingBounds(mMapView.getZoomLevel(), oldLocation,
mMyLocationPreviousRect);
}
mLocation = location;
mMapCoords.set(0, 0);
if (mLocation != null) {
TileSystem tileSystem = mMapView.getTileProvider().getTileSource().getTileSystem();
tileSystem.LatLongToPixelXY(mLocation.getLatitude(), mLocation.getLongitude(),
MapViewConstants.MAXIMUM_ZOOMLEVEL, mMapCoords);
final int worldWidthSize_2 = tileSystem.MapWidthPixelSize(MapViewConstants.MAXIMUM_ZOOMLEVEL) / 2;
final int worldHeigthSize_2 = tileSystem.MapHeigthPixelSize(MapViewConstants.MAXIMUM_ZOOMLEVEL) / 2;
mMapCoords.offset(-worldWidthSize_2, -worldHeigthSize_2);
if (mIsFollowing) {
mGeoPoint.setLatitudeE6((int) (mLocation.getLatitude() * 1E6));
mGeoPoint.setLongitudeE6((int) (mLocation.getLongitude() * 1E6));
mMapController.animateTo(mGeoPoint);
} else {
// Get new drawing bounds
this.getMyLocationDrawingBounds(mMapView.getZoomLevel(), mLocation, mMyLocationRect);
// If we had a previous location, merge in those bounds too
if (oldLocation != null) {
mMyLocationRect.union(mMyLocationPreviousRect);
}
final int left = mMyLocationRect.left;
final int top = mMyLocationRect.top;
final int right = mMyLocationRect.right;
final int bottom = mMyLocationRect.bottom;
// Invalidate the bounds
mMapView.post(new Runnable() {
@Override
public void run() {
mMapView.invalidateMapCoordinates(left, top, right, bottom);
}
});
}
}
for (final Runnable runnable : mRunOnFirstFix) {
new Thread(runnable).start();
}
mRunOnFirstFix.clear();
}
public boolean enableMyLocation(IMyLocationProvider myLocationProvider) {
this.setMyLocationProvider(myLocationProvider);
mIsLocationEnabled = false;
return enableMyLocation();
}
/**
* Enable receiving location updates from the provided IMyLocationProvider 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() {
boolean result = true;
if (mIsLocationEnabled)
mMyLocationProvider.stopLocationProvider();
result = mMyLocationProvider.startLocationProvider(this);
mIsLocationEnabled = result;
// set initial location when enabled
if (result && isFollowLocationEnabled()) {
mLocation = mMyLocationProvider.getLastKnownLocation();
if (mLocation != null) {
TileSystem tileSystem = mMapView.getTileProvider().getTileSource().getTileSystem();
tileSystem.LatLongToPixelXY(mLocation.getLatitude(), mLocation.getLongitude(),
MapViewConstants.MAXIMUM_ZOOMLEVEL, mMapCoords);
final int worldWidthSize_2 = tileSystem.MapWidthPixelSize(MapViewConstants.MAXIMUM_ZOOMLEVEL) / 2;
final int worldHeigthSize_2 = tileSystem.MapHeigthPixelSize(MapViewConstants.MAXIMUM_ZOOMLEVEL) / 2;
mMapCoords.offset(-worldWidthSize_2, -worldHeigthSize_2);
mMapController.animateTo(new GeoPoint(mLocation));
}
}
// Update the screen to see changes take effect
if (mMapView != null) {
mMapView.postInvalidate();
}
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 {
mRunOnFirstFix.addLast(runnable);
return false;
}
}
}