package net.osmand.plus.views; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.os.Handler; import android.os.Message; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.view.GestureDetector; import android.view.MotionEvent; import net.osmand.Location; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.data.QuadPoint; import net.osmand.data.RotatedTileBox; import net.osmand.plus.MapMarkersHelper; import net.osmand.plus.MapMarkersHelper.MapMarker; import net.osmand.plus.OsmAndConstants; import net.osmand.plus.OsmandSettings; import net.osmand.plus.R; import net.osmand.plus.TargetPointsHelper.TargetPoint; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.views.ContextMenuLayer.ApplyMovedObjectCallback; import net.osmand.plus.views.ContextMenuLayer.IContextMenuProvider; import net.osmand.plus.views.ContextMenuLayer.IContextMenuProviderSelection; import net.osmand.plus.views.mapwidgets.MapMarkersWidgetsFactory; import java.util.ArrayList; import java.util.List; public class MapMarkersLayer extends OsmandMapLayer implements IContextMenuProvider, IContextMenuProviderSelection, ContextMenuLayer.IMoveObjectProvider { protected static final int DIST_TO_SHOW = 80; protected static final long USE_FINGER_LOCATION_DELAY = 1000; private static final int MAP_REFRESH_MESSAGE = OsmAndConstants.UI_HANDLER_MAP_VIEW + 6; private final MapActivity map; private OsmandMapTileView view; private MapMarkersWidgetsFactory widgetsFactory; private Paint bitmapPaint; private Bitmap markerBitmapBlue; private Bitmap markerBitmapGreen; private Bitmap markerBitmapOrange; private Bitmap markerBitmapRed; private Bitmap markerBitmapYellow; private Bitmap markerBitmapTeal; private Bitmap markerBitmapPurple; private Paint bitmapPaintDestBlue; private Paint bitmapPaintDestGreen; private Paint bitmapPaintDestOrange; private Paint bitmapPaintDestRed; private Paint bitmapPaintDestYellow; private Paint bitmapPaintDestTeal; private Paint bitmapPaintDestPurple; private Bitmap arrowToDestination; private float[] calculations = new float[2]; private Paint paint; private Path path; private List<LatLon> route = new ArrayList<>(); private LatLon fingerLocation; private boolean hasMoved; private boolean moving; private boolean useFingerLocation; private GestureDetector longTapDetector; private Handler handler; private ContextMenuLayer contextMenuLayer; public MapMarkersLayer(MapActivity map) { this.map = map; } public MapMarkersWidgetsFactory getWidgetsFactory() { return widgetsFactory; } private void initUI() { bitmapPaint = new Paint(); bitmapPaint.setDither(true); bitmapPaint.setAntiAlias(true); bitmapPaint.setFilterBitmap(true); markerBitmapBlue = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_blue); markerBitmapGreen = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_green); markerBitmapOrange = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_orange); markerBitmapRed = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_red); markerBitmapYellow = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_yellow); markerBitmapTeal = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_teal); markerBitmapPurple = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_purple); arrowToDestination = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_arrow_to_destination); bitmapPaintDestBlue = createPaintDest(R.color.marker_blue); bitmapPaintDestGreen = createPaintDest(R.color.marker_green); bitmapPaintDestOrange = createPaintDest(R.color.marker_orange); bitmapPaintDestRed = createPaintDest(R.color.marker_red); bitmapPaintDestYellow = createPaintDest(R.color.marker_yellow); bitmapPaintDestTeal = createPaintDest(R.color.marker_teal); bitmapPaintDestPurple = createPaintDest(R.color.marker_purple); path = new Path(); paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(7 * view.getDensity()); paint.setAntiAlias(true); paint.setStrokeCap(Paint.Cap.ROUND); paint.setStrokeJoin(Paint.Join.ROUND); paint.setColor(ContextCompat.getColor(map, R.color.marker_red)); paint.setAlpha(200); widgetsFactory = new MapMarkersWidgetsFactory(map); contextMenuLayer = view.getLayerByClass(ContextMenuLayer.class); } private Paint createPaintDest(int colorId) { Paint paint = new Paint(); paint.setDither(true); paint.setAntiAlias(true); paint.setFilterBitmap(true); int color = ContextCompat.getColor(map, colorId); paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)); return paint; } private Paint getMarkerDestPaint(int colorIndex) { switch (colorIndex) { case 0: return bitmapPaintDestBlue; case 1: return bitmapPaintDestGreen; case 2: return bitmapPaintDestOrange; case 3: return bitmapPaintDestRed; case 4: return bitmapPaintDestYellow; case 5: return bitmapPaintDestTeal; case 6: return bitmapPaintDestPurple; default: return bitmapPaintDestBlue; } } private Bitmap getMapMarkerBitmap(int colorIndex) { switch (colorIndex) { case 0: return markerBitmapBlue; case 1: return markerBitmapGreen; case 2: return markerBitmapOrange; case 3: return markerBitmapRed; case 4: return markerBitmapYellow; case 5: return markerBitmapTeal; case 6: return markerBitmapPurple; default: return markerBitmapBlue; } } public void setRoute(List<LatLon> points) { route.clear(); route.addAll(points); } public boolean clearRoute() { boolean res = route.size() > 0; route.clear(); return res; } @Override public void initLayer(OsmandMapTileView view) { this.view = view; handler = new Handler(); initUI(); longTapDetector = new GestureDetector(view.getContext(), new GestureDetector.OnGestureListener() { @Override public boolean onDown(MotionEvent e) { return false; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } @Override public void onLongPress(MotionEvent e) { cancelFingerAction(); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } }); } @Override public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings nightMode) { widgetsFactory.updateInfo(useFingerLocation ? fingerLocation : null, tileBox.getZoom()); if (tileBox.getZoom() < 3 || !map.getMyApplication().getSettings().USE_MAP_MARKERS.get()) { return; } MapMarkersHelper markersHelper = map.getMyApplication().getMapMarkersHelper(); if (route.size() > 0) { path.reset(); boolean first = true; Location myLocation = map.getMapViewTrackingUtilities().getMyLocation(); if (markersHelper.isStartFromMyLocation() && myLocation != null) { int locationX = tileBox.getPixXFromLonNoRot(myLocation.getLongitude()); int locationY = tileBox.getPixYFromLatNoRot(myLocation.getLatitude()); path.moveTo(locationX, locationY); first = false; } for (LatLon point : route) { int locationX = tileBox.getPixXFromLonNoRot(point.getLongitude()); int locationY = tileBox.getPixYFromLatNoRot(point.getLatitude()); if (first) { path.moveTo(locationX, locationY); first = false; } else { path.lineTo(locationX, locationY); } } canvas.drawPath(path, paint); } List<MapMarker> activeMapMarkers = markersHelper.getMapMarkers(); for (MapMarker marker : activeMapMarkers) { if (isLocationVisible(tileBox, marker) && !overlappedByWaypoint(marker) && !isInMotion(marker)) { Bitmap bmp = getMapMarkerBitmap(marker.colorIndex); int marginX = bmp.getWidth() / 6; int marginY = bmp.getHeight(); int locationX = tileBox.getPixXFromLonNoRot(marker.getLongitude()); int locationY = tileBox.getPixYFromLatNoRot(marker.getLatitude()); canvas.rotate(-tileBox.getRotate(), locationX, locationY); canvas.drawBitmap(bmp, locationX - marginX, locationY - marginY, bitmapPaint); canvas.rotate(tileBox.getRotate(), locationX, locationY); } } boolean show = useFingerLocation || (map.getMyApplication().getSettings().MAP_MARKERS_MODE.get() == OsmandSettings.MapMarkersMode.WIDGETS && map.getMyApplication().getSettings().SHOW_DESTINATION_ARROW.get()); if (show) { LatLon loc = fingerLocation; if (!useFingerLocation) { loc = tileBox.getCenterLatLon(); } if (loc != null) { List<MapMarker> mapMarkers = markersHelper.getMapMarkers(); int i = 0; for (MapMarker marker : mapMarkers) { if (!isLocationVisible(tileBox, marker) && !isInMotion(marker)) { canvas.save(); net.osmand.Location.distanceBetween(loc.getLatitude(), loc.getLongitude(), marker.getLatitude(), marker.getLongitude(), calculations); float bearing = calculations[1] - 90; float radiusBearing = DIST_TO_SHOW * tileBox.getDensity(); final QuadPoint cp = tileBox.getCenterPixelPoint(); canvas.rotate(bearing, cp.x, cp.y); canvas.translate(-24 * tileBox.getDensity() + radiusBearing, -22 * tileBox.getDensity()); canvas.drawBitmap(arrowToDestination, cp.x, cp.y, getMarkerDestPaint(marker.colorIndex)); canvas.restore(); } i++; if (i > 1) { break; } } } } if (contextMenuLayer.getMoveableObject() instanceof MapMarker) { MapMarker objectInMotion = (MapMarker) contextMenuLayer.getMoveableObject(); Bitmap bitmap = getMapMarkerBitmap(objectInMotion.colorIndex); PointF pf = contextMenuLayer.getMovableCenterPoint(tileBox); int marginX = bitmap.getWidth() / 6; int marginY = bitmap.getHeight(); float locationX = pf.x; float locationY = pf.y; canvas.rotate(-tileBox.getRotate(), locationX, locationY); canvas.drawBitmap(bitmap, locationX - marginX, locationY - marginY, bitmapPaint); } } private boolean isInMotion(@NonNull MapMarker marker) { return marker.equals(contextMenuLayer.getMoveableObject()); } public boolean isLocationVisible(RotatedTileBox tb, MapMarker marker) { //noinspection SimplifiableIfStatement if (marker == null || tb == null) { return false; } return containsLatLon(tb, marker.getLatitude(), marker.getLongitude()); } public boolean containsLatLon(RotatedTileBox tb, double lat, double lon) { double widgetHeight = 0; if (widgetsFactory.isTopBarVisible()) { widgetHeight = widgetsFactory.getTopBarHeight(); } double tx = tb.getPixXFromLatLon(lat, lon); double ty = tb.getPixYFromLatLon(lat, lon); return tx >= 0 && tx <= tb.getPixWidth() && ty >= widgetHeight && ty <= tb.getPixHeight(); } public boolean overlappedByWaypoint(MapMarker marker) { List<TargetPoint> targetPoints = map.getMyApplication().getTargetPointsHelper().getAllPoints(); for (TargetPoint t : targetPoints) { if (t.point.equals(marker.point)) { return true; } } return false; } @Override public void destroyLayer() { } @Override public boolean onTouchEvent(MotionEvent event, RotatedTileBox tileBox) { if (!longTapDetector.onTouchEvent(event)) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: float x = event.getX(); float y = event.getY(); fingerLocation = tileBox.getLatLonFromPixel(x, y); hasMoved = false; moving = true; break; case MotionEvent.ACTION_MOVE: if (!hasMoved) { if (!handler.hasMessages(MAP_REFRESH_MESSAGE)) { Message msg = Message.obtain(handler, new Runnable() { @Override public void run() { handler.removeMessages(MAP_REFRESH_MESSAGE); if (moving) { if (!useFingerLocation) { useFingerLocation = true; map.refreshMap(); } } } }); msg.what = MAP_REFRESH_MESSAGE; handler.sendMessageDelayed(msg, USE_FINGER_LOCATION_DELAY); } hasMoved = true; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: cancelFingerAction(); break; } } return super.onTouchEvent(event, tileBox); } private void cancelFingerAction() { handler.removeMessages(MAP_REFRESH_MESSAGE); useFingerLocation = false; moving = false; fingerLocation = null; map.refreshMap(); } @Override public boolean drawInScreenPixels() { return false; } @Override public boolean disableSingleTap() { return false; } @Override public boolean disableLongPressOnMap() { return false; } @Override public boolean isObjectClickable(Object o) { return false; } @Override public void collectObjectsFromPoint(PointF point, RotatedTileBox tileBox, List<Object> o) { if (tileBox.getZoom() < 3 || !map.getMyApplication().getSettings().USE_MAP_MARKERS.get()) { return; } MapMarkersHelper markersHelper = map.getMyApplication().getMapMarkersHelper(); List<MapMarker> markers = markersHelper.getMapMarkers(); int r = getRadiusPoi(tileBox); for (int i = 0; i < markers.size(); i++) { MapMarker marker = markers.get(i); LatLon latLon = marker.point; if (latLon != null) { int ex = (int) point.x; int ey = (int) point.y; int x = (int) tileBox.getPixXFromLatLon(latLon.getLatitude(), latLon.getLongitude()); int y = (int) tileBox.getPixYFromLatLon(latLon.getLatitude(), latLon.getLongitude()); if (calculateBelongs(ex, ey, x, y, r)) { o.add(marker); } } } } private boolean calculateBelongs(int ex, int ey, int objx, int objy, int radius) { return Math.abs(objx - ex) <= radius && (ey - objy) <= radius && (objy - ey) <= 2.5 * radius; } public int getRadiusPoi(RotatedTileBox tb) { int r; final double zoom = tb.getZoom(); if (zoom <= 15) { r = 10; } else if (zoom <= 16) { r = 14; } else if (zoom <= 17) { r = 16; } else { r = 18; } return (int) (r * tb.getDensity()); } @Override public LatLon getObjectLocation(Object o) { if (o instanceof MapMarker) { return ((MapMarker) o).point; } return null; } @Override public PointDescription getObjectName(Object o) { if (o instanceof MapMarker) { return ((MapMarker) o).getPointDescription(view.getContext()); } return null; } @Override public int getOrder(Object o) { return 0; } @Override public void setSelectedObject(Object o) { if (o instanceof MapMarker) { MapMarkersHelper markersHelper = map.getMyApplication().getMapMarkersHelper(); MapMarker marker = (MapMarker) o; List<MapMarker> mapMarkers = markersHelper.getMapMarkers(); int i = mapMarkers.indexOf(marker); if (i != -1) { mapMarkers.remove(i); mapMarkers.add(0, marker); markersHelper.saveMapMarkers(mapMarkers, null); marker.index = 0; } } } @Override public void clearSelectedObject() { } @Override public boolean isObjectMovable(Object o) { return o instanceof MapMarker; } @Override public void applyNewObjectPosition(@NonNull Object o, @NonNull LatLon position, @Nullable ApplyMovedObjectCallback callback) { boolean result = false; MapMarker newObject = null; if (o instanceof MapMarker) { MapMarkersHelper markersHelper = map.getMyApplication().getMapMarkersHelper(); MapMarker marker = (MapMarker) o; PointDescription originalDescription = marker.getOriginalPointDescription(); if (originalDescription.isLocation()) { originalDescription.setName(PointDescription.getSearchAddressStr(map)); } markersHelper.moveMapMarker(marker, position); int index = markersHelper.getMapMarkers().indexOf(marker); if (index != -1) { newObject = markersHelper.getMapMarkers().get(index); } result = true; } if (callback != null) { callback.onApplyMovedObject(result, newObject == null ? o : newObject); } } }