package com.arman.osmdroidmapsforge.map.overlays; import java.util.LinkedList; import org.osmdroid.DefaultResourceProxyImpl; import org.osmdroid.ResourceProxy; import org.osmdroid.SensorEventListenerProxy; import org.osmdroid.api.IMapController; import org.osmdroid.api.IMapView; import org.osmdroid.api.IMyLocationOverlay; import org.osmdroid.bonuspack.overlays.InfoWindow; import org.osmdroid.bonuspack.overlays.Marker; import org.osmdroid.util.GeoPoint; import org.osmdroid.util.TileSystem; import org.osmdroid.views.MapView; import org.osmdroid.views.Projection; import org.osmdroid.views.overlay.IOverlayMenuProvider; import org.osmdroid.views.overlay.Overlay.Snappable; import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider; import org.osmdroid.views.overlay.mylocation.IMyLocationConsumer; import org.osmdroid.views.overlay.mylocation.IMyLocationProvider; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.Picture; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; 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.Bundle; import android.os.Handler; import android.os.Looper; import android.util.FloatMath; import android.view.Display; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.Surface; import android.view.WindowManager; import android.widget.Toast; import com.arman.osmdroidmapsforge.R; public class MyLocationNewOverlay extends Marker implements IMyLocationConsumer, IMyLocationOverlay, IOverlayMenuProvider, Snappable, SensorEventListener { // =========================================================== // Constants // =========================================================== // =========================================================== // Fields // =========================================================== protected final Paint mPaint = new Paint(); protected final Paint mCirclePaint = new Paint(); protected final Bitmap mIconBitmap; protected Drawable mIcon; protected final Bitmap mDirectionArrowBitmap; protected static Drawable mDefaultIcon = null; //cache for default icon (resourceProxy.getDrawable being slow) protected final MapView mMapView; private final IMapController mMapController; public IMyLocationProvider mMyLocationProvider; private final LinkedList<Runnable> mRunOnFirstFix = new LinkedList<Runnable>(); private final Point mMapCoordsProjected = new Point(); private final Point mMapCoordsTranslated = new Point(); private final Handler mHandler; private final Object mHandlerToken = new Object(); 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 float mDirectionArrowCenterX; protected final float 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(); protected float mAnchorU, mAnchorV; protected float mIWAnchorU, mIWAnchorV; protected OnMyLocationNewOverlayClickListener mOnMyLocationNewOverlayClickListener; protected OnMyLocationNewOverlayDragListener mOnMyLocationNewOverlayDragListener; protected Drawable mImage; protected Point mPositionPixels; protected boolean mDraggable, mIsDragged; protected GeoPoint mPosition; protected final Picture mCompassFrame = new Picture(); protected final Picture mCompassRose = new Picture(); private final Matrix mCompassMatrix = new Matrix(); private final Display mDisplay; /** * The bearing, in degrees east of north, or NaN if none has been set. */ private float mAzimuth = Float.NaN; private float mCompassCenterX = 35.0f; private float mCompassCenterY = 35.0f; private final float mCompassRadius = 20.0f; protected final float COMPASS_FRAME_CENTER_X; protected final float COMPASS_FRAME_CENTER_Y; protected final float COMPASS_ROSE_CENTER_X; protected final float COMPASS_ROSE_CENTER_Y; public SensorEventListenerProxy mSensorListener = null; private final SensorManager mSensorManager; /** Usual values in the (U,V) coordinates system of the icon image */ public static final float ANCHOR_CENTER=0.5f, ANCHOR_LEFT=0.0f, ANCHOR_TOP=0.0f, ANCHOR_RIGHT=1.0f, ANCHOR_BOTTOM=1.0f; // =========================================================== // 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(mapView); mMapView = mapView; mMapController = mapView.getController(); mCirclePaint.setARGB(0, 100, 100, 255); mCirclePaint.setAntiAlias(true); mPosition = new GeoPoint(0 ,0); mPositionPixels = new Point(); mAnchorU = ANCHOR_CENTER; mAnchorV = ANCHOR_CENTER; final WindowManager windowManager = (WindowManager) this.mMapView.getContext().getSystemService(Context.WINDOW_SERVICE); mDisplay = windowManager.getDefaultDisplay(); mSensorManager = (SensorManager) this.mMapView.getContext().getSystemService(Context.SENSOR_SERVICE); mPaint.setFilterBitmap(true); if (mDefaultIcon == null) mDefaultIcon = this.mMapView.getContext().getResources().getDrawable(R.drawable.current_pos); mIcon = mDefaultIcon; mIconBitmap = BitmapFactory.decodeResource(this.mMapView.getContext().getResources(), R.drawable.current_pos); mDirectionArrowBitmap = BitmapFactory.decodeResource(this.mMapView.getContext().getResources(), R.drawable.direction_arrow); mDirectionArrowCenterX = mDirectionArrowBitmap.getWidth() / 2.0f - 0.5f; mDirectionArrowCenterY = mDirectionArrowBitmap.getHeight() / 2.0f - 0.5f; // Calculate position of person icon's feet, scaled to screen density mPersonHotspot = new PointF(18.0f * mScale, 18.0f * mScale); mHandler = new Handler(Looper.getMainLooper()); setMyLocationProvider(myLocationProvider); createCompassFramePicture(); createCompassRosePicture(); COMPASS_FRAME_CENTER_X = mCompassFrame.getWidth() / 2 - 0.5f; COMPASS_FRAME_CENTER_Y = mCompassFrame.getHeight() / 2 - 0.5f; COMPASS_ROSE_CENTER_X = mCompassRose.getWidth() / 2 - 0.5f; COMPASS_ROSE_CENTER_Y = mCompassRose.getHeight() / 2 - 0.5f; } private void invalidateCompass() { Rect screenRect = mMapView.getProjection().getScreenRect(); final int frameLeft = screenRect.left + (mMapView.getWidth() / 2) + (int) Math.ceil((mCompassCenterX - COMPASS_FRAME_CENTER_X) * mScale); final int frameTop = screenRect.top + (mMapView.getHeight() / 2) + (int) Math.ceil((mCompassCenterY - COMPASS_FRAME_CENTER_Y) * mScale); final int frameRight = screenRect.left + (mMapView.getWidth() / 2) + (int) Math.ceil((mCompassCenterX + COMPASS_FRAME_CENTER_X) * mScale); final int frameBottom = screenRect.top + (mMapView.getHeight() / 2) + (int) Math.ceil((mCompassCenterY + COMPASS_FRAME_CENTER_Y) * mScale); // Offset by 2 to cover stroke width mMapView.postInvalidate(frameLeft - 2, frameTop - 2, frameRight + 2, frameBottom + 2); } public void setCompassCenter(final float x, final float y) { mCompassCenterX = x; mCompassCenterY = y; } protected void drawCompass(final Canvas canvas, final float bearing, final Rect screenRect) { final float centerX = mCompassCenterX * mScale; final float centerY = mCompassCenterY * mScale + (canvas.getHeight() - mMapView.getHeight()); mCompassMatrix.setTranslate(-COMPASS_FRAME_CENTER_X, -COMPASS_FRAME_CENTER_Y); mCompassMatrix.postTranslate(centerX, centerY); canvas.save(); canvas.setMatrix(mCompassMatrix); canvas.drawPicture(mCompassFrame); mCompassMatrix.setRotate(-bearing, COMPASS_ROSE_CENTER_X, COMPASS_ROSE_CENTER_Y); mCompassMatrix.postTranslate(-COMPASS_ROSE_CENTER_X, -COMPASS_ROSE_CENTER_Y); mCompassMatrix.postTranslate(centerX, centerY); canvas.setMatrix(mCompassMatrix); canvas.drawPicture(mCompassRose); canvas.restore(); } @Override public void onDetach(MapView mapView) { this.disableMyLocation(); super.onDetach(mapView); } public void setInfoWindowAnchor(float anchorU, float anchorV){ mIWAnchorU = anchorU; mIWAnchorV= anchorV; } public void setOnMyLocationNewOverlayClickListener(OnMyLocationNewOverlayClickListener listener){ mOnMyLocationNewOverlayClickListener = listener; } public void setOnMyLocationNewOverlayDragListener(OnMyLocationNewOverlayDragListener listener){ mOnMyLocationNewOverlayDragListener = listener; } /** set an image to be shown in the InfoWindow - this is not the marker icon */ public void setImage(Drawable image){ mImage = image; } /** get the image to be shown in the InfoWindow - this is not the marker icon */ public Drawable getImage(){ return mImage; } /** Set the InfoWindow to be used. * Default is a MyLocationNewOverlayInfoWindow, with the layout named "bonuspack_bubble". * You can use this method either to use your own layout, or to use your own sub-class of InfoWindow. * Note that this InfoWindow will receive the MyLocationNewOverlay object as an input, so it MUST be able to handle MyLocationNewOverlay attributes. * If you don't want any InfoWindow to open, you can set it to null. */ public void setInfoWindow(InfoWindow infoWindow){ mInfoWindow = infoWindow; } public void showInfoWindow(){ if (mInfoWindow == null) return; int markerWidth = 0, markerHeight = 0; markerWidth = mIcon.getIntrinsicWidth(); markerHeight = mIcon.getIntrinsicHeight(); int offsetX = (int)(mIWAnchorU*markerWidth) - (int)(mAnchorU*markerWidth); int offsetY = (int)(mIWAnchorV*markerHeight) - (int)(mAnchorV*markerHeight); mInfoWindow.open(this, mPosition, offsetX, offsetY); } public boolean isInfoWindowShown(){ if (mInfoWindow instanceof CurrentPosInfoWindow){ CurrentPosInfoWindow iw = (CurrentPosInfoWindow)mInfoWindow; return (iw != null) && iw.isOpen() ; } else return super.isInfoWindowOpen(); } @Override public boolean onSingleTapConfirmed(final MotionEvent event, final MapView mapView){ boolean touched = hitTest(event, mapView); if (touched){ if (mOnMyLocationNewOverlayClickListener == null){ return onMyLocationNewOverlayClickDefault(this, mapView); } else { return mOnMyLocationNewOverlayClickListener.onMyLocationNewOverlayClick(this, mapView); } } else return touched; } public boolean hitTest(final MotionEvent event, final MapView mapView){ final Projection pj = mapView.getProjection(); pj.toPixelsFromProjected(mMapCoordsProjected, mPositionPixels); final Rect screenRect = pj.getIntrinsicScreenRect(); int x = -mPositionPixels.x + screenRect.left + (int) event.getX(); int y = -mPositionPixels.y + screenRect.top + (int) event.getY(); boolean hit = mIcon.getBounds().contains(x, y); return hit; // return true; } @Override public boolean onLongPress(final MotionEvent event, final MapView mapView) { boolean touched = hitTest(event, mapView); if (touched){ if (mDraggable){ //starts dragging mode: mIsDragged = true; closeInfoWindow(); if (mOnMyLocationNewOverlayDragListener != null) mOnMyLocationNewOverlayDragListener.onMyLocationNewOverlayDragStart(this); moveToEventPosition(event, mapView); } } return touched; } public void moveToEventPosition(final MotionEvent event, final MapView mapView){ final Projection pj = mapView.getProjection(); mPosition = (GeoPoint) pj.fromPixels((int)event.getX(), (int)event.getY()); mapView.invalidate(); } @Override public boolean onTouchEvent(final MotionEvent event, final MapView mapView) { if (mDraggable && mIsDragged){ if (event.getAction() == MotionEvent.ACTION_UP) { mIsDragged = false; if (mOnMyLocationNewOverlayDragListener != null) mOnMyLocationNewOverlayDragListener.onMyLocationNewOverlayDragEnd(this); return true; } else if (event.getAction() == MotionEvent.ACTION_MOVE){ moveToEventPosition(event, mapView); if (mOnMyLocationNewOverlayDragListener != null) mOnMyLocationNewOverlayDragListener.onMyLocationNewOverlayDrag(this); return true; } else return false; } else return false; } /** default behaviour when no click listener is set */ protected boolean onMyLocationNewOverlayClickDefault(MyLocationNewOverlay marker, MapView mapView) { marker.showInfoWindow(); mapView.getController().animateTo(mPosition); return true; } public interface OnMyLocationNewOverlayClickListener{ abstract boolean onMyLocationNewOverlayClick(MyLocationNewOverlay marker, MapView mapView); } public interface OnMyLocationNewOverlayDragListener{ abstract void onMyLocationNewOverlayDrag(MyLocationNewOverlay marker); abstract void onMyLocationNewOverlayDragEnd(MyLocationNewOverlay marker); abstract void onMyLocationNewOverlayDragStart(MyLocationNewOverlay marker); } // =========================================================== // 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 (isMyLocationEnabled()) stopLocationProvider(); mMyLocationProvider = myLocationProvider; } public void setPersonHotspot(float x, float y) { mPersonHotspot.set(x, y); } protected void drawMyLocation(final Canvas canvas, final MapView mapView, final Location lastFix) { // LocationUpdateService.staticLog("ARMAN", "draw mylocation"); mPosition.setLatitudeE6((int)(lastFix.getLatitude() * 1000000)); mPosition.setLongitudeE6((int) (lastFix.getLongitude() * 1000000)); super.setPosition(mPosition); final Projection pj = mapView.getProjection(); pj.toPixelsFromProjected(mMapCoordsProjected, mMapCoordsTranslated); pj.toPixelsFromProjected(mMapCoordsProjected, mPositionPixels); int width = mIcon.getIntrinsicWidth(); int height = mIcon.getIntrinsicHeight(); Rect rect = new Rect(0, 0, width, height); rect.offset(-(int)(mAnchorU * width), -(int)(mAnchorV * height)); mIcon.setBounds(rect); if (mDrawAccuracyEnabled) { final float radius = lastFix.getAccuracy() / (float) TileSystem.GroundResolution(lastFix.getLatitude(), mapView.getZoomLevel()); mCirclePaint.setAlpha(50); mCirclePaint.setStyle(Style.FILL); canvas.drawCircle(mMapCoordsTranslated.x, mMapCoordsTranslated.y, radius, mCirclePaint); mCirclePaint.setAlpha(150); mCirclePaint.setStyle(Style.STROKE); canvas.drawCircle(mMapCoordsTranslated.x, mMapCoordsTranslated.y, 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]); if (lastFix.hasBearing()) { canvas.save(); // Rotate the icon canvas.rotate(lastFix.getBearing(), mMapCoordsTranslated.x, mMapCoordsTranslated.y); // Counteract any scaling that may be happening so the icon stays the same size canvas.scale(1 / scaleX, 1 / scaleY, mMapCoordsTranslated.x, mMapCoordsTranslated.y); // Draw the bitmap canvas.drawBitmap(mDirectionArrowBitmap, mMapCoordsTranslated.x - mDirectionArrowCenterX, mMapCoordsTranslated.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(), mMapCoordsTranslated.x, mMapCoordsTranslated.y); // Counteract any scaling that may be happening so the icon stays the same size canvas.scale(1 / scaleX, 1 / scaleY, mMapCoordsTranslated.x, mMapCoordsTranslated.y); // Draw the bitmap canvas.drawBitmap(mIconBitmap, mMapCoordsTranslated.x - mPersonHotspot.x, mMapCoordsTranslated.y - mPersonHotspot.y, mPaint); canvas.restore(); } } protected Rect getMyLocationDrawingBounds(int zoomLevel, Location lastFix, Rect reuse) { if (reuse == null) reuse = new Rect(); final Projection pj = mMapView.getProjection(); pj.toPixelsFromProjected(mMapCoordsProjected, mMapCoordsTranslated); // 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(mMapCoordsTranslated.x, mMapCoordsTranslated.y, mMapCoordsTranslated.x + widestEdge, mMapCoordsTranslated.y + widestEdge); reuse.offset(-widestEdge / 2, -widestEdge / 2); } else { reuse.set(mMapCoordsTranslated.x, mMapCoordsTranslated.y, mMapCoordsTranslated.x + mIconBitmap.getWidth(), mMapCoordsTranslated.y + mIconBitmap.getHeight()); reuse.offset((int) (-mPersonHotspot.x + 0.5f), (int) (-mPersonHotspot.y + 0.5f)); } // Add in the accuracy circle if enabled if (mDrawAccuracyEnabled) { final int radius = (int) java.lang.Math.ceil(lastFix.getAccuracy() / (float) TileSystem.GroundResolution(lastFix.getLatitude(), zoomLevel)); reuse.union(mMapCoordsTranslated.x - radius, mMapCoordsTranslated.y - radius, mMapCoordsTranslated.x + radius, mMapCoordsTranslated.y + radius); final int strokeWidth = (int) java.lang.Math.ceil(mCirclePaint.getStrokeWidth() == 0 ? 1 : mCirclePaint.getStrokeWidth()); reuse.inset(-strokeWidth, -strokeWidth); } return reuse; } /** Sets the icon for the marker. Can be changed at any time. * @param icon if null, the default osmdroid marker is used. */ public void setIcon(Drawable icon){ if (icon != null) mIcon = icon; else mIcon = mDefaultIcon; } // =========================================================== // Methods from SuperClass/Interfaces // =========================================================== @Override public void draw(final Canvas c, MapView mapView, boolean shadow) { if (shadow) return; if (mLocation != null && isMyLocationEnabled()) { drawMyLocation(c, mapView, mLocation); } if (isCompassEnabled() && !Float.isNaN(mAzimuth)) { drawCompass(c, mAzimuth + getDisplayOrientation(), mapView.getProjection() .getScreenRect()); } } @Override public void onAccuracyChanged(final Sensor arg0, final int arg1) { // This is not interesting for us at the moment } @Override public void onSensorChanged(final SensorEvent event) { if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) { if (event.values != null) { mAzimuth = event.values[0]; this.invalidateCompass(); } } } /** * Enable orientation sensor (compass) updates and show a compass on the map. You will want to * call enableCompass() probably from your Activity's Activity.onResume() method, to enable the * features of this overlay. Remember to call the corresponding disableCompass() in your * Activity's Activity.onPause() method to turn off updates when in the background. */ public boolean enableCompass() { boolean result = true; if (mSensorListener == null) { mSensorListener = new SensorEventListenerProxy(mSensorManager); result = mSensorListener.startListening(this, Sensor.TYPE_ORIENTATION, SensorManager.SENSOR_DELAY_UI); } // Update the screen to see changes take effect if (mMapView != null) { this.invalidateCompass(); } return result; } /** * Disable orientation updates */ @Override public void disableCompass() { if (mSensorListener != null) { mSensorListener.stopListening(); } // Reset values mSensorListener = null; mAzimuth = Float.NaN; // Update the screen to see changes take effect if (mMapView != null) { this.invalidateCompass(); } } /** * If enabled, the map is receiving orientation updates and drawing your location on the map. * * @return true if enabled, false otherwise */ @Override public boolean isCompassEnabled() { return mSensorListener != null; } @Override public float getOrientation() { return mAzimuth; } @Override public boolean onSnapToItem(final int x, final int y, final Point snapPoint, final IMapView mapView) { if (this.mLocation != null) { Projection pj = mMapView.getProjection(); pj.toPixelsFromProjected(mMapCoordsProjected, mMapCoordsTranslated); snapPoint.x = mMapCoordsTranslated.x; snapPoint.y = mMapCoordsTranslated.y; final double xDiff = x - mMapCoordsTranslated.x; final double yDiff = y - mMapCoordsTranslated.y; boolean snap = xDiff * xDiff + yDiff * yDiff < 64; // if (DEBUGMODE) { // logger.debug("snap=" + snap); // } return snap; } else { return false; } } private int getDisplayOrientation() { switch (mDisplay.getOrientation()) { case Surface.ROTATION_90: return 90; case Surface.ROTATION_180: return 180; case Surface.ROTATION_270: return 270; default: return 0; } } @Override public void onStatusChanged(final String provider, final int status, final Bundle extras) { } // @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()) { Location location = mMyLocationProvider.getLastKnownLocation(); if (location != null) { setLocation(location); } } // Update the screen to see changes take effect if (mMapView != null) { mMapView.postInvalidate(); } } public void tryUpdateCurrentLocation() { Location location = mMyLocationProvider.getLastKnownLocation(); if (location != null) { setLocation(location); } else Toast.makeText(mMapView.getContext(), "Searching for your location", Toast.LENGTH_LONG).show(); } /** * 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(final Location location, IMyLocationProvider source) { if (location != null) { // These location updates can come in from different threads mHandler.postAtTime(new Runnable() { @Override public void run() { setLocation(location); for (final Runnable runnable : mRunOnFirstFix) { new Thread(runnable).start(); } mRunOnFirstFix.clear(); } }, mHandlerToken, 0); } } protected void setLocation(Location location) { // 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; // Cache location point mMapView.getProjection().toProjectedPixels((int) (mLocation.getLatitude() * 1E6), (int) (mLocation.getLongitude() * 1E6), mMapCoordsProjected); if (mIsFollowing) { mGeoPoint.setLatitudeE6((int) (mLocation.getLatitude() * 1E6)); mGeoPoint.setLongitudeE6((int) (mLocation.getLongitude() * 1E6)); // mMapController.animateTo(mGeoPoint); changed by Arman to: mMapController.setCenter(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.invalidateMapCoordinates(left, top, right, bottom); } } public boolean enableMyLocation(IMyLocationProvider myLocationProvider) { // Set the location provider. This will call stopLocationProvider(). setMyLocationProvider(myLocationProvider); tryUpdateCurrentLocation(); boolean success = mMyLocationProvider.startLocationProvider(this); mIsLocationEnabled = success; // set initial location when enabled if (success) { Location location = mMyLocationProvider.getLastKnownLocation(); if (location != null) { setLocation(location); } } // Update the screen to see changes take effect if (mMapView != null) { mMapView.postInvalidate(); } return success; } /** * 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() { return enableMyLocation(mMyLocationProvider); } /** * Disable location updates */ public void disableMyLocation() { mIsLocationEnabled = false; stopLocationProvider(); // Update the screen to see changes take effect if (mMapView != null) { mMapView.postInvalidate(); } } protected void stopLocationProvider() { if (mMyLocationProvider != null) { mMyLocationProvider.stopLocationProvider(); } mHandler.removeCallbacksAndMessages(mHandlerToken); } /** * 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; } /** * Queues a runnable to be executed as soon as we have a location fix. If we already have a fix, * we'll execute the runnable immediately and return true. If not, we'll hang on to the runnable * and return false; as soon as we get a location fix, we'll run it in in a new thread. */ public boolean runOnFirstFix(final Runnable runnable) { if (mMyLocationProvider != null && mLocation != null) { new Thread(runnable).start(); return true; } else { mRunOnFirstFix.addLast(runnable); return false; } } // =========================================================== // Inner and Anonymous Classes // =========================================================== private Point calculatePointOnCircle(final float centerX, final float centerY, final float radius, final float degrees) { // for trigonometry, 0 is pointing east, so subtract 90 // compass degrees are the wrong way round final double dblRadians = Math.toRadians(-degrees + 90); final int intX = (int) (radius * Math.cos(dblRadians)); final int intY = (int) (radius * Math.sin(dblRadians)); return new Point((int) centerX + intX, (int) centerY - intY); } private void drawTriangle(final Canvas canvas, final float x, final float y, final float radius, final float degrees, final Paint paint) { canvas.save(); final Point point = this.calculatePointOnCircle(x, y, radius, degrees); canvas.rotate(degrees, point.x, point.y); final Path p = new Path(); p.moveTo(point.x - 2 * mScale, point.y); p.lineTo(point.x + 2 * mScale, point.y); p.lineTo(point.x, point.y - 5 * mScale); p.close(); canvas.drawPath(p, paint); canvas.restore(); } private void createCompassFramePicture() { // The inside of the compass is white and transparent final Paint innerPaint = new Paint(); innerPaint.setColor(Color.WHITE); innerPaint.setAntiAlias(true); innerPaint.setStyle(Style.FILL); innerPaint.setAlpha(200); // The outer part (circle and little triangles) is gray and transparent final Paint outerPaint = new Paint(); outerPaint.setColor(Color.GRAY); outerPaint.setAntiAlias(true); outerPaint.setStyle(Style.STROKE); outerPaint.setStrokeWidth(2.0f); outerPaint.setAlpha(200); final int picBorderWidthAndHeight = (int) ((mCompassRadius + 5) * 2); final int center = picBorderWidthAndHeight / 2; final Canvas canvas = mCompassFrame.beginRecording(picBorderWidthAndHeight, picBorderWidthAndHeight); // draw compass inner circle and border canvas.drawCircle(center, center, mCompassRadius * mScale, innerPaint); canvas.drawCircle(center, center, mCompassRadius * mScale, outerPaint); // Draw little triangles north, south, west and east (don't move) // to make those move use "-bearing + 0" etc. (Note: that would mean to draw the triangles // in the onDraw() method) drawTriangle(canvas, center, center, mCompassRadius * mScale, 0, outerPaint); drawTriangle(canvas, center, center, mCompassRadius * mScale, 90, outerPaint); drawTriangle(canvas, center, center, mCompassRadius * mScale, 180, outerPaint); drawTriangle(canvas, center, center, mCompassRadius * mScale, 270, outerPaint); mCompassFrame.endRecording(); } private void createCompassRosePicture() { // Paint design of north triangle (it's common to paint north in red color) final Paint northPaint = new Paint(); northPaint.setColor(0xFFA00000); northPaint.setAntiAlias(true); northPaint.setStyle(Style.FILL); northPaint.setAlpha(220); // Paint design of south triangle (black) final Paint southPaint = new Paint(); southPaint.setColor(Color.BLACK); southPaint.setAntiAlias(true); southPaint.setStyle(Style.FILL); southPaint.setAlpha(220); // Create a little white dot in the middle of the compass rose final Paint centerPaint = new Paint(); centerPaint.setColor(Color.WHITE); centerPaint.setAntiAlias(true); centerPaint.setStyle(Style.FILL); centerPaint.setAlpha(220); // final int picBorderWidthAndHeight = (int) ((mCompassRadius + 5) * 2 * mScale); final int picBorderWidthAndHeight = (int) ((mCompassRadius + 5) * 2); final int center = picBorderWidthAndHeight / 2; final Canvas canvas = mCompassRose.beginRecording(picBorderWidthAndHeight, picBorderWidthAndHeight); // Blue triangle pointing north final Path pathNorth = new Path(); pathNorth.moveTo(center, center - (mCompassRadius - 3) * mScale); pathNorth.lineTo(center + 4 * mScale, center); pathNorth.lineTo(center - 4 * mScale, center); pathNorth.lineTo(center, center - (mCompassRadius - 3) * mScale); pathNorth.close(); canvas.drawPath(pathNorth, northPaint); // Red triangle pointing south final Path pathSouth = new Path(); pathSouth.moveTo(center, center + (mCompassRadius - 3) * mScale); pathSouth.lineTo(center + 4 * mScale, center); pathSouth.lineTo(center - 4 * mScale, center); pathSouth.lineTo(center, center + (mCompassRadius - 3) * mScale); pathSouth.close(); canvas.drawPath(pathSouth, southPaint); // Draw a little white dot in the middle canvas.drawCircle(center, center, 2, centerPaint); mCompassRose.endRecording(); } }