diff --git a/CHANGELOG.md b/CHANGELOG.md index 28789ee861..06d4132ca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added - Added support for ISO 3166-1 Alpha-2 / Alpha-3 codes for routing directions option avoid_countries (Issue #195) +- Added support for free hand route option/ skip segments (Issue #167) ### Fixed - Fixed `geometry_simplify` parameter, which had no effect before. `geometry_simplify` is incompatible with `extra_info` (#381) ### Changed diff --git a/openrouteservice-api-tests/src/test/java/heigit/ors/v2/services/routing/ParamsTest.java b/openrouteservice-api-tests/src/test/java/heigit/ors/v2/services/routing/ParamsTest.java index eba690f5b7..db4f7d6b10 100644 --- a/openrouteservice-api-tests/src/test/java/heigit/ors/v2/services/routing/ParamsTest.java +++ b/openrouteservice-api-tests/src/test/java/heigit/ors/v2/services/routing/ParamsTest.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.*; @@ -1103,9 +1104,31 @@ public void expectWarningsAndExtraInfo() { .assertThat() .body("any { it.key == 'routes' }", is(true)) .body("routes[0].containsKey('warnings')", is(true)) + .body("routes[0].warnings[0].containsKey('code')", is(true)) + .body("routes[0].warnings[0].containsKey('message')", is(true)) .body("routes[0].containsKey('extras')", is(true)) .body("routes[0].extras.containsKey('roadaccessrestrictions')", is(true)) .statusCode(200); + + given() + .header("Accept", "application/geo+json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/geojson") + .then().log().all() + .assertThat() + .body("any { it.key == 'features' }", is(true)) + .body("any { it.key == 'bbox' }", is(true)) + .body("any { it.key == 'type' }", is(true)) + .body("features[0].containsKey('properties')", is(true)) + .body("features[0].properties.containsKey('extras')", is(true)) + .body("features[0].properties.containsKey('warnings')", is(true)) + .body("features[0].properties.warnings[0].containsKey('code')", is(true)) + .body("features[0].properties.warnings[0].containsKey('message')", is(true)) + .body("features[0].properties.extras.containsKey('roadaccessrestrictions')", is(true)) + .statusCode(200); } @Test @@ -1158,4 +1181,161 @@ public void expectSimplifyIncompatibleWithExtraInfo() { .body("error.code", is(RoutingErrorCodes.INCOMPATIBLE_PARAMETERS)) .statusCode(400); } + + @Test + public void expectSkipSegmentsErrors() { + List skipSegmentsEmpty = new ArrayList<>(1); + List skipSegmentsTooHigh = new ArrayList<>(1); + List skipSegmentsTooSmall = new ArrayList<>(1); + List skipSegmentsTooMany = new ArrayList<>(3); + skipSegmentsEmpty.add(0, 0); + skipSegmentsTooHigh.add(0, 99); + skipSegmentsTooSmall.add(0, -99); + skipSegmentsTooMany.add(0, 1); + skipSegmentsTooMany.add(1, 1); + skipSegmentsTooMany.add(2, 1); + + JSONObject body = new JSONObject(); + body.put("coordinates", getParameter("coordinatesShort")); + + body.put("skip_segments", skipSegmentsEmpty); + given() + .header("Accept", "application/geo+json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/geojson") + .then().log().all() + .assertThat() + .body("error.code", is(RoutingErrorCodes.INVALID_PARAMETER_VALUE)) + .statusCode(400); + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/json") + .then().log().all() + .assertThat() + .body("error.code", is(RoutingErrorCodes.INVALID_PARAMETER_VALUE)) + .statusCode(400); + + body.put("skip_segments", skipSegmentsTooHigh); + given() + .header("Accept", "application/geo+json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/geojson") + .then().log().all() + .assertThat() + .body("error.code", is(RoutingErrorCodes.INVALID_PARAMETER_VALUE)) + .statusCode(400); + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/json") + .then().log().all() + .assertThat() + .body("error.code", is(RoutingErrorCodes.INVALID_PARAMETER_VALUE)) + .statusCode(400); + + body.put("skip_segments", skipSegmentsTooSmall); + given() + .header("Accept", "application/geo+json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/geojson") + .then().log().all() + .assertThat() + .body("error.code", is(RoutingErrorCodes.INVALID_PARAMETER_VALUE)) + .statusCode(400); + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/json") + .then().log().all() + .assertThat() + .body("error.code", is(RoutingErrorCodes.INVALID_PARAMETER_VALUE)) + .statusCode(400); + + body.put("skip_segments", skipSegmentsTooMany); + given() + .header("Accept", "application/geo+json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/geojson") + .then().log().all() + .assertThat() + .body("error.code", is(RoutingErrorCodes.INVALID_PARAMETER_VALUE)) + .statusCode(400); + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/json") + .then().log().all() + .assertThat() + .body("error.code", is(RoutingErrorCodes.INVALID_PARAMETER_VALUE)) + .statusCode(400); + } + + @Test + public void expectSkipSegmentsWarnings() { + List skipSegments = new ArrayList<>(1); + skipSegments.add(1); + + JSONObject body = new JSONObject(); + body.put("coordinates", getParameter("coordinatesShort")); + + + body.put("skip_segments", skipSegments); + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when().log().all() + .post(getEndPointPath() + "/{profile}/json") + .then().log().all() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) + .body("routes[0].containsKey('warnings')", is(true)) + .body("routes[0].warnings[0].containsKey('code')", is(true)) + .body("routes[0].warnings[0].containsKey('message')", is(true)) + .statusCode(200); + + given() + .header("Accept", "application/geo+json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/geojson") + .then().log().all() + .assertThat() + .body("any { it.key == 'features' }", is(true)) + .body("any { it.key == 'bbox' }", is(true)) + .body("any { it.key == 'type' }", is(true)) + .body("features[0].containsKey('properties')", is(true)) + .body("features[0].properties.containsKey('warnings')", is(true)) + .body("features[0].properties.warnings[0].containsKey('code')", is(true)) + .body("features[0].properties.warnings[0].containsKey('message')", is(true)) + .statusCode(200); + } } diff --git a/openrouteservice-api-tests/src/test/java/heigit/ors/v2/services/routing/ResultTest.java b/openrouteservice-api-tests/src/test/java/heigit/ors/v2/services/routing/ResultTest.java index ca3e465fe5..94f5ed4864 100644 --- a/openrouteservice-api-tests/src/test/java/heigit/ors/v2/services/routing/ResultTest.java +++ b/openrouteservice-api-tests/src/test/java/heigit/ors/v2/services/routing/ResultTest.java @@ -4,14 +4,14 @@ * http://www.giscience.uni-hd.de * http://www.heigit.org * - * under one or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information regarding copyright - * ownership. The GIScience licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in compliance + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. The GIScience 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. @@ -39,6 +39,8 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.hasItems; @@ -1788,6 +1790,28 @@ public void testAccessRestrictionsWarnings() { .body("routes[0].extras.containsKey('roadaccessrestrictions')", is(true)) .body("routes[0].extras.roadaccessrestrictions.values[1][2]", is(32)) .statusCode(200); + + given() + .header("Accept", "application/geo+json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/geojson") + .then().log().all() + .assertThat() + .body("any { it.key == 'features' }", is(true)) + .body("any { it.key == 'bbox' }", is(true)) + .body("any { it.key == 'type' }", is(true)) + .body("features[0].containsKey('properties')", is(true)) + .body("features[0].properties.containsKey('extras')", is(true)) + .body("features[0].properties.containsKey('warnings')", is(true)) + .body("features[0].properties.warnings[0].containsKey('code')", is(true)) + .body("features[0].properties.warnings[0].containsKey('message')", is(true)) + .body("features[0].properties.warnings[0].code", is(1)) + .body("features[0].properties.extras.containsKey('roadaccessrestrictions')", is(true)) + .body("features[0].properties.extras.roadaccessrestrictions.values[1][2]", is(32)) + .statusCode(200); } @Test @@ -1822,6 +1846,253 @@ public void testSimplifyHasLessWayPoints() { .statusCode(200); } + @Test + public void testSkipSegmentWarning() { + List skipSegments = new ArrayList<>(1); + skipSegments.add(1); + + JSONObject body = new JSONObject(); + body.put("coordinates", getParameter("coordinatesShort")); + + + body.put("skip_segments", skipSegments); + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when().log().all() + .post(getEndPointPath() + "/{profile}/json") + .then().log().all() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) + .body("routes[0].containsKey('warnings')", is(true)) + .body("routes[0].warnings[0].containsKey('code')", is(true)) + .body("routes[0].warnings[0].code", is(3)) + .statusCode(200); + + given() + .header("Accept", "application/geo+json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/geojson") + .then().log().all() + .assertThat() + .body("any { it.key == 'features' }", is(true)) + .body("any { it.key == 'bbox' }", is(true)) + .body("any { it.key == 'type' }", is(true)) + .body("features[0].containsKey('properties')", is(true)) + .body("features[0].properties.containsKey('warnings')", is(true)) + .body("features[0].properties.warnings[0].containsKey('code')", is(true)) + .body("features[0].properties.warnings[0].code", is(3)) + .statusCode(200); + } + + @Test + public void testSkipSegments() { + List skipSegments = new ArrayList<>(1); + skipSegments.add(1); + + JSONObject body = new JSONObject(); + body.put("skip_segments", skipSegments); + + body.put("coordinates", getParameter("coordinatesShort")); + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when().log().all() + .post(getEndPointPath() + "/{profile}/json") + .then().log().all() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) + .body("routes[0].summary.distance", is(1744.3f)) + .body("routes[0].containsKey('geometry')", is(true)) + .body("routes[0].containsKey('way_points')", is(true)) + .body("routes[0].geometry", is("gvqlHi`~s@ooAix@")) + .body("routes[0].way_points[0]", is(0)) + .body("routes[0].way_points[1]", is(1)) + .body("routes[0].segments[0].steps[0].distance", is(1744.3f)) + .body("routes[0].segments[0].steps[0].duration", is(0.0f)) + .body("routes[0].segments[0].steps[0].type", is(11)) + .body("routes[0].segments[0].steps[0].name", is("free hand route")) + .body("routes[0].segments[0].steps[0].containsKey('instruction')", is(true)) + .body("routes[0].segments[0].steps[0].containsKey('way_points')", is(true)) + .body("routes[0].segments[0].steps[0].way_points[0]", is(0)) + .body("routes[0].segments[0].steps[0].way_points[1]", is(1)) + + .body("routes[0].segments[0].steps[1].distance", is(0.0f)) + .body("routes[0].segments[0].steps[1].duration", is(0.0f)) + .body("routes[0].segments[0].steps[1].type", is(10)) + .body("routes[0].segments[0].steps[1].name", is("end of free hand route")) + .body("routes[0].segments[0].steps[1].containsKey('instruction')", is(true)) + .body("routes[0].segments[0].steps[1].containsKey('way_points')", is(true)) + .body("routes[0].segments[0].steps[1].way_points[0]", is(1)) + .body("routes[0].segments[0].steps[1].way_points[1]", is(1)) + + .body("routes[0].containsKey('warnings')", is(true)) + .body("routes[0].warnings[0].containsKey('code')", is(true)) + .body("routes[0].warnings[0].code", is(3)) + .statusCode(200); + + given() + .header("Accept", "application/geo+json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/geojson") + .then().log().all() + .assertThat() + .body("any { it.key == 'features' }", is(true)) + .body("any { it.key == 'bbox' }", is(true)) + .body("any { it.key == 'type' }", is(true)) + .body("features[0].containsKey('properties')", is(true)) + .body("features[0].containsKey('geometry')", is(true)) + .body("features[0].geometry.coordinates[0][0]", is(8.678613f)) + .body("features[0].geometry.coordinates[0][1]", is(49.411721f)) + .body("features[0].geometry.coordinates[1][0]", is(8.687782f)) + .body("features[0].geometry.coordinates[1][1]", is(49.424597f)) + .body("features[0].geometry.type", is("LineString")) + .body("features[0].properties.containsKey('segments')", is(true)) + .body("features[0].properties.containsKey('warnings')", is(true)) + .body("features[0].properties.containsKey('summary')", is(true)) + .body("features[0].properties.containsKey('way_points')", is(true)) + + .body("features[0].properties.segments[0].distance", is(1744.3f)) + .body("features[0].properties.segments[0].steps[0].distance", is(1744.3f)) + .body("features[0].properties.segments[0].steps[0].duration", is(0.0f)) + .body("features[0].properties.segments[0].steps[0].type", is(11)) + .body("features[0].properties.segments[0].steps[0].name", is("free hand route")) + .body("features[0].properties.segments[0].steps[0].containsKey('instruction')", is(true)) + .body("features[0].properties.segments[0].steps[0].containsKey('way_points')", is(true)) + .body("features[0].properties.segments[0].steps[0].way_points[0]", is(0)) + .body("features[0].properties.segments[0].steps[0].way_points[1]", is(1)) + + .body("features[0].properties.segments[0].steps[1].distance", is(0.0f)) + .body("features[0].properties.segments[0].steps[1].duration", is(0.0f)) + .body("features[0].properties.segments[0].steps[1].type", is(10)) + .body("features[0].properties.segments[0].steps[1].name", is("end of free hand route")) + .body("features[0].properties.segments[0].steps[1].containsKey('instruction')", is(true)) + .body("features[0].properties.segments[0].steps[1].containsKey('way_points')", is(true)) + .body("features[0].properties.segments[0].steps[1].way_points[0]", is(1)) + .body("features[0].properties.segments[0].steps[1].way_points[1]", is(1)) + + + .body("features[0].properties.summary.distance", is(1744.3f)) + .body("features[0].properties.way_points[0]", is(0)) + .body("features[0].properties.way_points[1]", is(1)) + + .body("features[0].properties.containsKey('warnings')", is(true)) + .body("features[0].properties.warnings[0].containsKey('code')", is(true)) + .body("features[0].properties.warnings[0].code", is(3)) + .statusCode(200); + + body.put("coordinates", getParameter("coordinatesLong")); + given() + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when().log().all() + .post(getEndPointPath() + "/{profile}/json") + .then().log().all() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) + .body("routes[0].summary.distance", is(10936.3f)) + .body("routes[0].containsKey('geometry')", is(true)) + .body("routes[0].containsKey('way_points')", is(true)) + .body("routes[0].geometry", is("gvqlHi`~s@hrBw`Fq@lAiEf@qEn@wH^[@i@BqAEuDu@qASgACi@B_BRs@N]L]" + + "X_A~@IJEFEBGFCBODSEUYMg@yAeKGq@O{CS{Bk@sEk@uDYkAGOSMK?IBIHGJQXg@p@cA`A_@f@MVIPs@pA_@j@GLEFg@j@" + + "gA~@k@v@KRMTo@tA_@lAa@fBW`B?J?D@DJFD?FC\\oAVk@l@q@z@a@|@Sn@Br@XZPPRHN@FDVARU`AStAGb@If@Ib@Q~@[" + + "fBm@dEEt@Ar@FbCCjBEl@O~@Kd@EPEROx@Kf@Sv@Sf@GPGPOZGDICCS?A@Ab@uA@G?C@m@OoAEy@?i@?SAm@EQEAEBQZKTC" + + "FGLKTm@rAEHEF]b@oCrBEN?@?@BB@?@@bAGz@MDBBH@JCLY^g@\\g@PQFIBcAh@_BzA_@^CBSV[t@Oh@G\\WlDKr@AJIh@I" + + "PE@JpE?d@?tA?rA?v@?n@@`@?HHfAJfARjB@TPdBJdAT|BBPDh@BNDZFr@D`@b@pEBVP~ARnBBLZxCD\\JhA@T[H_@HQFw@V" + + "eBh@m@NgAXo@PsA`@QDSFcBf@{@X_@LKBO@M@Y@C?[BmJ`Be@ROFO?qADqAFK?I@gA?{@Bk@@o@BiCHO@C?k@@m@HOD]VgA" + + "lA_AfAUREDC?Q?OBE@qBn@A@SHOJELCDgAb@q@\\mAt@y@f@y@XeBt@YJsBp@c@N{C`A_DfAuAf@MHKJQVEEACCGI?KB")) + .body("routes[0].way_points[0]", is(0)) + .body("routes[0].way_points[1]", is(1)) + .body("routes[0].segments[0].steps[0].distance", is(4499.5f)) + .body("routes[0].segments[0].steps[0].duration", is(561.2f)) + .body("routes[0].segments[0].steps[0].type", is(11)) + .body("routes[0].segments[0].steps[0].name", is("free hand route")) + .body("routes[0].segments[0].steps[0].containsKey('instruction')", is(true)) + .body("routes[0].segments[0].steps[0].containsKey('way_points')", is(true)) + .body("routes[0].segments[0].steps[0].way_points[0]", is(0)) + .body("routes[0].segments[0].steps[0].way_points[1]", is(1)) + + .body("routes[0].segments[0].steps[1].distance", is(0.0f)) + .body("routes[0].segments[0].steps[1].duration", is(0.0f)) + .body("routes[0].segments[0].steps[1].type", is(10)) + .body("routes[0].segments[0].steps[1].name", is("end of free hand route")) + .body("routes[0].segments[0].steps[1].containsKey('instruction')", is(true)) + .body("routes[0].segments[0].steps[1].containsKey('way_points')", is(true)) + .body("routes[0].segments[0].steps[1].way_points[0]", is(1)) + .body("routes[0].segments[0].steps[1].way_points[1]", is(1)) + + .body("routes[0].containsKey('warnings')", is(true)) + .body("routes[0].warnings[0].containsKey('code')", is(true)) + .body("routes[0].warnings[0].code", is(3)) + .statusCode(200); + + given() + .header("Accept", "application/geo+json") + .header("Content-Type", "application/json") + .pathParam("profile", getParameter("carProfile")) + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}/geojson") + .then().log().all() + .assertThat() + .body("any { it.key == 'features' }", is(true)) + .body("any { it.key == 'bbox' }", is(true)) + .body("any { it.key == 'type' }", is(true)) + .body("features[0].containsKey('properties')", is(true)) + .body("features[0].containsKey('geometry')", is(true)) + .body("features[0].geometry.type", is("LineString")) + .body("features[0].properties.containsKey('segments')", is(true)) + .body("features[0].properties.containsKey('warnings')", is(true)) + .body("features[0].properties.containsKey('summary')", is(true)) + .body("features[0].properties.containsKey('way_points')", is(true)) + + .body("features[0].properties.segments[0].distance", is(4499.5f)) + .body("features[0].properties.segments[0].duration", is(561.2f)) + .body("features[0].properties.segments[0].steps[0].distance", is(4499.5f)) + .body("features[0].properties.segments[0].steps[0].duration", is(561.2f)) + .body("features[0].properties.segments[0].steps[0].type", is(11)) + .body("features[0].properties.segments[0].steps[0].name", is("free hand route")) + .body("features[0].properties.segments[0].steps[0].containsKey('instruction')", is(true)) + .body("features[0].properties.segments[0].steps[0].containsKey('way_points')", is(true)) + .body("features[0].properties.segments[0].steps[0].way_points[0]", is(0)) + .body("features[0].properties.segments[0].steps[0].way_points[1]", is(1)) + + .body("features[0].properties.segments[0].steps[1].distance", is(0.0f)) + .body("features[0].properties.segments[0].steps[1].duration", is(0.0f)) + .body("features[0].properties.segments[0].steps[1].type", is(10)) + .body("features[0].properties.segments[0].steps[1].name", is("end of free hand route")) + .body("features[0].properties.segments[0].steps[1].containsKey('instruction')", is(true)) + .body("features[0].properties.segments[0].steps[1].containsKey('way_points')", is(true)) + .body("features[0].properties.segments[0].steps[1].way_points[0]", is(1)) + .body("features[0].properties.segments[0].steps[1].way_points[1]", is(1)) + + + .body("features[0].properties.summary.distance", is(10936.3f)) + .body("features[0].properties.summary.duration", is(1364.0f)) + .body("features[0].properties.way_points[0]", is(0)) + .body("features[0].properties.way_points[1]", is(1)) + + .body("features[0].properties.containsKey('warnings')", is(true)) + .body("features[0].properties.warnings[0].containsKey('code')", is(true)) + .body("features[0].properties.warnings[0].code", is(3)) + .statusCode(200); + + + + } + private JSONArray constructCoords(String coordString) { JSONArray coordinates = new JSONArray(); String[] coordPairs = coordString.split("\\|"); diff --git a/openrouteservice/src/main/java/heigit/ors/api/requests/routing/RouteRequest.java b/openrouteservice/src/main/java/heigit/ors/api/requests/routing/RouteRequest.java index 5444d31d7e..c358a5b7e8 100644 --- a/openrouteservice/src/main/java/heigit/ors/api/requests/routing/RouteRequest.java +++ b/openrouteservice/src/main/java/heigit/ors/api/requests/routing/RouteRequest.java @@ -55,6 +55,7 @@ public class RouteRequest { public static final String PARAM_OPTIONS = "options"; public static final String PARAM_SUPPRESS_WARNINGS = "suppress_warnings"; public static final String PARAM_SIMPLIFY_GEOMETRY = "geometry_simplify"; + public static final String PARAM_SKIP_SEGMENTS = "skip_segments"; @ApiModelProperty(name = PARAM_ID, value = "Arbitrary identification string of the request reflected in the meta information.") @@ -146,7 +147,7 @@ public class RouteRequest { "CUSTOM_KEYS:{'apiDefault':false}", example = "false") @JsonProperty(value = PARAM_MANEUVERS) - private Boolean incĺudeManeuvers; + private Boolean includeManeuvers; @JsonIgnore private boolean hasIncludeManeuvers = false; @@ -227,6 +228,14 @@ public class RouteRequest { @JsonIgnore private boolean hasSimplifyGeometry = false; + @ApiModelProperty(name = PARAM_SKIP_SEGMENTS, value = "Specifies the segments that should be skipped in the route calculation. " + + "A segment is the connection between two given coordinates and the counting starts with 1 for the connection between the first and second coordinate.", + example = "[2,4]") + @JsonProperty(PARAM_SKIP_SEGMENTS) + private List skipSegments; + @JsonIgnore + private boolean hasSkipSegments = false; + @JsonCreator public RouteRequest(@JsonProperty(value = PARAM_COORDINATES, required = true) List> coordinates) { this.coordinates = coordinates; @@ -395,12 +404,12 @@ public void setAttributes(APIEnums.Attributes[] attributes) { this.hasAttributes = true; } - public Boolean getIncĺudeManeuvers() { - return incĺudeManeuvers; + public Boolean getIncludeManeuvers() { + return includeManeuvers; } - public void setIncĺudeManeuvers(Boolean incĺudeManeuvers) { - this.incĺudeManeuvers = incĺudeManeuvers; + public void setIncludeManeuvers(Boolean includeManeuvers) { + this.includeManeuvers = includeManeuvers; hasIncludeManeuvers = true; } @@ -467,6 +476,15 @@ public void setSimplifyGeometry(boolean simplifyGeometry) { this.hasSimplifyGeometry = true; } + public List getSkipSegments(){ + return this.skipSegments; + } + + public void setSkipSegments(List skipSegments){ + this.skipSegments = skipSegments; + hasSkipSegments = true; + } + public boolean hasIncludeRoundaboutExitInfo() { return hasIncludeRoundaboutExitInfo; } @@ -534,4 +552,6 @@ public boolean hasSuppressWarnings() { public boolean hasSimplifyGeometry() { return hasSimplifyGeometry; } + + public boolean hasSkipSegments() { return hasSkipSegments;} } diff --git a/openrouteservice/src/main/java/heigit/ors/api/requests/routing/RouteRequestHandler.java b/openrouteservice/src/main/java/heigit/ors/api/requests/routing/RouteRequestHandler.java index a539e15d06..96f247ab3a 100644 --- a/openrouteservice/src/main/java/heigit/ors/api/requests/routing/RouteRequestHandler.java +++ b/openrouteservice/src/main/java/heigit/ors/api/requests/routing/RouteRequestHandler.java @@ -69,7 +69,7 @@ public RoutingRequest convertRouteRequest(RouteRequest request) throws StatusCo routingRequest.setIncludeGeometry(convertIncludeGeometry(request)); if (request.hasIncludeManeuvers()) - routingRequest.setIncludeManeuvers(request.getIncĺudeManeuvers()); + routingRequest.setIncludeManeuvers(request.getIncludeManeuvers()); if (request.hasIncludeInstructions()) routingRequest.setIncludeInstructions(request.getIncludeInstructionsInResponse()); @@ -99,6 +99,10 @@ public RoutingRequest convertRouteRequest(RouteRequest request) throws StatusCo } } + if (request.hasSkipSegments()) { + routingRequest.setSkipSegments(processSkipSegments(request)); + } + if(request.hasId()) routingRequest.setId(request.getId()); @@ -143,6 +147,26 @@ public RoutingRequest convertRouteRequest(RouteRequest request) throws StatusCo return routingRequest; } + private List processSkipSegments(RouteRequest request) throws ParameterOutOfRangeException, ParameterValueException, EmptyElementException { + List skipSegments = request.getSkipSegments(); + for (Integer skipSegment: skipSegments){ + if (skipSegment >= request.getCoordinates().size()) { + throw new ParameterOutOfRangeException(RoutingErrorCodes.INVALID_PARAMETER_VALUE, RouteRequest.PARAM_SKIP_SEGMENTS, skipSegment.toString(), String.valueOf(request.getCoordinates().size() - 1)); + } + if (skipSegment <= 0) { + throw new ParameterValueException(RoutingErrorCodes.INVALID_PARAMETER_VALUE, RouteRequest.PARAM_SKIP_SEGMENTS, skipSegments.toString(), "The individual skip_segments values have to be greater than 0."); + } + + } + if (skipSegments.size() > request.getCoordinates().size() - 1) { + throw new ParameterValueException(RoutingErrorCodes.INVALID_PARAMETER_VALUE, RouteRequest.PARAM_SKIP_SEGMENTS, skipSegments.toString(), "The amount of segments to skip shouldn't be more than segments in the coordinates."); + } + if (skipSegments.isEmpty()) { + throw new EmptyElementException(RoutingErrorCodes.EMPTY_ELEMENT, RouteRequest.PARAM_SKIP_SEGMENTS); + } + return skipSegments; + } + private RouteSearchParameters processRouteRequestOptions(RouteRequest request, RouteSearchParameters params) throws StatusCodeException { RouteRequestOptions routeOptions = request.getRouteOptions(); params = processRequestOptions(routeOptions,params); diff --git a/openrouteservice/src/main/java/heigit/ors/api/responses/routing/GeoJSONRouteResponseObjects/GeoJSONSummary.java b/openrouteservice/src/main/java/heigit/ors/api/responses/routing/GeoJSONRouteResponseObjects/GeoJSONSummary.java index 71313be5e6..d7a6a0b71d 100644 --- a/openrouteservice/src/main/java/heigit/ors/api/responses/routing/GeoJSONRouteResponseObjects/GeoJSONSummary.java +++ b/openrouteservice/src/main/java/heigit/ors/api/responses/routing/GeoJSONRouteResponseObjects/GeoJSONSummary.java @@ -22,7 +22,10 @@ import heigit.ors.api.responses.routing.JSONRouteResponseObjects.JSONSegment; import heigit.ors.api.responses.routing.JSONRouteResponseObjects.JSONSummary; import heigit.ors.routing.RouteResult; +import heigit.ors.routing.RouteWarning; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,12 +36,14 @@ public class GeoJSONSummary extends JSONSummary { private List segments; private int[] wayPoints; private Map extras; + private List warnings; public GeoJSONSummary(RouteResult result, List segments, Map extras, boolean includeElevation) { super(result, includeElevation); this.segments = segments; this.wayPoints = result.getWayPointsIndices(); this.extras = extras; + this.warnings = result.getWarnings(); } public List getSegments() { @@ -59,4 +64,16 @@ public Map getExtras() { public JSONSummary getSummary() { return new JSONSummary(this.distance, this.duration); } + + @JsonProperty("warnings") + public List getWarnings() { + List warningsMap = new ArrayList<>(); + for (RouteWarning warning: warnings) { + Map warningMap = new HashMap<>(); + warningMap.put("code", warning.getWarningCode()); + warningMap.put("message", warning.getWarningMessage()); + warningsMap.add(warningMap); + } + return warningsMap; + } } diff --git a/openrouteservice/src/main/java/heigit/ors/routing/RouteResultBuilder.java b/openrouteservice/src/main/java/heigit/ors/routing/RouteResultBuilder.java index 71133a042a..6651036ec4 100644 --- a/openrouteservice/src/main/java/heigit/ors/routing/RouteResultBuilder.java +++ b/openrouteservice/src/main/java/heigit/ors/routing/RouteResultBuilder.java @@ -110,6 +110,10 @@ public RouteResult createRouteResult(List routes, RoutingRequest req } } + if (request.getSkipSegments() != null && !request.getSkipSegments().isEmpty()) { + result.addWarning(new RouteWarning(RouteWarning.SKIPPED_SEGMENTS)); + } + for (int ri = 0; ri < nRoutes; ++ri) { GHResponse resp = routes.get(ri); diff --git a/openrouteservice/src/main/java/heigit/ors/routing/RouteWarning.java b/openrouteservice/src/main/java/heigit/ors/routing/RouteWarning.java index d905c4beb7..c4c9224c45 100644 --- a/openrouteservice/src/main/java/heigit/ors/routing/RouteWarning.java +++ b/openrouteservice/src/main/java/heigit/ors/routing/RouteWarning.java @@ -21,6 +21,7 @@ public class RouteWarning { public final static int ACCESS_RESTRICTION = 1; public final static int TOLLWAYS = 2; + public final static int SKIPPED_SEGMENTS = 3; private int warningCode = 0; private String warningMessage = ""; @@ -38,6 +39,9 @@ public RouteWarning(int warning) { case TOLLWAYS: warningMessage = "There are tollways along the route"; break; + case SKIPPED_SEGMENTS: + warningMessage = "There are skipped segments along the route. Durations and accessibility may not be correct"; + break; } } diff --git a/openrouteservice/src/main/java/heigit/ors/routing/RoutingProfile.java b/openrouteservice/src/main/java/heigit/ors/routing/RoutingProfile.java index cb9dbe449d..7adf42d769 100644 --- a/openrouteservice/src/main/java/heigit/ors/routing/RoutingProfile.java +++ b/openrouteservice/src/main/java/heigit/ors/routing/RoutingProfile.java @@ -761,15 +761,17 @@ else if (bearings[1] == null) if (_astarApproximation != null) req.getHints().put("astarbi.approximation", _astarApproximation); - /*if (directedSegment) - resp = mGraphHopper.directRoute(req); NOTE IMPLEMENTED!!! - else */ - - mGraphHopper.setSimplifyResponse(geometrySimplify); - resp = mGraphHopper.route(req); - - if (DebugUtility.isDebug()) { - System.out.println("visited_nodes.average - " + resp.getHints().get("visited_nodes.average", "")); + if (directedSegment) { + resp = mGraphHopper.constructFreeHandRoute(req); + } else { + mGraphHopper.setSimplifyResponse(geometrySimplify); + resp = mGraphHopper.route(req); + } + if (DebugUtility.isDebug() && !directedSegment) { + LOGGER.info("visited_nodes.average - " + resp.getHints().get("visited_nodes.average", "")); + } + if (DebugUtility.isDebug() && directedSegment) { + LOGGER.info("skipped segment - " + resp.getHints().get("skipped_segment", "")); } endUseGH(); @@ -807,7 +809,6 @@ private boolean useDynamicWeights(RouteSearchParameters searchParams) { * So the first step in the function is a checkup on that. * * @param parameters The input are {@link IsochroneSearchParameters} - * @param attributes The input are a {@link String}[] holding the attributes if set * @return The return will be an {@link IsochroneMap} * @throws Exception */ diff --git a/openrouteservice/src/main/java/heigit/ors/routing/RoutingProfileManager.java b/openrouteservice/src/main/java/heigit/ors/routing/RoutingProfileManager.java index f27caa24eb..1b3fe75758 100644 --- a/openrouteservice/src/main/java/heigit/ors/routing/RoutingProfileManager.java +++ b/openrouteservice/src/main/java/heigit/ors/routing/RoutingProfileManager.java @@ -347,12 +347,13 @@ public RouteResult matchTrack(MapMatchingRequest req) throws Exception { } public RouteResult computeRoute(RoutingRequest req) throws Exception { + List skipSegments = req.getSkipSegments(); List routes = new ArrayList(); RoutingProfile rp = getRouteProfile(req, false); RouteSearchParameters searchParams = req.getSearchParameters(); PathProcessor pathProcessor = null; - + pathProcessor = new ExtraInfoProcessor(rp.getGraphhopper(), req); Coordinate[] coords = req.getCoordinates(); @@ -388,11 +389,16 @@ public RouteResult computeRoute(RoutingRequest req) throws Exception { radiuses[1] = searchParams.getMaximumRadiuses()[i]; } - GHResponse gr = rp.computeRoute(c0.y, c0.x, c1.y, c1.x, bearings, radiuses, c0.z == 1.0, searchParams, customEdgeFilter, routeProcCntx, req.getGeometrySimplify()); + GHResponse gr; + if ((skipSegments.contains(i))) { + gr = rp.computeRoute(c0.y, c0.x, c1.y, c1.x, bearings, radiuses, true, searchParams, customEdgeFilter, routeProcCntx, req.getGeometrySimplify()); + } else { + gr = rp.computeRoute(c0.y, c0.x, c1.y, c1.x, bearings, radiuses, false, searchParams, customEdgeFilter, routeProcCntx, req.getGeometrySimplify()); + } if (gr.hasErrors()) { if (gr.getErrors().size() > 0) { - if(gr.getErrors().get(0) instanceof com.graphhopper.util.exceptions.ConnectionNotFoundException) { + if (gr.getErrors().get(0) instanceof com.graphhopper.util.exceptions.ConnectionNotFoundException) { throw new RouteNotFoundException( RoutingErrorCodes.ROUTE_NOT_FOUND, String.format("Unable to find a route between points %d (%s) and %d (%s).", @@ -430,10 +436,55 @@ public RouteResult computeRoute(RoutingRequest req) throws Exception { routes.add(gr); c0 = c1; } - + routes = enrichDirectRoutesTime(routes); return new RouteResultBuilder().createRouteResult(routes, req, (pathProcessor != null && (pathProcessor instanceof ExtraInfoProcessor)) ? ((ExtraInfoProcessor) pathProcessor).getExtras() : null); } + /** + * This will enrich all direct routes with an approximated travel time that is being calculated from the real graphhopper + * results. The routes object should contain all routes, so the function can maintain and return the proper order! + * + * @param routes Should hold all the routes that have been calculated, not only the direct routes. + * @return will return routes object with enriched direct routes if any we're found in the same order as the input object. + */ + private List enrichDirectRoutesTime(List routes) { + List graphhopperRoutes = new ArrayList<>(); + List directRoutes = new ArrayList<>(); + long graphHopperTravelTime = 0; + double graphHopperTravelDistance = 0; + double averageTravelTimePerMeter; + + for (GHResponse ghResponse : routes) { + if (!ghResponse.getHints().has("skipped_segment")) { + graphHopperTravelDistance += ghResponse.getBest().getDistance(); + graphHopperTravelTime += ghResponse.getBest().getTime(); + graphhopperRoutes.add(ghResponse); + } else { + directRoutes.add(ghResponse); + } + } + + if (graphhopperRoutes.isEmpty() || directRoutes.isEmpty()) { + return routes; + } + + if (graphHopperTravelDistance == 0) { + return routes; + } + + averageTravelTimePerMeter = graphHopperTravelTime / graphHopperTravelDistance; + for (GHResponse ghResponse : routes) { + if (ghResponse.getHints().has("skipped_segment")) { + double directRouteDistance = ghResponse.getBest().getDistance(); + ghResponse.getBest().setTime(Math.round(directRouteDistance * averageTravelTimePerMeter)); + double directRouteInstructionDistance = ghResponse.getBest().getInstructions().get(0).getDistance(); + ghResponse.getBest().getInstructions().get(0).setTime(Math.round(directRouteInstructionDistance * averageTravelTimePerMeter)); + } + } + + return routes; + } + private double getHeadingDirection(GHResponse resp) { PointList points = resp.getBest().getPoints(); int nPoints = points.size(); @@ -524,7 +575,6 @@ public RoutingProfile getRouteProfile(RoutingRequest req, boolean oneToMany) thr * This function sends the {@link IsochroneSearchParameters} together with the Attributes to the {@link RoutingProfile}. * * @param parameters The input is a {@link IsochroneSearchParameters} - * @param attributes The attributes are a {@link String}[] holding the set attributes from the api query * @return Return is a {@link IsochroneMap} holding the calculated data plus statistical data if the attributes where set. * @throws Exception */ diff --git a/openrouteservice/src/main/java/heigit/ors/routing/RoutingRequest.java b/openrouteservice/src/main/java/heigit/ors/routing/RoutingRequest.java index 1dc3519d46..43125b1912 100644 --- a/openrouteservice/src/main/java/heigit/ors/routing/RoutingRequest.java +++ b/openrouteservice/src/main/java/heigit/ors/routing/RoutingRequest.java @@ -1,202 +1,214 @@ -/* 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 - * 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. - * 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 . - */ -package heigit.ors.routing; - -import com.vividsolutions.jts.geom.Coordinate; - -import heigit.ors.common.DistanceUnit; -import heigit.ors.services.ServiceRequest; - -public class RoutingRequest extends ServiceRequest -{ - private Coordinate[] _coordinates; - private RouteSearchParameters _searchParameters; - private DistanceUnit _units = DistanceUnit.Meters; - private String _language = "en"; - private String _geometryFormat = "encodedpolyline"; - private Boolean _geometrySimplify = false; - private RouteInstructionsFormat _instructionsFormat = RouteInstructionsFormat.TEXT; - private Boolean _includeInstructions = true; - private Boolean _includeElevation = false; - private Boolean _includeGeometry = true; - private Boolean _includeManeuvers = false; - private boolean _includeRoundaboutExits = false; - private String[] _attributes = null; - private int _extraInfo; - private int _locationIndex = -1; - private boolean _continueStraight = false; - private Boolean _suppressWarnings = false; - - public RoutingRequest() - { - _searchParameters = new RouteSearchParameters(); - } - - public Coordinate[] getCoordinates() { - return _coordinates; - } - - public Coordinate getDestination() - { - return _coordinates[_coordinates.length - 1]; - } - - public void setCoordinates(Coordinate[] _coordinates) { - this._coordinates = _coordinates; - } - - public RouteSearchParameters getSearchParameters() { - return _searchParameters; - } - - public void setSearchParameters(RouteSearchParameters _searchParameters) { - this._searchParameters = _searchParameters; - } - - public boolean getIncludeInstructions() { - return _includeInstructions; - } - - public void setIncludeInstructions(boolean includeInstructions) { - _includeInstructions = includeInstructions; - } - - public DistanceUnit getUnits() { - return _units; - } - - public void setUnits(DistanceUnit units) { - _units = units; - } - - public String getGeometryFormat() { - return _geometryFormat; - } - - public void setGeometryFormat(String geometryFormat) { - _geometryFormat = geometryFormat; - } - - public boolean getGeometrySimplify() { return _geometrySimplify; } - - public void setGeometrySimplify(boolean geometrySimplify) { _geometrySimplify = geometrySimplify; } - - public String getLanguage() { - return _language; - } - - public void setLanguage(String language) { - _language = language; - } - - public RouteInstructionsFormat getInstructionsFormat() { - return _instructionsFormat; - } - - public void setInstructionsFormat(RouteInstructionsFormat format) { - _instructionsFormat = format; - } - - public int getExtraInfo() { - return _extraInfo; - } - - public void setExtraInfo(int extraInfo) { - _extraInfo = extraInfo; - } - - public Boolean getIncludeElevation() { - return _includeElevation; - } - - public void setIncludeElevation(Boolean includeElevation) { - this._includeElevation = includeElevation; - } - - public Boolean getIncludeGeometry() { - return _includeGeometry; - } - - public void setIncludeGeometry(Boolean includeGeometry) { - this._includeGeometry = includeGeometry; - } - - public String[] getAttributes() { - return _attributes; - } - - public void setAttributes(String[] attributes) { - _attributes = attributes; - } - - public boolean hasAttribute(String attr) { - if (_attributes == null || attr == null) - return false; - - for (int i = 0; i< _attributes.length; i++) - if (attr.equalsIgnoreCase(_attributes[i])) - return true; - - return false; - } - - public boolean getConsiderTraffic(){ - return this._searchParameters.getConsiderTraffic(); - - } - - public int getLocationIndex() { - return _locationIndex; - } - - public void setLocationIndex(int locationIndex) { - _locationIndex = locationIndex; - } - - public Boolean getIncludeManeuvers() { - return _includeManeuvers; - } - - public void setIncludeManeuvers(Boolean includeManeuvers) { - _includeManeuvers = includeManeuvers; - } - - public boolean getContinueStraight() { - return _continueStraight; - } - - public void setContinueStraight(boolean continueStraight) { - _continueStraight = continueStraight; - } - - public boolean getIncludeRoundaboutExits() { - return _includeRoundaboutExits; - } - - public void setIncludeRoundaboutExits(boolean includeRoundaboutExits) { - _includeRoundaboutExits = includeRoundaboutExits; - } - - public boolean getSuppressWarnings() { - return _suppressWarnings; - } - - public void setSuppressWarnings(boolean suppressWarnings) { - _suppressWarnings = suppressWarnings; - } - - public boolean isValid() { - return !(_coordinates == null); - } -} +/* 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 + * 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. + * 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 . + */ +package heigit.ors.routing; + +import com.vividsolutions.jts.geom.Coordinate; + +import heigit.ors.common.DistanceUnit; +import heigit.ors.services.ServiceRequest; + +import java.util.ArrayList; +import java.util.List; + +public class RoutingRequest extends ServiceRequest +{ + private Coordinate[] _coordinates; + private RouteSearchParameters _searchParameters; + private DistanceUnit _units = DistanceUnit.Meters; + private String _language = "en"; + private String _geometryFormat = "encodedpolyline"; + private Boolean _geometrySimplify = false; + private RouteInstructionsFormat _instructionsFormat = RouteInstructionsFormat.TEXT; + private Boolean _includeInstructions = true; + private Boolean _includeElevation = false; + private Boolean _includeGeometry = true; + private Boolean _includeManeuvers = false; + private boolean _includeRoundaboutExits = false; + private String[] _attributes = null; + private int _extraInfo; + private int _locationIndex = -1; + private boolean _continueStraight = false; + private Boolean _suppressWarnings = false; + private List _skipSegments = new ArrayList<>(); + + public RoutingRequest() + { + _searchParameters = new RouteSearchParameters(); + } + + public Coordinate[] getCoordinates() { + return _coordinates; + } + + public Coordinate getDestination() + { + return _coordinates[_coordinates.length - 1]; + } + + public void setCoordinates(Coordinate[] _coordinates) { + this._coordinates = _coordinates; + } + + public RouteSearchParameters getSearchParameters() { + return _searchParameters; + } + + public void setSearchParameters(RouteSearchParameters _searchParameters) { + this._searchParameters = _searchParameters; + } + + public boolean getIncludeInstructions() { + return _includeInstructions; + } + + public void setIncludeInstructions(boolean includeInstructions) { + _includeInstructions = includeInstructions; + } + + public DistanceUnit getUnits() { + return _units; + } + + public void setUnits(DistanceUnit units) { + _units = units; + } + + public String getGeometryFormat() { + return _geometryFormat; + } + + public void setGeometryFormat(String geometryFormat) { + _geometryFormat = geometryFormat; + } + + public boolean getGeometrySimplify() { return _geometrySimplify; } + + public void setGeometrySimplify(boolean geometrySimplify) { _geometrySimplify = geometrySimplify; } + + public String getLanguage() { + return _language; + } + + public void setLanguage(String language) { + _language = language; + } + + public RouteInstructionsFormat getInstructionsFormat() { + return _instructionsFormat; + } + + public void setInstructionsFormat(RouteInstructionsFormat format) { + _instructionsFormat = format; + } + + public int getExtraInfo() { + return _extraInfo; + } + + public void setExtraInfo(int extraInfo) { + _extraInfo = extraInfo; + } + + public Boolean getIncludeElevation() { + return _includeElevation; + } + + public void setIncludeElevation(Boolean includeElevation) { + this._includeElevation = includeElevation; + } + + public Boolean getIncludeGeometry() { + return _includeGeometry; + } + + public void setIncludeGeometry(Boolean includeGeometry) { + this._includeGeometry = includeGeometry; + } + + public String[] getAttributes() { + return _attributes; + } + + public void setAttributes(String[] attributes) { + _attributes = attributes; + } + + public boolean hasAttribute(String attr) { + if (_attributes == null || attr == null) + return false; + + for (int i = 0; i< _attributes.length; i++) + if (attr.equalsIgnoreCase(_attributes[i])) + return true; + + return false; + } + + public boolean getConsiderTraffic(){ + return this._searchParameters.getConsiderTraffic(); + + } + + public int getLocationIndex() { + return _locationIndex; + } + + public void setLocationIndex(int locationIndex) { + _locationIndex = locationIndex; + } + + public Boolean getIncludeManeuvers() { + return _includeManeuvers; + } + + public void setIncludeManeuvers(Boolean includeManeuvers) { + _includeManeuvers = includeManeuvers; + } + + public boolean getContinueStraight() { + return _continueStraight; + } + + public void setContinueStraight(boolean continueStraight) { + _continueStraight = continueStraight; + } + + public boolean getIncludeRoundaboutExits() { + return _includeRoundaboutExits; + } + + public void setIncludeRoundaboutExits(boolean includeRoundaboutExits) { + _includeRoundaboutExits = includeRoundaboutExits; + } + + public boolean getSuppressWarnings() { + return _suppressWarnings; + } + + public void setSuppressWarnings(boolean suppressWarnings) { + _suppressWarnings = suppressWarnings; + } + + public boolean isValid() { + return !(_coordinates == null); + } + + public List getSkipSegments() { + return _skipSegments; + } + + public void setSkipSegments(List skipSegments) { + _skipSegments = skipSegments; + } +} diff --git a/openrouteservice/src/main/java/heigit/ors/routing/graphhopper/extensions/ORSGraphHopper.java b/openrouteservice/src/main/java/heigit/ors/routing/graphhopper/extensions/ORSGraphHopper.java index c02232bfea..4fc2dc44c7 100644 --- a/openrouteservice/src/main/java/heigit/ors/routing/graphhopper/extensions/ORSGraphHopper.java +++ b/openrouteservice/src/main/java/heigit/ors/routing/graphhopper/extensions/ORSGraphHopper.java @@ -30,10 +30,19 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; -import com.graphhopper.routing.subnetwork.PrepareRoutingSubnetworks; +import com.graphhopper.PathWrapper; import com.graphhopper.routing.util.FlagEncoder; import com.graphhopper.util.CmdArgs; +import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.Instruction; +import com.graphhopper.util.InstructionAnnotation; +import com.graphhopper.util.InstructionList; +import com.graphhopper.util.PointList; +import com.graphhopper.util.Translation; +import com.graphhopper.util.TranslationMap; +import com.vividsolutions.jts.geom.LineString; import heigit.ors.mapmatching.RouteSegmentInfo; import heigit.ors.routing.RoutingProfile; @@ -44,11 +53,10 @@ import com.graphhopper.routing.Path; import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.storage.GraphHopperStorage; -import com.graphhopper.util.EdgeIteratorState; -import com.graphhopper.util.PointList; import com.graphhopper.util.shapes.GHPoint; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; +import heigit.ors.util.CoordTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -75,6 +83,10 @@ public ORSGraphHopper(GraphProcessContext procCntx, boolean useTmc, RoutingProfi _procCntx.init(this); } + public ORSGraphHopper() { + // used to initialize tests more easily without the need to create GraphProcessContext etc. when they're anyway not used in the tested functions. + } + protected DataReader createReader(GraphHopperStorage tmpGraph) { return initDataReader(new ORSOSMReader(tmpGraph, _procCntx, tmcEdges, osmId2EdgeIds, refRouteProfile)); } @@ -234,6 +246,62 @@ protected void cleanUp() { } + public GHResponse constructFreeHandRoute(GHRequest request) { + LineString directRouteGeometry = constructFreeHandRouteGeometry(request); + PathWrapper directRoutePathWrapper = constructFreeHandRoutePathWrapper(directRouteGeometry); + GHResponse directRouteResponse = new GHResponse(); + directRouteResponse.add(directRoutePathWrapper); + directRouteResponse.getHints().put("skipped_segment", "true"); + return directRouteResponse; + } + + private PathWrapper constructFreeHandRoutePathWrapper(LineString lineString) { + PathWrapper pathWrapper = new PathWrapper(); + PointList pointList = new PointList(); + PointList startPointList = new PointList(); + PointList endPointList = new PointList(); + PointList wayPointList = new PointList(); + Coordinate startCoordinate = lineString.getCoordinateN(0); + Coordinate endCoordinate = lineString.getCoordinateN(1); + double distance = CoordTools.calcDistHaversine(startCoordinate.x, startCoordinate.y, endCoordinate.x, endCoordinate.y); + pointList.add(lineString.getCoordinateN(0).x, lineString.getCoordinateN(0).y); + pointList.add(lineString.getCoordinateN(1).x, lineString.getCoordinateN(1).y); + wayPointList.add(lineString.getCoordinateN(0).x, lineString.getCoordinateN(0).y); + wayPointList.add(lineString.getCoordinateN(1).x, lineString.getCoordinateN(1).y); + startPointList.add(lineString.getCoordinateN(0).x, lineString.getCoordinateN(0).y); + endPointList.add(lineString.getCoordinateN(1).x, lineString.getCoordinateN(1).y); + Translation translation = new TranslationMap.ORSTranslationHashMapWithExtendedInfo(new Locale("")); + InstructionList instructions = new InstructionList(translation); + Instruction startInstruction = new Instruction(Instruction.REACHED_VIA, "free hand route", new InstructionAnnotation(0, ""), startPointList); + Instruction endInstruction = new Instruction(Instruction.FINISH, "end of free hand route", new InstructionAnnotation(0, ""), endPointList); + instructions.add(0, startInstruction); + instructions.add(1, endInstruction); + pathWrapper.setDistance(distance); + pathWrapper.setAscend(0.0); + pathWrapper.setDescend(0.0); + pathWrapper.setTime(0); + pathWrapper.setInstructions(instructions); + pathWrapper.setWaypoints(wayPointList); + pathWrapper.setPoints(pointList); + pathWrapper.setRouteWeight(0.0); + pathWrapper.setDescription(new ArrayList<>()); + pathWrapper.setImpossible(false); + startInstruction.setDistance(distance); + startInstruction.setTime(0); + return pathWrapper; + } + + private LineString constructFreeHandRouteGeometry(GHRequest request){ + Coordinate start = new Coordinate(); + Coordinate end = new Coordinate(); + start.x = request.getPoints().get(0).getLat(); + start.y = request.getPoints().get(0).getLon(); + end.x = request.getPoints().get(1).getLat(); + end.y = request.getPoints().get(1).getLon(); + Coordinate[] coords = new Coordinate[]{start, end}; + return new GeometryFactory().createLineString(coords); + } + public HashMap getTmcGraphEdges() { return tmcEdges; } diff --git a/openrouteservice/src/test/java/heigit/ors/api/requests/routing/RouteRequestHandlerTest.java b/openrouteservice/src/test/java/heigit/ors/api/requests/routing/RouteRequestHandlerTest.java index df7eb3dcfb..2a203190c4 100644 --- a/openrouteservice/src/test/java/heigit/ors/api/requests/routing/RouteRequestHandlerTest.java +++ b/openrouteservice/src/test/java/heigit/ors/api/requests/routing/RouteRequestHandlerTest.java @@ -19,8 +19,11 @@ import com.vividsolutions.jts.geom.Polygon; import heigit.ors.api.requests.common.APIEnums; import heigit.ors.common.DistanceUnit; +import heigit.ors.exceptions.EmptyElementException; import heigit.ors.exceptions.IncompatibleParameterException; +import heigit.ors.exceptions.ParameterOutOfRangeException; import heigit.ors.exceptions.ParameterValueException; +import heigit.ors.exceptions.StatusCodeException; import heigit.ors.routing.*; import heigit.ors.routing.graphhopper.extensions.VehicleLoadCharacteristicsFlags; import heigit.ors.routing.graphhopper.extensions.WheelchairTypesEncoder; @@ -33,8 +36,10 @@ import org.junit.Before; import org.junit.Test; +import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; +import java.util.List; public class RouteRequestHandlerTest { RouteRequest request; @@ -91,6 +96,10 @@ public void init() throws Exception { coords[1] = new Double[] {27.4,38.6}; coords[2] = new Double[] {26.5,37.2}; + List skip_segments = new ArrayList<>(); + skip_segments.add(0, 1); + skip_segments.add(1, 2); + request = new RouteRequest(coords); request.setProfile(APIEnums.Profile.DRIVING_CAR); @@ -101,7 +110,7 @@ public void init() throws Exception { request.setIncludeGeometry(true); request.setIncludeInstructionsInResponse(true); request.setIncludeRoundaboutExitInfo(true); - request.setIncĺudeManeuvers(true); + request.setIncludeManeuvers(true); request.setInstructionsFormat(APIEnums.InstructionsFormat.HTML); request.setLanguage(APIEnums.Languages.DE); request.setMaximumSearchRadii(new Double[] { 50.0, 20.0, 100.0}); @@ -110,6 +119,7 @@ public void init() throws Exception { request.setRoutePreference(APIEnums.RoutePreference.FASTEST); request.setUnits(APIEnums.Units.METRES); request.setUseContractionHierarchies(false); + request.setSkipSegments(skip_segments); RouteRequestOptions options = new RouteRequestOptions(); options.setAvoidBorders(APIEnums.AvoidBorders.CONTROLLED); @@ -187,6 +197,9 @@ public void convertRouteRequestTest() throws Exception { Assert.assertEquals(BordersExtractor.Avoid.CONTROLLED, routingRequest.getSearchParameters().getAvoidBorders()); Assert.assertArrayEquals(new int[] {115}, routingRequest.getSearchParameters().getAvoidCountries()); Assert.assertEquals(AvoidFeatureFlags.getFromString("fords"), routingRequest.getSearchParameters().getAvoidFeatureTypes()); + Assert.assertEquals(2, routingRequest.getSkipSegments().size()); + Assert.assertEquals(Integer.valueOf(1), routingRequest.getSkipSegments().get(0)); + Assert.assertEquals(Integer.valueOf(2), routingRequest.getSkipSegments().get(1)); checkPolygon(routingRequest.getSearchParameters().getAvoidAreas(), geoJsonPolygon); @@ -289,6 +302,39 @@ public void vehicleType() throws Exception{ } } + @Test(expected = ParameterValueException.class) + public void invalidSkipSegmentsLength() throws StatusCodeException { + List skip_segments = new ArrayList<>(); + skip_segments.add(0, 1); + skip_segments.add(0, 2); + skip_segments.add(0, 2); + request.setSkipSegments(skip_segments); + new RouteRequestHandler().convertRouteRequest(request); + } + + @Test(expected = EmptyElementException.class) + public void emptySkipSegments() throws StatusCodeException { + List skip_segments = new ArrayList<>(); + request.setSkipSegments(skip_segments); + new RouteRequestHandler().convertRouteRequest(request); + } + + @Test(expected = ParameterOutOfRangeException.class) + public void skipSegmentsValueTooBig() throws StatusCodeException { + List skip_segments = new ArrayList<>(); + skip_segments.add(0, 99); + request.setSkipSegments(skip_segments); + new RouteRequestHandler().convertRouteRequest(request); + } + + @Test(expected = ParameterValueException.class) + public void skipSegmentsValueTooSmall() throws StatusCodeException { + List skip_segments = new ArrayList<>(); + skip_segments.add(0, -99); + request.setSkipSegments(skip_segments); + new RouteRequestHandler().convertRouteRequest(request); + } + private void checkPolygon(Polygon[] requestPolys, JSONObject apiPolys) { Assert.assertEquals(1, requestPolys.length); diff --git a/openrouteservice/src/test/java/heigit/ors/api/requests/routing/RouteRequestTest.java b/openrouteservice/src/test/java/heigit/ors/api/requests/routing/RouteRequestTest.java index 2266703e75..397d6ef299 100644 --- a/openrouteservice/src/test/java/heigit/ors/api/requests/routing/RouteRequestTest.java +++ b/openrouteservice/src/test/java/heigit/ors/api/requests/routing/RouteRequestTest.java @@ -22,6 +22,9 @@ import org.junit.Before; import org.junit.Test; +import java.util.ArrayList; +import java.util.List; + public class RouteRequestTest { RouteRequest request; @@ -107,4 +110,13 @@ public void testHasSuppressWarnings() { request.setSuppressWarnings(true); Assert.assertTrue(request.hasSuppressWarnings()); } + + @Test + public void testHasSkipSegments() { + List testSegments = new ArrayList<>(); + testSegments.add(0, 1); + Assert.assertFalse(request.hasSkipSegments()); + request.setSkipSegments(testSegments); + Assert.assertTrue(request.hasSkipSegments()); + } } diff --git a/openrouteservice/src/test/java/heigit/ors/routing/RouteRequestHandlerTest.java b/openrouteservice/src/test/java/heigit/ors/routing/RouteRequestHandlerTest.java index d94351028e..025405b1be 100644 --- a/openrouteservice/src/test/java/heigit/ors/routing/RouteRequestHandlerTest.java +++ b/openrouteservice/src/test/java/heigit/ors/routing/RouteRequestHandlerTest.java @@ -84,7 +84,7 @@ public void init() throws Exception { request.setIncludeGeometry(true); request.setIncludeInstructionsInResponse(true); request.setIncludeRoundaboutExitInfo(true); - request.setIncĺudeManeuvers(true); + request.setIncludeManeuvers(true); request.setInstructionsFormat(APIEnums.InstructionsFormat.HTML); request.setLanguage(APIEnums.Languages.DE); request.setMaximumSearchRadii(new Double[] { 50.0, 20.0, 100.0}); diff --git a/openrouteservice/src/test/java/heigit/ors/routing/graphhopper/extensions/ORSGraphHopperTest.java b/openrouteservice/src/test/java/heigit/ors/routing/graphhopper/extensions/ORSGraphHopperTest.java new file mode 100644 index 0000000000..f503f2775f --- /dev/null +++ b/openrouteservice/src/test/java/heigit/ors/routing/graphhopper/extensions/ORSGraphHopperTest.java @@ -0,0 +1,88 @@ +package heigit.ors.routing.graphhopper.extensions; + +import com.graphhopper.GHRequest; + +import com.graphhopper.GHResponse; +import com.graphhopper.PathWrapper; +import com.graphhopper.util.Instruction; +import com.graphhopper.util.InstructionList; +import com.graphhopper.util.PointList; +import org.junit.Assert; +import org.junit.Test; + +public class ORSGraphHopperTest { + + @Test + public void directRouteTest() { + GHRequest ghRequest = new GHRequest(49.41281601436809, 8.686215877532959, 49.410163456220076, 8.687160015106201); + GHResponse ghResponse = new ORSGraphHopper().constructFreeHandRoute(ghRequest); + + Assert.assertTrue(ghResponse.getHints().has("skipped_segment")); + Assert.assertTrue(ghResponse.getHints().getBool("skipped_segment", false)); + + Assert.assertEquals(1, ghResponse.getAll().size()); + PathWrapper directRouteWrapper = ghResponse.getAll().get(0); + + Assert.assertEquals(0, directRouteWrapper.getErrors().size()); + Assert.assertEquals(0, directRouteWrapper.getDescription().size()); + Assert.assertEquals(309.892f, directRouteWrapper.getDistance(), 3); + Assert.assertEquals(0.0, directRouteWrapper.getAscend(), 0); + Assert.assertEquals(0.0, directRouteWrapper.getDescend(), 0); + Assert.assertEquals(0.0, directRouteWrapper.getRouteWeight(), 0); + Assert.assertEquals(0, directRouteWrapper.getTime()); + Assert.assertEquals("", directRouteWrapper.getDebugInfo()); + Assert.assertEquals(2, directRouteWrapper.getInstructions().size()); + Assert.assertEquals(1, directRouteWrapper.getInstructions().get(0).getPoints().size()); + Assert.assertEquals(0, directRouteWrapper.getNumChanges()); + Assert.assertEquals(0, directRouteWrapper.getLegs().size()); + Assert.assertEquals(0, directRouteWrapper.getPathDetails().size()); + Assert.assertNull(directRouteWrapper.getFare()); + Assert.assertFalse(directRouteWrapper.isImpossible()); + + checkInstructions(directRouteWrapper.getInstructions()); + checkPointList(directRouteWrapper.getWaypoints()); + checkPointList(directRouteWrapper.getPoints()); + + } + + private void checkInstructions(InstructionList instructions) { + for (Instruction instruction : instructions) { + PointList points = instruction.getPoints(); + + Assert.assertEquals(2, points.getDimension()); + Assert.assertFalse(points.isEmpty()); + Assert.assertFalse(points.is3D()); + Assert.assertFalse(points.isImmutable()); + Assert.assertTrue(instruction.getAnnotation().isEmpty()); + Assert.assertEquals(0, instruction.getExtraInfoJSON().size()); + + if (instruction.getName().equals("free hand route") && instruction.getSign() == Instruction.REACHED_VIA) { + Assert.assertEquals(1, instruction.getPoints().size()); + Assert.assertEquals(49.41281601436809, instruction.getPoints().getLat(0), 0); + Assert.assertEquals(8.686215877532959, instruction.getPoints().getLon(0), 0); + Assert.assertEquals(309.892f, instruction.getDistance(), 3); + Assert.assertEquals(0, instruction.getTime()); + } else if (instruction.getName().equals("end of free hand route") && instruction.getSign() == Instruction.FINISH) { + Assert.assertEquals(1, instruction.getPoints().size()); + Assert.assertEquals(49.410163456220076, instruction.getPoints().getLat(0), 0); + Assert.assertEquals(8.687160015106201, instruction.getPoints().getLon(0), 0); + Assert.assertEquals(0.0, instruction.getDistance(), 0); + Assert.assertEquals(0, instruction.getTime()); + } else { + Assert.fail("The name or instruction sign of the skipped_segments instructions are wrong."); + } + } + + } + + private void checkPointList(PointList waypoints) { + Assert.assertFalse(waypoints.is3D()); + Assert.assertFalse(waypoints.isImmutable()); + Assert.assertEquals(2, waypoints.getSize()); + Assert.assertEquals(49.41281601436809, waypoints.getLat(0), 0); + Assert.assertEquals(49.410163456220076, waypoints.getLat(1), 0); + Assert.assertEquals(8.686215877532959, waypoints.getLon(0), 0); + Assert.assertEquals(8.687160015106201, waypoints.getLon(1), 0); + } + +} \ No newline at end of file