From fdf1204f860f7eb27bacd5caaefac5898692b340 Mon Sep 17 00:00:00 2001 From: Nils Date: Tue, 2 Apr 2024 11:34:07 +0200 Subject: [PATCH] truck penalty for hgv=no edges (#4650) --- CHANGELOG.md | 1 + docs/docs/api/turn-by-turn/api-reference.md | 1 + lua/graph.lua | 22 +++--- proto/options.proto | 3 + src/mjolnir/pbfgraphparser.cc | 3 +- src/mjolnir/restrictionbuilder.cc | 2 +- src/sif/truckcost.cc | 38 +++++++--- src/thor/bidirectional_astar.cc | 12 ++- src/thor/costmatrix.cc | 12 ++- src/thor/dijkstras.cc | 12 ++- src/thor/timedistancematrix.cc | 12 ++- src/thor/unidirectional_astar.cc | 12 ++- test/gurka/test_matrix.cc | 69 +++++++++++++++++ test/gurka/test_route.cc | 82 +++++++++++++++++++++ valhalla/sif/dynamiccost.h | 18 ++++- valhalla/sif/edgelabel.h | 49 +++++++++--- 16 files changed, 291 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16af4e7693..77ce4845d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,6 +106,7 @@ * 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) * UPDATED: tz database to 2024a [#4643](https://github.com/valhalla/valhalla/pull/4643) + * ADDED: `hgv_no_penalty` costing option to allow penalized truck access to `hgv=no` edges [#4650](https://github.com/valhalla/valhalla/pull/4650) ## Release Date: 2023-05-11 Valhalla 3.4.0 * **Removed** diff --git a/docs/docs/api/turn-by-turn/api-reference.md b/docs/docs/api/turn-by-turn/api-reference.md index 1676f3895c..a5961c38b7 100644 --- a/docs/docs/api/turn-by-turn/api-reference.md +++ b/docs/docs/api/turn-by-turn/api-reference.md @@ -148,6 +148,7 @@ The following options are available for `truck` costing. | `axle_load` | The axle load of the truck (in metric tons). Default 9.07. | | `axle_count` | The axle count of the truck. Default 5. | | `hazmat` | A value indicating if the truck is carrying hazardous materials. Default false. | +| `hgv_no_access_penalty` | A penalty applied to roads with no HGV/truck access. If set to a value less than 43200 seconds, HGV will be allowed on these roads and the penalty applies. Default 43200, i.e. trucks are not allowed. | ##### Bicycle costing options The default bicycle costing is tuned toward road bicycles with a slight preference for using [cycleways](http://wiki.openstreetmap.org/wiki/Key:cycleway) or roads with bicycle lanes. Bicycle routes use regular roads where needed or where no direct bicycle lane options exist, but avoid roads without bicycle access. The costing model recognizes several factors unique to bicycle travel and offers several options for tuning bicycle routes. Several factors unique to travel by bicycle influence the resulting route. diff --git a/lua/graph.lua b/lua/graph.lua index b27d4af43b..0067cf1c25 100644 --- a/lua/graph.lua +++ b/lua/graph.lua @@ -91,7 +91,7 @@ access = { ["forestry"] = "false", ["destination"] = "true", ["customers"] = "true", -["official"] = "false", +["official"] = "true", ["public"] = "true", ["restricted"] = "true", ["allowed"] = "true", @@ -155,7 +155,7 @@ motor_vehicle = { ["forestry"] = "false", ["destination"] = "true", ["customers"] = "true", -["official"] = "false", +["official"] = "true", ["public"] = "true", ["restricted"] = "true", ["allowed"] = "true", @@ -232,7 +232,7 @@ bus = { ["restricted"] = "true", ["destination"] = "true", ["delivery"] = "false", -["official"] = "false", +["official"] = "true", ["permit"] = "true" } @@ -245,7 +245,7 @@ taxi = { ["restricted"] = "true", ["destination"] = "true", ["delivery"] = "false", -["official"] = "false", +["official"] = "true", ["permit"] = "true" } @@ -270,10 +270,10 @@ truck = { ["agricultural"] = "false", ["private"] = "true", ["discouraged"] = "false", -["permissive"] = "false", +["permissive"] = "true", ["unsuitable"] = "false", ["agricultural;forestry"] = "false", -["official"] = "false", +["official"] = "true", ["forestry"] = "false", ["destination;delivery"] = "true", ["permit"] = "true", @@ -537,7 +537,7 @@ motor_cycle_node = { ["forestry"] = 0, ["destination"] = 1024, ["customers"] = 1024, -["official"] = 0, +["official"] = 1024, ["public"] = 1024, ["restricted"] = 1024, ["allowed"] = 1024, @@ -553,7 +553,7 @@ bus_node = { ["restricted"] = 64, ["destination"] = 64, ["delivery"] = 0, -["official"] = 0, +["official"] = 64, ["permit"] = 64 } @@ -566,7 +566,7 @@ taxi_node = { ["restricted"] = 32, ["destination"] = 32, ["delivery"] = 0, -["official"] = 0, +["official"] = 32, ["permit"] = 32 } @@ -580,10 +580,10 @@ truck_node = { ["agricultural"] = 0, ["private"] = 8, ["discouraged"] = 0, -["permissive"] = 0, +["permissive"] = 8, ["unsuitable"] = 0, ["agricultural;forestry"] = 0, -["official"] = 0, +["official"] = 8, ["forestry"] = 0, ["destination;delivery"] = 8, ["permit"] = 8, diff --git a/proto/options.proto b/proto/options.proto index 79549f216a..afb3866ccc 100644 --- a/proto/options.proto +++ b/proto/options.proto @@ -313,6 +313,9 @@ message Costing { float use_lit = 82; bool disable_hierarchy_pruning = 83; bool ignore_non_vehicular_restrictions = 84; + oneof has_hgv_no_access_penalty { + float hgv_no_access_penalty = 85; + } } oneof has_options { diff --git a/src/mjolnir/pbfgraphparser.cc b/src/mjolnir/pbfgraphparser.cc index 24663c5fbb..3312c05eff 100644 --- a/src/mjolnir/pbfgraphparser.cc +++ b/src/mjolnir/pbfgraphparser.cc @@ -4990,7 +4990,8 @@ void PBFGraphParser::ParseRelations(const boost::property_tree::ptree& pt, OSMPBF::Interest::CHANGESETS), callback); } - LOG_INFO("Finished with " + std::to_string(osmdata.restrictions.size()) + " simple restrictions"); + LOG_INFO("Finished with " + std::to_string(osmdata.restrictions.size()) + + " simple turn restrictions"); LOG_INFO("Finished with " + std::to_string(osmdata.lane_connectivity_map.size()) + " lane connections"); diff --git a/src/mjolnir/restrictionbuilder.cc b/src/mjolnir/restrictionbuilder.cc index 6c8c050a81..da322d954e 100644 --- a/src/mjolnir/restrictionbuilder.cc +++ b/src/mjolnir/restrictionbuilder.cc @@ -752,7 +752,7 @@ void RestrictionBuilder::Build(const boost::property_tree::ptree& pt, std::vector> promises(threads.size()); // Start the threads - LOG_INFO("Adding Restrictions at level " + std::to_string(tl->level)); + LOG_INFO("Adding complex turn restrictions at level " + std::to_string(tl->level)); for (size_t i = 0; i < threads.size(); ++i) { threads[i].reset(new std::thread(build, std::cref(complex_from_restrictions_file), std::cref(complex_to_restrictions_file), diff --git a/src/sif/truckcost.cc b/src/sif/truckcost.cc index 94b8b4ba42..de5c415939 100644 --- a/src/sif/truckcost.cc +++ b/src/sif/truckcost.cc @@ -88,16 +88,17 @@ constexpr float kSurfaceFactor[] = { }; // Valid ranges and defaults -constexpr ranged_default_t kLowClassPenaltyRange{0, kDefaultLowClassPenalty, kMaxPenalty}; -constexpr ranged_default_t kTruckWeightRange{0, kDefaultTruckWeight, 100.0f}; -constexpr ranged_default_t kTruckAxleLoadRange{0, kDefaultTruckAxleLoad, 40.0f}; -constexpr ranged_default_t kTruckHeightRange{0, kDefaultTruckHeight, 10.0f}; -constexpr ranged_default_t kTruckWidthRange{0, kDefaultTruckWidth, 10.0f}; -constexpr ranged_default_t kTruckLengthRange{0, kDefaultTruckLength, 50.0f}; -constexpr ranged_default_t kUseTollsRange{0, kDefaultUseTolls, 1.0f}; +constexpr ranged_default_t kLowClassPenaltyRange{0.f, kDefaultLowClassPenalty, kMaxPenalty}; +constexpr ranged_default_t kTruckWeightRange{0.f, kDefaultTruckWeight, 100.0f}; +constexpr ranged_default_t kTruckAxleLoadRange{0.f, kDefaultTruckAxleLoad, 40.0f}; +constexpr ranged_default_t kTruckHeightRange{0.f, kDefaultTruckHeight, 10.0f}; +constexpr ranged_default_t kTruckWidthRange{0.f, kDefaultTruckWidth, 10.0f}; +constexpr ranged_default_t kTruckLengthRange{0.f, kDefaultTruckLength, 50.0f}; +constexpr ranged_default_t kUseTollsRange{0.f, kDefaultUseTolls, 1.0f}; constexpr ranged_default_t kAxleCountRange{2, kDefaultAxleCount, 20}; -constexpr ranged_default_t kUseHighwaysRange{0, kDefaultUseHighways, 1.0f}; -constexpr ranged_default_t kTopSpeedRange{10, kMaxAssumedTruckSpeed, kMaxSpeedKph}; +constexpr ranged_default_t kUseHighwaysRange{0.f, kDefaultUseHighways, 1.0f}; +constexpr ranged_default_t kTopSpeedRange{10.f, kMaxAssumedTruckSpeed, kMaxSpeedKph}; +constexpr ranged_default_t kHGVNoAccessRange{0.f, kMaxPenalty, kMaxPenalty}; BaseCostingOptionsConfig GetBaseCostOptsConfig() { BaseCostingOptionsConfig cfg{}; @@ -311,6 +312,9 @@ class TruckCost : public DynamicCost { // Density factor used in edge transition costing std::vector trans_density_factor_; + + // determine if we should allow hgv=no edges and penalize them instead + float no_hgv_access_penalty_; }; // Constructor @@ -368,6 +372,12 @@ TruckCost::TruckCost(const Costing& costing) for (uint32_t d = 0; d < 16; d++) { density_factor_[d] = 0.85f + (d * 0.025f); } + + // determine what to do with hgv=no edges + bool no_hgv_access_penalty_active = !(costing_options.hgv_no_access_penalty() == kMaxPenalty); + no_hgv_access_penalty_ = no_hgv_access_penalty_active * costing_options.hgv_no_access_penalty(); + // set the access mask to both car & truck if that penalty is active + access_mask_ = no_hgv_access_penalty_active ? (kAutoAccess | kTruckAccess) : kTruckAccess; } // Destructor @@ -551,6 +561,10 @@ Cost TruckCost::TransitionCost(const baldr::DirectedEdge* edge, c.cost += low_class_penalty_; } + // Penalty if the request wants to avoid hgv=no edges instead of disallowing + c.cost += + no_hgv_access_penalty_ * (pred.has_hgv_access() && !(edge->forwardaccess() & kTruckAccess)); + // Transition time = turncost * stopimpact * densityfactor if (edge->stopimpact(idx) > 0 && !shortest_) { float turn_cost; @@ -624,6 +638,10 @@ Cost TruckCost::TransitionCostReverse(const uint32_t idx, c.cost += low_class_penalty_; } + // Penalty if the request wants to avoid hgv=no edges instead of disallowing + c.cost += no_hgv_access_penalty_ * + ((pred->forwardaccess() & kTruckAccess) && !(edge->forwardaccess() & kTruckAccess)); + // Transition time = turncost * stopimpact * densityfactor if (edge->stopimpact(idx) > 0 && !shortest_) { float turn_cost; @@ -713,6 +731,8 @@ void ParseTruckCostOptions(const rapidjson::Document& doc, JSON_PBF_RANGED_DEFAULT(co, kUseHighwaysRange, json, "/use_highways", use_highways); JSON_PBF_RANGED_DEFAULT_V2(co, kAxleCountRange, json, "/axle_count", axle_count); JSON_PBF_RANGED_DEFAULT(co, kTopSpeedRange, json, "/top_speed", top_speed); + JSON_PBF_RANGED_DEFAULT(co, kHGVNoAccessRange, json, "/hgv_no_access_penalty", + hgv_no_access_penalty); } cost_ptr_t CreateTruckCost(const Costing& costing_options) { diff --git a/src/thor/bidirectional_astar.cc b/src/thor/bidirectional_astar.cc index 1d31b9bef2..e6907a2e22 100644 --- a/src/thor/bidirectional_astar.cc +++ b/src/thor/bidirectional_astar.cc @@ -329,7 +329,8 @@ inline bool BidirectionalAStar::ExpandInner(baldr::GraphReader& graphreader, costing_->TurnType(pred.opp_local_idx(), nodeinfo, meta.edge), restriction_idx, 0, meta.edge->destonly() || - (costing_->is_hgv() && meta.edge->destonly_hgv())); + (costing_->is_hgv() && meta.edge->destonly_hgv()), + meta.edge->forwardaccess() & kTruckAccess); adjacencylist_forward_.add(idx); } else { idx = edgelabels_reverse_.size(); @@ -346,7 +347,8 @@ inline bool BidirectionalAStar::ExpandInner(baldr::GraphReader& graphreader, opp_pred_edge), restriction_idx, 0, opp_edge->destonly() || - (costing_->is_hgv() && opp_edge->destonly_hgv())); + (costing_->is_hgv() && opp_edge->destonly_hgv()), + opp_edge->forwardaccess() & kTruckAccess); adjacencylist_reverse_.add(idx); } @@ -1017,7 +1019,8 @@ void BidirectionalAStar::SetOrigin(GraphReader& graphreader, static_cast(flow_sources & kDefaultFlowMask), sif::InternalTurn::kNoTurn, 0, directededge->destonly() || - (costing_->is_hgv() && directededge->destonly_hgv())); + (costing_->is_hgv() && directededge->destonly_hgv()), + directededge->forwardaccess() & kTruckAccess); adjacencylist_forward_.add(idx); // setting this edge as reached @@ -1113,7 +1116,8 @@ void BidirectionalAStar::SetDestination(GraphReader& graphreader, static_cast(flow_sources & kDefaultFlowMask), sif::InternalTurn::kNoTurn, kInvalidRestriction, directededge->destonly() || - (costing_->is_hgv() && directededge->destonly_hgv())); + (costing_->is_hgv() && directededge->destonly_hgv()), + directededge->forwardaccess() & kTruckAccess); adjacencylist_reverse_.add(idx); // setting this edge as reached, sending the opposing because this is the reverse tree diff --git a/src/thor/costmatrix.cc b/src/thor/costmatrix.cc index b260177e41..21e440c93d 100644 --- a/src/thor/costmatrix.cc +++ b/src/thor/costmatrix.cc @@ -527,7 +527,8 @@ bool CostMatrix::ExpandInner(baldr::GraphReader& graphreader, costing_->TurnType(pred.opp_local_idx(), nodeinfo, meta.edge), restriction_idx, 0, meta.edge->destonly() || - (costing_->is_hgv() && meta.edge->destonly_hgv())); + (costing_->is_hgv() && meta.edge->destonly_hgv()), + meta.edge->forwardaccess() & kTruckAccess); } else { edgelabels.emplace_back(pred_idx, meta.edge_id, opp_edge_id, meta.edge, newcost, mode_, tc, pred_dist, not_thru_pruning, @@ -536,7 +537,8 @@ bool CostMatrix::ExpandInner(baldr::GraphReader& graphreader, costing_->TurnType(meta.edge->localedgeidx(), nodeinfo, opp_edge, opp_pred_edge), restriction_idx, 0, - opp_edge->destonly() || (costing_->is_hgv() && opp_edge->destonly_hgv())); + opp_edge->destonly() || (costing_->is_hgv() && opp_edge->destonly_hgv()), + opp_edge->forwardaccess() & kTruckAccess); } adj.add(idx); // mark the edge as settled for the connection check @@ -1030,7 +1032,8 @@ void CostMatrix::SetSources(GraphReader& graphreader, InternalTurn::kNoTurn, kInvalidRestriction, static_cast(costing_->Allowed(directededge, tile)), directededge->destonly() || - (costing_->is_hgv() && directededge->destonly_hgv())); + (costing_->is_hgv() && directededge->destonly_hgv()), + directededge->forwardaccess() & kTruckAccess); edge_label.set_not_thru(false); // Add EdgeLabel to the adjacency list (but do not set its status). @@ -1114,7 +1117,8 @@ void CostMatrix::SetTargets(baldr::GraphReader& graphreader, InternalTurn::kNoTurn, kInvalidRestriction, static_cast(costing_->Allowed(opp_dir_edge, opp_tile)), directededge->destonly() || - (costing_->is_hgv() && directededge->destonly_hgv())); + (costing_->is_hgv() && directededge->destonly_hgv()), + directededge->forwardaccess() & kTruckAccess); edge_label.set_not_thru(false); // Add EdgeLabel to the adjacency list (but do not set its status). diff --git a/src/thor/dijkstras.cc b/src/thor/dijkstras.cc index 24174d02c3..1e4099b067 100644 --- a/src/thor/dijkstras.cc +++ b/src/thor/dijkstras.cc @@ -254,7 +254,8 @@ void Dijkstras::ExpandInner(baldr::GraphReader& graphreader, costing_->TurnType(pred.opp_local_idx(), nodeinfo, directededge), restriction_idx, pred.path_id(), directededge->destonly() || - (costing_->is_hgv() && directededge->destonly_hgv())); + (costing_->is_hgv() && directededge->destonly_hgv()), + directededge->forwardaccess() & kTruckAccess); } else { bdedgelabels_.emplace_back(pred_idx, edgeid, oppedgeid, directededge, newcost, mode_, @@ -265,7 +266,8 @@ void Dijkstras::ExpandInner(baldr::GraphReader& graphreader, opp_pred_edge), restriction_idx, pred.path_id(), opp_edge->destonly() || - (costing_->is_hgv() && opp_edge->destonly_hgv())); + (costing_->is_hgv() && opp_edge->destonly_hgv()), + opp_edge->forwardaccess() & kTruckAccess); } adjacencylist_.add(idx); } @@ -823,7 +825,8 @@ void Dijkstras::SetOriginLocations(GraphReader& graphreader, static_cast(flow_sources & kDefaultFlowMask), InternalTurn::kNoTurn, kInvalidRestriction, multipath_ ? path_id : 0, directededge->destonly() || - (costing_->is_hgv() && directededge->destonly_hgv())); + (costing_->is_hgv() && directededge->destonly_hgv()), + directededge->forwardaccess() & kTruckAccess); // Set the origin flag bdedgelabels_.back().set_origin(); @@ -914,7 +917,8 @@ void Dijkstras::SetDestinationLocations( static_cast(flow_sources & kDefaultFlowMask), InternalTurn::kNoTurn, restriction_idx, multipath_ ? path_id : 0, directededge->destonly() || - (costing_->is_hgv() && directededge->destonly_hgv())); + (costing_->is_hgv() && directededge->destonly_hgv()), + directededge->forwardaccess() & kTruckAccess); adjacencylist_.add(idx); edgestatus_.Set(opp_edge_id, EdgeSet::kTemporary, idx, opp_tile, multipath_ ? path_id : 0); } diff --git a/src/thor/timedistancematrix.cc b/src/thor/timedistancematrix.cc index c8abb4a633..e6176b8a6e 100644 --- a/src/thor/timedistancematrix.cc +++ b/src/thor/timedistancematrix.cc @@ -156,7 +156,8 @@ void TimeDistanceMatrix::Expand(GraphReader& graphreader, 0 != (flow_sources & kDefaultFlowMask), costing_->TurnType(pred.opp_local_idx(), nodeinfo, directededge), 0, directededge->destonly() || - (costing_->is_hgv() && directededge->destonly_hgv())); + (costing_->is_hgv() && directededge->destonly_hgv()), + directededge->forwardaccess() & kTruckAccess); } else { edgelabels_.emplace_back(pred_idx, edgeid, directededge, newcost, newcost.cost, mode_, path_distance, restriction_idx, @@ -166,7 +167,8 @@ void TimeDistanceMatrix::Expand(GraphReader& graphreader, opp_pred_edge), 0, opp_edge->destonly() || - (costing_->is_hgv() && opp_edge->destonly_hgv())); + (costing_->is_hgv() && opp_edge->destonly_hgv()), + opp_edge->forwardaccess() & kTruckAccess); } *es = {EdgeSet::kTemporary, idx}; @@ -374,14 +376,16 @@ void TimeDistanceMatrix::SetOrigin(GraphReader& graphreader, static_cast(flow_sources & kDefaultFlowMask), InternalTurn::kNoTurn, 0, directededge->destonly() || - (costing_->is_hgv() && directededge->destonly_hgv())); + (costing_->is_hgv() && directededge->destonly_hgv()), + directededge->forwardaccess() & kTruckAccess); } else { edgelabels_.emplace_back(kInvalidLabel, opp_edge_id, opp_dir_edge, cost, cost.cost, mode_, dist, baldr::kInvalidRestriction, !costing_->IsClosed(directededge, tile), static_cast(flow_sources & kDefaultFlowMask), InternalTurn::kNoTurn, 0, directededge->destonly() || - (costing_->is_hgv() && directededge->destonly_hgv())); + (costing_->is_hgv() && directededge->destonly_hgv()), + directededge->forwardaccess() & kTruckAccess); } edgelabels_.back().set_origin(); adjacencylist_.add(edgelabels_.size() - 1); diff --git a/src/thor/unidirectional_astar.cc b/src/thor/unidirectional_astar.cc index 15a055334d..036f9c2bea 100644 --- a/src/thor/unidirectional_astar.cc +++ b/src/thor/unidirectional_astar.cc @@ -276,7 +276,8 @@ inline bool UnidirectionalAStar::ExpandInner( costing_->TurnType(pred.opp_local_idx(), nodeinfo, meta.edge), restriction_idx, 0, meta.edge->destonly() || - (costing_->is_hgv() && meta.edge->destonly_hgv())); + (costing_->is_hgv() && meta.edge->destonly_hgv()), + meta.edge->forwardaccess() & kTruckAccess); } else { edgelabels_.emplace_back(pred_idx, meta.edge_id, opp_edge_id, meta.edge, cost, sortcost, dist, mode_, transition_cost, @@ -287,7 +288,8 @@ inline bool UnidirectionalAStar::ExpandInner( opp_pred_edge), restriction_idx, 0, opp_edge->destonly() || - (costing_->is_hgv() && opp_edge->destonly_hgv())); + (costing_->is_hgv() && opp_edge->destonly_hgv()), + opp_edge->forwardaccess() & kTruckAccess); } auto& edge_label = edgelabels_.back(); @@ -758,7 +760,8 @@ void UnidirectionalAStar::SetOrigin( 0 != (flow_sources & kDefaultFlowMask), sif::InternalTurn::kNoTurn, kInvalidRestriction, 0, directededge->destonly() || - (costing_->is_hgv() && directededge->destonly_hgv())); + (costing_->is_hgv() && directededge->destonly_hgv()), + directededge->forwardaccess() & kTruckAccess); } else { edgelabels_.emplace_back(kInvalidLabel, opp_edge_id, edgeid, opp_dir_edge, cost, sortcost, dist, mode_, Cost{}, false, @@ -766,7 +769,8 @@ void UnidirectionalAStar::SetOrigin( 0 != (flow_sources & kDefaultFlowMask), sif::InternalTurn::kNoTurn, kInvalidRestriction, 0, directededge->destonly() || - (costing_->is_hgv() && directededge->destonly_hgv())); + (costing_->is_hgv() && directededge->destonly_hgv()), + directededge->forwardaccess() & kTruckAccess); } auto& edge_label = edgelabels_.back(); diff --git a/test/gurka/test_matrix.cc b/test/gurka/test_matrix.cc index 3500fcdaec..960841ebdf 100644 --- a/test/gurka/test_matrix.cc +++ b/test/gurka/test_matrix.cc @@ -820,3 +820,72 @@ TEST(StandAlone, CostMatrixTrivialRoutes) { EXPECT_EQ(matrix.matrix().shapes(0), encoded); } } + +TEST(StandAlone, HGVNoAccessPenalty) { + // if hgv_no_penalty is on we should still respect the maxweight restriction on CD + // so we should take the next-best hgv=no edge with JK + const std::string ascii_map = R"( + A-1-------B----C----D----E--2-------F + | | + J----K + | | + | | + L----M + )"; + + const gurka::ways ways = { + {"AB", {{"highway", "residential"}, {"hgv", "no"}}}, + {"BC", {{"highway", "residential"}}}, + {"CD", {{"highway", "residential"}, {"hgv", "no"}, {"maxweight", "3.5"}}}, + {"DE", {{"highway", "residential"}}}, + {"EF", {{"highway", "residential"}, {"hgv", "no"}}}, + {"CJ", {{"highway", "residential"}}}, + {"JK", {{"highway", "residential"}, {"hgv", "no"}}}, + {"JLMK", {{"highway", "residential"}}}, + {"KD", {{"highway", "residential"}}}, + }; + + const auto layout = gurka::detail::map_to_coordinates(ascii_map, 100); + gurka::map map = gurka::buildtiles(layout, ways, {}, {}, "test/data/hgv_no_access_penalty", + {{"service_limits.max_timedep_distance_matrix", "50000"}}); + + std::unordered_map cost_matrix = + {{"/costing_options/truck/hgv_no_access_penalty", "2000"}, + {"/sources/0/date_time", "2024-03-20T09:00"}, + {"/prioritize_bidirectional", "1"}}; + std::unordered_map td_matrix = + {{"/costing_options/truck/hgv_no_access_penalty", "2000"}, + {"/sources/0/date_time", "2024-03-20T09:00"}}; + + // do both costmatrix & timedistancematrix + std::vector> options = {cost_matrix, td_matrix}; + for (auto& truck_options : options) { + + // by default, take the detour via LM + // NOTE, we're not snapping to the hgv=no edges either + { + auto matrix = + gurka::do_action(valhalla::Options::sources_to_targets, map, {"1"}, {"2"}, "truck"); + EXPECT_EQ(matrix.matrix().distances(0), 2500); + } + + // with a high hgv_no_penalty also take the detour via LM, but do snap to the hgv=no edges + { + auto matrix = gurka::do_action(valhalla::Options::sources_to_targets, map, {"1"}, {"2"}, + "truck", truck_options); + // TODO(nils): timedistancematrix seems to have a tiny bug where time options result in slightly + // less distances + EXPECT_NEAR(matrix.matrix().distances(0), 3600, 2); + } + + // with a low hgv_no_penalty take the JK edge + { + truck_options["/costing_options/truck/hgv_no_access_penalty"] = "10"; + auto matrix = gurka::do_action(valhalla::Options::sources_to_targets, map, {"1"}, {"2"}, + "truck", truck_options); + // TODO(nils): timedistancematrix seems to have a tiny bug where time options result in slightly + // less distances + EXPECT_NEAR(matrix.matrix().distances(0), 3000, 2); + } + } +} diff --git a/test/gurka/test_route.cc b/test/gurka/test_route.cc index a7ab49095a..2d62d04aca 100644 --- a/test/gurka/test_route.cc +++ b/test/gurka/test_route.cc @@ -1153,3 +1153,85 @@ TEST_F(DateTimeTest, Invariant) { } } } + +TEST(StandAlone, HGVNoAccessPenalty) { + // if hgv_no_penalty is on we should still respect the maxweight restriction on CD + // so we should take the next-best hgv=no edge with JK + const std::string ascii_map = R"( + A-1--B----C----D----E--2-F----G----H--3-I + | | + J----K + | | + | | + L----M + )"; + + const gurka::ways ways = { + {"AB", {{"highway", "residential"}, {"hgv", "no"}}}, + {"BC", {{"highway", "residential"}}}, + {"CD", {{"highway", "residential"}, {"hgv", "no"}, {"maxweight", "3.5"}}}, + {"DE", {{"highway", "residential"}}}, + {"EF", {{"highway", "residential"}, {"hgv", "no"}}}, + {"FG", {{"highway", "residential"}, {"hgv", "no"}}}, + {"GH", {{"highway", "residential"}, {"hgv", "no"}}}, + {"HI", {{"highway", "residential"}, {"hgv", "no"}}}, + {"CJ", {{"highway", "residential"}}}, + {"JK", {{"highway", "residential"}, {"hgv", "no"}}}, + {"JLMK", {{"highway", "residential"}}}, + {"KD", {{"highway", "residential"}}}, + }; + + const auto layout = gurka::detail::map_to_coordinates(ascii_map, 100); + gurka::map map = gurka::buildtiles(layout, ways, {}, {}, "test/data/hgv_no_access_penalty"); + + std::unordered_map no_time = { + {"/costing_options/truck/hgv_no_access_penalty", "2000"}}; + std::unordered_map with_depart_at = + {{"/costing_options/truck/hgv_no_access_penalty", "2000"}, + {"/locations/0/date_time", "2024-03-20T09:00"}}; + std::unordered_map with_arrive_by = + {{"/costing_options/truck/hgv_no_access_penalty", "2000"}, + {"/locations/1/date_time", "2024-03-20T09:00"}}; + + auto get_leg_cost = [](const valhalla::Api& response) { + return response.trip().routes(0).legs(0).node().rbegin()->cost().elapsed_cost().cost(); + }; + + // do both bidirectional & both unidirectional a* + std::vector> options = {no_time, with_depart_at, + with_arrive_by}; + for (auto& truck_options : options) { + + // by default, take the detour via LM + // NOTE, we're not snapping to the hgv=no edges either + { + auto route = gurka::do_action(valhalla::Options::route, map, {"1", "2"}, "truck"); + gurka::assert::raw::expect_path(route, {"BC", "CJ", "JLMK", "KD", "DE"}); + } + + // with a high hgv_no_penalty also take the detour via LM, but do snap to the hgv=no edges + { + auto route = + gurka::do_action(valhalla::Options::route, map, {"1", "2"}, "truck", truck_options); + gurka::assert::raw::expect_path(route, {"AB", "BC", "CJ", "JLMK", "KD", "DE", "EF"}); + } + + // with a low hgv_no_penalty take the JK edge + { + truck_options["/costing_options/truck/hgv_no_access_penalty"] = "10"; + auto route = + gurka::do_action(valhalla::Options::route, map, {"1", "2"}, "truck", truck_options); + gurka::assert::raw::expect_path(route, {"AB", "BC", "CJ", "JK", "KD", "DE", "EF"}); + } + + // if all hgv=no and a high hgv_no_penalty, truck should not trigger the penalty at all + // so cost should be similar to car + { + truck_options["/costing_options/truck/hgv_no_access_penalty"] = "2000"; + auto route_car = gurka::do_action(valhalla::Options::route, map, {"2", "3"}, "auto"); + auto route_truck = + gurka::do_action(valhalla::Options::route, map, {"2", "3"}, "truck", truck_options); + EXPECT_NEAR(get_leg_cost(route_car), get_leg_cost(route_truck), 300.0); + } + } +} diff --git a/valhalla/sif/dynamiccost.h b/valhalla/sif/dynamiccost.h index 441a64c1c5..02efa7e056 100644 --- a/valhalla/sif/dynamiccost.h +++ b/valhalla/sif/dynamiccost.h @@ -497,10 +497,10 @@ class DynamicCost { // If forward, check if the edge marks the end of a restriction, else check // if the edge marks the start of a complex restriction. - if ((forward && (edge->end_restriction() & access_mode())) || - (!forward && (edge->start_restriction() & access_mode()))) { + if ((forward && (edge->end_restriction() & access_mask_)) || + (!forward && (edge->start_restriction() & access_mask_))) { // Get complex restrictions. Return false if no restrictions are found - auto restrictions = tile->GetRestrictions(forward, edgeid, access_mode()); + auto restrictions = tile->GetRestrictions(forward, edgeid, access_mask_); if (restrictions.size() == 0) { return false; } @@ -599,6 +599,18 @@ class DynamicCost { baldr::DateTime::get_tz_db().from_index(tz_index)); } + /*** + * Evaluates mode-specific and time-dependent access restrictions, including a binary + * search to get the tile's access restrictions. + * + * @param access_mode The access mode to get restrictions for + * @param edge The edge to check for restrictions + * @param is_dest Is there a destination on the edge? + * @param tile The edge's tile + * @param current_time Needed for time dependent restrictions + * @param tz_index The current timezone index + * @param restriction_idx Records the restriction in the tile for later retrieval + */ inline bool EvaluateRestrictions(uint32_t access_mode, const baldr::DirectedEdge* edge, const bool is_dest, diff --git a/valhalla/sif/edgelabel.h b/valhalla/sif/edgelabel.h index 471cacc836..d5a9dc1b23 100644 --- a/valhalla/sif/edgelabel.h +++ b/valhalla/sif/edgelabel.h @@ -39,7 +39,7 @@ class EdgeLabel { endnode_(baldr::kInvalidGraphId), use_(0), classification_(0), shortcut_(0), dest_only_(0), origin_(0), destination_(0), toll_(0), not_thru_(0), deadend_(0), on_complex_rest_(0), closure_pruning_(0), path_id_(0), restriction_idx_(0), internal_turn_(0), unpaved_(0), - has_measured_speed_(0), cost_(0, 0), sortcost_(0) { + has_measured_speed_(0), hgv_access_(0), cost_(0, 0), sortcost_(0) { assert(path_id_ <= baldr::kMaxMultiPathId); } @@ -61,6 +61,7 @@ class EdgeLabel { * @param path_id When searching more than one path at a time this denotes which path * the this label is tracking * @param destonly Destination only, either mode-specific or general + * @param hgv_access Whether HGV is allowed */ EdgeLabel(const uint32_t predecessor, const baldr::GraphId& edgeid, @@ -74,7 +75,8 @@ class EdgeLabel { const bool has_measured_speed, const InternalTurn internal_turn, const uint8_t path_id = 0, - const bool destonly = false) + const bool destonly = false, + const bool hgv_access = false) : predecessor_(predecessor), path_distance_(path_distance), restrictions_(edge->restrictions()), edgeid_(edgeid), opp_index_(edge->opp_index()), opp_local_idx_(edge->opp_local_idx()), mode_(static_cast(mode)), endnode_(edge->endnode()), @@ -86,7 +88,8 @@ class EdgeLabel { edge->end_restriction()), closure_pruning_(closure_pruning), path_id_(path_id), restriction_idx_(restriction_idx), internal_turn_(static_cast(internal_turn)), unpaved_(edge->unpaved()), - has_measured_speed_(has_measured_speed), cost_(cost), sortcost_(sortcost) { + has_measured_speed_(has_measured_speed), hgv_access_(hgv_access), cost_(cost), + sortcost_(sortcost) { dest_only_ = destonly ? destonly : edge->destonly(); assert(path_id_ <= baldr::kMaxMultiPathId); } @@ -371,6 +374,14 @@ class EdgeLabel { return unpaved_; } + /** + * Does it have HGV access? + * @return Returns true if the (opposing) edge had HGV access + */ + bool has_hgv_access() const { + return hgv_access_; + } + protected: // predecessor_: Index to the predecessor edge label information. // Note: invalid predecessor value uses all 32 bits (so if this needs to @@ -433,7 +444,9 @@ class EdgeLabel { // Flag indicating edge is an unpaved road. uint32_t unpaved_ : 1; uint32_t has_measured_speed_ : 1; - uint32_t spare : 13; + // Flag if this edge had HGV access + uint32_t hgv_access_ : 1; + uint32_t spare : 12; Cost cost_; // Cost and elapsed time along the path. float sortcost_; // Sort cost - includes A* heuristic. @@ -469,6 +482,7 @@ class PathEdgeLabel : public EdgeLabel { * @param path_id When searching more than one path at a time this denotes which path * the this label is tracking * @param destonly Destination only, either mode-specific or general + * @param hgv_access Whether HGV is allowed */ PathEdgeLabel(const uint32_t predecessor, const baldr::GraphId& edgeid, @@ -483,7 +497,8 @@ class PathEdgeLabel : public EdgeLabel { const bool has_measured_speed, const InternalTurn internal_turn, const uint8_t path_id = 0, - const bool destonly = false) + const bool destonly = false, + const bool hgv_access = false) : EdgeLabel(predecessor, edgeid, edge, @@ -496,7 +511,8 @@ class PathEdgeLabel : public EdgeLabel { has_measured_speed, internal_turn, path_id, - destonly), + destonly, + hgv_access), transition_cost_(transition_cost) { assert(path_id_ <= baldr::kMaxMultiPathId); } @@ -549,6 +565,7 @@ class BDEdgeLabel : public EdgeLabel { * @param path_id When searching more than one path at a time this denotes which path * the this label is tracking * @param destonly Destination only, either mode-specific or general + * @param hgv_access Whether HGV is allowed */ BDEdgeLabel(const uint32_t predecessor, const baldr::GraphId& edgeid, @@ -565,7 +582,8 @@ class BDEdgeLabel : public EdgeLabel { const sif::InternalTurn internal_turn, const uint8_t restriction_idx, const uint8_t path_id = 0, - const bool destonly = false) + const bool destonly = false, + const bool hgv_access = false) : EdgeLabel(predecessor, edgeid, edge, @@ -578,7 +596,8 @@ class BDEdgeLabel : public EdgeLabel { has_measured_speed, internal_turn, path_id, - destonly), + destonly, + hgv_access), transition_cost_(transition_cost), opp_edgeid_(oppedgeid), not_thru_pruning_(not_thru_pruning), distance_(dist) { } @@ -604,6 +623,7 @@ class BDEdgeLabel : public EdgeLabel { * @param path_id When searching more than one path at a time this denotes which path * the this label is tracking * @param destonly Destination only, either mode-specific or general + * @param hgv_access Whether HGV is allowed */ BDEdgeLabel(const uint32_t predecessor, const baldr::GraphId& edgeid, @@ -619,7 +639,8 @@ class BDEdgeLabel : public EdgeLabel { const sif::InternalTurn internal_turn, const uint8_t restriction_idx, const uint8_t path_id = 0, - const bool destonly = false) + const bool destonly = false, + const bool hgv_access = false) : EdgeLabel(predecessor, edgeid, edge, @@ -632,7 +653,8 @@ class BDEdgeLabel : public EdgeLabel { has_measured_speed, internal_turn, path_id, - destonly), + destonly, + hgv_access), transition_cost_(transition_cost), opp_edgeid_(oppedgeid), not_thru_pruning_(not_thru_pruning), distance_(0.0f) { } @@ -655,6 +677,7 @@ class BDEdgeLabel : public EdgeLabel { * @param path_id When searching more than one path at a time this denotes which path * the this label is tracking * @param destonly Destination only, either mode-specific or general + * @param hgv_access Whether HGV is allowed */ BDEdgeLabel(const uint32_t predecessor, const baldr::GraphId& edgeid, @@ -668,7 +691,8 @@ class BDEdgeLabel : public EdgeLabel { const bool has_measured_speed, const sif::InternalTurn internal_turn, const uint8_t path_id = 0, - const bool destonly = false) + const bool destonly = false, + const bool hgv_access = false) : EdgeLabel(predecessor, edgeid, edge, @@ -681,7 +705,8 @@ class BDEdgeLabel : public EdgeLabel { has_measured_speed, internal_turn, path_id, - destonly), + destonly, + hgv_access), transition_cost_({}), not_thru_pruning_(!edge->not_thru()), distance_(dist) { opp_edgeid_ = {}; }