location) throws StatusCodeException {
+ if (location.size() != 2) {
+ throw new ParameterValueException(SnappingErrorCodes.INVALID_PARAMETER_VALUE, SnappingApiRequest.PARAM_LOCATIONS);
+ }
+ return new Coordinate(location.get(0), location.get(1));
+ }
+
+ public SnappingResult computeResult(SnappingRequest snappingRequest, GraphHopper gh) throws Exception {
+ String encoderName = RoutingProfileType.getEncoderName(snappingRequest.getProfileType());
+ FlagEncoder flagEncoder = gh.getEncodingManager().getEncoder(encoderName);
+ PMap hintsMap = new PMap();
+ int weightingMethod = WeightingMethod.RECOMMENDED; // Only needed to create the profile string
+ ProfileTools.setWeightingMethod(hintsMap, weightingMethod, snappingRequest.getProfileType(), false);
+ ProfileTools.setWeighting(hintsMap, weightingMethod, snappingRequest.getProfileType(), false);
+ String profileName = ProfileTools.makeProfileName(encoderName, hintsMap.getString("weighting", ""), false);
+ GraphHopperStorage ghStorage = gh.getGraphHopperStorage();
+ String graphDate = ghStorage.getProperties().get("datareader.import.date");
+
+ // TODO: replace usage of matrix search context by snapping-specific class
+ MatrixSearchContextBuilder builder = new MatrixSearchContextBuilder(ghStorage, gh.getLocationIndex(), AccessFilter.allEdges(flagEncoder.getAccessEnc()), true);
+ Weighting weighting = new ORSWeightingFactory(ghStorage, gh.getEncodingManager()).createWeighting(gh.getProfile(profileName), hintsMap, false);
+ MatrixSearchContext mtxSearchCntx = builder.create(ghStorage.getBaseGraph(), null, weighting, profileName, snappingRequest.getLocations(), snappingRequest.getLocations(), snappingRequest.getMaximumSearchRadius());
+ return new SnappingResult(mtxSearchCntx.getSources().getLocations(), graphDate);
+ }
+}
diff --git a/ors-api/src/main/java/org/heigit/ors/api/util/AppConfigMigration.java b/ors-api/src/main/java/org/heigit/ors/api/util/AppConfigMigration.java
index 2b0fafaedb..7841b05ef2 100644
--- a/ors-api/src/main/java/org/heigit/ors/api/util/AppConfigMigration.java
+++ b/ors-api/src/main/java/org/heigit/ors/api/util/AppConfigMigration.java
@@ -165,6 +165,15 @@ public static EndpointsProperties overrideEndpointsProperties(EndpointsPropertie
}
isochrones.getStatisticsProviders().putAll(statisticsProviderPropertiesMap);
+// ### Snap ###
+ EndpointsProperties.EndpointSnapProperties snap = endpoints.getSnap();
+ value = config.getServiceParameter("snap", "enabled");
+ if (value != null)
+ snap.setEnabled(Boolean.parseBoolean(value));
+ value = config.getServiceParameter("snap", "attribution");
+ if (value != null)
+ snap.setAttribution(value);
+
// ### Matrix ###
EndpointsProperties.EndpointMatrixProperties matrix = endpoints.getMatrix();
value = config.getServiceParameter(SERVICE_NAME_MATRIX, "enabled");
diff --git a/ors-api/src/main/resources/application.yml b/ors-api/src/main/resources/application.yml
index cf87085ca0..9f74896395 100644
--- a/ors-api/src/main/resources/application.yml
+++ b/ors-api/src/main/resources/application.yml
@@ -96,6 +96,9 @@ ors:
maximum_range_time:
- profiles: driving-car, driving-hgv
value: 10800
+ Snap:
+ enabled: true
+ attribution: openrouteservice.org, OpenStreetMap contributors
##### ORS engine settings #####
engine:
diff --git a/ors-api/src/test/java/org/heigit/ors/apitests/snapping/ParamsTest.java b/ors-api/src/test/java/org/heigit/ors/apitests/snapping/ParamsTest.java
new file mode 100644
index 0000000000..1d47d399e8
--- /dev/null
+++ b/ors-api/src/test/java/org/heigit/ors/apitests/snapping/ParamsTest.java
@@ -0,0 +1,398 @@
+package org.heigit.ors.apitests.snapping;
+
+import io.restassured.response.ValidatableResponse;
+import org.apache.commons.lang3.StringUtils;
+import org.hamcrest.Matchers;
+import org.heigit.ors.apitests.common.EndPointAnnotation;
+import org.heigit.ors.apitests.common.ServiceTest;
+import org.heigit.ors.apitests.common.VersionAnnotation;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static io.restassured.RestAssured.given;
+import static jakarta.servlet.http.HttpServletResponse.SC_NOT_ACCEPTABLE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.heigit.ors.apitests.utils.CommonHeaders.jsonContent;
+import static org.heigit.ors.common.StatusCode.BAD_REQUEST;
+import static org.heigit.ors.common.StatusCode.NOT_FOUND;
+import static org.heigit.ors.snapping.SnappingErrorCodes.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+@EndPointAnnotation(name = "snap")
+@VersionAnnotation(version = "v2")
+class ParamsTest extends ServiceTest {
+
+ /**
+ * Generates a JSONArray with fake locations for testing purposes.
+ *
+ * @param maximumSize The maximum size of the JSONArray.
+ * @return A JSONArray containing fake locations with the specified size.
+ */
+ private static JSONArray fakeLocations(int maximumSize) {
+ JSONArray overloadedLocations = new JSONArray();
+ for (int i = 0; i < maximumSize; i++) {
+ overloadedLocations.put(invalidLocation());
+ }
+ return overloadedLocations;
+ }
+
+ private static JSONArray location2m() {
+ JSONArray coord2 = new JSONArray();
+ coord2.put(8.687782);
+ coord2.put(49.424597);
+ return coord2;
+ }
+
+ private static JSONArray location94m() {
+ JSONArray coord1 = new JSONArray();
+ coord1.put(8.680916);
+ coord1.put(49.410973);
+ return coord1;
+ }
+
+ private static JSONArray invalidLocation() {
+ JSONArray coord1 = new JSONArray();
+ coord1.put(0.0);
+ coord1.put(0.0);
+ return coord1;
+ }
+
+ /**
+ * Generates a JSONArray with valid locations for testing purposes.
+ *
+ * @return A JSONArray containing valid coordinates for testing.
+ */
+ private static JSONArray createLocations(JSONArray... locations) {
+ JSONArray correctTestLocations = new JSONArray();
+ correctTestLocations.putAll(locations);
+ return correctTestLocations;
+ }
+
+
+ private static JSONObject validBody() {
+ return new JSONObject()
+ .put("locations", createLocations(location94m(), location2m()))
+ .put("maximum_search_radius", "300");
+ }
+
+ /**
+ * Provides a stream of test arguments for testing successful scenarios in the Snapping Endpoint.
+ *
+ * Each test case is represented as an instance of the Arguments class, containing the following parameters:
+ * - The request body (JSONObject).
+ * - Boolean flag indicating whether an empty result is expected.
+ * - Boolean flag indicating whether a partially empty result is expected.
+ * - The endpoint type (String).
+ * - The routing profile type (String).
+ *
+ * @return A stream of Arguments instances for testing successful scenarios in the Snapping Endpoint.
+ */
+ public static Stream snappingEndpointSuccessTestProvider() {
+ return Stream.of(
+ Arguments.of(Arrays.asList(false, false), "driving-hgv", new JSONObject()
+ .put("locations", createLocations(location94m(), location2m())).put("maximum_search_radius", "-1")),
+ Arguments.of(Arrays.asList(false, false), "driving-hgv", new JSONObject()
+ .put("locations", createLocations(location94m(), location2m())).put("maximum_search_radius", "0")),
+ Arguments.of(Arrays.asList(false, false), "driving-hgv", new JSONObject()
+ .put("locations", createLocations(location94m(), location2m())).put("maximum_search_radius", "1")),
+ Arguments.of(Arrays.asList(false, true), "driving-hgv", new JSONObject()
+ .put("locations", createLocations(location94m(), location2m())).put("maximum_search_radius", "10")),
+ Arguments.of(Arrays.asList(true, true), "driving-hgv", new JSONObject()
+ .put("locations", createLocations(location94m(), location2m())).put("maximum_search_radius", "300")),
+ Arguments.of(Arrays.asList(true, false, true), "driving-hgv", new JSONObject()
+ .put("locations", createLocations(location2m(), location94m(), location2m())).put("maximum_search_radius", "10")),
+ Arguments.of(Arrays.asList(true, true), "driving-hgv", new JSONObject()
+ .put("locations", createLocations(location94m(), location2m())).put("maximum_search_radius", "400")),
+ Arguments.of(Arrays.asList(true, true), "driving-hgv", new JSONObject()
+ .put("locations", createLocations(location94m(), location2m())).put("maximum_search_radius", "1000"))
+ );
+ }
+
+ /**
+ * Parameterized test method for testing various scenarios in the Snapping Endpoint.
+ *
+ * @param expectedSnapped Boolean flags indicating if the locations are expected to be snapped.
+ * @param body The request body (JSONObject).
+ * @param profile The routing profile type (String).
+ */
+ @ParameterizedTest
+ @MethodSource("snappingEndpointSuccessTestProvider")
+ void testSnappingSuccessJson(List expectedSnapped, String profile, JSONObject body) {
+ String endPoint = "json";
+ ValidatableResponse result = doRequestAndExceptSuccess(body, profile, endPoint);
+ validateJsonResponse(expectedSnapped, result);
+ }
+
+ @Test
+ void testMissingPathParameterFormat_defaultsToJson() {
+ JSONObject body = validBody();
+ ValidatableResponse result = doRequestAndExceptSuccess(body, "driving-hgv", null);
+ validateJsonResponse(Arrays.asList(true, true), result);
+ }
+
+ private static void validateJsonResponse(List expectedSnappedList, ValidatableResponse result) {
+ result.body("any { it.key == 'locations' }", is(true));
+
+ // Iterate over the snappedLocations array and check the types of the values
+ ArrayList snappedLocations = result.extract().jsonPath().get("locations");
+ for (int i = 0; i < snappedLocations.size(); i++) {
+ boolean expectedSnapped = expectedSnappedList.get(i);
+ if (!expectedSnapped) {
+ assertNull(result.extract().jsonPath().get("locations[" + i + "].location[0]"));
+ } else {
+ // Type expectations
+ assertEquals(Float.class, result.extract().jsonPath().get("locations[" + i + "].location[0]").getClass());
+ assertEquals(Float.class, result.extract().jsonPath().get("locations[" + i + "].location[1]").getClass());
+ assertEquals(Float.class, result.extract().jsonPath().get("locations[" + i + "].snapped_distance").getClass());
+ // If name is in the response, check the type
+ if (result.extract().jsonPath().get("locations[" + i + "].name") != null)
+ assertEquals(String.class, result.extract().jsonPath().get("locations[" + i + "].name").getClass());
+ }
+ }
+ }
+
+ /**
+ * Parameterized test method for testing various scenarios in the Snapping Endpoint.
+ *
+ * @param expectedSnapped Boolean flags indicating if the locations are expected to be snapped.
+ * @param body The request body (JSONObject).
+ * @param profile The routing profile type (String).
+ */
+ @ParameterizedTest
+ @MethodSource("snappingEndpointSuccessTestProvider")
+ void testSnappingSuccessGeojson(List expectedSnapped, String profile, JSONObject body) {
+ String endPoint = "geojson";
+ ValidatableResponse result = doRequestAndExceptSuccess(body, profile, endPoint);
+
+ result.body("any { it.key == 'features' }", is(true));
+ result.body("any { it.key == 'type' }", is(true));
+ List expectedSourceIds = new ArrayList<>();
+ for (int i = 0; i < expectedSnapped.size(); i++) {
+ if (expectedSnapped.get(i)) {
+ expectedSourceIds.add(i);
+ }
+ }
+
+ ArrayList features = result.extract().jsonPath().get("features");
+ assertThat(features).hasSize(expectedSourceIds.size());
+ for (int i = 0; i < features.size(); i++) {
+ assertEquals(Float.class, result.extract().jsonPath().get("features[" + i + "].geometry.coordinates[0]").getClass());
+ assertEquals(Float.class, result.extract().jsonPath().get("features[" + i + "].geometry.coordinates[1]").getClass());
+ assertEquals(Float.class, result.extract().jsonPath().get("features[" + i + "].properties.snapped_distance").getClass());
+ assertEquals(Integer.class, result.extract().jsonPath().get("features[" + i + "].properties.source_id").getClass());
+ assertEquals(result.extract().jsonPath().get("features[" + i + "].properties.source_id"), expectedSourceIds.get(i));
+ // If name is in the response, check the type
+ if (result.extract().jsonPath().get("features[" + i + "].properties.name") != null)
+ assertEquals(String.class, result.extract().jsonPath().get("features[" + i + "].properties.name").getClass());
+ }
+ }
+
+ private ValidatableResponse doRequestAndExceptSuccess(JSONObject body, String profile, String endPoint) {
+ String url = getEndPointPath() + "/{profile}";
+ if (StringUtils.isNotBlank(endPoint))
+ url = "%s/%s".formatted(url, endPoint);
+
+ ValidatableResponse result = given()
+ .headers(jsonContent)
+ .pathParam("profile", profile)
+ .body(body.toString())
+ .when()
+ .log().ifValidationFails()
+ .post(url)
+ .then()
+ .log().ifValidationFails()
+ .statusCode(200);
+
+ // Check if the response contains the expected keys
+ result.body("any { it.key == 'metadata' }", is(true));
+ result.body("metadata.containsKey('attribution')", is(true));
+ result.body("metadata.service", is("snap"));
+ result.body("metadata.containsKey('timestamp')", is(true));
+ result.body("metadata.containsKey('query')", is(true));
+ result.body("metadata.containsKey('engine')", is(true));
+ result.body("metadata.containsKey('system_message')", is(true));
+ result.body("metadata.query.locations.size()", is(((JSONArray) body.get("locations")).toList().size()));
+ result.body("metadata.query.locations[0].size()", is(2));
+ result.body("metadata.query.locations[1].size()", is(2));
+ result.body("metadata.query.profile", is(profile));
+
+ if (StringUtils.isNotBlank(endPoint))
+ result.body("metadata.query.format", is(endPoint));
+
+ if (body.get("maximum_search_radius") != "0") {
+ result.body("metadata.query.maximum_search_radius", is(Float.parseFloat(body.get("maximum_search_radius").toString())));
+ }
+ return result;
+ }
+
+
+ /**
+ * Provides a stream of test arguments for testing the Snapping Endpoint with various scenarios.
+ *
+ * The scenarios include:
+ * 1. Single fake location to check exception handling for single locations.
+ * 2. Ten fake locations to check exception handling for multiple locations.
+ * 3. Broken fake location to check exception handling for invalid locations.
+ * 4. Wrong profile to check exception handling for invalid profiles.
+ * 5. Unknown parameter to check exception handling for unknown parameters.
+ *
+ * Each test case is represented as an instance of the Arguments class, containing the following parameters:
+ * - The routing profile type (String).
+ * - The locations (JSONArray).
+ * - Expected error code for the test case (SnappingErrorCodes).
+ * - Expected HTTP status code for the test case (StatusCode).
+ * - Body parameter for testing (JSONObject).
+ *
+ * @return A stream of Arguments instances for testing the Snapping Endpoint with different scenarios.
+ */
+ public static Stream snappingEndpointExceptionTestProvider() {
+ // Create fake locations for testing
+ JSONArray oneFakeLocation = fakeLocations(1);
+ JSONArray tenFakeLocations = fakeLocations(10);
+
+ // Create a broken fake location with invalid coordinates
+ JSONArray brokenFakeLocation = new JSONArray();
+ brokenFakeLocation.put(0.0);
+ brokenFakeLocation.put(0.0);
+
+ JSONArray invalidCoords = new JSONArray();
+ JSONArray invalidCoord1 = new JSONArray();
+ invalidCoord1.put(8.680916);
+ invalidCoords.put(invalidCoord1);
+
+ JSONArray correctTestLocations = createLocations(location94m(), location2m());
+ // Return a stream of test arguments
+ return Stream.of(
+ Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "driving-car", new JSONObject()),
+ // Check exception for one fake location to ensure single locations are checked
+ Arguments.of(POINT_NOT_FOUND, NOT_FOUND, "driving-car", new JSONObject()
+ .put("locations", oneFakeLocation)),
+ // Check exception for ten fake locations to ensure multiple locations are checked
+ Arguments.of(POINT_NOT_FOUND, NOT_FOUND, "driving-car", new JSONObject()
+ .put("locations", tenFakeLocations)),
+ // Check exception for broken location to ensure invalid locations are checked
+ Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "driving-car", new JSONObject()
+ .put("locations", brokenFakeLocation)),
+ // Check exception for wrong profile to ensure invalid profiles are checked
+ Arguments.of(INVALID_PARAMETER_VALUE, BAD_REQUEST, "driving-foo", new JSONObject()
+ .put("locations", correctTestLocations)),
+ // Check exception for unknown parameter to ensure unknown parameters are checked
+ Arguments.of(UNKNOWN_PARAMETER, BAD_REQUEST, "driving-car", new JSONObject()
+ .put("locations", correctTestLocations).put("unknown", "unknown")),
+ // Check exception for invalid locations parameter (only one ccordinate)
+ Arguments.of(INVALID_PARAMETER_VALUE, BAD_REQUEST, "driving-car", new JSONObject()
+ .put("locations", invalidCoords).put("maximum_search_radius", "300")),
+ // Check exception for invalid locations parameter (only one ccordinate)
+ Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "driving-car", new JSONObject()
+ .put("locations", "noJsonArray").put("maximum_search_radius", "300")),
+ // Check exception for invalid maximum_search_radius
+ Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "driving-car", new JSONObject()
+ .put("locations", correctTestLocations).put("maximum_search_radius", "notANumber")),
+ // Check exception for missing locations parameter
+ Arguments.of(INVALID_PARAMETER_FORMAT, BAD_REQUEST, "driving-car", new JSONObject()
+ .put("maximum_search_radius", "300")
+ ));
+ }
+
+ /**
+ * Parameterized test method for testing various exception scenarios in the Snapping Endpoint.
+ *
+ * @param expectedErrorCode The expected error code for the test case (SnappingErrorCodes).
+ * @param expectedStatusCode The expected HTTP status code for the test case (StatusCode).
+ * @param profile The routing profile type (String).
+ * @param body The request body (JSONObject).
+ */
+ @ParameterizedTest
+ @MethodSource("snappingEndpointExceptionTestProvider")
+ void testSnappingExceptionsJson(int expectedErrorCode, int expectedStatusCode, String profile, JSONObject body) {
+ doRequestAndExpectError(expectedErrorCode, expectedStatusCode, profile, "json", body);
+ }
+
+ /**
+ * Parameterized test method for testing various exception scenarios in the Snapping Endpoint.
+ *
+ * @param expectedErrorCode The expected error code for the test case (SnappingErrorCodes).
+ * @param expectedStatusCode The expected HTTP status code for the test case (StatusCode).
+ * @param profile The routing profile type (String).
+ * @param body The request body (JSONObject).
+ */
+ @ParameterizedTest
+ @MethodSource("snappingEndpointExceptionTestProvider")
+ void testSnappingExceptionsGeojson(int expectedErrorCode, int expectedStatusCode, String profile, JSONObject body) {
+ doRequestAndExpectError(expectedErrorCode, expectedStatusCode, profile, "geojson", body);
+ }
+
+ void doRequestAndExpectError(int expectedErrorCode, int expectedStatusCode, String profile, String endPoint, JSONObject body) {
+ given()
+ .headers(jsonContent)
+ .pathParam("profile", profile)
+ .body(body.toString())
+ .when()
+ .log().ifValidationFails()
+ .post(getEndPointPath() + "/{profile}/" + endPoint)
+ .then()
+ .log().ifValidationFails()
+ .assertThat()
+ .body("error.code", Matchers.is(expectedErrorCode))
+ .statusCode(expectedStatusCode);
+ }
+
+ @Test
+ void testMissingPathParametersProfileAndFormat() {
+ JSONObject body = validBody();
+
+ given()
+ .headers(jsonContent)
+ .body(body.toString())
+ .when()
+ .log().ifValidationFails()
+ .post(getEndPointPath())
+ .then()
+ .log().ifValidationFails()
+ .assertThat()
+ .body("error.code", Matchers.is(MISSING_PARAMETER))
+ .statusCode(BAD_REQUEST);
+ }
+
+ @Test
+ void testMissingPathParameterProfile() {
+ given()
+ .headers(jsonContent)
+ .body(validBody().toString())
+ .when()
+ .log().ifValidationFails()
+ .post(getEndPointPath() + "/json")
+ .then()
+ .log().ifValidationFails()
+ .assertThat()
+ .body("error.code", Matchers.is(INVALID_PARAMETER_VALUE))
+ .statusCode(BAD_REQUEST);
+ }
+
+ @Test
+ void testBadExportFormat() {
+ given()
+ .headers(jsonContent)
+ .body(validBody().toString())
+ .when()
+ .log().ifValidationFails()
+ .post(getEndPointPath() + "/driving-car/xml")
+ .then()
+ .log().ifValidationFails()
+ .assertThat()
+ .body("error.code", Matchers.is(UNSUPPORTED_EXPORT_FORMAT))
+ .statusCode(SC_NOT_ACCEPTABLE);
+ }
+
+}
diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/APIEnums.java b/ors-engine/src/main/java/org/heigit/ors/routing/APIEnums.java
index 15063c6279..aae356f3c4 100644
--- a/ors-engine/src/main/java/org/heigit/ors/routing/APIEnums.java
+++ b/ors-engine/src/main/java/org/heigit/ors/routing/APIEnums.java
@@ -172,6 +172,33 @@ public String toString() {
}
}
+ @Schema(name = "Snapping response type", description = "Format of the snapping response.")
+ public enum SnappingResponseType {
+ JSON("json"),
+ GEOJSON("geojson");
+
+ private final String value;
+
+ SnappingResponseType(String value) {
+ this.value = value;
+ }
+
+ @JsonCreator
+ public static SnappingResponseType forValue(String v) throws ParameterValueException {
+ for (SnappingResponseType enumItem : SnappingResponseType.values()) {
+ if (enumItem.value.equals(v))
+ return enumItem;
+ }
+ throw new ParameterValueException(GenericErrorCodes.INVALID_PARAMETER_VALUE, "format", v);
+ }
+
+ @Override
+ @JsonValue
+ public String toString() {
+ return value;
+ }
+ }
+
@Schema(name = "Vehicle type", description = "Definition of the vehicle type.")
public enum VehicleType {
HGV("hgv"),
diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/RoutingProfileManager.java b/ors-engine/src/main/java/org/heigit/ors/routing/RoutingProfileManager.java
index 3617dbc27f..eed5e6857e 100644
--- a/ors-engine/src/main/java/org/heigit/ors/routing/RoutingProfileManager.java
+++ b/ors-engine/src/main/java/org/heigit/ors/routing/RoutingProfileManager.java
@@ -14,10 +14,7 @@
package org.heigit.ors.routing;
import com.graphhopper.GHResponse;
-import com.graphhopper.util.AngleCalc;
-import com.graphhopper.util.DistanceCalc;
-import com.graphhopper.util.DistanceCalcEarth;
-import com.graphhopper.util.PointList;
+import com.graphhopper.util.*;
import com.graphhopper.util.exceptions.ConnectionNotFoundException;
import com.graphhopper.util.exceptions.MaximumNodesExceededException;
import org.apache.log4j.Logger;
@@ -29,16 +26,11 @@
import org.heigit.ors.isochrones.IsochroneMap;
import org.heigit.ors.isochrones.IsochroneSearchParameters;
import org.heigit.ors.mapmatching.MapMatchingRequest;
-import org.heigit.ors.matrix.MatrixErrorCodes;
-import org.heigit.ors.matrix.MatrixRequest;
-import org.heigit.ors.matrix.MatrixResult;
+import org.heigit.ors.matrix.*;
import org.heigit.ors.routing.configuration.RouteProfileConfiguration;
import org.heigit.ors.routing.configuration.RoutingManagerConfiguration;
import org.heigit.ors.routing.pathprocessors.ExtraInfoProcessor;
-import org.heigit.ors.util.FormatUtility;
-import org.heigit.ors.util.RuntimeUtility;
-import org.heigit.ors.util.StringUtility;
-import org.heigit.ors.util.TimeUtility;
+import org.heigit.ors.util.*;
import org.locationtech.jts.geom.Coordinate;
import java.util.ArrayList;
@@ -614,4 +606,5 @@ public ExportResult computeExport(ExportRequest req) throws Exception {
throw new InternalServerException(ExportErrorCodes.UNKNOWN, "Unable to find an appropriate routing profile.");
return rp.computeExport(req);
}
+
}
diff --git a/ors-engine/src/main/java/org/heigit/ors/snapping/SnappingErrorCodes.java b/ors-engine/src/main/java/org/heigit/ors/snapping/SnappingErrorCodes.java
new file mode 100644
index 0000000000..a620ab4450
--- /dev/null
+++ b/ors-engine/src/main/java/org/heigit/ors/snapping/SnappingErrorCodes.java
@@ -0,0 +1,18 @@
+package org.heigit.ors.snapping;
+
+public class SnappingErrorCodes {
+ public static final int BASE = 8000;
+ public static final int INVALID_JSON_FORMAT = 8000;
+ public static final int MISSING_PARAMETER = 8001;
+ public static final int INVALID_PARAMETER_FORMAT = 8002;
+ public static final int INVALID_PARAMETER_VALUE = 8003;
+ public static final int UNKNOWN_PARAMETER = 8004;
+ public static final int UNSUPPORTED_EXPORT_FORMAT = 8006;
+
+ public static final int POINT_NOT_FOUND = 8010;
+ public static final int UNKNOWN = 8099;
+
+ private SnappingErrorCodes() {
+ }
+
+}
diff --git a/ors-engine/src/main/java/org/heigit/ors/snapping/SnappingRequest.java b/ors-engine/src/main/java/org/heigit/ors/snapping/SnappingRequest.java
new file mode 100644
index 0000000000..4c970d48d0
--- /dev/null
+++ b/ors-engine/src/main/java/org/heigit/ors/snapping/SnappingRequest.java
@@ -0,0 +1,28 @@
+package org.heigit.ors.snapping;
+
+import org.heigit.ors.common.ServiceRequest;
+import org.locationtech.jts.geom.Coordinate;
+
+public class SnappingRequest extends ServiceRequest {
+ private final int profileType;
+ private final Coordinate[] locations;
+ private final double maximumSearchRadius;
+
+ public SnappingRequest(int profileType, Coordinate[] locations, double maximumSearchRadius) {
+ this.profileType = profileType;
+ this.locations = locations;
+ this.maximumSearchRadius = maximumSearchRadius;
+ }
+
+ public int getProfileType() {
+ return profileType;
+ }
+
+ public Coordinate[] getLocations() {
+ return locations;
+ }
+
+ public double getMaximumSearchRadius() {
+ return maximumSearchRadius;
+ }
+}
diff --git a/ors-engine/src/main/java/org/heigit/ors/snapping/SnappingResult.java b/ors-engine/src/main/java/org/heigit/ors/snapping/SnappingResult.java
new file mode 100644
index 0000000000..52c9af14d6
--- /dev/null
+++ b/ors-engine/src/main/java/org/heigit/ors/snapping/SnappingResult.java
@@ -0,0 +1,18 @@
+package org.heigit.ors.snapping;
+
+import org.heigit.ors.matrix.ResolvedLocation;
+
+public class SnappingResult {
+ private final ResolvedLocation[] locations;
+ private String graphDate = "";
+ public SnappingResult(ResolvedLocation[] locations, String graphDate) {
+ this.locations = locations;
+ this.graphDate = graphDate;
+ }
+
+ public ResolvedLocation[] getLocations() {
+ return locations;
+ }
+
+ public String getGraphDate() { return graphDate; }
+}