diff --git a/docs/profiles.md b/docs/profiles.md index 48e8aebd98..16381f0835 100644 --- a/docs/profiles.md +++ b/docs/profiles.md @@ -416,6 +416,7 @@ angle | Read | Float | number_of_roads | Read | Integer | Number of ways at the intersection of the turn is_u_turn | Read | Boolean | Is the turn a u-turn? has_traffic_light | Read | Boolean | Is a traffic light present at this turn? +has_turning_facility | Read | Boolean | Is a turning facility (turning_circle, turning_loop, or mini_roundabout) present at this intersection? is_left_hand_driving | Read | Boolean | Is left-hand traffic? source_restricted | Read | Boolean | Is it from a restricted access road? (See definition in `process_way`) source_mode | Read | Enum | Travel mode before the turn. Defined in `include/extractor/travel_mode.hpp` diff --git a/features/car/turning_circle_uturn.feature b/features/car/turning_circle_uturn.feature new file mode 100644 index 0000000000..63244e864d --- /dev/null +++ b/features/car/turning_circle_uturn.feature @@ -0,0 +1,190 @@ +@routing @car @turning_circle @uturn +Feature: Car - Use turning circles for u-turns + + Background: + Given the profile "car" + + Scenario: Car - Should use turning_circle for u-turn when direct u-turn is restricted + Given the node map + """ + a---b---c---e + | + d + """ + + And the ways + | nodes | highway | + | abce | primary | + | bd | primary | + + And the nodes + | node | highway | + | d | turning_circle | + + And the relations + | type | way:from | node:via | way:to | restriction | + | restriction | abce | b | abce | no_u_turn | + + When I route I should get + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abce,bd,bd,abce,abce | depart,turn right,continue uturn,turn left,arrive | + + Scenario: Car - Should use turning_loop for u-turn when direct u-turn is restricted + Given the node map + """ + a---b---c---d + | + e + """ + + And the ways + | nodes | highway | + | abcd | primary | + | be | primary | + + And the nodes + | node | highway | + | e | turning_loop | + + And the relations + | type | way:from | node:via | way:to | restriction | + | restriction | abcd | b | abcd | no_u_turn | + + When I route I should get + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abcd,be,be,abcd,abcd | depart,turn right,continue uturn,turn left,arrive | + + Scenario: Car - Should use mini_roundabout for u-turn when direct u-turn is restricted + Given the node map + """ + a---b---c---d + | + e + """ + + And the ways + | nodes | highway | + | abcd | primary | + | be | primary | + + And the nodes + | node | highway | + | e | mini_roundabout | + + And the relations + | type | way:from | node:via | way:to | restriction | + | restriction | abcd | b | abcd | no_u_turn | + + When I route I should get + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abcd,be,be,abcd,abcd | depart,turn right,continue uturn,turn left,arrive | + + Scenario: Car - Prefer turning_circle over plain dead end for u-turn + Given the node map + """ + c + | + a---b + | + d + """ + + And the ways + | nodes | highway | + | ab | primary | + | bc | primary | + | bd | primary | + + And the nodes + | node | highway | + | d | turning_circle | + + And the relations + | type | way:from | node:via | way:to | restriction | + | restriction | ab | b | ab | no_u_turn | + + When I route I should get + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | ab,bd,bd,ab,ab | depart,turn right,continue uturn,turn left,arrive | + + Scenario: Car - Multiple turning facilities, use closest + Given the node map + """ + a---b---c---d---e + | | + f g + """ + + And the ways + | nodes | highway | + | abcde | primary | + | bf | primary | + | dg | primary | + + And the nodes + | node | highway | + | f | turning_circle | + | g | turning_loop | + + When I route I should get + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abcde,bf,bf,abcde,abcde | depart,turn right,continue uturn,turn left,arrive | + + Scenario: Car - Dead end with turning_circle + Given the node map + """ + a---b---c + | + d + """ + + And the ways + | nodes | highway | + | abc | primary | + | cd | primary | + + And the nodes + | node | highway | + | d | turning_circle | + + When I route I should get + | waypoints | route | turns | + | a,d | abc,cd,cd | depart,new name right,arrive | + | d,a | cd,abc,abc | depart,new name left,arrive | + + Scenario: Car - Regular u-turn without turning facility still penalized + Given the node map + """ + a---b---c---d + """ + + And the ways + | nodes | highway | + | abcd | primary | + + # Note: No turning facility nodes defined + + When I route I should get + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abcd,abcd,abcd | depart,continue uturn,arrive | + + Scenario: Car - Turning circle on one-way should respect direction + Given the node map + """ + a---b---c + | + d + """ + + And the ways + | nodes | highway | oneway | + | abc | primary | no | + | bd | primary | yes | + + And the nodes + | node | highway | + | d | turning_circle | + + When I route I should get + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abc,abc,abc | depart,continue uturn,arrive | diff --git a/features/step_definitions/distance_matrix.js b/features/step_definitions/distance_matrix.js index c3e67ee424..7dcf287d63 100644 --- a/features/step_definitions/distance_matrix.js +++ b/features/step_definitions/distance_matrix.js @@ -73,7 +73,7 @@ async function tableCodeOnlyParse(table, annotation, format) { let got; await this.reprocessAndLoadData(); - const testRow = function (row, ri) { + const testRow = function (row, _ri) { return new Promise((resolve, reject) => { const afterRequest = function (err, res, body) { if (err) return reject(err); @@ -174,7 +174,7 @@ async function tableParse(table, noRoute, annotation, format) { await this.reprocessAndLoadData(); // compute matrix - const { response, body } = await new Promise((resolve, reject) => { + const { body } = await new Promise((resolve, reject) => { this.requestTable(waypoints, params, (err, response, body) => { if (err) return reject(err); resolve({ response, body }); diff --git a/features/step_definitions/matching.js b/features/step_definitions/matching.js index 8c6e1adef8..0a96083073 100644 --- a/features/step_definitions/matching.js +++ b/features/step_definitions/matching.js @@ -8,7 +8,7 @@ When(/^I match I should get$/, async function (table) { let got; await this.reprocessAndLoadData(); - const testRow = function (row, ri) { + const testRow = function (row, _ri) { return new Promise((resolve, reject) => { const afterRequest = function (err, res, body) { if (err) return reject(err); diff --git a/features/step_definitions/nearest.js b/features/step_definitions/nearest.js index ac3394be0b..0d50f37342 100644 --- a/features/step_definitions/nearest.js +++ b/features/step_definitions/nearest.js @@ -8,7 +8,7 @@ import { When } from '@cucumber/cucumber'; When(/^I request nearest I should get$/, async function (table) { await this.reprocessAndLoadData(); - const testRow = function (row, ri) { + const testRow = function (row, _ri) { return new Promise((resolve, reject) => { const inNode = this.findNodeByName(row.in); if (!inNode) return reject(new Error(util.format('*** unknown in-node "%s"', row.in))); @@ -79,7 +79,7 @@ When(/^I request nearest I should get$/, async function (table) { When(/^I request nearest with flatbuffers I should get$/, async function (table) { await this.reprocessAndLoadData(); - const testRow = function (row, ri) { + const testRow = function (row, _ri) { return new Promise((resolve, reject) => { const inNode = this.findNodeByName(row.in); if (!inNode) return reject(new Error(util.format('*** unknown in-node "%s"', row.in))); diff --git a/features/step_definitions/trip.js b/features/step_definitions/trip.js index d2e8352843..d67eebecd7 100644 --- a/features/step_definitions/trip.js +++ b/features/step_definitions/trip.js @@ -11,7 +11,7 @@ When(/^I plan a trip I should get$/, async function (table) { let got; await this.reprocessAndLoadData(); - const testRow = function (row, ri) { + const testRow = function (row, _ri) { return new Promise((resolve, reject) => { const afterRequest = function (err, res, body) { if (err) return reject(err); diff --git a/include/extractor/extraction_turn.hpp b/include/extractor/extraction_turn.hpp index 8a71c7a2f6..22d5ea9521 100644 --- a/include/extractor/extraction_turn.hpp +++ b/include/extractor/extraction_turn.hpp @@ -82,6 +82,7 @@ struct ExtractionTurn int number_of_roads, bool is_u_turn, bool has_traffic_light, + bool has_turning_facility, bool is_left_hand_driving, bool source_restricted, @@ -112,7 +113,8 @@ struct ExtractionTurn const NodeID via, const NodeID to) : angle(180. - angle), number_of_roads(number_of_roads), is_u_turn(is_u_turn), - has_traffic_light(has_traffic_light), is_left_hand_driving(is_left_hand_driving), + has_traffic_light(has_traffic_light), has_turning_facility(has_turning_facility), + is_left_hand_driving(is_left_hand_driving), source_restricted(source_restricted), source_mode(source_mode), source_is_motorway(source_is_motorway), source_is_link(source_is_link), @@ -146,11 +148,13 @@ struct ExtractionTurn const ExtractionTurnLeg::EdgeData &target_edge, const std::vector &roads_on_the_right, const std::vector &roads_on_the_left, - const bool has_traffic_light) + const bool has_traffic_light, + const bool has_turning_facility) : ExtractionTurn{0, 2, false, has_traffic_light, + has_turning_facility, false, // source false, @@ -187,6 +191,7 @@ struct ExtractionTurn const int number_of_roads; const bool is_u_turn; const bool has_traffic_light; + const bool has_turning_facility; const bool is_left_hand_driving; // source info diff --git a/include/extractor/obstacles.hpp b/include/extractor/obstacles.hpp index 3eaa400395..8e5cb1ba72 100644 --- a/include/extractor/obstacles.hpp +++ b/include/extractor/obstacles.hpp @@ -174,6 +174,13 @@ class ObstacleMap // inexpensive general test bool any(NodeID to) const { return obstacles.contains(to); } + // is there any obstacle of type 'type' at node 'to' (from any direction)? + // 'type' can be a bitwise-or combination of Obstacle::Type + bool any(NodeID to, Obstacle::Type type) const + { + return any(to) && !get(SPECIAL_NODEID, to, type).empty(); + } + // is there any obstacle of type 'type' at node 'to' when coming from node 'from'? // pass SPECIAL_NODEID as 'from' to query all obstacles at 'to' // 'type' can be a bitwise-or combination of Obstacle::Type diff --git a/profiles/car.lua b/profiles/car.lua index 7689013ebb..19b66add34 100644 --- a/profiles/car.lua +++ b/profiles/car.lua @@ -37,6 +37,10 @@ function setup() speed_reduction = 0.8, turn_bias = 1.075, cardinal_directions = false, + -- Penalty in seconds for u-turns at designated turning facilities + -- (highway=turning_circle, turning_loop, mini_roundabout). + -- Lower than u_turn_penalty because these nodes are specifically designed for turning around. + turning_circle_penalty = 5, -- Penalty multiplier for roads with no lane markings (lane_markings=no) -- Applied to bidirectional roads to prefer roads with clear lane markings @@ -538,7 +542,12 @@ function process_turn(profile, turn) end if turn.is_u_turn then - turn.duration = turn.duration + profile.properties.u_turn_penalty + if turn.has_turning_facility then + -- No regular u-turn penalty at designated turning facilities (turning_circle, turning_loop, mini_roundabout) + turn.duration = turn.duration + profile.turning_circle_penalty + else + turn.duration = turn.duration + profile.properties.u_turn_penalty + end end end diff --git a/profiles/lib/obstacles.lua b/profiles/lib/obstacles.lua index a2ba2ca4aa..c0ec4f06ee 100644 --- a/profiles/lib/obstacles.lua +++ b/profiles/lib/obstacles.lua @@ -3,12 +3,24 @@ local Obstacles = {} +-- Mapping from highway tag values to obstacle types +local highway_to_obstacle_type = { + ["traffic_signals"] = obstacle_type.traffic_signals, + ["stop"] = obstacle_type.stop, + ["give_way"] = obstacle_type.give_way, + ["crossing"] = obstacle_type.crossing, + ["traffic_calming"] = obstacle_type.traffic_calming, + ["mini_roundabout"] = obstacle_type.mini_roundabout, + ["turning_loop"] = obstacle_type.turning_loop, + ["turning_circle"] = obstacle_type.turning_circle +} + -- process the obstacles at the given node -- note: does not process barriers function Obstacles.process_node(profile, node) local highway = node:get_value_by_key("highway") if highway then - local type = obstacle_type[highway] + local type = highway_to_obstacle_type[highway] -- barriers already handled in car.lua if type and type ~= obstacle_type.barrier then local direction = node:get_value_by_key("direction") diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp index 948e91b2a8..26929614bb 100644 --- a/src/extractor/edge_based_graph_factory.cpp +++ b/src/extractor/edge_based_graph_factory.cpp @@ -621,6 +621,8 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // node. But we'll check anyway. const bool is_traffic_light = scripting_environment.m_obstacle_map.any( node_along_road_entering, intersection_node, Obstacle::Type::TrafficSignals); + const bool has_turning_facility = scripting_environment.m_obstacle_map.any( + intersection_node, Obstacle::Type::Turning); const bool is_uturn = guidance::getTurnDirection(turn_angle) == guidance::DirectionModifier::UTurn; @@ -631,6 +633,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( is_uturn), is_uturn, is_traffic_light, + has_turning_facility, m_edge_based_node_container.GetAnnotation(edge_data1.annotation_data) .is_left_hand_driving, // source info diff --git a/src/extractor/graph_compressor.cpp b/src/extractor/graph_compressor.cpp index f755a72959..d108af2640 100644 --- a/src/extractor/graph_compressor.cpp +++ b/src/extractor/graph_compressor.cpp @@ -265,15 +265,19 @@ void GraphCompressor::Compress(ScriptingEnvironment &scripting_environment, EdgePenalties &penalties) { // generate an artificial turn for the turn penalty generation - ExtractionTurn fake_turn{from, - via, - to, - from_edge, - to_edge, - no_other_roads, - no_other_roads, - scripting_environment.m_obstacle_map.any( - from, via, Obstacle::Type::TrafficSignals)}; + ExtractionTurn fake_turn{ + from, + via, + to, + from_edge, + to_edge, + no_other_roads, + no_other_roads, + scripting_environment.m_obstacle_map.any( + from, via, Obstacle::Type::TrafficSignals), + // Turning facilities (turning_circle, turning_loop, mini_roundabout) are + // always Incompressible, so compressed nodes are never turning facilities. + false}; scripting_environment.ProcessTurn(fake_turn); penalties.duration += to_alias(fake_turn.duration * SECOND_TO_DECISECOND); diff --git a/src/extractor/scripting_environment_lua.cpp b/src/extractor/scripting_environment_lua.cpp index 0aa82bb424..86c69c1339 100644 --- a/src/extractor/scripting_environment_lua.cpp +++ b/src/extractor/scripting_environment_lua.cpp @@ -765,6 +765,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context) }), "has_traffic_light", &ExtractionTurn::has_traffic_light, + "has_turning_facility", + &ExtractionTurn::has_turning_facility, "weight", &ExtractionTurn::weight, "duration", @@ -890,6 +892,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context) &ExtractionTurn::is_u_turn, "has_traffic_light", &ExtractionTurn::has_traffic_light, + "has_turning_facility", + &ExtractionTurn::has_turning_facility, "is_left_hand_driving", &ExtractionTurn::is_left_hand_driving,