refactor merging of segregated roads
adjust to generalFindMaximum function moved parallel detection to ratio/absolute based regression testing considerably improved detection quality using normalised regression lines only follow initial direction/narrow turns for parallel detection
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
#include "extractor/guidance/intersection_normalizer.hpp"
|
||||
#include "util/bearing.hpp"
|
||||
#include "util/coordinate_calculation.hpp"
|
||||
#include "util/guidance/name_announcements.hpp"
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
@@ -21,34 +20,40 @@ IntersectionNormalizer::IntersectionNormalizer(
|
||||
const util::NameTable &name_table,
|
||||
const SuffixTable &street_name_suffix_table,
|
||||
const IntersectionGenerator &intersection_generator)
|
||||
: node_based_graph(node_based_graph), node_coordinates(node_coordinates),
|
||||
name_table(name_table), street_name_suffix_table(street_name_suffix_table),
|
||||
intersection_generator(intersection_generator)
|
||||
: node_based_graph(node_based_graph), intersection_generator(intersection_generator),
|
||||
mergable_road_detector(node_based_graph,
|
||||
node_coordinates,
|
||||
intersection_generator,
|
||||
intersection_generator.GetCoordinateExtractor(),
|
||||
name_table,
|
||||
street_name_suffix_table)
|
||||
{
|
||||
}
|
||||
|
||||
std::pair<IntersectionShape, std::vector<std::pair<EdgeID, EdgeID>>> IntersectionNormalizer::
|
||||
IntersectionNormalizer::NormalizationResult IntersectionNormalizer::
|
||||
operator()(const NodeID node_at_intersection, IntersectionShape intersection) const
|
||||
{
|
||||
const auto intersection_copy = intersection;
|
||||
auto merged_shape_and_merges =
|
||||
MergeSegregatedRoads(node_at_intersection, std::move(intersection));
|
||||
merged_shape_and_merges.first = AdjustBearingsForMergeAtDestination(
|
||||
node_at_intersection, std::move(merged_shape_and_merges.first));
|
||||
merged_shape_and_merges.normalized_shape = AdjustBearingsForMergeAtDestination(
|
||||
node_at_intersection, std::move(merged_shape_and_merges.normalized_shape));
|
||||
return merged_shape_and_merges;
|
||||
}
|
||||
|
||||
bool IntersectionNormalizer::CanMerge(const NodeID intersection_node,
|
||||
const IntersectionShape &intersection,
|
||||
std::size_t first_index,
|
||||
std::size_t second_index) const
|
||||
std::size_t fist_index_in_ccw,
|
||||
std::size_t second_index_in_ccw) const
|
||||
{
|
||||
BOOST_ASSERT(((first_index + 1) % intersection.size()) == second_index);
|
||||
BOOST_ASSERT(((fist_index_in_ccw + 1) % intersection.size()) == second_index_in_ccw);
|
||||
|
||||
// call wrapper to capture intersection_node and intersection
|
||||
const auto mergable = [this, intersection_node, &intersection](const std::size_t left_index,
|
||||
const std::size_t right_index) {
|
||||
return InnerCanMerge(intersection_node, intersection, left_index, right_index);
|
||||
};
|
||||
// don't merge on degree two, since it's most likely a bollard/traffic light or a round way
|
||||
if (intersection.size() <= 2)
|
||||
return false;
|
||||
|
||||
const auto can_merge = mergable_road_detector.CanMergeRoad(
|
||||
intersection_node, intersection[fist_index_in_ccw], intersection[second_index_in_ccw]);
|
||||
|
||||
/*
|
||||
* Merging should never depend on order/never merge more than two roads. To ensure that we don't
|
||||
@@ -56,143 +61,71 @@ bool IntersectionNormalizer::CanMerge(const NodeID intersection_node,
|
||||
* parking lots/border checkpoints), we check if the neigboring roads would be merged as well.
|
||||
* In that case, we cannot merge, since we would end up merging multiple items together
|
||||
*/
|
||||
if (mergable(first_index, second_index))
|
||||
{
|
||||
const auto is_distinct_merge =
|
||||
!mergable(second_index, (second_index + 1) % intersection.size()) &&
|
||||
!mergable((first_index + intersection.size() - 1) % intersection.size(), first_index) &&
|
||||
!mergable(second_index,
|
||||
(first_index + intersection.size() - 1) % intersection.size()) &&
|
||||
!mergable(first_index, (second_index + 1) % intersection.size());
|
||||
return is_distinct_merge;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Checks for mergability of two ways that represent the same intersection. For further
|
||||
// information see interface documentation in header.
|
||||
bool IntersectionNormalizer::InnerCanMerge(const NodeID node_at_intersection,
|
||||
const IntersectionShape &intersection,
|
||||
std::size_t first_index,
|
||||
std::size_t second_index) const
|
||||
{
|
||||
const auto &first_data = node_based_graph.GetEdgeData(intersection[first_index].eid);
|
||||
const auto &second_data = node_based_graph.GetEdgeData(intersection[second_index].eid);
|
||||
|
||||
// only merge named ids
|
||||
if (first_data.name_id == EMPTY_NAMEID || second_data.name_id == EMPTY_NAMEID)
|
||||
return false;
|
||||
|
||||
// need to be same name
|
||||
if (util::guidance::requiresNameAnnounced(
|
||||
first_data.name_id, second_data.name_id, name_table, street_name_suffix_table))
|
||||
return false;
|
||||
// needs to be symmetrical for names
|
||||
if (util::guidance::requiresNameAnnounced(
|
||||
second_data.name_id, first_data.name_id, name_table, street_name_suffix_table))
|
||||
return false;
|
||||
|
||||
// compatibility is required
|
||||
if (first_data.travel_mode != second_data.travel_mode)
|
||||
return false;
|
||||
if (first_data.road_classification != second_data.road_classification)
|
||||
return false;
|
||||
|
||||
// may not be on a roundabout
|
||||
if (first_data.roundabout || second_data.roundabout || first_data.circular ||
|
||||
second_data.circular)
|
||||
return false;
|
||||
|
||||
// exactly one of them has to be reversed
|
||||
if (first_data.reversed == second_data.reversed)
|
||||
return false;
|
||||
|
||||
// mergeable if the angle is not too big
|
||||
const auto angle_between =
|
||||
angularDeviation(intersection[first_index].bearing, intersection[second_index].bearing);
|
||||
|
||||
const auto coordinate_at_intersection = node_coordinates[node_at_intersection];
|
||||
|
||||
if (angle_between >= 120)
|
||||
return false;
|
||||
|
||||
const auto isValidYArm = [this, intersection, coordinate_at_intersection, node_at_intersection](
|
||||
const std::size_t index, const std::size_t other_index) {
|
||||
const auto GetActualTarget = [&](const std::size_t index) {
|
||||
EdgeID edge_id;
|
||||
std::tie(std::ignore, edge_id) = intersection_generator.SkipDegreeTwoNodes(
|
||||
node_at_intersection, intersection[index].eid);
|
||||
return node_based_graph.GetTarget(edge_id);
|
||||
};
|
||||
|
||||
const auto target_id = GetActualTarget(index);
|
||||
const auto other_target_id = GetActualTarget(other_index);
|
||||
if (target_id == node_at_intersection || other_target_id == node_at_intersection)
|
||||
return false;
|
||||
|
||||
const auto coordinate_at_target = node_coordinates[target_id];
|
||||
const auto coordinate_at_other_target = node_coordinates[other_target_id];
|
||||
|
||||
const auto turn_bearing =
|
||||
util::coordinate_calculation::bearing(coordinate_at_intersection, coordinate_at_target);
|
||||
const auto other_turn_bearing = util::coordinate_calculation::bearing(
|
||||
coordinate_at_intersection, coordinate_at_other_target);
|
||||
|
||||
// fuzzy becomes narrower due to minor differences in angle computations, yay floating point
|
||||
const bool becomes_narrower =
|
||||
angularDeviation(turn_bearing, other_turn_bearing) < NARROW_TURN_ANGLE &&
|
||||
angularDeviation(turn_bearing, other_turn_bearing) <=
|
||||
angularDeviation(intersection[index].bearing, intersection[other_index].bearing) +
|
||||
MAXIMAL_ALLOWED_NO_TURN_DEVIATION;
|
||||
|
||||
return becomes_narrower;
|
||||
const auto is_distinct = [&]() {
|
||||
const auto next_index_in_ccw = (second_index_in_ccw + 1) % intersection.size();
|
||||
const auto distinct_to_next_in_ccw = mergable_road_detector.IsDistinctFrom(
|
||||
intersection[second_index_in_ccw], intersection[next_index_in_ccw]);
|
||||
const auto prev_index_in_ccw =
|
||||
(fist_index_in_ccw + intersection.size() - 1) % intersection.size();
|
||||
const auto distinct_to_prev_in_ccw = mergable_road_detector.IsDistinctFrom(
|
||||
intersection[prev_index_in_ccw], intersection[fist_index_in_ccw]);
|
||||
return distinct_to_next_in_ccw && distinct_to_prev_in_ccw;
|
||||
};
|
||||
|
||||
const bool is_y_arm_first = isValidYArm(first_index, second_index);
|
||||
const bool is_y_arm_second = isValidYArm(second_index, first_index);
|
||||
// use lazy evaluation to check only if mergable
|
||||
return can_merge && is_distinct();
|
||||
}
|
||||
|
||||
// Only merge valid y-arms
|
||||
if (!is_y_arm_first || !is_y_arm_second)
|
||||
return false;
|
||||
IntersectionNormalizationOperation
|
||||
IntersectionNormalizer::DetermineMergeDirection(const IntersectionShapeData &lhs,
|
||||
const IntersectionShapeData &rhs) const
|
||||
{
|
||||
if (node_based_graph.GetEdgeData(lhs.eid).reversed)
|
||||
return {lhs.eid, rhs.eid};
|
||||
else
|
||||
return {rhs.eid, lhs.eid};
|
||||
}
|
||||
|
||||
if (angle_between < 60)
|
||||
return true;
|
||||
IntersectionShapeData IntersectionNormalizer::MergeRoads(const IntersectionShapeData &into,
|
||||
const IntersectionShapeData &from) const
|
||||
{
|
||||
// we only merge small angles. If the difference between both is large, we are looking at a
|
||||
// bearing leading north. Such a bearing cannot be handled via the basic average. In this
|
||||
// case we actually need to shift the bearing by half the difference.
|
||||
const auto aroundZero = [](const double first, const double second) {
|
||||
return (std::max(first, second) - std::min(first, second)) >= 180;
|
||||
};
|
||||
|
||||
// Finally, we also allow merging if all streets offer the same name, it is only three roads and
|
||||
// the angle is not fully extreme:
|
||||
if (intersection.size() != 3)
|
||||
return false;
|
||||
|
||||
// since we have an intersection of size three now, there is only one index we are not looking
|
||||
// at right now. The final index in the intersection is calculated next:
|
||||
const std::size_t third_index = [first_index, second_index]() {
|
||||
if (first_index == 0)
|
||||
return second_index == 2 ? 1 : 2;
|
||||
else if (first_index == 1)
|
||||
return second_index == 2 ? 0 : 2;
|
||||
// find the angle between two other angles
|
||||
const auto combineAngles = [aroundZero](const double first, const double second) {
|
||||
if (!aroundZero(first, second))
|
||||
return .5 * (first + second);
|
||||
else
|
||||
return second_index == 1 ? 0 : 1;
|
||||
}();
|
||||
{
|
||||
const auto offset = angularDeviation(first, second);
|
||||
auto new_angle = std::max(first, second) + .5 * offset;
|
||||
if (new_angle >= 360)
|
||||
return new_angle - 360;
|
||||
return new_angle;
|
||||
}
|
||||
};
|
||||
|
||||
// needs to be same road coming in
|
||||
const auto &third_data = node_based_graph.GetEdgeData(intersection[third_index].eid);
|
||||
auto result = into;
|
||||
BOOST_ASSERT(!node_based_graph.GetEdgeData(into.eid).reversed);
|
||||
result.bearing = combineAngles(into.bearing, from.bearing);
|
||||
BOOST_ASSERT(0 <= result.bearing && result.bearing < 360.0);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (third_data.name_id != EMPTY_NAMEID &&
|
||||
util::guidance::requiresNameAnnounced(
|
||||
third_data.name_id, first_data.name_id, name_table, street_name_suffix_table))
|
||||
return false;
|
||||
|
||||
// we only allow collapsing of a Y like fork. So the angle to the third index has to be
|
||||
// roughly equal:
|
||||
const auto y_angle_difference = angularDeviation(
|
||||
angularDeviation(intersection[third_index].bearing, intersection[first_index].bearing),
|
||||
angularDeviation(intersection[third_index].bearing, intersection[second_index].bearing));
|
||||
// Allow larger angles if its three roads only of the same name
|
||||
// This is a heuristic and might need to be revised.
|
||||
const bool assume_y_intersection =
|
||||
angle_between < 100 && y_angle_difference < FUZZY_ANGLE_DIFFERENCE;
|
||||
return assume_y_intersection;
|
||||
IntersectionShapeData
|
||||
IntersectionNormalizer::MergeRoads(const IntersectionNormalizationOperation direction,
|
||||
const IntersectionShapeData &lhs,
|
||||
const IntersectionShapeData &rhs) const
|
||||
{
|
||||
if (direction.merged_eid == lhs.eid)
|
||||
return MergeRoads(rhs, lhs);
|
||||
else
|
||||
return MergeRoads(lhs, rhs);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -218,7 +151,7 @@ bool IntersectionNormalizer::InnerCanMerge(const NodeID node_at_intersection,
|
||||
* Anything containing the first u-turn in a merge affects all other angles
|
||||
* and is handled separately from all others.
|
||||
*/
|
||||
std::pair<IntersectionShape, std::vector<std::pair<EdgeID, EdgeID>>>
|
||||
IntersectionNormalizer::NormalizationResult
|
||||
IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node,
|
||||
IntersectionShape intersection) const
|
||||
{
|
||||
@@ -226,50 +159,25 @@ IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node,
|
||||
return (index + intersection.size() - 1) % intersection.size();
|
||||
};
|
||||
|
||||
// we only merge small angles. If the difference between both is large, we are looking at a
|
||||
// bearing leading north. Such a bearing cannot be handled via the basic average. In this
|
||||
// case we actually need to shift the bearing by half the difference.
|
||||
const auto aroundZero = [](const double first, const double second) {
|
||||
return (std::max(first, second) - std::min(first, second)) >= 180;
|
||||
};
|
||||
|
||||
// find the angle between two other angles
|
||||
const auto combineAngles = [aroundZero](const double first, const double second) {
|
||||
if (!aroundZero(first, second))
|
||||
return .5 * (first + second);
|
||||
else
|
||||
{
|
||||
const auto offset = angularDeviation(first, second);
|
||||
auto new_angle = std::max(first, second) + .5 * offset;
|
||||
if (new_angle >= 360)
|
||||
return new_angle - 360;
|
||||
return new_angle;
|
||||
}
|
||||
};
|
||||
|
||||
// This map stores for all edges that participated in a merging operation in which edge id they
|
||||
// end up in the end. We only store what we have merged into other edges.
|
||||
std::vector<std::pair<EdgeID, EdgeID>> merging_map;
|
||||
std::vector<IntersectionNormalizationOperation> merging_map;
|
||||
const auto merge = [this, &merging_map](const IntersectionShapeData &first,
|
||||
const IntersectionShapeData &second) {
|
||||
|
||||
const auto merge = [this, combineAngles, &merging_map](const IntersectionShapeData &first,
|
||||
const IntersectionShapeData &second) {
|
||||
IntersectionShapeData result =
|
||||
!node_based_graph.GetEdgeData(first.eid).reversed ? first : second;
|
||||
result.bearing = combineAngles(first.bearing, second.bearing);
|
||||
BOOST_ASSERT(0 <= result.bearing && result.bearing < 360.0);
|
||||
// the other ID
|
||||
const auto merged_from = result.eid == first.eid ? second.eid : first.eid;
|
||||
const auto direction = DetermineMergeDirection(first, second);
|
||||
BOOST_ASSERT(
|
||||
std::find_if(merging_map.begin(), merging_map.end(), [merged_from](const auto pair) {
|
||||
return pair.first == merged_from;
|
||||
std::find_if(merging_map.begin(), merging_map.end(), [direction](const auto pair) {
|
||||
return pair.merged_eid == direction.merged_eid;
|
||||
}) == merging_map.end());
|
||||
merging_map.push_back(std::make_pair(merged_from, result.eid));
|
||||
return result;
|
||||
merging_map.push_back(direction);
|
||||
return MergeRoads(direction, first, second);
|
||||
};
|
||||
|
||||
if (intersection.size() <= 1)
|
||||
return std::make_pair(intersection, merging_map);
|
||||
return {intersection, merging_map};
|
||||
|
||||
const auto intersection_copy = intersection;
|
||||
// check for merges including the basic u-turn
|
||||
// these result in an adjustment of all other angles. This is due to how these angles are
|
||||
// perceived. Considering the following example:
|
||||
@@ -301,11 +209,9 @@ IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node,
|
||||
// If the merge occurs at the u-turn edge, we need to adjust all angles, though, since they are
|
||||
// with respect to the now changed perceived location of a. If we move (a) to the left, we add
|
||||
// the difference to all angles. Otherwise we subtract it.
|
||||
bool merged_first = false;
|
||||
// these result in an adjustment of all other angles
|
||||
if (CanMerge(intersection_node, intersection, intersection.size() - 1, 0))
|
||||
{
|
||||
merged_first = true;
|
||||
// moving `a` to the left
|
||||
intersection[0] = merge(intersection.front(), intersection.back());
|
||||
// FIXME if we have a left-sided country, we need to switch this off and enable it
|
||||
@@ -314,7 +220,6 @@ IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node,
|
||||
}
|
||||
else if (CanMerge(intersection_node, intersection, 0, 1))
|
||||
{
|
||||
merged_first = true;
|
||||
intersection[0] = merge(intersection.front(), intersection[1]);
|
||||
intersection.erase(intersection.begin() + 1);
|
||||
}
|
||||
@@ -331,8 +236,7 @@ IntersectionNormalizer::MergeSegregatedRoads(const NodeID intersection_node,
|
||||
--index;
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(intersection, merging_map);
|
||||
return {intersection, merging_map};
|
||||
}
|
||||
|
||||
// OSM can have some very steep angles for joining roads. Considering the following intersection:
|
||||
@@ -432,7 +336,7 @@ IntersectionNormalizer::AdjustBearingsForMergeAtDestination(const NodeID node_at
|
||||
intersection[(intersection.size() + index - 1) % intersection.size()]);
|
||||
// at the target intersection, we merge to the right, so we need to shift the current
|
||||
// angle to the left
|
||||
road.bearing = adjustAngle(road.bearing, -corrected_offset);
|
||||
road.bearing = adjustAngle(road.bearing, corrected_offset);
|
||||
}
|
||||
else if (CanMerge(node_at_next_intersection,
|
||||
next_intersection_along_road,
|
||||
@@ -447,7 +351,7 @@ IntersectionNormalizer::AdjustBearingsForMergeAtDestination(const NodeID node_at
|
||||
get_corrected_offset(offset, road, intersection[(index + 1) % intersection.size()]);
|
||||
// at the target intersection, we merge to the left, so we need to shift the current
|
||||
// angle to the right
|
||||
road.bearing = adjustAngle(road.bearing, corrected_offset);
|
||||
road.bearing = adjustAngle(road.bearing, -corrected_offset);
|
||||
}
|
||||
}
|
||||
return intersection;
|
||||
|
||||
Reference in New Issue
Block a user