diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ed1826aa56..27a41680807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Changes from 5.19.0: - Table: - CHANGED: switch to pre-calculated distances for table responses for large speedup and 10% memory increase. [#5251](https://github.com/Project-OSRM/osrm-backend/pull/5251) + - ADDED: new parameter `fallback_speed` which will fill `null` cells with estimated value [#5257](https://github.com/Project-OSRM/osrm-backend/pull/5257) - Features: - ADDED: direct mmapping of datafiles is now supported via the `--mmap` switch. [#5242](https://github.com/Project-OSRM/osrm-backend/pull/5242) - REMOVED: the previous `--memory_file` switch is now deprecated and will fallback to `--mmap` [#5242](https://github.com/Project-OSRM/osrm-backend/pull/5242) diff --git a/docs/http.md b/docs/http.md index 3cb2b811bd6..cd4fb988576 100644 --- a/docs/http.md +++ b/docs/http.md @@ -236,8 +236,9 @@ In addition to the [general options](#general-options) the following options are |------------|--------------------------------------------------|---------------------------------------------| |sources |`{index};{index}[;{index} ...]` or `all` (default)|Use location with given index as source. | |destinations|`{index};{index}[;{index} ...]` or `all` (default)|Use location with given index as destination.| -|annotations |`duration` (default), `distance`, or `duration,distance`|Return the requested table or tables in response. Note that computing the `distances` table is currently only implemented for CH. If `annotations=distance` or `annotations=duration,distance` is requested when running a MLD router, a `NotImplemented` error will be returned. -| +|annotations |`duration` (default), `distance`, or `duration,distance`|Return the requested table or tables in response. Note that computing the `distances` table is currently only implemented for CH. If `annotations=distance` or `annotations=duration,distance` is requested when running a MLD router, a `NotImplemented` error will be returned. | +|fallback_speed|`double > 0`|If no route found between a source/destination pair, calculate the as-the-crow-flies distance, then use this speed to estimate duration.| +|fallback_coordinate|`input` (default), or `snapped`| When using a `fallback_speed`, use the user-supplied coordinate (`input`), or the snapped location (`snapped`) for calculating distances.| Unlike other array encoded options, the length of `sources` and `destinations` can be **smaller or equal** to number of input locations; diff --git a/docs/nodejs/api.md b/docs/nodejs/api.md index 49cad3920fd..1f9765e0828 100644 --- a/docs/nodejs/api.md +++ b/docs/nodejs/api.md @@ -129,6 +129,8 @@ tables. Optionally returns distance table. - `options.destinations` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)?** An array of `index` elements (`0 <= integer < #coordinates`) to use location with given index as destination. Default is to use all. - `options.approaches` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)?** Keep waypoints on curb side. Can be `null` (unrestricted, default) or `curb`. + - `options.fallback_speed` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** Replace `null` responses in result with as-the-crow-flies estimates based on `fallback_speed`. Value is in metres/second. + - `options.fallback_coordinate` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** Either `input` (default) or `snapped`. If using a `fallback_speed`, use either the user-supplied coordinate (`input`), or the snapped coordinate (`snapped`) for calculating the as-the-crow-flies diestance between two points. - `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** **Examples** diff --git a/features/testbot/distance_matrix.feature b/features/testbot/distance_matrix.feature index 0b9e7c733d8..83d32d28347 100644 --- a/features/testbot/distance_matrix.feature +++ b/features/testbot/distance_matrix.feature @@ -595,3 +595,75 @@ Feature: Basic Distance Matrix | d | 1478.9 | 1579 | 1779.1 | 0 | 1280.1 | 882.5 | | e | 198.8 | 298.9 | 499 | 710.3 | 0 | 1592.8 | | f | 596.4 | 696.5 | 896.6 | 1107.9 | 397.6 | 0 | + + + Scenario: Testbot - Filling in noroutes with estimates (defaults to input coordinate location) + Given a grid size of 300 meters + Given the extract extra arguments "--small-component-size 4" + Given the query options + | fallback_speed | 5 | + Given the node map + """ + a b f h 1 + d e g i + """ + + And the ways + | nodes | + | abeda | + | fhigf | + + When I request a travel distance matrix I should get + | | a | b | f | 1 | + | a | 0 | 300.2 | 900.7 | 1501.1 | + | b | 300.2 | 0 | 600.5 | 1200.9 | + | f | 900.7 | 600.5 | 0 | 302.2 | + | 1 | 1501.1 | 1200.9 | 300.2 | 0 | + + Scenario: Testbot - Filling in noroutes with estimates - use input coordinate + Given a grid size of 300 meters + Given the extract extra arguments "--small-component-size 4" + Given the query options + | fallback_speed | 5 | + | fallback_coordinate | input | + Given the node map + """ + a b f h 1 + d e g i + """ + + And the ways + | nodes | + | abeda | + | fhigf | + + When I request a travel distance matrix I should get + | | a | b | f | 1 | + | a | 0 | 300.2 | 900.7 | 1501.1 | + | b | 300.2 | 0 | 600.5 | 1200.9 | + | f | 900.7 | 600.5 | 0 | 302.2 | + | 1 | 1501.1 | 1200.9 | 300.2 | 0 | + + Scenario: Testbot - Filling in noroutes with estimates - use snapped coordinate + Given a grid size of 300 meters + Given the extract extra arguments "--small-component-size 4" + Given the query options + | fallback_speed | 5 | + | fallback_coordinate | snapped | + Given the node map + """ + a b f h 1 + d e g i + """ + + And the ways + | nodes | + | abeda | + | fhigf | + + When I request a travel distance matrix I should get + | | a | b | f | 1 | + | a | 0 | 300.2 | 900.7 | 1200.9 | + | b | 300.2 | 0 | 600.5 | 900.7 | + | f | 900.7 | 600.5 | 0 | 302.2 | + | 1 | 1200.9 | 900.7 | 300.2 | 0 | \ No newline at end of file diff --git a/features/testbot/duration_matrix.feature b/features/testbot/duration_matrix.feature index c6977dfabd9..629a9f3f92c 100644 --- a/features/testbot/duration_matrix.feature +++ b/features/testbot/duration_matrix.feature @@ -510,3 +510,74 @@ Feature: Basic Duration Matrix | | a | | a | 0 | | b | 24.1 | + + Scenario: Testbot - Filling in noroutes with estimates (defaults to input coordinate location) + Given a grid size of 300 meters + Given the extract extra arguments "--small-component-size 4" + Given the query options + | fallback_speed | 5 | + Given the node map + """ + a b f h 1 + d e g i + """ + + And the ways + | nodes | + | abeda | + | fhigf | + + When I request a travel time matrix I should get + | | a | b | f | 1 | + | a | 0 | 30 | 18 | 30 | + | b | 30 | 0 | 12 | 24 | + | f | 18 | 12 | 0 | 30 | + | 1 | 30 | 24 | 30 | 0 | + + Scenario: Testbot - Filling in noroutes with estimates - use input coordinate + Given a grid size of 300 meters + Given the extract extra arguments "--small-component-size 4" + Given the query options + | fallback_speed | 5 | + | fallback_coordinate | input | + Given the node map + """ + a b f h 1 + d e g i + """ + + And the ways + | nodes | + | abeda | + | fhigf | + + When I request a travel time matrix I should get + | | a | b | f | 1 | + | a | 0 | 30 | 18 | 30 | + | b | 30 | 0 | 12 | 24 | + | f | 18 | 12 | 0 | 30 | + | 1 | 30 | 24 | 30 | 0 | + + Scenario: Testbot - Filling in noroutes with estimates - use snapped coordinate + Given a grid size of 300 meters + Given the extract extra arguments "--small-component-size 4" + Given the query options + | fallback_speed | 5 | + | fallback_coordinate | snapped | + Given the node map + """ + a b f h 1 + d e g i + """ + + And the ways + | nodes | + | abeda | + | fhigf | + + When I request a travel time matrix I should get + | | a | b | f | 1 | + | a | 0 | 30 | 18 | 24 | + | b | 30 | 0 | 12 | 18 | + | f | 18 | 12 | 0 | 30 | + | 1 | 24 | 18 | 30 | 0 | \ No newline at end of file diff --git a/include/engine/api/table_parameters.hpp b/include/engine/api/table_parameters.hpp index 4f1a496bd6f..243d9414231 100644 --- a/include/engine/api/table_parameters.hpp +++ b/include/engine/api/table_parameters.hpp @@ -59,6 +59,15 @@ struct TableParameters : public BaseParameters { std::vector sources; std::vector destinations; + double fallback_speed = 0; + + enum class FallbackCoordinateType + { + Input = 0, + Snapped = 1 + }; + + FallbackCoordinateType fallback_coordinate_type = FallbackCoordinateType::Input; enum class AnnotationsType { @@ -90,6 +99,19 @@ struct TableParameters : public BaseParameters { } + template + TableParameters(std::vector sources_, + std::vector destinations_, + const AnnotationsType annotations_, + double fallback_speed_, + FallbackCoordinateType fallback_coordinate_type_, + Args... args_) + : BaseParameters{std::forward(args_)...}, sources{std::move(sources_)}, + destinations{std::move(destinations_)}, fallback_speed{fallback_speed_}, + fallback_coordinate_type{fallback_coordinate_type_}, annotations{annotations_} + { + } + bool IsValid() const { if (!BaseParameters::IsValid()) @@ -117,6 +139,9 @@ struct TableParameters : public BaseParameters if (std::any_of(begin(destinations), end(destinations), not_in_range)) return false; + if (fallback_speed < 0) + return false; + return true; } }; diff --git a/include/nodejs/node_osrm_support.hpp b/include/nodejs/node_osrm_support.hpp index 27752e431bd..ba4aba6ceab 100644 --- a/include/nodejs/node_osrm_support.hpp +++ b/include/nodejs/node_osrm_support.hpp @@ -1183,6 +1183,52 @@ argumentsToTableParameter(const Nan::FunctionCallbackInfo &args, } } + if (obj->Has(Nan::New("fallback_speed").ToLocalChecked())) + { + auto fallback_speed = obj->Get(Nan::New("fallback_speed").ToLocalChecked()); + + if (!fallback_speed->IsNumber()) + { + Nan::ThrowError("fallback_speed must be a number"); + return table_parameters_ptr(); + } + else if (fallback_speed->NumberValue() < 0) + { + Nan::ThrowError("fallback_speed must be > 0"); + return table_parameters_ptr(); + } + + params->fallback_speed = static_cast(fallback_speed->NumberValue()); + } + + if (obj->Has(Nan::New("fallback_coordinate").ToLocalChecked())) + { + auto fallback_coordinate = obj->Get(Nan::New("fallback_coordinate").ToLocalChecked()); + + if (!fallback_coordinate->IsString()) + { + Nan::ThrowError("fallback_coordinate must be a string: [input, snapped]"); + return table_parameters_ptr(); + } + + std::string fallback_coordinate_str = *v8::String::Utf8Value(fallback_coordinate); + + if (fallback_coordinate_str == "snapped") + { + params->fallback_coordinate_type = + osrm::TableParameters::FallbackCoordinateType::Snapped; + } + else if (fallback_coordinate_str == "input") + { + params->fallback_coordinate_type = osrm::TableParameters::FallbackCoordinateType::Input; + } + else + { + Nan::ThrowError("'fallback_coordinate' param must be one of [input, snapped]"); + return table_parameters_ptr(); + } + } + return params; } diff --git a/include/server/api/base_parameters_grammar.hpp b/include/server/api/base_parameters_grammar.hpp index 66eee9448cc..bb731e8d39c 100644 --- a/include/server/api/base_parameters_grammar.hpp +++ b/include/server/api/base_parameters_grammar.hpp @@ -178,6 +178,8 @@ struct BaseParametersGrammar : boost::spirit::qi::grammar qi::rule base_rule; qi::rule query_rule; + qi::real_parser double_; + private: qi::rule bearings_rule; qi::rule radiuses_rule; @@ -195,7 +197,6 @@ struct BaseParametersGrammar : boost::spirit::qi::grammar qi::rule base64_char; qi::rule polyline_chars; qi::rule unlimited_rule; - qi::real_parser double_; qi::symbols approach_type; }; diff --git a/include/server/api/table_parameter_grammar.hpp b/include/server/api/table_parameter_grammar.hpp index 5be79364501..8b6d0ace69a 100644 --- a/include/server/api/table_parameter_grammar.hpp +++ b/include/server/api/table_parameter_grammar.hpp @@ -48,10 +48,24 @@ struct TableParametersGrammar : public BaseParametersGrammar -qi::lit(".json") > - -('?' > (table_rule(qi::_r1) | base_rule(qi::_r1)) % '&'); + root_rule = + BaseGrammar::query_rule(qi::_r1) > -qi::lit(".json") > + -('?' > (table_rule(qi::_r1) | base_rule(qi::_r1) | fallback_speed_rule(qi::_r1) | + (qi::lit("fallback_coordinate=") > + fallback_coordinate_type + [ph::bind(&engine::api::TableParameters::fallback_coordinate_type, + qi::_r1) = qi::_1])) % + '&'); } TableParametersGrammar(qi::rule &root_rule_) : BaseGrammar(root_rule_) @@ -73,13 +87,19 @@ struct TableParametersGrammar : public BaseParametersGrammar base_rule; private: + using json_policy = no_trailing_dot_policy; + qi::rule root_rule; qi::rule table_rule; qi::rule sources_rule; qi::rule destinations_rule; + qi::rule fallback_speed_rule; qi::rule size_t_; qi::symbols annotations; qi::rule annotations_list; + qi::symbols + fallback_coordinate_type; + qi::real_parser double_; }; } } diff --git a/src/engine/plugins/table.cpp b/src/engine/plugins/table.cpp index 3d89bc25a75..d1d7786eec8 100644 --- a/src/engine/plugins/table.cpp +++ b/src/engine/plugins/table.cpp @@ -4,6 +4,7 @@ #include "engine/api/table_parameters.hpp" #include "engine/routing_algorithms/many_to_many.hpp" #include "engine/search_engine_data.hpp" +#include "util/coordinate_calculation.hpp" #include "util/json_container.hpp" #include "util/string_util.hpp" @@ -94,6 +95,41 @@ Status TablePlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms, return Error("NoTable", "No table found", result); } + // Scan table for null results - if any exist, replace with distance estimates + if (params.fallback_speed > 0) + { + for (std::size_t row = 0; row < num_sources; row++) + { + for (std::size_t column = 0; column < num_destinations; column++) + { + const auto &table_index = row * num_sources + column; + if (result_tables_pair.first[table_index] == MAXIMAL_EDGE_DURATION) + { + const auto &source = + snapped_phantoms[params.sources.empty() ? row : params.sources[row]]; + const auto &destination = + snapped_phantoms[params.destinations.empty() ? column + : params.destinations[column]]; + + auto distance_estimate = + params.fallback_coordinate_type == + api::TableParameters::FallbackCoordinateType::Input + ? util::coordinate_calculation::fccApproximateDistance( + source.input_location, destination.input_location) + : util::coordinate_calculation::fccApproximateDistance( + source.location, destination.location); + + result_tables_pair.first[table_index] = + distance_estimate / (double)params.fallback_speed; + if (!result_tables_pair.second.empty()) + { + result_tables_pair.second[table_index] = distance_estimate; + } + } + } + } + } + api::TableAPI table_api{facade, params}; table_api.MakeResponse(result_tables_pair, snapped_phantoms, result); diff --git a/src/server/service/table_service.cpp b/src/server/service/table_service.cpp index 9a9ce0ab218..374ea22022c 100644 --- a/src/server/service/table_service.cpp +++ b/src/server/service/table_service.cpp @@ -56,6 +56,11 @@ std::string getWrongOptionHelp(const engine::api::TableParameters ¶meters) help = "Number of coordinates needs to be at least two."; } + if (parameters.fallback_speed < 0) + { + help = "fallback_speed must be > 0"; + } + return help; } } // anon. ns diff --git a/test/nodejs/table.js b/test/nodejs/table.js index e763eea866e..e8142e6bcc0 100644 --- a/test/nodejs/table.js +++ b/test/nodejs/table.js @@ -245,5 +245,20 @@ tables.forEach(function(annotation) { assert.equal(response[annotation].length, 2); }); }); + + test('table: ' + annotation + ' table in Monaco with fallback speeds', function(assert) { + assert.plan(1); + var osrm = new OSRM({path: mld_data_path, algorithm: 'MLD'}); + var options = { + coordinates: two_test_coordinates, + annotations: [annotation.slice(0,-1)], + fallback_speed: 1, + fallback_coordinate: 'input' + }; + osrm.table(options, function(err, response) { + assert.equal(response[annotation].length, 2); + }); + }); + }); diff --git a/unit_tests/server/parameters_parser.cpp b/unit_tests/server/parameters_parser.cpp index 4d3b8c53b59..4667486a73c 100644 --- a/unit_tests/server/parameters_parser.cpp +++ b/unit_tests/server/parameters_parser.cpp @@ -89,6 +89,8 @@ BOOST_AUTO_TEST_CASE(invalid_table_urls) BOOST_CHECK_EQUAL( testInvalidOptions("1,2;3,4?sources=all&destinations=all&annotations=bla"), 49UL); + BOOST_CHECK_EQUAL(testInvalidOptions("1,2;3,4?fallback_coordinate=asdf"), + 28UL); } BOOST_AUTO_TEST_CASE(valid_route_hint)