package net.osmand.plus.osmo; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Paint.Cap; import android.graphics.Paint.Join; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.PointF; import android.os.Handler; import android.util.DisplayMetrics; import android.view.WindowManager; import net.osmand.Location; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.data.RotatedTileBox; import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.R; import net.osmand.plus.TargetPointsHelper; import net.osmand.plus.TargetPointsHelper.TargetPoint; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.osmo.OsMoGroups.OsMoGroupsUIListener; import net.osmand.plus.osmo.OsMoGroupsStorage.OsMoDevice; import net.osmand.plus.osmo.OsMoGroupsStorage.OsMoGroup; import net.osmand.plus.routing.RoutingHelper; import net.osmand.plus.views.ContextMenuLayer; import net.osmand.plus.views.OsmandMapLayer; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; /** * Class represents a layer for osmo positions * */ public class OsMoPositionLayer extends OsmandMapLayer implements ContextMenuLayer.IContextMenuProvider, OsMoGroupsUIListener, ContextMenuLayer.IContextMenuProviderSelection{ private static int POINT_OUTER_COLOR = 0x88555555; private static int PAINT_TEXT_ICON_COLOR = Color.BLACK; private DisplayMetrics dm; private final MapActivity map; private OsmandMapTileView view; private Paint pointInnerCircle; private Paint pointOuter; private OsMoPlugin plugin; private final static float startZoom = 7; private Handler uiHandler; private Paint paintPath; private Path pth; private Paint paintTextIcon; public OsMoPositionLayer(MapActivity map, OsMoPlugin plugin) { this.map = map; this.plugin = plugin; } @Override public void initLayer(OsmandMapTileView view) { this.view = view; uiHandler = new Handler(); dm = new DisplayMetrics(); WindowManager wmgr = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE); wmgr.getDefaultDisplay().getMetrics(dm); pointInnerCircle = new Paint(); pointInnerCircle.setColor(view.getApplication().getResources().getColor(R.color.poi_background)); pointInnerCircle.setStyle(Style.FILL); pointInnerCircle.setAntiAlias(true); paintPath = new Paint(); paintPath.setStyle(Style.STROKE); paintPath.setStrokeWidth(14); paintPath.setAntiAlias(true); paintPath.setStrokeCap(Cap.ROUND); paintPath.setStrokeJoin(Join.ROUND); paintTextIcon = new Paint(); paintTextIcon.setTextSize(10 * view.getDensity()); paintTextIcon.setTextAlign(Align.CENTER); paintTextIcon.setFakeBoldText(true); paintTextIcon.setColor(PAINT_TEXT_ICON_COLOR); paintTextIcon.setAntiAlias(true); pth = new Path(); pointOuter = new Paint(); pointOuter.setColor(POINT_OUTER_COLOR); pointOuter.setAntiAlias(true); pointOuter.setStyle(Style.FILL_AND_STROKE); } public Collection<OsMoDevice> getTrackingDevices() { return plugin.getTracker().getTrackingDevices(); } public int getRadiusPoi(RotatedTileBox tb){ int r = 0; final double zoom = tb.getZoom(); if(zoom < startZoom){ r = 0; } else if(zoom <= 11){ r = 10; } else if(zoom <= 14){ r = 12; } else { r = 14; } return (int) (r * tb.getDensity()); } @Override public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings nightMode) { final int r = getRadiusPoi(tileBox); long treshold = System.currentTimeMillis() - 15000; for (OsMoDevice t : getTrackingDevices()) { Location l = t.getLastLocation(); ConcurrentLinkedQueue<Location> plocations = t.getPreviousLocations(treshold); if (!plocations.isEmpty() && l != null) { int x = (int) tileBox.getPixXFromLonNoRot(l.getLongitude()); int y = (int) tileBox.getPixYFromLatNoRot(l.getLatitude()); pth.rewind(); Iterator<Location> it = plocations.iterator(); boolean f = true; while (it.hasNext()) { Location lo = it.next(); int xt = (int) tileBox.getPixXFromLonNoRot(lo.getLongitude()); int yt = (int) tileBox.getPixYFromLatNoRot(lo.getLatitude()); if (f) { f = false; pth.moveTo(xt, yt); } else { pth.lineTo(xt, yt); } } pth.lineTo(x, y); paintPath.setColor(t.getColor()); canvas.drawPath(pth, paintPath); } } canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); for (OsMoDevice t : getTrackingDevices()) { Location l = t.getLastLocation(); if (l != null) { int x = (int) tileBox.getPixXFromLatLon(l.getLatitude(), l.getLongitude()); int y = (int) tileBox.getPixYFromLatLon(l.getLatitude(), l.getLongitude()); pointInnerCircle.setColor(t.getColor()); pointOuter.setColor(POINT_OUTER_COLOR); paintTextIcon.setColor(PAINT_TEXT_ICON_COLOR); Location lastLocation = t.getLastLocation(); if (lastLocation != null) { long now = System.currentTimeMillis(); boolean recent = Math.abs( now - lastLocation.getTime() ) < OsMoGroupsActivity.RECENT_THRESHOLD; if (!recent) { int color = t.getNonActiveColor(); pointInnerCircle.setColor(color); pointOuter.setColor(Color.argb(Color.alpha(color), Color.red(POINT_OUTER_COLOR), Color.green(POINT_OUTER_COLOR), Color.blue(POINT_OUTER_COLOR))); paintTextIcon.setColor(Color.argb(Color.alpha(color), Color.red(PAINT_TEXT_ICON_COLOR), Color.green(PAINT_TEXT_ICON_COLOR), Color.blue(PAINT_TEXT_ICON_COLOR))); } } else { int color = t.getNonActiveColor(); pointInnerCircle.setColor(color); pointOuter.setColor(Color.argb(Color.alpha(color), Color.red(POINT_OUTER_COLOR), Color.green(POINT_OUTER_COLOR), Color.blue(POINT_OUTER_COLOR))); paintTextIcon.setColor(Color.argb(Color.alpha(color), Color.red(PAINT_TEXT_ICON_COLOR), Color.green(PAINT_TEXT_ICON_COLOR), Color.blue(PAINT_TEXT_ICON_COLOR))); } canvas.drawCircle(x, y, r + (float)Math.ceil(tileBox.getDensity()), pointOuter); canvas.drawCircle(x, y, r - (float)Math.ceil(tileBox.getDensity()), pointInnerCircle); paintTextIcon.setTextSize(r * 3 / 2); canvas.drawText(t.getVisibleName().substring(0, 1).toUpperCase(), x, y + r / 2, paintTextIcon); } } } @Override public void destroyLayer() { } @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 o instanceof OsMoDevice; } @Override public void collectObjectsFromPoint(PointF point, RotatedTileBox tileBox, List<Object> o) { getOsmoFromPoint(tileBox, point, o); } @Override public LatLon getObjectLocation(Object o) { if(o instanceof OsMoDevice) { Location loc = ((OsMoDevice) o).getLastLocation(); if(loc != null) { return new LatLon(loc.getLatitude(), loc.getLongitude()); } } return null; } @Override public PointDescription getObjectName(Object o) { if(o instanceof OsMoDevice) { return new PointDescription(PointDescription.POINT_TYPE_MARKER, map.getString(R.string.osmo_user_name) + " " + ((OsMoDevice) o).getVisibleName()); } else { return null; } //String desc = getObjectDescription(o); //return desc == null ? null : new PointDescription(PointDescription.POINT_TYPE_MARKER, desc); } public void refresh() { if (view != null) { view.refreshMap(); } } private void getOsmoFromPoint(RotatedTileBox tb, PointF point, List<? super OsMoDevice> points) { if (view != null) { int ex = (int) point.x; int ey = (int) point.y; final int rp = getRadiusPoi(tb); int compare = rp; int radius = rp * 3 / 2; for (OsMoDevice d : getTrackingDevices()) { Location position = d.getLastLocation(); if (position != null) { int x = (int) tb.getPixXFromLatLon(position.getLatitude(), position.getLongitude()); int y = (int) tb.getPixYFromLatLon(position.getLatitude(), position.getLongitude()); // the width of an image is 40 px, the height is 60 px -> radius = 20, // the position of a parking point relatively to the icon is at the center of the bottom line of the // image if (Math.abs(x - ex) <= compare && Math.abs(y - ey) <= compare) { compare = radius; points.add(d); } } } } } @Override public void groupsListChange(String operation, OsMoGroup group) { } private volatile boolean schedule = false; // store between rotations private static String followTrackerId; private static LatLon followMapLocation; private static String followDestinationId; private static LatLon followTargetLocation; public static void setFollowTrackerId(OsMoDevice d, Location l) { if(d != null) { followTrackerId = d.trackerId; if(l != null) { followMapLocation = new LatLon(l.getLatitude(), l.getLongitude()); } } else { followTrackerId = null; } } public static void setFollowDestination(OsMoDevice followDestination) { followDestinationId = followDestination == null ? null : followDestination.trackerId; } public static String getFollowDestinationId() { return followDestinationId; } @Override public void deviceLocationChanged(final OsMoDevice device) { boolean sameDestId = Algorithms.objectEquals(followDestinationId, device.trackerId); Location l = device.getLastLocation(); if(sameDestId && l != null) { TargetPointsHelper targets = map.getMyApplication().getTargetPointsHelper(); final TargetPoint pn = targets.getPointToNavigate(); LatLon lt = new LatLon(l.getLatitude(), l.getLongitude()); boolean cancelDestinationId = false; if(followTargetLocation != null ) { if(pn == null || pn.point == null || MapUtils.getDistance(pn.point, followTargetLocation) > 10) { cancelDestinationId = true; } } if(cancelDestinationId) { followTargetLocation = null; followDestinationId = null; } else { RoutingHelper rh = map.getMyApplication().getRoutingHelper(); double dist = 1; if (rh.isRouteBeingCalculated()) { dist = 100; } else if (rh.isRouteCalculated()) { dist = 30; } if (pn == null || MapUtils.getDistance(pn.point, lt) > dist) { followTargetLocation = lt; targets.navigateToPoint(lt, true, -1); } } } boolean sameId = Algorithms.objectEquals(followTrackerId, device.trackerId); if(sameId && !schedule && l != null) { ContextMenuLayer cl = map.getMapLayers().getContextMenuLayer(); final boolean sameObject; if (map.getContextMenu().getObject() instanceof OsMoDevice && cl.isVisible()) { sameObject = Algorithms.objectEquals(device.trackerId, ((OsMoDevice) map.getContextMenu().getObject()).trackerId); } else { sameObject = false; } LatLon mapLoc = new LatLon(map.getMapView().getLatitude(), map.getMapView().getLongitude()); final boolean centered = followMapLocation != null && MapUtils.getDistance(mapLoc, followMapLocation) < 1; if(sameObject || centered) { final LatLon loc; if(centered ) { loc = new LatLon(l.getLatitude(), l.getLongitude()); } else if(!map.getMapView().getAnimatedDraggingThread().isAnimating()) { // disable tracking loc = null; } else { loc = followMapLocation; } followMapLocation = loc; schedule = true; uiHandler.postDelayed(new Runnable() { @Override public void run() { schedule = false; if (sameObject) { Location l = device.getLastLocation(); if (centered) { map.getContextMenu().updateMapCenter(loc); } map.getContextMenu().update(new LatLon(l.getLatitude(), l.getLongitude()), getObjectName(device), device); } if (centered) { map.getMapView().setLatLon(loc.getLatitude(), loc.getLongitude()); } map.getMapView().refreshMap(); } }, 150); } else { followTrackerId = null; } } uiHandler.postDelayed(new Runnable() { @Override public void run() { map.getMapView().refreshMap(); } }, 350); } @Override public int getOrder(Object o) { return 0; } @Override public void setSelectedObject(Object o) { if(o instanceof OsMoDevice) { followTrackerId = ((OsMoDevice) o).getTrackerId(); } } @Override public void clearSelectedObject() { LatLon mapLoc = new LatLon(map.getMapView().getLatitude(), map.getMapView().getLongitude()); final boolean centered = Algorithms.objectEquals(followMapLocation, mapLoc); if(!centered && followTrackerId != null) { followTrackerId = null; } } }