Refactors and improves the Sliproad Handler, resolves #3109
This commit is contained in:
committed by
Patrick Niklaus
parent
df3c39cef5
commit
875f482203
@@ -4,11 +4,14 @@
|
||||
#include "util/coordinate_calculation.hpp"
|
||||
#include "util/guidance/name_announcements.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData;
|
||||
using osrm::extractor::guidance::getTurnDirection;
|
||||
using osrm::util::angularDeviation;
|
||||
|
||||
@@ -32,7 +35,8 @@ SliproadHandler::SliproadHandler(const IntersectionGenerator &intersection_gener
|
||||
{
|
||||
}
|
||||
|
||||
// included for interface reasons only
|
||||
// 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
|
||||
@@ -40,261 +44,699 @@ bool SliproadHandler::canProcess(const NodeID /*nid*/,
|
||||
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, const EdgeID source_edge_id, Intersection intersection) const
|
||||
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);
|
||||
|
||||
// if there is no turn, there is no sliproad
|
||||
if (intersection.size() <= 2)
|
||||
return intersection;
|
||||
// Road index prefering non-sliproads (bc)
|
||||
auto obvious = getObviousIndexWithSliproads(source_edge_id, intersection, intersection_node_id);
|
||||
|
||||
const auto findNextIntersectionForRoad =
|
||||
[&](const NodeID at_node, const ConnectedRoad &road, NodeID &output_node) {
|
||||
auto intersection = intersection_generator(at_node, road.eid);
|
||||
auto in_edge = road.eid;
|
||||
// skip over traffic lights
|
||||
// to prevent ending up in an endless loop, we remember all visited nodes. This is
|
||||
// necessary, since merging of roads can actually create enterable loops of degree two
|
||||
std::unordered_set<NodeID> visited_nodes;
|
||||
auto node = at_node;
|
||||
while (intersection.size() == 2 && visited_nodes.count(node) == 0)
|
||||
{
|
||||
visited_nodes.insert(node);
|
||||
node = node_based_graph.GetTarget(in_edge);
|
||||
if (node == at_node)
|
||||
{
|
||||
// we ended up in a loop without exit
|
||||
output_node = SPECIAL_NODEID;
|
||||
intersection.clear();
|
||||
return intersection;
|
||||
}
|
||||
in_edge = intersection[1].eid;
|
||||
output_node = node_based_graph.GetTarget(in_edge);
|
||||
intersection = intersection_generator(node, in_edge);
|
||||
}
|
||||
if (intersection.size() <= 2)
|
||||
{
|
||||
output_node = SPECIAL_NODEID;
|
||||
intersection.clear();
|
||||
}
|
||||
return intersection;
|
||||
};
|
||||
|
||||
const std::size_t obvious_turn_index = [&]() -> std::size_t {
|
||||
const auto index = findObviousTurn(source_edge_id, intersection);
|
||||
if (index != 0)
|
||||
return index;
|
||||
else if (intersection.size() == 3 && intersection[1].instruction.type == TurnType::Fork)
|
||||
{
|
||||
// Forks themselves do not contain a `obvious` turn index. If we look at a fork that has
|
||||
// a one-sided sliproad, however, the non-sliproad can be considered `obvious`. Here we
|
||||
// assume that this could be the case and check for a potential sliproad/non-sliproad
|
||||
// situation.
|
||||
NodeID intersection_node_one = SPECIAL_NODEID, intersection_node_two = SPECIAL_NODEID;
|
||||
const auto intersection_following_index_one = findNextIntersectionForRoad(
|
||||
intersection_node_id, intersection[1], intersection_node_one);
|
||||
const auto intersection_following_index_two = findNextIntersectionForRoad(
|
||||
intersection_node_id, intersection[2], intersection_node_two);
|
||||
// in case of broken roads, we return
|
||||
if (intersection_following_index_one.empty() ||
|
||||
intersection_following_index_two.empty())
|
||||
return 0;
|
||||
|
||||
// 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.
|
||||
|
||||
// a sliproad has to enter a road without choice
|
||||
const auto couldBeSliproad = [](const IntersectionView &intersection) {
|
||||
if (intersection.size() != 3)
|
||||
return false;
|
||||
if ((intersection[1].entry_allowed && intersection[2].entry_allowed) ||
|
||||
intersection[0].entry_allowed)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (couldBeSliproad(intersection_following_index_one) &&
|
||||
intersection_node_id != intersection_node_two)
|
||||
return 2;
|
||||
else if (couldBeSliproad(intersection_following_index_two) &&
|
||||
intersection_node_id != intersection_node_one)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}();
|
||||
|
||||
if (obvious_turn_index == 0)
|
||||
return intersection;
|
||||
|
||||
const auto &next_road = intersection[obvious_turn_index];
|
||||
|
||||
const auto linkTest = [this, next_road](const ConnectedRoad &road) {
|
||||
return !node_based_graph.GetEdgeData(road.eid).roundabout && road.entry_allowed &&
|
||||
angularDeviation(road.angle, STRAIGHT_ANGLE) <= 2 * NARROW_TURN_ANGLE &&
|
||||
!hasRoundaboutType(road.instruction) &&
|
||||
angularDeviation(next_road.angle, road.angle) >
|
||||
std::numeric_limits<double>::epsilon();
|
||||
};
|
||||
|
||||
bool hasNarrow =
|
||||
std::find_if(intersection.begin(), intersection.end(), linkTest) != intersection.end();
|
||||
if (!hasNarrow)
|
||||
return intersection;
|
||||
|
||||
const auto source_edge_data = node_based_graph.GetEdgeData(source_edge_id);
|
||||
// check whether the continue road is valid
|
||||
const auto check_valid = [this, source_edge_data](const ConnectedRoad &road) {
|
||||
const auto road_edge_data = node_based_graph.GetEdgeData(road.eid);
|
||||
// Test to see if the source edge and the one we're looking at are the same road
|
||||
|
||||
const auto same_name = !util::guidance::requiresNameAnnounced(
|
||||
road_edge_data.name_id, source_edge_data.name_id, name_table, street_name_suffix_table);
|
||||
|
||||
return road_edge_data.road_classification == source_edge_data.road_classification &&
|
||||
road_edge_data.name_id != EMPTY_NAMEID && source_edge_data.name_id != EMPTY_NAMEID &&
|
||||
same_name && road.entry_allowed;
|
||||
};
|
||||
|
||||
if (!check_valid(next_road))
|
||||
return intersection;
|
||||
|
||||
// Threshold check, if the intersection is too far away, don't bother continuing
|
||||
const auto coordinate_extractor = intersection_generator.GetCoordinateExtractor();
|
||||
const auto next_road_length = util::coordinate_calculation::getLength(
|
||||
coordinate_extractor.GetForwardCoordinatesAlongRoad(
|
||||
node_based_graph.GetTarget(source_edge_id), next_road.eid),
|
||||
&util::coordinate_calculation::haversineDistance);
|
||||
if (next_road_length > MAX_SLIPROAD_THRESHOLD)
|
||||
if (!obvious)
|
||||
{
|
||||
return intersection;
|
||||
}
|
||||
auto next_intersection_node = node_based_graph.GetTarget(next_road.eid);
|
||||
|
||||
const auto next_road_next_intersection =
|
||||
findNextIntersectionForRoad(intersection_node_id, next_road, next_intersection_node);
|
||||
// Potential non-sliproad road (bc), leading to the intersection (c) the Sliproad (bd) shortcuts
|
||||
const auto &main_road = intersection[*obvious];
|
||||
|
||||
if (next_road_next_intersection.empty())
|
||||
// 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);
|
||||
|
||||
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 == next_intersection_node)
|
||||
return intersection;
|
||||
|
||||
std::unordered_set<NameID> target_road_names;
|
||||
|
||||
for (const auto &road : next_road_next_intersection)
|
||||
if (intersection_node_id == main_road_intersection->node)
|
||||
{
|
||||
const auto &target_data = node_based_graph.GetEdgeData(road.eid);
|
||||
target_road_names.insert(target_data.name_id);
|
||||
return intersection;
|
||||
}
|
||||
|
||||
for (auto &road : 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)
|
||||
{
|
||||
if (linkTest(road))
|
||||
const auto &target_data = node_based_graph.GetEdgeData(road.eid);
|
||||
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_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();
|
||||
|
||||
if (is_right_sliproad_turn)
|
||||
return main_road_intersection->intersection.getRightmostRoad();
|
||||
|
||||
BOOST_ASSERT_MSG(false, "Sliproad is neither a left nor right of obvious main road");
|
||||
return main_road_intersection->intersection.getLeftmostRoad();
|
||||
}();
|
||||
|
||||
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)
|
||||
{
|
||||
EdgeID candidate_in = road.eid;
|
||||
const auto target_intersection = [&](NodeID node) {
|
||||
auto intersection = intersection_generator(node, candidate_in);
|
||||
// skip over traffic lights
|
||||
if (intersection.size() == 2)
|
||||
{
|
||||
node = node_based_graph.GetTarget(candidate_in);
|
||||
candidate_in = intersection[1].eid;
|
||||
intersection = intersection_generator(node, candidate_in);
|
||||
}
|
||||
return intersection;
|
||||
}(intersection_node_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto link_data = node_based_graph.GetEdgeData(road.eid);
|
||||
// Check if the road continues here
|
||||
const bool is_through_street =
|
||||
!target_intersection.empty() && link_data.name_id != EMPTY_NAMEID &&
|
||||
target_intersection.end() !=
|
||||
std::find_if(
|
||||
target_intersection.begin() + 1,
|
||||
target_intersection.end(),
|
||||
[this, &link_data](const IntersectionViewData &road) {
|
||||
const auto &road_edge_data = node_based_graph.GetEdgeData(road.eid);
|
||||
// Discard service and other low priority roads - never Sliproad candidate
|
||||
if (sliproad_data.road_classification.IsLowPriorityRoadClass())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto same_name =
|
||||
road_edge_data.name_id != EMPTY_NAMEID &&
|
||||
!util::guidance::requiresNameAnnounced(road_edge_data.name_id,
|
||||
link_data.name_id,
|
||||
name_table,
|
||||
street_name_suffix_table);
|
||||
// Incoming-only can never be a Sliproad
|
||||
if (sliproad_data.reversed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return same_name;
|
||||
});
|
||||
// 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 the sliproad candidate is a through street, we cannot handle it as a sliproad
|
||||
if (is_through_street)
|
||||
continue;
|
||||
if (!is_potential_link(sliproad))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto &candidate_road : target_intersection)
|
||||
// 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, intersection_generator};
|
||||
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;
|
||||
|
||||
// Constrain the Sliproad's target to sliproad, outgoing, incoming from main intersection
|
||||
if (target_intersection.size() != 3)
|
||||
{
|
||||
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 the sliproad candidate is a through street, we cannot handle it as a sliproad.
|
||||
if (isThroughStreet(sliproad_edge, target_intersection))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// The turn off of the Sliproad has to be obvious and a narrow turn
|
||||
{
|
||||
const auto index = findObviousTurn(sliproad_edge, target_intersection);
|
||||
|
||||
if (index == 0)
|
||||
{
|
||||
const auto &candidate_data = node_based_graph.GetEdgeData(candidate_road.eid);
|
||||
if (target_road_names.count(candidate_data.name_id) > 0)
|
||||
{
|
||||
if (node_based_graph.GetTarget(candidate_road.eid) == next_intersection_node)
|
||||
{
|
||||
road.instruction.type = TurnType::Sliproad;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto skip_traffic_light_intersection = intersection_generator(
|
||||
node_based_graph.GetTarget(candidate_in), candidate_road.eid);
|
||||
if (skip_traffic_light_intersection.size() == 2 &&
|
||||
node_based_graph.GetTarget(skip_traffic_light_intersection[1].eid) ==
|
||||
next_intersection_node)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
road.instruction.type = TurnType::Sliproad;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 auto &extractor = intersection_generator.GetCoordinateExtractor();
|
||||
|
||||
const NodeID start = intersection_node_id; // b
|
||||
const EdgeID edge = sliproad_edge; // bd
|
||||
|
||||
const auto coords = extractor.GetForwardCoordinatesAlongRoad(start, edge);
|
||||
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 or less ~90 degree 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_info_list[intersection_node_id],
|
||||
node_info_list[main_road_intersection->node]);
|
||||
|
||||
const double perpendicular_angle = 90 + FUZZY_ANGLE_DIFFERENCE;
|
||||
|
||||
if (length >= MIN_LENGTH)
|
||||
{
|
||||
if (!snuggles)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (deviation_from_straight > perpendicular_angle)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for area under triangle `bdc`.
|
||||
//
|
||||
// a ... b .... c .
|
||||
// ` .
|
||||
// ` .
|
||||
// ` .
|
||||
// d
|
||||
//
|
||||
const auto area_threshold = std::pow(
|
||||
scaledThresholdByRoadClass(MAX_SLIPROAD_THRESHOLD, sliproad_data.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_based_graph.GetEdgeData(candidate_road.eid);
|
||||
|
||||
// Name mismatch: check roads at `c` and `d` for same name
|
||||
const auto name_mismatch = [&](const NameID road_name_id) {
|
||||
const auto unnamed = road_name_id == EMPTY_NAMEID;
|
||||
|
||||
return unnamed ||
|
||||
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;
|
||||
}
|
||||
|
||||
// If the Sliproad `bd` is a link, `bc` and `cd` must not be links.
|
||||
if (!isValidSliproadLink(sliproad, main_road, candidate_road))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node_based_graph.GetTarget(candidate_road.eid) == main_road_intersection->node)
|
||||
{
|
||||
sliproad.instruction.type = TurnType::Sliproad;
|
||||
sliproad_found = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto skip_traffic_light_intersection = intersection_generator(
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (next_road.instruction.type == TurnType::Fork)
|
||||
// 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 &next_data = node_based_graph.GetEdgeData(next_road.eid);
|
||||
const auto &source_edge_data = node_based_graph.GetEdgeData(source_edge_id);
|
||||
const auto &main_road_data = node_based_graph.GetEdgeData(main_road.eid);
|
||||
|
||||
const auto same_name =
|
||||
next_data.name_id != EMPTY_NAMEID && source_edge_data.name_id != EMPTY_NAMEID &&
|
||||
!util::guidance::requiresNameAnnounced(
|
||||
next_data.name_id, source_edge_data.name_id, name_table, street_name_suffix_table);
|
||||
const auto same_name = source_edge_data.name_id != EMPTY_NAMEID && //
|
||||
main_road_data.name_id != EMPTY_NAMEID && //
|
||||
!util::guidance::requiresNameAnnounced(source_edge_data.name_id,
|
||||
main_road_data.name_id,
|
||||
name_table,
|
||||
street_name_suffix_table); //
|
||||
|
||||
if (same_name)
|
||||
{
|
||||
if (angularDeviation(next_road.angle, STRAIGHT_ANGLE) < 5)
|
||||
intersection[obvious_turn_index].instruction.type = TurnType::Suppressed;
|
||||
if (angularDeviation(main_road.angle, STRAIGHT_ANGLE) < 5)
|
||||
intersection[*obvious].instruction.type = TurnType::Suppressed;
|
||||
else
|
||||
intersection[obvious_turn_index].instruction.type = TurnType::Continue;
|
||||
intersection[obvious_turn_index].instruction.direction_modifier =
|
||||
getTurnDirection(intersection[obvious_turn_index].angle);
|
||||
intersection[*obvious].instruction.type = TurnType::Continue;
|
||||
intersection[*obvious].instruction.direction_modifier =
|
||||
getTurnDirection(intersection[*obvious].angle);
|
||||
}
|
||||
else if (next_data.name_id != EMPTY_NAMEID)
|
||||
else if (main_road_data.name_id != EMPTY_NAMEID)
|
||||
{
|
||||
intersection[obvious_turn_index].instruction.type = TurnType::NewName;
|
||||
intersection[obvious_turn_index].instruction.direction_modifier =
|
||||
getTurnDirection(intersection[obvious_turn_index].angle);
|
||||
intersection[*obvious].instruction.type = TurnType::NewName;
|
||||
intersection[*obvious].instruction.direction_modifier =
|
||||
getTurnDirection(intersection[*obvious].angle);
|
||||
}
|
||||
else
|
||||
{
|
||||
intersection[obvious_turn_index].instruction.type = TurnType::Suppressed;
|
||||
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);
|
||||
|
||||
const auto &extractor = intersection_generator.GetCoordinateExtractor();
|
||||
|
||||
// Base max distance threshold on the current road class we're on
|
||||
const auto &data = node_based_graph.GetEdgeData(onto);
|
||||
const auto threshold = scaledThresholdByRoadClass(MAX_SLIPROAD_THRESHOLD, // <- scales down
|
||||
data.road_classification);
|
||||
|
||||
DistanceToNextIntersectionAccumulator accumulator{extractor, node_based_graph, threshold};
|
||||
const SkipTrafficSignalBarrierRoadSelector selector{};
|
||||
|
||||
(void)graph_walker.TraverseRoad(start, onto, accumulator, selector);
|
||||
|
||||
return accumulator.too_far_away;
|
||||
}
|
||||
|
||||
bool SliproadHandler::isThroughStreet(const EdgeID from, const IntersectionView &intersection) const
|
||||
{
|
||||
BOOST_ASSERT(from != SPECIAL_EDGEID);
|
||||
BOOST_ASSERT(!intersection.empty());
|
||||
|
||||
const auto &edge_name_id = node_based_graph.GetEdgeData(from).name_id;
|
||||
|
||||
auto first = begin(intersection) + 1; // Skip UTurn road
|
||||
auto last = end(intersection);
|
||||
|
||||
auto same_name = [&](const auto &road) {
|
||||
const auto &road_name_id = node_based_graph.GetEdgeData(road.eid).name_id;
|
||||
|
||||
return edge_name_id != EMPTY_NAMEID && //
|
||||
road_name_id != EMPTY_NAMEID && //
|
||||
!util::guidance::requiresNameAnnounced(edge_name_id,
|
||||
road_name_id,
|
||||
name_table,
|
||||
street_name_suffix_table); //
|
||||
};
|
||||
|
||||
return std::find_if(first, last, same_name) != last;
|
||||
}
|
||||
|
||||
bool SliproadHandler::roadContinues(const EdgeID current, const EdgeID next) const
|
||||
{
|
||||
const auto ¤t_data = node_based_graph.GetEdgeData(current);
|
||||
const auto &next_data = node_based_graph.GetEdgeData(next);
|
||||
|
||||
auto same_road_category = current_data.road_classification == next_data.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_info_list[a];
|
||||
const auto second = node_info_list[b];
|
||||
const auto third = node_info_list[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);
|
||||
if (!sliproad_data.road_classification.IsLinkClass())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise neither the first road leading to the intersection we shortcut
|
||||
const auto &first_road_data = node_based_graph.GetEdgeData(first.eid);
|
||||
if (first_road_data.road_classification.IsLinkClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// nor the second road coming from the intersection we shotcut must be links
|
||||
const auto &second_road_data = node_based_graph.GetEdgeData(second.eid);
|
||||
if (second_road_data.road_classification.IsLinkClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user