diff --git a/CHANGELOG.md b/CHANGELOG.md index 2665836cf81..b131602a23f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Changes from 5.15.0: - Profile: - FIXED: `highway=service` will now be used for restricted access, `access=private` is still disabled for snapping. + - ADDED #4775: Exposes more information to the turn function, now being able to set turn weights with highway and access information of the turn as well as other roads at the intersection [#4775](https://github.com/Project-OSRM/osrm-backend/issues/4775) # 5.15.0 - Changes from 5.14.3: @@ -701,4 +702,4 @@ - `properties.traffic_signal_penalty` - `properties.use_turn_restrictions` - `properties.u_turn_penalty` - - `properties.allow_u_turn_at_via` + - `properties.allow_u_turn_at_via` \ No newline at end of file diff --git a/docs/profiles.md b/docs/profiles.md index dda62e921de..b2c8a3d4d25 100644 --- a/docs/profiles.md +++ b/docs/profiles.md @@ -208,19 +208,100 @@ The `process_turn` function is called for every possible turn in the network. Ba The following attributes can be read and set on the result in `process_turn`: -Attribute | Read/write? | Type | Notes ----------------------|-------------|---------|------------------------------------------------------ -angle | Read | Float | Angle of turn in degrees (`0-360`: `0`=u-turn, `180`=straight on) -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? -source_restricted | Read | Boolean | Is it from a restricted access road? (See definition in `process_way`) -target_restricted | Read | Boolean | Is it to a restricted access road? (See definition in `process_way`) -is_left_hand_driving | Read | Boolean | Is left-hand traffic? -weight | Read/write | Float | Penalty to be applied for this turn (routing weight) -duration | Read/write | Float | Penalty to be applied for this turn (duration in deciseconds) -source_mode | Read | Enum | Travel mode before the turn. Defined in `include/extractor/travel_mode.hpp` -target_mode | Read | Enum | Travel mode after the turn. Defined in `include/extractor/travel_mode.hpp` +Attribute | Read/write? | Type | Notes +--------------------- | ------------- | --------- | ------------------------------------------------------ +angle | Read | Float | Angle of turn in degrees (`[-179, 180]`: `0`=straight, `180`=u turn, `+x`=x degrees to the right, `-x`= x degrees to the left) +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? +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` +source_is_motorway | Read | Boolean | Is the source road a motorway? +source_is_link | Read | Boolean | Is the source road a link? +source_number_of_lanes | Read | Integer | How many lanes does the source road have? (default when not tagged: 0) +source_highway_turn_classification | Read | Integer | Classification based on highway tag defined by user during setup. (default when not set: 0, allowed classification values are: 0-15)) +source_access_turn_classification | Read | Integer | Classification based on access tag defined by user during setup. (default when not set: 0, allowed classification values are: 0-15)) +source_speed | Read | Integer | Speed on this source road in km/h +target_restricted | Read | Boolean | Is it from a restricted access road? (See definition in `process_way`) +target_mode | Read | Enum | Travel mode before the turn. Defined in `include/extractor/travel_mode.hpp` +target_is_motorway | Read | Boolean | Is the target road a motorway? +target_is_link | Read | Boolean | Is the target road a link? +target_number_of_lanes | Read | Integer | How many lanes does the target road have? (default when not tagged: 0) +target_highway_turn_classification | Read | Integer | Classification based on highway tag defined by user during setup. (default when not set: 0, allowed classification values are: 0-15)) +target_access_turn_classification | Read | Integer | Classification based on access tag defined by user during setup. (default when not set: 0, allowed classification values are: 0-15)) +target_speed | Read | Integer | Speed on this target road in km/h +roads_on_the_right | Read | Vector | Vector with information about other roads on the right of the turn that are also connected at the intersection +roads_on_the_left | Read | Vector | Vector with information about other roads on the left of the turn that are also connected at the intersection. If turn is a u turn, this is empty. +weight | Read/write | Float | Penalty to be applied for this turn (routing weight) +duration | Read/write | Float | Penalty to be applied for this turn (duration in deciseconds) + +#### `roads_on_the_right` and `roads_on_the_left` + +The information of `roads_on_the_right` and `roads_on_the_left` that can be read are as follows: + +Attribute | Read/write? | Type | Notes +--------------------- | ------------- | --------- | ------------------------------------------------------ +is_restricted | Read | Boolean | Is it a restricted access road? (See definition in `process_way`) +mode | Read | Enum | Travel mode before the turn. Defined in `include/extractor/travel_mode.hpp` +is_motorway | Read | Boolean | Is the road a motorway? +is_link | Read | Boolean | Is the road a link? +number_of_lanes | Read | Integer | How many lanes does the road have? (default when not tagged: 0) +highway_turn_classification | Read | Integer | Classification based on highway tag defined by user during setup. (default when not set: 0, allowed classification values are: 0-15) +access_turn_classification | Read | Integer | Classification based on access tag defined by user during setup. (default when not set: 0, allowed classification values are: 0-15) +speed | Read | Integer | Speed on this road in km/h +is_incoming | Read | Boolean | Is the road an incoming road of the intersection +is_outgoing | Read | Boolean | Is the road an outgoing road of the intersection + +The order of the roads in `roads_on_the_right` and `roads_on_the_left` are *counter clockwise*. If the turn is a u turn, all other connected roads will be in `roads_on_the_right`. + +**Example** + +``` + c e + | / + | / + a ---- x ---- b + /| + / | + f d + + +``` +When turning from `a` to `b` via `x`, +* `roads_on_the_right[1]` is the road `xf` +* `roads_on_the_right[2]` is the road `xd` +* `roads_on_the_left[1]` is the road `xe` +* `roads_on_the_left[2]` is the road `xc` + +Note that indices of arrays in lua are 1-based. + +#### `highway_turn_classification` and `access_turn_classification` +When setting appropriate turn weights and duration, information about the highway and access tags of roads that are involved in the turn are necessary. The lua turn function `process_turn` does not have access to the original osrm tags anymore. However, `highway_turn_classification` and `access_turn_classification` can be set during setup. The classification set during setup can be later used in `process_turn`. + +**Example** + +In the following example we use `highway_turn_classification` to set the turn weight to `10` if the turn is on a highway and to `5` if the turn is on a primary. + +``` +function setup() + return { + highway_turn_classification = { + ['motorway'] = 2, + ['primary'] = 1 + } + } +end + +function process_turn(profile, turn) { + if turn.source_highway_turn_classification == 2 and turn.target_highway_turn_classification == 2 then + turn.weight = 10 + end + if turn.source_highway_turn_classification == 1 and turn.target_highway_turn_classification == 1 then + turn.weight = 5 + end +} +``` ## Guidance The guidance parameters in profiles are currently a work in progress. They can and will change. diff --git a/features/options/extract/turn_function.feature b/features/options/extract/turn_function.feature new file mode 100644 index 00000000000..897c1187cdf --- /dev/null +++ b/features/options/extract/turn_function.feature @@ -0,0 +1,176 @@ +@routing @testbot @turn_function +Feature: Turn Function Information + + + Background: + Given the profile file + """ + functions = require('car') + + function test_setup() + profile = functions.setup() + profile.highway_turn_classification = { + ['motorway'] = 4, + ['motorway_link'] = 4, + ['trunk'] = 4, + ['trunk_link'] = 4, + ['primary'] = 4, + ['primary_link'] = 4, + ['secondary'] = 3, + ['secondary_link'] = 3, + ['tertiary'] = 2, + ['tertiary_link'] = 2, + ['residential'] = 1, + ['living_street'] = 1, + } + + profile.access_turn_classification = { + ['discouraged'] = 1; + ['permissive'] = 1; + ['private'] = 1; + ['customers'] = 1; + ['dismount'] = 1; + } + return profile + end + + function turn_leg_string (leg) + return 'speed: ' .. tostring(leg.speed) + .. ', is_incoming: ' .. tostring(leg.is_incoming) + .. ', is_outgoing: ' .. tostring(leg.is_outgoing) + .. ', highway_turn_classification: ' .. tostring(leg.highway_turn_classification) + .. ', access_turn_classification: ' .. tostring(leg.access_turn_classification) + end + + function print_turn (profile, turn) + print ('source_restricted ' .. string.format("%s", tostring(turn.source_restricted))) + print ('source_is_motorway ' .. string.format("%s", tostring(turn.source_is_motorway))) + print ('source_is_link ' .. string.format("%s", tostring(turn.source_is_link))) + print ('source_number_of_lanes ' .. string.format("%s", tostring(turn.source_number_of_lanes))) + print ('source_highway_turn_classification ' .. string.format("%s", tostring(turn.source_highway_turn_classification))) + print ('source_access_turn_classification ' .. string.format("%s", tostring(turn.source_access_turn_classification))) + print ('source_speed ' .. string.format("%s", tostring(turn.source_speed))) + + print ('target_restricted ' .. string.format("%s", tostring(turn.target_restricted))) + print ('target_is_motorway ' .. string.format("%s", tostring(turn.target_is_motorway))) + print ('target_is_link ' .. string.format("%s", tostring(turn.target_is_link))) + print ('target_number_of_lanes ' .. string.format("%s", tostring(turn.target_number_of_lanes))) + print ('target_highway_turn_classification ' .. string.format("%s", tostring(turn.target_highway_turn_classification))) + print ('target_access_turn_classification ' .. string.format("%s", tostring(turn.target_access_turn_classification))) + print ('target_speed ' .. string.format("%s", tostring(turn.target_speed))) + + print ('number_of_roads ' .. string.format("%s", tostring(turn.number_of_roads))) + if not turn.is_u_turn then + for roadCount, road in ipairs(turn.roads_on_the_right) do + print('roads_on_the_right [' .. tostring(roadCount) .. '] ' .. turn_leg_string(road)) + end + + for roadCount, road in ipairs(turn.roads_on_the_left) do + print('roads_on_the_left [' .. tostring(roadCount) .. '] ' .. turn_leg_string(road)) + end + end + end + + return { + setup = test_setup, + process_way = functions.process_way, + process_node = functions.process_node, + process_turn = print_turn + } + """ + + Scenario: Turns should have correct information of source and target + Given the node map + """ + + a b c + + """ + And the ways + | nodes | highway | + | ab | motorway | + | bc | motorway | + And the data has been saved to disk + + When I run "osrm-extract --profile {profile_file} {osm_file}" + Then it should exit successfully + And stdout should contain "source_is_motorway true" + And stdout should contain "target_is_motorway true" + And stdout should contain "source_is_link false" + And stdout should contain "target_is_motorway true" + And stdout should contain "target_is_link false" + + + Scenario: Turns should detect when turn is leaving highway + Given the node map + """ + + a b c + + """ + And the ways + | nodes | highway | lanes | + | ab | motorway | 3 | + | bc | motorway_link | | + + And the data has been saved to disk + + When I run "osrm-extract --profile {profile_file} {osm_file}" + Then it should exit successfully + And stdout should contain "source_is_motorway true" + And stdout should contain "source_is_link false" + And stdout should contain "source_number_of_lanes 3" + And stdout should contain "target_is_motorway false" + And stdout should contain "target_is_link true" + And stdout should contain "target_number_of_lanes 0" + And stdout should contain "number_of_roads 2" + + Scenario: Turns should have correct information of other roads at intersection I + Given the node map + """ + d + ^ + | + a->b->c + """ + And the ways + | nodes | highway | oneway | + | ab | primary | yes | + | bc | motorway | yes | + | bd | residential | yes | + And the data has been saved to disk + + When I run "osrm-extract --profile {profile_file} {osm_file}" + Then it should exit successfully + And stdout should contain "number_of_roads 3" + # turning abd, give information about bc + And stdout should contain /roads_on_the_right \[1\] speed: [0-9]+, is_incoming: false, is_outgoing: true, highway_turn_classification: 4, access_turn_classification: 0/ + # turning abc, give information about bd + And stdout should contain /roads_on_the_left \[1\] speed: [0-9]+, is_incoming: false, is_outgoing: true, highway_turn_classification: 1, access_turn_classification: 0/ + + Scenario: Turns should have correct information of other roads at intersection II + Given the node map + """ + d + | + v + a->b->c + """ + And the ways + | nodes | highway | oneway | access | + | ab | secondary | yes | | + | bc | motorway | yes | | + | db | unclassified | yes | discouraged | + And the data has been saved to disk + + When I run "osrm-extract --profile {profile_file} {osm_file}" + Then it should exit successfully + And stdout should contain "number_of_roads 3" + # turning dbc, give information about about ab + And stdout should contain /roads_on_the_right \[1\] speed: [0-9]+, is_incoming: true, is_outgoing: false, highway_turn_classification: 3, access_turn_classification: 0/ + # turning abc, give information about about db + And stdout should contain /roads_on_the_left \[1\] speed: [0-9]+, is_incoming: true, is_outgoing: false, highway_turn_classification: 0, access_turn_classification: 1/ + + + + diff --git a/features/support/hooks.js b/features/support/hooks.js index 6b5185e9e7b..e58b34bfa22 100644 --- a/features/support/hooks.js +++ b/features/support/hooks.js @@ -50,6 +50,8 @@ module.exports = function () { .defer(mkdirp, logDir) .defer(rimraf, this.scenarioLogFile) .awaitAll(callback); + // uncomment to get path to logfile + // console.log(" Writing logging output to " + this.scenarioLogFile) }); this.After((scenario, callback) => { diff --git a/include/extractor/extraction_turn.hpp b/include/extractor/extraction_turn.hpp index 4f45a098750..24624f489a6 100644 --- a/include/extractor/extraction_turn.hpp +++ b/include/extractor/extraction_turn.hpp @@ -12,35 +12,115 @@ namespace osrm namespace extractor { +struct ExtractionTurnLeg +{ + ExtractionTurnLeg(bool is_restricted, + bool is_motorway, + bool is_link, + int number_of_lanes, + int highway_turn_classification, + int access_turn_classification, + int speed, + bool is_incoming, + bool is_outgoing) + : is_restricted(is_restricted), is_motorway(is_motorway), is_link(is_link), + number_of_lanes(number_of_lanes), + highway_turn_classification(highway_turn_classification), + access_turn_classification(access_turn_classification), speed(speed), + is_incoming(is_incoming), is_outgoing(is_outgoing) + { + } + + const bool is_restricted; + const bool is_motorway; + const bool is_link; + const int number_of_lanes; + const int highway_turn_classification; + const int access_turn_classification; + const int speed; + const bool is_incoming; + const bool is_outgoing; +}; + struct ExtractionTurn { ExtractionTurn(double angle, int number_of_roads, bool is_u_turn, bool has_traffic_light, - bool source_restricted, - bool target_restricted, bool is_left_hand_driving, + bool source_restricted, TravelMode source_mode, - TravelMode target_mode) + bool source_is_motorway, + bool source_is_link, + + int source_number_of_lanes, + int source_highway_turn_classification, + int source_access_turn_classification, + int source_speed, + bool target_restricted, + TravelMode target_mode, + bool target_is_motorway, + bool target_is_link, + int target_number_of_lanes, + int target_highway_turn_classification, + int target_access_turn_classification, + int target_speed, + const std::vector &roads_on_the_right, + const std::vector &roads_on_the_left) : angle(180. - angle), number_of_roads(number_of_roads), is_u_turn(is_u_turn), - has_traffic_light(has_traffic_light), source_restricted(source_restricted), - target_restricted(target_restricted), is_left_hand_driving(is_left_hand_driving), - weight(0.), duration(0.), source_mode(source_mode), target_mode(target_mode) + has_traffic_light(has_traffic_light), 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), + source_number_of_lanes(source_number_of_lanes), + source_highway_turn_classification(source_highway_turn_classification), + source_access_turn_classification(source_access_turn_classification), + source_speed(source_speed), + + target_restricted(target_restricted), target_mode(target_mode), + target_is_motorway(target_is_motorway), target_is_link(target_is_link), + target_number_of_lanes(target_number_of_lanes), + target_highway_turn_classification(target_highway_turn_classification), + target_access_turn_classification(target_access_turn_classification), + target_speed(target_speed), + + roads_on_the_right(roads_on_the_right), roads_on_the_left(roads_on_the_left), weight(0.), + duration(0.) + { } - const double angle; const int number_of_roads; const bool is_u_turn; const bool has_traffic_light; + const bool is_left_hand_driving; + + // source info const bool source_restricted; + const TravelMode source_mode; + const bool source_is_motorway; + const bool source_is_link; + const int source_number_of_lanes; + const int source_highway_turn_classification; + const int source_access_turn_classification; + const int source_speed; + + // target info const bool target_restricted; - const bool is_left_hand_driving; + const TravelMode target_mode; + const bool target_is_motorway; + const bool target_is_link; + const int target_number_of_lanes; + const int target_highway_turn_classification; + const int target_access_turn_classification; + const int target_speed; + + const std::vector roads_on_the_right; + const std::vector roads_on_the_left; + double weight; double duration; - const TravelMode source_mode; - const TravelMode target_mode; }; } } diff --git a/include/extractor/extraction_way.hpp b/include/extractor/extraction_way.hpp index a44fb2140f2..29e07d0d387 100644 --- a/include/extractor/extraction_way.hpp +++ b/include/extractor/extraction_way.hpp @@ -63,6 +63,8 @@ struct ExtractionWay forward_restricted = false; backward_restricted = false; is_left_hand_driving = false; + highway_turn_classification = 0; + access_turn_classification = 0; } // wrappers to allow assigning nil (nullptr) to string values @@ -123,6 +125,10 @@ struct ExtractionWay bool backward_restricted : 1; bool is_left_hand_driving : 1; bool : 2; + + // user classifications for turn penalties + std::uint8_t highway_turn_classification : 4; + std::uint8_t access_turn_classification : 4; }; } } diff --git a/include/extractor/node_based_edge.hpp b/include/extractor/node_based_edge.hpp index baa34e98de5..5f2d6d94a26 100644 --- a/include/extractor/node_based_edge.hpp +++ b/include/extractor/node_based_edge.hpp @@ -27,6 +27,8 @@ struct NodeBasedEdgeClassification std::uint8_t startpoint : 1; // 1 std::uint8_t restricted : 1; // 1 guidance::RoadClassification road_classification; // 16 2 + std::uint8_t highway_turn_classification : 4; // 4 + std::uint8_t access_turn_classification : 4; // 4 NodeBasedEdgeClassification(); @@ -37,10 +39,14 @@ struct NodeBasedEdgeClassification const bool circular, const bool startpoint, const bool restricted, - guidance::RoadClassification road_classification) + guidance::RoadClassification road_classification, + const std::uint8_t highway_turn_classification, + const std::uint8_t access_turn_classification) : forward(forward), backward(backward), is_split(is_split), roundabout(roundabout), circular(circular), startpoint(startpoint), restricted(restricted), - road_classification(road_classification) + road_classification(road_classification), + highway_turn_classification(highway_turn_classification), + access_turn_classification(access_turn_classification) { } diff --git a/include/partition/multi_level_partition.hpp b/include/partition/multi_level_partition.hpp index 4fe037b7b5e..9847409aa38 100644 --- a/include/partition/multi_level_partition.hpp +++ b/include/partition/multi_level_partition.hpp @@ -148,7 +148,7 @@ template class MultiLevelPartitionImpl final auto offsets = MakeLevelOffsets(lidx_to_num_cells); auto masks = MakeLevelMasks(offsets, num_level); auto bits = MakeBitToLevel(offsets, num_level); - return std::make_unique(LevelData{num_level, offsets, masks, bits, {0}}); + return std::make_unique(LevelData{num_level, offsets, masks, bits, {{0}}}); } inline std::size_t LevelIDToIndex(LevelID l) const { return l - 1; } diff --git a/include/util/assert.hpp b/include/util/assert.hpp index d4cd00c22b4..ac4b80797d5 100644 --- a/include/util/assert.hpp +++ b/include/util/assert.hpp @@ -20,9 +20,7 @@ if (!static_cast(cond)) \ { \ ::osrm::util::FloatCoordinate c_(loc); \ - std::cerr << "[Location] " \ - << "http://www.openstreetmap.org/?mlat=" << c_.lat << "&mlon=" << c_.lon \ - << "#map=19/" << c_.lat << "/" << c_.lon << '\n'; \ + std::cerr << "[Location] " << c_.toOSMLink() << '\n'; \ } \ BOOST_ASSERT_MSG(cond, msg); \ } while (0) diff --git a/include/util/coordinate.hpp b/include/util/coordinate.hpp index 3ff309ba90d..21ba703f81c 100644 --- a/include/util/coordinate.hpp +++ b/include/util/coordinate.hpp @@ -34,6 +34,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include //for std::ostream +#include #include #include @@ -216,6 +217,15 @@ struct Coordinate friend bool operator==(const Coordinate lhs, const Coordinate rhs); friend bool operator!=(const Coordinate lhs, const Coordinate rhs); friend std::ostream &operator<<(std::ostream &out, const Coordinate coordinate); + + std::string toOSMLink() const + { + std::stringstream link; + link << "http://www.openstreetmap.org/?mlat=" << toFloating(lat) + << "&mlon=" << toFloating(lon) << "#map=19/" << toFloating(lat) << "/" + << toFloating(lon); + return link.str(); + } }; /** @@ -257,6 +267,14 @@ struct FloatCoordinate friend bool operator==(const FloatCoordinate lhs, const FloatCoordinate rhs); friend bool operator!=(const FloatCoordinate lhs, const FloatCoordinate rhs); friend std::ostream &operator<<(std::ostream &out, const FloatCoordinate coordinate); + + std::string toOSMLink() const + { + std::stringstream link; + link << "http://www.openstreetmap.org/?mlat=" << lat << "&mlon=" << lon << "#map=19/" << lat + << "/" << lon; + return link.str(); + } }; bool operator==(const Coordinate lhs, const Coordinate rhs); diff --git a/profiles/car.lua b/profiles/car.lua index c58a1612053..38cd794c53f 100644 --- a/profiles/car.lua +++ b/profiles/car.lua @@ -298,6 +298,14 @@ function setup() relation_types = Sequence { "route" + }, + + -- classify highway tags when necessary for turn weights + highway_turn_classification = { + }, + + -- classify access tags when necessary for turn weights + access_turn_classification = { } } end @@ -413,7 +421,10 @@ function process_way(profile, way, result, relations) WayHandlers.names, -- set weight properties of the way - WayHandlers.weights + WayHandlers.weights, + + -- set classification of ways relevant for turns + WayHandlers.way_classification_for_turn } WayHandlers.run(profile, way, result, data, handlers, relations) diff --git a/profiles/lib/way_handlers.lua b/profiles/lib/way_handlers.lua index 58d25f11039..8da5d9d6612 100644 --- a/profiles/lib/way_handlers.lua +++ b/profiles/lib/way_handlers.lua @@ -221,6 +221,23 @@ function WayHandlers.hov(profile,way,result,data) end end + +-- set highway and access classification by user preference +function WayHandlers.way_classification_for_turn(profile,way,result,data) + local highway = way:get_value_by_key("highway") + local access = way:get_value_by_key("access") + + if highway and profile.highway_turn_classification[highway] then + assert(profile.highway_turn_classification[highway] < 16, "highway_turn_classification must be smaller than 16") + result.highway_turn_classification = profile.highway_turn_classification[highway] + end + if access and profile.access_turn_classification[access] then + assert(profile.access_turn_classification[access] < 16, "access_turn_classification must be smaller than 16") + result.access_turn_classification = profile.access_turn_classification[access] + end +end + + -- check accessibility by traversing our access tag hierarchy function WayHandlers.access(profile,way,result,data) data.forward_access, data.backward_access = diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp index 43e92adfe5b..62682654b99 100644 --- a/src/extractor/edge_based_graph_factory.cpp +++ b/src/extractor/edge_based_graph_factory.cpp @@ -267,7 +267,8 @@ void EdgeBasedGraphFactory::Run(ScriptingEnvironment &scripting_environment, /// Returns the number of edge-based nodes. unsigned EdgeBasedGraphFactory::LabelEdgeBasedNodes() { - // heuristic: node-based graph node is a simple intersection with four edges (edge-based nodes) + // heuristic: node-based graph node is a simple intersection with four edges + // (edge-based nodes) m_edge_based_node_weights.reserve(4 * m_node_based_graph.GetNumberOfNodes()); nbe_to_ebn_mapping.resize(m_node_based_graph.GetEdgeCapacity(), SPECIAL_NODEID); @@ -295,7 +296,7 @@ unsigned EdgeBasedGraphFactory::LabelEdgeBasedNodes() return numbered_edges_count; } -/// Creates the nodes in the edge expanded graph from edges in the node-based graph. +// Creates the nodes in the edge expanded graph from edges in the node-based graph. std::vector EdgeBasedGraphFactory::GenerateEdgeExpandedNodes(const WayRestrictionMap &way_restriction_map) { @@ -323,8 +324,8 @@ EdgeBasedGraphFactory::GenerateEdgeExpandedNodes(const WayRestrictionMap &way_re BOOST_ASSERT(nbg_node_v != SPECIAL_NODEID); BOOST_ASSERT(nbg_node_u != nbg_node_v); - // pick only every other edge, since we have every edge as an outgoing - // and incoming egde + // pick only every other edge, since we have every edge as an outgoing and incoming + // egde if (nbg_node_u >= nbg_node_v) { continue; @@ -410,7 +411,6 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( const ConditionalRestrictionMap &conditional_restriction_map, const WayRestrictionMap &way_restriction_map) { - util::Log() << "Generating edge-expanded edges "; std::size_t node_based_edge_counter = 0; @@ -472,14 +472,12 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // filled in during next stage, kept alive through following scope std::vector conditionals; - // The following block generates the edge-based-edges using a parallel processing - // pipeline. Sets of intersection IDs are batched in groups of GRAINSIZE (100) - // `generator_stage`, - // then those groups are processed in parallel `processor_stage`. Finally, results are - // appended to the various buffer vectors by the `output_stage` in the same order - // that the `generator_stage` created them in (tbb::filter::serial_in_order creates this - // guarantee). The order needs to be maintained because we depend on it later in the - // processing pipeline. + // The following block generates the edge-based-edges using a parallel processing pipeline. + // Sets of intersection IDs are batched in groups of GRAINSIZE (100) `generator_stage`, then + // those groups are processed in parallel `processor_stage`. Finally, results are appended to + // the various buffer vectors by the `output_stage` in the same order that the `generator_stage` + // created them in (tbb::filter::serial_in_order creates this guarantee). The order needs to be + // maintained because we depend on it later in the processing pipeline. { util::UnbufferedLog log; @@ -488,17 +486,18 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // This counter is used to keep track of how far along we've made it std::uint64_t nodes_completed = 0; - // going over all nodes (which form the center of an intersection), we compute all - // possible turns along these intersections. + // going over all nodes (which form the center of an intersection), we compute all possible + // turns along these intersections. NodeID current_node = 0; - // Handle intersections in sets of 100. The pipeline below has a serial bottleneck - // during the writing phase, so we want to make the parallel workers do more work - // to give the serial final stage time to complete its tasks. + // Handle intersections in sets of 100. The pipeline below has a serial bottleneck during + // the writing phase, so we want to make the parallel workers do more work to give the + // serial final stage time to complete its tasks. const constexpr unsigned GRAINSIZE = 100; - // First part of the pipeline generates iterator ranges of IDs in sets of GRAINSIZE + // First part of the pipeline generates iterator ranges of IDs in sets of + // GRAINSIZE tbb::filter_t> generator_stage( tbb::filter::serial_in_order, [&](tbb::flow_control &fc) -> tbb::blocked_range { if (current_node < node_count) @@ -515,8 +514,8 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( } }); - // This struct is the buffered output of the `processor_stage`. This data is - // appended to the various output arrays/files by the `output_stage`. + // This struct is the buffered output of the `processor_stage`. This data is appended to + // the various output arrays/files by the `output_stage`. struct IntersectionData { std::vector turn_indexes; @@ -562,7 +561,10 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( const auto node_based_edge_to, const auto incoming_bearing, const auto &turn, - const auto entry_class_id) { + const auto &road_legs_on_the_right, + const auto &road_legs_on_the_left, + const auto entry_class_id, + const auto &edge_geometries) { const auto node_restricted = isRestricted(node_along_road_entering, intersection_node, @@ -598,21 +600,46 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // compute weight and duration penalties auto is_traffic_light = m_traffic_lights.count(intersection_node); + ExtractionTurn extracted_turn( + // general info turn.angle, - m_node_based_graph.GetOutDegree(intersection_node), + road_legs_on_the_right.size() + road_legs_on_the_left.size() + 2 - + turn.instruction.IsUTurn(), turn.instruction.IsUTurn(), is_traffic_light, - edge_data1.flags.restricted, - edge_data2.flags.restricted, m_edge_based_node_container.GetAnnotation(edge_data1.annotation_data) .is_left_hand_driving, + // source info + edge_data1.flags.restricted, m_edge_based_node_container.GetAnnotation(edge_data1.annotation_data).travel_mode, - m_edge_based_node_container.GetAnnotation(edge_data2.annotation_data).travel_mode); + edge_data1.flags.road_classification.IsMotorwayClass(), + edge_data1.flags.road_classification.IsLinkClass(), + edge_data1.flags.road_classification.GetNumberOfLanes(), + edge_data1.flags.highway_turn_classification, + edge_data1.flags.access_turn_classification, + ((double)intersection::findEdgeLength(edge_geometries, node_based_edge_from) / + edge_data1.duration) * + 36, + // target info + edge_data2.flags.restricted, + m_edge_based_node_container.GetAnnotation(edge_data2.annotation_data).travel_mode, + edge_data2.flags.road_classification.IsMotorwayClass(), + edge_data2.flags.road_classification.IsLinkClass(), + edge_data2.flags.road_classification.GetNumberOfLanes(), + edge_data2.flags.highway_turn_classification, + edge_data2.flags.access_turn_classification, + ((double)intersection::findEdgeLength(edge_geometries, node_based_edge_to) / + edge_data2.duration) * + 36, + // connected roads + road_legs_on_the_right, + road_legs_on_the_left); + scripting_environment.ProcessTurn(extracted_turn); - // turn penalties are limited to [-2^15, 2^15) which roughly - // translates to 54 minutes and fits signed 16bit deci-seconds + // turn penalties are limited to [-2^15, 2^15) which roughly translates to 54 minutes + // and fits signed 16bit deci-seconds auto weight_penalty = boost::numeric_cast(extracted_turn.weight * weight_multiplier); auto duration_penalty = boost::numeric_cast(extracted_turn.duration * 10.); @@ -634,16 +661,15 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( true, false}; - // We write out the mapping between the edge-expanded edges and - // the original nodes. Since each edge represents a possible - // maneuver, external programs can use this to quickly perform updates to edge - // weights in order to penalize certain turns. + // We write out the mapping between the edge-expanded edges and the original nodes. + // Since each edge represents a possible maneuver, external programs can use this to + // quickly perform updates to edge weights in order to penalize certain turns. - // If this edge is 'trivial' -- where the compressed edge - // corresponds exactly to an original OSM segment -- we can pull the turn's - // preceding node ID directly with `node_along_road_entering`; - // otherwise, we need to look up the node immediately preceding the turn - // from the compressed edge container. + // If this edge is 'trivial' -- where the compressed edge corresponds exactly to an + // original OSM segment -- we can pull the turn's preceding node ID directly with + // `node_along_road_entering`; + // otherwise, we need to look up the node immediately preceding the turn from the + // compressed edge container. const bool isTrivial = m_compressed_edge_container.IsTrivial(node_based_edge_from); const auto &from_node = @@ -660,8 +686,8 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( conditional); }; - // Second part of the pipeline is where the intersection analysis is done for - // each intersection + // Second part of the pipeline is where the intersection analysis is done for each + // intersection tbb::filter_t, std::shared_ptr> processor_stage( tbb::filter::parallel, [&](const tbb::blocked_range &intersection_node_range) { @@ -678,8 +704,8 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( intersection_node < end; ++intersection_node) { - // We capture the thread-local work in these objects, then flush - // them in a controlled manner at the end of the parallel range + // We capture the thread-local work in these objects, then flush them in a + // controlled manner at the end of the parallel range const auto &incoming_edges = intersection::getIncomingEdges(m_node_based_graph, intersection_node); const auto &outgoing_edges = @@ -709,9 +735,9 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // b: a,rev=1 c,rev=0 d,rev=0 // c: b,rev=0 // - // From the flags alone, we cannot determine which nodes are connected to - // `b` by an outgoing edge. Therefore, we have to search all connected edges for - // edges entering `b` + // From the flags alone, we cannot determine which nodes are connected to `b` by + // an outgoing edge. Therefore, we have to search all connected edges for edges + // entering `b` for (const auto &incoming_edge : incoming_edges) { @@ -746,9 +772,8 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( const auto bearing_class_id = bearing_class_hash.ConcurrentFindOrAdd(turn_classification.second); - // Note - this is strictly speaking not thread safe, but we know we - // should never be touching the same element twice, so we should - // be fine. + // Note - this is strictly speaking not thread safe, but we know we should + // never be touching the same element twice, so we should be fine. bearing_class_by_node_based_node[intersection_node] = bearing_class_id; // check if we are turning off a via way @@ -780,6 +805,76 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( OSRM_ASSERT(turn != intersection.end(), m_coordinates[intersection_node]); + std::vector road_legs_on_the_right; + std::vector road_legs_on_the_left; + + auto get_connected_road_info = [&](const auto &connected_edge) { + const auto &edge_data = + m_node_based_graph.GetEdgeData(connected_edge.eid); + return ExtractionTurnLeg( + edge_data.flags.restricted, + edge_data.flags.road_classification.IsMotorwayClass(), + edge_data.flags.road_classification.IsLinkClass(), + edge_data.flags.road_classification.GetNumberOfLanes(), + edge_data.flags.highway_turn_classification, + edge_data.flags.access_turn_classification, + ((double)intersection::findEdgeLength(edge_geometries, + connected_edge.eid) / + edge_data.duration) * + 36, + !connected_edge.entry_allowed || + (edge_data.flags.forward && + edge_data.flags.backward), // is incoming + connected_edge.entry_allowed); + }; + + // all connected roads on the right of a u turn + if (turn->instruction.IsUTurn()) + { + if (turn != intersection.begin()) + { + std::transform(intersection.begin() + 1, + turn, + std::back_inserter(road_legs_on_the_right), + get_connected_road_info); + } + + std::transform(turn + 1, + intersection.end(), + std::back_inserter(road_legs_on_the_right), + get_connected_road_info); + } + else + { + if (intersection.begin() != turn) + { + std::transform(intersection.begin() + 1, + turn, + std::back_inserter(road_legs_on_the_right), + get_connected_road_info); + } + std::transform(turn + 1, + intersection.end(), + std::back_inserter(road_legs_on_the_left), + get_connected_road_info); + } + + if (turn->instruction.IsUTurn() && turn != intersection.begin()) + { + util::Log(logWARNING) + << "Turn is a u turn but not turning to the first connected " + "edge of the intersection. Node ID: " + << intersection_node << ", OSM link: " + << m_coordinates[intersection_node].toOSMLink(); + } + else if (turn == intersection.begin() && !turn->instruction.IsUTurn()) + { + util::Log(logWARNING) + << "Turn is a u turn but not classified as a u turn. Node ID: " + << intersection_node << ", OSM link: " + << m_coordinates[intersection_node].toOSMLink(); + } + // In case a way restriction starts at a given location, add a turn onto // every artificial node eminating here. // @@ -796,10 +891,9 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // Since every restriction group (abc | abe) refers to the same // artificial node, we simply have to find a single representative for // the turn. Here we check whether the turn in question is the start of - // a via way restriction. If that should be the case, we switch - // the id of the edge-based-node for the target to the ID of the - // duplicated node associated with the turn. (e.g. ab via bc switches bc - // to bc_dup) + // a via way restriction. If that should be the case, we switch the id + // of the edge-based-node for the target to the ID of the duplicated + // node associated with the turn. (e.g. ab via bc switches bc to bc_dup) auto const target_id = way_restriction_map.RemapIfRestricted( nbe_to_ebn_mapping[outgoing_edge.edge], incoming_edge.node, @@ -817,7 +911,10 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( outgoing_edge.edge, reversed_incoming_bearing, *turn, - entry_class_id); + road_legs_on_the_right, + road_legs_on_the_left, + entry_class_id, + edge_geometries); buffer->continuous_data.edges_list.push_back( edge_with_data_and_condition.first.edge); @@ -879,7 +976,10 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( outgoing_edge.edge, reversed_incoming_bearing, *turn, - entry_class_id); + road_legs_on_the_right, + road_legs_on_the_left, + entry_class_id, + edge_geometries); buffer->delayed_data.push_back( std::move(edge_with_data_and_condition.first)); @@ -913,7 +1013,10 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( outgoing_edge.edge, reversed_incoming_bearing, *turn, - entry_class_id); + road_legs_on_the_right, + road_legs_on_the_left, + entry_class_id, + edge_geometries); buffer->delayed_data.push_back( std::move(edge_with_data_and_condition.first)); @@ -933,11 +1036,9 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( return buffer; }); - // Because we write TurnIndexBlock data as we go, we'll - // buffer them into groups of 1000 to reduce the syscall - // count by 1000x. This doesn't need much memory, but - // greatly reduces the syscall overhead of writing lots - // of small objects + // Because we write TurnIndexBlock data as we go, we'll buffer them into groups of 1000 to + // reduce the syscall count by 1000x. This doesn't need much memory, but greatly reduces + // the syscall overhead of writing lots of small objects const constexpr int TURN_INDEX_WRITE_BUFFER_SIZE = 1000; std::vector turn_indexes_write_buffer; turn_indexes_write_buffer.reserve(TURN_INDEX_WRITE_BUFFER_SIZE); @@ -983,11 +1084,11 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( delayed_data.end(), buffer->delayed_data.begin(), buffer->delayed_data.end()); }); - // Now, execute the pipeline. The value of "5" here was chosen by experimentation - // on a 16-CPU machine and seemed to give the best performance. This value needs - // to be balanced with the GRAINSIZE above - ideally, the pipeline puts as much work - // as possible in the `intersection_handler` step so that those parallel workers don't - // get blocked too much by the slower (io-performing) `buffer_storage` + // Now, execute the pipeline. The value of "5" here was chosen by experimentation on a + // 16-CPU machine and seemed to give the best performance. This value needs to be balanced + // with the GRAINSIZE above - ideally, the pipeline puts as much work as possible in the + // `intersection_handler` step so that those parallel workers don't get blocked too much by + // the slower (io-performing) `buffer_storage` tbb::parallel_pipeline(tbb::task_scheduler_init::default_num_threads() * 5, generator_stage & processor_stage & output_stage); @@ -1013,8 +1114,8 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( } util::Log() << "Reunmbering turns"; - // Now, update the turn_id property on every EdgeBasedEdge - it will equal the - // position in the m_edge_based_edge_list array for each object. + // Now, update the turn_id property on every EdgeBasedEdge - it will equal the position in the + // m_edge_based_edge_list array for each object. tbb::parallel_for(tbb::blocked_range(0, m_edge_based_edge_list.size()), [this](const tbb::blocked_range &range) { for (auto x = range.begin(), end = range.end(); x != end; ++x) @@ -1023,8 +1124,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( } }); - // re-hash conditionals to ocnnect to their respective edge-based edges. Due to the - // ordering, we + // re-hash conditionals to ocnnect to their respective edge-based edges. Due to the ordering, we // do not really have a choice but to index the conditional penalties and walk over all // edge-based-edges to find the ID of the edge auto const indexed_conditionals = IndexConditionals(std::move(conditionals)); diff --git a/src/extractor/extractor_callbacks.cpp b/src/extractor/extractor_callbacks.cpp index 184adf268b2..da08048e374 100644 --- a/src/extractor/extractor_callbacks.cpp +++ b/src/extractor/extractor_callbacks.cpp @@ -423,7 +423,9 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti parsed_way.circular, parsed_way.is_startpoint, parsed_way.forward_restricted, - road_classification}}; + road_classification, + parsed_way.highway_turn_classification, + parsed_way.access_turn_classification}}; external_memory.all_edges_list.push_back(InternalExtractorEdge( std::move(edge), forward_weight_data, forward_duration_data, {})); @@ -456,7 +458,9 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti parsed_way.circular, parsed_way.is_startpoint, parsed_way.backward_restricted, - road_classification}}; + road_classification, + parsed_way.highway_turn_classification, + parsed_way.access_turn_classification}}; external_memory.all_edges_list.push_back(InternalExtractorEdge( std::move(edge), backward_weight_data, backward_duration_data, {})); diff --git a/src/extractor/graph_compressor.cpp b/src/extractor/graph_compressor.cpp index 37f8ef32dd2..3b147b51592 100644 --- a/src/extractor/graph_compressor.cpp +++ b/src/extractor/graph_compressor.cpp @@ -220,17 +220,31 @@ void GraphCompressor::Compress( continue; // generate an artifical turn for the turn penalty generation - ExtractionTurn extraction_turn( - 0, - 2, - false, - true, - fwd_edge_data1.flags.restricted, - fwd_edge_data2.flags.restricted, - node_data_container[fwd_edge_data1.annotation_data].is_left_hand_driving, - TRAVEL_MODE_DRIVING, - TRAVEL_MODE_DRIVING); - + std::vector roads_on_the_right; + std::vector roads_on_the_left; + ExtractionTurn extraction_turn(0, + 2, + false, + true, + false, + false, + TRAVEL_MODE_DRIVING, + false, + false, + 1, + 0, + 0, + 0, + false, + TRAVEL_MODE_DRIVING, + false, + false, + 1, + 0, + 0, + 0, + roads_on_the_right, + roads_on_the_left); scripting_environment.ProcessTurn(extraction_turn); node_duration_penalty = extraction_turn.duration * 10; node_weight_penalty = extraction_turn.weight * weight_multiplier; diff --git a/src/extractor/scripting_environment_lua.cpp b/src/extractor/scripting_environment_lua.cpp index ae601243995..e446d587c6e 100644 --- a/src/extractor/scripting_environment_lua.cpp +++ b/src/extractor/scripting_environment_lua.cpp @@ -346,7 +346,13 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context) [](ExtractionWay &way, bool flag) { way.backward_restricted = flag; }), "is_left_hand_driving", sol::property([](const ExtractionWay &way) { return way.is_left_hand_driving; }, - [](ExtractionWay &way, bool flag) { way.is_left_hand_driving = flag; })); + [](ExtractionWay &way, bool flag) { way.is_left_hand_driving = flag; }), + "highway_turn_classification", + sol::property([](const ExtractionWay &way) { return way.highway_turn_classification; }, + [](ExtractionWay &way, int flag) { way.highway_turn_classification = flag; }), + "access_turn_classification", + sol::property([](const ExtractionWay &way) { return way.access_turn_classification; }, + [](ExtractionWay &way, int flag) { way.access_turn_classification = flag; })); auto getTypedRefBySol = [](const sol::object &obj) -> ExtractionRelation::OsmIDTyped { if (obj.is()) @@ -670,29 +676,82 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context) { case 4: { - context.state.new_usertype("ExtractionTurn", - "angle", - &ExtractionTurn::angle, - "number_of_roads", - &ExtractionTurn::number_of_roads, - "is_u_turn", - &ExtractionTurn::is_u_turn, - "has_traffic_light", - &ExtractionTurn::has_traffic_light, - "weight", - &ExtractionTurn::weight, - "duration", - &ExtractionTurn::duration, - "source_restricted", - &ExtractionTurn::source_restricted, - "target_restricted", - &ExtractionTurn::target_restricted, - "is_left_hand_driving", - &ExtractionTurn::is_left_hand_driving, - "source_mode", - &ExtractionTurn::source_mode, - "target_mode", - &ExtractionTurn::target_mode); + context.state.new_usertype( + "ExtractionTurnLeg", + "is_restricted", + &ExtractionTurnLeg::is_restricted, + "is_motorway", + &ExtractionTurnLeg::is_motorway, + "is_link", + &ExtractionTurnLeg::is_link, + "number_of_lanes", + &ExtractionTurnLeg::number_of_lanes, + "highway_turn_classification", + &ExtractionTurnLeg::highway_turn_classification, + "access_turn_classification", + &ExtractionTurnLeg::access_turn_classification, + "speed", + &ExtractionTurnLeg::speed, + "is_incoming", + &ExtractionTurnLeg::is_incoming, + "is_outgoing", + &ExtractionTurnLeg::is_outgoing); + + context.state.new_usertype( + "ExtractionTurn", + "angle", + &ExtractionTurn::angle, + "number_of_roads", + &ExtractionTurn::number_of_roads, + "is_u_turn", + &ExtractionTurn::is_u_turn, + "has_traffic_light", + &ExtractionTurn::has_traffic_light, + "is_left_hand_driving", + &ExtractionTurn::is_left_hand_driving, + + "source_restricted", + &ExtractionTurn::source_restricted, + "source_mode", + &ExtractionTurn::source_mode, + "source_is_motorway", + &ExtractionTurn::source_is_motorway, + "source_is_link", + &ExtractionTurn::source_is_link, + "source_number_of_lanes", + &ExtractionTurn::source_number_of_lanes, + "source_highway_turn_classification", + &ExtractionTurn::source_highway_turn_classification, + "source_access_turn_classification", + &ExtractionTurn::source_access_turn_classification, + "source_speed", + &ExtractionTurn::source_speed, + + "target_restricted", + &ExtractionTurn::target_restricted, + "target_mode", + &ExtractionTurn::target_mode, + "target_is_motorway", + &ExtractionTurn::target_is_motorway, + "target_is_link", + &ExtractionTurn::target_is_link, + "target_number_of_lanes", + &ExtractionTurn::target_number_of_lanes, + "target_highway_turn_classification", + &ExtractionTurn::target_highway_turn_classification, + "target_access_turn_classification", + &ExtractionTurn::target_access_turn_classification, + "target_speed", + &ExtractionTurn::target_speed, + + "roads_on_the_right", + &ExtractionTurn::roads_on_the_right, + "roads_on_the_left", + &ExtractionTurn::roads_on_the_left, + "weight", + &ExtractionTurn::weight, + "duration", + &ExtractionTurn::duration); initV2Context(); break; } diff --git a/unit_tests/extractor/intersection_analysis_tests.cpp b/unit_tests/extractor/intersection_analysis_tests.cpp index 1692a1cc4e0..48ec29a42e4 100644 --- a/unit_tests/extractor/intersection_analysis_tests.cpp +++ b/unit_tests/extractor/intersection_analysis_tests.cpp @@ -167,15 +167,15 @@ BOOST_AUTO_TEST_CASE(roundabout_intersection_connectivity) // ↙ ↑ ↘ // 4 5 6 const auto unit_edge = [](const NodeID from, const NodeID to, bool allowed, bool roundabout) { - return InputEdge{ - from, - to, - 1, - 1, - GeometryID{0, false}, - !allowed, - NodeBasedEdgeClassification{true, false, false, roundabout, false, false, false, {}}, - 0}; + return InputEdge{from, + to, + 1, + 1, + GeometryID{0, false}, + !allowed, + NodeBasedEdgeClassification{ + true, false, false, roundabout, false, false, false, {}, 0, 0}, + 0}; }; std::vector edges = {unit_edge(0, 1, false, false), unit_edge(0, 2, true, true),