Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"soft" ignore restrictions #4606

Merged
merged 20 commits into from Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -96,6 +96,7 @@
* FIXED: Fixed roundoff issue in Tiles Row and Col methods [#4585](https://github.com/valhalla/valhalla/pull/4585)
* ADDED: isochrone proper polygon support & pbf output for isochrone [#4575](https://github.com/valhalla/valhalla/pull/4575)
* ADDED: return isotile grid as geotiff [#4594](https://github.com/valhalla/valhalla/pull/4594)
* ADDED: `ignore_non_vehicular_restrictions` parameter for truck costing [#4606](https://github.com/valhalla/valhalla/pull/4606)

## Release Date: 2023-05-11 Valhalla 3.4.0
* **Removed**
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/api/turn-by-turn/api-reference.md
Expand Up @@ -121,6 +121,10 @@ These options are available for `auto`, `bus`, and `truck` costing methods.
| `fixed_speed` | Fixed speed the vehicle can go. Used to override the calculated speed. Can be useful if speed of vehicle is known. `fixed_speed` must be between 1 and 252 KPH. The default value is 0 KPH which disables fixed speed and falls back to the standard calculated speed based on the road attribution. |
| `ignore_closures` | If set to `true`, ignores all closures, marked due to live traffic closures, during routing. **Note:** This option cannot be set if `location.search_filter.exclude_closures` is also specified in the request and will return an error if it is |
| `closure_factor` | A factor that penalizes the cost when traversing a closed edge (eg: if `search_filter.exclude_closures` is `false` for origin and/or destination location and the route starts/ends on closed edges). Its value can range from `1.0` - don't penalize closed edges, to `10.0` - apply high cost penalty to closed edges. Default value is `9.0`. **Note:** This factor is applicable only for motorized modes of transport, i.e `auto`, `motorcycle`, `motor_scooter`, `bus`, `truck` & `taxi`. |
| `ignore_restrictions` | If set to `true`, ignores any restrictions (e.g. turn/dimensional/conditional restrictions). Especially useful for matching GPS traces to the road network regardless of restrictions. Default is `false`. |
| `ignore_non_vehicular_restrictions` | Similar to `ignore_restrictions`, but will respect restrictions that impact vehicle safety, such as weight and size restrictions. |
| `ignore_access` | Will ignore mode-specific access tags. Especially useful for matching GPS traces to the road network regardless of restrictions. Default is `false`. |
| `ignore_closures` | Will ignore traffic closures. Default is `false`. |

###### Other costing options
The following options are available for `auto`, `bus`, `taxi`, and `truck` costing methods.
Expand Down
1 change: 1 addition & 0 deletions proto/options.proto
Expand Up @@ -312,6 +312,7 @@ message Costing {
uint32 axle_count = 81;
float use_lit = 82;
bool disable_hierarchy_pruning = 83;
bool ignore_non_vehicular_restrictions = 84;
}

oneof has_options {
Expand Down
8 changes: 4 additions & 4 deletions src/sif/autocost.cc
Expand Up @@ -421,7 +421,7 @@ bool AutoCost::Allowed(const baldr::DirectedEdge* edge,
// a not thru region and a heading selected an edge entering the
// region.
if (!IsAccessible(edge) || (!pred.deadend() && pred.opp_local_idx() == edge->localedgeidx()) ||
((pred.restrictions() & (1 << edge->localedgeidx())) && !ignore_restrictions_) ||
((pred.restrictions() & (1 << edge->localedgeidx())) && !ignore_turn_restrictions_) ||
edge->surface() == Surface::kImpassable || IsUserAvoidEdge(edgeid) ||
(!allow_destination_only_ && !pred.destonly() && edge->destonly()) ||
(pred.closure_pruning() && IsClosed(edge, tile)) ||
Expand All @@ -446,7 +446,7 @@ bool AutoCost::AllowedReverse(const baldr::DirectedEdge* edge,
// Check access, U-turn, and simple turn restriction.
// Allow U-turns at dead-end nodes.
if (!IsAccessible(opp_edge) || (!pred.deadend() && pred.opp_local_idx() == edge->localedgeidx()) ||
((opp_edge->restrictions() & (1 << pred.opp_local_idx())) && !ignore_restrictions_) ||
((opp_edge->restrictions() & (1 << pred.opp_local_idx())) && !ignore_turn_restrictions_) ||
opp_edge->surface() == Surface::kImpassable || IsUserAvoidEdge(opp_edgeid) ||
(!allow_destination_only_ && !pred.destonly() && opp_edge->destonly()) ||
(pred.closure_pruning() && IsClosed(opp_edge, tile)) ||
Expand Down Expand Up @@ -808,7 +808,7 @@ bool BusCost::AllowedReverse(const baldr::DirectedEdge* edge,
// Check access, U-turn, and simple turn restriction.
// Allow U-turns at dead-end nodes.
if (!IsAccessible(opp_edge) || (!pred.deadend() && pred.opp_local_idx() == edge->localedgeidx()) ||
((opp_edge->restrictions() & (1 << pred.opp_local_idx())) && !ignore_restrictions_) ||
((opp_edge->restrictions() & (1 << pred.opp_local_idx())) && !ignore_turn_restrictions_) ||
opp_edge->surface() == Surface::kImpassable || IsUserAvoidEdge(opp_edgeid) ||
(!allow_destination_only_ && !pred.destonly() && opp_edge->destonly()) ||
(pred.closure_pruning() && IsClosed(opp_edge, tile)) ||
Expand Down Expand Up @@ -987,7 +987,7 @@ bool TaxiCost::AllowedReverse(const baldr::DirectedEdge* edge,
// Check access, U-turn, and simple turn restriction.
// Allow U-turns at dead-end nodes.
if (!IsAccessible(opp_edge) || (!pred.deadend() && pred.opp_local_idx() == edge->localedgeidx()) ||
((opp_edge->restrictions() & (1 << pred.opp_local_idx())) && !ignore_restrictions_) ||
((opp_edge->restrictions() & (1 << pred.opp_local_idx())) && !ignore_turn_restrictions_) ||
opp_edge->surface() == Surface::kImpassable || IsUserAvoidEdge(opp_edgeid) ||
(!allow_destination_only_ && !pred.destonly() && opp_edge->destonly()) ||
(pred.closure_pruning() && IsClosed(opp_edge, tile)) ||
Expand Down
4 changes: 2 additions & 2 deletions src/sif/bicyclecost.cc
Expand Up @@ -545,7 +545,7 @@ bool BicycleCost::Allowed(const baldr::DirectedEdge* edge,
if (!IsAccessible(edge) || edge->is_shortcut() ||
(!pred.deadend() && pred.opp_local_idx() == edge->localedgeidx() &&
pred.mode() == TravelMode::kBicycle) ||
(!ignore_restrictions_ && (pred.restrictions() & (1 << edge->localedgeidx()))) ||
(!ignore_turn_restrictions_ && (pred.restrictions() & (1 << edge->localedgeidx()))) ||
IsUserAvoidEdge(edgeid)) {
return false;
}
Expand Down Expand Up @@ -582,7 +582,7 @@ bool BicycleCost::AllowedReverse(const baldr::DirectedEdge* edge,
opp_edge->use() == Use::kPlatformConnection ||
(!pred.deadend() && pred.opp_local_idx() == edge->localedgeidx() &&
pred.mode() == TravelMode::kBicycle) ||
(!ignore_restrictions_ && (opp_edge->restrictions() & (1 << pred.opp_local_idx()))) ||
(!ignore_turn_restrictions_ && (opp_edge->restrictions() & (1 << pred.opp_local_idx()))) ||
IsUserAvoidEdge(opp_edgeid)) {
return false;
}
Expand Down
5 changes: 5 additions & 0 deletions src/sif/dynamiccost.cc
Expand Up @@ -151,6 +151,9 @@ DynamicCost::DynamicCost(const Costing& costing,
closure_factor_(kDefaultClosureFactor), flow_mask_(kDefaultFlowMask),
shortest_(costing.options().shortest()),
ignore_restrictions_(costing.options().ignore_restrictions()),
ignore_non_vehicular_restrictions_(costing.options().ignore_non_vehicular_restrictions()),
ignore_turn_restrictions_(costing.options().ignore_restrictions() ||
costing.options().ignore_non_vehicular_restrictions()),
ignore_oneways_(costing.options().ignore_oneways()),
ignore_access_(costing.options().ignore_access()),
ignore_closures_(costing.options().ignore_closures()),
Expand Down Expand Up @@ -389,6 +392,8 @@ void ParseBaseCostOptions(const rapidjson::Value& json,
JSON_PBF_DEFAULT(co, false, json, "/ignore_oneways", ignore_oneways);
JSON_PBF_DEFAULT(co, false, json, "/ignore_access", ignore_access);
JSON_PBF_DEFAULT(co, false, json, "/ignore_closures", ignore_closures);
JSON_PBF_DEFAULT(co, false, json, "/ignore_non_vehicular_restrictions",
ignore_non_vehicular_restrictions);

// shortest
JSON_PBF_DEFAULT(co, false, json, "/shortest", shortest);
Expand Down
4 changes: 2 additions & 2 deletions src/sif/motorcyclecost.cc
Expand Up @@ -355,7 +355,7 @@ bool MotorcycleCost::Allowed(const baldr::DirectedEdge* edge,
// Check access, U-turn, and simple turn restriction.
// Allow U-turns at dead-end nodes.
if (!IsAccessible(edge) || (!pred.deadend() && pred.opp_local_idx() == edge->localedgeidx()) ||
((pred.restrictions() & (1 << edge->localedgeidx())) && !ignore_restrictions_) ||
((pred.restrictions() & (1 << edge->localedgeidx())) && !ignore_turn_restrictions_) ||
(edge->surface() > kMinimumMotorcycleSurface) || IsUserAvoidEdge(edgeid) ||
(!allow_destination_only_ && !pred.destonly() && edge->destonly()) ||
(pred.closure_pruning() && IsClosed(edge, tile))) {
Expand All @@ -379,7 +379,7 @@ bool MotorcycleCost::AllowedReverse(const baldr::DirectedEdge* edge,
// Check access, U-turn, and simple turn restriction.
// Allow U-turns at dead-end nodes.
if (!IsAccessible(opp_edge) || (!pred.deadend() && pred.opp_local_idx() == edge->localedgeidx()) ||
((opp_edge->restrictions() & (1 << pred.opp_local_idx())) && !ignore_restrictions_) ||
((opp_edge->restrictions() & (1 << pred.opp_local_idx())) && !ignore_turn_restrictions_) ||
(opp_edge->surface() > kMinimumMotorcycleSurface) || IsUserAvoidEdge(opp_edgeid) ||
(!allow_destination_only_ && !pred.destonly() && opp_edge->destonly()) ||
(pred.closure_pruning() && IsClosed(opp_edge, tile))) {
Expand Down
4 changes: 2 additions & 2 deletions src/sif/motorscootercost.cc
Expand Up @@ -376,7 +376,7 @@ bool MotorScooterCost::Allowed(const baldr::DirectedEdge* edge,
// Check access, U-turn, and simple turn restriction.
// Allow U-turns at dead-end nodes.
if (!IsAccessible(edge) || (!pred.deadend() && pred.opp_local_idx() == edge->localedgeidx()) ||
((pred.restrictions() & (1 << edge->localedgeidx())) && !ignore_restrictions_) ||
((pred.restrictions() & (1 << edge->localedgeidx())) && !ignore_turn_restrictions_) ||
(edge->surface() > kMinimumScooterSurface) || IsUserAvoidEdge(edgeid) ||
(!allow_destination_only_ && !pred.destonly() && edge->destonly()) ||
(pred.closure_pruning() && IsClosed(edge, tile))) {
Expand All @@ -400,7 +400,7 @@ bool MotorScooterCost::AllowedReverse(const baldr::DirectedEdge* edge,
// Check access, U-turn, and simple turn restriction.
// Allow U-turns at dead-end nodes.
if (!IsAccessible(opp_edge) || (!pred.deadend() && pred.opp_local_idx() == edge->localedgeidx()) ||
((opp_edge->restrictions() & (1 << pred.opp_local_idx())) && !ignore_restrictions_) ||
((opp_edge->restrictions() & (1 << pred.opp_local_idx())) && !ignore_turn_restrictions_) ||
(opp_edge->surface() > kMinimumScooterSurface) || IsUserAvoidEdge(opp_edgeid) ||
(!allow_destination_only_ && !pred.destonly() && opp_edge->destonly()) ||
(pred.closure_pruning() && IsClosed(opp_edge, tile))) {
Expand Down
4 changes: 2 additions & 2 deletions src/sif/truckcost.cc
Expand Up @@ -439,7 +439,7 @@ inline bool TruckCost::Allowed(const baldr::DirectedEdge* edge,
uint8_t& restriction_idx) const {
// Check access, U-turn, and simple turn restriction.
if (!IsAccessible(edge) || (!pred.deadend() && pred.opp_local_idx() == edge->localedgeidx()) ||
((pred.restrictions() & (1 << edge->localedgeidx())) && !ignore_restrictions_) ||
((pred.restrictions() & (1 << edge->localedgeidx())) && (!ignore_turn_restrictions_)) ||
edge->surface() == Surface::kImpassable || IsUserAvoidEdge(edgeid) ||
(!allow_destination_only_ && !pred.destonly() && edge->destonly_hgv()) ||
(pred.closure_pruning() && IsClosed(edge, tile)) ||
Expand All @@ -463,7 +463,7 @@ bool TruckCost::AllowedReverse(const baldr::DirectedEdge* edge,
uint8_t& restriction_idx) const {
// Check access, U-turn, and simple turn restriction.
if (!IsAccessible(opp_edge) || (!pred.deadend() && pred.opp_local_idx() == edge->localedgeidx()) ||
((opp_edge->restrictions() & (1 << pred.opp_local_idx())) && !ignore_restrictions_) ||
((opp_edge->restrictions() & (1 << pred.opp_local_idx())) && !ignore_turn_restrictions_) ||
opp_edge->surface() == Surface::kImpassable || IsUserAvoidEdge(opp_edgeid) ||
(!allow_destination_only_ && !pred.destonly() && opp_edge->destonly_hgv()) ||
(pred.closure_pruning() && IsClosed(opp_edge, tile)) ||
Expand Down
5 changes: 3 additions & 2 deletions test/gurka/test_conditional_restrictions.cc
Expand Up @@ -97,8 +97,9 @@ class ConditionalRestrictions : public ::testing::Test {
};

const auto layout = gurka::detail::map_to_coordinates(ascii_map, grid_size_meters);
map = gurka::buildtiles(layout, ways, {}, {}, "test/data/conditional_restrictions",
{{"mjolnir.timezone", {"test/data/tz.sqlite"}}});
map = gurka::buildtiles(layout, ways, {}, {},
VALHALLA_BUILD_DIR "test/data/conditional_restrictions",
{{"mjolnir.timezone", {VALHALLA_BUILD_DIR "test/data/tz.sqlite"}}});
}
};

Expand Down
180 changes: 180 additions & 0 deletions test/gurka/test_ignore_restrictions.cc
@@ -0,0 +1,180 @@

#include "gurka.h"
#include "test.h"
#include <gtest/gtest.h>

using namespace valhalla;

std::string get_access_mode(const std::string& costing_mode) {
if (costing_mode == "auto") {
return "motorcar";
} else if (costing_mode == "truck") {
return "hgv";
} else if (costing_mode == "motorcycle") {
return "motorcycle";
} else if (costing_mode == "taxi") {
return "taxi";
} else if (costing_mode == "bus") {
return "bus";
} else if (costing_mode == "motor_scooter") {
return "moped";
} else if (costing_mode == "bicycle") {
return "bicycle";
}

throw std::runtime_error("unexpected costing mode " + costing_mode + ".");
}

class CommonRestrictionTest : public ::testing::TestWithParam<std::string> {
protected:
static gurka::nodelayout layout;

static void SetUpTestSuite() {
constexpr double gridsize = 500;

const std::string map = R"(
A----------B-----C----D
|
E
|
|
F
)";

layout = gurka::detail::map_to_coordinates(map, gridsize);
}
};
gurka::nodelayout CommonRestrictionTest::layout = {};

TEST_P(CommonRestrictionTest, IgnoreCommonRestrictions) {
const std::string& costing = GetParam();
const gurka::ways ways = {
{"AB", {{"highway", "secondary"}}},
{"BC", {{"highway", "secondary"}}},
{"CD", {{"highway", "secondary"}}},
{"AE", {{"highway", "secondary"}}},
{"EF",
{{"highway", "secondary"}, {get_access_mode(costing) + ":conditional", "no @ (09:00-18:00)"}}},
};
const gurka::relations relations = {{{
{gurka::way_member, "AB", "from"},
{gurka::way_member, "BC", "to"},
{gurka::node_member, "B", "via"},
},
{{"type", "restriction"}, {"restriction", "no_straight_on"}}}};
gurka::map map =
gurka::buildtiles(layout, ways, {}, relations, "test/data/ignore_non_vehicular_restrictions",
{{"mjolnir.timezone", {VALHALLA_BUILD_DIR "test/data/tz.sqlite"}}});
// first, route through turn restriction, should fail...
try {
valhalla::Api route = gurka::do_action(valhalla::Options::route, map, {"A", "D"}, costing, {});
FAIL() << "Expected valhalla_exception_t.";
} catch (const valhalla_exception_t& err) { EXPECT_EQ(err.code, 442); } catch (...) {
FAIL() << "Expected valhalla_exception_t.";
}

// ...but succeed with ignore_non_vehicular_restrictions
valhalla::Api route =
gurka::do_action(valhalla::Options::route, map, {"A", "D"}, costing,
{{"/costing_options/" + costing + "/ignore_non_vehicular_restrictions", "1"}});
gurka::assert::raw::expect_path(route, {"AB", "BC", "CD"});

// second, route through time based access restrictions, should fail...
try {
valhalla::Api route =
gurka::do_action(valhalla::Options::route, map, {"A", "F"}, costing,
{{"/date_time/type", "1"}, {"/date_time/value", "2020-10-10T13:00"}});
FAIL() << "Expected route to fail.";
} catch (const valhalla_exception_t& err) { EXPECT_EQ(err.code, 442); } catch (...) {
FAIL() << "Expected different error code.";
}

//...but succeed with ignore_non_vehicular_restrictions
valhalla::Api route_succeed =
gurka::do_action(valhalla::Options::route, map, {"A", "F"}, costing,
{{"/costing_options/" + costing + "/ignore_non_vehicular_restrictions", "1"},
{"/date_time/type", "1"},
{"/date_time/value", "2020-10-10T13:00"}});
gurka::assert::raw::expect_path(route_succeed, {"AE", "EF"});
}

// check that dimensional restrictions are not affected
TEST_P(CommonRestrictionTest, IgnoreCommonRestrictionsFail) {
const std::string& costing = GetParam();

if (costing == "motorcycle" || costing == "bicycle" || costing == "motor_scooter")
return; // no height restrictions for these

const gurka::ways ways = {
{"AB", {{"highway", "secondary"}}}, {"BC", {{"highway", "secondary"}, {"maxheight", "2.5"}}},
{"CD", {{"highway", "secondary"}}}, {"AE", {{"highway", "secondary"}}},
{"EF", {{"highway", "secondary"}}},
};
gurka::map map =
gurka::buildtiles(layout, ways, {}, {}, "test/data/ignore_non_vehicular_restrictions",
{{"mjolnir.timezone", {VALHALLA_BUILD_DIR "test/data/tz.sqlite"}}});
// should fail, too low
try {
valhalla::Api route = gurka::do_action(valhalla::Options::route, map, {"A", "D"}, costing,
{{"/costing_options/" + costing + "/height", "3"}});
FAIL() << "Expected valhalla_exception_t.";
} catch (const valhalla_exception_t& err) { EXPECT_EQ(err.code, 442); } catch (...) {
FAIL() << "Expected valhalla_exception_t.";
}

// still too low
try {
valhalla::Api route =
gurka::do_action(valhalla::Options::route, map, {"A", "D"}, costing,
{{"/costing_options/" + costing + "/ignore_non_vehicular_restrictions", "1"},
{"/costing_options/" + costing + "/height", "3"}});
FAIL() << "Expected valhalla_exception_t.";
} catch (const valhalla_exception_t& err) { EXPECT_EQ(err.code, 442); } catch (...) {
FAIL() << "Expected valhalla_exception_t.";
}
}
INSTANTIATE_TEST_SUITE_P(
CommonRestrictionsTest,
CommonRestrictionTest,
::testing::Values("auto", "truck", "motorcycle", "taxi", "bus", "bicycle", "motor_scooter"));

// make sure truck weight restrictions are not affected by the request parameter
TEST(CommonRestrictionsFail, Truck) {

constexpr double gridsize = 500;

const std::string ascii_map = R"(
A----------B-----C----D
)";

const auto layout = gurka::detail::map_to_coordinates(ascii_map, gridsize);
const gurka::ways ways = {{"AB", {{"highway", "residential"}}},
{"BC", {{"highway", "residential"}, {"maxheight", "2.5"}}},
{"CD", {{"highway", "residential"}}}};

gurka::map map =
gurka::buildtiles(layout, ways, {}, {}, "test/data/ignore_non_vehicular_restrictions_truck",
{{"mjolnir.timezone", {VALHALLA_BUILD_DIR "test/data/tz.sqlite"}}});

// too long
try {
valhalla::Api route = gurka::do_action(valhalla::Options::route, map, {"A", "D"}, "truck",
{{"/costing_options/truck/height", "3"}});

FAIL() << "Expected valhalla_exception_t.";
} catch (const valhalla_exception_t& err) { EXPECT_EQ(err.code, 442); } catch (...) {
FAIL() << "Expected to fail with a different error code.";
}

// ...still too long
try {
valhalla::Api route =
gurka::do_action(valhalla::Options::route, map, {"A", "D"}, "truck",
{{"/costing_options/truck/ignore_non_vehicular_restrictions", "1"},
{"/costing_options/truck/height", "3"}});
FAIL() << "Expected no route to be found.";

} catch (const valhalla_exception_t& err) { EXPECT_EQ(err.code, 442); } catch (...) {
FAIL() << "Expected to fail with a different error code.";
}
}