Merge pull request #10652 from osmandapp/TurnTypes

Implement Graphhopper turn types
This commit is contained in:
vshcherb 2021-01-25 10:32:56 +01:00 committed by GitHub
commit 92217cba9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 237 additions and 55 deletions

View file

@ -25,6 +25,8 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static net.osmand.util.Algorithms.isEmpty;
public class OnlineRoutingHelper { public class OnlineRoutingHelper {
private static final Log LOG = PlatformUtil.getLog(OnlineRoutingHelper.class); private static final Log LOG = PlatformUtil.getLog(OnlineRoutingHelper.class);
@ -71,12 +73,21 @@ public class OnlineRoutingHelper {
return null; return null;
} }
@NonNull @Nullable
public List<LatLon> calculateRouteOnline(@NonNull OnlineRoutingEngine engine, public OnlineRoutingResponse calculateRouteOnline(@Nullable String stringKey,
@NonNull List<LatLon> path) throws IOException, JSONException { @NonNull List<LatLon> 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<LatLon> path,
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); return engine.parseServerResponse(content, leftSideNavigation);
} }
@NonNull @NonNull
@ -131,7 +142,7 @@ public class OnlineRoutingHelper {
@NonNull @NonNull
private String createEngineKeyIfNeeded(@NonNull OnlineRoutingEngine engine) { private String createEngineKeyIfNeeded(@NonNull OnlineRoutingEngine engine) {
String key = engine.get(EngineParameter.KEY); String key = engine.get(EngineParameter.KEY);
if (Algorithms.isEmpty(key)) { if (isEmpty(key)) {
key = OnlineRoutingEngine.generateKey(); key = OnlineRoutingEngine.generateKey();
engine.put(EngineParameter.KEY, key); engine.put(EngineParameter.KEY, key);
} }
@ -151,7 +162,7 @@ public class OnlineRoutingHelper {
private List<OnlineRoutingEngine> readFromSettings() { private List<OnlineRoutingEngine> readFromSettings() {
List<OnlineRoutingEngine> engines = new ArrayList<>(); List<OnlineRoutingEngine> engines = new ArrayList<>();
String jsonString = settings.ONLINE_ROUTING_ENGINES.get(); String jsonString = settings.ONLINE_ROUTING_ENGINES.get();
if (!Algorithms.isEmpty(jsonString)) { if (!isEmpty(jsonString)) {
try { try {
JSONObject json = new JSONObject(jsonString); JSONObject json = new JSONObject(jsonString);
OnlineRoutingUtils.readFromJson(json, engines); OnlineRoutingUtils.readFromJson(json, engines);
@ -163,7 +174,7 @@ public class OnlineRoutingHelper {
} }
private void saveCacheToSettings() { private void saveCacheToSettings() {
if (!Algorithms.isEmpty(cachedEngines)) { if (!isEmpty(cachedEngines)) {
try { try {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
OnlineRoutingUtils.writeToJson(json, getEngines()); OnlineRoutingUtils.writeToJson(json, getEngines());

View file

@ -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<Location> route;
private List<RouteDirectionInfo> directions;
public OnlineRoutingResponse(List<Location> route, List<RouteDirectionInfo> directions) {
this.route = route;
this.directions = directions;
}
public List<Location> getRoute() {
return route;
}
public List<RouteDirectionInfo> getDirections() {
return directions;
}
}

View file

@ -3,15 +3,21 @@ package net.osmand.plus.onlinerouting.engine;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import net.osmand.Location;
import net.osmand.data.LatLon; import net.osmand.data.LatLon;
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.VehicleType; import net.osmand.plus.onlinerouting.VehicleType;
import net.osmand.plus.routing.RouteDirectionInfo;
import net.osmand.router.TurnType;
import net.osmand.util.GeoPolylineParserUtil; import net.osmand.util.GeoPolylineParserUtil;
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;
@ -23,8 +29,9 @@ public class GraphhopperEngine extends OnlineRoutingEngine {
super(params); super(params);
} }
@NonNull
@Override @Override
public @NonNull EngineType getType() { public EngineType getType() {
return EngineType.GRAPHHOPPER; return EngineType.GRAPHHOPPER;
} }
@ -71,15 +78,49 @@ public class GraphhopperEngine extends OnlineRoutingEngine {
if (!isEmpty(apiKey)) { if (!isEmpty(apiKey)) {
sb.append('&').append("key=").append(apiKey); sb.append('&').append("key=").append(apiKey);
} }
sb.append('&').append("details=").append("lanes");
} }
@NonNull @Nullable
@Override @Override
public List<LatLon> parseServerResponse(@NonNull String content) throws JSONException { public OnlineRoutingResponse parseServerResponse(@NonNull String content,
boolean leftSideNavigation) throws JSONException {
JSONObject obj = new JSONObject(content); JSONObject obj = new JSONObject(content);
return GeoPolylineParserUtil.parse( JSONObject root = obj.getJSONArray("paths").getJSONObject(0);
obj.getJSONArray("paths").getJSONObject(0).getString("points"),
GeoPolylineParserUtil.PRECISION_5); String encoded = root.getString("points");
List<LatLon> points = GeoPolylineParserUtil.parse(encoded, GeoPolylineParserUtil.PRECISION_5);
if (isEmpty(points)) return null;
List<Location> route = convertRouteToLocationsList(points);
JSONArray instructions = root.getJSONArray("instructions");
List<RouteDirectionInfo> 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 @Override
@ -92,4 +133,82 @@ public class GraphhopperEngine extends OnlineRoutingEngine {
} }
return obj.has("paths"); 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;
}
} }

View file

@ -5,11 +5,15 @@ import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.Location;
import net.osmand.data.LatLon; import net.osmand.data.LatLon;
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;
import net.osmand.plus.onlinerouting.OnlineRoutingResponse;
import net.osmand.plus.onlinerouting.VehicleType; import net.osmand.plus.onlinerouting.VehicleType;
import net.osmand.plus.routing.RouteProvider;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import org.json.JSONException; import org.json.JSONException;
@ -28,7 +32,8 @@ import static net.osmand.util.Algorithms.isEmpty;
public abstract class OnlineRoutingEngine implements Cloneable { 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 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<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<>();
@ -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 @NonNull
public String getFullUrl(@NonNull List<LatLon> path) { public String getFullUrl(@NonNull List<LatLon> path) {
StringBuilder sb = new StringBuilder(getBaseUrl()); StringBuilder sb = new StringBuilder(getBaseUrl());
@ -91,11 +87,35 @@ public abstract class OnlineRoutingEngine implements Cloneable {
@NonNull List<LatLon> path); @NonNull List<LatLon> path);
@NonNull @NonNull
public abstract List<LatLon> parseServerResponse(@NonNull String content) throws JSONException; public String getBaseUrl() {
String customUrl = get(EngineParameter.CUSTOM_URL);
if (isEmpty(customUrl)) {
return getStandardUrl();
}
return customUrl;
}
@NonNull @NonNull
public abstract String getStandardUrl(); public abstract String getStandardUrl();
@Nullable
public abstract OnlineRoutingResponse parseServerResponse(@NonNull String content,
boolean leftSideNavigation) throws JSONException;
@NonNull
protected List<Location> convertRouteToLocationsList(@NonNull List<LatLon> route) {
List<Location> 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 @NonNull
public Map<String, String> getParams() { public Map<String, String> getParams() {
return params; return params;

View file

@ -3,9 +3,11 @@ package net.osmand.plus.onlinerouting.engine;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import net.osmand.Location;
import net.osmand.data.LatLon; import net.osmand.data.LatLon;
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.VehicleType; import net.osmand.plus.onlinerouting.VehicleType;
import org.json.JSONArray; import org.json.JSONArray;
@ -24,8 +26,9 @@ public class OrsEngine extends OnlineRoutingEngine {
super(params); super(params);
} }
@NonNull
@Override @Override
public @NonNull EngineType getType() { public EngineType getType() {
return EngineType.ORS; return EngineType.ORS;
} }
@ -75,20 +78,25 @@ public class OrsEngine extends OnlineRoutingEngine {
} }
} }
@NonNull @Nullable
@Override @Override
public List<LatLon> parseServerResponse(@NonNull String content) throws JSONException { public OnlineRoutingResponse parseServerResponse(@NonNull String content,
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)
.getJSONObject("geometry").getJSONArray("coordinates"); .getJSONObject("geometry").getJSONArray("coordinates");
List<LatLon> track = new ArrayList<>(); List<LatLon> points = new ArrayList<>();
for (int i = 0; i < array.length(); i++) { for (int i = 0; i < array.length(); i++) {
JSONArray point = array.getJSONArray(i); JSONArray point = array.getJSONArray(i);
double lon = Double.parseDouble(point.getString(0)); double lon = Double.parseDouble(point.getString(0));
double lat = Double.parseDouble(point.getString(1)); 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<Location> route = convertRouteToLocationsList(points);
new OnlineRoutingResponse(route, null);
}
return null;
} }
@Override @Override

View file

@ -3,9 +3,11 @@ package net.osmand.plus.onlinerouting.engine;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import net.osmand.Location;
import net.osmand.data.LatLon; import net.osmand.data.LatLon;
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.VehicleType; import net.osmand.plus.onlinerouting.VehicleType;
import net.osmand.util.GeoPolylineParserUtil; import net.osmand.util.GeoPolylineParserUtil;
@ -24,7 +26,8 @@ public class OsrmEngine extends OnlineRoutingEngine {
} }
@Override @Override
public @NonNull EngineType getType() { public @NonNull
EngineType getType() {
return EngineType.OSRM; return EngineType.OSRM;
} }
@ -35,7 +38,8 @@ public class OsrmEngine extends OnlineRoutingEngine {
} }
@Override @Override
protected void collectAllowedParameters() { } protected void collectAllowedParameters() {
}
@Override @Override
protected void collectAllowedVehicles(@NonNull List<VehicleType> vehicles) { protected void collectAllowedVehicles(@NonNull List<VehicleType> vehicles) {
@ -60,15 +64,21 @@ public class OsrmEngine extends OnlineRoutingEngine {
} }
sb.append('?'); sb.append('?');
sb.append("overview=full"); sb.append("overview=full");
sb.append('&').append("steps=true");
} }
@NonNull @Nullable
@Override @Override
public List<LatLon> parseServerResponse(@NonNull String content) throws JSONException { public OnlineRoutingResponse parseServerResponse(@NonNull String content,
boolean leftSideNavigation) throws JSONException {
JSONObject obj = new JSONObject(content); JSONObject obj = new JSONObject(content);
return GeoPolylineParserUtil.parse( String encoded = obj.getJSONArray("routes").getJSONObject(0).getString("geometry");
obj.getJSONArray("routes").getJSONObject(0).getString("geometry"), List<LatLon> points = GeoPolylineParserUtil.parse(encoded, GeoPolylineParserUtil.PRECISION_5);
GeoPolylineParserUtil.PRECISION_5); if (!isEmpty(points)) {
List<Location> route = convertRouteToLocationsList(points);
return new OnlineRoutingResponse(route, null);
}
return null;
} }
@Override @Override

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().getValue() == TurnType.C) { if (r.getTurnType() != null && 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

@ -21,7 +21,7 @@ import net.osmand.data.LocationPoint;
import net.osmand.data.WptLocationPoint; import net.osmand.data.WptLocationPoint;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.onlinerouting.OnlineRoutingHelper; 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.OsmandSettings;
import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.settings.backend.CommonPreference;
import net.osmand.plus.R; 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"); Location loc = new Location("OsmandRouteProvider");
loc.setLatitude(pt.lat); loc.setLatitude(pt.lat);
loc.setLongitude(pt.lon); loc.setLongitude(pt.lon);
@ -1202,27 +1202,17 @@ public class RouteProvider {
private RouteCalculationResult findOnlineRoute(RouteCalculationParams params) throws IOException, JSONException { private RouteCalculationResult findOnlineRoute(RouteCalculationParams params) throws IOException, JSONException {
OnlineRoutingHelper helper = params.ctx.getOnlineRoutingHelper(); OnlineRoutingHelper helper = params.ctx.getOnlineRoutingHelper();
String stringKey = params.mode.getRoutingProfile(); String stringKey = params.mode.getRoutingProfile();
OnlineRoutingEngine engine = helper.getEngineByKey(stringKey); OnlineRoutingResponse response =
List<LatLon> route = null; helper.calculateRouteOnline(stringKey, getPathFromParams(params), params.leftSide);
if (engine != null) { if (response != null) {
route = helper.calculateRouteOnline(engine, getFullPathFromParams(params));
}
if (!Algorithms.isEmpty(route)) {
List<Location> res = new ArrayList<>();
for (LatLon pt : route) {
WptPt wpt = new WptPt();
wpt.lat = pt.getLatitude();
wpt.lon = pt.getLongitude();
res.add(createLocation(wpt));
}
params.intermediates = null; params.intermediates = null;
return new RouteCalculationResult(res, null, params, null, true); return new RouteCalculationResult(response.getRoute(), response.getDirections(), params, null, true);
} else { } else {
return new RouteCalculationResult("Route is empty"); return new RouteCalculationResult("Route is empty");
} }
} }
private static List<LatLon> getFullPathFromParams(RouteCalculationParams params) { private static List<LatLon> getPathFromParams(RouteCalculationParams params) {
List<LatLon> points = new ArrayList<>(); List<LatLon> points = new ArrayList<>();
points.add(new LatLon(params.start.getLatitude(), params.start.getLongitude())); points.add(new LatLon(params.start.getLatitude(), params.start.getLongitude()));
if (Algorithms.isEmpty(params.intermediates)) { if (Algorithms.isEmpty(params.intermediates)) {