All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.graphhopper.navigation.NavigateResource Maven / Gradle / Ivy

There is a newer version: 1.0-prelmfix
Show newest version
/*
 *  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.navigation;

import com.graphhopper.GHRequest;
import com.graphhopper.GHResponse;
import com.graphhopper.GraphHopperAPI;
import com.graphhopper.util.Helper;
import com.graphhopper.util.Parameters;
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.Collections;
import java.util.List;

import static com.graphhopper.util.Parameters.Routing.*;

/**
 * Provides an endpoint that is consumable with the Mapbox Navigation SDK. The Mapbox Navigation SDK consumes json
 * responses that follow the specification of the Mapbox API v5.
 *
 * See: https://www.mapbox.com/api-documentation/#directions
 *
 * The baseurl of this endpoint is: [YOUR-IP/HOST]/navigate
 * The version of this endpoint is: v5
 * The user of this endpoint is: gh
 *
 * @author Robin Boldt
 */
@Path("navigate/directions/v5/gh")
public class NavigateResource {

    private static final Logger logger = LoggerFactory.getLogger(NavigateResource.class);

    private final GraphHopperAPI graphHopper;
    private final TranslationMap translationMap;
    private static final TranslationMap navigateResponseConverterTranslationMap = new NavigateResponseConverterTranslationMap().doImport();

    @Inject
    public NavigateResource(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("bearings") @DefaultValue("") String bearings,
            @QueryParam("language") @DefaultValue("en") String localeStr,
            @PathParam("profile") String profile) {

        /*
            Mapbox always uses fastest or priority weighting, except for walking then it's shortest
            https://www.mapbox.com/api-documentation/#directions
         */
        final String weighting = "fastest";

        /*
            Currently, the NavigateResponseConverter 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 requestPoints = getPointsFromRequest(httpReq, profile);

        List favoredHeadings = getBearing(bearings);
        if (favoredHeadings.size() > 0 && favoredHeadings.size() != requestPoints.size()) {
            throw new IllegalArgumentException("Number of bearings and waypoints did not match");
        }

        StopWatch sw = new StopWatch().start();

        GHResponse ghResponse = calcRoute(favoredHeadings, requestPoints, vehicleStr, weighting, localeStr, enableInstructions, minPathPrecision);

        // Only do this, when there are more than 2 points, otherwise we use alternative routes
        if (!ghResponse.hasErrors() && favoredHeadings.size() > 0) {
            GHResponse noHeadingResponse = calcRoute(Collections.EMPTY_LIST, requestPoints, vehicleStr, weighting, localeStr, enableInstructions, minPathPrecision);
            if (ghResponse.getBest().getDistance() != noHeadingResponse.getBest().getDistance()) {
                ghResponse.getAll().add(noHeadingResponse.getBest());
            }
        }

        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(NavigateResponseConverter.convertFromGHResponseError(ghResponse)).
                    header("X-GH-Took", "" + Math.round(took * 1000)).
                    build();
        } else {
            return Response.ok(NavigateResponseConverter.convertFromGHResponse(ghResponse, translationMap, navigateResponseConverterTranslationMap, Helper.getLocale(localeStr))).
                    header("X-GH-Took", "" + Math.round(took * 1000)).
                    build();
        }
    }

    private GHResponse calcRoute(List favoredHeadings, List requestPoints, String vehicleStr, String weighting, String localeStr, boolean enableInstructions, double minPathPrecision) {
        GHRequest request;
        if (favoredHeadings.size() > 0) {
            request = new GHRequest(requestPoints, favoredHeadings);
        } else {
            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).
                put(Parameters.CH.DISABLE, true).
                put(Parameters.Routing.PASS_THROUGH, false);

        return graphHopper.route(request);
    }

    /**
     * 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 getPointsFromRequest(HttpServletRequest httpServletRequest, String profile) {

        String url = httpServletRequest.getRequestURI();
        url = url.replaceFirst("/navigate/directions/v5/gh/" + profile + "/", "");
        url = url.replaceAll("\\?[*]", "");

        String[] pointStrings = url.split(";");

        List points = new ArrayList<>(pointStrings.length);
        for (int i = 0; i < pointStrings.length; i++) {
            points.add(GHPoint.fromStringLonLat(pointStrings[i]));
        }

        return points;
    }

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

    static List getBearing(String bearingString) {
        if (bearingString == null || bearingString.isEmpty())
            return Collections.EMPTY_LIST;

        String[] bearingArray = bearingString.split(";", -1);
        List bearings = new ArrayList<>(bearingArray.length);

        for (int i = 0; i < bearingArray.length; i++) {
            String singleBearing = bearingArray[i];
            if (singleBearing.isEmpty()) {
                bearings.add(Double.NaN);
            } else {
                if (!singleBearing.contains(",")) {
                    throw new IllegalArgumentException("You passed an invalid bearings parameter " + bearingString);
                }
                String[] singleBearingArray = singleBearing.split(",");
                try {
                    bearings.add(Double.parseDouble(singleBearingArray[0]));
                } catch (NumberFormatException e) {
                    throw new IllegalArgumentException("You passed an invalid bearings parameter " + bearingString);
                }
            }
        }
        return bearings;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy