diff --git a/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java b/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java index aeb0570950..7c83cb8f89 100644 --- a/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java +++ b/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java @@ -661,6 +661,41 @@ public class MapUtils { return new LatLon(Math.toDegrees(phi2), Math.toDegrees(lambda2)); } + + public static double getVectorMagnitude(int startX, int startY, int endX, int endY) { + return Math.sqrt(Math.pow((double) (endX - startX), 2.0) + Math.pow((double) (endY - startY), 2.0)); + } + + //angle of vector + public static double getAngleForRadiusVector(int startX, int startY, int endX, int endY) { + return 2 * Math.atan((endY - startY) / (endX - startX + + Math.sqrt(Math.pow((double) (endX - startX), 2.0) + Math.pow((double) (endY - startY), 2.0)))); + } + + //returns coordinates of point on circle + public static double[] getCoordinatesFromRadiusAndAngle(double centerX, double centerY, double radius, double angle) { + double x = centerX + radius * Math.cos(angle); + double y = centerY + radius * Math.sin(angle); + return new double[]{x,y}; + } + + //returns signed angle between vectors in radians + public static double getAngleBetweenVectors(int vectorAStartX, int vectorAStartY, int vectorAEndX, int vectorAEndY, + int vectorBStartX, int vectorBStartY, int vectorBEndX, int vectorBEndY) { + int[] vectorA = new int[] {getVectorAxisValue(vectorAStartX, vectorAEndX), getVectorAxisValue(vectorAStartY, vectorAEndY)}; + int[] vectorB = new int[] {getVectorAxisValue(vectorBStartX, vectorBEndX), getVectorAxisValue(vectorBStartY, vectorBEndY)}; + return Math.atan2(vectorA[0] * vectorB[1] - vectorA[1] * vectorB [0], vectorA[0] * vectorB[0] + vectorA[1] * vectorB[1]); + } + + //calculates vector value for axis + public static int getVectorAxisValue(int axisStart, int axisEnd) { + if (axisEnd < axisStart) { + return Math.abs(axisEnd) - Math.abs(axisStart); + } else { + return Math.abs(axisStart) - Math.abs(axisEnd); + } + } + } diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 05a85ae427..abf0c5d13d 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -11,6 +11,7 @@ Thx - Hardy --> + Direct-to-point Clear recorded data • Profiles: now you can change order, set icon for map, change all setting for base profiles and restore them back to defaults\n\n diff --git a/OsmAnd/src/net/osmand/plus/profiles/RoutingProfileDataObject.java b/OsmAnd/src/net/osmand/plus/profiles/RoutingProfileDataObject.java index b812022ec1..3cd3c74293 100644 --- a/OsmAnd/src/net/osmand/plus/profiles/RoutingProfileDataObject.java +++ b/OsmAnd/src/net/osmand/plus/profiles/RoutingProfileDataObject.java @@ -20,6 +20,7 @@ public class RoutingProfileDataObject extends ProfileDataObject { } public enum RoutingProfilesResources { + DIRECT_TO_MODE(R.string.routing_profile_direct_to, R.drawable.ic_action_navigation_type_direct_to), STRAIGHT_LINE_MODE(R.string.routing_profile_straightline, R.drawable.ic_action_split_interval), BROUTER_MODE(R.string.routing_profile_broutrer, R.drawable.ic_action_split_interval), CAR(R.string.rendering_value_car_name, R.drawable.ic_action_car_dark), diff --git a/OsmAnd/src/net/osmand/plus/routing/RouteCalculationParams.java b/OsmAnd/src/net/osmand/plus/routing/RouteCalculationParams.java index 00b414465c..99d7c6a959 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RouteCalculationParams.java +++ b/OsmAnd/src/net/osmand/plus/routing/RouteCalculationParams.java @@ -18,7 +18,6 @@ public class RouteCalculationParams { public LatLon end; public List intermediates; - public OsmandApplication ctx; public RoutingContext cachedRoutingContext; public ApplicationMode mode; @@ -35,6 +34,8 @@ public class RouteCalculationParams { public RouteCalculationProgressCallback calculationProgressCallback; public RouteCalculationResultListener resultListener; + public boolean showOriginalRoute; + public interface RouteCalculationResultListener { void onRouteCalculated(RouteCalculationResult route); } diff --git a/OsmAnd/src/net/osmand/plus/routing/RouteCalculationResult.java b/OsmAnd/src/net/osmand/plus/routing/RouteCalculationResult.java index f93886ac3d..1c838ec327 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RouteCalculationResult.java +++ b/OsmAnd/src/net/osmand/plus/routing/RouteCalculationResult.java @@ -64,6 +64,8 @@ public class RouteCalculationResult { protected int lastWaypointGPX = 0; protected ApplicationMode appMode; + protected boolean showOriginalRoute = false; + public RouteCalculationResult(String errorMessage) { this.errorMessage = errorMessage; this.routingTime = 0; @@ -77,7 +79,7 @@ public class RouteCalculationResult { this.directions = new ArrayList(); this.alarmInfo = new ArrayList(); } - + public RouteCalculationResult(List list, List directions, RouteCalculationParams params, List waypoints, boolean addMissingTurns) { this.routingTime = 0; this.loadedTiles = 0; @@ -109,6 +111,8 @@ public class RouteCalculationResult { calculateIntermediateIndexes(params.ctx, this.locations, params.intermediates, localDirections, this.intermediatePoints); this.directions = Collections.unmodifiableList(localDirections); updateDirectionsTime(this.directions, this.listDistance); + + this.showOriginalRoute = params.showOriginalRoute; } public RouteCalculationResult(List list, Location start, LatLon end, List intermediates, @@ -1206,5 +1210,8 @@ public class RouteCalculationResult { public int imminent; private int directionInfoInd; } - + + public boolean isShowOriginalRoute() { + return showOriginalRoute; + } } diff --git a/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java b/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java index 018bb5ae9c..f24a47851a 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java +++ b/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java @@ -79,7 +79,8 @@ public class RouteProvider { public enum RouteService { OSMAND("OsmAnd (offline)"), BROUTER("BRouter (offline)"), - STRAIGHT("Straight line"); + STRAIGHT("Straight line"), + DIRECT_TO("Direct To"); private final String name; @@ -305,7 +306,7 @@ public class RouteProvider { try { RouteCalculationResult res; boolean calcGPXRoute = params.gpxRoute != null && !params.gpxRoute.points.isEmpty(); - if(calcGPXRoute && !params.gpxRoute.calculateOsmAndRoute){ + if (calcGPXRoute && !params.gpxRoute.calculateOsmAndRoute) { res = calculateGpxRoute(params); } else if (params.mode.getRouteService() == RouteService.OSMAND) { res = findVectorMapsRoute(params, calcGPXRoute); @@ -315,10 +316,11 @@ public class RouteProvider { // res = findORSRoute(params); // } else if (params.type == RouteService.OSRM) { // res = findOSRMRoute(params); - } else if (params.mode.getRouteService() == RouteService.STRAIGHT){ + } else if (params.mode.getRouteService() == RouteService.STRAIGHT) { res = findStraightRoute(params); - } - else { + } else if (params.mode.getRouteService() == RouteService.DIRECT_TO) { + res = findDirectTo(params); + } else { res = new RouteCalculationResult("Selected route service is not available"); } if(log.isInfoEnabled() ){ @@ -1257,4 +1259,31 @@ public class RouteProvider { dots.add(location); return new RouteCalculationResult(dots, null, params, null, true); } + + private RouteCalculationResult findDirectTo(RouteCalculationParams params) { + params.showOriginalRoute = true; + double[] lats = new double[] { params.start.getLatitude(), params.end.getLatitude() }; + double[] lons = new double[] { params.start.getLongitude(), params.end.getLongitude() }; + List intermediates = params.intermediates; + List dots = new ArrayList(); + //writing start location + Location location = new Location(String.valueOf("start")); + location.setLatitude(lats[0]); + location.setLongitude(lons[0]); + //adding intermediate dots if they exists + if (intermediates != null){ + for(int i =0; i intermediatePoints; private Location lastProjection; private Location lastFixedLocation; + private Location originalStartingLocation; + + private RouteCalculationResult originalRoute = null; private static final int RECALCULATE_THRESHOLD_COUNT_CAUSING_FULL_RECALCULATE = 3; private static final int RECALCULATE_THRESHOLD_CAUSING_FULL_RECALCULATE_INTERVAL = 2*60*1000; @@ -176,6 +179,7 @@ public class RoutingHelper { } public synchronized void setFinalAndCurrentLocation(LatLon finalLocation, List intermediatePoints, Location currentLocation){ + setOriginalStartLocation(currentLocation); checkAndUpdateStartLocation(currentLocation); RouteCalculationResult previousRoute = route; clearCurrentRoute(finalLocation, intermediatePoints); @@ -183,10 +187,30 @@ public class RoutingHelper { setCurrentLocation(currentLocation, false, previousRoute, true); } + public RouteCalculationResult getOriginalRoute() { + return originalRoute; + } + public List getOriginalRouteAllLoc() { + return originalRoute.getImmutableAllLocations(); + } + + public void setOriginalRoute(RouteCalculationResult originalRoute) { + this.originalRoute = originalRoute; + } + + private void setOriginalStartLocation(Location currentLocation) { + originalStartingLocation = currentLocation; + } + + public Location getOriginalStartingLocation() { + return originalStartingLocation; + } + public synchronized void clearCurrentRoute(LatLon newFinalLocation, List newIntermediatePoints) { route = new RouteCalculationResult(""); isDeviatedFromRoute = false; evalWaitInterval = 0; + originalRoute = null; app.getWaypointHelper().setNewRoute(route); app.runInUIThread(new Runnable() { @Override @@ -401,6 +425,7 @@ public class RoutingHelper { // 0. Route empty or needs to be extended? Then re-calculate route. if(route.isEmpty()) { calculateRoute = true; + //originalRoute = null; } else { // 1. Update current route position status according to latest received location boolean finished = updateCurrentRouteStatus(currentLocation, posTolerance); @@ -989,6 +1014,7 @@ public class RoutingHelper { if (res.isCalculated()) { if (!params.inSnapToRoadMode && !params.inPublicTransportMode) { route = res; + updateOriginalRoute(); } if (params.resultListener != null) { params.resultListener.onRouteCalculated(res); @@ -1094,6 +1120,12 @@ public class RoutingHelper { } } + private void updateOriginalRoute() { + if (originalRoute == null) { + originalRoute = route; + } + } + public void startRouteCalculationThread(RouteCalculationParams params, boolean paramsChanged, boolean updateProgress) { synchronized (this) { final Thread prevRunningJob = currentRunningJob; diff --git a/OsmAnd/src/net/osmand/plus/settings/NavigationFragment.java b/OsmAnd/src/net/osmand/plus/settings/NavigationFragment.java index 8699504c92..2c559e2312 100644 --- a/OsmAnd/src/net/osmand/plus/settings/NavigationFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/NavigationFragment.java @@ -157,6 +157,8 @@ public class NavigationFragment extends BaseSettingsFragment { RouteProvider.RouteService routeService; if (profileKey.equals(RoutingProfilesResources.STRAIGHT_LINE_MODE.name())) { routeService = RouteProvider.RouteService.STRAIGHT; + } else if (profileKey.equals(RoutingProfilesResources.DIRECT_TO_MODE.name())){ + routeService = RouteProvider.RouteService.DIRECT_TO; } else if (profileKey.equals(RoutingProfilesResources.BROUTER_MODE.name())) { routeService = RouteProvider.RouteService.BROUTER; } else { @@ -209,6 +211,12 @@ public class NavigationFragment extends BaseSettingsFragment { context.getString(R.string.special_routing_type), RoutingProfilesResources.STRAIGHT_LINE_MODE.getIconRes(), false, null)); + profilesObjects.put(RoutingProfilesResources.DIRECT_TO_MODE.name(), new RoutingProfileDataObject( + RoutingProfilesResources.DIRECT_TO_MODE.name(), + context.getString(RoutingProfilesResources.DIRECT_TO_MODE.getStringRes()), + context.getString(R.string.special_routing_type), + RoutingProfilesResources.DIRECT_TO_MODE.getIconRes(), + false, null)); if (context.getBRouterService() != null) { profilesObjects.put(RoutingProfilesResources.BROUTER_MODE.name(), new RoutingProfileDataObject( RoutingProfilesResources.BROUTER_MODE.name(), diff --git a/OsmAnd/src/net/osmand/plus/views/RouteLayer.java b/OsmAnd/src/net/osmand/plus/views/RouteLayer.java index 6475fa6d78..97b22822b4 100644 --- a/OsmAnd/src/net/osmand/plus/views/RouteLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/RouteLayer.java @@ -18,6 +18,7 @@ import android.support.v4.content.ContextCompat; import android.util.Pair; import net.osmand.Location; +import net.osmand.PlatformUtil; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.data.QuadRect; @@ -45,6 +46,8 @@ import net.osmand.router.TransportRoutePlanner.TransportRouteResult; import net.osmand.router.TransportRoutePlanner.TransportRouteResultSegment; import net.osmand.util.MapUtils; +import org.apache.commons.logging.Log; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -68,6 +71,7 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont // keep array lists created private List actionPoints = new ArrayList(); private List routeTransportStops = new ArrayList<>(); + private double[] lastProjectionOnPathPoint; // cache private Bitmap actionArrow; @@ -87,6 +91,8 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont private GeometryWayContext wayContext; + private LayerDrawable projectionIcon; + private final static Log log = PlatformUtil.getLog(RouteLayer.class); public RouteLayer(RoutingHelper helper) { this.helper = helper; this.transportHelper = helper.getTransportRoutingHelper(); @@ -309,6 +315,21 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont } } + private void drawProjectionPoint(RotatedTileBox box, Canvas canvas, double[] projectionXY) { + if (projectionIcon == null) { + projectionIcon = (LayerDrawable) view.getResources().getDrawable(helper.getSettings().getApplicationMode().getLocationIcon().getIconId()); + } + int locationX = (int) projectionXY[0]; + int locationY = (int) projectionXY[1]; + + projectionIcon.setBounds(locationX - projectionIcon.getIntrinsicWidth() / 2, + locationY - projectionIcon.getIntrinsicHeight() / 2, + locationX + projectionIcon.getIntrinsicWidth() / 2, + locationY + projectionIcon.getIntrinsicHeight() / 2); + projectionIcon.draw(canvas); + + } + @ColorInt public int getRouteLineColor(boolean night) { updateAttrs(new DrawSettings(night), view.getCurrentRotatedTileBox()); @@ -820,7 +841,7 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont return simplifyPoints; } } - + private class RouteSimplificationGeometry { RouteCalculationResult route; TransportRouteResult transportRoute; @@ -930,7 +951,7 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont } private void drawSegments(RotatedTileBox tb, Canvas canvas, double topLatitude, double leftLongitude, - double bottomLatitude, double rightLongitude, Location lastProjection, int currentRoute) { + double bottomLatitude, double rightLongitude, Location lastProjection, int currentRoute, boolean showOriginalRoute) { if (locations.size() == 0) { return; } @@ -951,12 +972,17 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont previousVisible = true; } } - List routeNodes = locations; + List routeNodes; + if (showOriginalRoute && helper.getOriginalRoute() != null && helper.getOriginalRouteAllLoc() != null) { + routeNodes = helper.getOriginalRouteAllLoc(); + } else { + routeNodes = locations; + } int previous = -1; for (int i = currentRoute; i < routeNodes.size(); i++) { Location ls = routeNodes.get(i); style = getStyle(i, defaultWayStyle); - if (simplification.getQuick(i) == 0 && !styleMap.containsKey(i)) { + if (!showOriginalRoute && (simplification.getQuick(i) == 0 && !styleMap.containsKey(i))) { continue; } if (leftLongitude <= ls.getLongitude() && ls.getLongitude() <= rightLongitude && bottomLatitude <= ls.getLatitude() @@ -1072,25 +1098,76 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont Location startLocation = new Location("transport"); startLocation.setLatitude(start.getLatitude()); startLocation.setLongitude(start.getLongitude()); - routeGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude, startLocation, 0); + routeGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude, startLocation, 0, false); } } else { RouteCalculationResult route = helper.getRoute(); routeGeometry.clearTransportRoute(); routeGeometry.updateRoute(tb, route); - routeGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude, - helper.getLastProjection(), route == null ? 0 : route.getCurrentRoute()); + if (helper.getRoute().isShowOriginalRoute() && helper.getOriginalStartingLocation() != null) { + routeGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude, + helper.getOriginalStartingLocation(), 0, true); + } else { + routeGeometry.drawSegments(tb, canvas, topLatitude, leftLongitude, bottomLatitude, rightLongitude, + helper.getLastProjection(), route == null ? 0 : route.getCurrentRoute(), false); + } List rd = helper.getRouteDirections(); Iterator it = rd.iterator(); - if (tb.getZoom() >= 14) { + if (!helper.getRoute().isShowOriginalRoute() && tb.getZoom() >= 14) { List actionPoints = calculateActionPoints(topLatitude, leftLongitude, bottomLatitude, rightLongitude, helper.getLastProjection(), helper.getRoute().getRouteLocations(), helper.getRoute().getCurrentRoute(), it, tb.getZoom()); drawAction(tb, canvas, actionPoints); } + if (helper.getRoute().isShowOriginalRoute()) { + //add projection point on original route + double[] projectionOnRoute = calculateProjectionOnRoutePoint(helper.getLastProjection(), + helper.getOriginalRouteAllLoc(), helper, tb); + if (projectionOnRoute != null) { + drawProjectionPoint(tb, canvas, projectionOnRoute); + } + } } } - + private double[] calculateProjectionOnRoutePoint(Location lastProjection, List routeNodes, RoutingHelper helper, RotatedTileBox box) { + double[] projectionXY; + boolean visible; + Location previousInRoute = null; + Location nextInRoute = null; + //need to change this code by fixing helper.route.getCurrentRoute() miscalculation + if (helper.getRoute().getIntermediatePointsToPass() > 0) { + for (int i = 1; i < routeNodes.size(); i++) { + LatLon routePoint = new LatLon(routeNodes.get(i).getLatitude(), routeNodes.get(i).getLongitude()); + if (routePoint.equals(helper.getIntermediatePoints().get(0))) { + previousInRoute = routeNodes.get(i - 1); + nextInRoute = routeNodes.get(i); + } + } + } else { + previousInRoute = routeNodes.get(routeNodes.size() - 2); + nextInRoute = routeNodes.get(routeNodes.size() - 1); + } + + int centerX = box.getPixXFromLonNoRot(nextInRoute.getLongitude()); + int centerY = box.getPixYFromLatNoRot(nextInRoute.getLatitude()); + int aX = box.getPixXFromLonNoRot(lastProjection.getLongitude()); + int aY = box.getPixYFromLatNoRot(lastProjection.getLatitude()); + int bX = box.getPixXFromLonNoRot(previousInRoute.getLongitude()); + int bY = box.getPixYFromLatNoRot(previousInRoute.getLatitude()); + + double radius = MapUtils.getVectorMagnitude(centerX, centerY, aX, aY); + double angle2 = MapUtils.getAngleForRadiusVector(centerX, centerY, bX, bY); + projectionXY = MapUtils.getCoordinatesFromRadiusAndAngle(centerX, centerY, radius, angle2); + visible = box.containsPoint((float)projectionXY[0], (float)projectionXY[1], 20.0f) + && Math.abs(Math.toDegrees(MapUtils.getAngleBetweenVectors(centerX, centerY, aX, aY, centerX, centerY, bX, bY))) < 90; + + if (visible) { + return projectionXY; + } else { + return null; + } + + } private List calculateActionPoints(double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, Location lastProjection, List routeNodes, int cd,