Skip to content
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

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d7f6f5e
Initial approach to wrap the Mapbox API with GH
boldtrn Jul 9, 2018
4e3d3eb
Made endpoint a bit more Mapbox alike
boldtrn Jul 9, 2018
4919153
Experiment with voice instructions
boldtrn Jul 10, 2018
e0ad4b8
Added translation map to have actual instructions and improved placement
boldtrn Jul 10, 2018
e843011
Further improved voice instructions
boldtrn Jul 10, 2018
fcc116d
Fix empty object in instruction
boldtrn Jul 10, 2018
534c3bd
improve instruction distance
boldtrn Jul 10, 2018
83d2bd0
Stop avoiding simplification
boldtrn Jul 10, 2018
df73660
Improve destination instruction placement
boldtrn Jul 10, 2018
4abdaaa
added banner instruction and voice locale
boldtrn Jul 10, 2018
a9dcbc9
Fix components position
boldtrn Jul 10, 2018
76b0155
Fix components position
boldtrn Jul 10, 2018
4a6f4de
improved tts position
boldtrn Jul 10, 2018
22a94b9
Improve banner instructions
boldtrn Jul 10, 2018
ec5f58d
Improve Instructions
boldtrn Jul 10, 2018
b25252f
Improve Instructions
boldtrn Jul 10, 2018
febec77
Refactor code to make it more maintainable and readable, added error …
boldtrn Jul 11, 2018
610ee9e
Improve rounding
boldtrn Jul 12, 2018
2450679
Improve rounding
boldtrn Jul 12, 2018
f604a4f
Completely disable simplification
boldtrn Jul 12, 2018
6f637f0
fake bearing
boldtrn Jul 12, 2018
b1701b6
fake intersection
boldtrn Jul 12, 2018
d846e39
Remove out of intersection
boldtrn Jul 12, 2018
2ac4b7c
Inject TranslationMap
boldtrn Jul 12, 2018
6274cc7
Minor improvement
boldtrn Jul 12, 2018
fb293a3
Keep simplification active
boldtrn Jul 12, 2018
e4298ac
Revert simplification disabling
boldtrn Jul 12, 2018
23c0c7d
Improve parsing of multiple points
boldtrn Jul 19, 2018
9ec5045
Merge remote-tracking branch 'remotes/gh/master' into mapbox_api
boldtrn Jul 19, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions web-api/src/main/java/com/graphhopper/http/WebHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,20 @@ public static String encodePolyline(PointList poly) {
}

public static String encodePolyline(PointList poly, boolean includeElevation) {
return encodePolyline(poly, includeElevation, 1e5);
}

public static String encodePolyline(PointList poly, boolean includeElevation, double precision) {
Copy link
Member Author

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

StringBuilder sb = new StringBuilder();
int size = poly.getSize();
int prevLat = 0;
int prevLon = 0;
int prevEle = 0;
for (int i = 0; i < size; i++) {
int num = (int) Math.floor(poly.getLatitude(i) * 1e5);
int num = (int) Math.floor(poly.getLatitude(i) * precision);
encodeNumber(sb, num - prevLat);
prevLat = num;
num = (int) Math.floor(poly.getLongitude(i) * 1e5);
num = (int) Math.floor(poly.getLongitude(i) * precision);
encodeNumber(sb, num - prevLon);
prevLon = num;
if (includeElevation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,7 @@
import org.glassfish.hk2.utilities.binding.AbstractBinder;

import javax.inject.Inject;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -265,6 +262,7 @@ public void stop() throws Exception {

private void runRegularGraphHopper(CmdArgs configuration, Environment environment) {
final GraphHopperManaged graphHopperManaged = new GraphHopperManaged(configuration, environment.getObjectMapper());
final TranslationMap translationMap = graphHopperManaged.getGraphHopper().getTranslationMap();
environment.lifecycle().manage(graphHopperManaged);
environment.jersey().register(new AbstractBinder() {
@Override
Expand All @@ -273,6 +271,7 @@ protected void configure() {
bind(graphHopperManaged).to(GraphHopperManaged.class);
bind(graphHopperManaged.getGraphHopper()).to(GraphHopper.class);
bind(graphHopperManaged.getGraphHopper()).to(GraphHopperAPI.class);
bind(translationMap).to(TranslationMap.class);

bindFactory(HasElevation.class).to(Boolean.class).named("hasElevation");
bindFactory(LocationIndexFactory.class).to(LocationIndex.class);
Expand All @@ -288,6 +287,7 @@ protected void configure() {
}
environment.jersey().register(NearestResource.class);
environment.jersey().register(RouteResource.class);
environment.jersey().register(MapboxResource.class);
environment.jersey().register(IsochroneResource.class);
environment.jersey().register(I18NResource.class);
environment.jersey().register(InfoResource.class);
Expand Down
181 changes: 181 additions & 0 deletions web-bundle/src/main/java/com/graphhopper/resources/MapboxResource.java
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 {
Copy link
Member

Choose a reason for hiding this comment

The 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 :)

Copy link
Member Author

@boldtrn boldtrn Jul 12, 2018

Choose a reason for hiding this comment

The 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;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the following work?

@Path("/{profile}/{coordinatesString}")

and then String[] pointStrings = coordinatesString.split(";");

This way at least the first three lines can be avoided.

Copy link
Member Author

Choose a reason for hiding this comment

The 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 ;, everything else is parsed as matrix parameter, which is unsorted.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, ugly. And this means {coordinatesArray: .*} is no longer in use?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the parameter {coordinatesArray: .*} is no longer used. I could rename it or see if I can remove it?

Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it like this:

@Path("/{profile}/{coordinatesArray : .+}")
...
@PathParam("coordinatesArray") String coordinates

I think this is what you meant? It does not work. The variable coordinates only contains the first coordinate up to the first ;, everything after that seems to be read as matrix parameter.


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);
}
}
}