From fbb1c770a1c2be8ab47af5c9cc92199505cfacbb Mon Sep 17 00:00:00 2001 From: nazar-kutz Date: Mon, 1 Feb 2021 10:10:50 +0200 Subject: [PATCH 1/2] implement OSRM turn types parsing --- .../main/java/net/osmand/util/MapUtils.java | 9 +- .../onlinerouting/OnlineRoutingHelper.java | 8 +- .../engine/GraphhopperEngine.java | 19 ++- .../engine/OnlineRoutingEngine.java | 3 +- .../plus/onlinerouting/engine/OrsEngine.java | 2 + .../plus/onlinerouting/engine/OsrmEngine.java | 155 +++++++++++++++++- .../plus/routing/RouteCalculationResult.java | 2 +- .../osmand/plus/routing/RouteProvider.java | 2 +- 8 files changed, 178 insertions(+), 22 deletions(-) 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 15c3364007..7a37eee3aa 100644 --- a/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java +++ b/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java @@ -668,8 +668,13 @@ public class MapUtils { public static boolean areLatLonEqual(Location l1, Location l2) { return l1 == null && l2 == null - || (l1 != null && l2 != null && Math.abs(l1.getLatitude() - l2.getLatitude()) < 0.00001 - && Math.abs(l1.getLongitude() - l2.getLongitude()) < 0.00001); + || (l2 != null && areLatLonEqual(l1, l2.getLatitude(), l2.getLongitude())); + } + + public static boolean areLatLonEqual(Location l, double lat, double lon) { + return l != null + && Math.abs(l.getLatitude() - lat) < 0.00001 + && Math.abs(l.getLongitude() - lon) < 0.00001; } public static LatLon rhumbDestinationPoint(LatLon latLon, double distance, double bearing){ diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java index c62d952eb3..ab56cade13 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java @@ -82,12 +82,12 @@ public class OnlineRoutingHelper { } @Nullable - public OnlineRoutingResponse calculateRouteOnline(@NonNull OnlineRoutingEngine engine, - @NonNull List path, - boolean leftSideNavigation) throws IOException, JSONException { + private 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, leftSideNavigation); + return engine.parseServerResponse(content, app, leftSideNavigation); } @NonNull diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java index f2c7e19829..bcb3654ac3 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java @@ -5,6 +5,7 @@ import androidx.annotation.Nullable; import net.osmand.Location; import net.osmand.data.LatLon; +import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.onlinerouting.EngineParameter; import net.osmand.plus.onlinerouting.OnlineRoutingResponse; @@ -84,6 +85,7 @@ public class GraphhopperEngine extends OnlineRoutingEngine { @Nullable @Override public OnlineRoutingResponse parseServerResponse(@NonNull String content, + @NonNull OsmandApplication app, boolean leftSideNavigation) throws JSONException { JSONObject obj = new JSONObject(content); JSONObject root = obj.getJSONArray("paths").getJSONObject(0); @@ -97,24 +99,24 @@ public class GraphhopperEngine extends OnlineRoutingEngine { 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"))); + int sign = item.getInt("sign"); + int distance = (int) Math.round(item.getDouble("distance")); String description = item.getString("text"); String streetName = item.getString("street_name"); - int timeInSeconds = (int) Math.round(Integer.parseInt(item.getString("time")) / 1000f); + int timeInSeconds = Math.round(item.getInt("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); + if (turnType == null) { + turnType = TurnType.straight(); + } // 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); @@ -143,7 +145,7 @@ public class GraphhopperEngine extends OnlineRoutingEngine { */ @Nullable public static TurnType identifyTurnType(int sign, boolean leftSide) { - int id = INVALID_ID; + Integer id = null; if (sign == -98) { // an U-turn without the knowledge @@ -192,6 +194,7 @@ public class GraphhopperEngine extends OnlineRoutingEngine { } else if (sign == 4) { // the finish instruction before the last point + id = TurnType.C; } else if (sign == 5) { // the instruction before a via point @@ -209,6 +212,6 @@ public class GraphhopperEngine extends OnlineRoutingEngine { id = TurnType.TRU; } - return id != INVALID_ID ? TurnType.valueOf(id, leftSide) : null; + return id != null ? 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 af94be4282..24e5db7d5d 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OnlineRoutingEngine.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OnlineRoutingEngine.java @@ -8,6 +8,7 @@ import androidx.annotation.Nullable; import net.osmand.GPXUtilities.WptPt; import net.osmand.Location; import net.osmand.data.LatLon; +import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.onlinerouting.EngineParameter; import net.osmand.plus.onlinerouting.OnlineRoutingFactory; @@ -33,7 +34,6 @@ public abstract class OnlineRoutingEngine implements Cloneable { public final static String ONLINE_ROUTING_ENGINE_PREFIX = "online_routing_engine_"; 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<>(); @@ -100,6 +100,7 @@ public abstract class OnlineRoutingEngine implements Cloneable { @Nullable public abstract OnlineRoutingResponse parseServerResponse(@NonNull String content, + @NonNull OsmandApplication app, boolean leftSideNavigation) throws JSONException; @NonNull diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java index 4502f5e957..5f8c2a5108 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java @@ -5,6 +5,7 @@ import androidx.annotation.Nullable; import net.osmand.Location; import net.osmand.data.LatLon; +import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.onlinerouting.EngineParameter; import net.osmand.plus.onlinerouting.OnlineRoutingResponse; @@ -81,6 +82,7 @@ public class OrsEngine extends OnlineRoutingEngine { @Nullable @Override public OnlineRoutingResponse parseServerResponse(@NonNull String content, + @NonNull OsmandApplication app, boolean leftSideNavigation) throws JSONException { JSONObject obj = new JSONObject(content); JSONArray array = obj.getJSONArray("features").getJSONObject(0) diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java index 478c24886e..2e892d2aa6 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java @@ -5,19 +5,27 @@ import androidx.annotation.Nullable; import net.osmand.Location; import net.osmand.data.LatLon; +import net.osmand.plus.OsmandApplication; 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.RouteCalculationResult; +import net.osmand.plus.routing.RouteDirectionInfo; +import net.osmand.router.TurnType; import net.osmand.util.GeoPolylineParserUtil; +import net.osmand.util.MapUtils; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.util.ArrayList; import java.util.List; import java.util.Map; import static net.osmand.util.Algorithms.isEmpty; +import static net.osmand.util.Algorithms.objectEquals; public class OsrmEngine extends OnlineRoutingEngine { @@ -70,17 +78,154 @@ public class OsrmEngine extends OnlineRoutingEngine { @Nullable @Override public OnlineRoutingResponse parseServerResponse(@NonNull String content, + @NonNull OsmandApplication app, boolean leftSideNavigation) throws JSONException { JSONObject obj = new JSONObject(content); - 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); + JSONObject routeInfo = obj.getJSONArray("routes").getJSONObject(0); + String encodedPoints = routeInfo.getString("geometry"); + List points = GeoPolylineParserUtil.parse(encodedPoints, GeoPolylineParserUtil.PRECISION_5); + if (isEmpty(points)) return null; + + List route = convertRouteToLocationsList(points); + List directions = new ArrayList<>(); + int startSearchingId = 0; + JSONArray legs = routeInfo.getJSONArray("legs"); + for (int i = 0; i < legs.length(); i++) { + JSONObject leg = legs.getJSONObject(i); + if (!leg.has("steps")) continue; + + JSONArray steps = leg.getJSONArray("steps"); + for (int j = 0; j < steps.length(); j++) { + JSONObject instruction = steps.getJSONObject(j); + JSONObject maneuver = instruction.getJSONObject("maneuver"); + String maneuverType = maneuver.getString("type"); + + JSONArray location = maneuver.getJSONArray("location"); + double lon = location.getDouble(0); + double lat = location.getDouble(1); + Integer routePointOffset = getLocationIndexInList(route, startSearchingId, lat, lon); + if (routePointOffset == null) continue; + startSearchingId = routePointOffset; + + // in meters + int distance = (int) Math.round(instruction.getDouble("distance")); + // in seconds + int duration = (int) Math.round(instruction.getDouble("duration")); + + float averageSpeed = (float) distance / duration; + TurnType turnType = parseTurnType(maneuver, leftSideNavigation); + RouteDirectionInfo direction = new RouteDirectionInfo(averageSpeed, turnType); + direction.setDistance(distance); + + String streetName = instruction.getString("name"); + String description = ""; + if (!objectEquals(maneuverType, "arrive")) { + description = RouteCalculationResult.toString(turnType, app, false) + " " + streetName; + } + description = description.trim(); + + direction.setStreetName(streetName); + direction.setDescriptionRoute(description); + direction.routePointOffset = routePointOffset; + directions.add(direction); + } + } + + return new OnlineRoutingResponse(route, directions); + } + + @Nullable + private Integer getLocationIndexInList(@NonNull List locations, + int startIndex, double lat, double lon) { + for (int i = startIndex; i < locations.size(); i++) { + Location l = locations.get(i); + if (MapUtils.areLatLonEqual(l, lat, lon)) { + return i; + } } return null; } + @NonNull + private TurnType parseTurnType(@NonNull JSONObject maneuver, + boolean leftSide) throws JSONException { + TurnType turnType = null; + + String type = maneuver.getString("type"); + String modifier = null; + if (maneuver.has("modifier")) { + modifier = maneuver.getString("modifier"); + } + + if (objectEquals(type, "roundabout") + || objectEquals(type, "rotary") + || objectEquals(type, "roundabout turn")) { + if (maneuver.has("exit")) { + int exit = maneuver.getInt("exit"); + turnType = TurnType.getExitTurn(exit, 0.0f, leftSide); + } else if (modifier != null) { + // for simple roundabout turn without "exit" parameter + turnType = identifyTurnType(modifier, leftSide); + } + } else { + // for other maneuver types find TurnType + // like a basic turn into direction of the modifier + if (modifier != null) { + turnType = identifyTurnType(modifier, leftSide); + } + } + if (turnType == null) { + turnType = TurnType.straight(); + } + + int bearingBefore = maneuver.getInt("bearing_before"); + int bearingAfter = maneuver.getInt("bearing_after"); + float angle = (float) MapUtils.degreesDiff(bearingAfter, bearingBefore); + turnType.setTurnAngle(angle); + + return turnType; + } + + @Nullable + private TurnType identifyTurnType(@NonNull String modifier, + boolean leftSide) { + Integer id = null; + switch (modifier) { + case "uturn": + id = TurnType.TU; + break; + + case "sharp right": + id = TurnType.TSHR; + break; + + case "right": + id = TurnType.TR; + break; + + case "slight right": + id = TurnType.TSLR; + break; + + case "straight": + id = TurnType.C; + break; + + case "slight left": + id = TurnType.TSLL; + break; + + case "left": + id = TurnType.TL; + break; + + case "sharp left": + id = TurnType.TSHL; + break; + } + return id != null ? TurnType.valueOf(id, leftSide) : null; + } + @Override public boolean parseServerMessage(@NonNull StringBuilder sb, @NonNull String content) throws JSONException { diff --git a/OsmAnd/src/net/osmand/plus/routing/RouteCalculationResult.java b/OsmAnd/src/net/osmand/plus/routing/RouteCalculationResult.java index 4ef286826d..e6efcc321f 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() != null && r.getTurnType().getValue() == TurnType.C) { + if (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 96bb1b19dc..0d2564eb3e 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java +++ b/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java @@ -1206,7 +1206,7 @@ public class RouteProvider { helper.calculateRouteOnline(stringKey, getPathFromParams(params), params.leftSide); if (response != null) { params.intermediates = null; - return new RouteCalculationResult(response.getRoute(), response.getDirections(), params, null, true); + return new RouteCalculationResult(response.getRoute(), response.getDirections(), params, null, false); } else { return new RouteCalculationResult("Route is empty"); } From 17bfdad2c6685030872103b509162a9eecfd15ea Mon Sep 17 00:00:00 2001 From: nazar-kutz Date: Mon, 1 Feb 2021 10:56:17 +0200 Subject: [PATCH 2/2] add roundabout info about exit number and turn angle --- .../main/java/net/osmand/router/TurnType.java | 6 +- .../engine/GraphhopperEngine.java | 68 +++++++++++-------- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/OsmAnd-java/src/main/java/net/osmand/router/TurnType.java b/OsmAnd-java/src/main/java/net/osmand/router/TurnType.java index 7b67669cf3..6f0db3fa86 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/TurnType.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/TurnType.java @@ -140,7 +140,7 @@ public class TurnType { r.setTurnAngle(angle); return r; } - + private TurnType(int vl) { this.value = vl; @@ -156,6 +156,10 @@ public class TurnType { return value == RNLB || value == TRU; } + public void setExitOut(int exitOut) { + this.exitOut = exitOut; + } + public void setTurnAngle(float turnAngle) { this.turnAngle = turnAngle; } diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java index bcb3654ac3..20bbfaa70e 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java @@ -98,24 +98,19 @@ public class GraphhopperEngine extends OnlineRoutingEngine { JSONArray instructions = root.getJSONArray("instructions"); List directions = new ArrayList<>(); for (int i = 0; i < instructions.length(); i++) { - JSONObject item = instructions.getJSONObject(i); - int sign = item.getInt("sign"); - int distance = (int) Math.round(item.getDouble("distance")); - String description = item.getString("text"); - String streetName = item.getString("street_name"); - int timeInSeconds = Math.round(item.getInt("time") / 1000f); - JSONArray interval = item.getJSONArray("interval"); + JSONObject instruction = instructions.getJSONObject(i); + int distance = (int) Math.round(instruction.getDouble("distance")); + String description = instruction.getString("text"); + String streetName = instruction.getString("street_name"); + int timeInSeconds = Math.round(instruction.getInt("time") / 1000f); + JSONArray interval = instruction.getJSONArray("interval"); int startPointOffset = interval.getInt(0); int endPointOffset = interval.getInt(1); float averageSpeed = (float) distance / timeInSeconds; - TurnType turnType = identifyTurnType(sign, leftSideNavigation); - if (turnType == null) { - turnType = TurnType.straight(); - } - // TODO turnType.setTurnAngle() - + TurnType turnType = parseTurnType(instruction, leftSideNavigation); RouteDirectionInfo direction = new RouteDirectionInfo(averageSpeed, turnType); + direction.routePointOffset = startPointOffset; direction.setDescriptionRoute(description); direction.setStreetName(streetName); @@ -125,24 +120,30 @@ public class GraphhopperEngine extends OnlineRoutingEngine { return new OnlineRoutingResponse(route, directions); } - @Override - public boolean parseServerMessage(@NonNull StringBuilder sb, - @NonNull String content) throws JSONException { - JSONObject obj = new JSONObject(content); - if (obj.has("message")) { - String message = obj.getString("message"); - sb.append(message); + @NonNull + private TurnType parseTurnType(@NonNull JSONObject instruction, + boolean leftSide) throws JSONException { + int sign = instruction.getInt("sign"); + TurnType turnType = identifyTurnType(sign, leftSide); + + if (turnType == null) { + turnType = TurnType.straight(); + } else if (turnType.isRoundAbout()) { + if (instruction.has("exit_number")) { + int exit = instruction.getInt("exit_number"); + turnType.setExitOut(exit); + } + if (instruction.has("turn_angle")) { + float angle = (float) instruction.getDouble("turn_angle"); + turnType.setTurnAngle(angle); + } + } else { + // TODO turnType.setTurnAngle() } - return obj.has("paths"); + + return turnType; } - /** - * @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) { Integer id = null; @@ -214,4 +215,15 @@ public class GraphhopperEngine extends OnlineRoutingEngine { return id != null ? TurnType.valueOf(id, leftSide) : null; } + + @Override + public boolean parseServerMessage(@NonNull StringBuilder sb, + @NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + if (obj.has("message")) { + String message = obj.getString("message"); + sb.append(message); + } + return obj.has("paths"); + } }