diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java b/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java index 6bb4da1ce5..26d984b2da 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java @@ -1222,7 +1222,7 @@ public class RouteResultPreparation { // 3. calculate angle difference // This method doesn't work if you go from S to N touching only 1 point of roundabout, // but it is very important to identify very sharp or very large angle to understand did you pass whole roundabout or small entrance - float turnAngleBasedOnCircle = (float) MapUtils.degreesDiff(firstRoundabout.getBearingBegin(), lastRoundabout.getBearingEnd() + 180); + float turnAngleBasedOnCircle = (float) -MapUtils.degreesDiff(firstRoundabout.getBearingBegin(), lastRoundabout.getBearingEnd() + 180); if (Math.abs(turnAngleBasedOnOutRoads) > 120) { // correctly identify if angle is +- 180, so we approach from left or right side t.setTurnAngle(turnAngleBasedOnCircle) ; diff --git a/OsmAnd/res/drawable/ic_action_user_account_16.xml b/OsmAnd/res/drawable/ic_action_user_account_16.xml new file mode 100644 index 0000000000..f05533e018 --- /dev/null +++ b/OsmAnd/res/drawable/ic_action_user_account_16.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java index 9b3d042699..c62d952eb3 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java @@ -25,6 +25,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import static net.osmand.util.Algorithms.isEmpty; + public class OnlineRoutingHelper { private static final Log LOG = PlatformUtil.getLog(OnlineRoutingHelper.class); @@ -71,12 +73,21 @@ public class OnlineRoutingHelper { return null; } - @NonNull - public List calculateRouteOnline(@NonNull OnlineRoutingEngine engine, - @NonNull List path) throws IOException, JSONException { + @Nullable + public OnlineRoutingResponse calculateRouteOnline(@Nullable String stringKey, + @NonNull List path, + boolean leftSideNavigation) throws IOException, JSONException { + OnlineRoutingEngine engine = getEngineByKey(stringKey); + return engine != null ? calculateRouteOnline(engine, path, leftSideNavigation) : null; + } + + @Nullable + public OnlineRoutingResponse calculateRouteOnline(@NonNull OnlineRoutingEngine engine, + @NonNull List path, + boolean leftSideNavigation) throws IOException, JSONException { String url = engine.getFullUrl(path); String content = makeRequest(url); - return engine.parseServerResponse(content); + return engine.parseServerResponse(content, leftSideNavigation); } @NonNull @@ -131,7 +142,7 @@ public class OnlineRoutingHelper { @NonNull private String createEngineKeyIfNeeded(@NonNull OnlineRoutingEngine engine) { String key = engine.get(EngineParameter.KEY); - if (Algorithms.isEmpty(key)) { + if (isEmpty(key)) { key = OnlineRoutingEngine.generateKey(); engine.put(EngineParameter.KEY, key); } @@ -151,7 +162,7 @@ public class OnlineRoutingHelper { private List readFromSettings() { List engines = new ArrayList<>(); String jsonString = settings.ONLINE_ROUTING_ENGINES.get(); - if (!Algorithms.isEmpty(jsonString)) { + if (!isEmpty(jsonString)) { try { JSONObject json = new JSONObject(jsonString); OnlineRoutingUtils.readFromJson(json, engines); @@ -163,7 +174,7 @@ public class OnlineRoutingHelper { } private void saveCacheToSettings() { - if (!Algorithms.isEmpty(cachedEngines)) { + if (!isEmpty(cachedEngines)) { try { JSONObject json = new JSONObject(); OnlineRoutingUtils.writeToJson(json, getEngines()); diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingResponse.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingResponse.java new file mode 100644 index 0000000000..02a6a8f53c --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingResponse.java @@ -0,0 +1,24 @@ +package net.osmand.plus.onlinerouting; + +import net.osmand.Location; +import net.osmand.plus.routing.RouteDirectionInfo; + +import java.util.List; + +public class OnlineRoutingResponse { + private List route; + private List directions; + + public OnlineRoutingResponse(List route, List directions) { + this.route = route; + this.directions = directions; + } + + public List getRoute() { + return route; + } + + public List getDirections() { + return directions; + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java index df9381352b..f2c7e19829 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java @@ -3,15 +3,21 @@ package net.osmand.plus.onlinerouting.engine; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import net.osmand.Location; import net.osmand.data.LatLon; import net.osmand.plus.R; import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.OnlineRoutingResponse; import net.osmand.plus.onlinerouting.VehicleType; +import net.osmand.plus.routing.RouteDirectionInfo; +import net.osmand.router.TurnType; import net.osmand.util.GeoPolylineParserUtil; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -23,8 +29,9 @@ public class GraphhopperEngine extends OnlineRoutingEngine { super(params); } + @NonNull @Override - public @NonNull EngineType getType() { + public EngineType getType() { return EngineType.GRAPHHOPPER; } @@ -71,15 +78,49 @@ public class GraphhopperEngine extends OnlineRoutingEngine { if (!isEmpty(apiKey)) { sb.append('&').append("key=").append(apiKey); } + sb.append('&').append("details=").append("lanes"); } - @NonNull + @Nullable @Override - public List parseServerResponse(@NonNull String content) throws JSONException { + public OnlineRoutingResponse parseServerResponse(@NonNull String content, + boolean leftSideNavigation) throws JSONException { JSONObject obj = new JSONObject(content); - return GeoPolylineParserUtil.parse( - obj.getJSONArray("paths").getJSONObject(0).getString("points"), - GeoPolylineParserUtil.PRECISION_5); + JSONObject root = obj.getJSONArray("paths").getJSONObject(0); + + String encoded = root.getString("points"); + List points = GeoPolylineParserUtil.parse(encoded, GeoPolylineParserUtil.PRECISION_5); + if (isEmpty(points)) return null; + List route = convertRouteToLocationsList(points); + + JSONArray instructions = root.getJSONArray("instructions"); + List directions = new ArrayList<>(); + for (int i = 0; i < instructions.length(); i++) { + JSONObject item = instructions.getJSONObject(i); + int sign = Integer.parseInt(item.getString("sign")); + int distance = (int) Math.round(Double.parseDouble(item.getString("distance"))); + String description = item.getString("text"); + String streetName = item.getString("street_name"); + int timeInSeconds = (int) Math.round(Integer.parseInt(item.getString("time")) / 1000f); + JSONArray interval = item.getJSONArray("interval"); + int startPointOffset = interval.getInt(0); + int endPointOffset = interval.getInt(1); + + float averageSpeed = (float) distance / timeInSeconds; + TurnType turnType = identifyTurnType(sign, leftSideNavigation); + // TODO turnType.setTurnAngle() + + RouteDirectionInfo direction = new RouteDirectionInfo(averageSpeed, turnType); + direction.routePointOffset = startPointOffset; + if (turnType != null && turnType.isRoundAbout()) { + direction.routeEndPointOffset = endPointOffset; + } + direction.setDescriptionRoute(description); + direction.setStreetName(streetName); + direction.setDistance(distance); + directions.add(direction); + } + return new OnlineRoutingResponse(route, directions); } @Override @@ -92,4 +133,82 @@ public class GraphhopperEngine extends OnlineRoutingEngine { } return obj.has("paths"); } + + /** + * @param sign - a number which specifies the turn type to show (Graphhopper API value) + * @return a TurnType object defined in OsmAnd which is equivalent to a value from the Graphhopper API + * + * For future compatibility it is important that all clients + * are able to handle also unknown instruction sign numbers + */ + @Nullable + public static TurnType identifyTurnType(int sign, boolean leftSide) { + int id = INVALID_ID; + + if (sign == -98) { + // an U-turn without the knowledge + // if it is a right or left U-turn + id = TurnType.TU; + + } else if (sign == -8) { + // a left U-turn + leftSide = false; + id = TurnType.TU; + + } else if (sign == -7) { + // keep left + id = TurnType.KL; + + } else if (sign == -6) { + // not yet used: leave roundabout + + } else if (sign == -3) { + // turn sharp left + id = TurnType.TSHL; + + } else if (sign == -2) { + // turn left + id = TurnType.TL; + + } else if (sign == -1) { + // turn slight left + id = TurnType.TSLL; + + } else if (sign == 0) { + // continue on street + id = TurnType.C; + + } else if (sign == 1) { + // turn slight right + id = TurnType.TSLR; + + } else if (sign == 2) { + // turn right + id = TurnType.TR; + + } else if (sign == 3) { + // turn sharp right + id = TurnType.TSHR; + + } else if (sign == 4) { + // the finish instruction before the last point + + } else if (sign == 5) { + // the instruction before a via point + + } else if (sign == 6) { + // the instruction before entering a roundabout + id = TurnType.RNDB; + + } else if (sign == 7) { + // keep right + id = TurnType.KR; + + } else if (sign == 8) { + // a right U-turn + id = TurnType.TRU; + } + + return id != INVALID_ID ? TurnType.valueOf(id, leftSide) : null; + } } diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OnlineRoutingEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OnlineRoutingEngine.java index 511f31cc67..af94be4282 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OnlineRoutingEngine.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OnlineRoutingEngine.java @@ -5,11 +5,15 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import net.osmand.GPXUtilities.WptPt; +import net.osmand.Location; import net.osmand.data.LatLon; import net.osmand.plus.R; import net.osmand.plus.onlinerouting.EngineParameter; import net.osmand.plus.onlinerouting.OnlineRoutingFactory; +import net.osmand.plus.onlinerouting.OnlineRoutingResponse; import net.osmand.plus.onlinerouting.VehicleType; +import net.osmand.plus.routing.RouteProvider; import net.osmand.util.Algorithms; import org.json.JSONException; @@ -28,7 +32,8 @@ import static net.osmand.util.Algorithms.isEmpty; public abstract class OnlineRoutingEngine implements Cloneable { public final static String ONLINE_ROUTING_ENGINE_PREFIX = "online_routing_engine_"; - public static final VehicleType CUSTOM_VEHICLE = new VehicleType("", R.string.shared_string_custom); + public final static VehicleType CUSTOM_VEHICLE = new VehicleType("", R.string.shared_string_custom); + public final static int INVALID_ID = -1; private final Map params = new HashMap<>(); private final List allowedVehicles = new ArrayList<>(); @@ -71,15 +76,6 @@ public abstract class OnlineRoutingEngine implements Cloneable { } } - @NonNull - public String getBaseUrl() { - String customUrl = get(EngineParameter.CUSTOM_URL); - if (isEmpty(customUrl)) { - return getStandardUrl(); - } - return customUrl; - } - @NonNull public String getFullUrl(@NonNull List path) { StringBuilder sb = new StringBuilder(getBaseUrl()); @@ -91,11 +87,35 @@ public abstract class OnlineRoutingEngine implements Cloneable { @NonNull List path); @NonNull - public abstract List parseServerResponse(@NonNull String content) throws JSONException; + public String getBaseUrl() { + String customUrl = get(EngineParameter.CUSTOM_URL); + if (isEmpty(customUrl)) { + return getStandardUrl(); + } + return customUrl; + } @NonNull public abstract String getStandardUrl(); + @Nullable + public abstract OnlineRoutingResponse parseServerResponse(@NonNull String content, + boolean leftSideNavigation) throws JSONException; + + @NonNull + protected List convertRouteToLocationsList(@NonNull List route) { + List result = new ArrayList<>(); + if (!isEmpty(route)) { + for (LatLon pt : route) { + WptPt wpt = new WptPt(); + wpt.lat = pt.getLatitude(); + wpt.lon = pt.getLongitude(); + result.add(RouteProvider.createLocation(wpt)); + } + } + return result; + } + @NonNull public Map getParams() { return params; diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java index 7c57737d46..4502f5e957 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java @@ -3,9 +3,11 @@ package net.osmand.plus.onlinerouting.engine; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import net.osmand.Location; import net.osmand.data.LatLon; import net.osmand.plus.R; import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.OnlineRoutingResponse; import net.osmand.plus.onlinerouting.VehicleType; import org.json.JSONArray; @@ -24,8 +26,9 @@ public class OrsEngine extends OnlineRoutingEngine { super(params); } + @NonNull @Override - public @NonNull EngineType getType() { + public EngineType getType() { return EngineType.ORS; } @@ -75,20 +78,25 @@ public class OrsEngine extends OnlineRoutingEngine { } } - @NonNull + @Nullable @Override - public List parseServerResponse(@NonNull String content) throws JSONException { + public OnlineRoutingResponse parseServerResponse(@NonNull String content, + boolean leftSideNavigation) throws JSONException { JSONObject obj = new JSONObject(content); JSONArray array = obj.getJSONArray("features").getJSONObject(0) .getJSONObject("geometry").getJSONArray("coordinates"); - List track = new ArrayList<>(); + List points = new ArrayList<>(); for (int i = 0; i < array.length(); i++) { JSONArray point = array.getJSONArray(i); double lon = Double.parseDouble(point.getString(0)); double lat = Double.parseDouble(point.getString(1)); - track.add(new LatLon(lat, lon)); + points.add(new LatLon(lat, lon)); } - return track; + if (!isEmpty(points)) { + List route = convertRouteToLocationsList(points); + new OnlineRoutingResponse(route, null); + } + return null; } @Override diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java index 1ef9c1a622..478c24886e 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java @@ -3,9 +3,11 @@ package net.osmand.plus.onlinerouting.engine; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import net.osmand.Location; import net.osmand.data.LatLon; import net.osmand.plus.R; import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.OnlineRoutingResponse; import net.osmand.plus.onlinerouting.VehicleType; import net.osmand.util.GeoPolylineParserUtil; @@ -24,7 +26,8 @@ public class OsrmEngine extends OnlineRoutingEngine { } @Override - public @NonNull EngineType getType() { + public @NonNull + EngineType getType() { return EngineType.OSRM; } @@ -35,7 +38,8 @@ public class OsrmEngine extends OnlineRoutingEngine { } @Override - protected void collectAllowedParameters() { } + protected void collectAllowedParameters() { + } @Override protected void collectAllowedVehicles(@NonNull List vehicles) { @@ -60,15 +64,21 @@ public class OsrmEngine extends OnlineRoutingEngine { } sb.append('?'); sb.append("overview=full"); + sb.append('&').append("steps=true"); } - @NonNull + @Nullable @Override - public List parseServerResponse(@NonNull String content) throws JSONException { + public OnlineRoutingResponse parseServerResponse(@NonNull String content, + boolean leftSideNavigation) throws JSONException { JSONObject obj = new JSONObject(content); - return GeoPolylineParserUtil.parse( - obj.getJSONArray("routes").getJSONObject(0).getString("geometry"), - GeoPolylineParserUtil.PRECISION_5); + String encoded = obj.getJSONArray("routes").getJSONObject(0).getString("geometry"); + List points = GeoPolylineParserUtil.parse(encoded, GeoPolylineParserUtil.PRECISION_5); + if (!isEmpty(points)) { + List route = convertRouteToLocationsList(points); + return new OnlineRoutingResponse(route, null); + } + return null; } @Override diff --git a/OsmAnd/src/net/osmand/plus/routing/RouteCalculationResult.java b/OsmAnd/src/net/osmand/plus/routing/RouteCalculationResult.java index e6efcc321f..4ef286826d 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RouteCalculationResult.java +++ b/OsmAnd/src/net/osmand/plus/routing/RouteCalculationResult.java @@ -711,7 +711,7 @@ public class RouteCalculationResult { if (directions != null && directions.size() > 1) { for (int i = 1; i < directions.size();) { RouteDirectionInfo r = directions.get(i); - if (r.getTurnType().getValue() == TurnType.C) { + if (r.getTurnType() != null && r.getTurnType().getValue() == TurnType.C) { RouteDirectionInfo prev = directions.get(i - 1); prev.setAverageSpeed((prev.distance + r.distance) / (prev.distance / prev.getAverageSpeed() + r.distance / r.getAverageSpeed())); diff --git a/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java b/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java index 96017bc96c..96bb1b19dc 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java +++ b/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java @@ -21,7 +21,7 @@ import net.osmand.data.LocationPoint; import net.osmand.data.WptLocationPoint; import net.osmand.plus.OsmandApplication; import net.osmand.plus.onlinerouting.OnlineRoutingHelper; -import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; +import net.osmand.plus.onlinerouting.OnlineRoutingResponse; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.R; @@ -326,7 +326,7 @@ public class RouteProvider { } } - private static Location createLocation(WptPt pt){ + public static Location createLocation(WptPt pt){ Location loc = new Location("OsmandRouteProvider"); loc.setLatitude(pt.lat); loc.setLongitude(pt.lon); @@ -1202,27 +1202,17 @@ public class RouteProvider { private RouteCalculationResult findOnlineRoute(RouteCalculationParams params) throws IOException, JSONException { OnlineRoutingHelper helper = params.ctx.getOnlineRoutingHelper(); String stringKey = params.mode.getRoutingProfile(); - OnlineRoutingEngine engine = helper.getEngineByKey(stringKey); - List route = null; - if (engine != null) { - route = helper.calculateRouteOnline(engine, getFullPathFromParams(params)); - } - if (!Algorithms.isEmpty(route)) { - List res = new ArrayList<>(); - for (LatLon pt : route) { - WptPt wpt = new WptPt(); - wpt.lat = pt.getLatitude(); - wpt.lon = pt.getLongitude(); - res.add(createLocation(wpt)); - } + OnlineRoutingResponse response = + helper.calculateRouteOnline(stringKey, getPathFromParams(params), params.leftSide); + if (response != null) { params.intermediates = null; - return new RouteCalculationResult(res, null, params, null, true); + return new RouteCalculationResult(response.getRoute(), response.getDirections(), params, null, true); } else { return new RouteCalculationResult("Route is empty"); } } - private static List getFullPathFromParams(RouteCalculationParams params) { + private static List getPathFromParams(RouteCalculationParams params) { List points = new ArrayList<>(); points.add(new LatLon(params.start.getLatitude(), params.start.getLongitude())); if (Algorithms.isEmpty(params.intermediates)) {