package org.wheelmap.android.osmdroid; import org.osmdroid.api.IGeoPoint; 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.Overlay; import org.osmdroid.views.util.constants.MapViewConstants; import org.wheelmap.android.online.R; import org.wheelmap.android.utils.ImageUtils; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.location.Location; import android.support.v4.content.ContextCompat; import android.util.FloatMath; import android.util.Log; import java.util.LinkedList; public class MarkItemOverlay extends Overlay { private static final String TAG = MarkItemOverlay.class.getSimpleName(); private static final int MAXIMUM_ZOOMLEVEL = 22; // =========================================================== // Constants // =========================================================== // =========================================================== // Fields // =========================================================== protected Context mContext; protected final Paint mCirclePaint = new Paint(); protected final Bitmap mPersonBitmap; protected final Bitmap mDirectionArrowBitmap; protected final MapView mMapView; private final LinkedList<Runnable> mRunOnFirstFix = new LinkedList<Runnable>(); private final Point mMapCoords = new Point(); private Location mLocation; 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; // 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 MarkItemOverlay(MapView mapView) { this(mapView.getContext(), mapView); } public MarkItemOverlay(Context context,MapView mapView) { super(context); mContext = context; mMapView = mapView; mCirclePaint.setARGB(0, 100, 100, 255); mCirclePaint.setAntiAlias(true); mPersonBitmap = ImageUtils.drawableToBitmap(ContextCompat.getDrawable(mContext, R.drawable.person)); mDirectionArrowBitmap = ImageUtils.drawableToBitmap(ContextCompat.getDrawable(mContext, R.drawable.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); } @Override protected void draw(Canvas canvas, MapView mapView, boolean shadow) { if (shadow) return; Log.d(TAG,"drawSafe "+mLocation+" "+shadow); if (mLocation != null) { drawMyLocation(canvas, mapView, mLocation); } } @Override public void onDetach(MapView mapView) { //this.disableMyLocation(); super.onDetach(mapView); } // =========================================================== // Getter & Setter // =========================================================== protected void drawMyLocation(final Canvas canvas, final MapView mapView, final Location lastFix) { final Projection pj = mapView.getProjection(); final int zoomDiff = MAXIMUM_ZOOMLEVEL - pj.getZoomLevel(); float radius = 10 * mContext.getResources().getDisplayMetrics().density; mCirclePaint.setAlpha(100); mCirclePaint.setStyle(Paint.Style.FILL); canvas.drawCircle(mMapCoords.x >> zoomDiff, mMapCoords.y >> zoomDiff, radius, mCirclePaint); mCirclePaint.setAlpha(150); mCirclePaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(mMapCoords.x >> zoomDiff, mMapCoords.y >> zoomDiff, radius, mCirclePaint); canvas.getMatrix(mMatrix); mMatrix.getValues(mMatrixValues); } protected Rect getMyLocationDrawingBounds(int zoomLevel, Location lastFix, Rect reuse) { if (reuse == null) reuse = new Rect(); final int zoomDiff = 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(-widestEdge / 2, -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) { final int radius = (int) Math.ceil(lastFix.getAccuracy() / (float) TileSystem.GroundResolution(lastFix.getLatitude(), zoomLevel)); reuse.union(posX - radius, posY - radius, posX + radius, posY + radius); final int strokeWidth = (int) Math.ceil(mCirclePaint.getStrokeWidth() == 0 ? 1 : mCirclePaint.getStrokeWidth()); reuse.inset(-strokeWidth, -strokeWidth); } return reuse; } public void setLocation(IGeoPoint geoPoint){ Location location = new Location("gps"); location.setLatitude(geoPoint.getLatitude()); location.setLongitude(geoPoint.getLongitude()); setLocation(location); } public void setLocation(Location location){ mLocation = location; TileSystem.LatLongToPixelXY(mLocation.getLatitude(), mLocation.getLongitude(), MAXIMUM_ZOOMLEVEL, mMapCoords); final int worldSize_2 = TileSystem.MapSize(MAXIMUM_ZOOMLEVEL) / 2; mMapCoords.offset(-worldSize_2, -worldSize_2); // Get new drawing bounds this.getMyLocationDrawingBounds(mMapView.getZoomLevel(), mLocation, mMyLocationRect); // If we had a previous location, merge in those bounds too if (mLocation != 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(); } // =========================================================== // Methods // =========================================================== /** * Return a GeoPoint of the last known location, or null if not known. */ public GeoPoint getLocation() { if (mLocation == null) { return null; } else { return new GeoPoint(mLocation); } } public Location getLastFix() { return mLocation; } }