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:
@@ -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,
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 = [¤t_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 = [¤t_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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user