osrm-backend/src/extractor/guidance/sliproad_handler.cpp

740 lines
26 KiB
C++
Raw Normal View History

2016-10-05 06:33:58 -04:00
#include "extractor/guidance/sliproad_handler.hpp"
2016-07-04 06:19:49 -04:00
#include "extractor/guidance/constants.hpp"
#include "util/bearing.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/guidance/name_announcements.hpp"
2016-07-04 06:19:49 -04:00
#include <cmath>
#include <algorithm>
#include <iterator>
2016-07-04 06:19:49 -04:00
#include <limits>
#include <boost/assert.hpp>
using osrm::extractor::guidance::getTurnDirection;
using osrm::util::angularDeviation;
2016-07-04 06:19:49 -04:00
namespace osrm
{
namespace extractor
{
namespace guidance
{
SliproadHandler::SliproadHandler(const IntersectionGenerator &intersection_generator,
const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_info_list,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table)
2016-08-15 06:43:26 -04:00
: IntersectionHandler(node_based_graph,
node_info_list,
name_table,
street_name_suffix_table,
intersection_generator)
2016-07-04 06:19:49 -04:00
{
}
// 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`.
2016-07-04 06:19:49 -04:00
bool SliproadHandler::canProcess(const NodeID /*nid*/,
const EdgeID /*via_eid*/,
const Intersection &intersection) const
2016-07-04 06:19:49 -04:00
{
return intersection.size() > 2;
2016-07-04 06:19:49 -04:00
}
// Detect sliproad b-d in the following example:
//
// .
// e
// .
// .
// a ... b .... c .
// ` .
// ` .
// ` .
// d
// .
//
// ^ a nid
// ^ ab source_edge_id
// ^ b intersection
2016-07-04 06:19:49 -04:00
Intersection SliproadHandler::
operator()(const NodeID /*nid*/, const EdgeID source_edge_id, Intersection intersection) const
2016-07-04 06:19:49 -04:00
{
BOOST_ASSERT(intersection.size() > 2);
// Potential splitting / start of a Sliproad (b)
2016-07-04 06:19:49 -04:00
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)
{
2016-07-04 06:19:49 -04:00
return intersection;
}
2016-07-04 06:19:49 -04:00
// 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;
}
2016-07-04 06:19:49 -04:00
// 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();
2016-07-04 06:19:49 -04:00
if (!is_narrow || same_angle)
{
return false;
}
2016-07-04 06:19:49 -04:00
const auto &road_data = node_based_graph.GetEdgeData(road.eid);
auto is_roundabout = road_data.roundabout;
if (is_roundabout)
{
return false;
}
return true;
2016-07-04 06:19:49 -04:00
};
if (!std::any_of(begin(intersection), end(intersection), is_potential_link))
{
2016-07-04 06:19:49 -04:00
return intersection;
}
2016-07-04 06:19:49 -04:00
// If the intersection is too far away, don't bother continuing
if (nextIntersectionIsTooFarAway(intersection_node_id, main_road.eid))
2016-07-04 06:19:49 -04:00
{
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;
}
2016-07-04 06:19:49 -04:00
std::vector<NameID> target_road_name_ids;
target_road_name_ids.reserve(main_road_intersection->intersection.size());
2016-07-04 06:19:49 -04:00
for (const auto &road : main_road_intersection->intersection)
2016-07-04 06:19:49 -04:00
{
const auto &target_data = node_based_graph.GetEdgeData(road.eid);
target_road_name_ids.push_back(target_data.name_id);
2016-07-04 06:19:49 -04:00
}
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)
2016-07-04 06:19:49 -04:00
{
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();
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_data.road_classification.IsLowPriorityRoadClass())
2016-07-04 06:19:49 -04:00
{
continue;
}
// Incoming-only can never be a Sliproad
if (sliproad_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, 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)
{
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;
}
}
// 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)
2016-07-04 06:19:49 -04:00
{
continue;
2016-07-04 06:19:49 -04:00
}
}
}
// 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)
{
2016-09-13 07:17:18 -04:00
continue;
}
// If the Sliproad `bd` is a link, `bc` and `cd` must not be links.
if (!isValidSliproadLink(sliproad, main_road, candidate_road))
{
continue;
}
2016-09-13 07:17:18 -04:00
if (node_based_graph.GetTarget(candidate_road.eid) == main_road_intersection->node)
2016-07-04 06:19:49 -04:00
{
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)
2016-07-04 06:19:49 -04:00
{
sliproad.instruction.type = TurnType::Sliproad;
sliproad_found = true;
break;
2016-07-04 06:19:49 -04:00
}
}
}
}
// 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)
2016-07-04 06:19:49 -04:00
{
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 = 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)
2016-07-04 06:19:49 -04:00
{
if (angularDeviation(main_road.angle, STRAIGHT_ANGLE) < 5)
intersection[*obvious].instruction.type = TurnType::Suppressed;
2016-07-04 06:19:49 -04:00
else
intersection[*obvious].instruction.type = TurnType::Continue;
intersection[*obvious].instruction.direction_modifier =
getTurnDirection(intersection[*obvious].angle);
2016-07-04 06:19:49 -04:00
}
else if (main_road_data.name_id != EMPTY_NAMEID)
2016-07-04 06:19:49 -04:00
{
intersection[*obvious].instruction.type = TurnType::NewName;
intersection[*obvious].instruction.direction_modifier =
getTurnDirection(intersection[*obvious].angle);
2016-07-04 06:19:49 -04:00
}
else
{
intersection[*obvious].instruction.type = TurnType::Suppressed;
2016-07-04 06:19:49 -04:00
}
}
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 &current_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;
}
2016-07-04 06:19:49 -04:00
} // namespace guidance
} // namespace extractor
} // namespace osrm