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:
@@ -260,8 +260,8 @@ void closeOffRoundabout(const bool on_roundabout,
|
||||
TurnType::EnterRoundaboutIntersectionAtExit)
|
||||
{
|
||||
BOOST_ASSERT(!propagation_step.intersections.empty());
|
||||
const double angle = util::angleBetweenBearings(
|
||||
util::reverseBearing(entry_intersection.bearings[entry_intersection.in]),
|
||||
const double angle = util::bearing::angleBetween(
|
||||
util::bearing::reverse(entry_intersection.bearings[entry_intersection.in]),
|
||||
exit_bearing);
|
||||
|
||||
auto bearings = propagation_step.intersections.front().bearings;
|
||||
@@ -306,7 +306,7 @@ bool isUTurn(const RouteStep &in_step, const RouteStep &out_step, const RouteSte
|
||||
(isLinkroad(in_step) && out_step.name_id != EMPTY_NAMEID &&
|
||||
pre_in_step.name_id != EMPTY_NAMEID && !isNoticeableNameChange(pre_in_step, out_step));
|
||||
const bool takes_u_turn = bearingsAreReversed(
|
||||
util::reverseBearing(
|
||||
util::bearing::reverse(
|
||||
in_step.intersections.front().bearings[in_step.intersections.front().in]),
|
||||
out_step.intersections.front().bearings[out_step.intersections.front().out]);
|
||||
|
||||
@@ -318,20 +318,20 @@ double findTotalTurnAngle(const RouteStep &entry_step, const RouteStep &exit_ste
|
||||
const auto exit_intersection = exit_step.intersections.front();
|
||||
const auto exit_step_exit_bearing = exit_intersection.bearings[exit_intersection.out];
|
||||
const auto exit_step_entry_bearing =
|
||||
util::reverseBearing(exit_intersection.bearings[exit_intersection.in]);
|
||||
util::bearing::reverse(exit_intersection.bearings[exit_intersection.in]);
|
||||
|
||||
const auto entry_intersection = entry_step.intersections.front();
|
||||
const auto entry_step_entry_bearing =
|
||||
util::reverseBearing(entry_intersection.bearings[entry_intersection.in]);
|
||||
util::bearing::reverse(entry_intersection.bearings[entry_intersection.in]);
|
||||
const auto entry_step_exit_bearing = entry_intersection.bearings[entry_intersection.out];
|
||||
|
||||
const auto exit_angle =
|
||||
util::angleBetweenBearings(exit_step_entry_bearing, exit_step_exit_bearing);
|
||||
util::bearing::angleBetween(exit_step_entry_bearing, exit_step_exit_bearing);
|
||||
const auto entry_angle =
|
||||
util::angleBetweenBearings(entry_step_entry_bearing, entry_step_exit_bearing);
|
||||
util::bearing::angleBetween(entry_step_entry_bearing, entry_step_exit_bearing);
|
||||
|
||||
const double total_angle =
|
||||
util::angleBetweenBearings(entry_step_entry_bearing, exit_step_exit_bearing);
|
||||
util::bearing::angleBetween(entry_step_entry_bearing, exit_step_exit_bearing);
|
||||
// We allow for minor deviations from a straight line
|
||||
if (((entry_step.distance < MAX_COLLAPSE_DISTANCE && exit_step.intersections.size() == 1) ||
|
||||
(entry_angle <= 185 && exit_angle <= 185) || (entry_angle >= 175 && exit_angle >= 175)) &&
|
||||
@@ -391,7 +391,7 @@ void collapseUTurn(std::vector<RouteStep> &steps,
|
||||
const bool direct_u_turn = !isNoticeableNameChange(steps[two_back_index], current_step);
|
||||
|
||||
// however, we might also deal with a dual-collapse scenario in which we have to
|
||||
// additionall collapse a name-change as welll
|
||||
// additionall collapse a name-change as well
|
||||
const auto next_step_index = step_index + 1;
|
||||
const bool continues_with_name_change =
|
||||
(next_step_index < steps.size()) && compatible(steps[step_index], steps[next_step_index]) &&
|
||||
@@ -531,18 +531,18 @@ void collapseTurnAt(std::vector<RouteStep> &steps,
|
||||
if (continue_or_suppressed || turning_name)
|
||||
{
|
||||
const auto in_bearing = [](const RouteStep &step) {
|
||||
return util::reverseBearing(
|
||||
return util::bearing::reverse(
|
||||
step.intersections.front().bearings[step.intersections.front().in]);
|
||||
};
|
||||
const auto out_bearing = [](const RouteStep &step) {
|
||||
return step.intersections.front().bearings[step.intersections.front().out];
|
||||
};
|
||||
|
||||
const auto first_angle = util::angleBetweenBearings(in_bearing(one_back_step),
|
||||
out_bearing(one_back_step));
|
||||
const auto second_angle =
|
||||
util::angleBetweenBearings(in_bearing(current_step), out_bearing(current_step));
|
||||
const auto bearing_turn_angle = util::angleBetweenBearings(
|
||||
const auto first_angle = util::bearing::angleBetween(in_bearing(one_back_step),
|
||||
out_bearing(one_back_step));
|
||||
const auto second_angle = util::bearing::angleBetween(in_bearing(current_step),
|
||||
out_bearing(current_step));
|
||||
const auto bearing_turn_angle = util::bearing::angleBetween(
|
||||
in_bearing(one_back_step), out_bearing(current_step));
|
||||
|
||||
// When looking at an intersection, some angles, even though present, feel more like
|
||||
@@ -676,7 +676,7 @@ void collapseTurnAt(std::vector<RouteStep> &steps,
|
||||
};
|
||||
|
||||
// If we Merge onto the same street, we end up with a u-turn in some cases
|
||||
if (bearingsAreReversed(util::reverseBearing(getBearing(true, one_back_step)),
|
||||
if (bearingsAreReversed(util::bearing::reverse(getBearing(true, one_back_step)),
|
||||
getBearing(false, current_step)))
|
||||
{
|
||||
steps[one_back_index].maneuver.instruction.direction_modifier =
|
||||
@@ -760,7 +760,7 @@ bool isStaggeredIntersection(const std::vector<RouteStep> &steps,
|
||||
const auto &intersection = step.intersections.front();
|
||||
const auto entry_bearing = intersection.bearings[intersection.in];
|
||||
const auto exit_bearing = intersection.bearings[intersection.out];
|
||||
return util::angleBetweenBearings(entry_bearing, exit_bearing);
|
||||
return util::bearing::angleBetween(entry_bearing, exit_bearing);
|
||||
};
|
||||
|
||||
// Instead of using turn modifiers (e.g. as in isRightTurn) we want to be more strict here.
|
||||
@@ -915,7 +915,7 @@ std::vector<RouteStep> postProcess(std::vector<RouteStep> steps)
|
||||
|
||||
// unterminated roundabout
|
||||
// Move backwards through the instructions until the start and remove the exit number
|
||||
// A roundabout without exit translates to enter-roundabout.
|
||||
// A roundabout without exit translates to enter-roundabout
|
||||
if (has_entered_roundabout || on_roundabout)
|
||||
{
|
||||
fixFinalRoundabout(steps);
|
||||
@@ -1238,10 +1238,8 @@ void trimShortSegments(std::vector<RouteStep> &steps, LegGeometry &geometry)
|
||||
if (zero_length_step)
|
||||
{
|
||||
// since we are not only checking for epsilon but for a full meter, we can have multiple
|
||||
// coordinates here.
|
||||
// move offsets to front
|
||||
// geometry offsets have to be adjusted. Move all offsets to the front and reduce by
|
||||
// one. (This is an inplace forward one and reduce by one)
|
||||
// coordinates here. Move all offsets to the front and reduce by one. (This is an
|
||||
// inplace forward one and reduce by one)
|
||||
std::transform(geometry.segment_offsets.begin() + 1,
|
||||
geometry.segment_offsets.end(),
|
||||
geometry.segment_offsets.begin(),
|
||||
@@ -1378,7 +1376,7 @@ void trimShortSegments(std::vector<RouteStep> &steps, LegGeometry &geometry)
|
||||
geometry.locations[next_to_last_step.geometry_end - 2],
|
||||
geometry.locations[last_step.geometry_begin]));
|
||||
last_step.maneuver.bearing_before = bearing;
|
||||
last_step.intersections.front().bearings.front() = util::reverseBearing(bearing);
|
||||
last_step.intersections.front().bearings.front() = util::bearing::reverse(bearing);
|
||||
}
|
||||
|
||||
BOOST_ASSERT(steps.back().geometry_end == geometry.locations.size());
|
||||
|
||||
@@ -415,9 +415,9 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges(
|
||||
turn_analysis.GetIntersectionGenerator().TransformIntersectionShapeIntoView(
|
||||
node_along_road_entering,
|
||||
incoming_edge,
|
||||
shape_result.normalised_intersection_shape,
|
||||
shape_result.annotated_normalized_shape.normalized_shape,
|
||||
shape_result.intersection_shape,
|
||||
shape_result.merging_map);
|
||||
shape_result.annotated_normalized_shape.performed_merges);
|
||||
|
||||
auto intersection = turn_analysis.AssignTurnTypes(
|
||||
node_along_road_entering, incoming_edge, intersection_with_flags_and_angles);
|
||||
|
||||
@@ -251,6 +251,7 @@ int Extractor::run(ScriptingEnvironment &scripting_environment)
|
||||
std::vector<bool> node_is_startpoint;
|
||||
std::vector<EdgeWeight> edge_based_node_weights;
|
||||
std::vector<QueryNode> internal_to_external_node_map;
|
||||
|
||||
auto graph_size = BuildEdgeExpandedGraph(scripting_environment,
|
||||
internal_to_external_node_map,
|
||||
edge_based_node_list,
|
||||
|
||||
@@ -139,8 +139,6 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME this need to be moved into the profiles
|
||||
const guidance::RoadClassification road_classification = parsed_way.road_classification;
|
||||
const auto laneStringToDescription = [](const std::string &lane_string) -> TurnLaneDescription {
|
||||
if (lane_string.empty())
|
||||
return {};
|
||||
@@ -237,6 +235,8 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
|
||||
const auto turn_lane_id_forward = requestId(parsed_way.turn_lanes_forward);
|
||||
const auto turn_lane_id_backward = requestId(parsed_way.turn_lanes_backward);
|
||||
|
||||
const auto road_classification = parsed_way.road_classification;
|
||||
|
||||
const constexpr auto MAX_STRING_LENGTH = 255u;
|
||||
// Get the unique identifier for the street name, destination, and ref
|
||||
const auto name_iterator = string_map.find(
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
#include "extractor/geojson_debug_policies.hpp"
|
||||
#include "util/coordinate.hpp"
|
||||
#include "util/geojson_debug_policy_toolkit.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace extractor
|
||||
{
|
||||
|
||||
IntersectionPrinter::IntersectionPrinter(
|
||||
const util::NodeBasedDynamicGraph &node_based_graph,
|
||||
const std::vector<extractor::QueryNode> &node_coordinates,
|
||||
const extractor::guidance::CoordinateExtractor &coordinate_extractor)
|
||||
: node_based_graph(node_based_graph), node_coordinates(node_coordinates),
|
||||
coordinate_extractor(coordinate_extractor)
|
||||
{
|
||||
}
|
||||
|
||||
util::json::Array IntersectionPrinter::
|
||||
operator()(const NodeID intersection_node,
|
||||
const extractor::guidance::Intersection &intersection,
|
||||
const boost::optional<util::json::Object> &node_style,
|
||||
const boost::optional<util::json::Object> &way_style) const
|
||||
{
|
||||
// request the number of lanes. This process needs to be in sync with what happens over at
|
||||
// intersection_generator
|
||||
const auto intersection_lanes = intersection.getHighestConnectedLaneCount(node_based_graph);
|
||||
|
||||
std::vector<util::Coordinate> coordinates;
|
||||
coordinates.reserve(intersection.size());
|
||||
coordinates.push_back(node_coordinates[intersection_node]);
|
||||
|
||||
const auto road_to_coordinate = [&](const extractor::guidance::ConnectedRoad &connected_road) {
|
||||
const constexpr auto FORWARD = false;
|
||||
const auto to_node = node_based_graph.GetTarget(connected_road.eid);
|
||||
return coordinate_extractor.GetCoordinateAlongRoad(
|
||||
intersection_node, connected_road.eid, FORWARD, to_node, intersection_lanes);
|
||||
};
|
||||
|
||||
std::transform(intersection.begin(),
|
||||
intersection.end(),
|
||||
std::back_inserter(coordinates),
|
||||
road_to_coordinate);
|
||||
|
||||
util::json::Array features;
|
||||
features.values.push_back(
|
||||
util::makeFeature("MultiPoint", makeJsonArray(coordinates), node_style));
|
||||
|
||||
if (coordinates.size() > 1)
|
||||
{
|
||||
std::vector<util::Coordinate> line_coordinates(2);
|
||||
line_coordinates[0] = coordinates.front();
|
||||
const auto coordinate_to_line = [&](const util::Coordinate coordinate) {
|
||||
line_coordinates[1] = coordinate;
|
||||
return util::makeFeature("LineString", makeJsonArray(line_coordinates), way_style);
|
||||
};
|
||||
|
||||
std::transform(std::next(coordinates.begin()),
|
||||
coordinates.end(),
|
||||
std::back_inserter(features.values),
|
||||
coordinate_to_line);
|
||||
}
|
||||
return features;
|
||||
}
|
||||
|
||||
} /* namespace extractor */
|
||||
} /* namespace osrm */
|
||||
@@ -31,7 +31,6 @@ const constexpr double LOOKAHEAD_DISTANCE_WITHOUT_LANES = 10.0;
|
||||
// The standard with of a interstate highway is 3.7 meters. Local roads have
|
||||
// smaller widths, ranging from 2.5 to 3.25 meters. As a compromise, we use
|
||||
// the 3.25 here for our angle calculations
|
||||
const constexpr double ASSUMED_LANE_WIDTH = 3.25;
|
||||
const constexpr double FAR_LOOKAHEAD_DISTANCE = 40.0;
|
||||
|
||||
// The count of lanes assumed when no lanes are present. Since most roads will have lanes for both
|
||||
@@ -650,7 +649,7 @@ bool CoordinateExtractor::IsCurve(const std::vector<util::Coordinate> &coordinat
|
||||
const auto end_bearing = util::coordinate_calculation::bearing(
|
||||
coordinates[coordinates.size() - 2], coordinates[coordinates.size() - 1]);
|
||||
|
||||
const auto total_angle = angularDeviation(begin_bearing, end_bearing);
|
||||
const auto total_angle = util::angularDeviation(begin_bearing, end_bearing);
|
||||
return total_angle > 0.5 * NARROW_TURN_ANGLE;
|
||||
}();
|
||||
|
||||
@@ -754,59 +753,61 @@ bool CoordinateExtractor::IsCurve(const std::vector<util::Coordinate> &coordinat
|
||||
return turn_angles;
|
||||
}();
|
||||
|
||||
const bool curve_is_valid =
|
||||
[&turn_angles, &segment_distances, &segment_length, &considered_lane_width]() {
|
||||
// internal state for our lamdae
|
||||
bool last_was_straight = false;
|
||||
// a turn angle represents two segments between three coordinates. We initialize the
|
||||
// distance with the very first segment length (in-segment) of the first turn-angle
|
||||
double straight_distance = std::max(0., segment_distances[1] - considered_lane_width);
|
||||
auto distance_itr = segment_distances.begin() + 1;
|
||||
const bool curve_is_valid = [&turn_angles,
|
||||
&segment_distances,
|
||||
&segment_length,
|
||||
&considered_lane_width]() {
|
||||
// internal state for our lamdae
|
||||
bool last_was_straight = false;
|
||||
// a turn angle represents two segments between three coordinates. We initialize the
|
||||
// distance with the very first segment length (in-segment) of the first turn-angle
|
||||
double straight_distance = std::max(0., segment_distances[1] - considered_lane_width);
|
||||
auto distance_itr = segment_distances.begin() + 1;
|
||||
|
||||
// every call to the lamda requires a call to the distances. They need to be aligned
|
||||
BOOST_ASSERT(segment_distances.size() == turn_angles.size() + 2);
|
||||
// every call to the lamda requires a call to the distances. They need to be aligned
|
||||
BOOST_ASSERT(segment_distances.size() == turn_angles.size() + 2);
|
||||
|
||||
const auto detect_invalid_curve = [&](const double previous_angle,
|
||||
const double current_angle) {
|
||||
const auto both_actually_turn =
|
||||
(angularDeviation(previous_angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE) &&
|
||||
(angularDeviation(current_angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE);
|
||||
// they cannot be straight, since they differ at least by FUZZY_ANGLE_DIFFERENCE
|
||||
const auto turn_direction_switches =
|
||||
(previous_angle > STRAIGHT_ANGLE) == (current_angle < STRAIGHT_ANGLE);
|
||||
const auto detect_invalid_curve = [&](const double previous_angle,
|
||||
const double current_angle) {
|
||||
const auto both_actually_turn =
|
||||
(util::angularDeviation(previous_angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE) &&
|
||||
(util::angularDeviation(current_angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE);
|
||||
// they cannot be straight, since they differ at least by FUZZY_ANGLE_DIFFERENCE
|
||||
const auto turn_direction_switches =
|
||||
(previous_angle > STRAIGHT_ANGLE) == (current_angle < STRAIGHT_ANGLE);
|
||||
|
||||
// a turn that switches direction mid-curve is not a valid curve
|
||||
if (both_actually_turn && turn_direction_switches)
|
||||
return true;
|
||||
// a turn that switches direction mid-curve is not a valid curve
|
||||
if (both_actually_turn && turn_direction_switches)
|
||||
return true;
|
||||
|
||||
const bool is_straight = angularDeviation(current_angle, STRAIGHT_ANGLE) < 5;
|
||||
++distance_itr;
|
||||
if (is_straight)
|
||||
const bool is_straight = util::angularDeviation(current_angle, STRAIGHT_ANGLE) < 5;
|
||||
++distance_itr;
|
||||
if (is_straight)
|
||||
{
|
||||
// since the angle is straight, we augment it by the second part of the segment
|
||||
straight_distance += *distance_itr;
|
||||
if (last_was_straight && straight_distance > 0.3 * segment_length)
|
||||
{
|
||||
// since the angle is straight, we augment it by the second part of the segment
|
||||
straight_distance += *distance_itr;
|
||||
if (last_was_straight && straight_distance > 0.3 * segment_length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
} // if a segment on its own is long enough, thats fair game as well
|
||||
else if (straight_distance > 0.3 * segment_length)
|
||||
return true;
|
||||
else
|
||||
{
|
||||
// we reset the last distance, starting with the next in-segment again
|
||||
straight_distance = *distance_itr;
|
||||
}
|
||||
last_was_straight = is_straight;
|
||||
return false;
|
||||
};
|
||||
} // if a segment on its own is long enough, thats fair game as well
|
||||
else if (straight_distance > 0.3 * segment_length)
|
||||
return true;
|
||||
else
|
||||
{
|
||||
// we reset the last distance, starting with the next in-segment again
|
||||
straight_distance = *distance_itr;
|
||||
}
|
||||
last_was_straight = is_straight;
|
||||
return false;
|
||||
};
|
||||
|
||||
const auto end_of_straight_segment =
|
||||
std::adjacent_find(turn_angles.begin(), turn_angles.end(), detect_invalid_curve);
|
||||
const auto end_of_straight_segment =
|
||||
std::adjacent_find(turn_angles.begin(), turn_angles.end(), detect_invalid_curve);
|
||||
|
||||
// No curve should have a very long straight segment
|
||||
return end_of_straight_segment == turn_angles.end();
|
||||
}();
|
||||
// No curve should have a very long straight segment
|
||||
return end_of_straight_segment == turn_angles.end();
|
||||
}();
|
||||
|
||||
return (segment_length > 2 * considered_lane_width && curve_is_valid);
|
||||
}
|
||||
@@ -1147,8 +1148,8 @@ CoordinateExtractor::RegressionLine(const std::vector<util::Coordinate> &coordin
|
||||
return {coordinates.front(), coordinates.back()};
|
||||
|
||||
// compute the regression vector based on the sum of least squares
|
||||
const auto regression_line =
|
||||
util::coordinate_calculation::leastSquareRegression(sampled_coordinates);
|
||||
const auto regression_line = util::coordinate_calculation::leastSquareRegression(
|
||||
sampled_coordinates.begin(), sampled_coordinates.end());
|
||||
const auto coord_between_front =
|
||||
util::coordinate_calculation::projectPointOnSegment(
|
||||
regression_line.first, regression_line.second, coordinates.front())
|
||||
|
||||
@@ -34,7 +34,7 @@ void ConnectedRoad::mirror()
|
||||
DirectionModifier::MaxDirectionModifier,
|
||||
"The list of mirrored modifiers needs to match the available modifiers in size.");
|
||||
|
||||
if (angularDeviation(angle, 0) > std::numeric_limits<double>::epsilon())
|
||||
if (util::angularDeviation(angle, 0) > std::numeric_limits<double>::epsilon())
|
||||
{
|
||||
angle = 360 - angle;
|
||||
instruction.direction_modifier = mirrored_modifiers[instruction.direction_modifier];
|
||||
@@ -48,6 +48,26 @@ ConnectedRoad ConnectedRoad::getMirroredCopy() const
|
||||
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] ";
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "extractor/guidance/intersection_generator.hpp"
|
||||
|
||||
#include "extractor/geojson_debug_policies.hpp"
|
||||
#include "util/geojson_debug_logger.hpp"
|
||||
|
||||
#include "util/bearing.hpp"
|
||||
#include "util/coordinate_calculation.hpp"
|
||||
|
||||
@@ -76,7 +79,9 @@ IntersectionGenerator::ComputeIntersectionShape(const NodeID node_at_center_of_i
|
||||
node_at_center_of_intersection, edge_connected_to_intersection, !INVERT, to_node);
|
||||
|
||||
const auto segment_length = util::coordinate_calculation::getLength(
|
||||
coordinates, util::coordinate_calculation::haversineDistance);
|
||||
coordinates.begin(),
|
||||
coordinates.end(),
|
||||
util::coordinate_calculation::haversineDistance);
|
||||
|
||||
const auto extract_coordinate = [&](const NodeID from_node,
|
||||
const EdgeID via_eid,
|
||||
@@ -116,9 +121,9 @@ IntersectionGenerator::ComputeIntersectionShape(const NodeID node_at_center_of_i
|
||||
return node_based_graph.GetTarget(data.eid) == *sorting_base;
|
||||
});
|
||||
if (itr != intersection.end())
|
||||
return util::reverseBearing(itr->bearing);
|
||||
return util::bearing::reverse(itr->bearing);
|
||||
}
|
||||
return util::reverseBearing(intersection.begin()->bearing);
|
||||
return util::bearing::reverse(intersection.begin()->bearing);
|
||||
}();
|
||||
std::sort(
|
||||
intersection.begin(), intersection.end(), makeCompareShapeDataByBearing(base_bearing));
|
||||
@@ -154,8 +159,8 @@ IntersectionView IntersectionGenerator::GetConnectedRoads(const NodeID from_node
|
||||
return TransformIntersectionShapeIntoView(from_node, via_eid, std::move(intersection));
|
||||
}
|
||||
|
||||
std::pair<NodeID, EdgeID> IntersectionGenerator::SkipDegreeTwoNodes(const NodeID starting_node,
|
||||
const EdgeID via_edge) const
|
||||
IntersectionGenerationParameters
|
||||
IntersectionGenerator::SkipDegreeTwoNodes(const NodeID starting_node, const EdgeID via_edge) const
|
||||
{
|
||||
NodeID query_node = starting_node;
|
||||
EdgeID query_edge = via_edge;
|
||||
@@ -177,16 +182,17 @@ std::pair<NodeID, EdgeID> IntersectionGenerator::SkipDegreeTwoNodes(const NodeID
|
||||
visited_nodes.insert(query_node);
|
||||
const auto next_node = node_based_graph.GetTarget(query_edge);
|
||||
const auto next_edge = get_next_edge(query_node, query_edge);
|
||||
|
||||
query_node = next_node;
|
||||
query_edge = next_edge;
|
||||
|
||||
if (!node_based_graph.GetEdgeData(query_edge)
|
||||
.IsCompatibleTo(node_based_graph.GetEdgeData(next_edge)) ||
|
||||
node_based_graph.GetTarget(next_edge) == starting_node)
|
||||
break;
|
||||
|
||||
query_node = next_node;
|
||||
query_edge = next_edge;
|
||||
}
|
||||
|
||||
return std::make_pair(query_node, query_edge);
|
||||
return {query_node, query_edge};
|
||||
}
|
||||
|
||||
IntersectionView IntersectionGenerator::TransformIntersectionShapeIntoView(
|
||||
@@ -205,9 +211,9 @@ IntersectionView IntersectionGenerator::TransformIntersectionShapeIntoView(
|
||||
IntersectionView IntersectionGenerator::TransformIntersectionShapeIntoView(
|
||||
const NodeID previous_node,
|
||||
const EdgeID entering_via_edge,
|
||||
const IntersectionShape &normalised_intersection,
|
||||
const IntersectionShape &normalized_intersection,
|
||||
const IntersectionShape &intersection,
|
||||
const std::vector<std::pair<EdgeID, EdgeID>> &performed_merges) const
|
||||
const std::vector<IntersectionNormalizationOperation> &performed_merges) const
|
||||
{
|
||||
const auto node_at_intersection = node_based_graph.GetTarget(entering_via_edge);
|
||||
|
||||
@@ -259,40 +265,40 @@ IntersectionView IntersectionGenerator::TransformIntersectionShapeIntoView(
|
||||
const auto uturn_bearing = [&]() {
|
||||
const auto merge_entry = std::find_if(
|
||||
performed_merges.begin(), performed_merges.end(), [&uturn_edge_itr](const auto entry) {
|
||||
return entry.first == uturn_edge_itr->eid;
|
||||
return entry.merged_eid == uturn_edge_itr->eid;
|
||||
});
|
||||
if (merge_entry != performed_merges.end())
|
||||
{
|
||||
const auto merged_into_id = merge_entry->second;
|
||||
const auto merged_into_id = merge_entry->into_eid;
|
||||
const auto merged_u_turn = std::find_if(
|
||||
normalised_intersection.begin(),
|
||||
normalised_intersection.end(),
|
||||
normalized_intersection.begin(),
|
||||
normalized_intersection.end(),
|
||||
[&](const IntersectionShapeData &road) { return road.eid == merged_into_id; });
|
||||
BOOST_ASSERT(merged_u_turn != normalised_intersection.end());
|
||||
return util::reverseBearing(merged_u_turn->bearing);
|
||||
BOOST_ASSERT(merged_u_turn != normalized_intersection.end());
|
||||
return util::bearing::reverse(merged_u_turn->bearing);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto uturn_edge_at_normalised_intersection_itr =
|
||||
std::find_if(normalised_intersection.begin(),
|
||||
normalised_intersection.end(),
|
||||
const auto uturn_edge_at_normalized_intersection_itr =
|
||||
std::find_if(normalized_intersection.begin(),
|
||||
normalized_intersection.end(),
|
||||
connect_to_previous_node);
|
||||
BOOST_ASSERT(uturn_edge_at_normalised_intersection_itr !=
|
||||
normalised_intersection.end());
|
||||
return util::reverseBearing(uturn_edge_at_normalised_intersection_itr->bearing);
|
||||
BOOST_ASSERT(uturn_edge_at_normalized_intersection_itr !=
|
||||
normalized_intersection.end());
|
||||
return util::bearing::reverse(uturn_edge_at_normalized_intersection_itr->bearing);
|
||||
}
|
||||
}();
|
||||
|
||||
IntersectionView intersection_view;
|
||||
intersection_view.reserve(normalised_intersection.size());
|
||||
std::transform(normalised_intersection.begin(),
|
||||
normalised_intersection.end(),
|
||||
intersection_view.reserve(normalized_intersection.size());
|
||||
std::transform(normalized_intersection.begin(),
|
||||
normalized_intersection.end(),
|
||||
std::back_inserter(intersection_view),
|
||||
[&](const IntersectionShapeData &road) {
|
||||
return IntersectionViewData(
|
||||
road,
|
||||
is_allowed_turn(road),
|
||||
util::angleBetweenBearings(uturn_bearing, road.bearing));
|
||||
util::bearing::angleBetween(uturn_bearing, road.bearing));
|
||||
});
|
||||
|
||||
const auto uturn_edge_at_intersection_view_itr =
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
#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
|
||||
@@ -384,19 +388,17 @@ IntersectionHandler::getNextIntersection(const NodeID at, const EdgeID via) cons
|
||||
// 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.
|
||||
|
||||
NodeID node = SPECIAL_NODEID;
|
||||
EdgeID edge = SPECIAL_EDGEID;
|
||||
|
||||
std::tie(node, edge) = intersection_generator.SkipDegreeTwoNodes(at, via);
|
||||
auto intersection = intersection_generator(node, edge);
|
||||
|
||||
const auto intersection_parameters = intersection_generator.SkipDegreeTwoNodes(at, via);
|
||||
// This should never happen, guard against nevertheless
|
||||
if (node == SPECIAL_NODEID || edge == SPECIAL_EDGEID)
|
||||
if (intersection_parameters.nid == SPECIAL_NODEID ||
|
||||
intersection_parameters.via_eid == SPECIAL_EDGEID)
|
||||
{
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
auto intersection_node = node_based_graph.GetTarget(edge);
|
||||
auto intersection =
|
||||
intersection_generator(intersection_parameters.nid, intersection_parameters.via_eid);
|
||||
auto intersection_node = node_based_graph.GetTarget(intersection_parameters.via_eid);
|
||||
|
||||
if (intersection.size() <= 2 || intersection.isTrafficSignalOrBarrier())
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,469 @@
|
||||
#include "extractor/guidance/mergable_road_detector.hpp"
|
||||
#include "extractor/guidance/constants.hpp"
|
||||
#include "extractor/guidance/coordinate_extractor.hpp"
|
||||
#include "extractor/guidance/intersection_generator.hpp"
|
||||
#include "extractor/guidance/node_based_graph_walker.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
|
||||
inline auto makeCheckRoadForName(const NameID name_id,
|
||||
const util::NodeBasedDynamicGraph &node_based_graph,
|
||||
const util::NameTable &name_table,
|
||||
const SuffixTable &suffix_table)
|
||||
{
|
||||
return [name_id, &node_based_graph, &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 = node_based_graph.GetEdgeData(road.eid).name_id;
|
||||
if (name_id == EMPTY_NAMEID || road_name == EMPTY_NAMEID)
|
||||
return true;
|
||||
const auto requires_announcement =
|
||||
util::guidance::requiresNameAnnounced(name_id, road_name, name_table, suffix_table) ||
|
||||
util::guidance::requiresNameAnnounced(road_name, name_id, name_table, suffix_table);
|
||||
|
||||
return requires_announcement;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
MergableRoadDetector::MergableRoadDetector(const util::NodeBasedDynamicGraph &node_based_graph,
|
||||
const std::vector<QueryNode> &node_coordinates,
|
||||
const IntersectionGenerator &intersection_generator,
|
||||
const CoordinateExtractor &coordinate_extractor,
|
||||
const util::NameTable &name_table,
|
||||
const SuffixTable &street_name_suffix_table)
|
||||
: node_based_graph(node_based_graph), node_coordinates(node_coordinates),
|
||||
intersection_generator(intersection_generator), coordinate_extractor(coordinate_extractor),
|
||||
name_table(name_table), street_name_suffix_table(street_name_suffix_table)
|
||||
{
|
||||
}
|
||||
|
||||
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_data = node_based_graph.GetEdgeData(lhs.eid);
|
||||
const auto &rhs_edge_data = node_based_graph.GetEdgeData(rhs.eid);
|
||||
|
||||
// and they need to describe the same road
|
||||
if (!EdgeDataSupportsMerge(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(lhs) == 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);
|
||||
}
|
||||
|
||||
bool MergableRoadDetector::HaveIdenticalNames(const NameID lhs, const NameID rhs) const
|
||||
{
|
||||
const auto non_empty = (lhs != EMPTY_NAMEID) && (rhs != EMPTY_NAMEID);
|
||||
|
||||
// symmetrical check for announcements
|
||||
return non_empty &&
|
||||
!util::guidance::requiresNameAnnounced(lhs, rhs, name_table, street_name_suffix_table) &&
|
||||
!util::guidance::requiresNameAnnounced(rhs, lhs, name_table, street_name_suffix_table);
|
||||
}
|
||||
|
||||
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_based_graph.GetEdgeData(lhs.eid).name_id,
|
||||
node_based_graph.GetEdgeData(rhs.eid).name_id);
|
||||
}
|
||||
|
||||
bool MergableRoadDetector::EdgeDataSupportsMerge(const util::NodeBasedEdgeData &lhs_edge_data,
|
||||
const util::NodeBasedEdgeData &rhs_edge_data) const
|
||||
{
|
||||
// roundabouts are special, simply don't hurt them. We might not want to bear the
|
||||
// consequences
|
||||
if (lhs_edge_data.roundabout || rhs_edge_data.roundabout)
|
||||
return false;
|
||||
|
||||
/* to describe the same road, but in opposite directions (which is what we require for a
|
||||
* merge), the roads have to feature one reversed and one non-reversed edge
|
||||
*/
|
||||
if (lhs_edge_data.reversed == rhs_edge_data.reversed)
|
||||
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_edge_data.travel_mode != rhs_edge_data.travel_mode)
|
||||
return false;
|
||||
|
||||
// we require valid names
|
||||
if (!HaveIdenticalNames(lhs_edge_data.name_id, rhs_edge_data.name_id))
|
||||
return false;
|
||||
|
||||
return lhs_edge_data.road_classification == rhs_edge_data.road_classification;
|
||||
}
|
||||
|
||||
bool MergableRoadDetector::IsTrafficLoop(const NodeID intersection_node,
|
||||
const MergableRoadData &road) const
|
||||
{
|
||||
const auto connection = intersection_generator.SkipDegreeTwoNodes(intersection_node, road.eid);
|
||||
return intersection_node == node_based_graph.GetTarget(connection.via_eid);
|
||||
}
|
||||
|
||||
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, intersection_generator),
|
||||
right_accumulator(SMALL_RANDOM_HOPLIMIT, intersection_generator);
|
||||
|
||||
/* Standard following the straightmost road
|
||||
* Since both items have the same id, we can `select` based on any setup
|
||||
*/
|
||||
SelectStraightmostRoadByNameAndOnlyChoice selector(
|
||||
node_based_graph.GetEdgeData(lhs.eid).name_id, lhs.bearing, /*requires entry=*/false);
|
||||
|
||||
NodeBasedGraphWalker graph_walker(node_based_graph, intersection_generator);
|
||||
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).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,
|
||||
intersection_generator);
|
||||
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::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, intersection_generator);
|
||||
const auto getCoordinatesAlongWay = [&](const EdgeID edge_id, const double max_length) {
|
||||
LengthLimitedCoordinateAccumulator accumulator(coordinate_extractor, max_length);
|
||||
SelectStraightmostRoadByNameAndOnlyChoice selector(
|
||||
node_based_graph.GetEdgeData(edge_id).name_id, lhs.bearing, /*requires_entry=*/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;
|
||||
|
||||
// many roads only do short parallel segments. To get a good impression of how `parallel` two
|
||||
// roads are, we look 100 meters down the road (wich can be quite short for very broad roads).
|
||||
const double constexpr distance_to_extract = 100;
|
||||
|
||||
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_between_roads = 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 lane_count_lhs = std::max<int>(
|
||||
1, node_based_graph.GetEdgeData(lhs.eid).road_classification.GetNumberOfLanes());
|
||||
const auto lane_count_rhs = std::max<int>(
|
||||
1, node_based_graph.GetEdgeData(rhs.eid).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 = 8;
|
||||
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_generator.SkipDegreeTwoNodes(intersection_node, lhs.eid);
|
||||
const auto right_connection =
|
||||
intersection_generator.SkipDegreeTwoNodes(intersection_node, rhs.eid);
|
||||
|
||||
const auto left_candidate = node_based_graph.GetTarget(left_connection.via_eid);
|
||||
const auto right_candidate = node_based_graph.GetTarget(right_connection.via_eid);
|
||||
|
||||
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_based_graph.GetEdgeData(range.front()).name_id;
|
||||
|
||||
const auto has_required_name = [this, required_name_id](const auto edge_id) {
|
||||
const auto road_name = node_based_graph.GetEdgeData(edge_id).name_id;
|
||||
if (required_name_id == EMPTY_NAMEID || road_name == EMPTY_NAMEID)
|
||||
return false;
|
||||
return !util::guidance::requiresNameAnnounced(
|
||||
required_name_id, road_name, name_table, street_name_suffix_table) ||
|
||||
!util::guidance::requiresNameAnnounced(
|
||||
road_name, 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_generator.SkipDegreeTwoNodes(intersection_node, road.eid);
|
||||
const auto next_intersection_along_road = intersection_generator.GetConnectedRoads(
|
||||
next_intersection_parameters.nid, next_intersection_parameters.via_eid);
|
||||
const auto extract_name_id = [this](const MergableRoadData &road) {
|
||||
return node_based_graph.GetEdgeData(road.eid).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, 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.nid)
|
||||
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) &&
|
||||
EdgeDataSupportsMerge(
|
||||
node_based_graph.GetEdgeData(next_road_along_path->eid),
|
||||
node_based_graph.GetEdgeData(opposite_of_next_road_along_path->eid));
|
||||
}
|
||||
|
||||
} // namespace guidance
|
||||
} // namespace extractor
|
||||
} // namespace osrm
|
||||
@@ -1,4 +1,7 @@
|
||||
#include "extractor/guidance/node_based_graph_walker.hpp"
|
||||
#include "util/bearing.hpp"
|
||||
#include "util/coordinate_calculation.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
using osrm::util::angularDeviation;
|
||||
@@ -18,11 +21,8 @@ NodeBasedGraphWalker::NodeBasedGraphWalker(const util::NodeBasedDynamicGraph &no
|
||||
}
|
||||
|
||||
LengthLimitedCoordinateAccumulator::LengthLimitedCoordinateAccumulator(
|
||||
const extractor::guidance::CoordinateExtractor &coordinate_extractor,
|
||||
const util::NodeBasedDynamicGraph &node_based_graph,
|
||||
const double max_length)
|
||||
: coordinate_extractor(coordinate_extractor), node_based_graph(node_based_graph),
|
||||
max_length(max_length), accumulated_length(0)
|
||||
const extractor::guidance::CoordinateExtractor &coordinate_extractor, const double max_length)
|
||||
: accumulated_length(0), coordinate_extractor(coordinate_extractor), max_length(max_length)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -37,8 +37,10 @@ void LengthLimitedCoordinateAccumulator::update(const NodeID from_node,
|
||||
auto current_coordinates =
|
||||
coordinate_extractor.GetForwardCoordinatesAlongRoad(from_node, via_edge);
|
||||
|
||||
const auto length = util::coordinate_calculation::getLength(
|
||||
current_coordinates, util::coordinate_calculation::haversineDistance);
|
||||
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)
|
||||
@@ -48,6 +50,7 @@ void LengthLimitedCoordinateAccumulator::update(const NodeID from_node,
|
||||
coordinates.insert(coordinates.end(), current_coordinates.begin(), current_coordinates.end());
|
||||
|
||||
accumulated_length += length;
|
||||
accumulated_length = std::min(accumulated_length, max_length);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
@@ -60,17 +63,15 @@ SelectRoadByNameOnlyChoiceAndStraightness::SelectRoadByNameOnlyChoiceAndStraight
|
||||
boost::optional<EdgeID> SelectRoadByNameOnlyChoiceAndStraightness::
|
||||
operator()(const NodeID /*nid*/,
|
||||
const EdgeID /*via_edge_id*/,
|
||||
const Intersection &intersection,
|
||||
const IntersectionView &intersection,
|
||||
const util::NodeBasedDynamicGraph &node_based_graph) const
|
||||
{
|
||||
BOOST_ASSERT(!intersection.empty());
|
||||
const auto comparator = [this, &node_based_graph](const ConnectedRoad &lhs,
|
||||
const ConnectedRoad &rhs) {
|
||||
const auto comparator = [this, &node_based_graph](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 = [this, &node_based_graph](const ConnectedRoad &road) {
|
||||
// invalid requested name_ids over non-requested narrow deviations over non-narrow
|
||||
const auto score = [this, &node_based_graph](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)
|
||||
@@ -95,6 +96,75 @@ operator()(const NodeID /*nid*/,
|
||||
return (*min_element).eid;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
SelectStraightmostRoadByNameAndOnlyChoice::SelectStraightmostRoadByNameAndOnlyChoice(
|
||||
const NameID desired_name_id, const double initial_bearing, const bool requires_entry)
|
||||
: desired_name_id(desired_name_id), initial_bearing(initial_bearing),
|
||||
requires_entry(requires_entry)
|
||||
{
|
||||
}
|
||||
|
||||
boost::optional<EdgeID> SelectStraightmostRoadByNameAndOnlyChoice::
|
||||
operator()(const NodeID /*nid*/,
|
||||
const EdgeID /*via_edge_id*/,
|
||||
const IntersectionView &intersection,
|
||||
const util::NodeBasedDynamicGraph &node_based_graph) const
|
||||
{
|
||||
BOOST_ASSERT(!intersection.empty());
|
||||
if (intersection.size() == 1)
|
||||
return {};
|
||||
|
||||
const auto comparator = [this, &node_based_graph](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 = [this, &node_based_graph](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_based_graph.GetEdgeData(road.eid).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),
|
||||
[this, &node_based_graph](const auto &road) {
|
||||
return node_based_graph.GetEdgeData(road.eid).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;
|
||||
const auto is_only_choice_with_same_name =
|
||||
count_desired_name <= 2 && // <= in case we come from a bridge
|
||||
node_based_graph.GetEdgeData(min_element->eid).name_id == desired_name_id &&
|
||||
angularDeviation(min_element->angle, STRAIGHT_ANGLE) < 100; // don't do crazy turns
|
||||
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;
|
||||
|
||||
// in cases where we have two edges between roads, we can have quite severe angles due to the
|
||||
// random split OSRM does to break up parallel edges at any coordinate
|
||||
if (!is_valid_choice || !(is_only_choice_with_same_name || has_valid_angle))
|
||||
return {};
|
||||
else
|
||||
return (*min_element).eid;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
IntersectionFinderAccumulator::IntersectionFinderAccumulator(
|
||||
const std::uint8_t hop_limit, const IntersectionGenerator &intersection_generator)
|
||||
@@ -123,7 +193,7 @@ void IntersectionFinderAccumulator::update(const NodeID from_node,
|
||||
nid = from_node;
|
||||
via_edge_id = via_edge;
|
||||
|
||||
intersection = intersection_generator.GetConnectedRoads(from_node, via_edge);
|
||||
intersection = intersection_generator.GetConnectedRoads(from_node, via_edge, true);
|
||||
}
|
||||
|
||||
} // namespace guidance
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "util/guidance/name_announcements.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
||||
@@ -80,13 +80,13 @@ Intersection TurnAnalysis::operator()(const NodeID node_prior_to_intersection,
|
||||
TurnAnalysis::ShapeResult shape_result =
|
||||
ComputeIntersectionShapes(node_based_graph.GetTarget(entering_via_edge));
|
||||
|
||||
// assign valid flags to normalised_shape
|
||||
// assign valid flags to normalized_shape
|
||||
const auto intersection_view = intersection_generator.TransformIntersectionShapeIntoView(
|
||||
node_prior_to_intersection,
|
||||
entering_via_edge,
|
||||
shape_result.normalised_intersection_shape,
|
||||
shape_result.annotated_normalized_shape.normalized_shape,
|
||||
shape_result.intersection_shape,
|
||||
shape_result.merging_map);
|
||||
shape_result.annotated_normalized_shape.performed_merges);
|
||||
|
||||
// assign the turn types to the intersection
|
||||
return AssignTurnTypes(node_prior_to_intersection, entering_via_edge, intersection_view);
|
||||
@@ -171,9 +171,8 @@ TurnAnalysis::ComputeIntersectionShapes(const NodeID node_at_center_of_intersect
|
||||
intersection_shape.intersection_shape =
|
||||
intersection_generator.ComputeIntersectionShape(node_at_center_of_intersection);
|
||||
|
||||
std::tie(intersection_shape.normalised_intersection_shape, intersection_shape.merging_map) =
|
||||
intersection_normalizer(node_at_center_of_intersection,
|
||||
intersection_shape.intersection_shape);
|
||||
intersection_shape.annotated_normalized_shape = intersection_normalizer(
|
||||
node_at_center_of_intersection, intersection_shape.intersection_shape);
|
||||
|
||||
return intersection_shape;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "extractor/guidance/turn_discovery.hpp"
|
||||
#include "extractor/guidance/constants.hpp"
|
||||
#include "util/bearing.hpp"
|
||||
#include "util/coordinate_calculation.hpp"
|
||||
|
||||
using osrm::util::angularDeviation;
|
||||
@@ -43,9 +44,12 @@ bool findPreviousIntersection(const NodeID node_v,
|
||||
const constexpr double COMBINE_DISTANCE_CUTOFF = 30;
|
||||
|
||||
const auto coordinate_extractor = intersection_generator.GetCoordinateExtractor();
|
||||
const auto via_edge_length = util::coordinate_calculation::getLength(
|
||||
coordinate_extractor.GetForwardCoordinatesAlongRoad(node_v, via_edge),
|
||||
&util::coordinate_calculation::haversineDistance);
|
||||
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
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData;
|
||||
using osrm::extractor::guidance::getTurnDirection;
|
||||
using osrm::util::angularDeviation;
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
@@ -125,29 +125,17 @@ Coordinate centroid(const Coordinate lhs, const Coordinate rhs)
|
||||
return centroid;
|
||||
}
|
||||
|
||||
double degToRad(const double degree)
|
||||
{
|
||||
using namespace boost::math::constants;
|
||||
return degree * (pi<double>() / 180.0);
|
||||
}
|
||||
|
||||
double radToDeg(const double radian)
|
||||
{
|
||||
using namespace boost::math::constants;
|
||||
return radian * (180.0 * (1. / pi<double>()));
|
||||
}
|
||||
|
||||
double bearing(const Coordinate first_coordinate, const Coordinate second_coordinate)
|
||||
{
|
||||
const double lon_diff =
|
||||
static_cast<double>(toFloating(second_coordinate.lon - first_coordinate.lon));
|
||||
const double lon_delta = degToRad(lon_diff);
|
||||
const double lat1 = degToRad(static_cast<double>(toFloating(first_coordinate.lat)));
|
||||
const double lat2 = degToRad(static_cast<double>(toFloating(second_coordinate.lat)));
|
||||
const double lon_delta = detail::degToRad(lon_diff);
|
||||
const double lat1 = detail::degToRad(static_cast<double>(toFloating(first_coordinate.lat)));
|
||||
const double lat2 = detail::degToRad(static_cast<double>(toFloating(second_coordinate.lat)));
|
||||
const double y = std::sin(lon_delta) * std::cos(lat2);
|
||||
const double x =
|
||||
std::cos(lat1) * std::sin(lat2) - std::sin(lat1) * std::cos(lat2) * std::cos(lon_delta);
|
||||
double result = radToDeg(std::atan2(y, x));
|
||||
double result = detail::radToDeg(std::atan2(y, x));
|
||||
while (result < 0.0)
|
||||
{
|
||||
result += 360.0;
|
||||
@@ -320,47 +308,72 @@ bool isCCW(const Coordinate first_coordinate,
|
||||
return signedArea(first_coordinate, second_coordinate, third_coordinate) > 0;
|
||||
}
|
||||
|
||||
std::pair<util::Coordinate, util::Coordinate>
|
||||
leastSquareRegression(const std::vector<util::Coordinate> &coordinates)
|
||||
// find the closest distance between a coordinate and a segment
|
||||
double findClosestDistance(const Coordinate coordinate,
|
||||
const Coordinate segment_begin,
|
||||
const Coordinate segment_end)
|
||||
{
|
||||
BOOST_ASSERT(coordinates.size() >= 2);
|
||||
double sum_lon = 0, sum_lat = 0, sum_lon_lat = 0, sum_lon_lon = 0;
|
||||
double min_lon = static_cast<double>(toFloating(coordinates.front().lon));
|
||||
double max_lon = static_cast<double>(toFloating(coordinates.front().lon));
|
||||
for (const auto coord : coordinates)
|
||||
{
|
||||
min_lon = std::min(min_lon, static_cast<double>(toFloating(coord.lon)));
|
||||
max_lon = std::max(max_lon, static_cast<double>(toFloating(coord.lon)));
|
||||
sum_lon += static_cast<double>(toFloating(coord.lon));
|
||||
sum_lon_lon +=
|
||||
static_cast<double>(toFloating(coord.lon)) * static_cast<double>(toFloating(coord.lon));
|
||||
sum_lat += static_cast<double>(toFloating(coord.lat));
|
||||
sum_lon_lat +=
|
||||
static_cast<double>(toFloating(coord.lon)) * static_cast<double>(toFloating(coord.lat));
|
||||
}
|
||||
return haversineDistance(coordinate,
|
||||
projectPointOnSegment(segment_begin, segment_end, coordinate).second);
|
||||
}
|
||||
|
||||
const auto dividend = coordinates.size() * sum_lon_lat - sum_lon * sum_lat;
|
||||
const auto divisor = coordinates.size() * sum_lon_lon - sum_lon * sum_lon;
|
||||
if (std::abs(divisor) < std::numeric_limits<double>::epsilon())
|
||||
return std::make_pair(coordinates.front(), coordinates.back());
|
||||
// find the closes distance between two sets of coordinates
|
||||
double findClosestDistance(const std::vector<Coordinate> &lhs, const std::vector<Coordinate> &rhs)
|
||||
{
|
||||
double current_min = std::numeric_limits<double>::max();
|
||||
|
||||
// slope of the regression line
|
||||
const auto slope = dividend / divisor;
|
||||
const auto intercept = (sum_lat - slope * sum_lon) / coordinates.size();
|
||||
|
||||
const auto GetLatAtLon = [intercept,
|
||||
slope](const util::FloatLongitude longitude) -> util::FloatLatitude {
|
||||
return {intercept + slope * static_cast<double>((longitude))};
|
||||
const auto compute_minimum_distance_in_rhs = [¤t_min, &rhs](const Coordinate coordinate) {
|
||||
current_min =
|
||||
std::min(current_min, findClosestDistance(coordinate, rhs.begin(), rhs.end()));
|
||||
return false;
|
||||
};
|
||||
|
||||
const util::Coordinate regression_first = {
|
||||
toFixed(util::FloatLongitude{min_lon - 1}),
|
||||
toFixed(util::FloatLatitude(GetLatAtLon(util::FloatLongitude{min_lon - 1})))};
|
||||
const util::Coordinate regression_end = {
|
||||
toFixed(util::FloatLongitude{max_lon + 1}),
|
||||
toFixed(util::FloatLatitude(GetLatAtLon(util::FloatLongitude{max_lon + 1})))};
|
||||
std::find_if(std::begin(lhs), std::end(lhs), compute_minimum_distance_in_rhs);
|
||||
return current_min;
|
||||
}
|
||||
|
||||
return {regression_first, regression_end};
|
||||
std::vector<double> getDeviations(const std::vector<Coordinate> &from,
|
||||
const std::vector<Coordinate> &to)
|
||||
{
|
||||
auto find_deviation = [&to](const Coordinate coordinate) {
|
||||
return findClosestDistance(coordinate, to.begin(), to.end());
|
||||
};
|
||||
|
||||
std::vector<double> deviations_from;
|
||||
deviations_from.reserve(from.size());
|
||||
std::transform(
|
||||
std::begin(from), std::end(from), std::back_inserter(deviations_from), find_deviation);
|
||||
|
||||
return deviations_from;
|
||||
}
|
||||
|
||||
Coordinate rotateCCWAroundZero(Coordinate coordinate, double angle_in_radians)
|
||||
{
|
||||
/*
|
||||
* a rotation around 0,0 in vector space is defined as
|
||||
*
|
||||
* | cos a -sin a | . | lon |
|
||||
* | sin a cos a | | lat |
|
||||
*
|
||||
* resulting in cos a lon - sin a lon for the new longitude and sin a lon + cos a lat for the
|
||||
* new latitude
|
||||
*/
|
||||
|
||||
const auto cos_alpha = cos(angle_in_radians);
|
||||
const auto sin_alpha = sin(angle_in_radians);
|
||||
|
||||
const auto lon = static_cast<double>(toFloating(coordinate.lon));
|
||||
const auto lat = static_cast<double>(toFloating(coordinate.lat));
|
||||
|
||||
return {util::FloatLongitude{cos_alpha * lon - sin_alpha * lat},
|
||||
util::FloatLatitude{sin_alpha * lon + cos_alpha * lat}};
|
||||
}
|
||||
|
||||
Coordinate difference(const Coordinate lhs, const Coordinate rhs)
|
||||
{
|
||||
const auto lon_diff_int = static_cast<int>(lhs.lon) - static_cast<int>(rhs.lon);
|
||||
const auto lat_diff_int = static_cast<int>(lhs.lat) - static_cast<int>(rhs.lat);
|
||||
return {util::FixedLongitude{lon_diff_int}, util::FixedLatitude{lat_diff_int}};
|
||||
}
|
||||
|
||||
} // ns coordinate_calculation
|
||||
|
||||
@@ -36,6 +36,7 @@ NodeIdVectorToMultiPoint::NodeIdVectorToMultiPoint(
|
||||
: node_coordinates(node_coordinates)
|
||||
{
|
||||
}
|
||||
|
||||
util::json::Object NodeIdVectorToMultiPoint::
|
||||
operator()(const std::vector<NodeID> &node_ids,
|
||||
const boost::optional<json::Object> &properties) const
|
||||
|
||||
Reference in New Issue
Block a user