Support OSM traffic signal directions (#6153)

Currently OSRM parses traffic signal nodes without consideration
for the direction in which the signal applies. This can lead
to duplicated routing penalties, especially when a forward and backward
signal are in close proximity on a way.

This commit adds support for directed signals to the extraction and
graph creation. Signal penalties are only applied in the direction
specified by the OSM tag.

We add the assignment of traffic directions to the lua scripts,
maintaining backwards compatibility with the existing boolean
traffic states.

As part of the changes to the internal structures used for tracking
traffic signals during extraction, we stop serialising/deserialising
signals to the `.osrm` file. The traffic signals are only used by
`osrm-extract` so whilst this is a data format change, it will not
break any existing user processes.
This commit is contained in:
Michael Bell
2022-08-30 10:36:49 +01:00
committed by GitHub
parent d8b358e810
commit b17cbb4c47
26 changed files with 648 additions and 139 deletions
+134 -19
View File
@@ -11,6 +11,7 @@
#include "util/exception.hpp"
#include "util/exception_utils.hpp"
#include "util/for_each_indexed.hpp"
#include "util/for_each_pair.hpp"
#include "util/log.hpp"
#include "util/timing_util.hpp"
@@ -413,6 +414,7 @@ void ExtractionContainers::PrepareData(ScriptingEnvironment &scripting_environme
const auto restriction_ways = IdentifyRestrictionWays();
const auto maneuver_override_ways = IdentifyManeuverOverrideWays();
const auto traffic_signals = IdentifyTrafficSignals();
PrepareNodes();
WriteNodes(writer);
@@ -422,6 +424,7 @@ void ExtractionContainers::PrepareData(ScriptingEnvironment &scripting_environme
WriteEdges(writer);
WriteMetadata(writer);
PrepareTrafficSignals(traffic_signals);
PrepareManeuverOverrides(maneuver_override_ways);
PrepareRestrictions(restriction_ways);
WriteCharData(name_file_name);
@@ -911,25 +914,6 @@ void ExtractionContainers::WriteNodes(storage::tar::FileWriter &writer) const
log << "ok, after " << TIMER_SEC(write_nodes) << "s";
}
{
util::UnbufferedLog log;
log << "Writing traffic light nodes ... ";
TIMER_START(write_nodes);
std::vector<NodeID> internal_traffic_signals;
for (const auto osm_id : traffic_signals)
{
const auto node_id = mapExternalToInternalNodeID(
used_node_id_list.begin(), used_node_id_list.end(), osm_id);
if (node_id != SPECIAL_NODEID)
{
internal_traffic_signals.push_back(node_id);
}
}
storage::serialization::write(
writer, "/extractor/traffic_lights", internal_traffic_signals);
log << "ok, after " << TIMER_SEC(write_nodes) << "s";
}
util::Log() << "Processed " << max_internal_node_id << " nodes";
}
@@ -983,6 +967,50 @@ ExtractionContainers::ReferencedWays ExtractionContainers::IdentifyManeuverOverr
return maneuver_override_ways;
}
void ExtractionContainers::PrepareTrafficSignals(
const ExtractionContainers::ReferencedTrafficSignals &referenced_traffic_signals)
{
const auto &bidirectional_signal_nodes = referenced_traffic_signals.first;
const auto &unidirectional_signal_segments = referenced_traffic_signals.second;
util::UnbufferedLog log;
log << "Preparing traffic light signals for " << bidirectional_signal_nodes.size()
<< " bidirectional, " << unidirectional_signal_segments.size()
<< " unidirectional nodes ...";
TIMER_START(prepare_traffic_signals);
std::unordered_set<NodeID> bidirectional;
std::unordered_set<std::pair<NodeID, NodeID>, boost::hash<std::pair<NodeID, NodeID>>>
unidirectional;
for (const auto &osm_node : bidirectional_signal_nodes)
{
const auto node_id = mapExternalToInternalNodeID(
used_node_id_list.begin(), used_node_id_list.end(), osm_node);
if (node_id != SPECIAL_NODEID)
{
bidirectional.insert(node_id);
}
}
for (const auto &to_from : unidirectional_signal_segments)
{
const auto to_node_id = mapExternalToInternalNodeID(
used_node_id_list.begin(), used_node_id_list.end(), to_from.first);
const auto from_node_id = mapExternalToInternalNodeID(
used_node_id_list.begin(), used_node_id_list.end(), to_from.second);
if (from_node_id != SPECIAL_NODEID && to_node_id != SPECIAL_NODEID)
{
unidirectional.insert({from_node_id, to_node_id});
}
}
internal_traffic_signals.bidirectional_nodes = std::move(bidirectional);
internal_traffic_signals.unidirectional_segments = std::move(unidirectional);
TIMER_STOP(prepare_traffic_signals);
log << "ok, after " << TIMER_SEC(prepare_traffic_signals) << "s";
}
void ExtractionContainers::PrepareManeuverOverrides(const ReferencedWays &maneuver_override_ways)
{
auto const osm_node_to_internal_nbn = [&](auto const osm_node) {
@@ -1163,6 +1191,93 @@ ExtractionContainers::ReferencedWays ExtractionContainers::IdentifyRestrictionWa
return restriction_ways;
}
ExtractionContainers::ReferencedTrafficSignals ExtractionContainers::IdentifyTrafficSignals()
{
util::UnbufferedLog log;
log << "Collecting traffic signal information on " << external_traffic_signals.size()
<< " signals...";
TIMER_START(identify_traffic_signals);
// Temporary store for nodes containing a unidirectional signal.
std::unordered_map<OSMNodeID, TrafficLightClass::Direction> unidirectional_signals;
// For each node that has a unidirectional traffic signal, we store the node(s)
// that lead up to the signal.
std::unordered_multimap<OSMNodeID, OSMNodeID> signal_segments;
std::unordered_set<OSMNodeID> bidirectional_signals;
const auto mark_signals = [&](auto const &traffic_signal) {
if (traffic_signal.second == TrafficLightClass::DIRECTION_FORWARD ||
traffic_signal.second == TrafficLightClass::DIRECTION_REVERSE)
{
unidirectional_signals.insert({traffic_signal.first, traffic_signal.second});
}
else
{
BOOST_ASSERT(traffic_signal.second == TrafficLightClass::DIRECTION_ALL);
bidirectional_signals.insert(traffic_signal.first);
}
};
std::for_each(external_traffic_signals.begin(), external_traffic_signals.end(), mark_signals);
// Extract all the segments that lead up to unidirectional traffic signals.
const auto set_segments = [&](const size_t way_list_idx, auto const & /*unused*/) {
const auto node_start_offset =
used_node_id_list.begin() + way_node_id_offsets[way_list_idx];
const auto node_end_offset =
used_node_id_list.begin() + way_node_id_offsets[way_list_idx + 1];
for (auto node_it = node_start_offset; node_it < node_end_offset; node_it++)
{
const auto sig = unidirectional_signals.find(*node_it);
if (sig != unidirectional_signals.end())
{
if (sig->second == TrafficLightClass::DIRECTION_FORWARD)
{
if (node_it != node_start_offset)
{
// Previous node leads to signal
signal_segments.insert({*node_it, *(node_it - 1)});
}
}
else
{
BOOST_ASSERT(sig->second == TrafficLightClass::DIRECTION_REVERSE);
if (node_it + 1 != node_end_offset)
{
// Next node leads to signal
signal_segments.insert({*node_it, *(node_it + 1)});
}
}
}
}
};
util::for_each_indexed(ways_list.cbegin(), ways_list.cend(), set_segments);
util::for_each_pair(
signal_segments, [](const auto pair_a, const auto pair_b) {
if (pair_a.first == pair_b.first)
{
// If a node is appearing multiple times in this map, then it's ambiguous.
// The node is an intersection and the traffic direction is being use for multiple
// ways. We can't be certain of the original intent. See:
// https://wiki.openstreetmap.org/wiki/Key:traffic_signals:direction
// OSRM will include the signal for all intersecting ways in the specified
// direction, but let's flag this as a concern.
util::Log(logWARNING)
<< "OSM node " << pair_a.first
<< " has a unidirectional traffic signal ambiguously applied to multiple ways";
}
});
TIMER_STOP(identify_traffic_signals);
log << "ok, after " << TIMER_SEC(identify_traffic_signals) << "s";
return {std::move(bidirectional_signals), std::move(signal_segments)};
}
void ExtractionContainers::PrepareRestrictions(const ReferencedWays &restriction_ways)
{