Move guidance pre-processing code into GUIDANCE library

This commit is contained in:
Michael Krasnyk
2018-01-05 13:05:53 +01:00
parent 1794185d43
commit 36877e4de5
89 changed files with 153 additions and 151 deletions
+2 -2
View File
@@ -2,10 +2,10 @@
#include "extractor/conditional_turn_penalty.hpp"
#include "extractor/edge_based_edge.hpp"
#include "extractor/files.hpp"
#include "extractor/guidance/turn_analysis.hpp"
#include "extractor/guidance/turn_lane_handler.hpp"
#include "extractor/scripting_environment.hpp"
#include "extractor/suffix_table.hpp"
#include "guidance/turn_analysis.hpp"
#include "guidance/turn_lane_handler.hpp"
#include "extractor/intersection/intersection_analysis.hpp"
+1 -1
View File
@@ -13,7 +13,7 @@
#include "extractor/restriction_parser.hpp"
#include "extractor/scripting_environment.hpp"
#include "extractor/guidance/segregated_intersection_classification.hpp"
#include "guidance/segregated_intersection_classification.hpp"
#include "storage/io.hpp"
+1 -1
View File
@@ -2,10 +2,10 @@
#include "extractor/extraction_containers.hpp"
#include "extractor/extraction_node.hpp"
#include "extractor/extraction_way.hpp"
#include "extractor/guidance/road_classification.hpp"
#include "extractor/profile_properties.hpp"
#include "extractor/query_node.hpp"
#include "extractor/restriction.hpp"
#include "guidance/road_classification.hpp"
#include "util/for_each_pair.hpp"
#include "util/guidance/turn_lanes.hpp"
+1 -1
View File
@@ -2,9 +2,9 @@
#include "extractor/compressed_edge_container.hpp"
#include "extractor/extraction_turn.hpp"
#include "extractor/guidance/intersection.hpp"
#include "extractor/restriction.hpp"
#include "extractor/restriction_compressor.hpp"
#include "guidance/intersection.hpp"
#include "util/dynamic_graph.hpp"
#include "util/node_based_graph.hpp"
File diff suppressed because it is too large Load Diff
@@ -1,88 +0,0 @@
#include "extractor/guidance/driveway_handler.hpp"
#include "util/assert.hpp"
using osrm::extractor::guidance::getTurnDirection;
using osrm::util::angularDeviation;
namespace osrm
{
namespace extractor
{
namespace guidance
{
DrivewayHandler::DrivewayHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &node_coordinates,
const extractor::CompressedEdgeContainer &compressed_geometries,
const RestrictionMap &node_restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table)
: IntersectionHandler(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table)
{
}
// The intersection without major roads needs to pass by service roads (bd, be)
// d
// .
// a--->b--->c
// .
// e
bool DrivewayHandler::canProcess(const NodeID /*nid*/,
const EdgeID /*via_eid*/,
const Intersection &intersection) const
{
const auto from_eid = intersection.getUTurnRoad().eid;
if (intersection.size() <= 2 ||
node_based_graph.GetEdgeData(from_eid).flags.road_classification.IsLowPriorityRoadClass())
return false;
auto low_priority_count =
std::count_if(intersection.begin(), intersection.end(), [this](const auto &road) {
return node_based_graph.GetEdgeData(road.eid)
.flags.road_classification.IsLowPriorityRoadClass();
});
// Process intersection if it has two edges with normal priority and one is the entry edge,
// and also has at least one edge with lower priority
return static_cast<std::size_t>(low_priority_count) + 2 == intersection.size();
}
Intersection DrivewayHandler::
operator()(const NodeID nid, const EdgeID source_edge_id, Intersection intersection) const
{
auto road =
std::find_if(intersection.begin() + 1, intersection.end(), [this](const auto &road) {
return !node_based_graph.GetEdgeData(road.eid)
.flags.road_classification.IsLowPriorityRoadClass();
});
(void)nid;
OSRM_ASSERT(road != intersection.end(), node_coordinates[nid]);
if (road->instruction == TurnInstruction::INVALID())
return intersection;
OSRM_ASSERT(road->instruction.type == TurnType::Turn, node_coordinates[nid]);
road->instruction.type =
isSameName(source_edge_id, road->eid) ? TurnType::NoTurn : TurnType::NewName;
return intersection;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
-92
View File
@@ -1,92 +0,0 @@
#include "extractor/guidance/intersection.hpp"
#include <limits>
#include <string>
#include <boost/range/adaptors.hpp>
using osrm::util::angularDeviation;
namespace osrm
{
namespace extractor
{
namespace guidance
{
bool IntersectionViewData::CompareByAngle(const IntersectionViewData &other) const
{
return angle < other.angle;
}
bool ConnectedRoad::compareByAngle(const ConnectedRoad &other) const { return angle < other.angle; }
void ConnectedRoad::mirror()
{
const constexpr DirectionModifier::Enum mirrored_modifiers[] = {DirectionModifier::UTurn,
DirectionModifier::SharpLeft,
DirectionModifier::Left,
DirectionModifier::SlightLeft,
DirectionModifier::Straight,
DirectionModifier::SlightRight,
DirectionModifier::Right,
DirectionModifier::SharpRight};
static_assert(sizeof(mirrored_modifiers) / sizeof(DirectionModifier::Enum) ==
DirectionModifier::MaxDirectionModifier,
"The list of mirrored modifiers needs to match the available modifiers in size.");
if (util::angularDeviation(angle, 0) > std::numeric_limits<double>::epsilon())
{
angle = 360 - angle;
instruction.direction_modifier = mirrored_modifiers[instruction.direction_modifier];
}
}
ConnectedRoad ConnectedRoad::getMirroredCopy() const
{
ConnectedRoad copy(*this);
copy.mirror();
return copy;
}
std::string toString(const IntersectionShapeData &shape)
{
std::string result =
"[shape] " + std::to_string(shape.eid) + " bearing: " + std::to_string(shape.bearing);
return result;
}
std::string toString(const IntersectionViewData &view)
{
std::string result = "[view] ";
result += std::to_string(view.eid);
result += " allows entry: ";
result += std::to_string(view.entry_allowed);
result += " angle: ";
result += std::to_string(view.angle);
result += " bearing: ";
result += std::to_string(view.bearing);
return result;
}
std::string toString(const ConnectedRoad &road)
{
std::string result = "[connection] ";
result += std::to_string(road.eid);
result += " allows entry: ";
result += std::to_string(road.entry_allowed);
result += " angle: ";
result += std::to_string(road.angle);
result += " bearing: ";
result += std::to_string(road.bearing);
result += " instruction: ";
result += std::to_string(static_cast<std::int32_t>(road.instruction.type)) + " " +
std::to_string(static_cast<std::int32_t>(road.instruction.direction_modifier)) + " " +
std::to_string(static_cast<std::int32_t>(road.lane_data_id));
return result;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -1,497 +0,0 @@
#include "extractor/guidance/intersection_handler.hpp"
#include "extractor/guidance/constants.hpp"
#include "extractor/intersection/intersection_analysis.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/guidance/name_announcements.hpp"
#include "util/log.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
#include <algorithm>
#include <cstddef>
using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData;
using osrm::extractor::guidance::getTurnDirection;
using osrm::util::angularDeviation;
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace detail
{
// TODO check flags!
inline bool requiresAnnouncement(const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const EdgeID from,
const EdgeID to)
{
const auto &from_edge = node_based_graph.GetEdgeData(from);
const auto &to_edge = node_based_graph.GetEdgeData(to);
if (from_edge.reversed != to_edge.reversed)
return true;
if (!(from_edge.flags == to_edge.flags))
return true;
const auto &annotation_from = node_data_container.GetAnnotation(from_edge.annotation_data);
const auto &annotation_to = node_data_container.GetAnnotation(to_edge.annotation_data);
return !annotation_from.CanCombineWith(annotation_to);
}
} // namespace detail
IntersectionHandler::IntersectionHandler(
const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &node_coordinates,
const extractor::CompressedEdgeContainer &compressed_geometries,
const RestrictionMap &node_restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table)
: node_based_graph(node_based_graph), node_data_container(node_data_container),
node_coordinates(node_coordinates), compressed_geometries(compressed_geometries),
node_restriction_map(node_restriction_map), barrier_nodes(barrier_nodes),
turn_lanes_data(turn_lanes_data), name_table(name_table),
street_name_suffix_table(street_name_suffix_table), graph_walker(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data)
{
}
// Inspects an intersection and a turn from via_edge onto road from the possible basic turn types
// (OnRamp, Continue, Turn) find the suitable turn type
TurnType::Enum IntersectionHandler::findBasicTurnType(const EdgeID via_edge,
const ConnectedRoad &road) const
{
bool on_ramp = node_based_graph.GetEdgeData(via_edge).flags.road_classification.IsRampClass();
bool onto_ramp = node_based_graph.GetEdgeData(road.eid).flags.road_classification.IsRampClass();
if (!on_ramp && onto_ramp)
return TurnType::OnRamp;
const auto &in_name_id =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(via_edge).annotation_data)
.name_id;
const auto &out_name_id =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data)
.name_id;
const auto &in_name_empty = name_table.GetNameForID(in_name_id).empty();
const auto &out_name_empty = name_table.GetNameForID(out_name_id).empty();
const auto same_name = !util::guidance::requiresNameAnnounced(
in_name_id, out_name_id, name_table, street_name_suffix_table);
if (!in_name_empty && !out_name_empty && same_name)
{
return TurnType::Continue;
}
return TurnType::Turn;
}
TurnType::Enum IntersectionHandler::areSameClasses(const EdgeID via_edge,
const ConnectedRoad &road) const
{
const auto &in_classes =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(via_edge).annotation_data)
.classes;
const auto &out_classes =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data)
.classes;
return in_classes == out_classes;
}
TurnInstruction IntersectionHandler::getInstructionForObvious(const std::size_t num_roads,
const EdgeID via_edge,
const bool through_street,
const ConnectedRoad &road) const
{
const auto type = findBasicTurnType(via_edge, road);
if (type == TurnType::OnRamp)
{
return {TurnType::OnRamp, getTurnDirection(road.angle)};
}
if (angularDeviation(road.angle, 0) < 0.01)
{
return {TurnType::Continue, DirectionModifier::UTurn};
}
// handle travel modes:
const auto in_mode =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(via_edge).annotation_data)
.travel_mode;
const auto out_mode =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data)
.travel_mode;
const auto needs_notification = in_mode != out_mode;
if (type == TurnType::Turn)
{
const auto &in_classification = node_based_graph.GetEdgeData(via_edge).flags;
const auto &in_data = node_data_container.GetAnnotation(
node_based_graph.GetEdgeData(via_edge).annotation_data);
const auto &out_classification = node_based_graph.GetEdgeData(road.eid).flags;
const auto &out_data = node_data_container.GetAnnotation(
node_based_graph.GetEdgeData(road.eid).annotation_data);
if (util::guidance::requiresNameAnnounced(
in_data.name_id, out_data.name_id, name_table, street_name_suffix_table))
{
// obvious turn onto a through street is a merge
if (through_street)
{
// We reserve merges for motorway types. All others are considered for simply going
// straight onto a road. This avoids confusion about merge directions on streets
// that could potentially also offer different choices
if (out_classification.road_classification.IsMotorwayClass())
return {TurnType::Merge,
road.angle > STRAIGHT_ANGLE ? DirectionModifier::SlightRight
: DirectionModifier::SlightLeft};
else if (in_classification.road_classification.IsRampClass() &&
out_classification.road_classification.IsRampClass())
{
// This check is more a precaution than anything else. Our current travel modes
// cannot reach this, since all ramps are exposing the same travel type. But we
// could see toll-type at some point.
return {in_mode == out_mode ? TurnType::Suppressed : TurnType::Notification,
getTurnDirection(road.angle)};
}
else
{
const double constexpr MAX_COLLAPSE_DISTANCE = 30;
// in normal road condidtions, we check if the turn is nearly straight.
// Doing so, we widen the angle that a turn is considered straight, but since it
// is obvious, the choice is arguably better. We need the road to continue for a
// bit though, until we assume this is safe to do. In addition, the angle cannot
// get too wide, so we only allow narrow turn angles to begin with.
// FIXME this requires https://github.com/Project-OSRM/osrm-backend/pull/2399,
// since `distance` does not refer to an actual distance but rather to the
// duration/weight of the traversal. We can only approximate the distance here
// or actually follow the full road. When 2399 lands, we can exchange here for a
// precalculated distance value.
const auto distance = util::coordinate_calculation::haversineDistance(
node_coordinates[node_based_graph.GetTarget(via_edge)],
node_coordinates[node_based_graph.GetTarget(road.eid)]);
return {TurnType::Turn,
(angularDeviation(road.angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE &&
distance > 2 * MAX_COLLAPSE_DISTANCE)
? DirectionModifier::Straight
: getTurnDirection(road.angle)};
}
}
else
{
return {needs_notification ? TurnType::Notification : TurnType::NewName,
getTurnDirection(road.angle)};
}
}
// name has not changed, suppress a turn here or indicate mode change
else
{
if (needs_notification)
return {TurnType::Notification, getTurnDirection(road.angle)};
else
return {num_roads == 2 && areSameClasses(via_edge, road) ? TurnType::NoTurn
: TurnType::Suppressed,
getTurnDirection(road.angle)};
}
}
BOOST_ASSERT(type == TurnType::Continue);
if (needs_notification)
{
return {TurnType::Notification, getTurnDirection(road.angle)};
}
if (num_roads > 2 || !areSameClasses(via_edge, road))
{
return {TurnType::Suppressed, getTurnDirection(road.angle)};
}
else
{
return {TurnType::NoTurn, getTurnDirection(road.angle)};
}
}
void IntersectionHandler::assignFork(const EdgeID via_edge,
ConnectedRoad &left,
ConnectedRoad &right) const
{
const auto &in_data =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(via_edge).annotation_data);
const auto &lhs_classification =
node_based_graph.GetEdgeData(left.eid).flags.road_classification;
const auto &lhs_data =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(left.eid).annotation_data);
const auto &rhs_classification =
node_based_graph.GetEdgeData(right.eid).flags.road_classification;
const auto &rhs_data =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(right.eid).annotation_data);
const bool low_priority_left = lhs_classification.IsLowPriorityRoadClass();
const bool low_priority_right = rhs_classification.IsLowPriorityRoadClass();
const auto same_mode_left = in_data.travel_mode == lhs_data.travel_mode;
const auto same_mode_right = in_data.travel_mode == rhs_data.travel_mode;
const auto suppressed_left_type =
same_mode_left ? TurnType::Suppressed : TurnType::Notification;
const auto suppressed_right_type =
same_mode_right ? TurnType::Suppressed : TurnType::Notification;
if ((angularDeviation(left.angle, STRAIGHT_ANGLE) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
angularDeviation(right.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE))
{
// left side is actually straight
if (detail::requiresAnnouncement(node_based_graph, node_data_container, via_edge, left.eid))
{
if (low_priority_right && !low_priority_left)
{
left.instruction = getInstructionForObvious(3, via_edge, false, left);
right.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
else
{
if (low_priority_left && !low_priority_right)
{
left.instruction = {findBasicTurnType(via_edge, left),
DirectionModifier::SlightLeft};
right.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
else
{
left.instruction = {TurnType::Fork, DirectionModifier::SlightLeft};
right.instruction = {TurnType::Fork, DirectionModifier::SlightRight};
}
}
}
else
{
left.instruction = {suppressed_left_type, DirectionModifier::Straight};
right.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
}
else if (angularDeviation(right.angle, STRAIGHT_ANGLE) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
angularDeviation(left.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE)
{
// right side is actually straight
if (angularDeviation(right.angle, STRAIGHT_ANGLE) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
angularDeviation(left.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE)
{
if (detail::requiresAnnouncement(
node_based_graph, node_data_container, via_edge, right.eid))
{
if (low_priority_left && !low_priority_right)
{
left.instruction = {findBasicTurnType(via_edge, left),
DirectionModifier::SlightLeft};
right.instruction = getInstructionForObvious(3, via_edge, false, right);
}
else
{
if (low_priority_right && !low_priority_left)
{
left.instruction = {findBasicTurnType(via_edge, left),
DirectionModifier::SlightLeft};
right.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
else
{
right.instruction = {TurnType::Fork, DirectionModifier::SlightRight};
left.instruction = {TurnType::Fork, DirectionModifier::SlightLeft};
}
}
}
else
{
right.instruction = {suppressed_right_type, DirectionModifier::Straight};
left.instruction = {findBasicTurnType(via_edge, left),
DirectionModifier::SlightLeft};
}
}
}
// left side of fork
if (low_priority_right && !low_priority_left)
left.instruction = {suppressed_left_type, DirectionModifier::SlightLeft};
else
{
if (low_priority_left && !low_priority_right)
{
left.instruction = {TurnType::Turn, DirectionModifier::SlightLeft};
}
else
{
left.instruction = {TurnType::Fork, DirectionModifier::SlightLeft};
}
}
// right side of fork
if (low_priority_left && !low_priority_right)
right.instruction = {suppressed_right_type, DirectionModifier::SlightRight};
else
{
if (low_priority_right && !low_priority_left)
{
right.instruction = {TurnType::Turn, DirectionModifier::SlightRight};
}
else
{
right.instruction = {TurnType::Fork, DirectionModifier::SlightRight};
}
}
}
void IntersectionHandler::assignFork(const EdgeID via_edge,
ConnectedRoad &left,
ConnectedRoad &center,
ConnectedRoad &right) const
{
// TODO handle low priority road classes in a reasonable way
const auto suppressed_type = [&](const ConnectedRoad &road) {
const auto in_mode =
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(via_edge).annotation_data)
.travel_mode;
const auto out_mode =
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data)
.travel_mode;
return in_mode == out_mode ? TurnType::Suppressed : TurnType::Notification;
};
if (left.entry_allowed && center.entry_allowed && right.entry_allowed)
{
left.instruction = {TurnType::Fork, DirectionModifier::SlightLeft};
if (angularDeviation(center.angle, 180) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION)
{
if (detail::requiresAnnouncement(
node_based_graph, node_data_container, via_edge, center.eid))
{
center.instruction = {TurnType::Fork, DirectionModifier::Straight};
}
else
{
center.instruction = {suppressed_type(center), DirectionModifier::Straight};
}
}
else
{
center.instruction = {TurnType::Fork, DirectionModifier::Straight};
}
right.instruction = {TurnType::Fork, DirectionModifier::SlightRight};
}
else if (left.entry_allowed)
{
if (right.entry_allowed)
assignFork(via_edge, left, right);
else if (center.entry_allowed)
assignFork(via_edge, left, center);
else
left.instruction = {findBasicTurnType(via_edge, left), getTurnDirection(left.angle)};
}
else if (right.entry_allowed)
{
if (center.entry_allowed)
assignFork(via_edge, center, right);
else
right.instruction = {findBasicTurnType(via_edge, right), getTurnDirection(right.angle)};
}
else
{
if (center.entry_allowed)
center.instruction = {findBasicTurnType(via_edge, center),
getTurnDirection(center.angle)};
}
}
void IntersectionHandler::assignTrivialTurns(const EdgeID via_eid,
Intersection &intersection,
const std::size_t begin,
const std::size_t end) const
{
for (std::size_t index = begin; index != end; ++index)
if (intersection[index].entry_allowed)
{
intersection[index].instruction = {findBasicTurnType(via_eid, intersection[index]),
getTurnDirection(intersection[index].angle)};
}
}
boost::optional<IntersectionHandler::IntersectionViewAndNode>
IntersectionHandler::getNextIntersection(const NodeID at, const EdgeID via) const
{
// We use the intersection generator to jump over traffic signals, barriers. The intersection
// generater takes a starting node and a corresponding edge starting at this node. It returns
// the next non-artificial intersection writing as out param. the source node and the edge
// for which the target is the next intersection.
//
// . .
// a . . tl . . c .
// . .
//
// e0 ^ ^ e1
//
// Starting at node `a` via edge `e0` the intersection generator returns the intersection at `c`
// writing `tl` (traffic signal) node and the edge `e1` which has the intersection as target.
const auto intersection_parameters =
intersection::skipDegreeTwoNodes(node_based_graph, {at, via});
// This should never happen, guard against nevertheless
if (intersection_parameters.node == SPECIAL_NODEID ||
intersection_parameters.edge == SPECIAL_EDGEID)
{
return boost::none;
}
auto intersection = intersection::getConnectedRoads<false>(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
intersection_parameters);
auto intersection_node = node_based_graph.GetTarget(intersection_parameters.edge);
if (intersection.size() <= 2 || intersection.isTrafficSignalOrBarrier())
{
return boost::none;
}
return boost::make_optional(
IntersectionViewAndNode{std::move(intersection), intersection_node});
}
bool IntersectionHandler::isSameName(const EdgeID source_edge_id, const EdgeID target_edge_id) const
{
const auto &source_edge_data = node_data_container.GetAnnotation(
node_based_graph.GetEdgeData(source_edge_id).annotation_data);
const auto &target_edge_data = node_data_container.GetAnnotation(
node_based_graph.GetEdgeData(target_edge_id).annotation_data);
return !name_table.GetNameForID(source_edge_data.name_id).empty() && //
!name_table.GetNameForID(target_edge_data.name_id).empty() && //
!util::guidance::requiresNameAnnounced(source_edge_data.name_id,
target_edge_data.name_id,
name_table,
street_name_suffix_table); //
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -1,620 +0,0 @@
#include "extractor/guidance/mergable_road_detector.hpp"
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/node_based_graph_walker.hpp"
#include "extractor/intersection/intersection_analysis.hpp"
#include "extractor/query_node.hpp"
#include "extractor/suffix_table.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/guidance/name_announcements.hpp"
#include "util/name_table.hpp"
using osrm::util::angularDeviation;
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace
{
// check a connected road for equality of a name
// returns 'true' if no equality because this is used as a filter elsewhere, i.e. filter if fn
// returns 'true'
inline auto makeCheckRoadForName(const NameID name_id,
const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const util::NameTable &name_table,
const SuffixTable &suffix_table)
{
return [name_id, &node_based_graph, &node_data_container, &name_table, &suffix_table](
const MergableRoadDetector::MergableRoadData &road) {
// since we filter here, we don't want any other name than the one we are looking for
const auto road_name_id =
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data)
.name_id;
const auto road_name_empty = name_table.GetNameForID(road_name_id).empty();
const auto in_name_empty = name_table.GetNameForID(name_id).empty();
if (in_name_empty || road_name_empty)
return true;
const auto requires_announcement =
util::guidance::requiresNameAnnounced(
name_id, road_name_id, name_table, suffix_table) ||
util::guidance::requiresNameAnnounced(road_name_id, name_id, name_table, suffix_table);
return requires_announcement;
};
}
}
MergableRoadDetector::MergableRoadDetector(
const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &node_coordinates,
const extractor::CompressedEdgeContainer &compressed_geometries,
const RestrictionMap &node_restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table)
: node_based_graph(node_based_graph), node_data_container(node_data_container),
node_coordinates(node_coordinates), compressed_geometries(compressed_geometries),
node_restriction_map(node_restriction_map), barrier_nodes(barrier_nodes),
turn_lanes_data(turn_lanes_data), name_table(name_table),
street_name_suffix_table(street_name_suffix_table),
coordinate_extractor(node_based_graph, compressed_geometries, node_coordinates)
{
}
bool MergableRoadDetector::CanMergeRoad(const NodeID intersection_node,
const IntersectionShapeData &lhs,
const IntersectionShapeData &rhs) const
{
// roads should be somewhat close
if (angularDeviation(lhs.bearing, rhs.bearing) > MERGABLE_ANGLE_DIFFERENCE)
return false;
const auto &lhs_edge = node_based_graph.GetEdgeData(lhs.eid);
const auto &rhs_edge = node_based_graph.GetEdgeData(rhs.eid);
const auto &lhs_edge_data = node_data_container.GetAnnotation(lhs_edge.annotation_data);
const auto &rhs_edge_data = node_data_container.GetAnnotation(rhs_edge.annotation_data);
// and they need to describe the same road
if ((lhs_edge.reversed == rhs_edge.reversed) ||
!EdgeDataSupportsMerge(lhs_edge.flags, rhs_edge.flags, lhs_edge_data, rhs_edge_data))
return false;
/* don't use any circular links, since they mess up detection we jump out early.
*
* / -- \
* a ---- b - - /
*/
const auto road_target = [this](const MergableRoadData &road) {
return node_based_graph.GetTarget(road.eid);
};
// TODO might have to skip over trivial intersections
if (road_target(lhs) == intersection_node || road_target(rhs) == intersection_node)
return false;
// Don't merge turning circles/traffic loops
if (IsTrafficLoop(intersection_node, lhs) || IsTrafficLoop(intersection_node, rhs))
return false;
// needs to be checked prior to link roads, since connections can seem like links
if (IsTrafficIsland(intersection_node, lhs, rhs))
return true;
// Don't merge link roads
if (IsLinkRoad(intersection_node, lhs) || IsLinkRoad(intersection_node, rhs))
return false;
// check if we simply split up prior to an intersection
if (IsNarrowTriangle(intersection_node, lhs, rhs))
return true;
// finally check if two roads describe the direction
return HaveSameDirection(intersection_node, lhs, rhs) &&
!IsCircularShape(intersection_node, lhs, rhs);
}
bool MergableRoadDetector::IsDistinctFrom(const MergableRoadData &lhs,
const MergableRoadData &rhs) const
{
// needs to be far away
if (angularDeviation(lhs.bearing, rhs.bearing) > MERGABLE_ANGLE_DIFFERENCE)
return true;
else // or it cannot have the same name
return !HaveIdenticalNames(
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(lhs.eid).annotation_data)
.name_id,
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(rhs.eid).annotation_data)
.name_id,
name_table,
street_name_suffix_table);
}
bool MergableRoadDetector::EdgeDataSupportsMerge(
const NodeBasedEdgeClassification &lhs_flags,
const NodeBasedEdgeClassification &rhs_flags,
const NodeBasedEdgeAnnotation &lhs_annotation,
const NodeBasedEdgeAnnotation &rhs_annotation) const
{
// roundabouts are special, simply don't hurt them. We might not want to bear the
// consequences
if (lhs_flags.roundabout || rhs_flags.roundabout)
return false;
/* The travel mode should be the same for both roads. If we were to merge different travel
* modes, we would hide information/run the risk of loosing valid choices (e.g. short period
* of pushing)
*/
if (lhs_annotation.travel_mode != rhs_annotation.travel_mode)
return false;
// we require valid names
if (!HaveIdenticalNames(
lhs_annotation.name_id, rhs_annotation.name_id, name_table, street_name_suffix_table))
return false;
return lhs_flags.road_classification == rhs_flags.road_classification;
}
bool MergableRoadDetector::IsTrafficLoop(const NodeID intersection_node,
const MergableRoadData &road) const
{
const auto connection =
intersection::skipDegreeTwoNodes(node_based_graph, {intersection_node, road.eid});
return intersection_node == node_based_graph.GetTarget(connection.edge);
}
bool MergableRoadDetector::IsNarrowTriangle(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const
{
// selection data to the right and left
const auto constexpr SMALL_RANDOM_HOPLIMIT = 5;
IntersectionFinderAccumulator left_accumulator(SMALL_RANDOM_HOPLIMIT,
node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data),
right_accumulator(SMALL_RANDOM_HOPLIMIT,
node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data);
/* Standard following the straightmost road
* Since both items have the same id, we can `select` based on any setup
*/
SelectStraightmostRoadByNameAndOnlyChoice selector(
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(lhs.eid).annotation_data)
.name_id,
lhs.bearing,
/*requires entry=*/false,
false);
NodeBasedGraphWalker graph_walker(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data);
graph_walker.TraverseRoad(intersection_node, lhs.eid, left_accumulator, selector);
/* if the intersection does not have a right turn, we continue onto the next one once
* (skipping over a single small side street)
*/
if (angularDeviation(left_accumulator.intersection.findClosestTurn(ORTHOGONAL_ANGLE)->angle,
ORTHOGONAL_ANGLE) > NARROW_TURN_ANGLE)
{
graph_walker.TraverseRoad(
node_based_graph.GetTarget(left_accumulator.via_edge_id),
left_accumulator.intersection.findClosestTurn(STRAIGHT_ANGLE)->eid,
left_accumulator,
selector);
}
const auto distance_to_triangle = util::coordinate_calculation::haversineDistance(
node_coordinates[intersection_node],
node_coordinates[node_based_graph.GetTarget(left_accumulator.via_edge_id)]);
// don't move too far down the road
const constexpr auto RANGE_TO_TRIANGLE_LIMIT = 80;
if (distance_to_triangle > RANGE_TO_TRIANGLE_LIMIT)
return false;
graph_walker.TraverseRoad(intersection_node, rhs.eid, right_accumulator, selector);
if (angularDeviation(right_accumulator.intersection.findClosestTurn(270)->angle, 270) >
NARROW_TURN_ANGLE)
{
graph_walker.TraverseRoad(
node_based_graph.GetTarget(right_accumulator.via_edge_id),
right_accumulator.intersection.findClosestTurn(STRAIGHT_ANGLE)->eid,
right_accumulator,
selector);
}
BOOST_ASSERT(!left_accumulator.intersection.empty() && !right_accumulator.intersection.empty());
// find the closes resembling a right turn
const auto connector_turn = left_accumulator.intersection.findClosestTurn(ORTHOGONAL_ANGLE);
/* check if that right turn connects to the right_accumulator intersection (i.e. we have a
* triangle)
* a connection should be somewhat to the right, when looking at the left side of the
* triangle
*
* b ..... c
* \ /
* \ /
* \ /
* a
*
* e.g. here when looking at `a,b`, a narrow triangle should offer a turn to the right, when
* we want to connect to c
*/
if (angularDeviation(connector_turn->angle, ORTHOGONAL_ANGLE) > NARROW_TURN_ANGLE)
return false;
const auto num_lanes = [this](const MergableRoadData &road) {
return std::max<std::uint8_t>(
node_based_graph.GetEdgeData(road.eid).flags.road_classification.GetNumberOfLanes(), 1);
};
// the width we can bridge at the intersection
const auto assumed_road_width = (num_lanes(lhs) + num_lanes(rhs)) * ASSUMED_LANE_WIDTH;
const constexpr auto MAXIMAL_ALLOWED_TRAFFIC_ISLAND_WIDTH = 10;
const auto distance_between_triangle_corners = util::coordinate_calculation::haversineDistance(
node_coordinates[node_based_graph.GetTarget(left_accumulator.via_edge_id)],
node_coordinates[node_based_graph.GetTarget(right_accumulator.via_edge_id)]);
if (distance_between_triangle_corners >
(assumed_road_width + MAXIMAL_ALLOWED_TRAFFIC_ISLAND_WIDTH))
return false;
// check if both intersections are connected
IntersectionFinderAccumulator connect_accumulator(SMALL_RANDOM_HOPLIMIT,
node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data);
graph_walker.TraverseRoad(node_based_graph.GetTarget(left_accumulator.via_edge_id),
connector_turn->eid,
connect_accumulator,
selector);
// the if both items are connected
return node_based_graph.GetTarget(connect_accumulator.via_edge_id) ==
node_based_graph.GetTarget(right_accumulator.via_edge_id);
}
bool MergableRoadDetector::IsCircularShape(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const
{
NodeBasedGraphWalker graph_walker(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data);
const auto getCoordinatesAlongWay = [&](const EdgeID edge_id, const double max_length) {
LengthLimitedCoordinateAccumulator accumulator(coordinate_extractor, max_length);
SelectStraightmostRoadByNameAndOnlyChoice selector(
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(edge_id).annotation_data)
.name_id,
lhs.bearing,
/*requires_entry=*/false,
false);
graph_walker.TraverseRoad(intersection_node, edge_id, accumulator, selector);
return std::make_pair(accumulator.accumulated_length, accumulator.coordinates);
};
std::vector<util::Coordinate> coordinates_to_the_left, coordinates_to_the_right;
double distance_traversed_to_the_left, distance_traversed_to_the_right;
std::tie(distance_traversed_to_the_left, coordinates_to_the_left) =
getCoordinatesAlongWay(lhs.eid, distance_to_extract);
std::tie(distance_traversed_to_the_right, coordinates_to_the_right) =
getCoordinatesAlongWay(rhs.eid, distance_to_extract);
const auto connect_again = (coordinates_to_the_left.back() == coordinates_to_the_right.back());
// Tuning parameter to detect and don't merge roads close to circular shapes
// if the area to squared circumference ratio is between the lower bound and 1/(4π)
// that correspond to isoperimetric inequality 4πA ≤ L² or lower bound ≤ A/L² ≤ 1/(4π).
// The lower bound must be larger enough to allow merging of square-shaped intersections
// with A/L² = 1/16 or 78.6%
// The condition suppresses roads merging for intersections like
// . .
// . .
// ---- ----
// . .
// . .
// but will allow roads merging for intersections like
// -------
// / \ 
// ---- ----
// \ /
// -------
const auto constexpr CIRCULAR_POLYGON_ISOPERIMETRIC_LOWER_BOUND = 0.85 / (4 * M_PI);
if (connect_again && coordinates_to_the_left.front() == coordinates_to_the_left.back())
{ // if the left and right roads connect again and are closed polygons ...
const auto area = util::coordinate_calculation::computeArea(coordinates_to_the_left);
const auto perimeter = distance_traversed_to_the_left;
const auto area_to_squared_perimeter_ratio = std::abs(area) / (perimeter * perimeter);
// then don't merge roads if A/L² is greater than the lower bound
BOOST_ASSERT(area_to_squared_perimeter_ratio <= 1. / (4 * M_PI));
if (area_to_squared_perimeter_ratio >= CIRCULAR_POLYGON_ISOPERIMETRIC_LOWER_BOUND)
return true;
}
return false;
}
bool MergableRoadDetector::HaveSameDirection(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const
{
if (angularDeviation(lhs.bearing, rhs.bearing) > MERGABLE_ANGLE_DIFFERENCE)
return false;
// Find a coordinate following a road that is far away
NodeBasedGraphWalker graph_walker(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data);
const auto getCoordinatesAlongWay = [&](const EdgeID edge_id, const double max_length) {
LengthLimitedCoordinateAccumulator accumulator(coordinate_extractor, max_length);
SelectStraightmostRoadByNameAndOnlyChoice selector(
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(edge_id).annotation_data)
.name_id,
lhs.bearing,
/*requires_entry=*/false,
true);
graph_walker.TraverseRoad(intersection_node, edge_id, accumulator, selector);
return std::make_pair(accumulator.accumulated_length, accumulator.coordinates);
};
std::vector<util::Coordinate> coordinates_to_the_left, coordinates_to_the_right;
double distance_traversed_to_the_left, distance_traversed_to_the_right;
std::tie(distance_traversed_to_the_left, coordinates_to_the_left) =
getCoordinatesAlongWay(lhs.eid, distance_to_extract);
// tuned parameter, if we didn't get as far as 40 meters, we might barely look past an
// intersection.
const auto constexpr MINIMUM_LENGTH_FOR_PARALLEL_DETECTION = 40;
// quit early if the road is not very long
if (distance_traversed_to_the_left <= MINIMUM_LENGTH_FOR_PARALLEL_DETECTION)
return false;
std::tie(distance_traversed_to_the_right, coordinates_to_the_right) =
getCoordinatesAlongWay(rhs.eid, distance_to_extract);
if (distance_traversed_to_the_right <= MINIMUM_LENGTH_FOR_PARALLEL_DETECTION)
return false;
const auto connect_again = (coordinates_to_the_left.back() == coordinates_to_the_right.back());
// sampling to correctly weight longer segments in regression calculations
const auto constexpr SAMPLE_INTERVAL = 5;
coordinates_to_the_left = coordinate_extractor.SampleCoordinates(
std::move(coordinates_to_the_left), distance_to_extract, SAMPLE_INTERVAL);
coordinates_to_the_right = coordinate_extractor.SampleCoordinates(
std::move(coordinates_to_the_right), distance_to_extract, SAMPLE_INTERVAL);
/* extract the number of lanes for a road
* restricts a vector to the last two thirds of the length
*/
const auto prune = [](auto &data_vector) {
BOOST_ASSERT(data_vector.size() >= 3);
// erase the first third of the vector
data_vector.erase(data_vector.begin(), data_vector.begin() + data_vector.size() / 3);
};
/* if the coordinates meet up again, e.g. due to a split and join, pruning can have a negative
* effect. We therefore only prune away the beginning, if the roads don't meet up again as well.
*/
if (!connect_again)
{
prune(coordinates_to_the_left);
prune(coordinates_to_the_right);
}
const auto are_parallel =
util::coordinate_calculation::areParallel(coordinates_to_the_left.begin(),
coordinates_to_the_left.end(),
coordinates_to_the_right.begin(),
coordinates_to_the_right.end());
if (!are_parallel)
return false;
// compare reference distance:
const auto distance_mid_left_to_right = util::coordinate_calculation::findClosestDistance(
coordinates_to_the_left[coordinates_to_the_left.size() / 2],
coordinates_to_the_right.begin(),
coordinates_to_the_right.end());
const auto distance_mid_right_to_left = util::coordinate_calculation::findClosestDistance(
coordinates_to_the_right[coordinates_to_the_right.size() / 2],
coordinates_to_the_left.begin(),
coordinates_to_the_left.end());
const auto distance_between_roads =
std::min(distance_mid_left_to_right, distance_mid_right_to_left);
const auto lane_count_lhs = std::max<int>(
1, node_based_graph.GetEdgeData(lhs.eid).flags.road_classification.GetNumberOfLanes());
const auto lane_count_rhs = std::max<int>(
1, node_based_graph.GetEdgeData(rhs.eid).flags.road_classification.GetNumberOfLanes());
const auto combined_road_width = 0.5 * (lane_count_lhs + lane_count_rhs) * ASSUMED_LANE_WIDTH;
const auto constexpr MAXIMAL_ALLOWED_SEPARATION_WIDTH = 12;
return distance_between_roads <= combined_road_width + MAXIMAL_ALLOWED_SEPARATION_WIDTH;
}
bool MergableRoadDetector::IsTrafficIsland(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const
{
/* compute the set of all intersection_nodes along the way of an edge, until it reaches a
* location with the same name repeatet at least three times
*/
const auto left_connection =
intersection::skipDegreeTwoNodes(node_based_graph, {intersection_node, lhs.eid});
const auto right_connection =
intersection::skipDegreeTwoNodes(node_based_graph, {intersection_node, rhs.eid});
const auto left_candidate = node_based_graph.GetTarget(left_connection.edge);
const auto right_candidate = node_based_graph.GetTarget(right_connection.edge);
const auto candidate_is_valid =
left_candidate == right_candidate && left_candidate != intersection_node;
if (!candidate_is_valid)
return false;
// check if all entries at the destination or at the source are the same
const auto all_same_name_and_degree_three = [this](const NodeID nid) {
// check if the intersection found has degree three
if (node_based_graph.GetOutDegree(nid) != 3)
return false;
// check if all items share a name
const auto range = node_based_graph.GetAdjacentEdgeRange(nid);
const auto required_name_id =
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(range.front()).annotation_data)
.name_id;
const auto has_required_name = [this, required_name_id](const auto edge_id) {
const auto road_name_id =
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(edge_id).annotation_data)
.name_id;
const auto &road_name_empty = name_table.GetNameForID(road_name_id).empty();
const auto &required_name_empty = name_table.GetNameForID(required_name_id).empty();
if (required_name_empty && road_name_empty)
return false;
return !util::guidance::requiresNameAnnounced(
required_name_id, road_name_id, name_table, street_name_suffix_table) ||
!util::guidance::requiresNameAnnounced(
road_name_id, required_name_id, name_table, street_name_suffix_table);
};
/* the beautiful way would be:
* return range.end() == std::find_if_not(range.begin(), range.end(), has_required_name);
* but that does not work due to range concepts
*/
for (const auto eid : range)
if (!has_required_name(eid))
return false;
return true;
};
const auto degree_three_connect_in = all_same_name_and_degree_three(intersection_node);
const auto degree_three_connect_out = all_same_name_and_degree_three(left_candidate);
if (!degree_three_connect_in && !degree_three_connect_out)
return false;
const auto distance_between_candidates = util::coordinate_calculation::haversineDistance(
node_coordinates[intersection_node], node_coordinates[left_candidate]);
const auto both_split_join = degree_three_connect_in && degree_three_connect_out;
// allow longer separations if both are joining directly
// widths are chosen via tuning on traffic islands
return both_split_join ? (distance_between_candidates < 30)
: (distance_between_candidates < 15);
}
bool MergableRoadDetector::IsLinkRoad(const NodeID intersection_node,
const MergableRoadData &road) const
{
const auto next_intersection_parameters =
intersection::skipDegreeTwoNodes(node_based_graph, {intersection_node, road.eid});
const auto next_intersection_along_road =
intersection::getConnectedRoads<false>(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
next_intersection_parameters);
const auto extract_name_id = [this](const MergableRoadData &road) {
return node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data)
.name_id;
};
const auto requested_name_id = extract_name_id(road);
const auto next_road_along_path = next_intersection_along_road.findClosestTurn(
STRAIGHT_ANGLE,
makeCheckRoadForName(requested_name_id,
node_based_graph,
node_data_container,
name_table,
street_name_suffix_table));
// we need to have a continuing road to successfully detect a link road
if (next_road_along_path == next_intersection_along_road.end())
return false;
const auto opposite_of_next_road_along_path = next_intersection_along_road.findClosestTurn(
util::restrictAngleToValidRange(next_road_along_path->angle + STRAIGHT_ANGLE));
// we cannot be looking at the same road we came from
if (node_based_graph.GetTarget(opposite_of_next_road_along_path->eid) ==
next_intersection_parameters.node)
return false;
/* check if the opposite of the next road decision was sane. It could have been just as well our
* incoming road.
*/
if (angularDeviation(angularDeviation(next_road_along_path->angle, STRAIGHT_ANGLE),
angularDeviation(opposite_of_next_road_along_path->angle, 0)) <
FUZZY_ANGLE_DIFFERENCE)
return false;
// near straight road that continues
return angularDeviation(opposite_of_next_road_along_path->angle, next_road_along_path->angle) >=
(STRAIGHT_ANGLE - FUZZY_ANGLE_DIFFERENCE) &&
(node_based_graph.GetEdgeData(next_road_along_path->eid).reversed ==
node_based_graph.GetEdgeData(opposite_of_next_road_along_path->eid).reversed) &&
EdgeDataSupportsMerge(
node_based_graph.GetEdgeData(next_road_along_path->eid).flags,
node_based_graph.GetEdgeData(opposite_of_next_road_along_path->eid).flags,
node_data_container.GetAnnotation(
node_based_graph.GetEdgeData(next_road_along_path->eid).annotation_data),
node_data_container.GetAnnotation(
node_based_graph.GetEdgeData(opposite_of_next_road_along_path->eid)
.annotation_data));
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
-568
View File
@@ -1,568 +0,0 @@
#include "extractor/guidance/motorway_handler.hpp"
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/road_classification.hpp"
#include "util/assert.hpp"
#include "util/bearing.hpp"
#include "util/guidance/name_announcements.hpp"
#include <limits>
#include <utility>
#include <boost/assert.hpp>
using osrm::util::angularDeviation;
using osrm::extractor::guidance::getTurnDirection;
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace
{
inline bool isMotorwayClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph)
{
return node_based_graph.GetEdgeData(eid).flags.road_classification.IsMotorwayClass();
}
inline RoadClassification roadClass(const ConnectedRoad &road,
const util::NodeBasedDynamicGraph &graph)
{
return graph.GetEdgeData(road.eid).flags.road_classification;
}
inline bool isRampClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph)
{
return node_based_graph.GetEdgeData(eid).flags.road_classification.IsRampClass();
}
} // namespace
MotorwayHandler::MotorwayHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &coordinates,
const extractor::CompressedEdgeContainer &compressed_geometries,
const RestrictionMap &node_restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table)
: IntersectionHandler(node_based_graph,
node_data_container,
coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table)
{
}
bool MotorwayHandler::canProcess(const NodeID,
const EdgeID via_eid,
const Intersection &intersection) const
{
bool has_motorway = false;
bool has_normal_roads = false;
for (const auto &road : intersection)
{
// not merging or forking?
if (road.entry_allowed && angularDeviation(road.angle, STRAIGHT_ANGLE) > 60)
return false;
else if (isMotorwayClass(road.eid, node_based_graph))
{
if (road.entry_allowed)
has_motorway = true;
}
else if (!isRampClass(road.eid, node_based_graph))
has_normal_roads = true;
}
if (has_normal_roads)
return false;
return has_motorway || isMotorwayClass(via_eid, node_based_graph);
}
Intersection MotorwayHandler::
operator()(const NodeID, const EdgeID via_eid, Intersection intersection) const
{
// coming from motorway
if (isMotorwayClass(via_eid, node_based_graph))
{
intersection = fromMotorway(via_eid, std::move(intersection));
std::for_each(intersection.begin(), intersection.end(), [](ConnectedRoad &road) {
if (road.instruction.type == TurnType::OnRamp)
road.instruction.type = TurnType::OffRamp;
});
return intersection;
}
else // coming from a ramp
{
return fromRamp(via_eid, std::move(intersection));
// ramp merging straight onto motorway
}
}
Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection intersection) const
{
const auto &in_data =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(via_eid).annotation_data);
BOOST_ASSERT(isMotorwayClass(via_eid, node_based_graph));
const auto countExitingMotorways = [this](const Intersection &intersection) {
unsigned count = 0;
for (const auto &road : intersection)
{
if (road.entry_allowed && isMotorwayClass(road.eid, node_based_graph))
++count;
}
return count;
};
// find the angle that continues on our current highway
const auto getContinueAngle = [this, in_data](const Intersection &intersection) {
for (const auto &road : intersection)
{
if (!road.entry_allowed)
continue;
const auto &out_data = node_data_container.GetAnnotation(
node_based_graph.GetEdgeData(road.eid).annotation_data);
const auto same_name = !util::guidance::requiresNameAnnounced(
in_data.name_id, out_data.name_id, name_table, street_name_suffix_table);
if (road.angle != 0 && in_data.name_id != EMPTY_NAMEID &&
out_data.name_id != EMPTY_NAMEID && same_name &&
isMotorwayClass(road.eid, node_based_graph))
return road.angle;
}
return intersection[0].angle;
};
const auto getMostLikelyContinue = [this, in_data](const Intersection &intersection) {
double angle = intersection[0].angle;
double best = 180;
for (const auto &road : intersection)
{
if (isMotorwayClass(road.eid, node_based_graph) &&
angularDeviation(road.angle, STRAIGHT_ANGLE) < best)
{
best = angularDeviation(road.angle, STRAIGHT_ANGLE);
angle = road.angle;
}
}
return angle;
};
const auto findBestContinue = [&]() {
const double continue_angle = getContinueAngle(intersection);
if (continue_angle != intersection[0].angle)
return continue_angle;
else
return getMostLikelyContinue(intersection);
};
// find continue angle
const double continue_angle = findBestContinue();
// highway does not continue and has no obvious choice
if (continue_angle == intersection[0].angle)
{
if (intersection.size() == 2)
{
// do not announce ramps at the end of a highway
intersection[1].instruction = {TurnType::NoTurn,
getTurnDirection(intersection[1].angle)};
}
else if (intersection.size() == 3)
{
// splitting ramp at the end of a highway
if (intersection[1].entry_allowed && intersection[2].entry_allowed)
{
assignFork(via_eid, intersection[2], intersection[1]);
}
else
{
// ending in a passing ramp
if (intersection[1].entry_allowed)
intersection[1].instruction = {TurnType::NoTurn,
getTurnDirection(intersection[1].angle)};
else
intersection[2].instruction = {TurnType::NoTurn,
getTurnDirection(intersection[2].angle)};
}
}
else if (intersection.size() == 4 &&
roadClass(intersection[1], node_based_graph) ==
roadClass(intersection[2], node_based_graph) &&
roadClass(intersection[2], node_based_graph) ==
roadClass(intersection[3], node_based_graph))
{
// tripple fork at the end
assignFork(via_eid, intersection[3], intersection[2], intersection[1]);
}
else if (intersection.countEnterable() > 0) // check whether turns exist at all
{
// FALLBACK, this should hopefully never be reached
return fallback(std::move(intersection));
}
}
else
{
const unsigned exiting_motorways = countExitingMotorways(intersection);
if (exiting_motorways == 0)
{
// Ending in Ramp
for (auto &road : intersection)
{
if (road.entry_allowed)
{
BOOST_ASSERT(isRampClass(road.eid, node_based_graph));
road.instruction = TurnInstruction::SUPPRESSED(getTurnDirection(road.angle));
}
}
}
else if (exiting_motorways == 1)
{
// normal motorway passing some ramps or mering onto another motorway
if (intersection.size() == 2)
{
BOOST_ASSERT(!isRampClass(intersection[1].eid, node_based_graph));
intersection[1].instruction =
getInstructionForObvious(intersection.size(),
via_eid,
isThroughStreet(1,
intersection,
node_based_graph,
node_data_container,
name_table,
street_name_suffix_table),
intersection[1]);
}
else
{
// Normal Highway exit or merge
for (auto &road : intersection)
{
// ignore invalid uturns/other
if (!road.entry_allowed)
continue;
if (road.angle == continue_angle)
{
road.instruction =
getInstructionForObvious(intersection.size(),
via_eid,
isThroughStreet(1,
intersection,
node_based_graph,
node_data_container,
name_table,
street_name_suffix_table),
road);
}
else if (road.angle < continue_angle)
{
road.instruction = {isRampClass(road.eid, node_based_graph)
? TurnType::OffRamp
: TurnType::Turn,
(road.angle < 145) ? DirectionModifier::Right
: DirectionModifier::SlightRight};
}
else if (road.angle > continue_angle)
{
road.instruction = {isRampClass(road.eid, node_based_graph)
? TurnType::OffRamp
: TurnType::Turn,
(road.angle > 215) ? DirectionModifier::Left
: DirectionModifier::SlightLeft};
}
}
}
}
// handle motorway forks
else if (exiting_motorways > 1)
{
if (exiting_motorways == 2)
{
OSRM_ASSERT(intersection.size() != 2,
node_coordinates[node_based_graph.GetTarget(via_eid)]);
// standard fork
std::size_t first_valid = std::numeric_limits<std::size_t>::max(),
second_valid = std::numeric_limits<std::size_t>::max();
for (std::size_t i = 0; i < intersection.size(); ++i)
{
if (intersection[i].entry_allowed &&
isMotorwayClass(intersection[i].eid, node_based_graph))
{
if (first_valid < intersection.size())
{
second_valid = i;
break;
}
else
{
first_valid = i;
}
}
}
assignFork(via_eid, intersection[second_valid], intersection[first_valid]);
}
else if (exiting_motorways == 3)
{
// triple fork
std::size_t first_valid = std::numeric_limits<std::size_t>::max(),
second_valid = std::numeric_limits<std::size_t>::max(),
third_valid = std::numeric_limits<std::size_t>::max();
for (std::size_t i = 0; i < intersection.size(); ++i)
{
if (intersection[i].entry_allowed &&
isMotorwayClass(intersection[i].eid, node_based_graph))
{
if (second_valid < intersection.size())
{
third_valid = i;
break;
}
else if (first_valid < intersection.size())
{
second_valid = i;
}
else
{
first_valid = i;
}
}
}
assignFork(via_eid,
intersection[third_valid],
intersection[second_valid],
intersection[first_valid]);
}
else
{
return fallback(std::move(intersection));
}
} // done for more than one highway exit
}
return intersection;
}
Intersection MotorwayHandler::fromRamp(const EdgeID via_eid, Intersection intersection) const
{
auto num_valid_turns = intersection.countEnterable();
// ramp straight into a motorway/ramp
if (intersection.size() == 2 && num_valid_turns == 1)
{
BOOST_ASSERT(!intersection[0].entry_allowed);
BOOST_ASSERT(isMotorwayClass(intersection[1].eid, node_based_graph));
intersection[1].instruction =
getInstructionForObvious(intersection.size(),
via_eid,
isThroughStreet(1,
intersection,
node_based_graph,
node_data_container,
name_table,
street_name_suffix_table),
intersection[1]);
}
else if (intersection.size() == 3)
{
const auto &second_intersection_data = node_data_container.GetAnnotation(
node_based_graph.GetEdgeData(intersection[2].eid).annotation_data);
const auto &first_intersection_data = node_data_container.GetAnnotation(
node_based_graph.GetEdgeData(intersection[1].eid).annotation_data);
const auto first_second_same_name =
!util::guidance::requiresNameAnnounced(second_intersection_data.name_id,
first_intersection_data.name_id,
name_table,
street_name_suffix_table);
// merging onto a passing highway / or two ramps merging onto the same highway
if (num_valid_turns == 1)
{
BOOST_ASSERT(!intersection[0].entry_allowed);
// check order of highways
// 4
// 5 3
//
// 6 2
//
// 7 1
// 0
const auto &first_intersection_name_empty =
name_table.GetNameForID(first_intersection_data.name_id).empty();
const auto &second_intersection_name_empty =
name_table.GetNameForID(second_intersection_data.name_id).empty();
if (intersection[1].entry_allowed)
{
if (isMotorwayClass(intersection[1].eid, node_based_graph) &&
!second_intersection_name_empty && !first_intersection_name_empty &&
first_second_same_name)
{
// circular order indicates a merge to the left (0-3 onto 4
if (angularDeviation(intersection[1].angle, STRAIGHT_ANGLE) <
2 * NARROW_TURN_ANGLE)
intersection[1].instruction = {TurnType::Merge,
DirectionModifier::SlightLeft};
else // fallback
intersection[1].instruction = {TurnType::Merge,
getTurnDirection(intersection[1].angle)};
}
else // passing by the end of a motorway
{
intersection[1].instruction =
getInstructionForObvious(intersection.size(),
via_eid,
isThroughStreet(1,
intersection,
node_based_graph,
node_data_container,
name_table,
street_name_suffix_table),
intersection[1]);
}
}
else
{
BOOST_ASSERT(intersection[2].entry_allowed);
if (isMotorwayClass(intersection[2].eid, node_based_graph) &&
!second_intersection_name_empty && !first_intersection_name_empty &&
first_second_same_name)
{
// circular order (5-0) onto 4
if (angularDeviation(intersection[2].angle, STRAIGHT_ANGLE) <
2 * NARROW_TURN_ANGLE)
intersection[2].instruction = {TurnType::Merge,
DirectionModifier::SlightRight};
else // fallback
intersection[2].instruction = {TurnType::Merge,
getTurnDirection(intersection[2].angle)};
}
else // passing the end of a highway
{
intersection[2].instruction =
getInstructionForObvious(intersection.size(),
via_eid,
isThroughStreet(2,
intersection,
node_based_graph,
node_data_container,
name_table,
street_name_suffix_table),
intersection[2]);
}
}
}
else
{
BOOST_ASSERT(num_valid_turns == 2);
// UTurn on ramps is not possible
BOOST_ASSERT(!intersection[0].entry_allowed);
BOOST_ASSERT(intersection[1].entry_allowed);
BOOST_ASSERT(intersection[2].entry_allowed);
// two motorways starting at end of ramp (fork)
// M M
// \ /
// |
// R
if (isMotorwayClass(intersection[1].eid, node_based_graph) &&
isMotorwayClass(intersection[2].eid, node_based_graph))
{
assignFork(via_eid, intersection[2], intersection[1]);
}
else
{
// continued ramp passing motorway entry
// M R
// M R
// | /
// R
if (isMotorwayClass(intersection[1].eid, node_based_graph))
{
intersection[1].instruction = {TurnType::Turn, DirectionModifier::SlightRight};
intersection[2].instruction = {TurnType::Continue,
DirectionModifier::SlightLeft};
}
else
{
assignFork(via_eid, intersection[2], intersection[1]);
}
}
}
}
// On - Off Ramp on passing Motorway, Ramp onto Fork(?)
else if (intersection.size() == 4)
{
bool passed_highway_entry = false;
for (auto &road : intersection)
{
if (!road.entry_allowed && isMotorwayClass(road.eid, node_based_graph))
{
passed_highway_entry = true;
}
else if (isMotorwayClass(road.eid, node_based_graph))
{
road.instruction = {TurnType::Merge,
passed_highway_entry ? DirectionModifier::SlightRight
: DirectionModifier::SlightLeft};
}
else
{
BOOST_ASSERT(isRampClass(road.eid, node_based_graph));
road.instruction = {TurnType::OffRamp, getTurnDirection(road.angle)};
}
}
}
else
{
return fallback(std::move(intersection));
}
return intersection;
}
Intersection MotorwayHandler::fallback(Intersection intersection) const
{
for (auto &road : intersection)
{
if (!road.entry_allowed)
continue;
const auto type =
isMotorwayClass(road.eid, node_based_graph) ? TurnType::Merge : TurnType::Turn;
if (type == TurnType::Turn)
{
if (angularDeviation(road.angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE)
road.instruction = {type, DirectionModifier::Straight};
else
{
road.instruction = {type,
road.angle > STRAIGHT_ANGLE ? DirectionModifier::SlightLeft
: DirectionModifier::SlightRight};
}
}
else
{
road.instruction = {type,
road.angle < STRAIGHT_ANGLE ? DirectionModifier::SlightLeft
: DirectionModifier::SlightRight};
}
}
return intersection;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -1,294 +0,0 @@
#include "extractor/guidance/node_based_graph_walker.hpp"
#include "extractor/intersection/intersection_analysis.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
#include <utility>
using osrm::util::angularDeviation;
namespace osrm
{
namespace extractor
{
namespace guidance
{
// ---------------------------------------------------------------------------------
NodeBasedGraphWalker::NodeBasedGraphWalker(
const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &node_coordinates,
const extractor::CompressedEdgeContainer &compressed_geometries,
const RestrictionMap &node_restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data)
: node_based_graph(node_based_graph), node_data_container(node_data_container),
node_coordinates(node_coordinates), compressed_geometries(compressed_geometries),
node_restriction_map(node_restriction_map), barrier_nodes(barrier_nodes),
turn_lanes_data(turn_lanes_data)
{
}
LengthLimitedCoordinateAccumulator::LengthLimitedCoordinateAccumulator(
const extractor::guidance::CoordinateExtractor &coordinate_extractor, const double max_length)
: accumulated_length(0), coordinate_extractor(coordinate_extractor), max_length(max_length)
{
}
bool LengthLimitedCoordinateAccumulator::terminate() { return accumulated_length >= max_length; }
// update the accumulator
void LengthLimitedCoordinateAccumulator::update(const NodeID from_node,
const EdgeID via_edge,
const NodeID /*to_node*/)
{
auto current_coordinates =
coordinate_extractor.GetForwardCoordinatesAlongRoad(from_node, via_edge);
const auto length =
util::coordinate_calculation::getLength(current_coordinates.begin(),
current_coordinates.end(),
util::coordinate_calculation::haversineDistance);
// in case we get too many coordinates, we limit them to our desired length
if (length + accumulated_length > max_length)
current_coordinates = coordinate_extractor.TrimCoordinatesToLength(
std::move(current_coordinates), max_length - accumulated_length);
coordinates.insert(coordinates.end(), current_coordinates.begin(), current_coordinates.end());
accumulated_length += length;
accumulated_length = std::min(accumulated_length, max_length);
}
// ---------------------------------------------------------------------------------
SelectRoadByNameOnlyChoiceAndStraightness::SelectRoadByNameOnlyChoiceAndStraightness(
const NameID desired_name_id, const bool requires_entry)
: desired_name_id(desired_name_id), requires_entry(requires_entry)
{
}
boost::optional<EdgeID> SelectRoadByNameOnlyChoiceAndStraightness::
operator()(const NodeID /*nid*/,
const EdgeID /*via_edge_id*/,
const IntersectionView &intersection,
const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container) const
{
BOOST_ASSERT(!intersection.empty());
const auto comparator = [&](const IntersectionViewData &lhs, const IntersectionViewData &rhs) {
// the score of an elemnt results in an ranking preferring valid entries, if required over
// invalid requested name_ids over non-requested narrow deviations over non-narrow
const auto score = [&](const IntersectionViewData &road) {
double result_score = 0;
// since angular deviation is limited by 0-180, we add 360 for invalid
if (requires_entry && !road.entry_allowed)
result_score += 360.;
// 180 for undesired name-ids
if (desired_name_id !=
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data)
.name_id)
result_score += 180;
return result_score + angularDeviation(road.angle, STRAIGHT_ANGLE);
};
return score(lhs) < score(rhs);
};
const auto min_element =
std::min_element(std::next(std::begin(intersection)), std::end(intersection), comparator);
if (min_element == intersection.end() || (requires_entry && !min_element->entry_allowed))
return {};
else
return (*min_element).eid;
}
// ---------------------------------------------------------------------------------
SelectStraightmostRoadByNameAndOnlyChoice::SelectStraightmostRoadByNameAndOnlyChoice(
const NameID desired_name_id,
const double initial_bearing,
const bool requires_entry,
const bool stop_on_ambiguous_turns)
: desired_name_id(desired_name_id), initial_bearing(initial_bearing),
requires_entry(requires_entry), stop_on_ambiguous_turns(stop_on_ambiguous_turns)
{
}
boost::optional<EdgeID> SelectStraightmostRoadByNameAndOnlyChoice::
operator()(const NodeID /*nid*/,
const EdgeID /*via_edge_id*/,
const IntersectionView &intersection,
const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container) const
{
BOOST_ASSERT(!intersection.empty());
if (intersection.size() == 1)
return {};
const auto comparator = [&](const IntersectionViewData &lhs, const IntersectionViewData &rhs) {
// the score of an elemnt results in an ranking preferring valid entries, if required over
// invalid requested name_ids over non-requested narrow deviations over non-narrow
const auto score = [&](const IntersectionViewData &road) {
double result_score = 0;
// since angular deviation is limited by 0-180, we add 360 for invalid
if (requires_entry && !road.entry_allowed)
result_score += 360.;
// 180 for undesired name-ids
if (desired_name_id !=
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data)
.name_id)
result_score += 180;
return result_score + angularDeviation(road.angle, STRAIGHT_ANGLE);
};
return score(lhs) < score(rhs);
};
const auto count_desired_name =
std::count_if(std::begin(intersection), std::end(intersection), [&](const auto &road) {
return node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data)
.name_id == desired_name_id;
});
if (count_desired_name > 2)
return {};
const auto min_element =
std::min_element(std::next(std::begin(intersection)), std::end(intersection), comparator);
const auto is_valid_choice = !requires_entry || min_element->entry_allowed;
if (!is_valid_choice)
return {};
// only road exiting or continuing in the same general direction
const auto has_valid_angle =
((intersection.size() == 2 ||
intersection.findClosestTurn(STRAIGHT_ANGLE) == min_element) &&
angularDeviation(min_element->angle, STRAIGHT_ANGLE) < NARROW_TURN_ANGLE) &&
angularDeviation(initial_bearing, min_element->bearing) < NARROW_TURN_ANGLE;
if (has_valid_angle)
return (*min_element).eid;
// in some cases, stronger turns are appropriate. We allow turns of just a bit over 90 degrees,
// if it's not a end of road situation. These angles come into play where roads split into dual
// carriage-ways.
//
// e - - f
// a - - - - b
// c - - d
// |
// g
//
// is technically
//
//
// a - - - - b (ce) - - (fg)
// |
// g
const auto is_only_choice_with_same_name =
count_desired_name <= 2 && // <= in case we come from a bridge, otherwise we have a u-turn
// and the outgoing edge
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(min_element->eid).annotation_data)
.name_id == desired_name_id &&
angularDeviation(min_element->angle, STRAIGHT_ANGLE) < 100; // don't do crazy turns
// do not allow major turns in the road, if other similar turns are present
// e.g.a turn at the end of the road:
//
// a - - a - - a - b - b
// |
// a - - a
// |
// c
//
// Such a turn can never be part of a merge
// We check if there is a similar turn to the other side. If such a turn exists, we consider the
// continuation of the road not possible
if (stop_on_ambiguous_turns &&
util::angularDeviation(STRAIGHT_ANGLE, min_element->angle) > GROUP_ANGLE)
{
auto opposite = intersection.findClosestTurn(util::bearing::reverse(min_element->angle));
auto opposite_deviation = util::angularDeviation(min_element->angle, opposite->angle);
// d - - - - c - - - -e
// |
// |
// a - - - - b
// from b-c onto min_element d with opposite side e
if (opposite_deviation > (180 - FUZZY_ANGLE_DIFFERENCE))
return {};
// e
// |
// a - - - - b - - - - -d
// doing a left turn while straight is a choice
auto const best = intersection.findClosestTurn(STRAIGHT_ANGLE);
if (util::angularDeviation(best->angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE)
return {};
}
return is_only_choice_with_same_name ? boost::optional<EdgeID>(min_element->eid) : boost::none;
}
// ---------------------------------------------------------------------------------
IntersectionFinderAccumulator::IntersectionFinderAccumulator(
const std::uint8_t hop_limit,
const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &node_coordinates,
const extractor::CompressedEdgeContainer &compressed_geometries,
const RestrictionMap &node_restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data)
: hops(0), hop_limit(hop_limit), node_based_graph(node_based_graph),
node_data_container(node_data_container), node_coordinates(node_coordinates),
compressed_geometries(compressed_geometries), node_restriction_map(node_restriction_map),
barrier_nodes(barrier_nodes), turn_lanes_data(turn_lanes_data)
{
}
bool IntersectionFinderAccumulator::terminate()
{
if (intersection.size() > 2 || hops == hop_limit)
{
hops = 0;
return true;
}
else
{
return false;
}
}
void IntersectionFinderAccumulator::update(const NodeID from_node,
const EdgeID via_edge,
const NodeID /*to_node*/)
{
++hops;
nid = from_node;
via_edge_id = via_edge;
intersection = intersection::getConnectedRoads<true>(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
{from_node, via_edge});
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -1,505 +0,0 @@
#include "extractor/guidance/roundabout_handler.hpp"
#include "extractor/guidance/constants.hpp"
#include "util/assert.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/guidance/name_announcements.hpp"
#include "util/log.hpp"
#include <algorithm>
#include <cmath>
#include <numeric>
#include <utility>
#include <boost/assert.hpp>
using osrm::extractor::guidance::getTurnDirection;
namespace osrm
{
namespace extractor
{
namespace guidance
{
RoundaboutHandler::RoundaboutHandler(
const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &coordinates,
const extractor::CompressedEdgeContainer &compressed_geometries,
const RestrictionMap &node_restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table)
: IntersectionHandler(node_based_graph,
node_data_container,
coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table),
coordinate_extractor(node_based_graph, compressed_geometries, coordinates)
{
}
bool RoundaboutHandler::canProcess(const NodeID from_nid,
const EdgeID via_eid,
const Intersection &intersection) const
{
const auto flags = getRoundaboutFlags(from_nid, via_eid, intersection);
if (!flags.on_roundabout && !flags.can_enter)
return false;
const auto roundabout_type = getRoundaboutType(node_based_graph.GetTarget(via_eid));
return roundabout_type != RoundaboutType::None;
}
Intersection RoundaboutHandler::
operator()(const NodeID from_nid, const EdgeID via_eid, Intersection intersection) const
{
const auto flags = getRoundaboutFlags(from_nid, via_eid, intersection);
const auto roundabout_type = getRoundaboutType(node_based_graph.GetTarget(via_eid));
// find the radius of the roundabout
return handleRoundabouts(roundabout_type,
via_eid,
flags.on_roundabout,
flags.can_exit_separately,
std::move(intersection));
}
detail::RoundaboutFlags RoundaboutHandler::getRoundaboutFlags(
const NodeID from_nid, const EdgeID via_eid, const Intersection &intersection) const
{
const auto &in_edge_data = node_based_graph.GetEdgeData(via_eid);
const auto &in_edge_class = in_edge_data.flags;
bool on_roundabout = in_edge_class.roundabout || in_edge_class.circular;
bool can_enter_roundabout = false;
bool can_exit_roundabout_separately = false;
const bool lhs =
node_data_container.GetAnnotation(in_edge_data.annotation_data).is_left_hand_driving;
const int step = lhs ? -1 : 1;
for (std::size_t cnt = 0, idx = lhs ? intersection.size() - 1 : 0; cnt < intersection.size();
++cnt, idx += step)
{
const auto &road = intersection[idx];
const auto &edge = node_based_graph.GetEdgeData(road.eid);
// only check actual outgoing edges
if (edge.reversed || !road.entry_allowed)
continue;
if (edge.flags.roundabout || edge.flags.circular)
{
can_enter_roundabout = true;
}
// Exiting roundabouts at an entry point is technically a data-modelling issue.
// This workaround handles cases in which an exit follows the entry.
// To correctly represent perceived exits, we only count exits leading to a
// separate vertex than the one we are coming from that are in the direction of
// the roundabout.
// The sorting of the angles represents a problem for left-sided driving, though.
// FIXME requires consideration of crossing the roundabout
else if (node_based_graph.GetTarget(road.eid) != from_nid && !can_enter_roundabout)
{
can_exit_roundabout_separately = true;
}
}
return {on_roundabout, can_enter_roundabout, can_exit_roundabout_separately};
}
// If we want to see a roundabout as a turn, the exits have to be distinct enough to be seen a
// dedicated turns. We are limiting it to four-way intersections with well distinct bearings.
// All entry/roads and exit roads have to be simple. Not segregated roads.
// Processing segregated roads would technically require an angle of the turn to be available
// in postprocessing since we correct the turn-angle in turn-generaion.
bool RoundaboutHandler::qualifiesAsRoundaboutIntersection(
const std::unordered_set<NodeID> &roundabout_nodes) const
{
const bool has_limited_size = roundabout_nodes.size() <= 4;
if (!has_limited_size)
return false;
const bool simple_exits =
roundabout_nodes.end() ==
std::find_if(roundabout_nodes.begin(), roundabout_nodes.end(), [this](const NodeID node) {
return (node_based_graph.GetOutDegree(node) > 3);
});
if (!simple_exits)
return false;
// Find all exit bearings. Only if they are well distinct (at least 60 degrees between
// them), we allow a roundabout turn
const auto exit_bearings = [this, &roundabout_nodes]() {
std::vector<double> result;
for (const auto node : roundabout_nodes)
{
// given the reverse edge and the forward edge on a roundabout, a simple entry/exit
// can only contain a single further road
for (const auto edge : node_based_graph.GetAdjacentEdgeRange(node))
{
const auto &edge_data = node_based_graph.GetEdgeData(edge);
if (edge_data.flags.roundabout || edge_data.flags.circular)
continue;
// there is a single non-roundabout edge
const auto src_coordinate = node_coordinates[node];
const auto edge_range = node_based_graph.GetAdjacentEdgeRange(node);
const auto number_of_lanes_at_intersection = std::accumulate(
edge_range.begin(),
edge_range.end(),
std::uint8_t{0},
[this](const auto current_max, const auto current_eid) {
return std::max(current_max,
node_based_graph.GetEdgeData(current_eid)
.flags.road_classification.GetNumberOfLanes());
});
const auto next_coordinate =
coordinate_extractor.GetCoordinateAlongRoad(node,
edge,
false,
node_based_graph.GetTarget(edge),
number_of_lanes_at_intersection);
result.push_back(
util::coordinate_calculation::bearing(src_coordinate, next_coordinate));
// OSM data sometimes contains duplicated nodes with identical coordinates, or
// because of coordinate precision rounding, end up at the same coordinate.
// It's impossible to calculate a bearing between these, so we log a warning
// that the data should be checked.
// The bearing calculation should return 0 in these cases, which may not be correct,
// but is at least not random.
if (src_coordinate == next_coordinate)
{
util::Log(logDEBUG) << "Zero length segment at " << next_coordinate
<< " could cause invalid roundabout exit bearings";
BOOST_ASSERT(std::abs(result.back()) <= 0.1);
}
break;
}
}
std::sort(result.begin(), result.end());
return result;
}();
const bool well_distinct_bearings = [](const std::vector<double> &bearings) {
for (std::size_t bearing_index = 0; bearing_index < bearings.size(); ++bearing_index)
{
const double difference =
std::abs(bearings[(bearing_index + 1) % bearings.size()] - bearings[bearing_index]);
// we assume non-narrow turns as well distinct
if (difference <= NARROW_TURN_ANGLE)
return false;
}
return true;
}(exit_bearings);
return well_distinct_bearings;
}
RoundaboutType RoundaboutHandler::getRoundaboutType(const NodeID nid) const
{
std::unordered_set<unsigned> roundabout_name_ids;
std::unordered_set<unsigned> connected_names;
const auto getNextOnRoundabout = [this, &roundabout_name_ids, &connected_names](
const NodeID node, const bool roundabout, const bool circular) {
BOOST_ASSERT(roundabout != circular);
EdgeID continue_edge = SPECIAL_EDGEID;
for (const auto edge_id : node_based_graph.GetAdjacentEdgeRange(node))
{
const auto &edge = node_based_graph.GetEdgeData(edge_id);
const auto &edge_data = node_data_container.GetAnnotation(edge.annotation_data);
if (!edge.reversed && (edge.flags.circular == circular) &&
(edge.flags.roundabout == roundabout))
{
if (SPECIAL_EDGEID != continue_edge)
{
// fork in roundabout
return SPECIAL_EDGEID;
}
const auto &edge_name_empty = name_table.GetNameForID(edge_data.name_id).empty();
if (!edge_name_empty)
{
const auto announce = [&](unsigned id) {
return util::guidance::requiresNameAnnounced(
id, edge_data.name_id, name_table, street_name_suffix_table);
};
if (std::all_of(begin(roundabout_name_ids), end(roundabout_name_ids), announce))
roundabout_name_ids.insert(edge_data.name_id);
}
continue_edge = edge_id;
}
else if (!edge.flags.roundabout && !edge.flags.circular)
{
// remember all connected road names
connected_names.insert(edge_data.name_id);
}
}
return continue_edge;
};
// this value is a hard abort to deal with potential self-loops
const auto countRoundaboutFlags = [&](const NodeID at_node) {
// FIXME: this would be nicer as boost::count_if, but our integer range does not support
// these range based handlers
std::size_t count = 0;
for (const auto edge : node_based_graph.GetAdjacentEdgeRange(at_node))
{
const auto &edge_data = node_based_graph.GetEdgeData(edge);
if (edge_data.flags.roundabout || edge_data.flags.circular)
count++;
}
return count;
};
const auto getEdgeLength = [&](const NodeID source_node, EdgeID eid) {
double length = 0.;
auto last_coord = node_coordinates[source_node];
const auto &edge_bucket = compressed_geometries.GetBucketReference(eid);
for (const auto &compressed_edge : edge_bucket)
{
const auto next_coord = node_coordinates[compressed_edge.node_id];
length += util::coordinate_calculation::haversineDistance(last_coord, next_coord);
last_coord = next_coord;
}
return length;
};
// the roundabout radius has to be the same for all locations we look at it from
// to guarantee this, we search the full roundabout for its vertices
// and select the three smallest ids
std::unordered_set<NodeID> roundabout_nodes;
double roundabout_length = 0.;
NodeID last_node = nid;
// cannot be find_if, as long as adjacent edge range does not work.
bool roundabout = false, circular = false;
for (const auto eid : node_based_graph.GetAdjacentEdgeRange(nid))
{
const auto data = node_based_graph.GetEdgeData(eid).flags;
roundabout |= data.roundabout;
circular |= data.circular;
}
if (roundabout == circular)
return RoundaboutType::None;
while (0 == roundabout_nodes.count(last_node))
{
// only count exits/entry locations
if (node_based_graph.GetOutDegree(last_node) > 2)
roundabout_nodes.insert(last_node);
// detect invalid/complex roundabout taggings
if (countRoundaboutFlags(last_node) != 2)
return RoundaboutType::None;
const auto eid = getNextOnRoundabout(last_node, roundabout, circular);
if (eid == SPECIAL_EDGEID)
{
return RoundaboutType::None;
}
roundabout_length += getEdgeLength(last_node, eid);
last_node = node_based_graph.GetTarget(eid);
if (last_node == nid)
break;
}
// a roundabout that cannot be entered or exited should not get here
if (roundabout_nodes.size() == 0)
return RoundaboutType::None;
// More a traffic loop than anything else, currently treated as roundabout turn
if (roundabout_nodes.size() == 1 && roundabout)
{
return RoundaboutType::RoundaboutIntersection;
}
const double radius = roundabout_length / (2 * M_PI);
// Looks like a rotary: large roundabout with dedicated name
// do we have a dedicated name for the rotary, if not its a roundabout
// This function can theoretically fail if the roundabout name is partly
// used with a reference and without. This will be fixed automatically
// when we handle references separately or if the useage is more consistent
const auto is_rotary = (1 == roundabout_name_ids.size()) &&
(circular || //
((0 == connected_names.count(*roundabout_name_ids.begin())) && //
(radius > MAX_ROUNDABOUT_RADIUS)));
if (is_rotary)
return RoundaboutType::Rotary;
// circular intersections need to be rotaries
if (circular)
return RoundaboutType::None;
// Looks like an intersection: four ways and turn angles are easy to distinguish
const auto is_roundabout_intersection = qualifiesAsRoundaboutIntersection(roundabout_nodes) &&
radius < MAX_ROUNDABOUT_INTERSECTION_RADIUS;
if (is_roundabout_intersection)
return RoundaboutType::RoundaboutIntersection;
// Not a special case, just a normal roundabout
return RoundaboutType::Roundabout;
}
Intersection RoundaboutHandler::handleRoundabouts(const RoundaboutType roundabout_type,
const EdgeID via_eid,
const bool on_roundabout,
const bool can_exit_roundabout_separately,
Intersection intersection) const
{
NodeID node_at_center_of_intersection = node_based_graph.GetTarget(via_eid);
const auto &in_edge_data = node_based_graph.GetEdgeData(via_eid);
const bool lhs =
node_data_container.GetAnnotation(in_edge_data.annotation_data).is_left_hand_driving;
const int step = lhs ? -1 : 1;
if (on_roundabout)
{
// Shoule hopefully have only a single exit and continue
// at least for cars. How about bikes?
for (std::size_t cnt = 0, idx = lhs ? intersection.size() - 1 : 0;
cnt < intersection.size();
++cnt, idx += step)
{
auto &road = intersection[idx];
auto &turn = road;
const auto &out_data = node_based_graph.GetEdgeData(road.eid).flags;
;
if (out_data.roundabout || out_data.circular)
{
// TODO can forks happen in roundabouts? E.g. required lane changes
if (1 == node_based_graph.GetDirectedOutDegree(node_at_center_of_intersection))
{
// No turn possible.
if (intersection.size() == 2)
turn.instruction = TurnInstruction::NO_TURN();
else
{
turn.instruction.type =
TurnType::Suppressed; // make sure to report intersection
turn.instruction.direction_modifier = getTurnDirection(turn.angle);
}
}
else
{
// Check if there is a non-service exit
const auto has_non_ignorable_exit = [&]() {
for (const auto eid :
node_based_graph.GetAdjacentEdgeRange(node_at_center_of_intersection))
{
const auto &leaving_edge = node_based_graph.GetEdgeData(eid);
if (!leaving_edge.reversed && !leaving_edge.flags.roundabout &&
!leaving_edge.flags.circular &&
!leaving_edge.flags.road_classification.IsLowPriorityRoadClass())
return true;
}
return false;
};
// Count normal exits and service roads, if the roundabout is a service road
// itself
if (out_data.road_classification.IsLowPriorityRoadClass() ||
has_non_ignorable_exit())
{
turn.instruction = TurnInstruction::REMAIN_ROUNDABOUT(
roundabout_type, getTurnDirection(turn.angle));
}
else
{ // Suppress exit instructions from normal roundabouts to service roads
turn.instruction = {TurnType::Suppressed, getTurnDirection(turn.angle)};
}
}
}
else
{
turn.instruction =
TurnInstruction::EXIT_ROUNDABOUT(roundabout_type, getTurnDirection(turn.angle));
}
}
return intersection;
}
else
{
bool crossing_roundabout = false;
for (std::size_t cnt = 0, idx = lhs ? intersection.size() - 1 : 0;
cnt < intersection.size();
++cnt, idx += step)
{
auto &turn = intersection[idx];
const auto &out_data = node_based_graph.GetEdgeData(turn.eid).flags;
// A roundabout consists of exactly two roads at an intersection. by toggeling this
// flag, we can switch between roads crossing the roundabout and roads that are on the
// same side as via_eid.
if (out_data.roundabout || out_data.circular)
crossing_roundabout = !crossing_roundabout;
if (!turn.entry_allowed)
continue;
if (out_data.roundabout || out_data.circular)
{
if (can_exit_roundabout_separately)
turn.instruction = TurnInstruction::ENTER_ROUNDABOUT_AT_EXIT(
roundabout_type, getTurnDirection(turn.angle));
else
turn.instruction = TurnInstruction::ENTER_ROUNDABOUT(
roundabout_type, getTurnDirection(turn.angle));
}
else
{
// Distinguish between throughabouts and entering a roundabout to directly exit: In
// case of a throughabout, both enter and exit do not show roundabout tags (as we
// already have checked, when arriving here) and the enter/exit are nearly straight
// and on different sides of the roundabouts
if (util::angularDeviation(turn.angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE &&
crossing_roundabout)
{
turn.instruction =
getInstructionForObvious(intersection.size(),
via_eid,
isThroughStreet(idx,
intersection,
node_based_graph,
node_data_container,
name_table,
street_name_suffix_table),
turn);
}
else
{
turn.instruction = TurnInstruction::ENTER_AND_EXIT_ROUNDABOUT(
roundabout_type, getTurnDirection(turn.angle));
}
}
}
}
return intersection;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -1,243 +0,0 @@
#include "extractor/guidance/segregated_intersection_classification.hpp"
#include "extractor/guidance/coordinate_extractor.hpp"
#include "extractor/node_based_graph_factory.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/name_table.hpp"
namespace osrm
{
namespace extractor
{
namespace guidance
{
struct EdgeInfo
{
NodeID node;
util::StringView name;
// 0 - outgoing (forward), 1 - incoming (reverse), 2 - both outgoing and incoming
int direction;
ClassData road_class;
RoadPriorityClass::Enum road_priority_class;
struct LessName
{
bool operator()(EdgeInfo const &e1, EdgeInfo const &e2) const { return e1.name < e2.name; }
};
};
bool IsSegregated(std::vector<EdgeInfo> v1,
std::vector<EdgeInfo> v2,
EdgeInfo const &current,
double edgeLength)
{
if (v1.size() < 2 || v2.size() < 2)
return false;
auto const sort_by_name_fn = [](std::vector<EdgeInfo> &v) {
std::sort(v.begin(), v.end(), EdgeInfo::LessName());
};
sort_by_name_fn(v1);
sort_by_name_fn(v2);
// Internal edge with the name should be connected with any other neibour edge with the same
// name, e.g. isolated edge with unique name is not segregated.
// b - 'b' road continues here
// |
// - - a - |
// b - segregated edge
// - - a - |
if (!current.name.empty())
{
auto const findNameFn = [&current](std::vector<EdgeInfo> const &v) {
return std::binary_search(v.begin(), v.end(), current, EdgeInfo::LessName());
};
if (!findNameFn(v1) && !findNameFn(v2))
return false;
}
// set_intersection like routine to get equal result pairs
std::vector<std::pair<EdgeInfo const *, EdgeInfo const *>> commons;
auto i1 = v1.begin();
auto i2 = v2.begin();
while (i1 != v1.end() && i2 != v2.end())
{
if (i1->name == i2->name)
{
if (!i1->name.empty())
commons.push_back(std::make_pair(&(*i1), &(*i2)));
++i1;
++i2;
}
else if (i1->name < i2->name)
++i1;
else
++i2;
}
if (commons.size() < 2)
return false;
auto const check_equal_class = [](std::pair<EdgeInfo const *, EdgeInfo const *> const &e) {
// Or (e.first->road_class & e.second->road_class != 0)
return e.first->road_class == e.second->road_class;
};
size_t equal_class_count = 0;
for (auto const &e : commons)
if (check_equal_class(e))
++equal_class_count;
if (equal_class_count < 2)
return false;
auto const get_length_threshold = [](EdgeInfo const *e) {
switch (e->road_priority_class)
{
case RoadPriorityClass::MOTORWAY:
case RoadPriorityClass::TRUNK:
return 30.0;
case RoadPriorityClass::PRIMARY:
return 20.0;
case RoadPriorityClass::SECONDARY:
case RoadPriorityClass::TERTIARY:
return 10.0;
default:
return 5.0;
}
};
double threshold = std::numeric_limits<double>::max();
for (auto const &e : commons)
threshold =
std::min(threshold, get_length_threshold(e.first) + get_length_threshold(e.second));
return edgeLength <= threshold;
}
std::unordered_set<EdgeID> findSegregatedNodes(const NodeBasedGraphFactory &factory,
const util::NameTable &names)
{
auto const &graph = factory.GetGraph();
auto const &annotation = factory.GetAnnotationData();
CoordinateExtractor coordExtractor(
graph, factory.GetCompressedEdges(), factory.GetCoordinates());
auto const get_edge_length = [&](NodeID from_node, EdgeID edgeID, NodeID to_node) {
auto const geom = coordExtractor.GetCoordinatesAlongRoad(from_node, edgeID, false, to_node);
double length = 0.0;
for (size_t i = 1; i < geom.size(); ++i)
{
length += util::coordinate_calculation::haversineDistance(geom[i - 1], geom[i]);
}
return length;
};
auto const get_edge_info = [&](NodeID node, auto const &edgeData) -> EdgeInfo {
/// @todo Make string normalization/lowercase/trim for comparison ...
auto const id = annotation[edgeData.annotation_data].name_id;
BOOST_ASSERT(id != INVALID_NAMEID);
auto const name = names.GetNameForID(id);
return {node,
name,
edgeData.reversed ? 1 : 0,
annotation[edgeData.annotation_data].classes,
edgeData.flags.road_classification.GetClass()};
};
auto const collect_edge_info_fn = [&](auto const &edges1, NodeID node2) {
std::vector<EdgeInfo> info;
for (auto const &e : edges1)
{
NodeID const target = graph.GetTarget(e);
if (target == node2)
continue;
info.push_back(get_edge_info(target, graph.GetEdgeData(e)));
}
if (info.empty())
return info;
std::sort(info.begin(), info.end(), [](EdgeInfo const &e1, EdgeInfo const &e2) {
return e1.node < e2.node;
});
// Merge equal infos with correct direction.
auto curr = info.begin();
auto next = curr;
while (++next != info.end())
{
if (curr->node == next->node)
{
BOOST_ASSERT(curr->name == next->name);
BOOST_ASSERT(curr->road_class == next->road_class);
BOOST_ASSERT(curr->direction != next->direction);
curr->direction = 2;
}
else
curr = next;
}
info.erase(
std::unique(info.begin(),
info.end(),
[](EdgeInfo const &e1, EdgeInfo const &e2) { return e1.node == e2.node; }),
info.end());
return info;
};
auto const isSegregatedFn = [&](auto const &edgeData,
auto const &edges1,
NodeID node1,
auto const &edges2,
NodeID node2,
double edgeLength) {
return IsSegregated(collect_edge_info_fn(edges1, node2),
collect_edge_info_fn(edges2, node1),
get_edge_info(node1, edgeData),
edgeLength);
};
std::unordered_set<EdgeID> segregated_edges;
for (NodeID sourceID = 0; sourceID < graph.GetNumberOfNodes(); ++sourceID)
{
auto const sourceEdges = graph.GetAdjacentEdgeRange(sourceID);
for (EdgeID edgeID : sourceEdges)
{
auto const &edgeData = graph.GetEdgeData(edgeID);
if (edgeData.reversed)
continue;
NodeID const targetID = graph.GetTarget(edgeID);
auto const targetEdges = graph.GetAdjacentEdgeRange(targetID);
double const length = get_edge_length(sourceID, edgeID, targetID);
if (isSegregatedFn(edgeData, sourceEdges, sourceID, targetEdges, targetID, length))
segregated_edges.insert(edgeID);
}
}
return segregated_edges;
}
}
}
}
-872
View File
@@ -1,872 +0,0 @@
#include "extractor/guidance/sliproad_handler.hpp"
#include "extractor/guidance/constants.hpp"
#include "util/assert.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/guidance/name_announcements.hpp"
#include <algorithm>
#include <cmath>
#include <iterator>
#include <limits>
#include <boost/assert.hpp>
using osrm::extractor::guidance::getTurnDirection;
using osrm::util::angularDeviation;
namespace osrm
{
namespace extractor
{
namespace guidance
{
SliproadHandler::SliproadHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &node_coordinates,
const extractor::CompressedEdgeContainer &compressed_geometries,
const RestrictionMap &node_restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table)
: IntersectionHandler(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table),
coordinate_extractor(node_based_graph, compressed_geometries, node_coordinates)
{
}
// The intersection has to connect a Sliproad, see the example scenario below:
// Intersection at `d`: Sliproad `bd` connecting `cd` and the road starting at `d`.
bool SliproadHandler::canProcess(const NodeID /*nid*/,
const EdgeID /*via_eid*/,
const Intersection &intersection) const
{
return intersection.size() > 2;
}
// Detect sliproad b-d in the following example:
//
// .
// e
// .
// .
// a ... b .... c .
// ` .
// ` .
// ` .
// d
// .
//
// ^ a nid
// ^ ab source_edge_id
// ^ b intersection
Intersection SliproadHandler::
operator()(const NodeID /*nid*/, const EdgeID source_edge_id, Intersection intersection) const
{
BOOST_ASSERT(intersection.size() > 2);
// Potential splitting / start of a Sliproad (b)
auto intersection_node_id = node_based_graph.GetTarget(source_edge_id);
// Road index prefering non-sliproads (bc)
auto obvious = getObviousIndexWithSliproads(source_edge_id, intersection, intersection_node_id);
if (!obvious)
{
return intersection;
}
// Potential non-sliproad road (bc), leading to the intersection (c) the Sliproad (bd) shortcuts
const auto &main_road = intersection[*obvious];
// The road leading to the intersection (bc) has to continue from our source
if (!roadContinues(source_edge_id, main_road.eid))
{
return intersection;
}
// Link-check for (bc) and later on (cd) which both are getting shortcutted by Sliproad
const auto is_potential_link = [this, main_road](const ConnectedRoad &road) {
if (!road.entry_allowed)
{
return false;
}
// Prevent from starting in or going onto a roundabout
auto onto_roundabout = hasRoundaboutType(road.instruction);
if (onto_roundabout)
{
return false;
}
// Narrow turn angle for road (bd) and guard against data issues (overlapping roads)
auto is_narrow = angularDeviation(road.angle, STRAIGHT_ANGLE) <= 2 * NARROW_TURN_ANGLE;
auto same_angle = angularDeviation(main_road.angle, road.angle) //
<= std::numeric_limits<double>::epsilon();
if (!is_narrow || same_angle)
{
return false;
}
const auto &road_data = node_based_graph.GetEdgeData(road.eid).flags;
auto is_roundabout = road_data.roundabout;
if (is_roundabout)
{
return false;
}
return true;
};
if (!std::any_of(begin(intersection), end(intersection), is_potential_link))
{
return intersection;
}
// If the intersection is too far away, don't bother continuing
if (nextIntersectionIsTooFarAway(intersection_node_id, main_road.eid))
{
return intersection;
}
// Try to find the intersection at (c) which the Sliproad shortcuts
const auto main_road_intersection = getNextIntersection(intersection_node_id, main_road.eid);
if (!main_road_intersection)
{
return intersection;
}
if (main_road_intersection->intersection.isDeadEnd())
{
return intersection;
}
// If we are at a traffic loop at the end of a road, don't consider it a sliproad
if (intersection_node_id == main_road_intersection->node)
{
return intersection;
}
std::vector<NameID> target_road_name_ids;
target_road_name_ids.reserve(main_road_intersection->intersection.size());
for (const auto &road : main_road_intersection->intersection)
{
const auto target_annotation_id = node_based_graph.GetEdgeData(road.eid).annotation_data;
const auto &target_data = node_data_container.GetAnnotation(target_annotation_id);
target_road_name_ids.push_back(target_data.name_id);
}
auto sliproad_found = false;
// For all roads at the main intersection except the UTurn road: check Sliproad scenarios.
for (std::size_t road_index = 1, last = intersection.size(); road_index < last; ++road_index)
{
const auto index_left_of_main_road = (*obvious - 1) % intersection.size();
const auto index_right_of_main_road = (*obvious + 1) % intersection.size();
// Be strict and require the Sliproad to be either left or right of the main road.
if (road_index != index_left_of_main_road && road_index != index_right_of_main_road)
continue;
auto &sliproad = intersection[road_index]; // this is what we're checking and assigning to
const auto &sliproad_edge_data = node_based_graph.GetEdgeData(sliproad.eid);
// Intersection is orderd: 0 is UTurn, then from sharp right to sharp left.
// We already have an obvious index (bc) for going straight-ish.
const auto is_right_sliproad_turn = road_index < *obvious;
const auto is_left_sliproad_turn = road_index > *obvious;
// Road at the intersection the main road leads onto which the sliproad arrives onto
const auto crossing_road = [&] {
if (is_left_sliproad_turn)
return main_road_intersection->intersection.getLeftmostRoad();
BOOST_ASSERT_MSG(is_right_sliproad_turn,
"Sliproad is neither a left nor right of obvious main road");
return main_road_intersection->intersection.getRightmostRoad();
}();
const auto &crossing_road_data = node_based_graph.GetEdgeData(crossing_road.eid);
// The crossing road at the main road's intersection must not be incoming-only
if (crossing_road_data.reversed)
{
continue;
}
// Discard service and other low priority roads - never Sliproad candidate
if (sliproad_edge_data.flags.road_classification.IsLowPriorityRoadClass())
{
continue;
}
// Incoming-only can never be a Sliproad
if (sliproad_edge_data.reversed)
{
continue;
}
// This is what we know so far:
//
// .
// e
// .
// .
// a ... b .... c . < `main_road_intersection` is intersection at `c`
// ` .
// ` .
// ` .
// d < `target_intersection` is intersection at `d`
// . `sliproad_edge_target` is node `d`
// e
//
//
// ^ `sliproad` is `bd`
// ^ `intersection` is intersection at `b`
if (!is_potential_link(sliproad))
{
continue;
}
// The Sliproad graph edge - in the following we use the graph walker to
// adjust this edge forward jumping over artificial intersections.
auto sliproad_edge = sliproad.eid;
// Starting out at the intersection and going onto the Sliproad we skip artificial
// degree two intersections and limit the max hop count in doing so.
IntersectionFinderAccumulator intersection_finder{10,
node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data};
const SkipTrafficSignalBarrierRoadSelector road_selector{};
(void)graph_walker.TraverseRoad(intersection_node_id, // start node
sliproad_edge, // onto edge
intersection_finder, // accumulator
road_selector); // selector
sliproad_edge = intersection_finder.via_edge_id;
const auto target_intersection = intersection_finder.intersection;
if (target_intersection.isDeadEnd())
continue;
const auto find_valid = [](const IntersectionView &view) {
// according to our current sliproad idea, there should only be one valid turn
auto itr = std::find_if(
view.begin(), view.end(), [](const auto &road) { return road.entry_allowed; });
BOOST_ASSERT(itr != view.end());
return itr;
};
// require all to be same mode, don't allow changes
if (!allSameMode(source_edge_id, sliproad.eid, find_valid(target_intersection)->eid))
continue;
// Constrain the sliproad's target intersection to 1 or 2 sliproads, outgoing road
// and incoming one from the main intersection
if (target_intersection.size() < 3 || target_intersection.size() > 4)
{
continue;
}
const NodeID sliproad_edge_target = node_based_graph.GetTarget(sliproad_edge);
// Distinct triangle nodes `bcd`
if (intersection_node_id == main_road_intersection->node ||
intersection_node_id == sliproad_edge_target ||
main_road_intersection->node == sliproad_edge_target)
{
continue;
}
if (target_intersection.size() == 4)
{
// Handle target intersections at `d` with 4 roads
//
// | `main_road_intersection` is intersection at `c`
// v
// a ... b .... c .... e <- fo
// ` . '
// ` . '
// ` . '
// d < `target_intersection` is intersection at `d`
// |
// Conditions for road `bd` to be a sliproad:
// - target_intersection at `d` has 4 roads
// - main_road_intersection at `c` has at least 3 roads
// - target nodes of `db` and `cd` roads is the same node `d`
// - target nodes of `ce` and `de` roads is the same node `e`
// - angle `bde` is sharp
// Check `c` has at least 3 roads at `c` and roads `bd` and `cd` share the node `d`
if (main_road_intersection->intersection.size() < 3 ||
sliproad_edge_target != node_based_graph.GetTarget(crossing_road.eid))
{
continue;
}
// Find a road at `d` that shares the same node `e` with `ce` and ∠ `bde` is sharp
auto next_to_crossing_idx =
is_left_sliproad_turn ? main_road_intersection->intersection.size() - 2 : 2;
auto next_to_crossing_road = main_road_intersection->intersection[next_to_crossing_idx];
auto next_to_crossing_node = node_based_graph.GetTarget(next_to_crossing_road.eid);
auto found_common_node = std::find_if(
begin(target_intersection), end(target_intersection), [&](const auto &road) {
if (next_to_crossing_node == node_based_graph.GetTarget(road.eid))
{
auto direction = getTurnDirection(road.angle);
return direction == DirectionModifier::SharpRight ||
direction == DirectionModifier::SharpLeft;
}
return false;
});
if (found_common_node == target_intersection.end())
continue;
}
// If the sliproad candidate is a through street, we cannot handle it as a sliproad.
auto sliproad_in_target_intersection =
std::find_if(begin(target_intersection),
end(target_intersection),
[&](const auto &road) { return road.eid == sliproad_edge; });
if (sliproad_in_target_intersection != target_intersection.end())
{
auto index_of_sliproad_in_target_intersection =
sliproad_in_target_intersection - target_intersection.begin();
if (isThroughStreet<IntersectionView>(index_of_sliproad_in_target_intersection,
target_intersection,
node_based_graph,
node_data_container,
name_table,
street_name_suffix_table))
{
continue;
}
}
// The turn off of the Sliproad has to be obvious and a narrow turn and must not be a
// roundabout
{
const auto index = findObviousTurn(sliproad_edge, target_intersection);
if (index == 0)
{
continue;
}
const auto onto = target_intersection[index];
const auto angle_deviation = angularDeviation(onto.angle, STRAIGHT_ANGLE);
const auto is_narrow_turn = angle_deviation <= 2 * NARROW_TURN_ANGLE;
if (!is_narrow_turn)
{
continue;
}
const auto &onto_data = node_based_graph.GetEdgeData(onto.eid).flags;
if (onto_data.roundabout)
{
continue;
}
}
// Check for curvature. Depending on the turn's direction at `c`. Scenario for right turn:
//
// a ... b .... c . a ... b .... c . a ... b .... c .
// ` . ` . . . .
// ` . . . . .
// ` . .. . .
// d d . d
//
// Sliproad Not a Sliproad
{
const NodeID start = intersection_node_id; // b
const EdgeID edge = sliproad_edge; // bd
const auto coords = coordinate_extractor.GetForwardCoordinatesAlongRoad(start, edge);
// due to filtering of duplicated coordinates, we can end up with empty segments.
// this can only happen as long as
// https://github.com/Project-OSRM/osrm-backend/issues/3470 persists
if (coords.size() < 2)
continue;
BOOST_ASSERT(coords.size() >= 2);
// Now keep start and end coordinate fix and check for curvature
const auto start_coord = coords.front();
const auto end_coord = coords.back();
const auto first = std::begin(coords) + 1;
const auto last = std::end(coords) - 1;
auto snuggles = false;
using namespace util::coordinate_calculation;
// In addition, if it's a right/left turn we expect the rightmost/leftmost
// turn at `c` to be more than a minimal angle (40°) for a Sliproad scenario.
double deviation_from_straight = 0;
if (is_right_sliproad_turn)
{
snuggles = std::all_of(first, last, [=](auto each) { //
return !isCCW(start_coord, each, end_coord);
});
const auto rightmost = main_road_intersection->intersection.getRightmostRoad();
deviation_from_straight = angularDeviation(rightmost.angle, STRAIGHT_ANGLE);
}
else if (is_left_sliproad_turn)
{
snuggles = std::all_of(first, last, [=](auto each) { //
return isCCW(start_coord, each, end_coord);
});
const auto leftmost = main_road_intersection->intersection.getLeftmostRoad();
deviation_from_straight = angularDeviation(leftmost.angle, STRAIGHT_ANGLE);
}
// The data modelling for small Sliproads is not reliable enough.
// Only check for curvature and ~90 degree when it makes sense to do so.
const constexpr auto MIN_LENGTH = 3.;
const auto length = haversineDistance(node_coordinates[intersection_node_id],
node_coordinates[main_road_intersection->node]);
const double minimal_crossroad_angle_of_intersection = 40.;
if (length >= MIN_LENGTH)
{
if (!snuggles)
{
continue;
}
// Check sliproads with skew main intersections
if (deviation_from_straight > 180. - minimal_crossroad_angle_of_intersection &&
!node_based_graph.GetEdgeData(sliproad.eid)
.flags.road_classification.IsLinkClass())
{
continue;
}
}
}
// Check for area under triangle `bdc`.
//
// a ... b .... c .
// ` .
// ` .
// ` .
// d
//
const auto area_threshold =
std::pow(scaledThresholdByRoadClass(MAX_SLIPROAD_THRESHOLD,
sliproad_edge_data.flags.road_classification),
2.);
if (!isValidSliproadArea(area_threshold,
intersection_node_id,
main_road_intersection->node,
sliproad_edge_target))
{
continue;
}
// Check all roads at `d` if one is connected to `c`, is so `bd` is Sliproad.
for (const auto &candidate_road : target_intersection)
{
const auto &candidate_data = node_data_container.GetAnnotation(
node_based_graph.GetEdgeData(candidate_road.eid).annotation_data);
// Name mismatch: check roads at `c` and `d` for same name
const auto name_mismatch = [&](const NameID road_name_id) {
return util::guidance::requiresNameAnnounced(road_name_id, //
candidate_data.name_id, //
name_table, //
street_name_suffix_table); //
};
const auto candidate_road_name_mismatch = std::all_of(begin(target_road_name_ids), //
end(target_road_name_ids), //
name_mismatch); //
if (candidate_road_name_mismatch)
{
continue;
}
// Check if main road -> sliproad (non-link) -> candidate road requires two name
// announcements then don't suppress one announcement via sliproad handler
const auto main_road_name_id =
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(main_road.eid).annotation_data)
.name_id;
const auto main_road_name_empty = name_table.GetNameForID(main_road_name_id).empty();
const auto &sliproad_annotation =
node_data_container.GetAnnotation(sliproad_edge_data.annotation_data);
const auto sliproad_name_empty =
name_table.GetNameForID(sliproad_annotation.name_id).empty();
const auto candidate_road_name_empty =
name_table.GetNameForID(candidate_data.name_id).empty();
if (!sliproad_edge_data.flags.road_classification.IsLinkClass() &&
!sliproad_name_empty && !main_road_name_empty && !candidate_road_name_empty &&
util::guidance::requiresNameAnnounced(main_road_name_id,
sliproad_annotation.name_id,
name_table,
street_name_suffix_table) &&
util::guidance::requiresNameAnnounced(sliproad_annotation.name_id,
candidate_data.name_id,
name_table,
street_name_suffix_table))
{
continue;
}
// If the Sliproad `bd` is a link, `bc` and `cd` must not be links.
if (!isValidSliproadLink(sliproad, main_road, candidate_road))
{
continue;
}
// Check that the cross-road `candidate_road_target` that starts at `d` ends at
// main intersection node `c` or has a common node `e` with a cross-road from `c`
// a ... b .... c a ... b .... c
// ` . ` .
// ` . ` e...
// ` . ` .
// d d
//
const auto candidate_road_target = node_based_graph.GetTarget(candidate_road.eid);
if ((candidate_road_target == main_road_intersection->node) ||
(candidate_road_target == node_based_graph.GetTarget(crossing_road.eid) &&
util::bearing::angleBetween(candidate_road.bearing, crossing_road.bearing) <
FUZZY_ANGLE_DIFFERENCE &&
(getTurnDirection(candidate_road.angle) == DirectionModifier::SharpRight ||
getTurnDirection(candidate_road.angle) == DirectionModifier::SharpLeft)))
{
sliproad.instruction.type = TurnType::Sliproad;
sliproad_found = true;
break;
}
else
{
const auto skip_traffic_light_intersection = intersection::getConnectedRoads<false>(
node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
{node_based_graph.GetTarget(sliproad_edge), candidate_road.eid});
if (skip_traffic_light_intersection.isTrafficSignalOrBarrier() &&
node_based_graph.GetTarget(skip_traffic_light_intersection[1].eid) ==
main_road_intersection->node)
{
sliproad.instruction.type = TurnType::Sliproad;
sliproad_found = true;
break;
}
}
}
}
// Now in case we found a Sliproad and assigned the corresponding type to the road,
// it could be that the intersection from which the Sliproad splits off was a Fork before.
// In those cases the obvious non-Sliproad is now obvious and we discard the Fork turn type.
if (sliproad_found && main_road.instruction.type == TurnType::Fork)
{
const auto &main_data = node_based_graph.GetEdgeData(main_road.eid);
const auto &main_annotation = node_data_container.GetAnnotation(main_data.annotation_data);
if (isSameName(source_edge_id, main_road.eid))
{
if (angularDeviation(main_road.angle, STRAIGHT_ANGLE) < 5)
intersection[*obvious].instruction.type = TurnType::Suppressed;
else
intersection[*obvious].instruction.type = TurnType::Continue;
intersection[*obvious].instruction.direction_modifier =
getTurnDirection(intersection[*obvious].angle);
}
else if (!name_table.GetNameForID(main_annotation.name_id).empty())
{
intersection[*obvious].instruction.type = TurnType::NewName;
intersection[*obvious].instruction.direction_modifier =
getTurnDirection(intersection[*obvious].angle);
}
else
{
intersection[*obvious].instruction.type = TurnType::Suppressed;
}
}
return intersection;
}
// Implementation details
boost::optional<std::size_t> SliproadHandler::getObviousIndexWithSliproads(
const EdgeID from, const Intersection &intersection, const NodeID at) const
{
BOOST_ASSERT(from != SPECIAL_EDGEID);
BOOST_ASSERT(at != SPECIAL_NODEID);
// If a turn is obvious without taking Sliproads into account use this
const auto index = findObviousTurn(from, intersection);
if (index != 0)
{
return boost::make_optional(index);
}
// Otherwise check if the road is forking into two and one of them is a Sliproad;
// then the non-Sliproad is the obvious one.
if (intersection.size() != 3)
{
return boost::none;
}
const auto forking = intersection[1].instruction.type == TurnType::Fork &&
intersection[2].instruction.type == TurnType::Fork;
if (!forking)
{
return boost::none;
}
const auto first = getNextIntersection(at, intersection.getRightmostRoad().eid);
const auto second = getNextIntersection(at, intersection.getLeftmostRoad().eid);
if (!first || !second)
{
return boost::none;
}
if (first->intersection.isDeadEnd() || second->intersection.isDeadEnd())
{
return boost::none;
}
// In case of loops at the end of the road, we will arrive back at the intersection
// itself. If that is the case, the road is obviously not a sliproad.
if (canBeTargetOfSliproad(first->intersection) && at != second->node)
{
return boost::make_optional(std::size_t{2});
}
if (canBeTargetOfSliproad(second->intersection) && at != first->node)
{
return boost::make_optional(std::size_t{1});
}
return boost::none;
}
bool SliproadHandler::nextIntersectionIsTooFarAway(const NodeID start, const EdgeID onto) const
{
BOOST_ASSERT(start != SPECIAL_NODEID);
BOOST_ASSERT(onto != SPECIAL_EDGEID);
// Base max distance threshold on the current road class we're on
const auto &data = node_based_graph.GetEdgeData(onto).flags;
const auto threshold = scaledThresholdByRoadClass(MAX_SLIPROAD_THRESHOLD, // <- scales down
data.road_classification);
DistanceToNextIntersectionAccumulator accumulator{
coordinate_extractor, node_based_graph, threshold};
const SkipTrafficSignalBarrierRoadSelector selector{};
(void)graph_walker.TraverseRoad(start, onto, accumulator, selector);
return accumulator.too_far_away;
}
bool SliproadHandler::roadContinues(const EdgeID current, const EdgeID next) const
{
const auto &current_data =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(current).annotation_data);
const auto &next_data =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(next).annotation_data);
auto same_road_category = node_based_graph.GetEdgeData(current).flags.road_classification ==
node_based_graph.GetEdgeData(next).flags.road_classification;
auto same_travel_mode = current_data.travel_mode == next_data.travel_mode;
auto same_name = current_data.name_id != EMPTY_NAMEID && //
next_data.name_id != EMPTY_NAMEID && //
!util::guidance::requiresNameAnnounced(current_data.name_id,
next_data.name_id,
name_table,
street_name_suffix_table); //
const auto continues = same_road_category && same_travel_mode && same_name;
return continues;
}
bool SliproadHandler::isValidSliproadArea(const double max_area,
const NodeID a,
const NodeID b,
const NodeID c) const
{
using namespace util::coordinate_calculation;
const auto first = node_coordinates[a];
const auto second = node_coordinates[b];
const auto third = node_coordinates[c];
const auto length = haversineDistance(first, second);
const auto heigth = haversineDistance(second, third);
const auto area = (length * heigth) / 2.;
// Everything below is data issue - there are some weird situations where
// nodes are really close to each other and / or tagging ist just plain off.
const constexpr auto MIN_SLIPROAD_AREA = 3.;
if (area < MIN_SLIPROAD_AREA || area > max_area)
{
return false;
}
return true;
}
bool SliproadHandler::isValidSliproadLink(const IntersectionViewData &sliproad,
const IntersectionViewData & /*first*/,
const IntersectionViewData &second) const
{
// If the Sliproad is not a link we don't care
const auto &sliproad_data = node_based_graph.GetEdgeData(sliproad.eid).flags;
if (!sliproad_data.road_classification.IsLinkClass())
{
return true;
}
// and the second road coming from the intersection we shortcut must be a non-link
const auto &second_road_data = node_based_graph.GetEdgeData(second.eid).flags;
if (second_road_data.road_classification.IsLinkClass())
{
return false;
}
return true;
}
bool SliproadHandler::allSameMode(const EdgeID from,
const EdgeID sliproad_candidate,
const EdgeID target_road) const
{
const auto &from_annotation =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(from).annotation_data);
const auto &sliproad_annotation = node_data_container.GetAnnotation(
node_based_graph.GetEdgeData(sliproad_candidate).annotation_data);
const auto &target_annotation = node_data_container.GetAnnotation(
node_based_graph.GetEdgeData(target_road).annotation_data);
return (from_annotation.travel_mode == sliproad_annotation.travel_mode) &&
(target_annotation.travel_mode == sliproad_annotation.travel_mode);
}
bool SliproadHandler::canBeTargetOfSliproad(const IntersectionView &intersection)
{
// Example to handle:
// .
// a . . b .
// ` .
// ` .
// c < intersection
// .
//
// One outgoing two incoming
if (intersection.size() != 3)
{
return false;
}
const auto backwards = intersection[0].entry_allowed;
const auto multiple_allowed = intersection[1].entry_allowed && intersection[2].entry_allowed;
if (backwards || multiple_allowed)
{
return false;
}
return true;
}
double SliproadHandler::scaledThresholdByRoadClass(const double max_threshold,
const RoadClassification &classification)
{
double factor = 1.0;
switch (classification.GetPriority())
{
case RoadPriorityClass::MOTORWAY:
factor = 1.0;
break;
case RoadPriorityClass::TRUNK:
factor = 0.8;
break;
case RoadPriorityClass::PRIMARY:
factor = 0.8;
break;
case RoadPriorityClass::SECONDARY:
factor = 0.6;
break;
case RoadPriorityClass::TERTIARY:
factor = 0.5;
break;
case RoadPriorityClass::MAIN_RESIDENTIAL:
factor = 0.4;
break;
case RoadPriorityClass::SIDE_RESIDENTIAL:
factor = 0.3;
break;
case RoadPriorityClass::LINK_ROAD:
factor = 0.3;
break;
case RoadPriorityClass::CONNECTIVITY:
factor = 0.1;
break;
// What
case RoadPriorityClass::BIKE_PATH:
case RoadPriorityClass::FOOT_PATH:
default:
factor = 0.1;
}
const auto scaled = max_threshold * factor;
BOOST_ASSERT(scaled <= max_threshold);
return scaled;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -1,84 +0,0 @@
#include "extractor/guidance/suppress_mode_handler.hpp"
#include "extractor/travel_mode.hpp"
#include <algorithm>
#include <iterator>
namespace osrm
{
namespace extractor
{
namespace guidance
{
SuppressModeHandler::SuppressModeHandler(
const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &coordinates,
const extractor::CompressedEdgeContainer &compressed_geometries,
const RestrictionMap &node_restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table)
: IntersectionHandler(node_based_graph,
node_data_container,
coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table)
{
}
bool SuppressModeHandler::canProcess(const NodeID,
const EdgeID via_eid,
const Intersection &intersection) const
{
using std::begin;
using std::end;
// travel modes for which navigation should be suppressed
static const constexpr char suppressed[] = {extractor::TRAVEL_MODE_TRAIN,
extractor::TRAVEL_MODE_FERRY};
// If the approach way is not on the suppression blacklist, and not all the exit ways share that
// mode, there are no ways to suppress by this criteria.
const auto in_mode =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(via_eid).annotation_data)
.travel_mode;
const auto suppress_in_mode = std::find(begin(suppressed), end(suppressed), in_mode);
const auto first = begin(intersection);
const auto last = end(intersection);
const auto all_share_mode = std::all_of(first, last, [this, &in_mode](const auto &road) {
return node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data)
.travel_mode == in_mode;
});
return (suppress_in_mode != end(suppressed)) && all_share_mode;
}
Intersection SuppressModeHandler::
operator()(const NodeID, const EdgeID, Intersection intersection) const
{
const auto first = begin(intersection);
const auto last = end(intersection);
std::for_each(first, last, [&](auto &road) {
const auto modifier = road.instruction.direction_modifier;
// use NoTurn, to not even have it as an IntermediateIntersection
const auto type = TurnType::NoTurn;
road.instruction = {type, modifier};
});
return intersection;
}
}
}
}
-207
View File
@@ -1,207 +0,0 @@
#include "extractor/guidance/turn_analysis.hpp"
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/road_classification.hpp"
#include "util/coordinate.hpp"
#include "util/coordinate_calculation.hpp"
#include <cstddef>
#include <set>
#include <unordered_set>
#include <utility>
using osrm::extractor::guidance::getTurnDirection;
namespace osrm
{
namespace extractor
{
namespace guidance
{
using EdgeData = util::NodeBasedDynamicGraph::EdgeData;
TurnAnalysis::TurnAnalysis(const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &node_coordinates,
const CompressedEdgeContainer &compressed_edge_container,
const RestrictionMap &restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table)
: node_based_graph(node_based_graph), roundabout_handler(node_based_graph,
node_data_container,
node_coordinates,
compressed_edge_container,
restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table),
motorway_handler(node_based_graph,
node_data_container,
node_coordinates,
compressed_edge_container,
restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table),
turn_handler(node_based_graph,
node_data_container,
node_coordinates,
compressed_edge_container,
restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table),
sliproad_handler(node_based_graph,
node_data_container,
node_coordinates,
compressed_edge_container,
restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table),
suppress_mode_handler(node_based_graph,
node_data_container,
node_coordinates,
compressed_edge_container,
restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table),
driveway_handler(node_based_graph,
node_data_container,
node_coordinates,
compressed_edge_container,
restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table),
statistics_handler(node_based_graph,
node_data_container,
node_coordinates,
compressed_edge_container,
restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table)
{
}
Intersection TurnAnalysis::AssignTurnTypes(const NodeID node_prior_to_intersection,
const EdgeID entering_via_edge,
const IntersectionView &intersection_view) const
{
// Roundabouts are a main priority. If there is a roundabout instruction present, we process the
// turn as a roundabout
// the following lines create a partly invalid intersection object. We might want to refactor
// this at some point
Intersection intersection;
intersection.reserve(intersection_view.size());
std::transform(intersection_view.begin(),
intersection_view.end(),
std::back_inserter(intersection),
[&](const IntersectionViewData &data) {
return ConnectedRoad(data,
{TurnType::Invalid, DirectionModifier::UTurn},
INVALID_LANE_DATAID);
});
// Suppress turns on ways between mode types that do not need guidance, think ferry routes.
// This handler has to come first and when it triggers we're done with the intersection: there's
// nothing left to be done once we suppressed instructions on such routes. Exit early.
if (suppress_mode_handler.canProcess(
node_prior_to_intersection, entering_via_edge, intersection))
{
intersection = suppress_mode_handler(
node_prior_to_intersection, entering_via_edge, std::move(intersection));
return intersection;
}
if (roundabout_handler.canProcess(node_prior_to_intersection, entering_via_edge, intersection))
{
intersection = roundabout_handler(
node_prior_to_intersection, entering_via_edge, std::move(intersection));
}
else
{
// set initial defaults for normal turns and modifier based on angle
intersection =
setTurnTypes(node_prior_to_intersection, entering_via_edge, std::move(intersection));
if (driveway_handler.canProcess(
node_prior_to_intersection, entering_via_edge, intersection))
{
intersection = driveway_handler(
node_prior_to_intersection, entering_via_edge, std::move(intersection));
}
else if (motorway_handler.canProcess(
node_prior_to_intersection, entering_via_edge, intersection))
{
intersection = motorway_handler(
node_prior_to_intersection, entering_via_edge, std::move(intersection));
}
else
{
BOOST_ASSERT(turn_handler.canProcess(
node_prior_to_intersection, entering_via_edge, intersection));
intersection = turn_handler(
node_prior_to_intersection, entering_via_edge, std::move(intersection));
}
}
// Handle sliproads
if (sliproad_handler.canProcess(node_prior_to_intersection, entering_via_edge, intersection))
intersection = sliproad_handler(
node_prior_to_intersection, entering_via_edge, std::move(intersection));
// Turn On Ramps Into Off Ramps, if we come from a motorway-like road
if (node_based_graph.GetEdgeData(entering_via_edge).flags.road_classification.IsMotorwayClass())
{
std::for_each(intersection.begin(), intersection.end(), [](ConnectedRoad &road) {
if (road.instruction.type == TurnType::OnRamp)
road.instruction.type = TurnType::OffRamp;
});
}
// After we ran all handlers and determined instruction type
// and direction modifier gather statistics about our decisions.
if (statistics_handler.canProcess(node_prior_to_intersection, entering_via_edge, intersection))
intersection = statistics_handler(
node_prior_to_intersection, entering_via_edge, std::move(intersection));
return intersection;
}
// Sets basic turn types as fallback for otherwise unhandled turns
Intersection TurnAnalysis::setTurnTypes(const NodeID node_prior_to_intersection,
const EdgeID,
Intersection intersection) const
{
for (auto &road : intersection)
{
if (!road.entry_allowed)
continue;
const EdgeID onto_edge = road.eid;
const NodeID to_nid = node_based_graph.GetTarget(onto_edge);
if (node_prior_to_intersection == to_nid)
road.instruction = {TurnType::Continue, DirectionModifier::UTurn};
else
road.instruction = {TurnType::Turn, getTurnDirection(road.angle)};
}
return intersection;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -1,91 +0,0 @@
#include "extractor/guidance/turn_classification.hpp"
#include <algorithm>
#include <cstddef>
#include <cstdint>
namespace osrm
{
namespace extractor
{
namespace guidance
{
std::pair<util::guidance::EntryClass, util::guidance::BearingClass>
classifyIntersection(Intersection intersection, const osrm::util::Coordinate &location)
{
if (intersection.empty())
return {};
std::sort(intersection.begin(),
intersection.end(),
[](const ConnectedRoad &left, const ConnectedRoad &right) {
return left.bearing < right.bearing;
});
util::guidance::EntryClass entry_class;
util::guidance::BearingClass bearing_class;
const bool canBeDiscretized = [&]() {
if (intersection.size() <= 1)
return true;
DiscreteBearing last_discrete_bearing = util::guidance::BearingClass::getDiscreteBearing(
std::round(intersection.back().bearing));
for (const auto &road : intersection)
{
const DiscreteBearing discrete_bearing =
util::guidance::BearingClass::getDiscreteBearing(std::round(road.bearing));
if (discrete_bearing == last_discrete_bearing)
return false;
last_discrete_bearing = discrete_bearing;
}
return true;
}();
// finally transfer data to the entry/bearing classes
std::size_t number = 0;
if (canBeDiscretized)
{
if (util::guidance::BearingClass::getDiscreteBearing(intersection.back().bearing) <
util::guidance::BearingClass::getDiscreteBearing(intersection.front().bearing))
{
intersection.insert(intersection.begin(), intersection.back());
intersection.pop_back();
}
for (const auto &road : intersection)
{
if (road.entry_allowed)
{
if (!entry_class.activate(number))
util::Log(logWARNING) << "Road " << number << " was not activated at "
<< location;
}
auto discrete_bearing_class =
util::guidance::BearingClass::getDiscreteBearing(std::round(road.bearing));
bearing_class.add(std::round(discrete_bearing_class *
util::guidance::BearingClass::discrete_step_size));
++number;
}
}
else
{
for (const auto &road : intersection)
{
if (road.entry_allowed)
{
if (!entry_class.activate(number))
util::Log(logWARNING) << "Road " << number << " was not activated at "
<< location;
}
bearing_class.add(std::round(road.bearing));
++number;
}
}
return std::make_pair(entry_class, bearing_class);
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
-150
View File
@@ -1,150 +0,0 @@
#include "extractor/guidance/turn_discovery.hpp"
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/coordinate_extractor.hpp"
#include "extractor/intersection/intersection_analysis.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
using osrm::util::angularDeviation;
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace lanes
{
bool findPreviousIntersection(const NodeID node_v,
const EdgeID via_edge,
const Intersection &intersection,
const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &node_coordinates,
const extractor::CompressedEdgeContainer &compressed_geometries,
const RestrictionMap &node_restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data,
// output parameters
NodeID &result_node,
EdgeID &result_via_edge,
IntersectionView &result_intersection)
{
/* We need to find the intersection that is located prior to via_edge.
*
* NODE_U -> PREVIOUS_ID -> NODE_V -> VIA_EDGE -> NODE_W:INTERSECTION
* NODE_U? <- STRAIGHTMOST <- NODE_V <- UTURN
* NODE_U? -> UTURN == PREVIOUSE_ID? -> NODE_V -> VIA_EDGE
*
* To do so, we first get the intersection atNODE and find the straightmost turn from that
* node. This will result in NODE_X. The uturn in the intersection at NODE_X should be
* PREVIOUS_ID. To verify that find, we check the intersection using our PREVIOUS_ID candidate
* to check the intersection at NODE for via_edge
*/
const constexpr double COMBINE_DISTANCE_CUTOFF = 30;
const CoordinateExtractor coordinate_extractor(
node_based_graph, compressed_geometries, node_coordinates);
const auto coordinates_along_via_edge =
coordinate_extractor.GetForwardCoordinatesAlongRoad(node_v, via_edge);
const auto via_edge_length =
util::coordinate_calculation::getLength(coordinates_along_via_edge.begin(),
coordinates_along_via_edge.end(),
&util::coordinate_calculation::haversineDistance);
// we check if via-edge is too short. In this case the previous turn cannot influence the turn
// at via_edge and the intersection at NODE_W
if (via_edge_length > COMBINE_DISTANCE_CUTOFF)
return false;
// Node -> Via_Edge -> Intersection[0 == UTURN] -> reverse_of(via_edge) -> Intersection at
// node
// (looking at the reverse direction).
const auto node_w = node_based_graph.GetTarget(via_edge);
const auto u_turn_at_node_w = intersection[0].eid;
// make sure the ID is actually valid
BOOST_ASSERT(node_based_graph.BeginEdges(node_w) <= u_turn_at_node_w &&
u_turn_at_node_w <= node_based_graph.EndEdges(node_w));
// if we can't find the correct road, stop
if (node_based_graph.GetTarget(u_turn_at_node_w) != node_v)
return false;
const auto node_v_reverse_intersection =
intersection::getConnectedRoads<true>(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
{node_w, u_turn_at_node_w});
// Continue along the straightmost turn. If there is no straight turn, we cannot find a valid
// previous intersection.
const auto straightmost_at_v_in_reverse =
node_v_reverse_intersection.findClosestTurn(STRAIGHT_ANGLE);
// TODO evaluate if narrow turn is the right criterion here... Might be that other angles are
// valid
if (angularDeviation(straightmost_at_v_in_reverse->angle, STRAIGHT_ANGLE) > GROUP_ANGLE)
return false;
const auto node_u = node_based_graph.GetTarget(straightmost_at_v_in_reverse->eid);
const auto node_u_reverse_intersection =
intersection::getConnectedRoads<true>(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
{node_v, straightmost_at_v_in_reverse->eid});
// now check that the u-turn at the given intersection connects to via-edge
// The u-turn at the now found intersection should, hopefully, represent the previous edge.
result_node = node_u;
result_via_edge = node_u_reverse_intersection[0].eid;
if (node_based_graph.GetTarget(result_via_edge) != node_v)
return false;
// if the edge is not traversable, we obviously don't have a previous intersection or couldn't
// find it.
if (node_based_graph.GetEdgeData(result_via_edge).reversed)
{
result_via_edge = SPECIAL_EDGEID;
result_node = SPECIAL_NODEID;
return false;
}
result_intersection = intersection::getConnectedRoads<false>(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
{node_u, result_via_edge});
const auto check_via_edge =
result_intersection.end() !=
std::find_if(result_intersection.begin(),
result_intersection.end(),
[via_edge](const IntersectionViewData &road) { return road.eid == via_edge; });
if (!check_via_edge)
{
result_via_edge = SPECIAL_EDGEID;
result_node = SPECIAL_NODEID;
return false;
}
return true;
}
} // namespace lanes
} // namespace guidance
} // namespace extractor
} // namespace osrm
-848
View File
@@ -1,848 +0,0 @@
#include "extractor/guidance/turn_handler.hpp"
#include "extractor/guidance/constants.hpp"
#include "util/bearing.hpp"
#include "util/guidance/name_announcements.hpp"
#include <algorithm>
#include <limits>
#include <utility>
#include <boost/assert.hpp>
#include <boost/optional.hpp>
using osrm::extractor::guidance::getTurnDirection;
using osrm::util::angularDeviation;
namespace
{
using namespace osrm::extractor::guidance;
// given two adjacent roads in clockwise order and `road1` being a candidate for a fork,
// return false, if next road `road2` is also a fork candidate or
// return true, if `road2` is not a suitable fork candidate and thus, `road1` the outermost fork
bool isOutermostForkCandidate(const ConnectedRoad &road1, const ConnectedRoad &road2)
{
const auto angle_between_next_road_and_straight = angularDeviation(road2.angle, STRAIGHT_ANGLE);
const auto angle_between_prev_road_and_next = angularDeviation(road1.angle, road2.angle);
const auto angle_between_prev_road_and_straight = angularDeviation(road1.angle, STRAIGHT_ANGLE);
// a road is a fork candidate if it is close to straight or
// close to a street that goes close to straight
// (reverse to find fork non-candidate)
if (angle_between_next_road_and_straight > NARROW_TURN_ANGLE)
{
if (angle_between_prev_road_and_next > NARROW_TURN_ANGLE ||
angle_between_prev_road_and_straight > GROUP_ANGLE)
{
return true;
}
}
return false;
}
bool isEndOfRoad(const ConnectedRoad &,
const ConnectedRoad &possible_right_turn,
const ConnectedRoad &possible_left_turn)
{
return angularDeviation(possible_right_turn.angle, 90) < NARROW_TURN_ANGLE &&
angularDeviation(possible_left_turn.angle, 270) < NARROW_TURN_ANGLE &&
angularDeviation(possible_right_turn.angle, possible_left_turn.angle) >
2 * NARROW_TURN_ANGLE;
}
template <typename InputIt>
InputIt findOutermostForkCandidate(const InputIt begin, const InputIt end)
{
static_assert(std::is_base_of<std::input_iterator_tag,
typename std::iterator_traits<InputIt>::iterator_category>::value,
"findOutermostForkCandidate() only accepts input iterators");
const auto outermost = std::adjacent_find(begin, end, isOutermostForkCandidate);
if (outermost != end)
{
return outermost;
}
// if all roads are part of a fork, set `candidate` to the last road
else
{
return outermost - 1;
}
}
}
namespace osrm
{
namespace extractor
{
namespace guidance
{
// a wrapper to handle road indices of forks at intersections
TurnHandler::Fork::Fork(const Intersection::iterator intersection_base,
const Intersection::iterator begin,
const Intersection::iterator end)
: intersection_base(intersection_base), begin(begin), end(end), size(std::distance(begin, end))
{
BOOST_ASSERT(begin < end);
BOOST_ASSERT(size == 2 || size == 3);
}
ConnectedRoad &TurnHandler::Fork::getRight() { return *begin; }
ConnectedRoad &TurnHandler::Fork::getLeft() { return *(end - 1); }
ConnectedRoad &TurnHandler::Fork::getMiddle()
{
BOOST_ASSERT(size == 3);
return *(begin + 1);
}
ConnectedRoad &TurnHandler::Fork::getRight() const { return *begin; }
ConnectedRoad &TurnHandler::Fork::getLeft() const { return *(end - 1); }
ConnectedRoad &TurnHandler::Fork::getMiddle() const
{
BOOST_ASSERT(size == 3);
return *(begin + 1);
}
std::size_t TurnHandler::Fork::getRightIndex() const
{
return std::distance(intersection_base, begin);
}
std::size_t TurnHandler::Fork::getLeftIndex() const
{
return std::distance(intersection_base, end) - 1;
}
TurnHandler::TurnHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &coordinates,
const extractor::CompressedEdgeContainer &compressed_geometries,
const RestrictionMap &node_restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table)
: IntersectionHandler(node_based_graph,
node_data_container,
coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
name_table,
street_name_suffix_table)
{
}
bool TurnHandler::canProcess(const NodeID, const EdgeID, const Intersection &) const
{
return true;
}
// Handles and processes possible turns
// Input parameters describe an intersection as described in
// #IntersectionExplanation@intersection_handler.hpp
Intersection TurnHandler::
operator()(const NodeID, const EdgeID via_edge, Intersection intersection) const
{
if (intersection.size() == 1)
return handleOneWayTurn(std::move(intersection));
// if u-turn is allowed, set the turn type of intersection[0] to its basic type and u-turn
if (intersection[0].entry_allowed)
{
intersection[0].instruction = {findBasicTurnType(via_edge, intersection[0]),
DirectionModifier::UTurn};
}
if (intersection.size() == 2)
return handleTwoWayTurn(via_edge, std::move(intersection));
if (intersection.size() == 3)
return handleThreeWayTurn(via_edge, std::move(intersection));
return handleComplexTurn(via_edge, std::move(intersection));
}
Intersection TurnHandler::handleOneWayTurn(Intersection intersection) const
{
BOOST_ASSERT(intersection[0].angle < 0.001);
return intersection;
}
Intersection TurnHandler::handleTwoWayTurn(const EdgeID via_edge, Intersection intersection) const
{
BOOST_ASSERT(intersection[0].angle < 0.001);
intersection[1].instruction =
getInstructionForObvious(intersection.size(), via_edge, false, intersection[1]);
return intersection;
}
// checks whether it is obvious to turn on `road` coming from `via_edge` while there is an`other`
// road at the same intersection
bool TurnHandler::isObviousOfTwo(const EdgeID via_edge,
const ConnectedRoad &road,
const ConnectedRoad &other) const
{
const auto &via_data =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(via_edge).annotation_data);
const auto &road_data =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data);
const auto &via_classification =
node_based_graph.GetEdgeData(via_edge).flags.road_classification;
const auto &road_classification =
node_based_graph.GetEdgeData(road.eid).flags.road_classification;
const auto &other_classification =
node_based_graph.GetEdgeData(other.eid).flags.road_classification;
// if one of the given roads is obvious by class, obviousness is trivial
if (obviousByRoadClass(via_classification, road_classification, other_classification))
{
return true;
}
else if (obviousByRoadClass(via_classification, other_classification, road_classification))
{
return false;
}
const bool turn_is_perfectly_straight =
angularDeviation(road.angle, STRAIGHT_ANGLE) < std::numeric_limits<double>::epsilon();
const auto &via_name_empty = name_table.GetNameForID(via_data.name_id).empty();
if (!via_name_empty)
{
const auto same_name = !util::guidance::requiresNameAnnounced(
via_data.name_id, road_data.name_id, name_table, street_name_suffix_table);
if (turn_is_perfectly_straight && same_name)
{
return true;
}
}
const bool is_much_narrower_than_other =
angularDeviation(other.angle, STRAIGHT_ANGLE) /
angularDeviation(road.angle, STRAIGHT_ANGLE) >
INCREASES_BY_FOURTY_PERCENT &&
angularDeviation(angularDeviation(other.angle, STRAIGHT_ANGLE),
angularDeviation(road.angle, STRAIGHT_ANGLE)) > FUZZY_ANGLE_DIFFERENCE;
return is_much_narrower_than_other;
}
bool TurnHandler::hasObvious(const EdgeID &via_edge, const Fork &fork) const
{
auto obvious_road =
std::adjacent_find(fork.begin, fork.end, [&, this](const auto &a, const auto &b) {
return this->isObviousOfTwo(via_edge, a, b) || this->isObviousOfTwo(via_edge, b, a);
});
// return whether an obvious road was found
return obvious_road != fork.end;
}
// handles a turn at a three-way intersection _coming from_ `via_edge`
// with `intersection` as described as in #IntersectionExplanation@intersection_handler.hpp
Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection intersection) const
{
BOOST_ASSERT(intersection.size() == 3);
const auto obvious_index = findObviousTurn(via_edge, intersection);
BOOST_ASSERT(intersection[0].angle < 0.001);
/* Two nearly straight turns -> FORK
OOOOOOO
/
IIIIII
\
OOOOOOO
*/
auto fork = findFork(via_edge, intersection);
if (fork && obvious_index == 0)
{
assignFork(via_edge, fork->getLeft(), fork->getRight());
}
/* T Intersection
OOOOOOO T OOOOOOOO
I
I
I
*/
else if (isEndOfRoad(intersection[0], intersection[1], intersection[2]) && obvious_index == 0)
{
if (intersection[1].entry_allowed)
{
if (TurnType::OnRamp != findBasicTurnType(via_edge, intersection[1]))
intersection[1].instruction = {TurnType::EndOfRoad, DirectionModifier::Right};
else
intersection[1].instruction = {TurnType::OnRamp, DirectionModifier::Right};
}
if (intersection[2].entry_allowed)
{
if (TurnType::OnRamp != findBasicTurnType(via_edge, intersection[2]))
intersection[2].instruction = {TurnType::EndOfRoad, DirectionModifier::Left};
else
intersection[2].instruction = {TurnType::OnRamp, DirectionModifier::Left};
}
}
else if (obvious_index != 0) // has an obvious continuing road/obvious turn
{
const auto direction_at_one = getTurnDirection(intersection[1].angle);
const auto direction_at_two = getTurnDirection(intersection[2].angle);
if (obvious_index == 1)
{
intersection[1].instruction =
getInstructionForObvious(3,
via_edge,
isThroughStreet(1,
intersection,
node_based_graph,
node_data_container,
name_table,
street_name_suffix_table),
intersection[1]);
const auto second_direction = (direction_at_one == direction_at_two &&
direction_at_two == DirectionModifier::Straight)
? DirectionModifier::SlightLeft
: direction_at_two;
intersection[2].instruction = {findBasicTurnType(via_edge, intersection[2]),
second_direction};
}
else
{
BOOST_ASSERT(obvious_index == 2);
intersection[2].instruction =
getInstructionForObvious(3,
via_edge,
isThroughStreet(2,
intersection,
node_based_graph,
node_data_container,
name_table,
street_name_suffix_table),
intersection[2]);
const auto first_direction = (direction_at_one == direction_at_two &&
direction_at_one == DirectionModifier::Straight)
? DirectionModifier::SlightRight
: direction_at_one;
intersection[1].instruction = {findBasicTurnType(via_edge, intersection[1]),
first_direction};
}
}
else // basic turn assignment
{
intersection[1].instruction = {findBasicTurnType(via_edge, intersection[1]),
getTurnDirection(intersection[1].angle)};
intersection[2].instruction = {findBasicTurnType(via_edge, intersection[2]),
getTurnDirection(intersection[2].angle)};
}
return intersection;
}
Intersection TurnHandler::handleComplexTurn(const EdgeID via_edge, Intersection intersection) const
{
const std::size_t obvious_index = findObviousTurn(via_edge, intersection);
const auto fork = findFork(via_edge, intersection);
const auto straightmost = intersection.findClosestTurn(STRAIGHT_ANGLE);
const auto straightmost_index = std::distance(intersection.begin(), straightmost);
const auto straightmost_angle_dev = angularDeviation(straightmost->angle, STRAIGHT_ANGLE);
// check whether the obvious choice is actually a through street
if (obvious_index != 0)
{
intersection[obvious_index].instruction =
getInstructionForObvious(intersection.size(),
via_edge,
isThroughStreet(obvious_index,
intersection,
node_based_graph,
node_data_container,
name_table,
street_name_suffix_table),
intersection[obvious_index]);
// assign left/right turns
intersection = assignLeftTurns(via_edge, std::move(intersection), obvious_index);
intersection = assignRightTurns(via_edge, std::move(intersection), obvious_index);
}
else if (fork) // found fork
{
if (fork->size == 2)
{
const auto left_classification =
node_based_graph.GetEdgeData(fork->getLeft().eid).flags.road_classification;
const auto right_classification =
node_based_graph.GetEdgeData(fork->getRight().eid).flags.road_classification;
if (canBeSeenAsFork(left_classification, right_classification))
{
assignFork(via_edge, fork->getLeft(), fork->getRight());
}
else if (left_classification.GetPriority() > right_classification.GetPriority())
{
fork->getRight().instruction = getInstructionForObvious(
intersection.size(), via_edge, false, fork->getRight());
fork->getLeft().instruction = {findBasicTurnType(via_edge, fork->getLeft()),
DirectionModifier::SlightLeft};
}
else
{
fork->getLeft().instruction =
getInstructionForObvious(intersection.size(), via_edge, false, fork->getLeft());
fork->getRight().instruction = {findBasicTurnType(via_edge, fork->getRight()),
DirectionModifier::SlightRight};
}
}
else if (fork->size == 3)
{
assignFork(via_edge,
fork->getLeft(),
// middle fork road
fork->getMiddle(),
fork->getRight());
}
intersection = assignLeftTurns(via_edge, std::move(intersection), fork->getLeftIndex());
intersection = assignRightTurns(via_edge, std::move(intersection), fork->getRightIndex());
}
else if (straightmost_angle_dev < FUZZY_ANGLE_DIFFERENCE && !straightmost->entry_allowed)
{
// invalid straight turn
intersection = assignLeftTurns(via_edge, std::move(intersection), straightmost_index);
intersection = assignRightTurns(via_edge, std::move(intersection), straightmost_index);
}
// no straight turn
else if (straightmost->angle > 180)
{
// at most three turns on either side
intersection = assignLeftTurns(via_edge, std::move(intersection), straightmost_index - 1);
intersection = assignRightTurns(via_edge, std::move(intersection), straightmost_index);
}
else if (straightmost->angle < 180)
{
intersection = assignLeftTurns(via_edge, std::move(intersection), straightmost_index);
intersection = assignRightTurns(via_edge, std::move(intersection), straightmost_index + 1);
}
else
{
assignTrivialTurns(via_edge, intersection, 1, intersection.size());
}
return intersection;
}
// Assignment of left turns hands of to right turns.
// To do so, we mirror every road segment and reverse the order.
// After the mirror and reversal / we assign right turns and
// mirror again and restore the original order.
Intersection TurnHandler::assignLeftTurns(const EdgeID via_edge,
Intersection intersection,
const std::size_t starting_at) const
{
BOOST_ASSERT(starting_at < intersection.size());
const auto switch_left_and_right = [](Intersection &intersection) {
BOOST_ASSERT(!intersection.empty());
for (auto &road : intersection)
road.mirror();
std::reverse(intersection.begin() + 1, intersection.end());
};
switch_left_and_right(intersection);
// account for the u-turn in the beginning
const auto count = intersection.size() - starting_at;
intersection = assignRightTurns(via_edge, std::move(intersection), count);
switch_left_and_right(intersection);
return intersection;
}
// can only assign three turns
Intersection TurnHandler::assignRightTurns(const EdgeID via_edge,
Intersection intersection,
const std::size_t up_to) const
{
BOOST_ASSERT(up_to <= intersection.size());
const auto count_valid = [&intersection, up_to]() {
std::size_t count = 0;
for (std::size_t i = 1; i < up_to; ++i)
if (intersection[i].entry_allowed)
++count;
return count;
};
if (up_to <= 1 || count_valid() == 0)
return intersection;
// handle single turn
if (up_to == 2)
{
assignTrivialTurns(via_edge, intersection, 1, up_to);
}
// Handle Turns 1-3
else if (up_to == 3)
{
const auto first_direction = getTurnDirection(intersection[1].angle);
const auto second_direction = getTurnDirection(intersection[2].angle);
if (first_direction == second_direction)
{
// conflict
handleDistinctConflict(via_edge, intersection[2], intersection[1]);
}
else
{
assignTrivialTurns(via_edge, intersection, 1, up_to);
}
}
// Handle Turns 1-4
else if (up_to == 4)
{
const auto first_direction = getTurnDirection(intersection[1].angle);
const auto second_direction = getTurnDirection(intersection[2].angle);
const auto third_direction = getTurnDirection(intersection[3].angle);
if (first_direction != second_direction && second_direction != third_direction)
{
// due to the circular order, the turn directions are unique
// first_direction != third_direction is implied
BOOST_ASSERT(first_direction != third_direction);
assignTrivialTurns(via_edge, intersection, 1, up_to);
}
else if (2 >= (intersection[1].entry_allowed + intersection[2].entry_allowed +
intersection[3].entry_allowed))
{
// at least a single invalid
if (!intersection[3].entry_allowed)
{
handleDistinctConflict(via_edge, intersection[2], intersection[1]);
}
else if (!intersection[1].entry_allowed)
{
handleDistinctConflict(via_edge, intersection[3], intersection[2]);
}
else // handles one-valid as well as two valid (1,3)
{
handleDistinctConflict(via_edge, intersection[3], intersection[1]);
}
}
// From here on out, intersection[1-3].entry_allowed has to be true (Otherwise we would have
// triggered 2>= ...)
//
// Conflicting Turns, but at least farther than what we call a narrow turn
else if (angularDeviation(intersection[1].angle, intersection[2].angle) >=
NARROW_TURN_ANGLE &&
angularDeviation(intersection[2].angle, intersection[3].angle) >=
NARROW_TURN_ANGLE)
{
BOOST_ASSERT(intersection[1].entry_allowed && intersection[2].entry_allowed &&
intersection[3].entry_allowed);
intersection[1].instruction = {findBasicTurnType(via_edge, intersection[1]),
DirectionModifier::SharpRight};
intersection[2].instruction = {findBasicTurnType(via_edge, intersection[2]),
DirectionModifier::Right};
intersection[3].instruction = {findBasicTurnType(via_edge, intersection[3]),
DirectionModifier::SlightRight};
}
else if (((first_direction == second_direction && second_direction == third_direction) ||
(first_direction == second_direction &&
angularDeviation(intersection[2].angle, intersection[3].angle) < GROUP_ANGLE) ||
(second_direction == third_direction &&
angularDeviation(intersection[1].angle, intersection[2].angle) < GROUP_ANGLE)))
{
BOOST_ASSERT(intersection[1].entry_allowed && intersection[2].entry_allowed &&
intersection[3].entry_allowed);
// count backwards from the slightest turn
assignTrivialTurns(via_edge, intersection, 1, up_to);
}
else if (((first_direction == second_direction &&
angularDeviation(intersection[2].angle, intersection[3].angle) >= GROUP_ANGLE) ||
(second_direction == third_direction &&
angularDeviation(intersection[1].angle, intersection[2].angle) >= GROUP_ANGLE)))
{
BOOST_ASSERT(intersection[1].entry_allowed && intersection[2].entry_allowed &&
intersection[3].entry_allowed);
if (angularDeviation(intersection[2].angle, intersection[3].angle) >= GROUP_ANGLE)
{
handleDistinctConflict(via_edge, intersection[2], intersection[1]);
intersection[3].instruction = {findBasicTurnType(via_edge, intersection[3]),
third_direction};
}
else
{
intersection[1].instruction = {findBasicTurnType(via_edge, intersection[1]),
first_direction};
handleDistinctConflict(via_edge, intersection[3], intersection[2]);
}
}
else
{
assignTrivialTurns(via_edge, intersection, 1, up_to);
}
}
else
{
assignTrivialTurns(via_edge, intersection, 1, up_to);
}
return intersection;
}
// finds a fork candidate by just looking at the geometry and angle of an intersection
boost::optional<TurnHandler::Fork>
TurnHandler::findForkCandidatesByGeometry(Intersection &intersection) const
{
if (intersection.size() >= 3)
{
const auto straightmost = intersection.findClosestTurn(STRAIGHT_ANGLE);
const auto straightmost_index = std::distance(intersection.begin(), straightmost);
const auto straightmost_angle_dev = angularDeviation(straightmost->angle, STRAIGHT_ANGLE);
// Forks can only happen when two or more roads have a pretty narrow angle between each
// other and are close to going straight
//
//
// left right left right
// \ / \ | /
// \ / \|/
// | |
// | |
// | |
//
// possibly a fork possibly a fork
//
//
// left left
// / \ 
// /____ right \ ______ right
// | |
// | |
// | |
//
// not a fork cause not a fork cause
// it's not going angle is too wide
// straigthish
//
//
// left and right will be indices of the leftmost and rightmost connected roads that are
// fork candidates
if (straightmost_angle_dev <= NARROW_TURN_ANGLE)
{
// find the rightmost road that might be part of a fork
const auto right = findOutermostForkCandidate(
intersection.rend() - straightmost_index - 1, intersection.rend());
const std::size_t right_index = intersection.rend() - right - 1;
const auto forward_right = intersection.begin() + right_index;
// find the leftmost road that might be part of a fork
const auto left = findOutermostForkCandidate(straightmost, intersection.end());
// if the leftmost and rightmost roads with the conditions above are the same
// or if there are more than three fork candidates
// they cannot be fork candidates
if (forward_right < left && left - forward_right < 3)
{
return Fork(intersection.begin(), forward_right, left + 1);
}
}
}
return boost::none;
}
// check if the fork candidates (all roads between left and right) and the
// incoming edge are compatible by class
bool TurnHandler::isCompatibleByRoadClass(const Intersection &intersection, const Fork fork) const
{
const auto via_class =
node_based_graph.GetEdgeData(intersection[0].eid).flags.road_classification;
// if any of the considered roads is a link road, it cannot be a fork
// except if rightmost fork candidate is also a link road
const auto is_right_link_class =
node_based_graph.GetEdgeData(fork.getRight().eid).flags.road_classification.IsLinkClass();
if (!std::all_of(fork.begin + 1, fork.end, [&](ConnectedRoad &road) {
return is_right_link_class ==
node_based_graph.GetEdgeData(road.eid).flags.road_classification.IsLinkClass();
}))
{
return false;
}
return std::all_of(fork.begin, fork.end, [&](ConnectedRoad &base) {
const auto base_class = node_based_graph.GetEdgeData(base.eid).flags.road_classification;
// check that there is no turn obvious == check that all turns are non-onvious
return std::all_of(fork.begin, fork.end, [&](ConnectedRoad &compare) {
const auto compare_class =
node_based_graph.GetEdgeData(compare.eid).flags.road_classification;
return compare.eid == base.eid ||
!(obviousByRoadClass(via_class, base_class, compare_class));
});
});
}
// Checks whether a three-way-intersection coming from `via_edge` is a fork
// with `intersection` as described as in #IntersectionExplanation@intersection_handler.hpp
boost::optional<TurnHandler::Fork> TurnHandler::findFork(const EdgeID via_edge,
Intersection &intersection) const
{
const auto fork = findForkCandidatesByGeometry(intersection);
if (fork)
{
// makes sure that the fork is isolated from other neighbouring streets on the left and
// right side
const auto next = fork->end == intersection.end() ? intersection.begin() : (fork->end);
const bool separated_at_left_side =
angularDeviation(fork->getLeft().angle, next->angle) >= GROUP_ANGLE;
BOOST_ASSERT((fork->begin - 1) >= intersection.begin());
const bool separated_at_right_side =
angularDeviation(fork->getRight().angle, (fork->begin - 1)->angle) >= GROUP_ANGLE;
// check whether there is an obvious turn to take; forks are never obvious - if there is an
// obvious turn, it's not a fork
const bool has_obvious = hasObvious(via_edge, *fork);
// A fork can only happen between edges of similar types where none of the ones is obvious
const bool has_compatible_classes = isCompatibleByRoadClass(intersection, *fork);
// check if all entries in the fork range allow entry
const bool only_valid_entries = intersection.hasAllValidEntries(fork->begin, fork->end);
const auto has_compatible_modes =
std::all_of(fork->begin, fork->end, [&](const auto &road) {
return node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(road.eid).annotation_data)
.travel_mode ==
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(via_edge).annotation_data)
.travel_mode;
});
if (separated_at_left_side && separated_at_right_side && !has_obvious &&
has_compatible_classes && only_valid_entries && has_compatible_modes)
{
return fork;
}
}
return boost::none;
}
void TurnHandler::handleDistinctConflict(const EdgeID via_edge,
ConnectedRoad &left,
ConnectedRoad &right) const
{
// single turn of both is valid (don't change the valid one)
// or multiple identical angles -> bad OSM intersection
if ((!left.entry_allowed || !right.entry_allowed) || (left.angle == right.angle))
{
if (left.entry_allowed)
left.instruction = {findBasicTurnType(via_edge, left), getTurnDirection(left.angle)};
if (right.entry_allowed)
right.instruction = {findBasicTurnType(via_edge, right), getTurnDirection(right.angle)};
return;
}
if (getTurnDirection(left.angle) == DirectionModifier::Straight ||
getTurnDirection(left.angle) == DirectionModifier::SlightLeft ||
getTurnDirection(right.angle) == DirectionModifier::SlightRight)
{
const auto left_classification =
node_based_graph.GetEdgeData(left.eid).flags.road_classification;
const auto right_classification =
node_based_graph.GetEdgeData(right.eid).flags.road_classification;
if (left_classification.GetPriority() > right_classification.GetPriority())
{
// FIXME this should possibly know about the actual roads?
// here we don't know about the intersection size. To be on the save side,
// we declare it
// as complex (at least size 4)
right.instruction = getInstructionForObvious(4, via_edge, false, right);
left.instruction = {findBasicTurnType(via_edge, left), DirectionModifier::SlightLeft};
}
else
{
// FIXME this should possibly know about the actual roads?
// here we don't know about the intersection size. To be on the save side,
// we declare it
// as complex (at least size 4)
left.instruction = getInstructionForObvious(4, via_edge, false, left);
right.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
return;
}
const auto left_type = findBasicTurnType(via_edge, left);
const auto right_type = findBasicTurnType(via_edge, right);
// Two Right Turns
if (angularDeviation(left.angle, 90) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION)
{
// Keep left perfect, shift right
left.instruction = {left_type, DirectionModifier::Right};
right.instruction = {right_type, DirectionModifier::SharpRight};
return;
}
if (angularDeviation(right.angle, 90) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION)
{
// Keep Right perfect, shift left
left.instruction = {left_type, DirectionModifier::SlightRight};
right.instruction = {right_type, DirectionModifier::Right};
return;
}
// Two Left Turns
if (angularDeviation(left.angle, 270) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION)
{
// Keep left perfect, shift right
left.instruction = {left_type, DirectionModifier::Left};
right.instruction = {right_type, DirectionModifier::SlightLeft};
return;
}
if (angularDeviation(right.angle, 270) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION)
{
// Keep Right perfect, shift left
left.instruction = {left_type, DirectionModifier::SharpLeft};
right.instruction = {right_type, DirectionModifier::Left};
return;
}
// Shift the lesser penalty
if (getTurnDirection(left.angle) == DirectionModifier::SharpLeft)
{
left.instruction = {left_type, DirectionModifier::SharpLeft};
right.instruction = {right_type, DirectionModifier::Left};
return;
}
if (getTurnDirection(right.angle) == DirectionModifier::SharpRight)
{
left.instruction = {left_type, DirectionModifier::Right};
right.instruction = {right_type, DirectionModifier::SharpRight};
return;
}
// turn to the right
if (getTurnDirection(left.angle) <= 180)
{
if (angularDeviation(left.angle, 85) >= angularDeviation(right.angle, 85))
{
left.instruction = {left_type, DirectionModifier::SlightRight};
right.instruction = {right_type, DirectionModifier::Right};
}
else
{
left.instruction = {left_type, DirectionModifier::Right};
right.instruction = {right_type, DirectionModifier::SharpRight};
}
}
else
{
if (angularDeviation(left.angle, 265) >= angularDeviation(right.angle, 265))
{
left.instruction = {left_type, DirectionModifier::SharpLeft};
right.instruction = {right_type, DirectionModifier::Left};
}
else
{
left.instruction = {left_type, DirectionModifier::Left};
right.instruction = {right_type, DirectionModifier::SlightLeft};
}
}
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -1,352 +0,0 @@
#include "extractor/guidance/turn_lane_augmentation.hpp"
#include "extractor/guidance/turn_lane_types.hpp"
#include "util/log.hpp"
#include <algorithm>
#include <boost/assert.hpp>
#include <cstddef>
#include <utility>
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace lanes
{
namespace
{
const constexpr TurnLaneType::Mask tag_by_modifier[] = {TurnLaneType::uturn,
TurnLaneType::sharp_right,
TurnLaneType::right,
TurnLaneType::slight_right,
TurnLaneType::straight,
TurnLaneType::slight_left,
TurnLaneType::left,
TurnLaneType::sharp_left};
std::size_t getNumberOfTurns(const Intersection &intersection)
{
return std::count_if(intersection.begin(), intersection.end(), [](const ConnectedRoad &road) {
return road.entry_allowed;
});
}
LaneDataVector augmentMultiple(const std::size_t none_index,
const std::size_t connection_count,
LaneDataVector lane_data,
const Intersection &intersection)
{
// a none-turn is allowing multiple turns. we have to add a lane-data entry for
// every possible turn. This should, hopefully, only be the case for single lane
// entries?
// looking at the left side first
const auto range = [&]() {
if (none_index == 0)
{
// find first connection_count - lane_data.size() valid turns
std::size_t count = 0;
for (std::size_t intersection_index = 1; intersection_index < intersection.size();
++intersection_index)
{
count += static_cast<int>(intersection[intersection_index].entry_allowed);
if (count > connection_count - lane_data.size())
return std::make_pair(std::size_t{1}, intersection_index + 1);
}
}
else if (none_index + 1 == lane_data.size())
{
BOOST_ASSERT(!lane_data.empty());
// find last connection-count - last_data.size() valid turns
std::size_t count = 0;
for (std::size_t intersection_index = intersection.size() - 1; intersection_index > 0;
--intersection_index)
{
count += static_cast<int>(intersection[intersection_index].entry_allowed);
if (count > connection_count - lane_data.size())
return std::make_pair(intersection_index, intersection.size());
}
}
else
{
// skip the first #index valid turns, find next connection_count -
// lane_data.size() valid ones
std::size_t begin = 1, count = 0, intersection_index;
for (intersection_index = 1; intersection_index < intersection.size();
++intersection_index)
{
count += static_cast<int>(intersection[intersection_index].entry_allowed);
// if we reach the amount of
if (count >= none_index)
{
begin = intersection_index + 1;
break;
}
}
// reset count to find the number of necessary entries
count = 0;
for (intersection_index = begin; intersection_index < intersection.size();
++intersection_index)
{
count += static_cast<int>(intersection[intersection_index].entry_allowed);
if (count > connection_count - lane_data.size())
{
return std::make_pair(begin, intersection_index + 1);
}
}
}
// this should, theoretically, never be reached
util::Log(logWARNING) << "Failed lane assignment. Reached bad situation.";
return std::make_pair(std::size_t{0}, std::size_t{0});
}();
const auto intersection_range_first = intersection.begin() + range.first;
const auto intersection_range_end = intersection.begin() + range.second;
const auto allowed_in_range =
std::count_if(intersection_range_first, intersection_range_end, [](const auto &road) {
return road.entry_allowed;
});
if (allowed_in_range > 1 && lane_data[none_index].to - lane_data[none_index].from >= 1)
{
// check if there is a straight turn
auto straight_itr =
std::find_if(intersection_range_first, intersection_range_end, [](const auto &road) {
return road.instruction.direction_modifier == DirectionModifier::Straight;
});
// we have a straight turn?
if (straight_itr != intersection_range_end)
{
for (auto itr = intersection_range_first; itr != straight_itr; ++itr)
{
lane_data.push_back({tag_by_modifier[itr->instruction.direction_modifier],
lane_data[none_index].from,
lane_data[none_index].from});
}
lane_data.push_back({tag_by_modifier[straight_itr->instruction.direction_modifier],
lane_data[none_index].from,
lane_data[none_index].to});
for (auto itr = straight_itr + 1; itr != intersection_range_end; ++itr)
{
lane_data.push_back({tag_by_modifier[itr->instruction.direction_modifier],
lane_data[none_index].to,
lane_data[none_index].to});
}
lane_data.erase(lane_data.begin() + none_index);
}
return lane_data;
}
else
{
for (auto intersection_index = range.first; intersection_index < range.second;
++intersection_index)
{
if (intersection[intersection_index].entry_allowed)
{
lane_data.push_back({tag_by_modifier[intersection[intersection_index]
.instruction.direction_modifier],
lane_data[none_index].from,
lane_data[none_index].to});
}
}
lane_data.erase(lane_data.begin() + none_index);
}
return lane_data;
}
// Merging none-tag into its neighboring fields
// This handles situations like "left | | | right".
LaneDataVector mergeNoneTag(const std::size_t none_index, LaneDataVector lane_data)
{
if (none_index == 0 || none_index + 1 == lane_data.size())
{
if (none_index == 0)
{
lane_data[1].from = lane_data[0].from;
}
else
{
lane_data[none_index - 1].to = lane_data[none_index].to;
}
lane_data.erase(lane_data.begin() + none_index);
}
else if (lane_data[none_index].to - lane_data[none_index].from <= 1)
{
lane_data[none_index - 1].to = lane_data[none_index].from;
lane_data[none_index + 1].from = lane_data[none_index].to;
lane_data.erase(lane_data.begin() + none_index);
}
return lane_data;
}
LaneDataVector handleRenamingSituations(const std::size_t none_index,
LaneDataVector lane_data,
const Intersection &intersection)
{
bool has_right = false;
bool has_through = false;
bool has_left = false;
for (const auto &road : intersection)
{
if (!road.entry_allowed)
continue;
const auto modifier = road.instruction.direction_modifier;
has_right |= modifier == DirectionModifier::Right;
has_right |= modifier == DirectionModifier::SlightRight;
has_right |= modifier == DirectionModifier::SharpRight;
has_through |= modifier == DirectionModifier::Straight;
has_left |= modifier == DirectionModifier::Left;
has_left |= modifier == DirectionModifier::SlightLeft;
has_left |= modifier == DirectionModifier::SharpLeft;
}
// find missing tag and augment neighboring, if possible
if (none_index == 0)
{
if (has_right &&
(lane_data.size() == 1 || (lane_data[none_index + 1].tag != TurnLaneType::sharp_right &&
lane_data[none_index + 1].tag != TurnLaneType::right)))
{
lane_data[none_index].tag = TurnLaneType::right;
if (lane_data.size() > 1 && lane_data[none_index + 1].tag == TurnLaneType::straight)
{
lane_data[none_index + 1].from = lane_data[none_index].from;
// turning right through a possible through lane is not possible
lane_data[none_index].to = lane_data[none_index].from;
}
}
else if (has_through &&
(lane_data.size() == 1 || lane_data[none_index + 1].tag != TurnLaneType::straight))
{
lane_data[none_index].tag = TurnLaneType::straight;
}
}
else if (none_index + 1 == lane_data.size())
{
if (has_left && ((lane_data[none_index - 1].tag != TurnLaneType::sharp_left &&
lane_data[none_index - 1].tag != TurnLaneType::left)))
{
lane_data[none_index].tag = TurnLaneType::left;
if (lane_data[none_index - 1].tag == TurnLaneType::straight)
{
lane_data[none_index - 1].to = lane_data[none_index].to;
// turning left through a possible through lane is not possible
lane_data[none_index].from = lane_data[none_index].to;
}
}
else if (has_through && lane_data[none_index - 1].tag != TurnLaneType::straight)
{
lane_data[none_index].tag = TurnLaneType::straight;
}
}
else
{
if ((lane_data[none_index + 1].tag == TurnLaneType::left ||
lane_data[none_index + 1].tag == TurnLaneType::slight_left ||
lane_data[none_index + 1].tag == TurnLaneType::sharp_left) &&
(lane_data[none_index - 1].tag == TurnLaneType::right ||
lane_data[none_index - 1].tag == TurnLaneType::slight_right ||
lane_data[none_index - 1].tag == TurnLaneType::sharp_right))
{
lane_data[none_index].tag = TurnLaneType::straight;
}
}
return lane_data;
}
} // namespace
/*
Lanes can have the tag none. While a nice feature for visibility, it is a terrible feature
for parsing. None can be part of neighboring turns, or not. We have to look at both the
intersection and the lane data to see what turns we have to augment by the none-lanes
*/
LaneDataVector handleNoneValueAtSimpleTurn(LaneDataVector lane_data,
const Intersection &intersection)
{
const bool needs_no_processing =
(intersection.empty() || lane_data.empty() || !hasTag(TurnLaneType::none, lane_data));
if (needs_no_processing)
return lane_data;
// FIXME all this needs to consider the number of lanes at the target to ensure that we
// augment lanes correctly, if the target lane allows for more turns
//
// -----------------
//
// ----- ----
// -v |
// ----- |
// | | |
//
// A situation like this would allow a right turn from the through lane.
//
// -----------------
//
// ----- --------
// -v |
// ----- |
// | |
//
// Here, the number of lanes in the right road would not allow turns from both lanes, but
// only from the right one.
const std::size_t connection_count =
getNumberOfTurns(intersection) -
((intersection[0].entry_allowed && lane_data.back().tag != TurnLaneType::uturn) ? 1 : 0);
// TODO check for impossible turns to see whether the turn lane is at the correct place
const std::size_t none_index =
std::distance(lane_data.begin(), findTag(TurnLaneType::none, lane_data));
BOOST_ASSERT(none_index != lane_data.size());
// we have to create multiple turns
if (connection_count > lane_data.size())
{
lane_data =
augmentMultiple(none_index, connection_count, std::move(lane_data), intersection);
}
// we have to reduce it, assigning it to neighboring turns
else if (connection_count < lane_data.size())
{
// a pgerequisite is simple turns. Larger differences should not end up here
// an additional line at the side is only reasonable if it is targeting public
// service vehicles. Otherwise, we should not have it
if (connection_count + 1 == lane_data.size())
{
lane_data = mergeNoneTag(none_index, std::move(lane_data));
}
else
{
// This represents a currently unhandled case. It should not even get here, but to be
// sure we return nevertheless.
return lane_data;
}
}
// we have to rename and possibly augment existing ones. The pure count remains the
// same.
else
{
lane_data = handleRenamingSituations(none_index, std::move(lane_data), intersection);
}
// finally make sure we are still sorted
std::sort(lane_data.begin(), lane_data.end());
return lane_data;
}
} // namespace lanes
} // namespace guidance
} // namespace extractor
} // namespace osrm
-160
View File
@@ -1,160 +0,0 @@
#include "extractor/guidance/turn_lane_data.hpp"
#include "util/guidance/turn_lanes.hpp"
#include <boost/numeric/conversion/cast.hpp>
#include <algorithm>
#include <cstddef>
#include <unordered_map>
#include <utility>
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace lanes
{
bool TurnLaneData::operator<(const TurnLaneData &other) const
{
if (from < other.from)
return true;
if (from > other.from)
return false;
if (to < other.to)
return true;
if (to > other.to)
return false;
// the suppress-assignment flag is ignored, since it does not influence the order
const constexpr TurnLaneType::Mask tag_by_modifier[] = {TurnLaneType::sharp_right,
TurnLaneType::right,
TurnLaneType::slight_right,
TurnLaneType::straight,
TurnLaneType::slight_left,
TurnLaneType::left,
TurnLaneType::sharp_left,
TurnLaneType::uturn};
// U-Turns are supposed to be on the outside. So if the first lane is 0 and we are looking at a
// u-turn, it has to be on the very left. If it is equal to the number of lanes, it has to be on
// the right. These sorting function assume reverse to be on the outside always. Might need to
// be reconsidered if there are situations that offer a reverse from some middle lane (seems
// improbable)
if (tag == TurnLaneType::uturn)
{
if (from == 0)
return true;
else
return false;
}
if (other.tag == TurnLaneType::uturn)
{
if (other.from == 0)
return false;
else
return true;
}
return std::find(tag_by_modifier, tag_by_modifier + 8, this->tag) <
std::find(tag_by_modifier, tag_by_modifier + 8, other.tag);
}
LaneDataVector laneDataFromDescription(TurnLaneDescription turn_lane_description)
{
typedef std::unordered_map<TurnLaneType::Mask, std::pair<LaneID, LaneID>> LaneMap;
// TODO need to handle cases that have none-in between two identical values
const auto num_lanes = boost::numeric_cast<LaneID>(turn_lane_description.size());
const auto setLaneData = [&](
LaneMap &map, TurnLaneType::Mask full_mask, const LaneID current_lane) {
const auto isSet = [&](const TurnLaneType::Mask test_mask) -> bool {
return (test_mask & full_mask) == test_mask;
};
for (std::size_t shift = 0; shift < TurnLaneType::detail::num_supported_lane_types; ++shift)
{
TurnLaneType::Mask mask = 1 << shift;
if (isSet(mask))
{
auto map_iterator = map.find(mask);
if (map_iterator == map.end())
map[mask] = std::make_pair(current_lane, current_lane);
else
{
map_iterator->second.first = current_lane;
}
}
}
};
LaneMap lane_map;
LaneID lane_nr = num_lanes - 1;
if (turn_lane_description.empty())
return {};
for (auto full_mask : turn_lane_description)
{
setLaneData(lane_map, full_mask, lane_nr);
--lane_nr;
}
// transform the map into the lane data vector
LaneDataVector lane_data;
lane_data.reserve(lane_map.size());
for (const auto &tag : lane_map)
lane_data.push_back({tag.first, tag.second.first, tag.second.second});
std::sort(lane_data.begin(), lane_data.end());
// check whether a given turn lane string resulted in valid lane data
const auto hasValidOverlaps = [](const LaneDataVector &lane_data) {
// Allow an overlap of at most one. Larger overlaps would result in crossing another turn,
// which is invalid
for (std::size_t index = 1; index < lane_data.size(); ++index)
{
// u-turn located somewhere in the middle
// Right now we can only handle u-turns at the sides. If we find a u-turn somewhere in
// the middle of the tags, we abort the handling right here.
if (index + 1 < lane_data.size() &&
((lane_data[index].tag & TurnLaneType::uturn) != TurnLaneType::empty))
return false;
if (lane_data[index - 1].to > lane_data[index].from)
return false;
}
return true;
};
if (!hasValidOverlaps(lane_data))
lane_data.clear();
return lane_data;
}
LaneDataVector::iterator findTag(const TurnLaneType::Mask tag, LaneDataVector &data)
{
return std::find_if(data.begin(), data.end(), [&](const TurnLaneData &lane_data) {
return (tag & lane_data.tag) != TurnLaneType::empty;
});
}
LaneDataVector::const_iterator findTag(const TurnLaneType::Mask tag, const LaneDataVector &data)
{
return std::find_if(data.cbegin(), data.cend(), [&](const TurnLaneData &lane_data) {
return (tag & lane_data.tag) != TurnLaneType::empty;
});
}
bool hasTag(const TurnLaneType::Mask tag, const LaneDataVector &data)
{
return findTag(tag, data) != data.cend();
}
} // namespace lanes
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -1,819 +0,0 @@
#include "extractor/guidance/turn_lane_handler.hpp"
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/turn_discovery.hpp"
#include "extractor/guidance/turn_lane_augmentation.hpp"
#include "extractor/guidance/turn_lane_matcher.hpp"
#include "extractor/intersection/intersection_analysis.hpp"
#include "util/bearing.hpp"
#include "util/log.hpp"
#include "util/typedefs.hpp"
#include <cstddef>
#include <cstdint>
#include <boost/numeric/conversion/cast.hpp>
using osrm::util::angularDeviation;
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace lanes
{
namespace
{
std::size_t getNumberOfTurns(const Intersection &intersection)
{
return std::count_if(intersection.begin(), intersection.end(), [](const ConnectedRoad &road) {
return road.entry_allowed;
});
}
} // namespace
TurnLaneHandler::TurnLaneHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const EdgeBasedNodeDataContainer &node_data_container,
const std::vector<util::Coordinate> &node_coordinates,
const extractor::CompressedEdgeContainer &compressed_geometries,
const RestrictionMap &node_restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const guidance::TurnLanesIndexedArray &turn_lanes_data,
LaneDescriptionMap &lane_description_map,
const TurnAnalysis &turn_analysis,
util::guidance::LaneDataIdMap &id_map)
: node_based_graph(node_based_graph), node_data_container(node_data_container),
node_coordinates(node_coordinates), compressed_geometries(compressed_geometries),
node_restriction_map(node_restriction_map), barrier_nodes(barrier_nodes),
turn_lanes_data(turn_lanes_data), lane_description_map(lane_description_map),
turn_analysis(turn_analysis), id_map(id_map)
{
std::tie(turn_lane_offsets, turn_lane_masks) = turn_lanes_data;
count_handled = count_called = 0;
}
TurnLaneHandler::~TurnLaneHandler()
{
util::Log() << "Handled: " << count_handled << " of " << count_called
<< " lanes: " << (double)(count_handled * 100) / (count_called) << " %.";
}
/*
Turn lanes are given in the form of strings that closely correspond to the direction modifiers
we use for our turn types. However, we still cannot simply perform a 1:1 assignment.
This function parses the turn_lane_descriptions of a format that describes an intersection as:
----------
A -^
----------
B -> -v
----------
C -v
----------
witch is the result of a string like looking |left|through;right|right| and performs an
assignment onto the turns.
For example: (130, turn slight right), (180, ramp straight), (320, turn sharp left).
*/
Intersection
TurnLaneHandler::assignTurnLanes(const NodeID at, const EdgeID via_edge, Intersection intersection)
{
// if only a uturn exists, there is nothing we can do
if (intersection.size() == 1)
return intersection;
// A list of output parameters to be filled in during deduceScenario.
// Data for the current intersection
LaneDescriptionID lane_description_id = INVALID_LANE_DESCRIPTIONID;
LaneDataVector lane_data;
// Data for the previous intersection
NodeID previous_node = SPECIAL_NODEID;
EdgeID previous_via_edge = SPECIAL_EDGEID;
Intersection previous_intersection;
LaneDataVector previous_lane_data;
LaneDescriptionID previous_description_id = INVALID_LANE_DESCRIPTIONID;
const auto scenario = deduceScenario(at,
via_edge,
intersection,
lane_description_id,
lane_data,
previous_node,
previous_via_edge,
previous_intersection,
previous_lane_data,
previous_description_id);
if (scenario != TurnLaneScenario::NONE)
count_called++;
switch (scenario)
{
// A turn based on current lane data
case TurnLaneScenario::SIMPLE:
case TurnLaneScenario::PARTITION_LOCAL:
lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection);
return simpleMatchTuplesToTurns(std::move(intersection), lane_data, lane_description_id);
// Cases operating on data carried over from a previous lane
case TurnLaneScenario::SIMPLE_PREVIOUS:
case TurnLaneScenario::PARTITION_PREVIOUS:
previous_lane_data =
handleNoneValueAtSimpleTurn(std::move(previous_lane_data), intersection);
return simpleMatchTuplesToTurns(
std::move(intersection), previous_lane_data, previous_description_id);
// Sliproads-turns that are to be handled as a single entity
case TurnLaneScenario::SLIPROAD:
return handleSliproadTurn(std::move(intersection),
lane_description_id,
std::move(lane_data),
previous_intersection);
case TurnLaneScenario::MERGE:
return intersection;
default:
// All different values that we cannot handle. For some me might want to print debug output
// later on, when the handling is actually improved to work in close to all cases.
// case TurnLaneScenario::UNKNOWN:
// case TurnLaneScenario::NONE:
// case TurnLaneScenario::INVALID:
return intersection;
}
}
// Find out which scenario we have to handle
TurnLaneScenario TurnLaneHandler::deduceScenario(const NodeID at,
const EdgeID via_edge,
const Intersection &intersection,
// Output Variables
LaneDescriptionID &lane_description_id,
LaneDataVector &lane_data,
NodeID &previous_node,
EdgeID &previous_via_edge,
Intersection &previous_intersection,
LaneDataVector &previous_lane_data,
LaneDescriptionID &previous_description_id)
{
// as long as we don't want to emit lanes on roundabout, don't assign them
if (node_based_graph.GetEdgeData(via_edge).flags.roundabout)
return TurnLaneScenario::NONE;
// really don't touch roundabouts (#2626)
if (intersection.end() !=
std::find_if(intersection.begin(), intersection.end(), [](const auto &road) {
return hasRoundaboutType(road.instruction);
}))
return TurnLaneScenario::NONE;
// if only a uturn exists, there is nothing we can do
if (intersection.size() == 1)
return TurnLaneScenario::NONE;
extractLaneData(via_edge, lane_description_id, lane_data);
// traffic lights are not compressed during our preprocessing. Due to this *shortcoming*, we can
// get to the following situation:
//
// d
// a - b - c
// e
//
// with a traffic light at b and a-b as well as b-c offering the same turn lanes.
// In such a situation, we don't need to handle the lanes at a-b, since we will get the same
// information at b-c, where the actual turns are taking place.
const bool is_going_straight_and_turns_continue =
(intersection.size() == 2 &&
((lane_description_id != INVALID_LANE_DESCRIPTIONID &&
lane_description_id ==
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(intersection[1].eid).annotation_data)
.lane_description_id) &&
angularDeviation(intersection[1].angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE));
if (is_going_straight_and_turns_continue)
return TurnLaneScenario::NONE;
// if we see an invalid conversion, we stop immediately
if (lane_description_id != INVALID_LANE_DESCRIPTIONID && lane_data.empty())
return TurnLaneScenario::INVALID;
// might be reasonable to handle multiple turns, if we know of a sequence of lanes
// e.g. one direction per lane, if three lanes and right, through, left available
if (lane_description_id != INVALID_LANE_DESCRIPTIONID && lane_data.size() == 1 &&
lane_data[0].tag == TurnLaneType::none)
return TurnLaneScenario::NONE;
// Due to sliproads, we might need access to the previous intersection at this point already;
previous_node = SPECIAL_NODEID;
previous_via_edge = SPECIAL_EDGEID;
IntersectionView previous_intersection_view;
if (findPreviousIntersection(at,
via_edge,
intersection,
node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
previous_node,
previous_via_edge,
previous_intersection_view))
{
extractLaneData(previous_via_edge, previous_description_id, previous_lane_data);
previous_intersection = turn_analysis.AssignTurnTypes(
previous_node, previous_via_edge, previous_intersection_view);
for (std::size_t road_index = 0; road_index < previous_intersection.size(); ++road_index)
{
const auto &road = previous_intersection[road_index];
// in case of a sliproad that is connected to road of simlar angle, we handle the
// turn as a combined turn
if (road.instruction.type == TurnType::Sliproad)
{
if (via_edge == road.eid)
return TurnLaneScenario::SLIPROAD;
const auto &closest_road = [&]() {
if (road_index + 1 == previous_intersection.size())
{
BOOST_ASSERT(road_index > 1);
return previous_intersection[road_index - 1];
}
else if (road_index == 1)
{
BOOST_ASSERT(road_index + 1 < previous_intersection.size());
return previous_intersection[road_index + 1];
}
else if (angularDeviation(road.angle,
previous_intersection.at(road_index - 1).angle) <
angularDeviation(road.angle,
previous_intersection.at(road_index + 1).angle))
return previous_intersection[road_index - 1];
else
return previous_intersection[road_index + 1];
}();
if (via_edge == closest_road.eid)
return TurnLaneScenario::SLIPROAD;
}
}
}
const std::size_t possible_entries = getNumberOfTurns(intersection);
// merge does not justify an instruction
const bool has_merge_lane =
hasTag(TurnLaneType::merge_to_left | TurnLaneType::merge_to_right, lane_data);
if (has_merge_lane)
return TurnLaneScenario::MERGE;
// Dead end streets that don't have any left-tag. This can happen due to the fallbacks for
// broken data/barriers.
const bool has_non_usable_u_turn = (intersection[0].entry_allowed &&
!hasTag(TurnLaneType::none | TurnLaneType::left |
TurnLaneType::sharp_left | TurnLaneType::uturn,
lane_data) &&
lane_data.size() + 1 == possible_entries);
if (has_non_usable_u_turn)
return TurnLaneScenario::INVALID;
// check if a u-turn is allowed (for some reason) and is missing from the list of tags (u-turn
// is often allowed from the `left` lane without an additional indication dedicated to u-turns).
const bool is_missing_valid_u_turn =
!lane_data.empty() && canMatchTrivially(intersection, lane_data) &&
lane_data.size() !=
static_cast<std::size_t>((
!hasTag(TurnLaneType::uturn, lane_data) && intersection[0].entry_allowed ? 1 : 0)) +
possible_entries &&
intersection[0].entry_allowed;
// FIXME the lane to add depends on the side of driving/u-turn rules in the country
if (!lane_data.empty() && canMatchTrivially(intersection, lane_data) &&
is_missing_valid_u_turn && !hasTag(TurnLaneType::none, lane_data))
lane_data.push_back({TurnLaneType::uturn, lane_data.back().to, lane_data.back().to});
bool is_simple = isSimpleIntersection(lane_data, intersection);
if (is_simple)
return TurnLaneScenario::SIMPLE;
// In case of intersections that don't offer all turns right away, we have to account for
// *delayed* turns. Consider the following example:
//
// e
// a - b - c - f
// d
//
// With lanes on a-b indicating: | left | through | right |.
// While right obviously refers to a-b-d, through and left refer to a-b-c-f and a-b-c-e
// respectively. While we are at a-b, we have to consider the right turn only.
// The turn a-b-c gets assigned the lanes of both *left* and *through*.
// At b-c, we get access to either a new set of lanes, or -- via the previous intersection
// -- to
// the second part of | left | through | right |. Lane anticipation can then deduce which
// lanes correspond to what and suppress unnecessary instructions.
//
// For our initial case, we consider only the turns that are available at the current
// location, which are given by partitioning the lane data and selecting the first part.
if (!lane_data.empty())
{
if (lane_data.size() >= possible_entries)
{
lane_data = partitionLaneData(node_based_graph.GetTarget(via_edge),
std::move(lane_data),
intersection)
.first;
// check if we were successfull in trimming
if (lane_data.size() == possible_entries &&
isSimpleIntersection(lane_data, intersection))
return TurnLaneScenario::PARTITION_LOCAL;
}
// If partitioning doesn't solve the problem, we don't know how to handle it right now
return TurnLaneScenario::UNKNOWN;
}
if (lane_description_id != INVALID_LANE_DESCRIPTIONID)
return TurnLaneScenario::UNKNOWN;
// acquire the lane data of a previous segment and, if possible, use it for the current
// intersection.
if (previous_via_edge == SPECIAL_EDGEID)
return TurnLaneScenario::NONE;
if (previous_lane_data.empty())
return TurnLaneScenario::NONE;
const bool previous_has_merge_lane =
hasTag(TurnLaneType::merge_to_left | TurnLaneType::merge_to_right, previous_lane_data);
if (previous_has_merge_lane)
return TurnLaneScenario::MERGE;
const auto is_simple_previous =
isSimpleIntersection(previous_lane_data, intersection) && previous_intersection.size() == 2;
if (is_simple_previous)
return TurnLaneScenario::SIMPLE_PREVIOUS;
// This is the second part of the previously described partitioning scenario.
if (previous_lane_data.size() >= getNumberOfTurns(previous_intersection) &&
previous_intersection.size() != 2)
{
previous_lane_data = partitionLaneData(node_based_graph.GetTarget(previous_via_edge),
std::move(previous_lane_data),
previous_intersection)
.second;
std::sort(previous_lane_data.begin(), previous_lane_data.end());
// check if we were successfull in trimming
if ((previous_lane_data.size() == possible_entries) &&
isSimpleIntersection(previous_lane_data, intersection))
return TurnLaneScenario::PARTITION_PREVIOUS;
}
return TurnLaneScenario::UNKNOWN;
}
void TurnLaneHandler::extractLaneData(const EdgeID via_edge,
LaneDescriptionID &lane_description_id,
LaneDataVector &lane_data) const
{
const auto &edge_data =
node_data_container.GetAnnotation(node_based_graph.GetEdgeData(via_edge).annotation_data);
lane_description_id = edge_data.lane_description_id;
// create an empty lane data
if (INVALID_LANE_DESCRIPTIONID != lane_description_id)
{
const auto lane_description = TurnLaneDescription(
turn_lane_masks.begin() + turn_lane_offsets[lane_description_id],
turn_lane_masks.begin() + turn_lane_offsets[lane_description_id + 1]);
lane_data = laneDataFromDescription(lane_description);
BOOST_ASSERT(lane_description.size() == (turn_lane_offsets[lane_description_id + 1] -
turn_lane_offsets[lane_description_id]));
}
else
{
lane_data.clear();
}
}
/* A simple intersection does not depend on the next intersection coming up. This is important
* for turn lanes, since traffic signals and/or segregated a intersection can influence the
* interpretation of turn-lanes at a given turn.
*
* Here we check for a simple intersection. A simple intersection has a long enough segment
* followin the turn, offers no straight turn, or only non-trivial turn operations.
*/
bool TurnLaneHandler::isSimpleIntersection(const LaneDataVector &lane_data,
const Intersection &intersection) const
{
if (lane_data.empty())
return false;
// if we are on a straight road, turn lanes are only reasonable in connection to the next
// intersection, or in case of a merge. If not all but one (straight) are merges, we don't
// consider the intersection simple
if (intersection.size() == 2)
{
return std::count_if(
lane_data.begin(),
lane_data.end(),
[](const TurnLaneData &data) {
return ((data.tag & TurnLaneType::merge_to_left) != TurnLaneType::empty) ||
((data.tag & TurnLaneType::merge_to_right) != TurnLaneType::empty);
}) +
std::size_t{1} >=
lane_data.size();
}
// in case an intersection offers far more lane data items than actual turns, some of them
// have to be for another intersection. A single additional item can be for an invalid bus lane.
const auto num_turns = [&]() {
auto count = getNumberOfTurns(intersection);
if (count < lane_data.size() && !intersection[0].entry_allowed &&
lane_data.back().tag == TurnLaneType::uturn)
return count + 1;
return count;
}();
// more than two additional lane data entries -> lanes target a different intersection
if (num_turns + std::size_t{2} <= lane_data.size())
{
return false;
}
// single additional lane data entry is alright, if it is none at the side. This usually
// refers to a bus-lane
if (num_turns + std::size_t{1} == lane_data.size() &&
lane_data.front().tag != TurnLaneType::none && lane_data.back().tag != TurnLaneType::none)
{
return false;
}
// more turns than lane data
if (num_turns > lane_data.size() &&
lane_data.end() ==
std::find_if(lane_data.begin(), lane_data.end(), [](const TurnLaneData &data) {
return data.tag == TurnLaneType::none;
}))
{
return false;
}
if (num_turns > lane_data.size() && intersection[0].entry_allowed &&
!(hasTag(TurnLaneType::uturn, lane_data) ||
(lane_data.back().tag != TurnLaneType::left &&
lane_data.back().tag != TurnLaneType::sharp_left)))
{
return false;
}
// check if we can find a valid 1:1 mapping in a straightforward manner
bool all_simple = true;
bool has_none = false;
std::unordered_set<std::size_t> matched_indices;
for (std::size_t data_index = 0; data_index < lane_data.size(); ++data_index)
{
const auto &data = lane_data[data_index];
if (data.tag == TurnLaneType::none)
{
has_none = true;
continue;
}
// u-turn tags are at the outside of the lane-tags and require special handling, since
// locating their best match requires knowledge on the neighboring tag. (see documentation
// on findBestMatch/findBestMatchForReverse
const auto best_match = [&]() {
// normal tag or u-turn as only choice (no other tag present)
if (data.tag != TurnLaneType::uturn || lane_data.size() == 1)
return findBestMatch(data.tag, intersection);
BOOST_ASSERT(data.tag == TurnLaneType::uturn);
// u-turn at the very left, leftmost turn at data_index - 1
if (data_index + 1 == lane_data.size())
return findBestMatchForReverse(lane_data[data_index - 1].tag, intersection);
// u-turn to the right (left-handed driving) -> rightmost turn to the left (data_index +
// 1)
if (data_index == 0)
return findBestMatchForReverse(lane_data[data_index + 1].tag, intersection);
return intersection.begin();
}();
BOOST_ASSERT(best_match != intersection.end());
std::size_t match_index = std::distance(intersection.begin(), best_match);
all_simple &= (matched_indices.count(match_index) == 0);
matched_indices.insert(match_index);
// in case of u-turns, we might need to activate them first
all_simple &= (best_match->entry_allowed ||
// check for possible u-turn match on non-reversed edge
((match_index == 0 || match_index + 1 == intersection.size()) &&
!node_based_graph.GetEdgeData(best_match->eid).reversed));
all_simple &= isValidMatch(data.tag, best_match->instruction);
}
// either all indices are matched, or we have a single none-value
if (all_simple && (matched_indices.size() == lane_data.size() ||
(matched_indices.size() + 1 == lane_data.size() && has_none)))
return true;
// better save than sorry
return false;
}
std::pair<LaneDataVector, LaneDataVector> TurnLaneHandler::partitionLaneData(
const NodeID at, LaneDataVector turn_lane_data, const Intersection &intersection) const
{
BOOST_ASSERT(turn_lane_data.size() >= getNumberOfTurns(intersection));
/*
* A Segregated intersection can provide turn lanes for turns that are not yet possible.
* The straightforward example would be coming up to the following situation:
* (1) (2)
* | A | | A |
* | | | | ^ |
* | v | | | |
* ------- ----------- ------
* B ->-^ B
* ------- ----------- ------
* B ->-v B
* ------- ----------- ------
* | A | | A |
*
* Traveling on road B, we have to pass A at (1) to turn left onto A at (2). The turn
* lane itself may only be specified prior to (1) and/or could be repeated between (1)
* and (2). To make sure to announce the lane correctly, we need to treat the (in this
* case left) turn lane as if it were to continue straight onto the intersection and
* look back between (1) and (2) to make sure we find the correct lane for the left-turn.
*
* Intersections like these have two parts. Turns that can be made at the first intersection
* and turns that have to be made at the second. The partitioning returns the lane data split
* into two parts, one for the first and one for the second intersection.
*/
// Try and maitch lanes to available turns. For Turns that are not directly matchable, check
// whether we can match them at the upcoming intersection.
const auto straightmost = intersection.findClosestTurn(STRAIGHT_ANGLE);
BOOST_ASSERT(straightmost < intersection.cend());
// we need to be able to enter the straightmost turn
if (!straightmost->entry_allowed)
return {turn_lane_data, {}};
std::vector<bool> matched_at_first(turn_lane_data.size(), false);
std::vector<bool> matched_at_second(turn_lane_data.size(), false);
// find out about the next intersection. To check for valid matches, we also need the turn
// types. We can skip merging/angle adjustments, though
const auto next_intersection = turn_analysis.AssignTurnTypes(
at,
straightmost->eid,
intersection::getConnectedRoads<false>(node_based_graph,
node_data_container,
node_coordinates,
compressed_geometries,
node_restriction_map,
barrier_nodes,
turn_lanes_data,
{at, straightmost->eid}));
// check where we can match turn lanes
std::size_t straightmost_tag_index = turn_lane_data.size();
for (std::size_t lane = 0; lane < turn_lane_data.size(); ++lane)
{
if ((turn_lane_data[lane].tag & (TurnLaneType::none | TurnLaneType::uturn)) !=
TurnLaneType::empty)
continue;
const auto best_match = findBestMatch(turn_lane_data[lane].tag, intersection);
if (best_match->entry_allowed &&
isValidMatch(turn_lane_data[lane].tag, best_match->instruction))
{
matched_at_first[lane] = true;
if (straightmost == best_match)
straightmost_tag_index = lane;
}
const auto best_match_at_next_intersection =
findBestMatch(turn_lane_data[lane].tag, next_intersection);
if (best_match_at_next_intersection->entry_allowed &&
isValidMatch(turn_lane_data[lane].tag, best_match_at_next_intersection->instruction))
{
if (!matched_at_first[lane] || turn_lane_data[lane].tag == TurnLaneType::straight ||
getMatchingQuality(turn_lane_data[lane].tag, *best_match) >
getMatchingQuality(turn_lane_data[lane].tag, *best_match_at_next_intersection))
{
if (turn_lane_data[lane].tag != TurnLaneType::straight)
matched_at_first[lane] = false;
matched_at_second[lane] = true;
}
}
// we need to match all items to either the current or the next intersection
if (!(matched_at_first[lane] || matched_at_second[lane]))
return {turn_lane_data, {}};
}
std::size_t none_index =
std::distance(turn_lane_data.begin(), findTag(TurnLaneType::none, turn_lane_data));
// if the turn lanes are pull forward, we might have to add an additional straight tag
// did we find something that matches against the straightmost road?
if (straightmost_tag_index == turn_lane_data.size())
{
if (none_index != turn_lane_data.size())
straightmost_tag_index = none_index;
}
// handle none values
if (none_index != turn_lane_data.size())
{
if (static_cast<std::size_t>(
std::count(matched_at_first.begin(), matched_at_first.end(), true)) <=
getNumberOfTurns(intersection))
matched_at_first[none_index] = true;
if (static_cast<std::size_t>(
std::count(matched_at_second.begin(), matched_at_second.end(), true)) <=
getNumberOfTurns(next_intersection))
matched_at_second[none_index] = true;
}
const auto augmentEntry = [&](TurnLaneData &data) {
for (std::size_t lane = 0; lane < turn_lane_data.size(); ++lane)
if (matched_at_second[lane])
{
data.from = std::min(turn_lane_data[lane].from, data.from);
data.to = std::max(turn_lane_data[lane].to, data.to);
}
};
LaneDataVector first, second;
first.reserve(turn_lane_data.size());
second.reserve(turn_lane_data.size());
for (std::size_t lane = 0; lane < turn_lane_data.size(); ++lane)
{
if (matched_at_second[lane])
second.push_back(turn_lane_data[lane]);
// augment straightmost at this intersection to match all turns that happen at the next
if (lane == straightmost_tag_index)
{
augmentEntry(turn_lane_data[straightmost_tag_index]);
}
if (matched_at_first[lane])
first.push_back(turn_lane_data[lane]);
}
if (straightmost_tag_index == turn_lane_data.size() &&
static_cast<std::size_t>(
std::count(matched_at_second.begin(), matched_at_second.end(), true)) ==
getNumberOfTurns(next_intersection))
{
TurnLaneData data = {TurnLaneType::straight, 255, 0};
augmentEntry(data);
first.push_back(data);
std::sort(first.begin(), first.end());
}
// TODO augment straightmost turn
return {std::move(first), std::move(second)};
}
Intersection TurnLaneHandler::simpleMatchTuplesToTurns(Intersection intersection,
const LaneDataVector &lane_data,
const LaneDescriptionID lane_description_id)
{
if (lane_data.empty() || !canMatchTrivially(intersection, lane_data))
return intersection;
BOOST_ASSERT(
!hasTag(TurnLaneType::none | TurnLaneType::merge_to_left | TurnLaneType::merge_to_right,
lane_data));
count_handled++;
return triviallyMatchLanesToTurns(
std::move(intersection), lane_data, node_based_graph, lane_description_id, id_map);
}
Intersection TurnLaneHandler::handleSliproadTurn(Intersection intersection,
const LaneDescriptionID lane_description_id,
LaneDataVector lane_data,
const Intersection &previous_intersection)
{
const std::size_t sliproad_index =
std::distance(previous_intersection.begin(),
std::find_if(previous_intersection.begin(),
previous_intersection.end(),
[](const ConnectedRoad &road) {
return road.instruction.type == TurnType::Sliproad;
}));
BOOST_ASSERT(sliproad_index <= previous_intersection.size());
const auto &sliproad = previous_intersection[sliproad_index];
// code duplicatino with deduceScenario: TODO refactor
const auto &main_road = [&]() {
if (sliproad_index + 1 == previous_intersection.size())
{
BOOST_ASSERT(sliproad_index > 1);
return previous_intersection[sliproad_index - 1];
}
else if (sliproad_index == 1)
{
BOOST_ASSERT(sliproad_index + 1 < previous_intersection.size());
return previous_intersection[sliproad_index + 1];
}
else if (angularDeviation(sliproad.angle,
previous_intersection.at(sliproad_index - 1).angle) <
angularDeviation(sliproad.angle,
previous_intersection.at(sliproad_index + 1).angle))
return previous_intersection[sliproad_index - 1];
else
return previous_intersection[sliproad_index + 1];
}();
const auto main_description_id =
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(main_road.eid).annotation_data)
.lane_description_id;
const auto sliproad_description_id =
node_data_container
.GetAnnotation(node_based_graph.GetEdgeData(sliproad.eid).annotation_data)
.lane_description_id;
if (main_description_id == INVALID_LANE_DESCRIPTIONID ||
sliproad_description_id == INVALID_LANE_DESCRIPTIONID)
return intersection;
TurnLaneDescription combined_description;
// is the sliproad going off to the right?
if (main_road.angle > sliproad.angle)
{
combined_description.insert(
combined_description.end(),
turn_lane_masks.begin() + turn_lane_offsets[main_description_id],
turn_lane_masks.begin() + turn_lane_offsets[main_description_id + 1]);
combined_description.insert(
combined_description.end(),
turn_lane_masks.begin() + turn_lane_offsets[sliproad_description_id],
turn_lane_masks.begin() + turn_lane_offsets[sliproad_description_id + 1]);
// if we handle the main road, we have to adjust the lane-data
if (main_description_id == lane_description_id)
{
const auto offset = turn_lane_offsets[sliproad_description_id + 1] -
turn_lane_offsets[sliproad_description_id];
for (auto &item : lane_data)
{
item.from += offset;
item.to += offset;
}
}
}
// or to the left?
else
{
combined_description.insert(
combined_description.end(),
turn_lane_masks.begin() + turn_lane_offsets[sliproad_description_id],
turn_lane_masks.begin() + turn_lane_offsets[sliproad_description_id + 1]);
combined_description.insert(
combined_description.end(),
turn_lane_masks.begin() + turn_lane_offsets[main_description_id],
turn_lane_masks.begin() + turn_lane_offsets[main_description_id + 1]);
// if we are handling the sliproad, we have to adjust its lane data
if (sliproad_description_id == lane_description_id)
{
const auto offset =
turn_lane_offsets[main_description_id + 1] - turn_lane_offsets[main_description_id];
for (auto &item : lane_data)
{
item.from += offset;
item.to += offset;
}
}
}
const auto combined_id = lane_description_map.ConcurrentFindOrAdd(combined_description);
return simpleMatchTuplesToTurns(std::move(intersection), lane_data, combined_id);
}
} // namespace lanes
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -1,281 +0,0 @@
#include "extractor/guidance/turn_lane_matcher.hpp"
#include "util/bearing.hpp"
#include <boost/assert.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <functional>
using osrm::util::angularDeviation;
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace lanes
{
// Translate Turn Tags into a Matching Direction Modifier
DirectionModifier::Enum getMatchingModifier(const TurnLaneType::Mask tag)
{
const constexpr TurnLaneType::Mask tag_by_modifier[] = {TurnLaneType::uturn,
TurnLaneType::sharp_right,
TurnLaneType::right,
TurnLaneType::slight_right,
TurnLaneType::straight,
TurnLaneType::slight_left,
TurnLaneType::left,
TurnLaneType::sharp_left,
TurnLaneType::merge_to_left,
TurnLaneType::merge_to_right};
const auto index =
std::distance(tag_by_modifier, std::find(tag_by_modifier, tag_by_modifier + 10, tag));
BOOST_ASSERT(index <= 10);
const constexpr DirectionModifier::Enum modifiers[11] = {
DirectionModifier::UTurn,
DirectionModifier::SharpRight,
DirectionModifier::Right,
DirectionModifier::SlightRight,
DirectionModifier::Straight,
DirectionModifier::SlightLeft,
DirectionModifier::Left,
DirectionModifier::SharpLeft,
DirectionModifier::Straight,
DirectionModifier::Straight,
DirectionModifier::UTurn}; // fallback for invalid tags
return modifiers[index];
}
// check whether a match of a given tag and a turn instruction can be seen as valid
bool isValidMatch(const TurnLaneType::Mask tag, const TurnInstruction instruction)
{
using extractor::guidance::hasLeftModifier;
using extractor::guidance::hasRightModifier;
const auto isMirroredModifier = [](const TurnInstruction instruction) {
return instruction.type == TurnType::Merge;
};
if (tag == TurnLaneType::uturn)
{
return hasLeftModifier(instruction) ||
instruction.direction_modifier == DirectionModifier::UTurn;
}
else if (tag == TurnLaneType::sharp_right || tag == TurnLaneType::right ||
tag == TurnLaneType::slight_right)
{
if (isMirroredModifier(instruction))
return hasLeftModifier(instruction);
else
// needs to be adjusted for left side driving
return leavesRoundabout(instruction) || hasRightModifier(instruction);
}
else if (tag == TurnLaneType::straight)
{
return instruction.direction_modifier == DirectionModifier::Straight ||
instruction.type == TurnType::Suppressed || instruction.type == TurnType::NewName ||
instruction.type == TurnType::StayOnRoundabout || entersRoundabout(instruction) ||
(instruction.type ==
TurnType::Fork && // Forks can be experienced, even for straight segments
(instruction.direction_modifier == DirectionModifier::SlightLeft ||
instruction.direction_modifier == DirectionModifier::SlightRight)) ||
(instruction.type ==
TurnType::Continue && // Forks can be experienced, even for straight segments
(instruction.direction_modifier == DirectionModifier::SlightLeft ||
instruction.direction_modifier == DirectionModifier::SlightRight));
}
else if (tag == TurnLaneType::slight_left || tag == TurnLaneType::left ||
tag == TurnLaneType::sharp_left)
{
if (isMirroredModifier(instruction))
return hasRightModifier(instruction);
else
{
// Needs to be fixed for left side driving
return (instruction.type == TurnType::StayOnRoundabout) || hasLeftModifier(instruction);
}
}
return false;
}
double getMatchingQuality(const TurnLaneType::Mask tag, const ConnectedRoad &road)
{
const constexpr double idealized_turn_angles[] = {0, 35, 90, 135, 180, 225, 270, 315};
const auto modifier = getMatchingModifier(tag);
BOOST_ASSERT(static_cast<std::size_t>(modifier) <
sizeof(idealized_turn_angles) / sizeof(*idealized_turn_angles));
const auto idealized_angle = idealized_turn_angles[modifier];
return angularDeviation(idealized_angle, road.angle);
}
// Every tag is somewhat idealized in form of the expected angle. A through lane should go straight
// (or follow a 180 degree turn angle between in/out segments.) The following function tries to find
// the best possible match for every tag in a given intersection, considering a few corner cases
// introduced to OSRM handling u-turns
typename Intersection::const_iterator findBestMatch(const TurnLaneType::Mask tag,
const Intersection &intersection)
{
return std::min_element(intersection.begin(),
intersection.end(),
[tag](const ConnectedRoad &lhs, const ConnectedRoad &rhs) {
// prefer valid matches
if (isValidMatch(tag, lhs.instruction) !=
isValidMatch(tag, rhs.instruction))
return isValidMatch(tag, lhs.instruction);
// if the entry allowed flags don't match, we select the one with
// entry allowed set to true
if (lhs.entry_allowed != rhs.entry_allowed)
return lhs.entry_allowed;
return getMatchingQuality(tag, lhs) < getMatchingQuality(tag, rhs);
});
}
// Reverse is a special case, because it requires access to the leftmost tag. It has its own
// matching function as a result of that. The leftmost tag is required, since u-turns are disabled
// by default in OSRM. Therefor we cannot check whether a turn is allowed, since it could be
// possible that it is forbidden. In addition, the best u-turn angle does not necessarily represent
// the u-turn, since it could be a sharp-left turn instead on a road with a middle island.
typename Intersection::const_iterator findBestMatchForReverse(const TurnLaneType::Mask neighbor_tag,
const Intersection &intersection)
{
const auto neighbor_itr = findBestMatch(neighbor_tag, intersection);
if (neighbor_itr + 1 == intersection.cend())
return intersection.begin();
const TurnLaneType::Mask tag = TurnLaneType::uturn;
return std::min_element(
intersection.begin() + std::distance(intersection.begin(), neighbor_itr),
intersection.end(),
[tag](const ConnectedRoad &lhs, const ConnectedRoad &rhs) {
// prefer valid matches
if (isValidMatch(tag, lhs.instruction) != isValidMatch(tag, rhs.instruction))
return isValidMatch(tag, lhs.instruction);
// if the entry allowed flags don't match, we select the one with
// entry allowed set to true
if (lhs.entry_allowed != rhs.entry_allowed)
return lhs.entry_allowed;
return getMatchingQuality(tag, lhs) < getMatchingQuality(tag, rhs);
});
}
// a match is trivial if all turns can be associated with their best match in a valid way and the
// matches occur in order
bool canMatchTrivially(const Intersection &intersection, const LaneDataVector &lane_data)
{
std::size_t road_index = 1, lane = 0;
if (!lane_data.empty() && lane_data.front().tag == TurnLaneType::uturn)
{
// the very first is a u-turn to the right
if (intersection[0].entry_allowed)
lane = 1;
}
for (; road_index < intersection.size() && lane < lane_data.size(); ++road_index)
{
if (intersection[road_index].entry_allowed)
{
BOOST_ASSERT(lane_data[lane].from != INVALID_LANEID);
if (!isValidMatch(lane_data[lane].tag, intersection[road_index].instruction))
return false;
if (findBestMatch(lane_data[lane].tag, intersection) !=
intersection.begin() + road_index)
return false;
++lane;
}
}
return lane == lane_data.size() ||
(lane + 1 == lane_data.size() && lane_data.back().tag == TurnLaneType::uturn);
}
Intersection triviallyMatchLanesToTurns(Intersection intersection,
const LaneDataVector &lane_data,
const util::NodeBasedDynamicGraph &node_based_graph,
const LaneDescriptionID lane_string_id,
util::guidance::LaneDataIdMap &lane_data_to_id)
{
std::size_t road_index = 1, lane = 0;
const auto matchRoad = [&](ConnectedRoad &road, const TurnLaneData &data) {
util::guidance::LaneTupleIdPair key{{LaneID(data.to - data.from + 1), data.from},
lane_string_id};
road.lane_data_id = lane_data_to_id.ConcurrentFindOrAdd(key);
};
if (!lane_data.empty() && lane_data.front().tag == TurnLaneType::uturn)
{
// the very first is a u-turn to the right
if (intersection[0].entry_allowed)
{
std::size_t u_turn = 0;
if (node_based_graph.GetEdgeData(intersection[0].eid).reversed)
{
if (intersection.size() <= 1 || !intersection[1].entry_allowed ||
intersection[1].instruction.direction_modifier != DirectionModifier::SharpRight)
{
// cannot match u-turn in a valid way
return intersection;
}
u_turn = 1;
road_index = 2;
}
intersection[u_turn].instruction.type = TurnType::Continue;
intersection[u_turn].instruction.direction_modifier = DirectionModifier::UTurn;
matchRoad(intersection[u_turn], lane_data.back());
// continue with the first lane
lane = 1;
}
else
return intersection;
}
for (; road_index < intersection.size() && lane < lane_data.size(); ++road_index)
{
if (intersection[road_index].entry_allowed)
{
BOOST_ASSERT(lane_data[lane].from != INVALID_LANEID);
BOOST_ASSERT(isValidMatch(lane_data[lane].tag, intersection[road_index].instruction));
BOOST_ASSERT(findBestMatch(lane_data[lane].tag, intersection) ==
intersection.begin() + road_index);
matchRoad(intersection[road_index], lane_data[lane]);
++lane;
}
}
// handle reverse tag, if present
if (lane + 1 == lane_data.size() && lane_data.back().tag == TurnLaneType::uturn)
{
std::size_t u_turn = 0;
if (node_based_graph.GetEdgeData(intersection[0].eid).reversed)
{
if (!intersection.back().entry_allowed ||
intersection.back().instruction.direction_modifier != DirectionModifier::SharpLeft)
{
// cannot match u-turn in a valid way
return intersection;
}
u_turn = intersection.size() - 1;
}
intersection[u_turn].instruction.type = TurnType::Continue;
intersection[u_turn].instruction.direction_modifier = DirectionModifier::UTurn;
matchRoad(intersection[u_turn], lane_data.back());
}
return intersection;
}
} // namespace lane_matching
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -4,7 +4,7 @@
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
#include "extractor/guidance/coordinate_extractor.hpp"
#include "guidance/coordinate_extractor.hpp"
#include <boost/optional/optional_io.hpp>