-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Basic support for Mapbox / OSRM endpoint #1422
Changes from all commits
d7f6f5e
4e3d3eb
4919153
e0ad4b8
e843011
fcc116d
534c3bd
83d2bd0
df73660
4abdaaa
a9dcbc9
76b0155
4a6f4de
22a94b9
ec5f58d
b25252f
febec77
610ee9e
2450679
f604a4f
6f637f0
b1701b6
d846e39
2ac4b7c
6274cc7
fb293a3
e4298ac
23c0c7d
9ec5045
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
/* | ||
* Licensed to GraphHopper GmbH under one or more contributor | ||
* license agreements. See the NOTICE file distributed with this work for | ||
* additional information regarding copyright ownership. | ||
* | ||
* GraphHopper GmbH licenses this file to you under the Apache License, | ||
* Version 2.0 (the "License"); you may not use this file except in | ||
* compliance with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.graphhopper.resources; | ||
|
||
import com.graphhopper.GHRequest; | ||
import com.graphhopper.GHResponse; | ||
import com.graphhopper.GraphHopperAPI; | ||
import com.graphhopper.util.StopWatch; | ||
import com.graphhopper.util.TranslationMap; | ||
import com.graphhopper.util.shapes.GHPoint; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import javax.inject.Inject; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.ws.rs.*; | ||
import javax.ws.rs.container.ContainerRequestContext; | ||
import javax.ws.rs.core.Context; | ||
import javax.ws.rs.core.MediaType; | ||
import javax.ws.rs.core.Response; | ||
import javax.ws.rs.core.UriInfo; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import static com.graphhopper.util.Parameters.Routing.*; | ||
|
||
/** | ||
* Provides an endpoint that is compatible with the Mapbox API v5 | ||
* | ||
* See: https://www.mapbox.com/api-documentation/#directions | ||
* | ||
* @author Robin Boldt | ||
*/ | ||
@Path("mapbox/directions/v5/mapbox") | ||
public class MapboxResource { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to use OSRM for the Mapbox SDK? If yes, can we rename to OSRMResource? If no, this is perfect already :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually I don't think that this is possible easily. The problem is that the OSRM endpoint does not generate voice instructions nor banner instructions which are both important for the MapboxSDK. |
||
|
||
private static final Logger logger = LoggerFactory.getLogger(MapboxResource.class); | ||
|
||
private final GraphHopperAPI graphHopper; | ||
private final TranslationMap translationMap; | ||
|
||
@Inject | ||
public MapboxResource(GraphHopperAPI graphHopper, TranslationMap translationMap) { | ||
this.graphHopper = graphHopper; | ||
this.translationMap = translationMap; | ||
} | ||
|
||
@GET | ||
@Path("/{profile}/{coordinatesArray: .*}") | ||
@Produces({MediaType.APPLICATION_JSON}) | ||
public Response doGet( | ||
@Context HttpServletRequest httpReq, | ||
@Context UriInfo uriInfo, | ||
@Context ContainerRequestContext rc, | ||
@QueryParam("steps") @DefaultValue("false") boolean enableInstructions, | ||
@QueryParam("voice_instructions") @DefaultValue("false") boolean voiceInstructions, | ||
@QueryParam("banner_instructions") @DefaultValue("false") boolean bannerInstructions, | ||
@QueryParam("roundabout_exits") @DefaultValue("false") boolean roundaboutExits, | ||
@QueryParam("voice_units") @DefaultValue("metric") String voiceUnits, | ||
@QueryParam("overview") @DefaultValue("simplified") String overview, | ||
@QueryParam("geometries") @DefaultValue("polyline") String geometries, | ||
@QueryParam("language") @DefaultValue("en") String localeStr, | ||
@QueryParam("heading") List<Double> favoredHeadings, | ||
@PathParam("profile") String profile) { | ||
|
||
/* | ||
Mapbox always uses fastest or priority weighting, except for walking, it's shortest | ||
https://www.mapbox.com/api-documentation/#directions | ||
*/ | ||
final String weighting = "fastest"; | ||
|
||
/* | ||
Currently, the MapboxResponseConverter is pretty limited. | ||
Therefore, we enforce these values to make sure the client does not receive an unexpected answer. | ||
*/ | ||
if (!geometries.equals("polyline6")) | ||
throw new IllegalArgumentException("Currently, we only support polyline6"); | ||
if (!enableInstructions) | ||
throw new IllegalArgumentException("Currently, you need to enable steps"); | ||
if (!roundaboutExits) | ||
throw new IllegalArgumentException("Roundabout exits have to be enabled right now"); | ||
if (!voiceUnits.equals("metric")) | ||
throw new IllegalArgumentException("Voice units only support metric right now"); | ||
if (!voiceInstructions) | ||
throw new IllegalArgumentException("You need to enable voice instructions right now"); | ||
if (!bannerInstructions) | ||
throw new IllegalArgumentException("You need to enable banner instructions right now"); | ||
|
||
double minPathPrecision = 1; | ||
if (overview.equals("full")) | ||
minPathPrecision = 0; | ||
|
||
String vehicleStr = convertProfileToGraphHopperVehicleString(profile); | ||
List<GHPoint> requestPoints = getPointsFromRequest(httpReq, profile); | ||
|
||
StopWatch sw = new StopWatch().start(); | ||
|
||
// TODO: initialization with heading/bearings | ||
// TODO: how should we use the "continue_straight" parameter? This is analog to pass_through, would require disabling CH | ||
|
||
GHRequest request = new GHRequest(requestPoints); | ||
request.setVehicle(vehicleStr). | ||
setWeighting(weighting). | ||
setLocale(localeStr). | ||
getHints(). | ||
put(CALC_POINTS, true). | ||
put(INSTRUCTIONS, enableInstructions). | ||
put(WAY_POINT_MAX_DISTANCE, minPathPrecision); | ||
|
||
GHResponse ghResponse = graphHopper.route(request); | ||
|
||
float took = sw.stop().getSeconds(); | ||
String infoStr = httpReq.getRemoteAddr() + " " + httpReq.getLocale() + " " + httpReq.getHeader("User-Agent"); | ||
String logStr = httpReq.getQueryString() + " " + infoStr + " " + requestPoints + ", took:" | ||
+ took + ", " + weighting + ", " + vehicleStr; | ||
|
||
if (ghResponse.hasErrors()) { | ||
logger.error(logStr + ", errors:" + ghResponse.getErrors()); | ||
// Mapbox specifies 422 return type for input errors | ||
return Response.status(422).entity(MapboxResponseConverter.convertFromGHResponseError(ghResponse)). | ||
header("X-GH-Took", "" + Math.round(took * 1000)). | ||
build(); | ||
} else { | ||
return Response.ok(MapboxResponseConverter.convertFromGHResponse(ghResponse, translationMap, request.getLocale())). | ||
header("X-GH-Took", "" + Math.round(took * 1000)). | ||
build(); | ||
} | ||
} | ||
|
||
/** | ||
* This method is parsing the request URL String. Unfortunately it seems that there is no better options right now. | ||
* See: https://stackoverflow.com/q/51420380/1548788 | ||
* | ||
* The url looks like: ".../{profile}/1.522438,42.504606;1.527209,42.504776;1.526113,42.505144;1.527218,42.50529?.." | ||
*/ | ||
private List<GHPoint> getPointsFromRequest(HttpServletRequest httpServletRequest, String profile) { | ||
|
||
String url = httpServletRequest.getRequestURI(); | ||
url = url.replaceFirst("/mapbox/directions/v5/mapbox/" + profile + "/", ""); | ||
url = url.replaceAll("\\?[*]", ""); | ||
|
||
String[] pointStrings = url.split(";"); | ||
|
||
List<GHPoint> points = new ArrayList<>(pointStrings.length); | ||
for (int i = 0; i < pointStrings.length; i++) { | ||
points.add(GHPoint.fromStringLonLat(pointStrings[i])); | ||
} | ||
|
||
return points; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will the following work?
and then String[] pointStrings = coordinatesString.split(";"); This way at least the first three lines can be avoided. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, this was my first idea as well :). The problem is that the path parameter coordinate string would only contain the first coordinate up to the first There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, ugly. And this means There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the parameter There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you try out a different regex and see if this works? See e.g. https://stackoverflow.com/q/2291428/194609 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried it like this:
I think this is what you meant? It does not work. The variable |
||
|
||
private String convertProfileToGraphHopperVehicleString(String profile) { | ||
switch (profile) { | ||
case "driving": | ||
// driving-traffic is mapped to regular car as well | ||
case "driving-traffic": | ||
return "car"; | ||
case "walking": | ||
return "foot"; | ||
case "cycling": | ||
return "bike"; | ||
default: | ||
throw new IllegalArgumentException("Not supported profile: " + profile); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
passing the precision is required to support the request parameter
polyline6