diff --git a/openrouteservice-api-tests/src/test/java/org/heigit/ors/v2/services/routing/ParamsTest.java b/openrouteservice-api-tests/src/test/java/org/heigit/ors/v2/services/routing/ParamsTest.java index e352b0cc2a..2df6903cf7 100644 --- a/openrouteservice-api-tests/src/test/java/org/heigit/ors/v2/services/routing/ParamsTest.java +++ b/openrouteservice-api-tests/src/test/java/org/heigit/ors/v2/services/routing/ParamsTest.java @@ -1828,4 +1828,215 @@ public void expectNoErrorOnSingleRadiusForMultipleCoordinates() { .body("any { it.key == 'routes' }", is(true)) .statusCode(200); } + // when given a user speed limit on a surface property, + // a route should be found and the user speed limit should be present in at least the returned query. + @Test + public void expectPassOnSurfaceSpeed() { + JSONObject userSpeedLimits = new JSONObject(); + JSONObject surfaceSpeedLimits = new JSONObject(); + surfaceSpeedLimits.put("gravel", 80); + userSpeedLimits.put("roadSpeeds", surfaceSpeedLimits); + + JSONObject body = new JSONObject(); + body.put("coordinates", getParameter("coordinatesShort")); + body.put("user_speed_limits", userSpeedLimits); + + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("profile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}") + .then() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) + .body("metadata.query.containsKey('user_speed_limits')", is(true)) + .statusCode(200); + } + + // when given a user speed limit on a road property, + // a route should be found and the user speed limit should be present in at least the returned query. + @Test + public void expectPassOnRoadSpeed() { + JSONObject userSpeedLimits = new JSONObject(); + JSONObject roadSpeedLimits = new JSONObject(); + roadSpeedLimits.put("motorway", 80); + userSpeedLimits.put("roadSpeeds", roadSpeedLimits); + + JSONObject body = new JSONObject(); + body.put("coordinates", getParameter("coordinatesShort")); + body.put("user_speed_limits", userSpeedLimits); + + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("profile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}") + .then() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) + .body("metadata.query.containsKey('user_speed_limits')", is(true)) + .statusCode(200); + } + + // when given a user speed limit and a custom unit, + // a route should be found and the user speed limit should be present in at least the returned query. + @Test + public void expectPassOnUserUnit() { + JSONObject userSpeedLimits = new JSONObject(); + JSONObject roadSpeedLimits = new JSONObject(); + roadSpeedLimits.put("motorway", 55); + userSpeedLimits.put("roadSpeeds", roadSpeedLimits); + userSpeedLimits.put("unit", "mph"); + + JSONObject body = new JSONObject(); + body.put("coordinates", getParameter("coordinatesShort")); + body.put("user_speed_limits", userSpeedLimits); + + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("profile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}") + .then() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) + .body("metadata.query.containsKey('user_speed_limits')", is(true)) + .body("metadata.query.user_speed_limits.containsKey('unit')", is(true)) + .body("metadata.query.user_speed_limits.unit", is("mph")) + .statusCode(200); + } + + @Test + public void expect2012OnUnknownKey() { + JSONObject userSpeedLimits = new JSONObject(); + JSONObject roadSpeedLimits = new JSONObject(); + roadSpeedLimits.put("primary", 80); + userSpeedLimits.put("unknownKey", roadSpeedLimits); + + JSONObject body = new JSONObject(); + body.put("coordinates", getParameter("coordinatesShort")); + body.put("user_speed_limits", userSpeedLimits); + + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("profile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}") + .then() + .assertThat() + .body("any { it.key == 'routes' }", is(false)) + .body("error.code", is(2012)) + .statusCode(400); + } + + + // when given a non-supported road type, an error should appear + @Test + public void expect2012onUnknownRoadType() { + JSONObject userSpeedLimits = new JSONObject(); + JSONObject roadSpeedLimits = new JSONObject(); + roadSpeedLimits.put("unknownProperty", 80); + userSpeedLimits.put("roadSpeeds", roadSpeedLimits); + + JSONObject body = new JSONObject(); + body.put("coordinates", getParameter("coordinatesShort")); + body.put("user_speed_limits", userSpeedLimits); + + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("profile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}") + .then() + .assertThat() + .body("any { it.key == 'routes' }", is(false)) + .body("error.code", is(2012)) + .statusCode(400); + } + + // when given a non-supported surface type, an error should appear + @Test + public void expect2012onUnknownSurfaceType() { + JSONObject userSpeedLimits = new JSONObject(); + JSONObject surfaceSpeedLimits = new JSONObject(); + surfaceSpeedLimits.put("unknownProperty", 80); + userSpeedLimits.put("surfaceSpeeds", surfaceSpeedLimits); + + JSONObject body = new JSONObject(); + body.put("coordinates", getParameter("coordinatesShort")); + body.put("user_speed_limits", userSpeedLimits); + + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("profile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}") + .then() + .assertThat() + .body("any { it.key == 'routes' }", is(false)) + .body("error.code", is(2012)) + .statusCode(400); + } + + // when given an invalid speed, an error should appear + @Test + public void expect2003onInvalidSpeed() { + JSONObject userSpeedLimits = new JSONObject(); + JSONObject surfaceSpeedLimits = new JSONObject(); + surfaceSpeedLimits.put("gravel", -80); + userSpeedLimits.put("surfaceSpeeds", surfaceSpeedLimits); + + JSONObject body = new JSONObject(); + body.put("coordinates", getParameter("coordinatesShort")); + body.put("user_speed_limits", userSpeedLimits); + + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("profile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}") + .then() + .assertThat() + .body("any { it.key == 'routes' }", is(false)) + .body("error.code", is(2003)) + .statusCode(400); + } + + // when given an unknown unit, an error should appear + @Test + public void expect2003onUnknownUnit(){ + JSONObject userSpeedLimits = new JSONObject(); + userSpeedLimits.put("unit", "unknownUnit"); + + JSONObject body = new JSONObject(); + body.put("coordinates", getParameter("coordinatesShort")); + body.put("user_speed_limits", userSpeedLimits); + + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("profile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}") + .then() + .assertThat() + .body("any { it.key == 'routes' }", is(false)) + .body("error.code", is(2003)) + .statusCode(400); + } } diff --git a/openrouteservice-api-tests/src/test/java/org/heigit/ors/v2/services/routing/ResultTest.java b/openrouteservice-api-tests/src/test/java/org/heigit/ors/v2/services/routing/ResultTest.java index e008e52991..7f5bb2f04c 100644 --- a/openrouteservice-api-tests/src/test/java/org/heigit/ors/v2/services/routing/ResultTest.java +++ b/openrouteservice-api-tests/src/test/java/org/heigit/ors/v2/services/routing/ResultTest.java @@ -3710,6 +3710,156 @@ public void expectMaxpeedHgvForward() { .statusCode(200); } + @Test + public void testUserRoadSpeed() { + JSONObject body = new JSONObject(); + JSONArray coords = new JSONArray(); + JSONArray neuenheim = new JSONArray(); + neuenheim.put(8.685036); + neuenheim.put(49.4201314); + JSONArray dossenheim = new JSONArray(); + dossenheim.put(8.671297); + dossenheim.put(49.4436); + coords.put(neuenheim); + coords.put(dossenheim); + body.put("coordinates", coords); + + // since we're testing on the same profile, "shortest" would not be dependent on speed settings + // and "recommended" will make too many assumptions on what route could be preferred. + body.put("preference", "fastest"); + + // request route without specifying user Speed + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when().log().ifValidationFails() + .post(getEndPointPath() + "/{profile}") + .then().log().ifValidationFails() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) + .body("routes[0].summary.distance", is(3822.6f)) + .body("routes[0].summary.duration", is(550.0f)) + .statusCode(200); + + JSONObject userSpeedLimits = new JSONObject(); + JSONObject roadSpeedLimits = new JSONObject(); + roadSpeedLimits.put("primary", 30); + roadSpeedLimits.put("secondary", 30); + userSpeedLimits.put("roadSpeeds", roadSpeedLimits); + body.put("user_speed_limits", userSpeedLimits); + + // request route limiting speed on primary and secondary roads to 30 + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when().log().ifValidationFails() + .post(getEndPointPath() + "/{profile}") + .then().log().ifValidationFails() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) + .body("routes[0].summary.distance", is(3958.0f)) + .body("routes[0].summary.duration", is(625.7f)) + .statusCode(200); + } + + @Test + public void testUserSurfaceSpeed() { + JSONObject body = new JSONObject(); + JSONArray coords = new JSONArray(); + JSONArray south = new JSONArray(); + south.put(8.707808); + south.put(49.398337); + JSONArray north = new JSONArray(); + north.put(8.710012); + north.put(49.405015); + coords.put(south); + coords.put(north); + body.put("coordinates", coords); + + // use "shortest" as "recommended" will make too many assumptions on what route could be preferred. + body.put("preference", "shortest"); + + // request route without specifying user Speed + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", "cycling-mountain") + .body(body.toString()) + .when().log().ifValidationFails() + .post(getEndPointPath() + "/{profile}") + .then().log().ifValidationFails() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) + .body("routes[0].summary.distance", is(1529.7f)) + .body("routes[0].summary.duration", is(349.2f)) + .statusCode(200); + + JSONObject userSpeedLimits = new JSONObject(); + JSONObject surfaceSpeedLimits = new JSONObject(); + surfaceSpeedLimits.put("gravel", 2); + surfaceSpeedLimits.put("ground", 2); + surfaceSpeedLimits.put("compacted", 2); + userSpeedLimits.put("surfaceSpeeds", surfaceSpeedLimits); + body.put("user_speed_limits", userSpeedLimits); + + // with modified speeds travel time should increase while the distance is expected to stay the same + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", "cycling-mountain") + .body(body.toString()) + .when().log().ifValidationFails() + .post(getEndPointPath() + "/{profile}") + .then().log().ifValidationFails() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) + .body("routes[0].summary.distance", is(1529.7f)) + .body("routes[0].summary.duration", is(greaterThan(349.2f))) + .statusCode(200); + } + + @Test + public void testUserSpeedUnit() { + JSONObject body = new JSONObject(); + JSONArray coords = new JSONArray(); + JSONArray neuenheim = new JSONArray(); + neuenheim.put(8.685036); + neuenheim.put(49.4201314); + JSONArray dossenheim = new JSONArray(); + dossenheim.put(8.671297); + dossenheim.put(49.4436); + coords.put(neuenheim); + coords.put(dossenheim); + body.put("coordinates", coords); + + // this is the same query as testUserRoadSpeed uses, but has speeds in mph + JSONObject userSpeedLimits = new JSONObject(); + JSONObject roadSpeedLimits = new JSONObject(); + roadSpeedLimits.put("primary", 18.6412f); + roadSpeedLimits.put("secondary", 18.6412f); + userSpeedLimits.put("roadSpeeds", roadSpeedLimits); + userSpeedLimits.put("unit", "mph"); + body.put("user_speed_limits", userSpeedLimits); + + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when().log().ifValidationFails() + .post(getEndPointPath() + "/{profile}") + .then().log().ifValidationFails() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) + .body("routes[0].summary.distance", is(3958.0f)) + .body("routes[0].summary.duration", is(625.7f)) + .statusCode(200); + } + private JSONArray constructBearings(String coordString) { JSONArray coordinates = new JSONArray(); String[] coordPairs = coordString.split("\\|"); diff --git a/openrouteservice/src/main/java/org/heigit/ors/api/requests/isochrones/IsochronesRequest.java b/openrouteservice/src/main/java/org/heigit/ors/api/requests/isochrones/IsochronesRequest.java index fd466cff73..ae7651c8d4 100644 --- a/openrouteservice/src/main/java/org/heigit/ors/api/requests/isochrones/IsochronesRequest.java +++ b/openrouteservice/src/main/java/org/heigit/ors/api/requests/isochrones/IsochronesRequest.java @@ -37,8 +37,10 @@ import org.heigit.ors.routing.RouteSearchParameters; import org.heigit.ors.routing.RoutingProfileManager; import org.heigit.ors.routing.RoutingProfileType; +import org.heigit.ors.routing.graphhopper.extensions.userspeed.RoadPropertySpeedParser; import org.heigit.ors.services.isochrones.IsochronesServiceSettings; import org.heigit.ors.util.DistanceUnitUtil; +import org.json.simple.JSONObject; import java.time.LocalDateTime; import java.util.Arrays; @@ -63,6 +65,7 @@ public class IsochronesRequest extends APIRequest { public static final String PARAM_INTERVAL = "interval"; public static final String PARAM_SMOOTHING = "smoothing"; public static final String PARAM_TIME = "time"; + public static final String PARAM_USER_SPEED_LIMITS = "user_speed_limits"; @ApiModelProperty(name = PARAM_LOCATIONS, value = "The locations to use for the route as an array of `longitude/latitude` pairs", @@ -173,6 +176,15 @@ public class IsochronesRequest extends APIRequest { @JsonIgnore private boolean hasTime = false; + @ApiModelProperty(name = PARAM_USER_SPEED_LIMITS, value = "Speed limits for various road and surface types provided by the user.", + example = "{ \"unit\": \"mph\"," + + "\"roadSpeeds\": { \"motorway\": 100, \"trunk\": 50 }," + + "\"surfaceSpeeds\": { \"paved\": 100, \"cobblestone\": 50, \"gravel\": 75 }}") + @JsonProperty(PARAM_USER_SPEED_LIMITS) + private JSONObject userSpeedLimits; + @JsonIgnore + private boolean hasUserSpeedLimits = false; + @JsonIgnore private IsochroneMapCollection isoMaps; @JsonIgnore @@ -362,6 +374,20 @@ public boolean hasTime() { return hasTime; } + public boolean hasUserSpeedLimits() { + return hasUserSpeedLimits; + } + + public JSONObject getUserSpeedLimits() { + return userSpeedLimits; + } + + public void setUserSpeedLimits(JSONObject userSpeedLimits) { + this.userSpeedLimits = userSpeedLimits; + hasUserSpeedLimits = true; + } + + public void generateIsochronesFromRequest() throws Exception { this.isochroneRequest = this.convertIsochroneRequest(); // request object is built, now check if ors config allows all settings @@ -543,6 +569,10 @@ RouteSearchParameters constructRouteSearchParameters() throws Exception { routeSearchParameters.setDeparture(this.getTime()); routeSearchParameters.setArrival(this.getTime()); } + if (this.hasUserSpeedLimits()) { + RoadPropertySpeedParser parser = new RoadPropertySpeedParser(); + routeSearchParameters.setRoadPropertySpeedMap(parser.parse(this.getUserSpeedLimits())); + } routeSearchParameters.setConsiderTurnRestrictions(false); return routeSearchParameters; } diff --git a/openrouteservice/src/main/java/org/heigit/ors/api/requests/routing/RouteRequest.java b/openrouteservice/src/main/java/org/heigit/ors/api/requests/routing/RouteRequest.java index f07d9716ce..a4111f9960 100644 --- a/openrouteservice/src/main/java/org/heigit/ors/api/requests/routing/RouteRequest.java +++ b/openrouteservice/src/main/java/org/heigit/ors/api/requests/routing/RouteRequest.java @@ -26,7 +26,9 @@ import org.heigit.ors.routing.*; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import org.heigit.ors.routing.graphhopper.extensions.userspeed.RoadPropertySpeedParser; import org.heigit.ors.util.StringUtility; +import org.json.simple.JSONObject; import java.time.LocalDateTime; import java.util.ArrayList; @@ -62,6 +64,7 @@ public class RouteRequest extends APIRequest { public static final String PARAM_DEPARTURE = "departure"; public static final String PARAM_ARRIVAL = "arrival"; public static final String PARAM_MAXIMUM_SPEED = "maximum_speed"; + public static final String PARAM_USER_SPEED_LIMITS = "user_speed_limits"; @ApiModelProperty(name = PARAM_COORDINATES, value = "The waypoints to use for the route as an array of `longitude/latitude` pairs", example = "[[8.681495,49.41461],[8.686507,49.41943],[8.687872,49.420318]]", @@ -267,6 +270,15 @@ public class RouteRequest extends APIRequest { @JsonIgnore private boolean hasMaximumSpeed = false; + @ApiModelProperty(name = PARAM_USER_SPEED_LIMITS, value = "Speed limits for various road and surface types provided by the user.", + example = "{ \"unit\": \"mph\"," + + "\"roadSpeeds\": { \"motorway\": 100, \"trunk\": 50 }," + + "\"surfaceSpeeds\": { \"paved\": 100, \"cobblestone\": 50, \"gravel\": 75 }}") + @JsonProperty(PARAM_USER_SPEED_LIMITS) + private JSONObject userSpeedLimits; + @JsonIgnore + private boolean hasUserSpeedLimits = false; + @JsonCreator public RouteRequest(@JsonProperty(value = PARAM_COORDINATES, required = true) List> coordinates) { this.coordinates = coordinates; @@ -531,6 +543,16 @@ public double getMaximumSpeed() { return maximumSpeed; } + public JSONObject getUserSpeedLimits() { + return userSpeedLimits; + } + + public void setUserSpeedLimits(JSONObject userSpeedLimits) { + this.userSpeedLimits = userSpeedLimits; + hasUserSpeedLimits = true; + } + + public boolean hasIncludeRoundaboutExitInfo() { return hasIncludeRoundaboutExitInfo; } @@ -611,6 +633,10 @@ public boolean hasMaximumSpeed() { return hasMaximumSpeed; } + public boolean hasUserSpeedLimits() { + return hasUserSpeedLimits; + } + public RouteResult[] generateRouteFromRequest() throws StatusCodeException { RoutingRequest routingRequest = this.convertRouteRequest(); @@ -755,6 +781,10 @@ else if (this.hasArrival()) if (this.hasMaximumSpeed()) { params.setMaximumSpeed(maximumSpeed); } + if (this.hasUserSpeedLimits()) { + RoadPropertySpeedParser parser = new RoadPropertySpeedParser(); + params.setRoadPropertySpeedMap(parser.parse(this.getUserSpeedLimits())); + } params.setConsiderTurnRestrictions(false); diff --git a/openrouteservice/src/main/java/org/heigit/ors/routing/RouteSearchParameters.java b/openrouteservice/src/main/java/org/heigit/ors/routing/RouteSearchParameters.java index d3479421b6..b1c0a92770 100644 --- a/openrouteservice/src/main/java/org/heigit/ors/routing/RouteSearchParameters.java +++ b/openrouteservice/src/main/java/org/heigit/ors/routing/RouteSearchParameters.java @@ -28,6 +28,7 @@ import org.heigit.ors.exceptions.UnknownParameterValueException; import org.heigit.ors.geojson.GeometryJSON; import org.heigit.ors.routing.graphhopper.extensions.HeavyVehicleAttributes; +import org.heigit.ors.routing.graphhopper.extensions.userspeed.RoadPropertySpeedMap; import org.heigit.ors.routing.graphhopper.extensions.VehicleLoadCharacteristicsFlags; import org.heigit.ors.routing.graphhopper.extensions.WheelchairTypesEncoder; import org.heigit.ors.routing.graphhopper.extensions.reader.borders.CountryBordersReader; @@ -92,6 +93,8 @@ public class RouteSearchParameters { private LocalDateTime departure; private LocalDateTime arrival; + private RoadPropertySpeedMap roadPropertySpeedMap; + public int getProfileType() { return profileType; } @@ -599,7 +602,8 @@ public boolean requiresFullyDynamicWeights() { || hasBearings() || hasContinueStraight() || (getProfileParameters() != null && getProfileParameters().hasWeightings()) - || getAlternativeRoutesCount() > 0; + || getAlternativeRoutesCount() > 0 + || hasRoadPropertySpeedMap(); } // time-dependent stuff @@ -629,4 +633,15 @@ public boolean isTimeDependent() { return (hasDeparture() || hasArrival()); } + public RoadPropertySpeedMap getRoadPropertySpeedMap() { + return roadPropertySpeedMap; + } + + public void setRoadPropertySpeedMap(RoadPropertySpeedMap map) { + this.roadPropertySpeedMap = map; + } + + public boolean hasRoadPropertySpeedMap() { + return this.roadPropertySpeedMap != null; + } } diff --git a/openrouteservice/src/main/java/org/heigit/ors/routing/RoutingProfile.java b/openrouteservice/src/main/java/org/heigit/ors/routing/RoutingProfile.java index b1f2d20aa9..ab0a79572a 100644 --- a/openrouteservice/src/main/java/org/heigit/ors/routing/RoutingProfile.java +++ b/openrouteservice/src/main/java/org/heigit/ors/routing/RoutingProfile.java @@ -773,6 +773,12 @@ else if (profileType == RoutingProfileType.WHEELCHAIR) { props.put("avoid_countries", Arrays.toString(searchParams.getAvoidCountries())); } + /* User defined road property dependent speeds */ + if (searchParams.hasRoadPropertySpeedMap()) { + props.put("user_speeds", true); + props.putObj("user_speeds", searchParams.getRoadPropertySpeedMap()); + } + if (profileParams != null && profileParams.hasWeightings()) { props.put(KEY_CUSTOM_WEIGHTINGS, true); Iterator iterator = profileParams.getWeightings().getIterator(); @@ -998,7 +1004,7 @@ private int getFlexibilityMode(int flexibleMode, RouteSearchParameters searchPar * @param profileType Necessary for HGV * @return Weighting as int */ - private void setWeighting(HintsMap map, int requestWeighting, int profileType, boolean hasTimeDependentSpeedorAccess) { + private void setWeighting(HintsMap map, int requestWeighting, int profileType, boolean hasTimeDependentSpeedOrAccess){ //Defaults String weighting = VAL_RECOMMENDED; String weightingMethod = VAL_RECOMMENDED; @@ -1021,7 +1027,7 @@ private void setWeighting(HintsMap map, int requestWeighting, int profileType, b map.put(KEY_WEIGHTING, weighting); map.put(KEY_WEIGHTING_METHOD, weightingMethod); - if (hasTimeDependentSpeedorAccess) + if (hasTimeDependentSpeedOrAccess) map.put(ORSParameters.Weighting.TIME_DEPENDENT_SPEED_OR_ACCESS, true); } diff --git a/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSGraphHopper.java b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSGraphHopper.java index 9662abaf94..a4a73f02bc 100644 --- a/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSGraphHopper.java +++ b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSGraphHopper.java @@ -71,6 +71,8 @@ import org.heigit.ors.routing.graphhopper.extensions.storages.TrafficGraphStorage; import org.heigit.ors.routing.graphhopper.extensions.storages.builders.GraphStorageBuilder; import org.heigit.ors.routing.graphhopper.extensions.storages.builders.HereTrafficGraphStorageBuilder; +import org.heigit.ors.routing.graphhopper.extensions.userspeed.RoadPropertySpeedCalculator; +import org.heigit.ors.routing.graphhopper.extensions.userspeed.RoadPropertySpeedMap; import org.heigit.ors.routing.graphhopper.extensions.util.ORSPMap; import org.heigit.ors.routing.graphhopper.extensions.util.ORSParameters; import org.heigit.ors.routing.graphhopper.extensions.weighting.HgvAccessWeighting; @@ -345,6 +347,19 @@ else if (ALT_ROUTE.equalsIgnoreCase(algoStr)) } } + try { + if (hints.has("user_speeds")) { + SpeedCalculator baseSpeedCalculator = weighting.getSpeedCalculator(); + RoadPropertySpeedCalculator roadPropertySpeedCalculator = new RoadPropertySpeedCalculator(baseSpeedCalculator, getGraphHopperStorage(), encoder); + RoadPropertySpeedMap roadPropertySpeedMap = (RoadPropertySpeedMap) ((ORSPMap) request.getAdditionalHints()).getObj("user_speeds"); + roadPropertySpeedCalculator.setRoadPropertySpeedMap(roadPropertySpeedMap); + weighting.setSpeedCalculator(roadPropertySpeedCalculator); + } + } + catch (IllegalStateException e){ + LOGGER.error("Unable to create RoadPropertySpeedMap " + e.getMessage()); + } + int maxVisitedNodesForRequest = hints.getInt(Parameters.Routing.MAX_VISITED_NODES, getMaxVisitedNodes()); if (maxVisitedNodesForRequest > getMaxVisitedNodes()) throw new IllegalArgumentException( diff --git a/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/PropertyType.java b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/PropertyType.java new file mode 100644 index 0000000000..2b18cfda43 --- /dev/null +++ b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/PropertyType.java @@ -0,0 +1,8 @@ +package org.heigit.ors.routing.graphhopper.extensions; + +public interface PropertyType { + public static PropertyType getFromString(String property) { + return null; + } + public int getOrdinal(); +} diff --git a/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/SurfaceType.java b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/SurfaceType.java index c0146153ed..ad266ad886 100644 --- a/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/SurfaceType.java +++ b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/SurfaceType.java @@ -1,89 +1,100 @@ /* This file is part of Openrouteservice. * - * Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the - * GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 + * Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. - * You should have received a copy of the GNU Lesser General Public License along with this library; - * if not, see . + * You should have received a copy of the GNU Lesser General Public License along with this library; + * if not, see . */ package org.heigit.ors.routing.graphhopper.extensions; -public class SurfaceType { - public static final int UNKNOWN = 0; - public static final int PAVED = 1; - public static final int UNPAVED = 2; - public static final int ASPHALT = 3; - public static final int CONCRETE = 4; - public static final int COBBLESTONE = 5; - public static final int METAL = 6; - public static final int WOOD = 7; - public static final int COMPACTED_GRAVEL = 8; - public static final int FINE_GRAVEL = 9; - public static final int GRAVEL = 10; - public static final int DIRT = 11; - public static final int GROUND = 12; - public static final int ICE = 13; - public static final int PAVING_STONE = 14; - public static final int SAND = 15; - public static final int WOODCHIPS = 16; - public static final int GRASS = 17; - public static final int GRASS_PAVER = 18; +public enum SurfaceType implements PropertyType { + UNKNOWN, + PAVED, + UNPAVED, + ASPHALT, + CONCRETE, + COBBLESTONE, + METAL, + WOOD, + COMPACTED_GRAVEL, + FINE_GRAVEL, + GRAVEL, + DIRT, + GROUND, + ICE, + PAVING_STONE, + SAND, + WOODCHIPS, + GRASS, + GRASS_PAVER; - private SurfaceType() {} + public static SurfaceType getFromString(String surface) { - public static int getFromString(String surface) { - - if (surface.contains(";")) - surface = surface.split(";")[0]; - - if ("paved".equalsIgnoreCase(surface)) { - return SurfaceType.PAVED; - } else if ("unpaved".equalsIgnoreCase(surface)) { - return SurfaceType.UNPAVED; - } else if ("asphalt".equalsIgnoreCase(surface)) { - return SurfaceType.ASPHALT; - } else if ("concrete".equalsIgnoreCase(surface) || "concrete:lanes".equalsIgnoreCase(surface) - || "concrete:plates".equalsIgnoreCase(surface)) { - return SurfaceType.CONCRETE; - } else if ("paving_stones".equalsIgnoreCase(surface) || "paving_stones:20".equalsIgnoreCase(surface) || "paving_stones:30".equalsIgnoreCase(surface) || "paving_stones:50".equalsIgnoreCase(surface) || "paved_stones".equalsIgnoreCase(surface)) { - return SurfaceType.PAVING_STONE; - } else if ("cobblestone:flattened".equalsIgnoreCase(surface) - || "sett".equalsIgnoreCase(surface)) { - return SurfaceType.PAVING_STONE; - } else if ("cobblestone".equalsIgnoreCase(surface)) { - return SurfaceType.COBBLESTONE; - } else if ("metal".equalsIgnoreCase(surface)) { - return SurfaceType.METAL; - } else if ("wood".equalsIgnoreCase(surface)) { - return SurfaceType.WOOD; - } else if ("compacted".equalsIgnoreCase(surface) || "pebblestone".equalsIgnoreCase(surface)) { - return SurfaceType.COMPACTED_GRAVEL; - } else if ("fine_gravel".equalsIgnoreCase(surface)) { - return SurfaceType.FINE_GRAVEL; - } else if ("gravel".equalsIgnoreCase(surface)) { - return SurfaceType.GRAVEL; - } else if ("dirt".equalsIgnoreCase(surface)) { - return SurfaceType.DIRT; - } else if ("ground".equalsIgnoreCase(surface) || "earth".equalsIgnoreCase(surface) - || "mud".equalsIgnoreCase(surface)) { - return SurfaceType.GROUND; - } else if ("ice".equalsIgnoreCase(surface) || "snow".equalsIgnoreCase(surface)) { - return SurfaceType.ICE; - } else if ("sand".equalsIgnoreCase(surface)) { - return SurfaceType.SAND; - } else if ("woodchips".equalsIgnoreCase(surface)) { - return SurfaceType.WOODCHIPS; - } else if ("grass".equalsIgnoreCase(surface)) { - return SurfaceType.GRASS; - } else if ("grass_paver".equalsIgnoreCase(surface)) { - return SurfaceType.GRASS_PAVER; - } + if (surface.contains(";")) + surface = surface.split(";")[0]; - return SurfaceType.UNKNOWN; - } + switch (surface.toLowerCase()) { + case "paved": + return SurfaceType.PAVED; + case "unpaved": + return SurfaceType.UNPAVED; + case "asphalt": + return SurfaceType.ASPHALT; + case "concrete": + case "concrete:lanes": + case "concrete:plates": + return SurfaceType.CONCRETE; + case "paving_stones": + case "paving_stones:20": + case "paving_stones:30": + case "paving_stones:50": + case "paved_stones": + case "cobblestone:flattened": + case "sett": + return SurfaceType.PAVING_STONE; + case "cobblestone": + return SurfaceType.COBBLESTONE; + case "metal": + return SurfaceType.METAL; + case "wood": + return SurfaceType.WOOD; + case "compacted": + case "pebblestone": + return SurfaceType.COMPACTED_GRAVEL; + case "fine_gravel": + return SurfaceType.FINE_GRAVEL; + case "gravel": + return SurfaceType.GRAVEL; + case "dirt": + return SurfaceType.DIRT; + case "ground": + case "earth": + case "mud": + return SurfaceType.GROUND; + case "ice": + case "snow": + return SurfaceType.ICE; + case "sand": + return SurfaceType.SAND; + case "woodchips": + return SurfaceType.WOODCHIPS; + case "grass": + return SurfaceType.GRASS; + case "grass_paver": + return SurfaceType.GRASS_PAVER; + default: + return SurfaceType.UNKNOWN; + } + } + + @Override + public int getOrdinal() { + return this.ordinal(); + } } diff --git a/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/WayType.java b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/WayType.java index 03ba1cb05d..df0189c73e 100644 --- a/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/WayType.java +++ b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/WayType.java @@ -1,67 +1,72 @@ /* This file is part of Openrouteservice. * - * Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the - * GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 + * Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. - * You should have received a copy of the GNU Lesser General Public License along with this library; - * if not, see . + * You should have received a copy of the GNU Lesser General Public License along with this library; + * if not, see . */ package org.heigit.ors.routing.graphhopper.extensions; -public class WayType { - public static final int UNKNOWN = 0; - public static final int STATE_ROAD = 1; - public static final int ROAD = 2; - public static final int STREET = 3; - public static final int PATH = 4; - public static final int TRACK = 5; - public static final int CYCLEWAY = 6; - public static final int FOOTWAY = 7; - public static final int STEPS = 8; - public static final int FERRY = 9; - public static final int CONSTRUCTION = 10; +public enum WayType implements PropertyType { + UNKNOWN, + STATE_ROAD, + ROAD, + STREET, + PATH, + TRACK, + CYCLEWAY, + FOOTWAY, + STEPS, + FERRY, + CONSTRUCTION; - private WayType() {} - - public static int getFromString(String highway) { - if ("primary".equalsIgnoreCase(highway) - || "primary_link".equalsIgnoreCase(highway) - || "motorway".equalsIgnoreCase(highway) - || "motorway_link".equalsIgnoreCase(highway) - || "trunk".equalsIgnoreCase(highway) - || "trunk_link".equalsIgnoreCase(highway)) { - return WayType.STATE_ROAD; - } else if ("secondary".equalsIgnoreCase(highway) - || "secondary_link".equalsIgnoreCase(highway) - || "tertiary".equalsIgnoreCase(highway) - || "tertiary_link".equalsIgnoreCase(highway) - || "road".equalsIgnoreCase(highway) - || "unclassified".equalsIgnoreCase(highway)) { - return WayType.ROAD; - } else if ("residential".equalsIgnoreCase(highway) - || "service".equalsIgnoreCase(highway) - || "living_street".equalsIgnoreCase(highway)) { - return WayType.STREET; - } else if ("path".equalsIgnoreCase(highway)) { - return WayType.PATH; - } else if ("track".equalsIgnoreCase(highway)) { - return WayType.TRACK; - } else if ("cycleway".equalsIgnoreCase(highway)) { - return WayType.CYCLEWAY; - } else if ("footway".equalsIgnoreCase(highway) - || "pedestrian".equalsIgnoreCase(highway) - || "crossing".equalsIgnoreCase(highway)) { - return WayType.FOOTWAY; - } else if ("steps".equalsIgnoreCase(highway)) { - return WayType.STEPS; - } else if ("construction".equalsIgnoreCase(highway)) { - return WayType.CONSTRUCTION; + public static WayType getFromString(String highway) { + switch(highway.toLowerCase()) { + case "primary": + case "primary_link": + case "motorway": + case "motorway_link": + case "trunk": + case "trunk_link": + return WayType.STATE_ROAD; + case "secondary": + case "secondary_link": + case "tertiary": + case "tertiary_link": + case "road": + case "unclassified": + return WayType.ROAD; + case "residential": + case "service": + case "living_street": + return WayType.STREET; + case "path": + return WayType.PATH; + case "track": + return WayType.TRACK; + case "cycleway": + return WayType.CYCLEWAY; + case "footway": + case "pedestrian": + case "crossing": + return WayType.FOOTWAY; + case "steps": + return WayType.STEPS; + case "construction": + return WayType.CONSTRUCTION; + default: + return WayType.UNKNOWN; } - return WayType.UNKNOWN; + } + + @Override + public int getOrdinal() { + return this.ordinal(); } } diff --git a/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WaySurfaceTypeGraphStorageBuilder.java b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WaySurfaceTypeGraphStorageBuilder.java index 360b3e8024..7e4b6a57c7 100644 --- a/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WaySurfaceTypeGraphStorageBuilder.java +++ b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WaySurfaceTypeGraphStorageBuilder.java @@ -1,15 +1,15 @@ /* This file is part of Openrouteservice. * - * Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the - * GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 + * Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. - * You should have received a copy of the GNU Lesser General Public License along with this library; - * if not, see . + * You should have received a copy of the GNU Lesser General Public License along with this library; + * if not, see . */ package org.heigit.ors.routing.graphhopper.extensions.storages.builders; @@ -25,58 +25,57 @@ import java.util.HashSet; public class WaySurfaceTypeGraphStorageBuilder extends AbstractGraphStorageBuilder { - public static final String TAG_HIGHWAY = "highway"; - public static final String TAG_SURFACE = "surface"; - public static final String TAG_ROUTE = "route"; - private WaySurfaceTypeGraphStorage storage; - private final WaySurfaceDescription waySurfaceDesc = new WaySurfaceDescription(); - protected final HashSet ferries; - - public WaySurfaceTypeGraphStorageBuilder() { - ferries = new HashSet<>(5); - ferries.add("shuttle_train"); - ferries.add("ferry"); - } - - public GraphExtension init(GraphHopper graphhopper) throws Exception { - if (storage != null) - throw new Exception("GraphStorageBuilder has been already initialized."); - - storage = new WaySurfaceTypeGraphStorage(); - return storage; - } + public static final String TAG_HIGHWAY = "highway"; + public static final String TAG_SURFACE = "surface"; + public static final String TAG_ROUTE = "route"; + protected final HashSet ferries; + private final WaySurfaceDescription waySurfaceDesc = new WaySurfaceDescription(); + private WaySurfaceTypeGraphStorage storage; - public void processWay(ReaderWay way) { - waySurfaceDesc.reset(); + public WaySurfaceTypeGraphStorageBuilder() { + ferries = new HashSet<>(5); + ferries.add("shuttle_train"); + ferries.add("ferry"); + } - int wayType; - if (way.hasTag(TAG_ROUTE, ferries)) { - wayType = WayType.FERRY; - } else if (way.hasTag(TAG_HIGHWAY)) { - wayType = WayType.getFromString(way.getTag(TAG_HIGHWAY)); - } else { - return; - } - waySurfaceDesc.setWayType(wayType); + public GraphExtension init(GraphHopper graphhopper) throws Exception { + if (storage != null) + throw new Exception("GraphStorageBuilder has been already initialized."); - int surfaceType = way.hasTag(TAG_SURFACE) ? SurfaceType.getFromString(way.getTag(TAG_SURFACE)) : SurfaceType.UNKNOWN; - if (surfaceType == SurfaceType.UNKNOWN) { - if (wayType == WayType.ROAD || wayType == WayType.STATE_ROAD || wayType == WayType.STREET) { - surfaceType = SurfaceType.PAVED; - } else if (wayType == WayType.PATH) { - surfaceType = SurfaceType.UNPAVED; - } - } - waySurfaceDesc.setSurfaceType(surfaceType); + storage = new WaySurfaceTypeGraphStorage(); + return storage; + } - } + public void processWay(ReaderWay way) { + waySurfaceDesc.reset(); - public void processEdge(ReaderWay way, EdgeIteratorState edge) { - storage.setEdgeValue(edge.getEdge(), waySurfaceDesc); - } + WayType wayType; + if (way.hasTag(TAG_ROUTE, ferries)) { + wayType = WayType.FERRY; + } else if (way.hasTag(TAG_HIGHWAY)) { + wayType = WayType.getFromString(way.getTag(TAG_HIGHWAY)); + } else { + return; + } + waySurfaceDesc.setWayType(wayType.ordinal()); - @Override - public String getName() { - return "WaySurfaceType"; - } + SurfaceType surfaceType = way.hasTag(TAG_SURFACE) ? SurfaceType.getFromString(way.getTag(TAG_SURFACE)) : SurfaceType.UNKNOWN; + if (surfaceType == SurfaceType.UNKNOWN) { + if (wayType == WayType.ROAD || wayType == WayType.STATE_ROAD || wayType == WayType.STREET) { + surfaceType = SurfaceType.PAVED; + } else if (wayType == WayType.PATH) { + surfaceType = SurfaceType.UNPAVED; + } + } + waySurfaceDesc.setSurfaceType(surfaceType.ordinal()); + } + + public void processEdge(ReaderWay way, EdgeIteratorState edge) { + storage.setEdgeValue(edge.getEdge(), waySurfaceDesc); + } + + @Override + public String getName() { + return "WaySurfaceType"; + } } diff --git a/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/userspeed/RoadPropertySpeedCalculator.java b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/userspeed/RoadPropertySpeedCalculator.java new file mode 100644 index 0000000000..4ce0178458 --- /dev/null +++ b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/userspeed/RoadPropertySpeedCalculator.java @@ -0,0 +1,58 @@ +package org.heigit.ors.routing.graphhopper.extensions.userspeed; + +import com.graphhopper.routing.EdgeIteratorStateHelper; +import com.graphhopper.routing.util.AbstractAdjustedSpeedCalculator; +import com.graphhopper.routing.util.FlagEncoder; +import com.graphhopper.routing.util.SpeedCalculator; +import com.graphhopper.storage.GraphHopperStorage; +import com.graphhopper.util.EdgeIteratorState; +import org.heigit.ors.routing.graphhopper.extensions.SurfaceType; +import org.heigit.ors.routing.graphhopper.extensions.WayType; +import org.heigit.ors.routing.graphhopper.extensions.storages.GraphStorageUtils; +import org.heigit.ors.routing.graphhopper.extensions.storages.WaySurfaceTypeGraphStorage; +import org.heigit.ors.routing.util.WaySurfaceDescription; + +public class RoadPropertySpeedCalculator extends AbstractAdjustedSpeedCalculator { + private static final double speedFactor = 0.9; //Adapted from ConditionalSpeedCalculator + private WaySurfaceTypeGraphStorage waySurfaceTypeGraphStorage; + private RoadPropertySpeedMap roadPropertySpeedMap; + private byte[] buffer = new byte[4]; + + public RoadPropertySpeedCalculator(SpeedCalculator superSpeedCalculator, GraphHopperStorage graphHopperStorage, FlagEncoder flagEncoder) { + super(superSpeedCalculator); + setWaySurfaceTypeGraphStorage(GraphStorageUtils.getGraphExtension(graphHopperStorage, WaySurfaceTypeGraphStorage.class)); + } + + /** + * Return the speed on an edge, potentially modified by user speed map + * + * @param edge + * @param reverse + * @param time currently unused + * @return speed of edge + */ + public double getSpeed(EdgeIteratorState edge, boolean reverse, long time) { + double speed = this.superSpeedCalculator.getSpeed(edge, reverse, time); + WaySurfaceDescription wsd = waySurfaceTypeGraphStorage.getEdgeValue(EdgeIteratorStateHelper.getOriginalEdge(edge), buffer); + Double surfaceTypeSpeed = roadPropertySpeedMap.getByTypedOrdinal(SurfaceType.class, wsd.getSurfaceType()); + if (surfaceTypeSpeed != null) + speed = Math.min(speed, speedFactor * surfaceTypeSpeed); + Double wayTypeSpeed = roadPropertySpeedMap.getByTypedOrdinal(WayType.class, wsd.getWayType()); + if (wayTypeSpeed != null) + speed = Math.min(speed, speedFactor * wayTypeSpeed); + return speed; + } + + @Override + public boolean isTimeDependent() { + return false; + } + + public void setWaySurfaceTypeGraphStorage(WaySurfaceTypeGraphStorage waySurfaceTypeGraphStorage) { + this.waySurfaceTypeGraphStorage = waySurfaceTypeGraphStorage; + } + + public void setRoadPropertySpeedMap(RoadPropertySpeedMap roadPropertySpeedMap) { + this.roadPropertySpeedMap = roadPropertySpeedMap; + } +} diff --git a/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/userspeed/RoadPropertySpeedMap.java b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/userspeed/RoadPropertySpeedMap.java new file mode 100644 index 0000000000..64366acfe4 --- /dev/null +++ b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/userspeed/RoadPropertySpeedMap.java @@ -0,0 +1,62 @@ +package org.heigit.ors.routing.graphhopper.extensions.userspeed; + +import org.heigit.ors.routing.graphhopper.extensions.PropertyType; +import org.heigit.ors.routing.graphhopper.extensions.SurfaceType; +import org.heigit.ors.routing.graphhopper.extensions.WayType; + +import java.util.HashMap; +import java.util.Map; + +public class RoadPropertySpeedMap { + private Map propToSpeed = new HashMap(); + + /** + * Add a maximum speed for a road property. Properties can be SurfaceType or WayType, defined in resp. files. + * @param property The property affected by the speed + * @param speed The maximum speed for this property + */ + public void addRoadPropertySpeed(String property, double speed) { + if(speed < 0) { + throw new IllegalArgumentException("Speed must be >= 0 but is " + speed); + } + SurfaceType surfaceType = SurfaceType.getFromString(property); + if (surfaceType != SurfaceType.UNKNOWN) { + propToSpeed.put(surfaceType, speed); + return; + } + WayType wayType = WayType.getFromString(property); + if (wayType != WayType.UNKNOWN) { + propToSpeed.put(wayType, speed); + return; + } + throw new IllegalArgumentException("Unknown property type: " + property); + } + + /** + * Get speed for property by ordinal of specified type + * @param type Class of property. Currently surfacetype and waytye + * @param ordinal ordinal of enum + * @return resp. speed + */ + public Double getByTypedOrdinal(Class type, int ordinal) { + for (Map.Entry entry : propToSpeed.entrySet()) { + if (type.isInstance(entry.getKey()) && entry.getKey().getOrdinal() == ordinal) + return entry.getValue(); + } + return null; + } + + /** + * Get speed for property by ordinal of specified type + * @param type Class of property. Currently surfacetype and waytye + * @param ordinal ordinal of enum + * @return resp. speed + */ + public Double getByTypedOrdinal(Class type, byte ordinal) { + for (Map.Entry entry : propToSpeed.entrySet()) { + if (type.isInstance(entry.getKey()) && entry.getKey().getOrdinal() == (int) ordinal) + return entry.getValue(); + } + return null; + } +} diff --git a/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/userspeed/RoadPropertySpeedParser.java b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/userspeed/RoadPropertySpeedParser.java new file mode 100644 index 0000000000..b5dee6541e --- /dev/null +++ b/openrouteservice/src/main/java/org/heigit/ors/routing/graphhopper/extensions/userspeed/RoadPropertySpeedParser.java @@ -0,0 +1,81 @@ +package org.heigit.ors.routing.graphhopper.extensions.userspeed; + +import org.heigit.ors.exceptions.*; +import org.heigit.ors.routing.RoutingErrorCodes; +import org.json.JSONObject; + +import java.util.Arrays; +import java.util.List; + +public class RoadPropertySpeedParser { + public RoadPropertySpeedMap parse(String input) throws StatusCodeException { + return parse(new JSONObject(input)); + } + + public RoadPropertySpeedMap parse(org.json.simple.JSONObject input) throws StatusCodeException { + return parse(input.toJSONString()); + } + + public RoadPropertySpeedMap parse(JSONObject json) throws StatusCodeException { + RoadPropertySpeedMap rsm = new RoadPropertySpeedMap(); + + // test that only valid keys are present: + List validKeys = Arrays.asList("unit", "roadSpeeds", "surfaceSpeeds"); + for (String key : json.keySet()) { + if (!validKeys.contains(key)) { + throw new UnknownParameterException(RoutingErrorCodes.UNKNOWN_PARAMETER, key); + } + } + + // parse units + String unit = "kmh"; + if (json.has("unit")) { + unit = json.getString("unit"); + } + + double unitFactor; + if (unit.equals("kmh")) { + unitFactor = 1.0; + } else if (unit.equals("mph")) { + unitFactor = 1.60934; + } else { + throw new ParameterValueException(RoutingErrorCodes.INVALID_PARAMETER_VALUE, "unit", unit); + } + + // parse road speeds + JSONObject roadSpeeds; + if (json.has("roadSpeeds")) { + roadSpeeds = json.getJSONObject("roadSpeeds"); + for (String roadType : roadSpeeds.keySet()) { + try { + rsm.addRoadPropertySpeed(roadType, roadSpeeds.getDouble(roadType) * unitFactor); + } catch (IllegalArgumentException e) { + if (e.getMessage().contains("must be")) { + throw new ParameterOutOfRangeException(RoutingErrorCodes.INVALID_PARAMETER_VALUE, "speed"); + } else { + throw new UnknownParameterException(RoutingErrorCodes.UNKNOWN_PARAMETER, e.getMessage().substring(23)); + } + } + } + } + + // parse surface speeds + JSONObject surfaceSpeeds; + if (json.has("surfaceSpeeds")) { + surfaceSpeeds = json.getJSONObject("surfaceSpeeds"); + for (String surfaceType : surfaceSpeeds.keySet()) { + try { + rsm.addRoadPropertySpeed(surfaceType, surfaceSpeeds.getDouble(surfaceType) * unitFactor); + } catch (IllegalArgumentException e) { + if (e.getMessage().contains("must be")) { + throw new ParameterOutOfRangeException(RoutingErrorCodes.INVALID_PARAMETER_VALUE, "speed"); + } else { + throw new UnknownParameterException(RoutingErrorCodes.UNKNOWN_PARAMETER, e.getMessage().substring(23)); + } + } + } + } + + return rsm; + } +} diff --git a/openrouteservice/src/test/java/org/heigit/ors/routing/graphhopper/extensions/roadproperty/RoadPropertyTest.java b/openrouteservice/src/test/java/org/heigit/ors/routing/graphhopper/extensions/roadproperty/RoadPropertyTest.java new file mode 100644 index 0000000000..cd766677b1 --- /dev/null +++ b/openrouteservice/src/test/java/org/heigit/ors/routing/graphhopper/extensions/roadproperty/RoadPropertyTest.java @@ -0,0 +1,142 @@ +package org.heigit.ors.routing.graphhopper.extensions.roadproperty; + +import com.graphhopper.routing.Dijkstra; +import com.graphhopper.routing.Path; +import com.graphhopper.routing.util.CarFlagEncoder; +import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.routing.util.TraversalMode; +import com.graphhopper.routing.weighting.FastestWeighting; +import com.graphhopper.routing.weighting.ShortestWeighting; +import com.graphhopper.routing.weighting.Weighting; +import com.graphhopper.storage.Graph; +import com.graphhopper.storage.GraphHopperStorage; +import com.graphhopper.storage.RAMDirectory; +import com.graphhopper.util.PMap; +import org.heigit.ors.routing.graphhopper.extensions.storages.WaySurfaceTypeGraphStorage; +import org.heigit.ors.routing.graphhopper.extensions.userspeed.RoadPropertySpeedCalculator; +import org.heigit.ors.routing.graphhopper.extensions.userspeed.RoadPropertySpeedMap; +import org.heigit.ors.routing.util.WaySurfaceDescription; +import org.heigit.ors.util.ToyGraphCreationUtil; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class RoadPropertyTest { + private final CarFlagEncoder carEncoder = new CarFlagEncoder(); + private final EncodingManager encodingManager = EncodingManager.create(carEncoder); + + @Test + public void testWeightAndTimeFastest() { + GraphHopperStorage graph = ToyGraphCreationUtil.createSimpleGraph(encodingManager); + + WaySurfaceTypeGraphStorage storage = new WaySurfaceTypeGraphStorage(); + storage.init(graph, new RAMDirectory()); + storage.create(10); + WaySurfaceDescription waySurfaceDesc = new WaySurfaceDescription(); + waySurfaceDesc.setSurfaceType(1); + storage.setEdgeValue(6, waySurfaceDesc); + RoadPropertySpeedMap roadPropertySpeedMap = new RoadPropertySpeedMap(); + roadPropertySpeedMap.addRoadPropertySpeed("paved", 30); + Weighting weighting = new FastestWeighting(carEncoder, new PMap()); + RoadPropertySpeedCalculator roadPropertySpeedCalculator = new RoadPropertySpeedCalculator(weighting.getSpeedCalculator(), graph, carEncoder); + roadPropertySpeedCalculator.setRoadPropertySpeedMap(roadPropertySpeedMap); + roadPropertySpeedCalculator.setWaySurfaceTypeGraphStorage(storage); + weighting.setSpeedCalculator(roadPropertySpeedCalculator); + + Path path = calcPath(5, 1, graph, weighting); + roadPropertySpeedMap.addRoadPropertySpeed("paved", 20); + Path path0 = calcPath(5, 1, graph, weighting); + assertTrue(path.getWeight() < path0.getWeight()); + assertTrue(path.getTime() < path0.getTime()); + assertEquals(path.getDistance(), path0.getDistance(), 0); + } + + @Test + public void testWeightAndTimeShortest() { + GraphHopperStorage graph = ToyGraphCreationUtil.createSimpleGraph(encodingManager); + + WaySurfaceTypeGraphStorage storage = new WaySurfaceTypeGraphStorage(); + storage.init(graph, new RAMDirectory()); + storage.create(10); + WaySurfaceDescription waySurfaceDesc = new WaySurfaceDescription(); + waySurfaceDesc.setSurfaceType(1); + storage.setEdgeValue(6, waySurfaceDesc); + RoadPropertySpeedMap roadPropertySpeedMap = new RoadPropertySpeedMap(); + roadPropertySpeedMap.addRoadPropertySpeed("paved", 30); + Weighting weighting = new ShortestWeighting(carEncoder); + RoadPropertySpeedCalculator roadPropertySpeedCalculator = new RoadPropertySpeedCalculator(weighting.getSpeedCalculator(), graph, carEncoder); + roadPropertySpeedCalculator.setRoadPropertySpeedMap(roadPropertySpeedMap); + roadPropertySpeedCalculator.setWaySurfaceTypeGraphStorage(storage); + weighting.setSpeedCalculator(roadPropertySpeedCalculator); + + Path path = calcPath(5, 1, graph, weighting); + roadPropertySpeedMap.addRoadPropertySpeed("paved", 20); + Path path0 = calcPath(5, 1, graph, weighting); + assertEquals(path.getWeight(), path0.getWeight(), 0); + assertTrue(path.getTime() < path0.getTime()); + assertEquals(path.getDistance(), path0.getDistance(), 0); + } + + @Test + public void testTooLargeValue() { + GraphHopperStorage graph = ToyGraphCreationUtil.createSimpleGraph(encodingManager); + + WaySurfaceTypeGraphStorage storage = new WaySurfaceTypeGraphStorage(); + storage.init(graph, new RAMDirectory()); + storage.create(10); + WaySurfaceDescription waySurfaceDesc = new WaySurfaceDescription(); + waySurfaceDesc.setSurfaceType(1); + storage.setEdgeValue(6, waySurfaceDesc); + RoadPropertySpeedMap roadPropertySpeedMap = new RoadPropertySpeedMap(); + Weighting weighting = new FastestWeighting(carEncoder, new PMap()); + RoadPropertySpeedCalculator roadPropertySpeedCalculator = new RoadPropertySpeedCalculator(weighting.getSpeedCalculator(), graph, carEncoder); + roadPropertySpeedCalculator.setRoadPropertySpeedMap(roadPropertySpeedMap); + roadPropertySpeedCalculator.setWaySurfaceTypeGraphStorage(storage); + weighting.setSpeedCalculator(roadPropertySpeedCalculator); + + Path path = calcPath(5, 1, graph, weighting); + roadPropertySpeedMap.addRoadPropertySpeed("paved", 3000); + Path path0 = calcPath(5, 1, graph, weighting); + assertEquals(path.getWeight(), path0.getWeight(), 0); + assertEquals(path.getTime(), path0.getTime(), 0); + assertEquals(path.getDistance(), path0.getDistance(), 0); + } + + @Test(expected = IllegalArgumentException.class) + public void testTooSmallValue() { + GraphHopperStorage graph = ToyGraphCreationUtil.createSimpleGraph(encodingManager); + + WaySurfaceTypeGraphStorage storage = new WaySurfaceTypeGraphStorage(); + storage.init(graph, new RAMDirectory()); + storage.create(10); + WaySurfaceDescription waySurfaceDesc = new WaySurfaceDescription(); + waySurfaceDesc.setSurfaceType(1); + storage.setEdgeValue(6, waySurfaceDesc); + RoadPropertySpeedMap roadPropertySpeedMap = new RoadPropertySpeedMap(); + roadPropertySpeedMap.addRoadPropertySpeed("paved", -10); + } + + @Test(expected = IllegalArgumentException.class) + public void testUnknownValue() { + GraphHopperStorage graph = ToyGraphCreationUtil.createSimpleGraph(encodingManager); + + WaySurfaceTypeGraphStorage storage = new WaySurfaceTypeGraphStorage(); + storage.init(graph, new RAMDirectory()); + storage.create(10); + WaySurfaceDescription waySurfaceDesc = new WaySurfaceDescription(); + waySurfaceDesc.setSurfaceType(1); + storage.setEdgeValue(6, waySurfaceDesc); + RoadPropertySpeedMap roadPropertySpeedMap = new RoadPropertySpeedMap(); + roadPropertySpeedMap.addRoadPropertySpeed("pavedD", 50); + } + + private Path calcPath(int source, int target, Graph graph, Weighting w) { + Dijkstra algo = createAlgo(graph, w); + return algo.calcPath(source, target); + } + + private Dijkstra createAlgo(Graph graph, Weighting weighting) { + return new Dijkstra(graph, weighting, TraversalMode.EDGE_BASED); + } +}