Move guidance pre-processing code into GUIDANCE library
This commit is contained in:
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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 ¢er,
|
||||
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
|
||||
@@ -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 ¤t,
|
||||
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 = [¤t](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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ¤t_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user