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.
497 lines
21 KiB
C++
497 lines
21 KiB
C++
#include "extractor/extractor_callbacks.hpp"
|
|
#include "extractor/extraction_containers.hpp"
|
|
#include "extractor/extraction_node.hpp"
|
|
#include "extractor/extraction_way.hpp"
|
|
#include "extractor/profile_properties.hpp"
|
|
#include "extractor/query_node.hpp"
|
|
#include "extractor/restriction.hpp"
|
|
#include "extractor/road_classification.hpp"
|
|
|
|
#include "util/for_each_pair.hpp"
|
|
#include "util/guidance/turn_lanes.hpp"
|
|
#include "util/log.hpp"
|
|
|
|
#include <boost/optional/optional.hpp>
|
|
#include <boost/tokenizer.hpp>
|
|
|
|
#include <osmium/osm.hpp>
|
|
|
|
#include "osrm/coordinate.hpp"
|
|
|
|
#include <limits>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#ifdef _MSC_VER
|
|
#if (_MSC_VER >= 1928)
|
|
#ifdef _DEBUG
|
|
namespace osrm
|
|
{
|
|
namespace extractor
|
|
{
|
|
namespace detail
|
|
{
|
|
const ByEdgeOrByMeterValue::ValueByEdge ByEdgeOrByMeterValue::by_edge;
|
|
const ByEdgeOrByMeterValue::ValueByMeter ByEdgeOrByMeterValue::by_meter;
|
|
} // namespace detail
|
|
} // namespace extractor
|
|
} // namespace osrm
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
namespace osrm
|
|
{
|
|
namespace extractor
|
|
{
|
|
ExtractorCallbacks::ExtractorCallbacks(ExtractionContainers &extraction_containers_,
|
|
std::unordered_map<std::string, ClassData> &classes_map,
|
|
LaneDescriptionMap &lane_description_map,
|
|
const ProfileProperties &properties)
|
|
: external_memory(extraction_containers_), classes_map(classes_map),
|
|
lane_description_map(lane_description_map),
|
|
fallback_to_duration(properties.fallback_to_duration),
|
|
force_split_edges(properties.force_split_edges)
|
|
{
|
|
// we reserved 0, 1, 2, 3, 4 for the empty case
|
|
string_map[MapKey("", "", "", "", "")] = 0;
|
|
lane_description_map.data[TurnLaneDescription()] = 0;
|
|
}
|
|
|
|
/**
|
|
* Takes the node position from osmium and the filtered properties from the lua
|
|
* profile and saves them to external memory.
|
|
*
|
|
* warning: caller needs to take care of synchronization!
|
|
*/
|
|
void ExtractorCallbacks::ProcessNode(const osmium::Node &input_node,
|
|
const ExtractionNode &result_node)
|
|
{
|
|
const auto id = OSMNodeID{static_cast<std::uint64_t>(input_node.id())};
|
|
|
|
external_memory.all_nodes_list.push_back(
|
|
QueryNode{util::toFixed(util::UnsafeFloatLongitude{input_node.location().lon()}),
|
|
util::toFixed(util::UnsafeFloatLatitude{input_node.location().lat()}),
|
|
id});
|
|
|
|
if (result_node.barrier)
|
|
{
|
|
external_memory.barrier_nodes.push_back(id);
|
|
}
|
|
if (result_node.traffic_lights != TrafficLightClass::NONE)
|
|
{
|
|
external_memory.external_traffic_signals.push_back({id, result_node.traffic_lights});
|
|
}
|
|
}
|
|
|
|
void ExtractorCallbacks::ProcessRestriction(const InputTurnRestriction &restriction)
|
|
{
|
|
external_memory.restrictions_list.push_back(restriction);
|
|
}
|
|
|
|
void ExtractorCallbacks::ProcessManeuverOverride(const InputManeuverOverride &override)
|
|
{
|
|
external_memory.external_maneuver_overrides_list.push_back(override);
|
|
}
|
|
|
|
/**
|
|
* Takes the geometry contained in the ```input_way``` and the tags computed
|
|
* by the lua profile inside ```parsed_way``` and computes all edge segments.
|
|
*
|
|
* Depending on the forward/backwards weights the edges are split into forward
|
|
* and backward edges.
|
|
*
|
|
* warning: caller needs to take care of synchronization!
|
|
*/
|
|
void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const ExtractionWay &parsed_way)
|
|
{
|
|
if ((parsed_way.forward_travel_mode == extractor::TRAVEL_MODE_INACCESSIBLE ||
|
|
parsed_way.forward_speed <= 0) &&
|
|
(parsed_way.backward_travel_mode == extractor::TRAVEL_MODE_INACCESSIBLE ||
|
|
parsed_way.backward_speed <= 0) &&
|
|
parsed_way.duration <= 0)
|
|
{ // Only true if the way is assigned a valid speed/duration
|
|
return;
|
|
}
|
|
|
|
if (!fallback_to_duration &&
|
|
(parsed_way.forward_travel_mode == extractor::TRAVEL_MODE_INACCESSIBLE ||
|
|
parsed_way.forward_rate <= 0) &&
|
|
(parsed_way.backward_travel_mode == extractor::TRAVEL_MODE_INACCESSIBLE ||
|
|
parsed_way.backward_rate <= 0) &&
|
|
parsed_way.weight <= 0)
|
|
{ // Only true if the way is assigned a valid rate/weight and there is no duration fallback
|
|
return;
|
|
}
|
|
|
|
const auto &nodes = input_way.nodes();
|
|
if (nodes.size() <= 1)
|
|
{ // safe-guard against broken data
|
|
return;
|
|
}
|
|
|
|
if (std::numeric_limits<decltype(input_way.id())>::max() == input_way.id())
|
|
{
|
|
util::Log(logDEBUG) << "found bogus way with id: " << input_way.id() << " of size "
|
|
<< nodes.size();
|
|
return;
|
|
}
|
|
|
|
InternalExtractorEdge::DurationData forward_duration_data;
|
|
InternalExtractorEdge::DurationData backward_duration_data;
|
|
InternalExtractorEdge::WeightData forward_weight_data;
|
|
InternalExtractorEdge::WeightData backward_weight_data;
|
|
|
|
const auto toValueByEdgeOrByMeter = [&nodes](const double by_way, const double by_meter) {
|
|
using Value = detail::ByEdgeOrByMeterValue;
|
|
// get value by weight per edge
|
|
if (by_way >= 0)
|
|
{
|
|
// FIXME We divide by the number of edges here, but should rather consider
|
|
// the length of each segment. We would either have to compute the length
|
|
// of the whole way here (we can't: no node coordinates) or push that back
|
|
// to the container and keep a reference to the way.
|
|
const std::size_t num_edges = (nodes.size() - 1);
|
|
return Value(Value::by_edge, by_way / num_edges);
|
|
}
|
|
else
|
|
{
|
|
// get value by deriving weight from speed per edge
|
|
return Value(Value::by_meter, by_meter);
|
|
}
|
|
};
|
|
|
|
if (parsed_way.forward_travel_mode != extractor::TRAVEL_MODE_INACCESSIBLE)
|
|
{
|
|
BOOST_ASSERT(parsed_way.duration > 0 || parsed_way.forward_speed > 0);
|
|
forward_duration_data =
|
|
toValueByEdgeOrByMeter(parsed_way.duration, parsed_way.forward_speed / 3.6);
|
|
// fallback to duration as weight
|
|
if (fallback_to_duration)
|
|
{
|
|
forward_weight_data = forward_duration_data;
|
|
}
|
|
else
|
|
{
|
|
BOOST_ASSERT(parsed_way.weight > 0 || parsed_way.forward_rate > 0);
|
|
forward_weight_data =
|
|
toValueByEdgeOrByMeter(parsed_way.weight, parsed_way.forward_rate);
|
|
}
|
|
}
|
|
if (parsed_way.backward_travel_mode != extractor::TRAVEL_MODE_INACCESSIBLE)
|
|
{
|
|
BOOST_ASSERT(parsed_way.duration > 0 || parsed_way.backward_speed > 0);
|
|
backward_duration_data =
|
|
toValueByEdgeOrByMeter(parsed_way.duration, parsed_way.backward_speed / 3.6);
|
|
// fallback to duration as weight
|
|
if (fallback_to_duration)
|
|
{
|
|
backward_weight_data = backward_duration_data;
|
|
}
|
|
else
|
|
{
|
|
BOOST_ASSERT(parsed_way.weight > 0 || parsed_way.backward_rate > 0);
|
|
backward_weight_data =
|
|
toValueByEdgeOrByMeter(parsed_way.weight, parsed_way.backward_rate);
|
|
}
|
|
}
|
|
|
|
const auto classStringToMask = [this](const std::string &class_name) {
|
|
auto iter = classes_map.find(class_name);
|
|
if (iter == classes_map.end())
|
|
{
|
|
if (classes_map.size() > MAX_CLASS_INDEX)
|
|
{
|
|
throw util::exception("Maximum number of classes is " +
|
|
std::to_string(MAX_CLASS_INDEX + 1));
|
|
}
|
|
ClassData class_mask = getClassData(classes_map.size());
|
|
classes_map[class_name] = class_mask;
|
|
return class_mask;
|
|
}
|
|
else
|
|
{
|
|
return iter->second;
|
|
}
|
|
};
|
|
const auto classesToMask = [&](const auto &classes) {
|
|
ClassData mask = 0;
|
|
for (const auto &name_and_flag : classes)
|
|
{
|
|
if (!isValidClassName(name_and_flag.first))
|
|
{
|
|
throw util::exception("Invalid class name " + name_and_flag.first +
|
|
" only [a-Z0-9] allowed.");
|
|
}
|
|
|
|
if (name_and_flag.second)
|
|
{
|
|
mask |= classStringToMask(name_and_flag.first);
|
|
}
|
|
}
|
|
return mask;
|
|
};
|
|
const ClassData forward_classes = classesToMask(parsed_way.forward_classes);
|
|
const ClassData backward_classes = classesToMask(parsed_way.backward_classes);
|
|
|
|
const auto laneStringToDescription = [](const std::string &lane_string) -> TurnLaneDescription {
|
|
if (lane_string.empty())
|
|
return {};
|
|
|
|
TurnLaneDescription lane_description;
|
|
|
|
typedef boost::tokenizer<boost::char_separator<char>> tokenizer;
|
|
boost::char_separator<char> sep("|", "", boost::keep_empty_tokens);
|
|
boost::char_separator<char> inner_sep(";", "");
|
|
tokenizer tokens(lane_string, sep);
|
|
|
|
const constexpr std::size_t num_osm_tags = 11;
|
|
const constexpr char *osm_lane_strings[num_osm_tags] = {"none",
|
|
"through",
|
|
"sharp_left",
|
|
"left",
|
|
"slight_left",
|
|
"slight_right",
|
|
"right",
|
|
"sharp_right",
|
|
"reverse",
|
|
"merge_to_left",
|
|
"merge_to_right"};
|
|
|
|
const constexpr TurnLaneType::Mask masks_by_osm_string[num_osm_tags + 1] = {
|
|
TurnLaneType::none,
|
|
TurnLaneType::straight,
|
|
TurnLaneType::sharp_left,
|
|
TurnLaneType::left,
|
|
TurnLaneType::slight_left,
|
|
TurnLaneType::slight_right,
|
|
TurnLaneType::right,
|
|
TurnLaneType::sharp_right,
|
|
TurnLaneType::uturn,
|
|
TurnLaneType::merge_to_left,
|
|
TurnLaneType::merge_to_right,
|
|
TurnLaneType::empty}; // fallback, if string not found
|
|
|
|
for (auto iter = tokens.begin(); iter != tokens.end(); ++iter)
|
|
{
|
|
tokenizer inner_tokens(*iter, inner_sep);
|
|
TurnLaneType::Mask lane_mask = inner_tokens.begin() == inner_tokens.end()
|
|
? TurnLaneType::none
|
|
: TurnLaneType::empty;
|
|
for (auto token_itr = inner_tokens.begin(); token_itr != inner_tokens.end();
|
|
++token_itr)
|
|
{
|
|
auto position =
|
|
std::find(osm_lane_strings, osm_lane_strings + num_osm_tags, *token_itr);
|
|
const auto translated_mask =
|
|
masks_by_osm_string[std::distance(osm_lane_strings, position)];
|
|
if (translated_mask == TurnLaneType::empty)
|
|
{
|
|
// if we have unsupported tags, don't handle them
|
|
util::Log(logDEBUG) << "Unsupported lane tag found: \"" << *token_itr << "\"";
|
|
return {};
|
|
}
|
|
|
|
// In case of multiple times the same lane indicators withn a lane, as in
|
|
// "left;left|.." or-ing the masks generates a single "left" enum.
|
|
// Which is fine since this is data issue and we can't represent it anyway.
|
|
lane_mask |= translated_mask;
|
|
}
|
|
// add the lane to the description
|
|
lane_description.push_back(lane_mask);
|
|
}
|
|
return lane_description;
|
|
};
|
|
|
|
// If we could parse turn lanes but could not parse number of lanes,
|
|
// count the turn lanes and use them for the way's number of lanes.
|
|
auto road_classification = parsed_way.road_classification;
|
|
std::uint8_t road_deduced_num_lanes = 0;
|
|
|
|
// Deduplicates street names, refs, destinations, pronunciation, exits.
|
|
// In case we do not already store the key, inserts (key, id) tuple and return id.
|
|
// Otherwise fetches the id based on the name and returns it without insertion.
|
|
auto turn_lane_id_forward = INVALID_LANE_DESCRIPTIONID;
|
|
auto turn_lane_id_backward = INVALID_LANE_DESCRIPTIONID;
|
|
|
|
// RoadClassification represents a the class for unidirectional ways,
|
|
// therefore we need to add up deduced forward and backward lane counts.
|
|
|
|
if (!parsed_way.turn_lanes_forward.empty())
|
|
{
|
|
auto desc = laneStringToDescription(parsed_way.turn_lanes_forward);
|
|
turn_lane_id_forward = lane_description_map.ConcurrentFindOrAdd(desc);
|
|
road_deduced_num_lanes += desc.size();
|
|
}
|
|
|
|
if (!parsed_way.turn_lanes_backward.empty())
|
|
{
|
|
auto desc = laneStringToDescription(parsed_way.turn_lanes_backward);
|
|
turn_lane_id_backward = lane_description_map.ConcurrentFindOrAdd(desc);
|
|
road_deduced_num_lanes += desc.size();
|
|
}
|
|
|
|
road_classification.SetNumberOfLanes(std::max(road_deduced_num_lanes, // len(turn:lanes)
|
|
road_classification.GetNumberOfLanes()));
|
|
|
|
const auto GetNameID = [this, &parsed_way](bool is_forward) -> NameID {
|
|
const std::string &ref = is_forward ? parsed_way.forward_ref : parsed_way.backward_ref;
|
|
// Get the unique identifier for the street name, destination, and ref
|
|
const auto name_iterator = string_map.find(MapKey(parsed_way.name,
|
|
parsed_way.destinations,
|
|
ref,
|
|
parsed_way.pronunciation,
|
|
parsed_way.exits));
|
|
|
|
NameID name_id = EMPTY_NAMEID;
|
|
if (string_map.end() == name_iterator)
|
|
{
|
|
// name_offsets has a sentinel element with the total name data size
|
|
// take the sentinels index as the name id of the new name data pack
|
|
// (name [name_id], destination [+1], pronunciation [+2], ref [+3], exits [+4])
|
|
name_id = external_memory.name_offsets.size() - 1;
|
|
|
|
std::copy(parsed_way.name.begin(),
|
|
parsed_way.name.end(),
|
|
std::back_inserter(external_memory.name_char_data));
|
|
external_memory.name_offsets.push_back(external_memory.name_char_data.size());
|
|
|
|
std::copy(parsed_way.destinations.begin(),
|
|
parsed_way.destinations.end(),
|
|
std::back_inserter(external_memory.name_char_data));
|
|
external_memory.name_offsets.push_back(external_memory.name_char_data.size());
|
|
|
|
std::copy(parsed_way.pronunciation.begin(),
|
|
parsed_way.pronunciation.end(),
|
|
std::back_inserter(external_memory.name_char_data));
|
|
external_memory.name_offsets.push_back(external_memory.name_char_data.size());
|
|
|
|
std::copy(ref.begin(), ref.end(), std::back_inserter(external_memory.name_char_data));
|
|
external_memory.name_offsets.push_back(external_memory.name_char_data.size());
|
|
|
|
std::copy(parsed_way.exits.begin(),
|
|
parsed_way.exits.end(),
|
|
std::back_inserter(external_memory.name_char_data));
|
|
external_memory.name_offsets.push_back(external_memory.name_char_data.size());
|
|
|
|
auto k = MapKey{parsed_way.name,
|
|
parsed_way.destinations,
|
|
ref,
|
|
parsed_way.pronunciation,
|
|
parsed_way.exits};
|
|
auto v = MapVal{name_id};
|
|
string_map.emplace(std::move(k), v);
|
|
}
|
|
else
|
|
{
|
|
name_id = name_iterator->second;
|
|
}
|
|
|
|
return name_id;
|
|
};
|
|
|
|
const NameID forward_name_id = GetNameID(true);
|
|
const NameID backward_name_id = GetNameID(false);
|
|
|
|
const bool in_forward_direction =
|
|
(parsed_way.forward_speed > 0 || parsed_way.forward_rate > 0 || parsed_way.duration > 0 ||
|
|
parsed_way.weight > 0) &&
|
|
(parsed_way.forward_travel_mode != extractor::TRAVEL_MODE_INACCESSIBLE);
|
|
|
|
const bool in_backward_direction =
|
|
(parsed_way.backward_speed > 0 || parsed_way.backward_rate > 0 || parsed_way.duration > 0 ||
|
|
parsed_way.weight > 0) &&
|
|
(parsed_way.backward_travel_mode != extractor::TRAVEL_MODE_INACCESSIBLE);
|
|
|
|
// split an edge into two edges if forwards/backwards behavior differ
|
|
const bool split_edge =
|
|
in_forward_direction && in_backward_direction &&
|
|
(force_split_edges || (parsed_way.forward_rate != parsed_way.backward_rate) ||
|
|
(parsed_way.forward_speed != parsed_way.backward_speed) ||
|
|
(parsed_way.forward_travel_mode != parsed_way.backward_travel_mode) ||
|
|
(turn_lane_id_forward != turn_lane_id_backward) || (forward_classes != backward_classes) ||
|
|
(parsed_way.forward_ref != parsed_way.backward_ref));
|
|
|
|
if (in_forward_direction)
|
|
{ // add (forward) segments or (forward,backward) for non-split edges in backward direction
|
|
const auto annotation_data_id = external_memory.all_edges_annotation_data_list.size();
|
|
external_memory.all_edges_annotation_data_list.push_back({forward_name_id,
|
|
turn_lane_id_forward,
|
|
forward_classes,
|
|
parsed_way.forward_travel_mode,
|
|
parsed_way.is_left_hand_driving});
|
|
util::for_each_pair(
|
|
nodes, [&](const osmium::NodeRef &first_node, const osmium::NodeRef &last_node) {
|
|
NodeBasedEdgeWithOSM edge = {
|
|
OSMNodeID{static_cast<std::uint64_t>(first_node.ref())},
|
|
OSMNodeID{static_cast<std::uint64_t>(last_node.ref())},
|
|
0, // weight
|
|
0, // duration
|
|
0, // distance
|
|
{}, // geometry id
|
|
static_cast<AnnotationID>(annotation_data_id),
|
|
{true,
|
|
in_backward_direction && !split_edge,
|
|
split_edge,
|
|
parsed_way.roundabout,
|
|
parsed_way.circular,
|
|
parsed_way.is_startpoint,
|
|
parsed_way.forward_restricted,
|
|
road_classification,
|
|
parsed_way.highway_turn_classification,
|
|
parsed_way.access_turn_classification}};
|
|
|
|
external_memory.all_edges_list.push_back(
|
|
InternalExtractorEdge(edge, forward_weight_data, forward_duration_data, {}));
|
|
});
|
|
}
|
|
|
|
if (in_backward_direction && (!in_forward_direction || split_edge))
|
|
{ // add (backward) segments for split edges or not in forward direction
|
|
const auto annotation_data_id = external_memory.all_edges_annotation_data_list.size();
|
|
external_memory.all_edges_annotation_data_list.push_back({backward_name_id,
|
|
turn_lane_id_backward,
|
|
backward_classes,
|
|
parsed_way.backward_travel_mode,
|
|
parsed_way.is_left_hand_driving});
|
|
util::for_each_pair(
|
|
nodes, [&](const osmium::NodeRef &first_node, const osmium::NodeRef &last_node) {
|
|
NodeBasedEdgeWithOSM edge = {
|
|
OSMNodeID{static_cast<std::uint64_t>(first_node.ref())},
|
|
OSMNodeID{static_cast<std::uint64_t>(last_node.ref())},
|
|
0, // weight
|
|
0, // duration
|
|
0, // distance
|
|
{}, // geometry id
|
|
static_cast<AnnotationID>(annotation_data_id),
|
|
{false,
|
|
true,
|
|
split_edge,
|
|
parsed_way.roundabout,
|
|
parsed_way.circular,
|
|
parsed_way.is_startpoint,
|
|
parsed_way.backward_restricted,
|
|
road_classification,
|
|
parsed_way.highway_turn_classification,
|
|
parsed_way.access_turn_classification}};
|
|
|
|
external_memory.all_edges_list.push_back(
|
|
InternalExtractorEdge(edge, backward_weight_data, backward_duration_data, {}));
|
|
});
|
|
}
|
|
|
|
std::transform(nodes.begin(),
|
|
nodes.end(),
|
|
std::back_inserter(external_memory.used_node_id_list),
|
|
[](const osmium::NodeRef &ref) {
|
|
return OSMNodeID{static_cast<std::uint64_t>(ref.ref())};
|
|
});
|
|
|
|
auto way_id = OSMWayID{static_cast<std::uint64_t>(input_way.id())};
|
|
external_memory.ways_list.push_back(way_id);
|
|
external_memory.way_node_id_offsets.push_back(external_memory.used_node_id_list.size());
|
|
}
|
|
|
|
} // namespace extractor
|
|
} // namespace osrm
|