diff --git a/CHANGELOG.md b/CHANGELOG.md index f1b4d00c6..a0683d62d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - API: - `osrm-datastore` now accepts the parameter `--max-wait` that specifies how long it waits before aquiring a shared memory lock by force - Shared memory now allows for multiple clients (multiple instances of libosrm on the same segment) + - Polyline geometries can now be requested with precision 5 as well as with precision 6 - Profiles - `restrictions` is now used for namespaced restrictions and restriction exceptions (e.g. `restriction:motorcar=` as well as `except=motorcar`) - replaced lhs/rhs profiles by using test defined profiles diff --git a/docs/http.md b/docs/http.md index 4157a4615..17f1a41fe 100644 --- a/docs/http.md +++ b/docs/http.md @@ -153,19 +153,19 @@ http://router.project-osrm.org/nearest/v1/driving/13.388860,52.517037?number=3&b ### Request ``` -http://{server}/route/v1/{profile}/{coordinates}?alternatives={true|false}&steps={true|false}&geometries={polyline|geojson}&overview={full|simplified|false}&annotations={true|false} +http://{server}/route/v1/{profile}/{coordinates}?alternatives={true|false}&steps={true|false}&geometries={polyline|polyline6|geojson}&overview={full|simplified|false}&annotations={true|false} ``` In addition to the [general options](#general-options) the following options are supported for this service: -|Option |Values |Description | -|------------|------------------------------------------|-------------------------------------------------------------------------------| -|alternatives|`true`, `false` (default) |Search for alternative routes and return as well.\* | -|steps |`true`, `false` (default) |Return route steps for each route leg | -|annotations |`true`, `false` (default) |Returns additional metadata for each coordinate along the route geometry. | -|geometries |`polyline` (default), `geojson` |Returned route geometry format (influences overview and per step) | -|overview |`simplified` (default), `full`, `false` |Add overview geometry either full, simplified according to highest zoom level it could be display on, or not at all.| -|continue_straight |`default` (default), `true`, `false`|Forces the route to keep going straight at waypoints and don't do a uturn even if it would be faster. Default value depends on the profile. | +|Option |Values |Description | +|------------|---------------------------------------------|-------------------------------------------------------------------------------| +|alternatives|`true`, `false` (default) |Search for alternative routes and return as well.\* | +|steps |`true`, `false` (default) |Return route steps for each route leg | +|annotations |`true`, `false` (default) |Returns additional metadata for each coordinate along the route geometry. | +|geometries |`polyline` (default), `polyline6`, `geojson` |Returned route geometry format (influences overview and per step) | +|overview |`simplified` (default), `full`, `false` |Add overview geometry either full, simplified according to highest zoom level it could be display on, or not at all.| +|continue_straight |`default` (default), `true`, `false` |Forces the route to keep going straight at waypoints and don't do a uturn even if it would be faster. Default value depends on the profile. | \* Please note that even if an alternative route is requested, a result cannot be guaranteed. @@ -179,7 +179,7 @@ In case of error the following `code`s are supported in addition to the general | Type | Description | |-------------------|-----------------| -| `NoRoute` | No route found. | +| `NoRoute` | No route found. | All other fields might be undefined. @@ -263,7 +263,7 @@ The algorithm might not be able to match all points. Outliers are removed if the ### Request ``` -http://{server}/match/v1/{profile}/{coordinates}?steps={true|false}&geometries={polyline|geojson}&overview={simplified|full|false}&annotations={true|false} +http://{server}/match/v1/{profile}/{coordinates}?steps={true|false}&geometries={polyline|polyline6|geojson}&overview={simplified|full|false}&annotations={true|false} ``` In addition to the [general options](#general-options) the following options are supported for this service: @@ -272,8 +272,8 @@ In addition to the [general options](#general-options) the following options are |Option |Values |Description | |------------|------------------------------------------------|------------------------------------------------------------------------------------------| |steps |`true`, `false` (default) |Return route steps for each route | -|geometries |`polyline` (default), `geojson` |Returned route geometry format (influences overview and per step) | -|annotations |`true`, `false` (default) |Returns additional metadata for each coordinate along the route geometry. | +|geometries |`polyline` (default), `polyline6`, `geojson` |Returned route geometry format (influences overview and per step) | +|annotations |`true`, `false` (default) |Returns additional metadata for each coordinate along the route geometry. | |overview |`simplified` (default), `full`, `false` |Add overview geometry either full, simplified according to highest zoom level it could be display on, or not at all.| |timestamps |`{timestamp};{timestamp}[;{timestamp} ...]` |Timestamp of the input location. Timestamps need to be monotonically increasing. | |radiuses |`{radius};{radius}[;{radius} ...]` |Standard deviation of GPS precision used for map matching. If applicable use GPS accuracy.| @@ -311,7 +311,7 @@ multiple trips for each connected component are returned. ### Request ``` -http://{server}/trip/v1/{profile}/{coordinates}?steps={true|false}&geometries={polyline|geojson}&overview={simplified|full|false}&annotations={true|false} +http://{server}/trip/v1/{profile}/{coordinates}?steps={true|false}&geometries={polyline|polyline6|geojson}&overview={simplified|full|false}&annotations={true|false} ``` In addition to the [general options](#general-options) the following options are supported for this service: @@ -319,8 +319,8 @@ In addition to the [general options](#general-options) the following options are |Option |Values |Description | |------------|------------------------------------------------|---------------------------------------------------------------------------| |steps |`true`, `false` (default) |Return route instructions for each trip | -|annotations |`true`, `false` (default) |Returns additional metadata for each coordinate along the route geometry. | -|geometries |`polyline` (default), `geojson` |Returned route geometry format (influences overview and per step) | +|annotations |`true`, `false` (default) |Returns additional metadata for each coordinate along the route geometry. | +|geometries |`polyline` (default), `polyline6`, `geojson` |Returned route geometry format (influences overview and per step) | |overview |`simplified` (default), `full`, `false` |Add overview geometry either full, simplified according to highest zoom level it could be display on, or not at all.| ### Response @@ -335,7 +335,7 @@ In case of error the following `code`s are supported in addition to the general | Type | Description | |-------------------|---------------------| -| `NoTrips` | No trips found. | +| `NoTrips` | No trips found. | All other fields might be undefined. @@ -468,6 +468,7 @@ step. | geometries | | |------------|--------------------------------------------------------------------| | polyline | [polyline](https://www.npmjs.com/package/polyline) with precision 5 in [latitude,longitude] encoding | + | polyline6 | [polyline](https://www.npmjs.com/package/polyline) with precision 6 in [latitude,longitude] encoding | | geojson | [GeoJSON `LineString`](http://geojson.org/geojson-spec.html#linestring) or [GeoJSON `Point`](http://geojson.org/geojson-spec.html#point) if it is only one coordinate (not wrapped by a GeoJSON feature)| - `name`: The name of the way along which travel proceeds. diff --git a/features/step_definitions/matching.js b/features/step_definitions/matching.js index 3fe3f3932..77f751c80 100644 --- a/features/step_definitions/matching.js +++ b/features/step_definitions/matching.js @@ -90,7 +90,7 @@ module.exports = function () { if (headers.has('geometry')) { if (json.matchings.length != 1) throw new Error('*** Checking geometry only supported for matchings with one subtrace'); - geometry = json.matchings[0].geometry.coordinates; + geometry = json.matchings[0].geometry; } if (headers.has('OSM IDs')) { @@ -116,10 +116,13 @@ module.exports = function () { } if (headers.has('geometry')) { - if (this.queryParams['geometries'] === 'polyline') + if (this.queryParams['geometries'] === 'polyline') { got.geometry = polyline.decode(geometry).toString(); - else - got.geometry = geometry; + } else if (this.queryParams['geometries'] === 'polyline6') { + got.geometry = polyline.decode(geometry,6).toString(); + } else { + got.geometry = geometry.coordinates; + } } if (headers.has('OSM IDs')) { diff --git a/features/step_definitions/trip.js b/features/step_definitions/trip.js index 8de1fe7eb..7e52c5d22 100644 --- a/features/step_definitions/trip.js +++ b/features/step_definitions/trip.js @@ -1,4 +1,5 @@ var util = require('util'); +var polyline = require('polyline'); module.exports = function () { function add(a, b) { @@ -41,6 +42,16 @@ module.exports = function () { got.message = json.status_message; } + if (headers.has('geometry')) { + if (this.queryParams['geometries'] === 'polyline') { + got.geometry = polyline.decode(json.trips[0].geometry).toString(); + } else if (this.queryParams['geometries'] === 'polyline6') { + got.geometry = polyline.decode(json.trips[0].geometry, 6).toString(); + } else { + got.geometry = json.trips[0].geometry.coordinates; + } + } + if (headers.has('#')) { // comment column got['#'] = row['#']; diff --git a/features/testbot/matching.feature b/features/testbot/matching.feature index 6d78bcf2f..a19ca2da9 100644 --- a/features/testbot/matching.feature +++ b/features/testbot/matching.feature @@ -177,7 +177,7 @@ Feature: Basic Map Matching | trace | matchings | geometry | | efbc | efbc | 1,0.99964,1.000359,0.99964,1.000359,1,1.000718,1 | - Scenario: Testbot - Geometry details + Scenario: Testbot - Geometry details using geojson Given the query options | overview | full | | geometries | geojson | @@ -197,7 +197,47 @@ Feature: Basic Map Matching | trace | matchings | geometry | | abd | abd | 1,1,1.000089,1,1.000089,1,1.000089,0.99991 | - Scenario: Testbot - Speed greater than speed threshhold, should split -- returns trace as abcd but should be split into ab,cd + Scenario: Testbot - Geometry details using polyline + Given the query options + | overview | full | + | geometries | polyline | + + Given the node map + """ + a b c + d + """ + + And the ways + | nodes | oneway | + | abc | no | + | bd | no | + + When I match I should get + | trace | matchings | geometry | + | abd | abd | 1,1,1,1.00009,1,1.00009,0.99991,1.00009 | + + Scenario: Testbot - Geometry details using polyline6 + Given the query options + | overview | full | + | geometries | polyline6 | + + Given the node map + """ + a b c + d + """ + + And the ways + | nodes | oneway | + | abc | no | + | bd | no | + + When I match I should get + | trace | matchings | geometry | + | abd | abd | 1,1,1,1.000089,1,1.000089,0.99991,1.000089 | + + Scenario: Testbot - Speed greater than speed threshhold Given a grid size of 10 meters Given the query options | geometries | geojson | @@ -218,7 +258,7 @@ Feature: Basic Map Matching | trace | timestamps | matchings | | abcd | 0 1 2 3 | ab,cd | - Scenario: Testbot - Speed less than speed threshhold, should not split + Scenario: Testbot - Speed less than speed threshhold Given a grid size of 10 meters Given the query options | geometries | geojson | diff --git a/features/testbot/trip.feature b/features/testbot/trip.feature index cd0b6fabc..52d262d82 100644 --- a/features/testbot/trip.feature +++ b/features/testbot/trip.feature @@ -122,3 +122,69 @@ Feature: Basic trip planning | waypoints | trips | | a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a | | + + Scenario: Testbot - Trip with geometry details of geojson + Given the query options + | geometries | geojson | + + Given the node map + """ + a b + c d + """ + + And the ways + | nodes | + | ab | + | bc | + | cb | + | da | + + When I plan a trip I should get + | waypoints | trips | durations | geometry | + | a,b,c,d | abcda | 7.6 | 1,1,1.000089,1,1,0.99991,1.000089,1,1,1,1.000089,0.99991,1,1 | + | d,b,c,a | dbcad | 7.6 | 1.000089,0.99991,1,1,1.000089,1,1,0.99991,1.000089,1,1,1,1.000089,0.99991 | + + Scenario: Testbot - Trip with geometry details of polyline + Given the query options + | geometries | polyline | + + Given the node map + """ + a b + c d + """ + + And the ways + | nodes | + | ab | + | bc | + | cb | + | da | + + When I plan a trip I should get + | waypoints | trips | durations | geometry | + | a,b,c,d | abcda | 7.6 | 1,1,1,1.00009,0.99991,1,1,1.00009,1,1,0.99991,1.00009,1,1 | + | d,b,c,a | dbcad | 7.6 | 0.99991,1.00009,1,1,1,1.00009,0.99991,1,1,1.00009,1,1,0.99991,1.00009 | + + Scenario: Testbot - Trip with geometry details of polyline6 + Given the query options + | geometries | polyline6 | + + Given the node map + """ + a b + c d + """ + + And the ways + | nodes | + | ab | + | bc | + | cb | + | da | + + When I plan a trip I should get + | waypoints | trips | durations | geometry | + | a,b,c,d | abcda | 7.6 | 1,1,1,1.000089,0.99991,1,1,1.000089,1,1,0.99991,1.000089,1,1 | + | d,b,c,a | dbcad | 7.6 | 0.99991,1.000089,1,1,1,1.000089,0.99991,1,1,1.000089,1,1,0.99991,1.000089 | diff --git a/include/engine/api/json_factory.hpp b/include/engine/api/json_factory.hpp index 9be7e3792..609db4b33 100644 --- a/include/engine/api/json_factory.hpp +++ b/include/engine/api/json_factory.hpp @@ -42,9 +42,9 @@ std::string modeToString(const extractor::TravelMode mode); } // namespace detail -template util::json::String makePolyline(ForwardIter begin, ForwardIter end) +template util::json::String makePolyline(ForwardIter begin, ForwardIter end) { - return {encodePolyline(begin, end)}; + return {encodePolyline(begin, end)}; } template diff --git a/include/engine/api/route_api.hpp b/include/engine/api/route_api.hpp index 95a40569b..fa83eca8e 100644 --- a/include/engine/api/route_api.hpp +++ b/include/engine/api/route_api.hpp @@ -68,7 +68,12 @@ class RouteAPI : public BaseAPI { if (parameters.geometries == RouteParameters::GeometriesType::Polyline) { - return json::makePolyline(begin, end); + return json::makePolyline<100000>(begin, end); + } + + if (parameters.geometries == RouteParameters::GeometriesType::Polyline6) + { + return json::makePolyline<1000000>(begin, end); } BOOST_ASSERT(parameters.geometries == RouteParameters::GeometriesType::GeoJSON); @@ -192,9 +197,17 @@ class RouteAPI : public BaseAPI if (parameters.geometries == RouteParameters::GeometriesType::Polyline) { return static_cast( - json::makePolyline(leg_geometry.locations.begin() + step.geometry_begin, + json::makePolyline<100000>(leg_geometry.locations.begin() + step.geometry_begin, leg_geometry.locations.begin() + step.geometry_end)); } + + if (parameters.geometries == RouteParameters::GeometriesType::Polyline6) + { + return static_cast( + json::makePolyline<1000000>(leg_geometry.locations.begin() + step.geometry_begin, + leg_geometry.locations.begin() + step.geometry_end)); + } + BOOST_ASSERT(parameters.geometries == RouteParameters::GeometriesType::GeoJSON); return static_cast(json::makeGeoJSONGeometry( leg_geometry.locations.begin() + step.geometry_begin, diff --git a/include/engine/api/route_parameters.hpp b/include/engine/api/route_parameters.hpp index 0c2d1e31e..039ba4384 100644 --- a/include/engine/api/route_parameters.hpp +++ b/include/engine/api/route_parameters.hpp @@ -45,7 +45,7 @@ namespace api * Holds member attributes: * - steps: return route step for each route leg * - alternatives: tries to find alternative routes - * - geometries: route geometry encoded in Polyline or GeoJSON + * - geometries: route geometry encoded in Polyline, Polyline6 or GeoJSON * - overview: adds overview geometry either Full, Simplified (according to highest zoom level) or * False (not at all) * - continue_straight: enable or disable continue_straight (disabled by default) @@ -58,6 +58,7 @@ struct RouteParameters : public BaseParameters enum class GeometriesType { Polyline, + Polyline6, GeoJSON }; enum class OverviewType diff --git a/include/engine/polyline_compressor.hpp b/include/engine/polyline_compressor.hpp index 4ecf94105..f1261a384 100644 --- a/include/engine/polyline_compressor.hpp +++ b/include/engine/polyline_compressor.hpp @@ -3,6 +3,8 @@ #include "util/coordinate.hpp" +#include +#include #include #include @@ -11,16 +13,44 @@ namespace osrm namespace engine { namespace detail -{ -constexpr double POLYLINE_PRECISION = 1e5; -constexpr double COORDINATE_TO_POLYLINE = POLYLINE_PRECISION / COORDINATE_PRECISION; -constexpr double POLYLINE_TO_COORDINATE = COORDINATE_PRECISION / POLYLINE_PRECISION; -} + { + constexpr double POLYLINE_DECODING_PRECISION = 1e5; + constexpr double POLYLINE_TO_COORDINATE = COORDINATE_PRECISION / POLYLINE_DECODING_PRECISION; + std::string encode(std::vector &numbers); + } using CoordVectorForwardIter = std::vector::const_iterator; // Encodes geometry into polyline format. // See: https://developers.google.com/maps/documentation/utilities/polylinealgorithm -std::string encodePolyline(CoordVectorForwardIter begin, CoordVectorForwardIter end); + +template +std::string encodePolyline(CoordVectorForwardIter begin, CoordVectorForwardIter end) +{ + double coordinate_to_polyline = POLYLINE_PRECISION / COORDINATE_PRECISION; + auto size = std::distance(begin, end); + if (size == 0) + { + return {}; + } + + std::vector delta_numbers; + BOOST_ASSERT(size > 0); + delta_numbers.reserve((size - 1) * 2); + int current_lat = 0; + int current_lon = 0; + std::for_each( + begin, end, [&delta_numbers, ¤t_lat, ¤t_lon, coordinate_to_polyline](const util::Coordinate loc) { + const int lat_diff = + std::round(static_cast(loc.lat) * coordinate_to_polyline) - current_lat; + const int lon_diff = + std::round(static_cast(loc.lon) * coordinate_to_polyline) - current_lon; + delta_numbers.emplace_back(lat_diff); + delta_numbers.emplace_back(lon_diff); + current_lat += lat_diff; + current_lon += lon_diff; + }); + return detail::encode(delta_numbers); +} // Decodes geometry from polyline format // See: https://developers.google.com/maps/documentation/utilities/polylinealgorithm diff --git a/include/server/api/route_parameters_grammar.hpp b/include/server/api/route_parameters_grammar.hpp index 845c3425c..deaa7a984 100644 --- a/include/server/api/route_parameters_grammar.hpp +++ b/include/server/api/route_parameters_grammar.hpp @@ -43,7 +43,8 @@ struct RouteParametersGrammar : public BaseParametersGrammar &root_rule_) : BaseGrammar(root_rule_) { geometries_type.add("geojson", engine::api::RouteParameters::GeometriesType::GeoJSON)( - "polyline", engine::api::RouteParameters::GeometriesType::Polyline); + "polyline", engine::api::RouteParameters::GeometriesType::Polyline)( + "polyline6", engine::api::RouteParameters::GeometriesType::Polyline6); overview_type.add("simplified", engine::api::RouteParameters::OverviewType::Simplified)( "full", engine::api::RouteParameters::OverviewType::Full)( diff --git a/src/engine/polyline_compressor.cpp b/src/engine/polyline_compressor.cpp index 11954b5d7..1e309d7f9 100644 --- a/src/engine/polyline_compressor.cpp +++ b/src/engine/polyline_compressor.cpp @@ -10,7 +10,7 @@ namespace osrm { namespace engine { -namespace /*detail*/ // anonymous to keep TU local +namespace detail // anonymous to keep TU local { std::string encode(int number_to_encode) @@ -55,35 +55,6 @@ std::string encode(std::vector &numbers) } return output; } -} // anonymous ns - -std::string encodePolyline(CoordVectorForwardIter begin, CoordVectorForwardIter end) -{ - auto size = std::distance(begin, end); - if (size == 0) - { - return {}; - } - - std::vector delta_numbers; - BOOST_ASSERT(size > 0); - delta_numbers.reserve((size - 1) * 2); - int current_lat = 0; - int current_lon = 0; - std::for_each( - begin, end, [&delta_numbers, ¤t_lat, ¤t_lon](const util::Coordinate loc) { - const int lat_diff = - std::round(static_cast(loc.lat) * detail::COORDINATE_TO_POLYLINE) - - current_lat; - const int lon_diff = - std::round(static_cast(loc.lon) * detail::COORDINATE_TO_POLYLINE) - - current_lon; - delta_numbers.emplace_back(lat_diff); - delta_numbers.emplace_back(lon_diff); - current_lat += lat_diff; - current_lon += lon_diff; - }); - return encode(delta_numbers); } std::vector decodePolyline(const std::string &geometry_string) diff --git a/unit_tests/engine/geometry_string.cpp b/unit_tests/engine/geometry_string.cpp index bdb0c3741..fffe56e7e 100644 --- a/unit_tests/engine/geometry_string.cpp +++ b/unit_tests/engine/geometry_string.cpp @@ -44,4 +44,47 @@ BOOST_AUTO_TEST_CASE(decode) } } +BOOST_AUTO_TEST_CASE(encode) +{ + // Coordinates; these would be the coordinates we give the loc parameter, + // e.g. loc=10.00,10.0&loc=10.01,10.1... + util::Coordinate coord1(util::FloatLongitude{10.0}, util::FloatLatitude{10.00}); + util::Coordinate coord2(util::FloatLongitude{10.1}, util::FloatLatitude{10.01}); + util::Coordinate coord3(util::FloatLongitude{10.2}, util::FloatLatitude{10.02}); + util::Coordinate coord4(util::FloatLongitude{10.3}, util::FloatLatitude{10.03}); + util::Coordinate coord5(util::FloatLongitude{10.4}, util::FloatLatitude{10.04}); + + // Test polyline string for the 5 coordinates + const std::string polyline = "_c`|@_c`|@o}@_pRo}@_pRo}@_pRo}@_pR"; + + // Put the test coordinates into the vector for comparison + std::vector cmp_coords = {coord1, coord2, coord3, coord4, coord5}; + + const auto encodedPolyline = encodePolyline<100000>(cmp_coords.begin(), cmp_coords.end()); + + BOOST_CHECK_EQUAL(encodedPolyline, polyline); +} + + +BOOST_AUTO_TEST_CASE(encode6) +{ + // Coordinates; these would be the coordinates we give the loc parameter, + // e.g. loc=10.00,10.0&loc=10.01,10.1... + util::Coordinate coord1(util::FloatLongitude{10.0}, util::FloatLatitude{10.00}); + util::Coordinate coord2(util::FloatLongitude{10.1}, util::FloatLatitude{10.01}); + util::Coordinate coord3(util::FloatLongitude{10.2}, util::FloatLatitude{10.02}); + util::Coordinate coord4(util::FloatLongitude{10.3}, util::FloatLatitude{10.03}); + util::Coordinate coord5(util::FloatLongitude{10.4}, util::FloatLatitude{10.04}); + + // Test polyline string for the 6 coordinates + const std::string polyline = "_gjaR_gjaR_pR_ibE_pR_ibE_pR_ibE_pR_ibE"; + + // Put the test coordinates into the vector for comparison + std::vector cmp_coords = {coord1, coord2, coord3, coord4, coord5}; + + const auto encodedPolyline = encodePolyline<1000000>(cmp_coords.begin(), cmp_coords.end()); + + BOOST_CHECK_EQUAL(encodedPolyline, polyline); +} + BOOST_AUTO_TEST_SUITE_END()