Skip to content

Commit

Permalink
feat: open up snapping endpoint in public API (#1796)
Browse files Browse the repository at this point in the history
aoles authored Jun 4, 2024
2 parents 31d8c8a + 7849517 commit 94384f0
Showing 24 changed files with 257 additions and 226 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/docker-build-and-test.yml
Original file line number Diff line number Diff line change
@@ -116,10 +116,13 @@ jobs:
cache-from: type=gha
- name: Start container from previously build image and wait for successful checks
run: |
mkdir -p $(pwd)/graphs $(pwd)/config
chown -R $UID $(pwd)/graphs $(pwd)/config
mkdir -p $(pwd)/graphs $(pwd)/config ./ors-docker/elevation_cache
chown -R $UID $(pwd)/graphs $(pwd)/config ./ors-docker ./ors-docker/elevation_cache
# Place cached elevation file where docker compose expects it to mount into the image
cp ors-api/src/test/files/elevation/srtm_38_03.gh ./ors-docker/elevation_cache
# Replace image: in the docker-compose.yml with the test image. The value of image: can vary.
sed -i "s|image:.*|image: ${{ needs.prepare_environment.outputs.test_image_name }}|" docker-compose.yml
sed -i "s|#logging.level.org.heigit: INFO|logging.level.org.heigit: DEBUG|" docker-compose.yml
# Start the first build with the docker-compose setup
docker compose up --build -d
# Wait for all logs to come in
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -35,14 +35,16 @@ RELEASING:

## [Unreleased]
### Added
- document which values of OSM key `surface` are being considered for way surface categories ([#1794](https://github.com/GIScience/openrouteservice/pull/1794))
- document which values of OSM tag `surface` are considered for way surface categories ([#1794](https://github.com/GIScience/openrouteservice/pull/1794))
- config parameter `maximum_locations` to snapping endpoint ([#1796](https://github.com/GIScience/openrouteservice/pull/1796))
### Changed
- way surface is now being determined based only on the value of OSM `surface` tag; when the tag is not present the surface is reported as "Unknown" and is no longer being inferred from the way type ([#1794](https://github.com/GIScience/openrouteservice/pull/1794))
- determine way surface based only on the value of OSM tag `surface`; if the tag is not present, the surface is reported as "Unknown" and no longer inferred from the way type ([#1794](https://github.com/GIScience/openrouteservice/pull/1794))
- improved performance of RPHAST matrix queries in the case when the number of sources is higher than the number of destinations
- revise snap endpoint error codes ([#1796](https://github.com/GIScience/openrouteservice/pull/1796))
### Deprecated
- soon-to-be removed features.
### Removed
- way surface categories "Fine gravel", "Cobblestone" and "Woodchips" have been merged with existing categories "Gravel", "Paving stones" and "Unpaved", respectively ([#1794](https://github.com/GIScience/openrouteservice/pull/1794))
- merge way surface categories "Fine gravel", "Cobblestone" and "Woodchips" with existing ones "Gravel", "Paving stones" and "Unpaved", respectively ([#1794](https://github.com/GIScience/openrouteservice/pull/1794))
### Fixed
- reliable encoding of way type and surface ([#1794](https://github.com/GIScience/openrouteservice/pull/1794))
### Security
236 changes: 118 additions & 118 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -52,6 +52,7 @@ services:
#ors.engine.graphs_root_path: /home/ors/graphs
#ors.engine.elevation.cache_path: /home/ors/elevation_cache
#ors.engine.profiles.car.enabled: true
#logging.level.org.heigit: INFO

# ----------------- ENV file configuration ------------------- #
# Too many variables for your 'environment:' section?
2 changes: 1 addition & 1 deletion docs/.vitepress/config.js
Original file line number Diff line number Diff line change
@@ -84,7 +84,7 @@ export default defineConfig({
},
{text: 'Isochrones', link: '/api-reference/endpoints/isochrones/'},
{text: 'Matrix', link: '/api-reference/endpoints/matrix/'},
{text: 'Snapping (not live)', link: '/api-reference/endpoints/snapping/'},
{text: 'Snapping', link: '/api-reference/endpoints/snapping/'},
{text: 'Export (not live)', link: '/api-reference/endpoints/export/'},
{text: 'Health (not live)', link: '/api-reference/endpoints/health/'},
{text: 'Status (not live)', link: '/api-reference/endpoints/status/'},
2 changes: 1 addition & 1 deletion docs/api-reference/endpoints/index.md
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ Openrouteservice offers a set of endpoints for different spatial purposes:
* [Directions Service](directions/index.md): Get directions for different modes of transport
* [Isochrones Service](isochrones/index.md): Obtain areas of reachability from given locations
* [Matrix Service](matrix/index.md): Obtain one-to-many, many-to-one and many-to-many matrices for time and distance
* [Snapping Service](snapping/index.md): Snap coordinates to the graph edges _(:warning: not available in our live API)_
* [Snapping Service](snapping/index.md): Snap coordinates to the road network

## Technical Endpoints

10 changes: 2 additions & 8 deletions docs/api-reference/endpoints/snapping/index.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
# Snapping Endpoint

:::warning NOTE
This endpoint is not available in the public API,
but you can use it when running an own instance of openrouteservice.
You can easily create requests with the [swagger-ui](../../index.md#swagger-ui).
:::

The snapping endpoint can be used to snap points to the edges of the street network for a specific means of transportation.

The endpoint returns a list of points snapped to the nearest edge in the graph as JSON or GeoJSON.
In case an appropriate snapping point cannot be found within the specified search radius, "null" is returned.
In case an appropriate snapping point cannot be found within the specified search radius, `null` is returned.

The routing profile has to be specified as path parameter.
The list of points to be snapped has to be specified as parameter `locations` in the request body,
@@ -19,7 +13,7 @@ Another required request body parameter is the `radius` in meters.
The result contains the snapped points in the same order as their origin position in the request.

In the following example request and result, the first point cannot be snapped within the search radius
and therefore the first entry in the result `locations` is null.
and therefore the first entry in the result `locations` is `null`.

Request:
```shell
21 changes: 11 additions & 10 deletions docs/api-reference/error-codes.md
Original file line number Diff line number Diff line change
@@ -121,13 +121,14 @@ Endpoints.

[//]: # (keep in sync with org.heigit.ors.snapping.SnappingErrorCodes)

| Error Code | Description |
|:----------:|--------------------------------|
| 8000 | Unable to parse JSON request. |
| 8001 | Required parameter is missing. |
| 8002 | Invalid parameter format. |
| 8003 | Invalid parameter value. |
| 8004 | Unknown parameter. |
| 8006 | Unsupported export format. |
| 8010 | Point not found. |
| 8099 | Unknown internal error. |
| Error Code | Description |
|:----------:|-------------------------------------------------------|
| 8000 | Unable to parse JSON request. |
| 8001 | Required parameter is missing. |
| 8002 | Invalid parameter format. |
| 8003 | Invalid parameter value. |
| 8004 | Parameter value exceeds the maximum configured limit. |
| 8007 | Unsupported export format. |
| 8010 | Point not found. |
| 8011 | Unknown parameter. |
| 8099 | Unknown internal error. |
2 changes: 1 addition & 1 deletion docs/api-reference/index.md
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ The following chapter [Endpoints](endpoints/index.md) contains explanation and e
be explained sufficiently in the API Playground.
Here, the single endpoints are not completely covered with all their request parameters etc.
But on the other hand, there is also information about endpoints that are not available in our live API,
but e.g. on instances you run or host yourself like [Export](endpoints/export/index.md), [Snapping](endpoints/snapping/index.md), [Health](endpoints/health/index.md) and [Status](endpoints/status/index.md).
but e.g. on instances you run or host yourself like [Export](endpoints/export/index.md), [Health](endpoints/health/index.md) and [Status](endpoints/status/index.md).

If you are developing ORS or running your own instance, you might benefit from the included [Swagger-UI](#swagger-ui).

2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ The fastest way to get to the info you are looking for is **the search bar above
* [Directions](api-reference/endpoints/directions/index.md) endpoint
* [Isochrones](api-reference/endpoints/isochrones/index.md) endpoint
* [Matrix](api-reference/endpoints/matrix/index.md) endpoint
* [Snapping](api-reference/endpoints/snapping/index.md) endpoint _(not available in our live API)_
* [Snapping](api-reference/endpoints/snapping/index.md) endpoint
* [Export](api-reference/endpoints/export/index.md) endpoint _(not available in our live API)_
* [Run ORS instance](run-instance/index.md) contains all info about setting up the ors locally.
* [System requirements](run-instance/system-requirements)
9 changes: 5 additions & 4 deletions docs/run-instance/configuration/json.md
Original file line number Diff line number Diff line change
@@ -257,10 +257,11 @@ The top level element.
---
#### ors.services.snap

| key | type | description | example value |
|-------------------------|---------|----------------------------------------------------------------|------------------------------------------------------|
| enabled | boolean | Enables or disables (true/false) the end-point (default: true) | `true` |
| attribution | string | Attribution added to the response metadata | `"openrouteservice.org, OpenStreetMap contributors"` |
| key | type | description | example value |
|-------------------|---------|----------------------------------------------------------------|------------------------------------------------------|
| enabled | boolean | Enables or disables (true/false) the end-point (default: true) | `true` |
| attribution | string | Attribution added to the response metadata | `"openrouteservice.org, OpenStreetMap contributors"` |
| maximum_locations | number | Maximum number of locations in one request | `5000` |

---

9 changes: 5 additions & 4 deletions docs/run-instance/configuration/ors/endpoints/snap.md
Original file line number Diff line number Diff line change
@@ -3,8 +3,9 @@

Settings for the snapping endpoint.

| key | type | description | default value |
|-------------|---------|--------------------------------------------|----------------------------------------------------|
| enabled | boolean | Enables or disables the end-point | `true` |
| attribution | string | Attribution added to the response metadata | `openrouteservice.org, OpenStreetMap contributors` |
| key | type | description | default value |
|-------------------|---------|--------------------------------------------|----------------------------------------------------|
| enabled | boolean | Enables or disables the end-point | `true` |
| attribution | string | Attribution added to the response metadata | `openrouteservice.org, OpenStreetMap contributors` |
| maximum_locations | number | Maximum number of locations per request | `5000` |

Original file line number Diff line number Diff line change
@@ -445,6 +445,7 @@ public void setAttribution(String attribution) {
public static class EndpointSnapProperties {
private boolean enabled;
private String attribution;
private int maximumLocations;

public boolean isEnabled() {
return enabled;
@@ -462,5 +463,11 @@ public void setAttribution(String attribution) {
this.attribution = attribution;
}

public int getMaximumLocations() {
return maximumLocations;
}
public void setMaximumLocations(int maximumLocations) {
this.maximumLocations = maximumLocations;
}
}
}
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@
import org.springframework.web.bind.annotation.*;

@RestController
@Tag(name = "Snapping Service", description = "Snap coordinates to the graph edges.")
@Tag(name = "Snapping Service", description = "Snap coordinates to the road network.")
@RequestMapping("/v2/snap")
@ApiResponse(responseCode = "400", description = "The request is incorrect and therefore can not be processed.")
@ApiResponse(responseCode = "404", description = "An element could not be found. If possible, a more detailed error code is provided.")
@@ -92,7 +92,7 @@ public void getInvalidResponseType() throws StatusCodeException {
@PostMapping(value = "/{profile}")
@Operation(
description = """
Returns a list of points snapped to the nearest edge in the graph. In case an appropriate
Returns a list of points snapped to the nearest edge in the routing graph. In case an appropriate
snapping point cannot be found within the specified search radius, "null" is returned.
""",
summary = "Snapping Service"
@@ -106,14 +106,14 @@ public void getInvalidResponseType() throws StatusCodeException {
)
})
public JsonSnappingResponse getDefault(@Parameter(description = "Specifies the route profile.", required = true, example = "driving-car") @PathVariable APIEnums.Profile profile,
@Parameter(description = "The request payload", required = true) @RequestBody SnappingApiRequest request) throws StatusCodeException {
@Parameter(description = "The request payload", required = true) @RequestBody SnappingApiRequest request) throws StatusCodeException {
return getJsonSnapping(profile, request);
}

@PostMapping(value = "/{profile}/json", produces = {"application/json;charset=UTF-8"})
@Operation(
description = """
Returns a list of points snapped to the nearest edge in the graph. In case an appropriate
Returns a list of points snapped to the nearest edge in the routing graph. In case an appropriate
snapping point cannot be found within the specified search radius, "null" is returned.
""",
summary = "Snapping Service JSON"
@@ -137,10 +137,10 @@ public JsonSnappingResponse getJsonSnapping(
return new JsonSnappingResponse(result, request, systemMessageProperties, endpointsProperties);
}

@PostMapping(value = "/{profile}/geojson", produces = {"application/json;charset=UTF-8"})
@PostMapping(value = "/{profile}/geojson", produces = {"application/json;charset=UTF-8"})
@Operation(
description = """
Returns a GeoJSON FeatureCollection of points snapped to the nearest edge in the graph.
Returns a GeoJSON FeatureCollection of points snapped to the nearest edge in the routing graph.
In case an appropriate snapping point cannot be found within the specified search radius,
it is omitted from the features array. The features provide the 'source_id' property, to match
the results with the input location array (IDs start at 0).
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ public class SnappingApiRequest extends APIRequest {
private APIEnums.Profile profile;

@Schema(name = PARAM_LOCATIONS, description = "The locations to be snapped as array of `longitude/latitude` pairs.",
example = "[[8.681495,49.41461],[8.686507,49.41943]]",
example = "[[8.669629,49.413025],[8.675841,49.418532],[8.665144,49.415594]]",
requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty(PARAM_LOCATIONS)
private List<List<Double>> locations; //apparently, this has to be a non-primitive type…
@@ -31,9 +31,9 @@ public class SnappingApiRequest extends APIRequest {
private APIEnums.SnappingResponseType responseType = APIEnums.SnappingResponseType.JSON;

@Schema(name = PARAM_MAXIMUM_SEARCH_RADIUS, description = "Maximum radius in meters around given coordinates to search for graph edges.",
example ="300", requiredMode = Schema.RequiredMode.REQUIRED)
example ="350", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty(PARAM_MAXIMUM_SEARCH_RADIUS)
private double maximumSearchRadius;
private double maximumSearchRadius = 350;//TODO: allow profile-specific value set in config file, see #1798

@JsonCreator
public SnappingApiRequest(@JsonProperty(value = PARAM_LOCATIONS, required = true) List<List<Double>> locations) {
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
package org.heigit.ors.api.services;

import org.heigit.ors.api.EndpointsProperties;
import org.heigit.ors.api.requests.snapping.SnappingApiRequest;
import org.heigit.ors.common.StatusCode;
import org.heigit.ors.exceptions.InternalServerException;
import org.heigit.ors.exceptions.ParameterValueException;
import org.heigit.ors.exceptions.PointNotFoundException;
import org.heigit.ors.exceptions.StatusCodeException;
import org.heigit.ors.exceptions.*;
import org.heigit.ors.routing.RoutingProfile;
import org.heigit.ors.routing.RoutingProfileManager;
import org.heigit.ors.snapping.SnappingErrorCodes;
import org.heigit.ors.snapping.SnappingRequest;
import org.heigit.ors.snapping.SnappingResult;
import org.locationtech.jts.geom.Coordinate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class SnappingService extends ApiService {

@Autowired
public SnappingService(EndpointsProperties endpointsProperties) {
this.endpointsProperties = endpointsProperties;
}

public SnappingResult generateSnappingFromRequest(SnappingApiRequest snappingApiRequest) throws StatusCodeException {
SnappingRequest snappingRequest = this.convertSnappingRequest(snappingApiRequest);

validateAgainstConfig(snappingRequest);
try {
RoutingProfile rp = RoutingProfileManager.getInstance().getProfileFromType(snappingRequest.getProfileType());
if (rp == null)
@@ -46,7 +50,8 @@ private SnappingRequest convertSnappingRequest(SnappingApiRequest snappingApiReq

SnappingRequest snappingRequest = new SnappingRequest(profileType,
convertLocations(snappingApiRequest.getLocations()), snappingApiRequest.getMaximumSearchRadius());

EndpointsProperties.EndpointSnapProperties snapProperties = endpointsProperties.getSnap();
snappingRequest.setMaximumLocations(snapProperties.getMaximumLocations());
if (snappingApiRequest.hasId())
snappingRequest.setId(snappingApiRequest.getId());
return snappingRequest;
@@ -70,4 +75,10 @@ private static Coordinate convertLocation(List<Double> location) throws StatusCo
return new Coordinate(location.get(0), location.get(1));
}

private void validateAgainstConfig(SnappingRequest snappingRequest) throws StatusCodeException {
int numberOfLocations = snappingRequest.getLocations().length;
int maximumLocations = snappingRequest.getMaximumLocations();
if (numberOfLocations > maximumLocations)
throw new ParameterOutOfRangeException(SnappingErrorCodes.PARAMETER_VALUE_EXCEEDS_MAXIMUM, SnappingApiRequest.PARAM_LOCATIONS, Integer.toString(numberOfLocations), Integer.toString(maximumLocations));
}
}
Loading

0 comments on commit 94384f0

Please sign in to comment.