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:
Moritz Kobitzsch
2016-12-06 13:22:51 +01:00
parent f7ad2e1e26
commit e6ff17ab2a
40 changed files with 2397 additions and 949 deletions
+9 -8
View File
@@ -154,7 +154,7 @@ inline std::vector<RouteStep> assembleSteps(const datafacade::BaseDataFacade &fa
intersection.entry.push_back(entry_class.allowsEntry(idx));
}
std::int16_t bearing_in_driving_direction =
util::reverseBearing(std::round(bearings.first));
util::bearing::reverse(std::round(bearings.first));
maneuver = {intersection.location,
bearing_in_driving_direction,
bearings.second,
@@ -214,13 +214,14 @@ inline std::vector<RouteStep> assembleSteps(const datafacade::BaseDataFacade &fa
BOOST_ASSERT(segment_index == number_of_segments - 1);
bearings = detail::getArriveBearings(leg_geometry);
intersection = {target_node.location,
std::vector<short>({static_cast<short>(util::reverseBearing(bearings.first))}),
std::vector<bool>({true}),
0,
IntermediateIntersection::NO_INDEX,
util::guidance::LaneTuple(),
{}};
intersection = {
target_node.location,
std::vector<short>({static_cast<short>(util::bearing::reverse(bearings.first))}),
std::vector<bool>({true}),
0,
IntermediateIntersection::NO_INDEX,
util::guidance::LaneTuple(),
{}};
// This step has length zero, the only reason we need it is the target location
maneuver = {intersection.location,
+66 -2
View File
@@ -1,6 +1,7 @@
#ifndef OSRM_EXTRACTOR_GEOJSON_DEBUG_POLICIES
#define OSRM_EXTRACTOR_GEOJSON_DEBUG_POLICIES
#include <algorithm>
#include <vector>
#include "extractor/query_node.hpp"
@@ -11,6 +12,8 @@
#include "extractor/guidance/coordinate_extractor.hpp"
#include "extractor/guidance/intersection.hpp"
#include "util/coordinate.hpp"
#include "util/geojson_debug_policy_toolkit.hpp"
#include <boost/optional.hpp>
@@ -18,8 +21,9 @@ namespace osrm
{
namespace extractor
{
// generate a visualisation of an intersection, printing the coordinates used for angle calculation
struct IntersectionPrinter
template <typename IntersectionType> struct IntersectionPrinter
{
IntersectionPrinter(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<extractor::QueryNode> &node_coordinates,
@@ -28,7 +32,7 @@ struct IntersectionPrinter
// renders the used coordinate locations for all entries/as well as the resulting
// intersection-classification
util::json::Array operator()(const NodeID intersection_node,
const extractor::guidance::Intersection &intersection,
const IntersectionType &intersection,
const boost::optional<util::json::Object> &node_style = {},
const boost::optional<util::json::Object> &way_style = {}) const;
@@ -37,6 +41,66 @@ struct IntersectionPrinter
const extractor::guidance::CoordinateExtractor &coordinate_extractor;
};
// IMPLEMENTATION
template <typename IntersectionType>
IntersectionPrinter<IntersectionType>::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)
{
}
template <typename IntersectionType>
util::json::Array IntersectionPrinter<IntersectionType>::
operator()(const NodeID intersection_node,
const IntersectionType &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.FindMaximum(guidance::makeExtractLanesForRoad(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 auto &road) {
const constexpr auto FORWARD = false;
const auto to_node = node_based_graph.GetTarget(road.eid);
return coordinate_extractor.GetCoordinateAlongRoad(
intersection_node, 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 */
+7
View File
@@ -12,6 +12,7 @@ const bool constexpr INVERT = true;
// what angle is interpreted as going straight
const double constexpr STRAIGHT_ANGLE = 180.;
const double constexpr ORTHOGONAL_ANGLE = 90.;
// if a turn deviates this much from going straight, it will be kept straight
const double constexpr MAXIMAL_ALLOWED_NO_TURN_DEVIATION = 3.;
// angle that lies between two nearly indistinguishable roads
@@ -36,6 +37,12 @@ const int constexpr MAX_SLIPROAD_THRESHOLD = 250;
// category).
const double constexpr PRIORITY_DISTINCTION_FACTOR = 1.75;
// the lane width we assume for a single lane
const auto constexpr ASSUMED_LANE_WIDTH = 3.25;
// how far apart can roads be at the most, when thinking about merging them?
const auto constexpr MERGABLE_ANGLE_DIFFERENCE = 95.0;
} // namespace guidance
} // namespace extractor
} // namespace osrm
+91 -33
View File
@@ -8,15 +8,15 @@
#include <type_traits>
#include <vector>
#include "extractor/guidance/turn_instruction.hpp"
#include "util/bearing.hpp"
#include "util/node_based_graph.hpp"
#include "util/typedefs.hpp" // EdgeID
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include "extractor/guidance/turn_instruction.hpp"
#include <boost/assert.hpp>
#include <boost/range/algorithm/min_element.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include <boost/range/algorithm/count_if.hpp>
namespace osrm
{
@@ -36,8 +36,8 @@ struct IntersectionShapeData
inline auto makeCompareShapeDataByBearing(const double base_bearing)
{
return [base_bearing](const auto &lhs, const auto &rhs) {
return util::angleBetweenBearings(base_bearing, lhs.bearing) <
util::angleBetweenBearings(base_bearing, rhs.bearing);
return util::bearing::angleBetween(lhs.bearing, base_bearing) <
util::bearing::angleBetween(rhs.bearing, base_bearing);
};
}
@@ -48,6 +48,13 @@ inline auto makeCompareAngularDeviation(const double angle)
};
}
inline auto makeExtractLanesForRoad(const util::NodeBasedDynamicGraph &node_based_graph)
{
return [&node_based_graph](const auto &road) {
return node_based_graph.GetEdgeData(road.eid).road_classification.GetNumberOfLanes();
};
}
// When viewing an intersection from an incoming edge, we can transform a shape into a view which
// gives additional information on angles and whether a turn is allowed
struct IntersectionViewData : IntersectionShapeData
@@ -108,11 +115,60 @@ struct ConnectedRoad final : IntersectionViewData
};
// small helper function to print the content of a connected road
std::string toString(const IntersectionShapeData &shape);
std::string toString(const IntersectionViewData &view);
std::string toString(const ConnectedRoad &road);
// Intersections are sorted roads: [0] being the UTurn road, then from sharp right to sharp left.
// common operations shared amongst all intersection types
template <typename Self> struct EnableShapeOps
{
// same as closest turn, but for bearings
auto FindClosestBearing(double bearing) const
{
auto comp = makeCompareShapeDataByBearing(bearing);
return std::min_element(self()->begin(), self()->end(), comp);
}
using IntersectionShape = std::vector<IntersectionShapeData>;
// search a given eid in the intersection
auto FindEid(const EdgeID eid) const
{
return boost::range::find_if(
*self(), [eid](const auto &road) { return road.eid == eid; });
}
// find the maximum value based on a conversion operator
template <typename UnaryProjection> auto FindMaximum(UnaryProjection converter) const
{
BOOST_ASSERT(!self()->empty());
auto initial = converter(self()->front());
const auto extract_maximal_value = [&initial, converter](const auto &road) {
initial = std::max(initial, converter(road));
return false;
};
boost::range::find_if(*self(), extract_maximal_value);
return initial;
}
// find the maximum value based on a conversion operator and a predefined initial value
template <typename UnaryPredicate> auto Count(UnaryPredicate detector) const
{
BOOST_ASSERT(!self()->empty());
return boost::range::count_if(*self(), detector);
}
private:
auto self() { return static_cast<Self *>(this); }
auto self() const { return static_cast<const Self *>(this); }
};
struct IntersectionShape final : std::vector<IntersectionShapeData>, //
EnableShapeOps<IntersectionShape> //
{
using Base = std::vector<IntersectionShapeData>;
};
// Common operations shared among IntersectionView and Intersections.
// Inherit to enable those operations on your compatible type. CRTP pattern.
@@ -123,12 +179,13 @@ template <typename Self> struct EnableIntersectionOps
auto findClosestTurn(double angle) const
{
auto comp = makeCompareAngularDeviation(angle);
return std::min_element(self()->begin(), self()->end(), comp);
return boost::range::min_element(*self(), comp);
}
// Check validity of the intersection object. We assume a few basic properties every set of
// connected roads should follow throughout guidance pre-processing. This utility function
// allows checking intersections for validity
/* Check validity of the intersection object. We assume a few basic properties every set of
* connected roads should follow throughout guidance pre-processing. This utility function
* allows checking intersections for validity
*/
auto valid() const
{
if (self()->empty())
@@ -149,26 +206,6 @@ template <typename Self> struct EnableIntersectionOps
return true;
}
// Given all possible turns which is the highest connected number of lanes per turn.
// This value is used for example during generation of intersections.
auto getHighestConnectedLaneCount(const util::NodeBasedDynamicGraph &graph) const
{
const std::function<std::uint8_t(const ConnectedRoad &)> to_lane_count =
[&](const ConnectedRoad &road) {
return graph.GetEdgeData(road.eid).road_classification.GetNumberOfLanes();
};
std::uint8_t max_lanes = 0;
const auto extract_maximal_value = [&max_lanes](std::uint8_t value) {
max_lanes = std::max(max_lanes, value);
return false;
};
const auto view = *self() | boost::adaptors::transformed(to_lane_count);
boost::range::find_if(view, extract_maximal_value);
return max_lanes;
}
// Returns the UTurn road we took to arrive at this intersection.
const auto &getUTurnRoad() const { return self()->operator[](0); }
@@ -191,31 +228,52 @@ template <typename Self> struct EnableIntersectionOps
auto isDeadEnd() const
{
auto pred = [](const auto &road) { return road.entry_allowed; };
return !std::any_of(self()->begin() + 1, self()->end(), pred);
return std::none_of(self()->begin() + 1, self()->end(), pred);
}
// Returns the number of roads we can enter at this intersection, respectively.
auto countEnterable() const
{
auto pred = [](const auto &road) { return road.entry_allowed; };
return std::count_if(self()->begin(), self()->end(), pred);
return boost::range::count_if(*self(), pred);
}
// Returns the number of roads we can not enter at this intersection, respectively.
auto countNonEnterable() const { return self()->size() - self()->countEnterable(); }
// same as find closests turn but with an additional predicate to allow filtering
// the filter has to return `true` for elements that should be ignored
template <typename UnaryPredicate>
auto findClosestTurn(const double angle, const UnaryPredicate filter) const
{
BOOST_ASSERT(!self()->empty());
const auto candidate = boost::range::min_element(
*self(), [angle, &filter](const auto &lhs, const auto &rhs) {
const auto filtered_lhs = filter(lhs), filtered_rhs = filter(rhs);
const auto deviation_lhs = util::angularDeviation(lhs.angle, angle),
deviation_rhs = util::angularDeviation(rhs.angle, angle);
return std::tie(filtered_lhs, deviation_lhs) <
std::tie(filtered_rhs, deviation_rhs);
});
// make sure only to return valid elements
return filter(*candidate) ? self()->end() : candidate;
}
private:
auto self() { return static_cast<Self *>(this); }
auto self() const { return static_cast<const Self *>(this); }
};
struct IntersectionView final : std::vector<IntersectionViewData>, //
EnableShapeOps<IntersectionView>, //
EnableIntersectionOps<IntersectionView> //
{
using Base = std::vector<IntersectionViewData>;
};
struct Intersection final : std::vector<ConnectedRoad>, //
EnableShapeOps<Intersection>, //
EnableIntersectionOps<Intersection> //
{
using Base = std::vector<ConnectedRoad>;
@@ -4,6 +4,7 @@
#include "extractor/compressed_edge_container.hpp"
#include "extractor/guidance/coordinate_extractor.hpp"
#include "extractor/guidance/intersection.hpp"
#include "extractor/guidance/intersection_normalization_operation.hpp"
#include "extractor/query_node.hpp"
#include "extractor/restriction_map.hpp"
#include "util/attributes.hpp"
@@ -22,6 +23,13 @@ namespace extractor
{
namespace guidance
{
struct IntersectionGenerationParameters
{
NodeID nid;
EdgeID via_eid;
};
// The Intersection Generator is given a turn location and generates an intersection representation
// from it. For this all turn possibilities are analysed.
// We consider turn restrictions to indicate possible turns. U-turns are generated based on profile
@@ -63,8 +71,8 @@ class IntersectionGenerator
// more than a single next road. This function skips over degree two nodes to find coorect input
// for GetConnectedRoads.
OSRM_ATTR_WARN_UNUSED
std::pair<NodeID, EdgeID> SkipDegreeTwoNodes(const NodeID starting_node,
const EdgeID via_edge) const;
IntersectionGenerationParameters SkipDegreeTwoNodes(const NodeID starting_node,
const EdgeID via_edge) const;
// Allow access to the coordinate extractor for all owners
const CoordinateExtractor &GetCoordinateExtractor() const;
@@ -73,7 +81,7 @@ class IntersectionGenerator
// the node reached from `from_node` via `via_eid`. The resulting candidates have to be analysed
// for their actual instructions later on.
// The switch for `use_low_precision_angles` enables a faster mode that will procude less
// accurate coordinates. It should be good enough to check order of turns, find striaghtmost
// accurate coordinates. It should be good enough to check order of turns, find straightmost
// turns. Even good enough to do some simple angle verifications. It is mostly available to
// allow for faster graph traversal in the extraction phase.
OSRM_ATTR_WARN_UNUSED
@@ -98,7 +106,7 @@ class IntersectionGenerator
const EdgeID entering_via_edge,
const IntersectionShape &normalised_intersection,
const IntersectionShape &intersection,
const std::vector<std::pair<EdgeID, EdgeID>> &merging_map) const;
const std::vector<IntersectionNormalizationOperation> &merging_map) const;
private:
const util::NodeBasedDynamicGraph &node_based_graph;
@@ -508,15 +508,15 @@ std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge,
// even reverse the direction. Since we don't want to compute actual turns but simply
// try to find whether there is a turn going to the opposite direction of our obvious
// turn, this should be alright.
NodeID new_node;
const auto previous_intersection = [&]() {
EdgeID turn_edge;
std::tie(new_node, turn_edge) = intersection_generator.SkipDegreeTwoNodes(
const auto previous_intersection = [&]() -> IntersectionView {
const auto parameters = intersection_generator.SkipDegreeTwoNodes(
node_at_intersection, intersection[0].eid);
return intersection_generator.GetConnectedRoads(new_node, turn_edge);
if (node_based_graph.GetTarget(parameters.via_eid) == node_at_intersection)
return {};
return intersection_generator.GetConnectedRoads(parameters.nid, parameters.via_eid);
}();
if (new_node != node_at_intersection)
if (!previous_intersection.empty())
{
const auto continue_road = intersection[best_continue];
for (const auto &comparison_road : previous_intersection)
@@ -0,0 +1,25 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZATION_OPERATION_HPP_
#define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZATION_OPERATION_HPP_
#include "util/typedefs.hpp"
namespace osrm
{
namespace extractor
{
namespace guidance
{
struct IntersectionNormalizationOperation
{
// the source of the merge, not part of the intersection after the merge is performed.
EdgeID merged_eid;
// the edge that is covering the `merged_eid`
EdgeID into_eid;
};
} // namespace guidance
} // namespace extractor
} // namespace osrm
#endif /*OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZATION_OPERATION_HPP_*/
@@ -1,17 +1,17 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZER_HPP_
#define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_NORMALIZER_HPP_
#include "util/typedefs.hpp"
#include "util/attributes.hpp"
#include "util/name_table.hpp"
#include "util/typedefs.hpp"
#include "extractor/guidance/coordinate_extractor.hpp"
#include "extractor/guidance/intersection.hpp"
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/guidance/intersection_normalization_operation.hpp"
#include "extractor/guidance/mergable_road_detector.hpp"
#include "extractor/query_node.hpp"
#include "extractor/suffix_table.hpp"
#include "util/name_table.hpp"
#include <utility>
#include <vector>
@@ -37,6 +37,11 @@ namespace guidance
class IntersectionNormalizer
{
public:
struct NormalizationResult
{
IntersectionShape normalized_shape;
std::vector<IntersectionNormalizationOperation> performed_merges;
};
IntersectionNormalizer(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<extractor::QueryNode> &node_coordinates,
const util::NameTable &name_table,
@@ -46,16 +51,13 @@ class IntersectionNormalizer
// The function takes an intersection an converts it to a `perceived` intersection which closer
// represents how a human might experience the intersection
OSRM_ATTR_WARN_UNUSED
std::pair<IntersectionShape, std::vector<std::pair<EdgeID, EdgeID>>>
operator()(const NodeID node_at_intersection, IntersectionShape intersection) const;
NormalizationResult operator()(const NodeID node_at_intersection,
IntersectionShape intersection) const;
private:
const util::NodeBasedDynamicGraph &node_based_graph;
const std::vector<extractor::QueryNode> &node_coordinates;
const util::NameTable &name_table;
const SuffixTable &street_name_suffix_table;
const IntersectionGenerator &intersection_generator;
const MergableRoadDetector mergable_road_detector;
/* check if two indices in an intersection can be seen as a single road in the perceived
* intersection representation. See below for an example. Utility function for
@@ -73,12 +75,15 @@ class IntersectionNormalizer
std::size_t first_index,
std::size_t second_index) const;
// A tool called by CanMerge. It checks whether two indices can be merged, not concerned without
// remaining parts of the intersection.
bool InnerCanMerge(const NodeID intersection_node,
const IntersectionShape &intersection,
std::size_t first_index,
std::size_t second_index) const;
// Perform an Actual Merge
IntersectionNormalizationOperation
DetermineMergeDirection(const IntersectionShapeData &lhs,
const IntersectionShapeData &rhs) const;
IntersectionShapeData MergeRoads(const IntersectionShapeData &destination,
const IntersectionShapeData &source) const;
IntersectionShapeData MergeRoads(const IntersectionNormalizationOperation direction,
const IntersectionShapeData &lhs,
const IntersectionShapeData &rhs) const;
// Merge segregated roads to omit invalid turns in favor of treating segregated roads as
// one.
@@ -92,8 +97,8 @@ class IntersectionNormalizer
// The treatment results in a straight turn angle of 180º rather than a turn angle of approx
// 160
OSRM_ATTR_WARN_UNUSED
std::pair<IntersectionShape, std::vector<std::pair<EdgeID, EdgeID>>>
MergeSegregatedRoads(const NodeID intersection_node, IntersectionShape intersection) const;
NormalizationResult MergeSegregatedRoads(const NodeID intersection_node,
IntersectionShape intersection) const;
// The counterpiece to mergeSegregatedRoads. While we can adjust roads that split up at the
// intersection itself, it can also happen that intersections are connected to joining roads.
@@ -0,0 +1,155 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_MERGEABLE_ROADS
#define OSRM_EXTRACTOR_GUIDANCE_MERGEABLE_ROADS
#include "extractor/guidance/intersection.hpp"
#include "util/node_based_graph.hpp"
#include "util/typedefs.hpp"
#include <cstdint>
#include <functional>
#include <limits>
#include <vector>
namespace osrm
{
//FWD declarations
namespace util
{
class NameTable;
} // namespace util
namespace extractor
{
struct QueryNode;
class SuffixTable;
namespace guidance
{
class IntersectionGenerator;
class CoordinateExtractor;
class MergableRoadDetector
{
public:
// in case we have to change the mode we are operating on
using MergableRoadData = IntersectionShapeData;
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);
// OSM ways tend to be modelled as separate ways for different directions. This is often due to
// small gras strips in the middle between the two directions or due to pedestrian islands at
// intersections.
//
// To reduce unnecessary information due to these artificial intersections (which are not
// actually perceived as such) we try and merge these for our internal representation to both
// get better perceived turn angles and get a better reprsentation of our intersections in
// general.
//
// i h i,h
// | | |
// | | |
// b - - - v - - - g |
// > a < is transformed into: b,c - - - a - - - g,f
// c - - - ^ - - - f |
// | | |
// | | |
// d e d,e
bool CanMergeRoad(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const;
// check if a road cannot influence the merging of the other. This is necessary to prevent
// situations with more than two roads that could participate in a merge
bool IsDistinctFrom(const MergableRoadData &lhs, const MergableRoadData &rhs) const;
private:
// check if two name ids can be seen as identical (in presence of refs/others)
// in our case this translates into no name announcement in either direction (lhs->rhs and
// rhs->lhs)
bool HaveIdenticalNames(const NameID lhs, const NameID rhs) const;
// When it comes to merging roads, we need to find out if two ways actually represent the
// same road. This check tries to identify roads which are the same road in opposite directions
bool EdgeDataSupportsMerge(const util::NodeBasedEdgeData &lhs_edge_data,
const util::NodeBasedEdgeData &rhs_edge_data) const;
// Detect traffic loops.
// Since OSRM cannot handle loop edges, we cannot directly see a connection between a node and
// itself. We need to skip at least a single node in between.
bool IsTrafficLoop(const NodeID intersection_node, const MergableRoadData &road) const;
// Detector to check if we are looking at roads splitting up just prior to entering an
// intersection:
//
// c
// / |
// a -< |
// \ |
// b
//
// A common scheme in OSRM is that roads spit up in separate ways when approaching an
// intersection. This detector tries to detect these narrow triangles which usually just offer a
// small island for pedestrians in the middle.
bool IsNarrowTriangle(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const;
// Detector to check for whether two roads are following the same direction.
// If roads don't end up right at a connected intersection, we could look at a situation like
//
// __________________________
// /
// ---
// \__________________________
//
// This detector tries to find out about whether two roads are parallel after the separation
bool HaveSameDirection(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const;
// Detector for small traffic islands. If a road is splitting up, just to connect again later,
// we don't wan't to have this information within our list of intersections/possible turn
// locations.
//
// ___________
// ---<___________>-----
//
//
// Would feel just like a single straight road to a driver and should be represented as such in
// our engine
bool IsTrafficIsland(const NodeID intersection_node,
const MergableRoadData &lhs,
const MergableRoadData &rhs) const;
// A negative detector, preventing a merge, trying to detect link roads between two main roads.
//
// d - - - - - - - - e - f
// . / '
// a - - - b - - - - - - c
//
// The detector wants to prevent merges that are connected to `b-e`
bool IsLinkRoad(const NodeID intersection_node, const MergableRoadData &road) const;
const util::NodeBasedDynamicGraph &node_based_graph;
const std::vector<QueryNode> &node_coordinates;
const IntersectionGenerator &intersection_generator;
const CoordinateExtractor &coordinate_extractor;
// name detection
const util::NameTable &name_table;
const SuffixTable &street_name_suffix_table;
};
} // namespace guidance
} // namespace extractor
} // namespace osrm
#endif
@@ -57,7 +57,6 @@ struct LengthLimitedCoordinateAccumulator
{
LengthLimitedCoordinateAccumulator(
const extractor::guidance::CoordinateExtractor &coordinate_extractor,
const util::NodeBasedDynamicGraph &node_based_graph,
const double max_length);
/*
@@ -78,11 +77,12 @@ struct LengthLimitedCoordinateAccumulator
*/
void update(const NodeID from_node, const EdgeID via_edge, const NodeID to_node);
const extractor::guidance::CoordinateExtractor &coordinate_extractor;
const util::NodeBasedDynamicGraph &node_based_graph;
const double max_length;
double accumulated_length;
double accumulated_length = 0;
std::vector<util::Coordinate> coordinates;
private:
const extractor::guidance::CoordinateExtractor &coordinate_extractor;
const double max_length;
};
/*
@@ -105,13 +105,40 @@ struct SelectRoadByNameOnlyChoiceAndStraightness
*/
boost::optional<EdgeID> operator()(const NodeID nid,
const EdgeID via_edge_id,
const Intersection &intersection,
const IntersectionView &intersection,
const util::NodeBasedDynamicGraph &node_based_graph) const;
private:
const NameID desired_name_id;
const bool requires_entry;
};
/* Following only a straight road
* Follow only the straightmost turn, as long as its the only choice or has the desired name
*/
struct SelectStraightmostRoadByNameAndOnlyChoice
{
SelectStraightmostRoadByNameAndOnlyChoice(const NameID desired_name_id,
const double initial_bearing,
const bool requires_entry);
/*
* !! REQUIRED - Function for the use of TraverseRoad in the graph walker.
* The operator() needs to return (if any is found) the next road to continue in the graph
* traversal. If no such edge is found, return {} is allowed. Usually you want to choose some
* form of obious turn to follow.
*/
boost::optional<EdgeID> operator()(const NodeID nid,
const EdgeID via_edge_id,
const IntersectionView &intersection,
const util::NodeBasedDynamicGraph &node_based_graph) const;
private:
const NameID desired_name_id;
const double initial_bearing;
const bool requires_entry;
};
// find the next intersection given a hop limit
struct IntersectionFinderAccumulator
{
@@ -166,8 +193,9 @@ NodeBasedGraphWalker::TraverseRoad(NodeID current_node_id,
return {};
// look at the next intersection
const auto next_intersection =
intersection_generator.GetConnectedRoads(current_node_id, current_edge_id);
const constexpr auto LOW_PRECISION = true;
const auto next_intersection = intersection_generator.GetConnectedRoads(
current_node_id, current_edge_id, LOW_PRECISION);
// don't follow u-turns or go past our initial intersection
if (next_intersection.size() <= 1)
@@ -235,7 +263,7 @@ struct DistanceToNextIntersectionAccumulator
using namespace util::coordinate_calculation;
const auto coords = extractor.GetForwardCoordinatesAlongRoad(start, onto);
distance += getLength(coords, &haversineDistance);
distance += getLength(coords.begin(), coords.end(), &haversineDistance);
}
const extractor::guidance::CoordinateExtractor &extractor;
+4 -5
View File
@@ -4,6 +4,7 @@
#include "extractor/compressed_edge_container.hpp"
#include "extractor/guidance/intersection.hpp"
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/guidance/intersection_normalization_operation.hpp"
#include "extractor/guidance/intersection_normalizer.hpp"
#include "extractor/guidance/motorway_handler.hpp"
#include "extractor/guidance/roundabout_handler.hpp"
@@ -62,11 +63,9 @@ class TurnAnalysis
{
// the basic shape, containing all turns
IntersectionShape intersection_shape;
// normalised shape, merged some roads into others, adjusted bearings
// see intersection_normaliser for further explanations
IntersectionShape normalised_intersection_shape;
// map containing information about which road was merged into which
std::vector<std::pair<EdgeID, EdgeID>> merging_map;
// normalized shape, merged some roads into others, adjusted bearings
// see intersection_normalizer for further explanations
IntersectionNormalizer::NormalizationResult annotated_normalized_shape;
};
OSRM_ATTR_WARN_UNUSED
ShapeResult ComputeIntersectionShapes(const NodeID node_at_center_of_intersection) const;
+32 -6
View File
@@ -91,9 +91,8 @@ inline bool CheckInBounds(const int A, const int B, const int range)
return normalized_B - range <= normalized_A && normalized_A <= normalized_B + range;
}
}
} // namespace bearing
inline double reverseBearing(const double bearing)
inline double reverse(const double bearing)
{
if (bearing >= 180)
return bearing - 180.;
@@ -119,8 +118,9 @@ inline double reverseBearing(const double bearing)
// % 360;
// All other cases are handled by first rotating both bearings to an
// entry_bearing of 0.
inline double angleBetweenBearings(const double entry_bearing, const double exit_bearing)
inline double angleBetween(const double entry_bearing, const double exit_bearing)
{
// transform bearing from cw into ccw order
const double offset = 360 - entry_bearing;
const double rotated_exit = [](double bearing, const double offset) {
bearing += offset;
@@ -131,13 +131,39 @@ inline double angleBetweenBearings(const double entry_bearing, const double exit
return angle >= 360 ? angle - 360 : angle;
}
// minimal difference between two angles/bearings going left or right
inline double angularDeviation(const double angle, const double from)
} // namespace bearing
// compute the minimum distance in degree between two angles/bearings
inline double angularDeviation(const double angle_or_bearing, const double from)
{
const double deviation = std::abs(angle - from);
const double deviation = std::abs(angle_or_bearing - from);
return std::min(360 - deviation, deviation);
}
/* Angles in OSRM are expressed in the range of [0,360). During calculations, we might violate
* this range via offsets. This function helps to ensure the range is kept. */
inline double restrictAngleToValidRange(const double angle)
{
if (angle < 0)
return restrictAngleToValidRange(angle + 360.);
else if (angle > 360)
return restrictAngleToValidRange(angle - 360.);
else
return angle;
}
// finds the angle between two angles, based on the minum difference between the two
inline double angleBetween(const double lhs, const double rhs)
{
const auto difference = std::abs(lhs - rhs);
const auto is_clockwise_difference = difference <= 180;
const auto angle_between_candidate = .5 * (lhs + rhs);
if (is_clockwise_difference)
return angle_between_candidate;
else
return restrictAngleToValidRange(angle_between_candidate + 180);
}
} // namespace util
} // namespace osrm
+289 -50
View File
@@ -3,9 +3,12 @@
#include "util/coordinate.hpp"
#include <boost/math/constants/constants.hpp>
#include <boost/optional.hpp>
#include <algorithm>
#include <cmath>
#include <numeric>
#include <utility>
#include <vector>
@@ -23,6 +26,18 @@ const constexpr long double RAD_TO_DEGREE = 1. / DEGREE_TO_RAD;
// earth radius varies between 6,356.750-6,378.135 km (3,949.901-3,963.189mi)
// The IUGG value for the equatorial radius is 6378.137 km (3963.19 miles)
const constexpr long double EARTH_RADIUS = 6372797.560856;
inline double degToRad(const double degree)
{
using namespace boost::math::constants;
return degree * (pi<double>() / 180.0);
}
inline double radToDeg(const double radian)
{
using namespace boost::math::constants;
return radian * (180.0 * (1. / pi<double>()));
}
}
//! Takes the squared euclidean distance of the input coordinates. Does not return meters!
@@ -33,22 +48,8 @@ double haversineDistance(const Coordinate first_coordinate, const Coordinate sec
double greatCircleDistance(const Coordinate first_coordinate, const Coordinate second_coordinate);
// get the length of a full coordinate vector, using one of our basic functions to compute distances
template <class BinaryOperation>
double getLength(const std::vector<Coordinate> &coordinates, BinaryOperation op)
{
if (coordinates.empty())
return 0.;
double result = 0;
const auto functor = [&result, op](const Coordinate lhs, const Coordinate rhs) {
result += op(lhs, rhs);
return false;
};
// side-effect find adding up distances
std::adjacent_find(coordinates.begin(), coordinates.end(), functor);
return result;
}
template <class BinaryOperation, typename iterator_type>
double getLength(iterator_type begin, const iterator_type end, BinaryOperation op);
// Find the closest distance and location between coordinate and the line connecting source and
// target:
@@ -59,41 +60,35 @@ double getLength(const std::vector<Coordinate> &coordinates, BinaryOperation op)
// returns x as well as the distance between source and x as ratio ([0,1])
inline std::pair<double, FloatCoordinate> projectPointOnSegment(const FloatCoordinate &source,
const FloatCoordinate &target,
const FloatCoordinate &coordinate)
{
const FloatCoordinate slope_vector{target.lon - source.lon, target.lat - source.lat};
const FloatCoordinate rel_coordinate{coordinate.lon - source.lon, coordinate.lat - source.lat};
// dot product of two un-normed vectors
const auto unnormed_ratio = static_cast<double>(slope_vector.lon * rel_coordinate.lon) +
static_cast<double>(slope_vector.lat * rel_coordinate.lat);
// squared length of the slope vector
const auto squared_length = static_cast<double>(slope_vector.lon * slope_vector.lon) +
static_cast<double>(slope_vector.lat * slope_vector.lat);
const FloatCoordinate &coordinate);
if (squared_length < std::numeric_limits<double>::epsilon())
{
return {0, source};
}
// find the closest distance between a coordinate and a segment
// O(1)
double findClosestDistance(const Coordinate coordinate,
const Coordinate segment_begin,
const Coordinate segment_end);
const double normed_ratio = unnormed_ratio / squared_length;
double clamped_ratio = normed_ratio;
if (clamped_ratio > 1.)
{
clamped_ratio = 1.;
}
else if (clamped_ratio < 0.)
{
clamped_ratio = 0.;
}
// find the closest distance between a coordinate and a set of coordinates
// O(|coordinates|)
template <typename iterator_type>
double findClosestDistance(const Coordinate coordinate,
const iterator_type begin,
const iterator_type end);
return {clamped_ratio,
{
FloatLongitude{1.0 - clamped_ratio} * source.lon +
target.lon * FloatLongitude{clamped_ratio},
FloatLatitude{1.0 - clamped_ratio} * source.lat +
target.lat * FloatLatitude{clamped_ratio},
}};
}
// find the closes distance between two sets of coordinates
// O(|lhs| * |rhs|)
template <typename iterator_type>
double findClosestDistance(const iterator_type lhs_begin,
const iterator_type lhs_end,
const iterator_type rhs_begin,
const iterator_type rhs_end);
// checks if two sets of coordinates describe a parallel set of ways
template <typename iterator_type>
bool areParallel(const iterator_type lhs_begin,
const iterator_type lhs_end,
const iterator_type rhs_begin,
const iterator_type rhs_end);
double perpendicularDistance(const Coordinate segment_source,
const Coordinate segment_target,
@@ -136,8 +131,252 @@ bool isCCW(const Coordinate first_coordinate,
const Coordinate second_coordinate,
const Coordinate third_coordinate);
std::pair<util::Coordinate, util::Coordinate>
leastSquareRegression(const std::vector<util::Coordinate> &coordinates);
template <typename iterator_type>
std::pair<Coordinate, Coordinate> leastSquareRegression(const iterator_type begin,
const iterator_type end);
// rotates a coordinate around the point (0,0). This function can be used to normalise a few
// computations around regression vectors
Coordinate rotateCCWAroundZero(Coordinate coordinate, double angle_in_radians);
// compute the difference vector of two coordinates lhs - rhs
Coordinate difference(const Coordinate lhs, const Coordinate rhs);
// TEMPLATE/INLINE DEFINITIONS
inline std::pair<double, FloatCoordinate> projectPointOnSegment(const FloatCoordinate &source,
const FloatCoordinate &target,
const FloatCoordinate &coordinate)
{
const FloatCoordinate slope_vector{target.lon - source.lon, target.lat - source.lat};
const FloatCoordinate rel_coordinate{coordinate.lon - source.lon, coordinate.lat - source.lat};
// dot product of two un-normed vectors
const auto unnormed_ratio = static_cast<double>(slope_vector.lon * rel_coordinate.lon) +
static_cast<double>(slope_vector.lat * rel_coordinate.lat);
// squared length of the slope vector
const auto squared_length = static_cast<double>(slope_vector.lon * slope_vector.lon) +
static_cast<double>(slope_vector.lat * slope_vector.lat);
if (squared_length < std::numeric_limits<double>::epsilon())
{
return {0, source};
}
const double normed_ratio = unnormed_ratio / squared_length;
double clamped_ratio = normed_ratio;
if (clamped_ratio > 1.)
{
clamped_ratio = 1.;
}
else if (clamped_ratio < 0.)
{
clamped_ratio = 0.;
}
return {clamped_ratio,
{
FloatLongitude{1.0 - clamped_ratio} * source.lon +
target.lon * FloatLongitude{clamped_ratio},
FloatLatitude{1.0 - clamped_ratio} * source.lat +
target.lat * FloatLatitude{clamped_ratio},
}};
}
template <class BinaryOperation, typename iterator_type>
double getLength(iterator_type begin, const iterator_type end, BinaryOperation op)
{
double result = 0;
const auto functor = [&result, op](const Coordinate lhs, const Coordinate rhs) {
result += op(lhs, rhs);
return false;
};
// side-effect find adding up distances
std::adjacent_find(begin, end, functor);
return result;
}
template <typename iterator_type>
double
findClosestDistance(const Coordinate coordinate, const iterator_type begin, const iterator_type end)
{
double current_min = std::numeric_limits<double>::max();
// comparator updating current_min without ever finding an element
const auto compute_minimum_distance = [&current_min, coordinate](const Coordinate lhs,
const Coordinate rhs) {
current_min = std::min(current_min, findClosestDistance(coordinate, lhs, rhs));
return false;
};
std::adjacent_find(begin, end, compute_minimum_distance);
return current_min;
}
template <typename iterator_type>
double findClosestDistance(const iterator_type lhs_begin,
const iterator_type lhs_end,
const iterator_type rhs_begin,
const iterator_type rhs_end)
{
double current_min = std::numeric_limits<double>::max();
const auto compute_minimum_distance_in_rhs = [&current_min, rhs_begin, rhs_end](
const Coordinate coordinate) {
current_min = std::min(current_min, findClosestDistance(coordinate, rhs_begin, rhs_end));
return false;
};
std::find_if(lhs_begin, lhs_end, compute_minimum_distance_in_rhs);
return current_min;
}
template <typename iterator_type>
std::pair<Coordinate, Coordinate> leastSquareRegression(const iterator_type begin,
const iterator_type end)
{
// following the formulas of https://faculty.elgin.edu/dkernler/statistics/ch04/4-2.html
const auto number_of_coordinates = std::distance(begin, end);
BOOST_ASSERT(number_of_coordinates >= 2);
const auto extract_lon = [](const Coordinate coordinate) {
return static_cast<double>(toFloating(coordinate.lon));
};
const auto extract_lat = [](const Coordinate coordinate) {
return static_cast<double>(toFloating(coordinate.lat));
};
double min_lon = extract_lon(*begin);
double max_lon = extract_lon(*begin);
double min_lat = extract_lat(*begin);
double max_lat = extract_lat(*begin);
for (auto coordinate_iterator = begin; coordinate_iterator != end; ++coordinate_iterator)
{
const auto c = *coordinate_iterator;
const auto lon = extract_lon(c);
min_lon = std::min(min_lon, lon);
max_lon = std::max(max_lon, lon);
const auto lat = extract_lat(c);
min_lat = std::min(min_lat, lat);
max_lat = std::max(max_lat, lat);
}
// very small difference in longitude -> would result in inaccurate calculation, check if lat is
// better
if ((max_lat - min_lat) > 2 * (max_lon - min_lon))
{
std::vector<util::Coordinate> rotated_coordinates(number_of_coordinates);
// rotate all coordinates to the right
std::transform(begin, end, rotated_coordinates.begin(), [](const auto coordinate) {
return rotateCCWAroundZero(coordinate, detail::degToRad(-90));
});
const auto rotated_regression =
leastSquareRegression(rotated_coordinates.begin(), rotated_coordinates.end());
return {rotateCCWAroundZero(rotated_regression.first, detail::degToRad(90)),
rotateCCWAroundZero(rotated_regression.second, detail::degToRad(90))};
}
const auto make_accumulate = [](const auto extraction_function) {
return [extraction_function](const double sum_so_far, const Coordinate coordinate) {
return sum_so_far + extraction_function(coordinate);
};
};
const auto accumulated_lon = std::accumulate(begin, end, 0., make_accumulate(extract_lon));
const auto accumulated_lat = std::accumulate(begin, end, 0., make_accumulate(extract_lat));
const auto mean_lon = accumulated_lon / number_of_coordinates;
const auto mean_lat = accumulated_lat / number_of_coordinates;
const auto make_variance = [](const auto mean, const auto extraction_function) {
return [extraction_function, mean](const double sum_so_far, const Coordinate coordinate) {
const auto difference = extraction_function(coordinate) - mean;
return sum_so_far + difference * difference;
};
};
// using the unbiased version, we divide by num_samples - 1 (see
// http://mathworld.wolfram.com/SampleVariance.html)
const auto sample_variance_lon =
std::sqrt(std::accumulate(begin, end, 0., make_variance(mean_lon, extract_lon)) /
(number_of_coordinates - 1));
// if we don't change longitude, return the vertical line as is
if (std::abs(sample_variance_lon) <
std::numeric_limits<decltype(sample_variance_lon)>::epsilon())
return {*begin, *(end - 1)};
const auto sample_variance_lat =
std::sqrt(std::accumulate(begin, end, 0., make_variance(mean_lat, extract_lat)) /
(number_of_coordinates - 1));
if (std::abs(sample_variance_lat) <
std::numeric_limits<decltype(sample_variance_lat)>::epsilon())
return {*begin, *(end - 1)};
const auto linear_correlation =
std::accumulate(begin,
end,
0.,
[&](const auto sum_so_far, const auto current_coordinate) {
return sum_so_far +
(extract_lon(current_coordinate) - mean_lon) *
(extract_lat(current_coordinate) - mean_lat) /
(sample_variance_lon * sample_variance_lat);
}) /
(number_of_coordinates - 1);
const auto slope = linear_correlation * sample_variance_lat / sample_variance_lon;
const auto intercept = mean_lat - slope * mean_lon;
const auto GetLatAtLon = [intercept,
slope](const util::FloatLongitude longitude) -> util::FloatLatitude {
return {intercept + slope * static_cast<double>((longitude))};
};
const double offset = 0.00001;
const Coordinate regression_first = {
toFixed(util::FloatLongitude{min_lon - offset}),
toFixed(util::FloatLatitude(GetLatAtLon(util::FloatLongitude{min_lon - offset})))};
const Coordinate regression_end = {
toFixed(util::FloatLongitude{max_lon + offset}),
toFixed(util::FloatLatitude(GetLatAtLon(util::FloatLongitude{max_lon + offset})))};
return {regression_first, regression_end};
}
template <typename iterator_type>
bool areParallel(const iterator_type lhs_begin,
const iterator_type lhs_end,
const iterator_type rhs_begin,
const iterator_type rhs_end)
{
const auto regression_lhs = leastSquareRegression(lhs_begin, lhs_end);
const auto regression_rhs = leastSquareRegression(rhs_begin, rhs_end);
const auto null_island = Coordinate(FixedLongitude{0}, FixedLatitude{0});
const auto difference_lhs = difference(regression_lhs.first, regression_lhs.second);
const auto difference_rhs = difference(regression_rhs.first, regression_rhs.second);
// we normalise the left slope to be zero, so we rotate the coordinates around 0,0 to match 90
// degrees
const auto bearing_lhs = bearing(null_island, difference_lhs);
// we rotate to have one of the lines facing horizontally to the right (bearing 90 degree)
const auto rotation_angle_radians = detail::degToRad(bearing_lhs - 90);
const auto rotated_difference_rhs = rotateCCWAroundZero(difference_rhs, rotation_angle_radians);
const auto get_slope = [](const Coordinate from, const Coordinate to) {
const auto diff_lat = static_cast<int>(from.lat) - static_cast<int>(to.lat);
const auto diff_lon = static_cast<int>(from.lon) - static_cast<int>(to.lon);
if (diff_lon == 0)
return std::numeric_limits<double>::max();
return static_cast<double>(diff_lat) / static_cast<double>(diff_lon);
};
const auto slope_rhs = get_slope(null_island, rotated_difference_rhs);
// the left hand side has a slope of `0` after the rotation. We can check the slope of the right
// hand side to ensure we only considering slight slopes
return std::abs(slope_rhs) < 0.20; // twenty percent incline at the most
}
} // ns coordinate_calculation
} // ns util
+19 -14
View File
@@ -22,7 +22,6 @@ namespace util
{
namespace guidance
{
// Name Change Logic
// Used both during Extraction as well as during Post-Processing
@@ -157,25 +156,31 @@ inline bool requiresNameAnnounced(const NameID from_name_id,
const util::NameTable &name_table,
const extractor::SuffixTable &suffix_table)
{
return requiresNameAnnounced(name_table.GetNameForID(from_name_id),
name_table.GetRefForID(from_name_id),
name_table.GetPronunciationForID(from_name_id),
name_table.GetNameForID(to_name_id),
name_table.GetRefForID(to_name_id),
name_table.GetPronunciationForID(to_name_id),
suffix_table);
if (from_name_id == to_name_id)
return false;
else
return requiresNameAnnounced(name_table.GetNameForID(from_name_id),
name_table.GetRefForID(from_name_id),
name_table.GetPronunciationForID(from_name_id),
name_table.GetNameForID(to_name_id),
name_table.GetRefForID(to_name_id),
name_table.GetPronunciationForID(to_name_id),
suffix_table);
}
inline bool requiresNameAnnounced(const NameID from_name_id,
const NameID to_name_id,
const util::NameTable &name_table)
{
return requiresNameAnnounced(name_table.GetNameForID(from_name_id),
name_table.GetRefForID(from_name_id),
name_table.GetPronunciationForID(from_name_id),
name_table.GetNameForID(to_name_id),
name_table.GetRefForID(to_name_id),
name_table.GetPronunciationForID(to_name_id));
if (from_name_id == to_name_id)
return false;
else
return requiresNameAnnounced(name_table.GetNameForID(from_name_id),
name_table.GetRefForID(from_name_id),
name_table.GetPronunciationForID(from_name_id),
name_table.GetNameForID(to_name_id),
name_table.GetRefForID(to_name_id),
name_table.GetPronunciationForID(to_name_id));
}
} // namespace guidance