Merge pull request #10725 from osmandapp/ParseOsrmTurnTypes

implement OSRM turn types parsing
This commit is contained in:
Vitaliy 2021-02-01 11:41:51 +02:00 committed by GitHub
commit 12ecfb256b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 217 additions and 45 deletions

View file

@ -156,6 +156,10 @@ public class TurnType {
return value == RNLB || value == TRU; return value == RNLB || value == TRU;
} }
public void setExitOut(int exitOut) {
this.exitOut = exitOut;
}
public void setTurnAngle(float turnAngle) { public void setTurnAngle(float turnAngle) {
this.turnAngle = turnAngle; this.turnAngle = turnAngle;
} }

View file

@ -668,8 +668,13 @@ public class MapUtils {
public static boolean areLatLonEqual(Location l1, Location l2) { public static boolean areLatLonEqual(Location l1, Location l2) {
return l1 == null && l2 == null return l1 == null && l2 == null
|| (l1 != null && l2 != null && Math.abs(l1.getLatitude() - l2.getLatitude()) < 0.00001 || (l2 != null && areLatLonEqual(l1, l2.getLatitude(), l2.getLongitude()));
&& Math.abs(l1.getLongitude() - l2.getLongitude()) < 0.00001); }
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){ public static LatLon rhumbDestinationPoint(LatLon latLon, double distance, double bearing){

View file

@ -82,12 +82,12 @@ public class OnlineRoutingHelper {
} }
@Nullable @Nullable
public OnlineRoutingResponse calculateRouteOnline(@NonNull OnlineRoutingEngine engine, private OnlineRoutingResponse calculateRouteOnline(@NonNull OnlineRoutingEngine engine,
@NonNull List<LatLon> path, @NonNull List<LatLon> path,
boolean leftSideNavigation) throws IOException, JSONException { boolean leftSideNavigation) throws IOException, JSONException {
String url = engine.getFullUrl(path); String url = engine.getFullUrl(path);
String content = makeRequest(url); String content = makeRequest(url);
return engine.parseServerResponse(content, leftSideNavigation); return engine.parseServerResponse(content, app, leftSideNavigation);
} }
@NonNull @NonNull

View file

@ -5,6 +5,7 @@ import androidx.annotation.Nullable;
import net.osmand.Location; import net.osmand.Location;
import net.osmand.data.LatLon; import net.osmand.data.LatLon;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.onlinerouting.EngineParameter; import net.osmand.plus.onlinerouting.EngineParameter;
import net.osmand.plus.onlinerouting.OnlineRoutingResponse; import net.osmand.plus.onlinerouting.OnlineRoutingResponse;
@ -84,6 +85,7 @@ public class GraphhopperEngine extends OnlineRoutingEngine {
@Nullable @Nullable
@Override @Override
public OnlineRoutingResponse parseServerResponse(@NonNull String content, public OnlineRoutingResponse parseServerResponse(@NonNull String content,
@NonNull OsmandApplication app,
boolean leftSideNavigation) throws JSONException { boolean leftSideNavigation) throws JSONException {
JSONObject obj = new JSONObject(content); JSONObject obj = new JSONObject(content);
JSONObject root = obj.getJSONArray("paths").getJSONObject(0); JSONObject root = obj.getJSONArray("paths").getJSONObject(0);
@ -96,25 +98,20 @@ public class GraphhopperEngine extends OnlineRoutingEngine {
JSONArray instructions = root.getJSONArray("instructions"); JSONArray instructions = root.getJSONArray("instructions");
List<RouteDirectionInfo> directions = new ArrayList<>(); List<RouteDirectionInfo> directions = new ArrayList<>();
for (int i = 0; i < instructions.length(); i++) { for (int i = 0; i < instructions.length(); i++) {
JSONObject item = instructions.getJSONObject(i); JSONObject instruction = instructions.getJSONObject(i);
int sign = Integer.parseInt(item.getString("sign")); int distance = (int) Math.round(instruction.getDouble("distance"));
int distance = (int) Math.round(Double.parseDouble(item.getString("distance"))); String description = instruction.getString("text");
String description = item.getString("text"); String streetName = instruction.getString("street_name");
String streetName = item.getString("street_name"); int timeInSeconds = Math.round(instruction.getInt("time") / 1000f);
int timeInSeconds = (int) Math.round(Integer.parseInt(item.getString("time")) / 1000f); JSONArray interval = instruction.getJSONArray("interval");
JSONArray interval = item.getJSONArray("interval");
int startPointOffset = interval.getInt(0); int startPointOffset = interval.getInt(0);
int endPointOffset = interval.getInt(1); int endPointOffset = interval.getInt(1);
float averageSpeed = (float) distance / timeInSeconds; float averageSpeed = (float) distance / timeInSeconds;
TurnType turnType = identifyTurnType(sign, leftSideNavigation); TurnType turnType = parseTurnType(instruction, leftSideNavigation);
// TODO turnType.setTurnAngle()
RouteDirectionInfo direction = new RouteDirectionInfo(averageSpeed, turnType); RouteDirectionInfo direction = new RouteDirectionInfo(averageSpeed, turnType);
direction.routePointOffset = startPointOffset; direction.routePointOffset = startPointOffset;
if (turnType != null && turnType.isRoundAbout()) {
direction.routeEndPointOffset = endPointOffset;
}
direction.setDescriptionRoute(description); direction.setDescriptionRoute(description);
direction.setStreetName(streetName); direction.setStreetName(streetName);
direction.setDistance(distance); direction.setDistance(distance);
@ -123,27 +120,33 @@ public class GraphhopperEngine extends OnlineRoutingEngine {
return new OnlineRoutingResponse(route, directions); return new OnlineRoutingResponse(route, directions);
} }
@Override @NonNull
public boolean parseServerMessage(@NonNull StringBuilder sb, private TurnType parseTurnType(@NonNull JSONObject instruction,
@NonNull String content) throws JSONException { boolean leftSide) throws JSONException {
JSONObject obj = new JSONObject(content); int sign = instruction.getInt("sign");
if (obj.has("message")) { TurnType turnType = identifyTurnType(sign, leftSide);
String message = obj.getString("message");
sb.append(message); 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 @Nullable
public static TurnType identifyTurnType(int sign, boolean leftSide) { public static TurnType identifyTurnType(int sign, boolean leftSide) {
int id = INVALID_ID; Integer id = null;
if (sign == -98) { if (sign == -98) {
// an U-turn without the knowledge // an U-turn without the knowledge
@ -192,6 +195,7 @@ public class GraphhopperEngine extends OnlineRoutingEngine {
} else if (sign == 4) { } else if (sign == 4) {
// the finish instruction before the last point // the finish instruction before the last point
id = TurnType.C;
} else if (sign == 5) { } else if (sign == 5) {
// the instruction before a via point // the instruction before a via point
@ -209,6 +213,17 @@ public class GraphhopperEngine extends OnlineRoutingEngine {
id = TurnType.TRU; id = TurnType.TRU;
} }
return id != INVALID_ID ? TurnType.valueOf(id, leftSide) : null; 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");
} }
} }

View file

@ -8,6 +8,7 @@ import androidx.annotation.Nullable;
import net.osmand.GPXUtilities.WptPt; import net.osmand.GPXUtilities.WptPt;
import net.osmand.Location; import net.osmand.Location;
import net.osmand.data.LatLon; import net.osmand.data.LatLon;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.onlinerouting.EngineParameter; import net.osmand.plus.onlinerouting.EngineParameter;
import net.osmand.plus.onlinerouting.OnlineRoutingFactory; 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 String ONLINE_ROUTING_ENGINE_PREFIX = "online_routing_engine_";
public final static 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<String, String> params = new HashMap<>(); private final Map<String, String> params = new HashMap<>();
private final List<VehicleType> allowedVehicles = new ArrayList<>(); private final List<VehicleType> allowedVehicles = new ArrayList<>();
@ -100,6 +100,7 @@ public abstract class OnlineRoutingEngine implements Cloneable {
@Nullable @Nullable
public abstract OnlineRoutingResponse parseServerResponse(@NonNull String content, public abstract OnlineRoutingResponse parseServerResponse(@NonNull String content,
@NonNull OsmandApplication app,
boolean leftSideNavigation) throws JSONException; boolean leftSideNavigation) throws JSONException;
@NonNull @NonNull

View file

@ -5,6 +5,7 @@ import androidx.annotation.Nullable;
import net.osmand.Location; import net.osmand.Location;
import net.osmand.data.LatLon; import net.osmand.data.LatLon;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.onlinerouting.EngineParameter; import net.osmand.plus.onlinerouting.EngineParameter;
import net.osmand.plus.onlinerouting.OnlineRoutingResponse; import net.osmand.plus.onlinerouting.OnlineRoutingResponse;
@ -81,6 +82,7 @@ public class OrsEngine extends OnlineRoutingEngine {
@Nullable @Nullable
@Override @Override
public OnlineRoutingResponse parseServerResponse(@NonNull String content, public OnlineRoutingResponse parseServerResponse(@NonNull String content,
@NonNull OsmandApplication app,
boolean leftSideNavigation) throws JSONException { boolean leftSideNavigation) throws JSONException {
JSONObject obj = new JSONObject(content); JSONObject obj = new JSONObject(content);
JSONArray array = obj.getJSONArray("features").getJSONObject(0) JSONArray array = obj.getJSONArray("features").getJSONObject(0)

View file

@ -5,19 +5,27 @@ import androidx.annotation.Nullable;
import net.osmand.Location; import net.osmand.Location;
import net.osmand.data.LatLon; import net.osmand.data.LatLon;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.onlinerouting.EngineParameter; import net.osmand.plus.onlinerouting.EngineParameter;
import net.osmand.plus.onlinerouting.OnlineRoutingResponse; import net.osmand.plus.onlinerouting.OnlineRoutingResponse;
import net.osmand.plus.onlinerouting.VehicleType; 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.GeoPolylineParserUtil;
import net.osmand.util.MapUtils;
import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static net.osmand.util.Algorithms.isEmpty; import static net.osmand.util.Algorithms.isEmpty;
import static net.osmand.util.Algorithms.objectEquals;
public class OsrmEngine extends OnlineRoutingEngine { public class OsrmEngine extends OnlineRoutingEngine {
@ -70,17 +78,154 @@ public class OsrmEngine extends OnlineRoutingEngine {
@Nullable @Nullable
@Override @Override
public OnlineRoutingResponse parseServerResponse(@NonNull String content, public OnlineRoutingResponse parseServerResponse(@NonNull String content,
@NonNull OsmandApplication app,
boolean leftSideNavigation) throws JSONException { boolean leftSideNavigation) throws JSONException {
JSONObject obj = new JSONObject(content); JSONObject obj = new JSONObject(content);
String encoded = obj.getJSONArray("routes").getJSONObject(0).getString("geometry"); JSONObject routeInfo = obj.getJSONArray("routes").getJSONObject(0);
List<LatLon> points = GeoPolylineParserUtil.parse(encoded, GeoPolylineParserUtil.PRECISION_5); String encodedPoints = routeInfo.getString("geometry");
if (!isEmpty(points)) { List<LatLon> points = GeoPolylineParserUtil.parse(encodedPoints, GeoPolylineParserUtil.PRECISION_5);
List<Location> route = convertRouteToLocationsList(points); if (isEmpty(points)) return null;
return new OnlineRoutingResponse(route, null);
List<Location> route = convertRouteToLocationsList(points);
List<RouteDirectionInfo> 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<Location> 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; 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 @Override
public boolean parseServerMessage(@NonNull StringBuilder sb, public boolean parseServerMessage(@NonNull StringBuilder sb,
@NonNull String content) throws JSONException { @NonNull String content) throws JSONException {

View file

@ -711,7 +711,7 @@ public class RouteCalculationResult {
if (directions != null && directions.size() > 1) { if (directions != null && directions.size() > 1) {
for (int i = 1; i < directions.size();) { for (int i = 1; i < directions.size();) {
RouteDirectionInfo r = directions.get(i); 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); RouteDirectionInfo prev = directions.get(i - 1);
prev.setAverageSpeed((prev.distance + r.distance) prev.setAverageSpeed((prev.distance + r.distance)
/ (prev.distance / prev.getAverageSpeed() + r.distance / r.getAverageSpeed())); / (prev.distance / prev.getAverageSpeed() + r.distance / r.getAverageSpeed()));

View file

@ -1206,7 +1206,7 @@ public class RouteProvider {
helper.calculateRouteOnline(stringKey, getPathFromParams(params), params.leftSide); helper.calculateRouteOnline(stringKey, getPathFromParams(params), params.leftSide);
if (response != null) { if (response != null) {
params.intermediates = 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 { } else {
return new RouteCalculationResult("Route is empty"); return new RouteCalculationResult("Route is empty");
} }