refactor of turn analysis into turn handlers

This commit is contained in:
Moritz Kobitzsch 2016-04-08 12:49:14 +02:00 committed by Patrick Niklaus
parent dfd180a292
commit fcbf527ba5
14 changed files with 2985 additions and 2612 deletions

View File

@ -0,0 +1,66 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HPP_
#define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HPP_
#include <string>
#include <vector>
#include "extractor/guidance/turn_instruction.hpp"
#include "util/typedefs.hpp" // EdgeID
namespace osrm
{
namespace extractor
{
namespace guidance
{
// Every Turn Operation describes a way of switching onto a segment, indicated by an EdgeID. The
// associated turn is described by an angle and an instruction that is used to announce it.
// The Turn Operation indicates what is exposed to the outside of the turn analysis.
struct TurnOperation final
{
EdgeID eid;
double angle;
TurnInstruction instruction;
};
// A Connected Road is the internal representation of a potential turn. Internally, we require
// full list of all connected roads to determine the outcome.
// The reasoning behind is that even invalid turns can influence the perceived angles, or even
// instructions themselves. An pososible example can be described like this:
//
// aaa(2)aa
// a - bbbbb
// aaa(1)aa
//
// will not be perceived as a turn from (1) -> b, and as a U-turn from (1) -> (2).
// In addition, they can influence whether a turn is obvious or not. b->(2) would also be no
// turn-operation,
// but rather a name change.
//
// If this were a normal intersection with
//
// cccccccc
// o bbbbb
// aaaaaaaa
//
// We would perceive a->c as a sharp turn, a->b as a slight turn, and b->c as a slight turn.
struct ConnectedRoad final
{
ConnectedRoad(const TurnOperation turn, const bool entry_allowed = false);
// a turn may be relevant to good instructions, even if we cannot enter the road
bool entry_allowed;
TurnOperation turn;
};
// small helper function to print the content of a connected road
std::string toString(const ConnectedRoad &road);
typedef std::vector<ConnectedRoad> Intersection;
} // namespace guidance
} // namespace extractor
} // namespace osrm
#endif /*OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HPP_*/

View File

@ -0,0 +1,68 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_GENERATOR_HPP_
#define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_GENERATOR_HPP_
#include "extractor/compressed_edge_container.hpp"
#include "extractor/guidance/intersection.hpp"
#include "extractor/query_node.hpp"
#include "extractor/restriction_map.hpp"
#include "util/node_based_graph.hpp"
#include "util/typedefs.hpp"
#include <unordered_set>
#include <vector>
namespace osrm
{
namespace extractor
{
namespace guidance
{
// 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
// decisions.
class IntersectionGenerator
{
public:
IntersectionGenerator(const util::NodeBasedDynamicGraph &node_based_graph,
const RestrictionMap &restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const std::vector<QueryNode> &node_info_list,
const CompressedEdgeContainer &compressed_edge_container);
Intersection operator()(const NodeID nid, const EdgeID via_eid) const;
private:
const util::NodeBasedDynamicGraph &node_based_graph;
const RestrictionMap &restriction_map;
const std::unordered_set<NodeID> &barrier_nodes;
const std::vector<QueryNode> &node_info_list;
const CompressedEdgeContainer &compressed_edge_container;
// Check for restrictions/barriers and generate a list of valid and invalid turns present at
// the
// node reached
// from `from_node` via `via_eid`
// The resulting candidates have to be analysed for their actual instructions later on.
Intersection getConnectedRoads(const NodeID from_node, const EdgeID via_eid) const;
// Merge segregated roads to omit invalid turns in favor of treating segregated roads as
// one.
// This function combines roads the following way:
//
// * *
// * is converted to *
// v ^ +
// v ^ +
//
// The treatment results in a straight turn angle of 180º rather than a turn angle of approx
// 160
Intersection mergeSegregatedRoads(Intersection intersection) const;
};
} // namespace guidance
} // namespace extractor
} // namespace osrm
#endif /* OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_GENERATOR_HPP_ */

View File

@ -0,0 +1,71 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HANDLER_HPP_
#define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HANDLER_HPP_
#include "extractor/guidance/intersection.hpp"
#include "extractor/query_node.hpp"
#include "util/name_table.hpp"
#include "util/node_based_graph.hpp"
#include <cstddef>
#include <vector>
namespace osrm
{
namespace extractor
{
namespace guidance
{
// Intersection handlers deal with all issues related to intersections.
// They assign appropriate turn operations to the TurnOperations.
// This base class provides both the interface and implementations for
// common functions.
class IntersectionHandler
{
public:
IntersectionHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_info_list,
const util::NameTable &name_table);
virtual ~IntersectionHandler();
// check whether the handler can actually handle the intersection
virtual bool
canProcess(const NodeID nid, const EdgeID via_eid, const Intersection &intersection) const = 0;
// process the intersection
virtual Intersection
operator()(const NodeID nid, const EdgeID via_eid, Intersection intersection) const = 0;
protected:
const util::NodeBasedDynamicGraph &node_based_graph;
const std::vector<QueryNode> &node_info_list;
const util::NameTable &name_table;
// counts the number on allowed entry roads
std::size_t countValid(const Intersection &intersection) const;
// Decide on a basic turn types
TurnType findBasicTurnType(const EdgeID via_edge, const ConnectedRoad &candidate) const;
// Get the Instruction for an obvious turn
TurnInstruction getInstructionForObvious(const std::size_t number_of_candidates,
const EdgeID via_edge,
const bool through_street,
const ConnectedRoad &candidate) const;
// Treating potential forks
void assignFork(const EdgeID via_edge, ConnectedRoad &left, ConnectedRoad &right) const;
void assignFork(const EdgeID via_edge,
ConnectedRoad &left,
ConnectedRoad &center,
ConnectedRoad &right) const;
bool isThroughStreet(const std::size_t index, const Intersection &intersection) const;
};
} // namespace guidance
} // namespace extractor
} // namespace osrm
#endif /*OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HANDLER_HPP_*/

View File

@ -0,0 +1,51 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_MOTORWAY_HANDLER_HPP_
#define OSRM_EXTRACTOR_GUIDANCE_MOTORWAY_HANDLER_HPP_
#include "extractor/guidance/intersection_handler.hpp"
#include "extractor/guidance/intersection.hpp"
#include "extractor/query_node.hpp"
#include "util/name_table.hpp"
#include "util/node_based_graph.hpp"
#include <vector>
namespace osrm
{
namespace extractor
{
namespace guidance
{
// Intersection handlers deal with all issues related to intersections.
// They assign appropriate turn operations to the TurnOperations.
class MotorwayHandler : public IntersectionHandler
{
public:
MotorwayHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_info_list,
const util::NameTable &name_table);
~MotorwayHandler() override final;
// check whether the handler can actually handle the intersection
bool canProcess(const NodeID nid,
const EdgeID via_eid,
const Intersection &intersection) const override final;
// process the intersection
Intersection operator()(const NodeID nid,
const EdgeID via_eid,
Intersection intersection) const override final;
private:
Intersection fromMotorway(const EdgeID via_edge, Intersection intersection) const;
Intersection fromRamp(const EdgeID via_edge, Intersection intersection) const;
Intersection fallback(Intersection intersection) const;
};
} // namespace guidance
} // namespace extractor
} // namespace osrm
#endif /*OSRM_EXTRACTOR_GUIDANCE_MOTORWAY_HANDLER_HPP_*/

View File

@ -0,0 +1,71 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_ROUNDABOUT_HANDLER_HPP_
#define OSRM_EXTRACTOR_GUIDANCE_ROUNDABOUT_HANDLER_HPP_
#include "extractor/guidance/intersection.hpp"
#include "extractor/guidance/intersection_handler.hpp"
#include "extractor/query_node.hpp"
#include "util/name_table.hpp"
#include "util/node_based_graph.hpp"
#include "util/typedefs.hpp"
#include <utility>
#include <vector>
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace detail
{
struct RoundaboutFlags
{
bool on_roundabout;
bool can_enter;
bool can_exit_separately;
};
} // namespace detail
// The roundabout handler processes all roundabout related instructions.
// It performs both the distinction between rotaries and roundabouts and
// assigns appropriate entry/exit instructions.
class RoundaboutHandler : public IntersectionHandler
{
public:
RoundaboutHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_info_list,
const util::NameTable &name_table);
~RoundaboutHandler() override final;
// check whether the handler can actually handle the intersection
bool canProcess(const NodeID from_nid, const EdgeID via_eid, const Intersection &intersection) const override final;
// process the intersection
Intersection operator()(const NodeID from_nid, const EdgeID via_eid, Intersection intersection) const override final;
private:
detail::RoundaboutFlags getRoundaboutFlags(const NodeID from_nid, const EdgeID via_eid, const Intersection &intersection) const;
// decide whether we lookk at a roundabout or a rotary
bool isRotary(const NodeID nid) const;
// TODO handle bike/walk cases that allow crossing a roundabout!
// Processing of roundabouts
// Produces instructions to enter/exit a roundabout or to stay on it.
// Performs the distinction between roundabout and rotaries.
Intersection handleRoundabouts(const bool is_rotary,
const EdgeID via_edge,
const bool on_roundabout,
const bool can_exit_roundabout,
Intersection intersection) const;
};
} // namespace guidance
} // namespace extractor
} // namespace osrm
#endif /*OSRM_EXTRACTOR_GUIDANCE_ROUNDABOUT_HANDLER_HPP_*/

View File

@ -2,11 +2,18 @@
#define OSRM_EXTRACTOR_TURN_ANALYSIS
#include "extractor/compressed_edge_container.hpp"
#include "extractor/guidance/intersection.hpp"
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/guidance/toolkit.hpp"
#include "extractor/guidance/turn_classification.hpp"
#include "extractor/guidance/roundabout_handler.hpp"
#include "extractor/guidance/motorway_handler.hpp"
#include "extractor/guidance/turn_handler.hpp"
#include "extractor/query_node.hpp"
#include "extractor/restriction_map.hpp"
#include "util/name_table.hpp"
#include "util/node_based_graph.hpp"
#include <cstdint>
@ -23,46 +30,6 @@ namespace extractor
namespace guidance
{
// What is exposed to the outside
struct TurnOperation final
{
EdgeID eid;
double angle;
TurnInstruction instruction;
};
// For the turn analysis, we require a full list of all connected roads to determine the outcome.
// Invalid turns can influence the perceived angles
//
// aaa(2)aa
// a - bbbbb
// aaa(1)aa
//
// will not be perceived as a turn from (1) -> b, and as a U-turn from (1) -> (2).
// In addition, they can influence whether a turn is obvious or not.
struct ConnectedRoad final
{
ConnectedRoad(const TurnOperation turn, const bool entry_allowed = false);
TurnOperation turn;
bool entry_allowed; // a turn may be relevant to good instructions, even if we cannot take
// the road
std::string toString() const
{
std::string result = "[connection] ";
result += std::to_string(turn.eid);
result += " allows entry: ";
result += std::to_string(entry_allowed);
result += " angle: ";
result += std::to_string(turn.angle);
result += " instruction: ";
result += std::to_string(static_cast<std::int32_t>(turn.instruction.type)) + " " +
std::to_string(static_cast<std::int32_t>(turn.instruction.direction_modifier));
return result;
}
};
class TurnAnalysis
{
@ -79,123 +46,14 @@ class TurnAnalysis
private:
const util::NodeBasedDynamicGraph &node_based_graph;
const std::vector<QueryNode> &node_info_list;
const RestrictionMap &restriction_map;
const std::unordered_set<NodeID> &barrier_nodes;
const CompressedEdgeContainer &compressed_edge_container;
const util::NameTable &name_table;
// Check for restrictions/barriers and generate a list of valid and invalid turns present at
// the
// node reached
// from `from_node` via `via_eid`
// The resulting candidates have to be analysed for their actual instructions later on.
std::vector<ConnectedRoad> getConnectedRoads(const NodeID from_node,
const EdgeID via_eid) const;
// Merge segregated roads to omit invalid turns in favor of treating segregated roads as
// one.
// This function combines roads the following way:
//
// * *
// * is converted to *
// v ^ +
// v ^ +
//
// The treatment results in a straight turn angle of 180º rather than a turn angle of approx
// 160
std::vector<ConnectedRoad> mergeSegregatedRoads(std::vector<ConnectedRoad> intersection) const;
// TODO distinguish roundabouts and rotaries
// TODO handle bike/walk cases that allow crossing a roundabout!
// Processing of roundabouts
// Produces instructions to enter/exit a roundabout or to stay on it.
// Performs the distinction between roundabout and rotaries.
std::vector<ConnectedRoad> handleRoundabouts(const bool is_rotary,
const EdgeID via_edge,
const bool on_roundabout,
const bool can_exit_roundabout,
std::vector<ConnectedRoad> intersection) const;
// Indicates a Junction containing a motoryway
bool isMotorwayJunction(const EdgeID via_edge,
const std::vector<ConnectedRoad> &intersection) const;
// Decide whether a turn is a turn or a ramp access
TurnType findBasicTurnType(const EdgeID via_edge, const ConnectedRoad &candidate) const;
// Get the Instruction for an obvious turn
// Instruction will be a silent instruction
TurnInstruction getInstructionForObvious(const std::size_t number_of_candidates,
const EdgeID via_edge,
const bool through_street,
const ConnectedRoad &candidate) const;
// Helper Function that decides between NoTurn or NewName
TurnInstruction
noTurnOrNewName(const NodeID from, const EdgeID via_edge, const ConnectedRoad &candidate) const;
// Basic Turn Handling
// Dead end.
std::vector<ConnectedRoad> handleOneWayTurn(std::vector<ConnectedRoad> intersection) const;
// Mode Changes, new names...
std::vector<ConnectedRoad> handleTwoWayTurn(const EdgeID via_edge,
std::vector<ConnectedRoad> intersection) const;
// Forks, T intersections and similar
std::vector<ConnectedRoad> handleThreeWayTurn(const EdgeID via_edge,
std::vector<ConnectedRoad> intersection) const;
// Handling of turns larger then degree three
std::vector<ConnectedRoad> handleComplexTurn(const EdgeID via_edge,
std::vector<ConnectedRoad> intersection) const;
// Any Junction containing motorways
std::vector<ConnectedRoad>
handleMotorwayJunction(const EdgeID via_edge, std::vector<ConnectedRoad> intersection) const;
std::vector<ConnectedRoad> handleFromMotorway(const EdgeID via_edge,
std::vector<ConnectedRoad> intersection) const;
std::vector<ConnectedRoad> handleMotorwayRamp(const EdgeID via_edge,
std::vector<ConnectedRoad> intersection) const;
const IntersectionGenerator intersection_generator;
const RoundaboutHandler roundabout_handler;
const MotorwayHandler motorway_handler;
const TurnHandler turn_handler;
// Utility function, setting basic turn types. Prepares for normal turn handling.
std::vector<ConnectedRoad> setTurnTypes(const NodeID from,
const EdgeID via_edge,
std::vector<ConnectedRoad> intersection) const;
// Assignment of specific turn types
void assignFork(const EdgeID via_edge, ConnectedRoad &left, ConnectedRoad &right) const;
void assignFork(const EdgeID via_edge,
ConnectedRoad &left,
ConnectedRoad &center,
ConnectedRoad &right) const;
void
handleDistinctConflict(const EdgeID via_edge, ConnectedRoad &left, ConnectedRoad &right) const;
// Type specific fallbacks
std::vector<ConnectedRoad>
fallbackTurnAssignmentMotorway(std::vector<ConnectedRoad> intersection) const;
// Classification
std::size_t findObviousTurn(const EdgeID via_edge,
const std::vector<ConnectedRoad> &intersection) const;
std::pair<std::size_t, std::size_t>
findFork(const std::vector<ConnectedRoad> &intersection) const;
std::vector<ConnectedRoad> assignLeftTurns(const EdgeID via_edge,
std::vector<ConnectedRoad> intersection,
const std::size_t starting_at) const;
std::vector<ConnectedRoad> assignRightTurns(const EdgeID via_edge,
std::vector<ConnectedRoad> intersection,
const std::size_t up_to) const;
bool isRotary(const NodeID nid) const;
Intersection
setTurnTypes(const NodeID from, const EdgeID via_edge, Intersection intersection) const;
}; // class TurnAnalysis
} // namespace guidance

View File

@ -0,0 +1,74 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_TURN_HANDLER_HPP_
#define OSRM_EXTRACTOR_GUIDANCE_TURN_HANDLER_HPP_
#include "extractor/guidance/intersection_handler.hpp"
#include "extractor/guidance/intersection.hpp"
#include "extractor/query_node.hpp"
#include "util/name_table.hpp"
#include "util/node_based_graph.hpp"
#include <cstddef>
#include <vector>
#include <utility>
namespace osrm
{
namespace extractor
{
namespace guidance
{
// Intersection handlers deal with all issues related to intersections.
// They assign appropriate turn operations to the TurnOperations.
class TurnHandler : public IntersectionHandler
{
public:
TurnHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_info_list,
const util::NameTable &name_table);
~TurnHandler() override final;
// check whether the handler can actually handle the intersection
bool canProcess(const NodeID nid,
const EdgeID via_eid,
const Intersection &intersection) const override final;
// process the intersection
Intersection operator()(const NodeID nid,
const EdgeID via_eid,
Intersection intersection) const override final;
private:
// Dead end.
Intersection handleOneWayTurn(Intersection intersection) const;
// Mode Changes, new names...
Intersection handleTwoWayTurn(const EdgeID via_edge, Intersection intersection) const;
// Forks, T intersections and similar
Intersection handleThreeWayTurn(const EdgeID via_edge, Intersection intersection) const;
// Handling of turns larger then degree three
Intersection handleComplexTurn(const EdgeID via_edge, Intersection intersection) const;
void
handleDistinctConflict(const EdgeID via_edge, ConnectedRoad &left, ConnectedRoad &right) const;
// Classification
std::size_t findObviousTurn(const EdgeID via_edge, const Intersection &intersection) const;
std::pair<std::size_t, std::size_t> findFork(const Intersection &intersection) const;
Intersection assignLeftTurns(const EdgeID via_edge,
Intersection intersection,
const std::size_t starting_at) const;
Intersection assignRightTurns(const EdgeID via_edge,
Intersection intersection,
const std::size_t up_to) const;
};
} // namespace guidance
} // namespace extractor
} // namespace osrm
#endif /*OSRM_EXTRACTOR_GUIDANCE_TURN_HANDLER_HPP_*/

View File

@ -0,0 +1,31 @@
#include "extractor/guidance/intersection.hpp"
namespace osrm
{
namespace extractor
{
namespace guidance
{
ConnectedRoad::ConnectedRoad(const TurnOperation turn, const bool entry_allowed)
: entry_allowed(entry_allowed), turn(turn)
{
}
std::string toString(const ConnectedRoad &road)
{
std::string result = "[connection] ";
result += std::to_string(road.turn.eid);
result += " allows entry: ";
result += std::to_string(road.entry_allowed);
result += " angle: ";
result += std::to_string(road.turn.angle);
result += " instruction: ";
result += std::to_string(static_cast<std::int32_t>(road.turn.instruction.type)) + " " +
std::to_string(static_cast<std::int32_t>(road.turn.instruction.direction_modifier));
return result;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm

View File

@ -0,0 +1,255 @@
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/guidance/toolkit.hpp"
#include <algorithm>
#include <iterator>
#include <limits>
#include <utility>
namespace osrm
{
namespace extractor
{
namespace guidance
{
IntersectionGenerator::IntersectionGenerator(const util::NodeBasedDynamicGraph &node_based_graph,
const RestrictionMap &restriction_map,
const std::unordered_set<NodeID> &barrier_nodes,
const std::vector<QueryNode> &node_info_list,
const CompressedEdgeContainer &compressed_edge_container)
: node_based_graph(node_based_graph), restriction_map(restriction_map),
barrier_nodes(barrier_nodes), node_info_list(node_info_list),
compressed_edge_container(compressed_edge_container)
{
}
Intersection IntersectionGenerator::operator()(const NodeID from_node, const EdgeID via_eid) const
{
return getConnectedRoads(from_node, via_eid);
}
// a
// |
// |
// v
// For an intersection from_node --via_edi--> turn_node ----> c
// ^
// |
// |
// b
// This functions returns _all_ turns as if the graph was undirected.
// That means we not only get (from_node, turn_node, c) in the above example
// but also (from_node, turn_node, a), (from_node, turn_node, b). These turns are
// marked as invalid and only needed for intersection classification.
Intersection IntersectionGenerator::getConnectedRoads(const NodeID from_node,
const EdgeID via_eid) const
{
Intersection intersection;
const NodeID turn_node = node_based_graph.GetTarget(via_eid);
const NodeID only_restriction_to_node =
restriction_map.CheckForEmanatingIsOnlyTurn(from_node, turn_node);
const bool is_barrier_node = barrier_nodes.find(turn_node) != barrier_nodes.end();
bool has_uturn_edge = false;
for (const EdgeID onto_edge : node_based_graph.GetAdjacentEdgeRange(turn_node))
{
BOOST_ASSERT(onto_edge != SPECIAL_EDGEID);
const NodeID to_node = node_based_graph.GetTarget(onto_edge);
bool turn_is_valid =
// reverse edges are never valid turns because the resulting turn would look like this:
// from_node --via_edge--> turn_node <--onto_edge-- to_node
// however we need this for capture intersection shape for incoming one-ways
!node_based_graph.GetEdgeData(onto_edge).reversed &&
// we are not turning over a barrier
(!is_barrier_node || from_node == to_node) &&
// We are at an only_-restriction but not at the right turn.
(only_restriction_to_node == SPECIAL_NODEID || to_node == only_restriction_to_node) &&
// the turn is not restricted
!restriction_map.CheckIfTurnIsRestricted(from_node, turn_node, to_node);
auto angle = 0.;
if (from_node == to_node)
{
if (turn_is_valid && !is_barrier_node)
{
// we only add u-turns for dead-end streets.
if (node_based_graph.GetOutDegree(turn_node) > 1)
{
auto number_of_emmiting_bidirectional_edges = 0;
for (auto edge : node_based_graph.GetAdjacentEdgeRange(turn_node))
{
auto target = node_based_graph.GetTarget(edge);
auto reverse_edge = node_based_graph.FindEdge(target, turn_node);
BOOST_ASSERT(reverse_edge != SPECIAL_EDGEID);
if (!node_based_graph.GetEdgeData(reverse_edge).reversed)
{
++number_of_emmiting_bidirectional_edges;
}
}
// is a dead-end
turn_is_valid = number_of_emmiting_bidirectional_edges <= 1;
}
}
has_uturn_edge = true;
BOOST_ASSERT(angle >= 0. && angle < std::numeric_limits<double>::epsilon());
}
else
{
// unpack first node of second segment if packed
const auto first_coordinate = getRepresentativeCoordinate(
from_node, turn_node, via_eid, INVERT, compressed_edge_container, node_info_list);
const auto third_coordinate = getRepresentativeCoordinate(
turn_node, to_node, onto_edge, !INVERT, compressed_edge_container, node_info_list);
angle = util::coordinate_calculation::computeAngle(
first_coordinate, node_info_list[turn_node], third_coordinate);
if (angle < std::numeric_limits<double>::epsilon())
has_uturn_edge = true;
}
intersection.push_back(ConnectedRoad(
TurnOperation{onto_edge, angle, {TurnType::Invalid, DirectionModifier::UTurn}},
turn_is_valid));
}
// We hit the case of a street leading into nothing-ness. Since the code here assumes that this
// will
// never happen we add an artificial invalid uturn in this case.
if (!has_uturn_edge)
{
intersection.push_back(
{TurnOperation{via_eid, 0., {TurnType::Invalid, DirectionModifier::UTurn}}, false});
}
const auto ByAngle = [](const ConnectedRoad &first, const ConnectedRoad second) {
return first.turn.angle < second.turn.angle;
};
std::sort(std::begin(intersection), std::end(intersection), ByAngle);
BOOST_ASSERT(intersection[0].turn.angle >= 0. &&
intersection[0].turn.angle < std::numeric_limits<double>::epsilon());
return mergeSegregatedRoads(std::move(intersection));
}
/*
* Segregated Roads often merge onto a single intersection.
* While technically representing different roads, they are
* often looked at as a single road.
* Due to the merging, turn Angles seem off, wenn we compute them from the
* initial positions.
*
* b<b<b<b(1)<b<b<b
* aaaaa-b
* b>b>b>b(2)>b>b>b
*
* Would be seen as a slight turn going fro a to (2). A Sharp turn going from
* (1) to (2).
*
* In cases like these, we megre this segregated roads into a single road to
* end up with a case like:
*
* aaaaa-bbbbbb
*
* for the turn representation.
* Anything containing the first u-turn in a merge affects all other angles
* and is handled separately from all others.
*/
Intersection IntersectionGenerator::mergeSegregatedRoads(Intersection intersection) const
{
const auto getRight = [&](std::size_t index) {
return (index + intersection.size() - 1) % intersection.size();
};
const auto mergable = [&](std::size_t first, std::size_t second) -> bool {
const auto &first_data = node_based_graph.GetEdgeData(intersection[first].turn.eid);
const auto &second_data = node_based_graph.GetEdgeData(intersection[second].turn.eid);
return first_data.name_id != INVALID_NAME_ID && first_data.name_id == second_data.name_id &&
!first_data.roundabout && !second_data.roundabout &&
first_data.travel_mode == second_data.travel_mode &&
first_data.road_classification == second_data.road_classification &&
// compatible threshold
angularDeviation(intersection[first].turn.angle, intersection[second].turn.angle) <
60 &&
first_data.reversed != second_data.reversed;
};
const auto merge = [](const ConnectedRoad &first,
const ConnectedRoad &second) -> ConnectedRoad {
if (!first.entry_allowed)
{
ConnectedRoad result = second;
result.turn.angle = (first.turn.angle + second.turn.angle) / 2;
if (first.turn.angle - second.turn.angle > 180)
result.turn.angle += 180;
if (result.turn.angle > 360)
result.turn.angle -= 360;
return result;
}
else
{
BOOST_ASSERT(!second.entry_allowed);
ConnectedRoad result = first;
result.turn.angle = (first.turn.angle + second.turn.angle) / 2;
if (first.turn.angle - second.turn.angle > 180)
result.turn.angle += 180;
if (result.turn.angle > 360)
result.turn.angle -= 360;
return result;
}
};
if (intersection.size() == 1)
return intersection;
// check for merges including the basic u-turn
// these result in an adjustment of all other angles
if (mergable(0, intersection.size() - 1))
{
const double correction_factor =
(360 - intersection[intersection.size() - 1].turn.angle) / 2;
for (std::size_t i = 1; i + 1 < intersection.size(); ++i)
intersection[i].turn.angle += correction_factor;
intersection[0] = merge(intersection.front(), intersection.back());
intersection[0].turn.angle = 0;
intersection.pop_back();
}
else if (mergable(0, 1))
{
const double correction_factor = (intersection[1].turn.angle) / 2;
for (std::size_t i = 2; i < intersection.size(); ++i)
intersection[i].turn.angle += correction_factor;
intersection[0] = merge(intersection[0], intersection[1]);
intersection[0].turn.angle = 0;
intersection.erase(intersection.begin() + 1);
}
// a merge including the first u-turn requres an adjustment of the turn angles
// therefore these are handled prior to this step
for (std::size_t index = 2; index < intersection.size(); ++index)
{
if (mergable(index, getRight(index)))
{
intersection[getRight(index)] =
merge(intersection[getRight(index)], intersection[index]);
intersection.erase(intersection.begin() + index);
--index;
}
}
const auto ByAngle = [](const ConnectedRoad &first, const ConnectedRoad second) {
return first.turn.angle < second.turn.angle;
};
std::sort(std::begin(intersection), std::end(intersection), ByAngle);
return intersection;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm

View File

@ -0,0 +1,307 @@
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/intersection_handler.hpp"
#include "extractor/guidance/toolkit.hpp"
#include <algorithm>
using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData;
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace detail
{
inline bool requiresAnnouncement(const EdgeData &from, const EdgeData &to)
{
return !from.IsCompatibleTo(to);
}
}
IntersectionHandler::IntersectionHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_info_list,
const util::NameTable &name_table)
: node_based_graph(node_based_graph), node_info_list(node_info_list), name_table(name_table)
{
}
IntersectionHandler::~IntersectionHandler() {}
std::size_t IntersectionHandler::countValid(const Intersection &intersection) const
{
return std::count_if(intersection.begin(), intersection.end(),
[](const ConnectedRoad &road) { return road.entry_allowed; });
}
TurnType IntersectionHandler::findBasicTurnType(const EdgeID via_edge,
const ConnectedRoad &road) const
{
const auto &in_data = node_based_graph.GetEdgeData(via_edge);
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
bool on_ramp = isRampClass(in_data.road_classification.road_class);
bool onto_ramp = isRampClass(out_data.road_classification.road_class);
if (!on_ramp && onto_ramp)
return TurnType::Ramp;
if (in_data.name_id == out_data.name_id && in_data.name_id != INVALID_NAME_ID)
{
return TurnType::Continue;
}
return TurnType::Turn;
}
TurnInstruction IntersectionHandler::getInstructionForObvious(const std::size_t num_roads,
const EdgeID via_edge,
const bool through_street,
const ConnectedRoad &road) const
{
const auto type = findBasicTurnType(via_edge, road);
// handle travel modes:
const auto in_mode = node_based_graph.GetEdgeData(via_edge).travel_mode;
const auto out_mode = node_based_graph.GetEdgeData(road.turn.eid).travel_mode;
if (type == TurnType::Ramp)
{
return {TurnType::Ramp, getTurnDirection(road.turn.angle)};
}
if (angularDeviation(road.turn.angle, 0) < 0.01)
{
return {TurnType::Turn, DirectionModifier::UTurn};
}
if (type == TurnType::Turn)
{
const auto &in_data = node_based_graph.GetEdgeData(via_edge);
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
if (in_data.name_id != out_data.name_id &&
requiresNameAnnounced(name_table.GetNameForID(in_data.name_id),
name_table.GetNameForID(out_data.name_id)))
{
// obvious turn onto a through street is a merge
if (through_street)
{
return {TurnType::Merge, road.turn.angle > STRAIGHT_ANGLE
? DirectionModifier::SlightRight
: DirectionModifier::SlightLeft};
}
else
{
return {TurnType::NewName, getTurnDirection(road.turn.angle)};
}
}
else
{
if (in_mode == out_mode)
return {TurnType::Suppressed, getTurnDirection(road.turn.angle)};
else
return {TurnType::Notification, getTurnDirection(road.turn.angle)};
}
}
BOOST_ASSERT(type == TurnType::Continue);
if (in_mode != out_mode)
{
return {TurnType::Notification, getTurnDirection(road.turn.angle)};
}
if (num_roads > 2)
{
return {TurnType::Suppressed, getTurnDirection(road.turn.angle)};
}
else
{
return {TurnType::NoTurn, getTurnDirection(road.turn.angle)};
}
}
void IntersectionHandler::assignFork(const EdgeID via_edge,
ConnectedRoad &left,
ConnectedRoad &right) const
{
const auto &in_data = node_based_graph.GetEdgeData(via_edge);
const bool low_priority_left = isLowPriorityRoadClass(
node_based_graph.GetEdgeData(left.turn.eid).road_classification.road_class);
const bool low_priority_right = isLowPriorityRoadClass(
node_based_graph.GetEdgeData(right.turn.eid).road_classification.road_class);
if ((angularDeviation(left.turn.angle, STRAIGHT_ANGLE) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
angularDeviation(right.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE))
{
// left side is actually straight
const auto &out_data = node_based_graph.GetEdgeData(left.turn.eid);
if (detail::requiresAnnouncement(in_data, out_data))
{
if (low_priority_right && !low_priority_left)
{
left.turn.instruction = getInstructionForObvious(3, via_edge, false, left);
right.turn.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
else
{
if (low_priority_left && !low_priority_right)
{
left.turn.instruction = {findBasicTurnType(via_edge, left),
DirectionModifier::SlightLeft};
right.turn.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
else
{
left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft};
right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight};
}
}
}
else
{
left.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight};
right.turn.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
}
else if (angularDeviation(right.turn.angle, STRAIGHT_ANGLE) <
MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
angularDeviation(left.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE)
{
// right side is actually straight
const auto &out_data = node_based_graph.GetEdgeData(right.turn.eid);
if (angularDeviation(right.turn.angle, STRAIGHT_ANGLE) <
MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
angularDeviation(left.turn.angle, STRAIGHT_ANGLE) > FUZZY_ANGLE_DIFFERENCE)
{
if (detail::requiresAnnouncement(in_data, out_data))
{
if (low_priority_left && !low_priority_right)
{
left.turn.instruction = {findBasicTurnType(via_edge, left),
DirectionModifier::SlightLeft};
right.turn.instruction = getInstructionForObvious(3, via_edge, false, right);
}
else
{
if (low_priority_right && !low_priority_left)
{
left.turn.instruction = {findBasicTurnType(via_edge, left),
DirectionModifier::SlightLeft};
right.turn.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};
}
else
{
right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight};
left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft};
}
}
}
else
{
right.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight};
left.turn.instruction = {findBasicTurnType(via_edge, left),
DirectionModifier::SlightLeft};
}
}
}
// left side of fork
if (low_priority_right && !low_priority_left)
left.turn.instruction = {TurnType::Suppressed, DirectionModifier::SlightLeft};
else
{
if (low_priority_left && !low_priority_right)
left.turn.instruction = {TurnType::Turn, DirectionModifier::SlightLeft};
else
left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft};
}
// right side of fork
if (low_priority_left && !low_priority_right)
right.turn.instruction = {TurnType::Suppressed, DirectionModifier::SlightLeft};
else
{
if (low_priority_right && !low_priority_left)
right.turn.instruction = {TurnType::Turn, DirectionModifier::SlightRight};
else
right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight};
}
}
void IntersectionHandler::assignFork(const EdgeID via_edge,
ConnectedRoad &left,
ConnectedRoad &center,
ConnectedRoad &right) const
{
// TODO handle low priority road classes in a reasonable way
if (left.entry_allowed && center.entry_allowed && right.entry_allowed)
{
left.turn.instruction = {TurnType::Fork, DirectionModifier::SlightLeft};
if (angularDeviation(center.turn.angle, 180) < MAXIMAL_ALLOWED_NO_TURN_DEVIATION)
{
const auto &in_data = node_based_graph.GetEdgeData(via_edge);
const auto &out_data = node_based_graph.GetEdgeData(center.turn.eid);
if (detail::requiresAnnouncement(in_data, out_data))
{
center.turn.instruction = {TurnType::Fork, DirectionModifier::Straight};
}
else
{
center.turn.instruction = {TurnType::Suppressed, DirectionModifier::Straight};
}
}
else
{
center.turn.instruction = {TurnType::Fork, DirectionModifier::Straight};
}
right.turn.instruction = {TurnType::Fork, DirectionModifier::SlightRight};
}
else if (left.entry_allowed)
{
if (right.entry_allowed)
assignFork(via_edge, left, right);
else if (center.entry_allowed)
assignFork(via_edge, left, center);
else
left.turn.instruction = {findBasicTurnType(via_edge, left),
getTurnDirection(left.turn.angle)};
}
else if (right.entry_allowed)
{
if (center.entry_allowed)
assignFork(via_edge, center, right);
else
right.turn.instruction = {findBasicTurnType(via_edge, right),
getTurnDirection(right.turn.angle)};
}
else
{
if (center.entry_allowed)
center.turn.instruction = {findBasicTurnType(via_edge, center),
getTurnDirection(center.turn.angle)};
}
}
bool IntersectionHandler::isThroughStreet(const std::size_t index,
const Intersection &intersection) const
{
if (node_based_graph.GetEdgeData(intersection[index].turn.eid).name_id == INVALID_NAME_ID)
return false;
for (const auto &road : intersection)
{
// a through street cannot start at our own position
if (road.turn.angle < std::numeric_limits<double>::epsilon())
continue;
if (angularDeviation(road.turn.angle, intersection[index].turn.angle) >
(STRAIGHT_ANGLE - NARROW_TURN_ANGLE) &&
node_based_graph.GetEdgeData(road.turn.eid).name_id ==
node_based_graph.GetEdgeData(intersection[index].turn.eid).name_id)
return true;
}
return false;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm

View File

@ -0,0 +1,524 @@
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/motorway_handler.hpp"
#include "extractor/guidance/toolkit.hpp"
#include "util/simple_logger.hpp"
#include <limits>
#include <utility>
#include <boost/assert.hpp>
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace detail
{
inline bool isMotorwayClass(const FunctionalRoadClass road_class)
{
return road_class == FunctionalRoadClass::MOTORWAY || road_class == FunctionalRoadClass::TRUNK;
}
inline bool isMotorwayClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph)
{
return isMotorwayClass(node_based_graph.GetEdgeData(eid).road_classification.road_class);
}
inline FunctionalRoadClass roadClass(const ConnectedRoad &road,
const util::NodeBasedDynamicGraph &graph)
{
return graph.GetEdgeData(road.turn.eid).road_classification.road_class;
}
inline bool isRampClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_based_graph)
{
return isRampClass(node_based_graph.GetEdgeData(eid).road_classification.road_class);
}
}
MotorwayHandler::MotorwayHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_info_list,
const util::NameTable &name_table)
: IntersectionHandler(node_based_graph, node_info_list, name_table)
{
}
MotorwayHandler::~MotorwayHandler() {}
bool MotorwayHandler::canProcess(const NodeID,
const EdgeID via_eid,
const Intersection &intersection) const
{
bool has_motorway = false;
bool has_normal_roads = false;
for (const auto &road : intersection)
{
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
// not merging or forking?
if (road.entry_allowed && angularDeviation(road.turn.angle, STRAIGHT_ANGLE) > 60)
return false;
else if (out_data.road_classification.road_class == FunctionalRoadClass::MOTORWAY ||
out_data.road_classification.road_class == FunctionalRoadClass::TRUNK)
{
if (road.entry_allowed)
has_motorway = true;
}
else if (!isRampClass(out_data.road_classification.road_class))
has_normal_roads = true;
}
if (has_normal_roads)
return false;
const auto &in_data = node_based_graph.GetEdgeData(via_eid);
return has_motorway ||
in_data.road_classification.road_class == FunctionalRoadClass::MOTORWAY ||
in_data.road_classification.road_class == FunctionalRoadClass::TRUNK;
}
Intersection MotorwayHandler::
operator()(const NodeID, const EdgeID via_eid, Intersection intersection) const
{
const auto &in_data = node_based_graph.GetEdgeData(via_eid);
// coming from motorway
if (detail::isMotorwayClass(in_data.road_classification.road_class))
{
return fromMotorway(via_eid, std::move(intersection));
}
else // coming from a ramp
{
return fromRamp(via_eid, std::move(intersection));
// ramp merging straight onto motorway
}
}
Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection intersection) const
{
const auto &in_data = node_based_graph.GetEdgeData(via_eid);
BOOST_ASSERT(detail::isMotorwayClass(in_data.road_classification.road_class));
const auto countExitingMotorways = [this](const Intersection &intersection) {
unsigned count = 0;
for (const auto &road : intersection)
{
if (road.entry_allowed && detail::isMotorwayClass(road.turn.eid, node_based_graph))
++count;
}
return count;
};
// find the angle that continues on our current highway
const auto getContinueAngle = [this, in_data](const Intersection &intersection) {
for (const auto &road : intersection)
{
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
if (road.turn.angle != 0 && in_data.name_id == out_data.name_id &&
in_data.name_id != INVALID_NAME_ID &&
detail::isMotorwayClass(out_data.road_classification.road_class))
return road.turn.angle;
}
return intersection[0].turn.angle;
};
const auto getMostLikelyContinue = [this, in_data](const Intersection &intersection) {
double angle = intersection[0].turn.angle;
double best = 180;
for (const auto &road : intersection)
{
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
if (detail::isMotorwayClass(out_data.road_classification.road_class) &&
angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < best)
{
best = angularDeviation(road.turn.angle, STRAIGHT_ANGLE);
angle = road.turn.angle;
}
}
return angle;
};
const auto findBestContinue = [&]() {
const double continue_angle = getContinueAngle(intersection);
if (continue_angle != intersection[0].turn.angle)
return continue_angle;
else
return getMostLikelyContinue(intersection);
};
// find continue angle
const double continue_angle = findBestContinue();
// highway does not continue and has no obvious choice
if (continue_angle == intersection[0].turn.angle)
{
if (intersection.size() == 2)
{
// do not announce ramps at the end of a highway
intersection[1].turn.instruction = {TurnType::NoTurn,
getTurnDirection(intersection[1].turn.angle)};
}
else if (intersection.size() == 3)
{
// splitting ramp at the end of a highway
if (intersection[1].entry_allowed && intersection[2].entry_allowed)
{
assignFork(via_eid, intersection[2], intersection[1]);
}
else
{
// ending in a passing ramp
if (intersection[1].entry_allowed)
intersection[1].turn.instruction = {
TurnType::NoTurn, getTurnDirection(intersection[1].turn.angle)};
else
intersection[2].turn.instruction = {
TurnType::NoTurn, getTurnDirection(intersection[2].turn.angle)};
}
}
else if (intersection.size() == 4 &&
detail::roadClass(intersection[1], node_based_graph) ==
detail::roadClass(intersection[2], node_based_graph) &&
detail::roadClass(intersection[2], node_based_graph) ==
detail::roadClass(intersection[3], node_based_graph))
{
// tripple fork at the end
assignFork(via_eid, intersection[3], intersection[2], intersection[1]);
}
else if (countValid(intersection) > 0) // check whether turns exist at all
{
// FALLBACK, this should hopefully never be reached
util::SimpleLogger().Write(logDEBUG)
<< "Fallback reached from motorway, no continue angle, " << intersection.size()
<< " roads, " << countValid(intersection) << " valid ones.";
return fallback(std::move(intersection));
}
}
else
{
const unsigned exiting_motorways = countExitingMotorways(intersection);
if (exiting_motorways == 0)
{
// Ending in Ramp
for (auto &road : intersection)
{
if (road.entry_allowed)
{
BOOST_ASSERT(detail::isRampClass(road.turn.eid, node_based_graph));
road.turn.instruction =
TurnInstruction::SUPPRESSED(getTurnDirection(road.turn.angle));
}
}
}
else if (exiting_motorways == 1)
{
// normal motorway passing some ramps or mering onto another motorway
if (intersection.size() == 2)
{
BOOST_ASSERT(!detail::isRampClass(intersection[1].turn.eid, node_based_graph));
intersection[1].turn.instruction =
getInstructionForObvious(intersection.size(), via_eid,
isThroughStreet(1,intersection), intersection[1]);
}
else
{
// Normal Highway exit or merge
for (auto &road : intersection)
{
// ignore invalid uturns/other
if (!road.entry_allowed)
continue;
if (road.turn.angle == continue_angle)
{
road.turn.instruction = getInstructionForObvious(
intersection.size(), via_eid, isThroughStreet(1,intersection), road);
}
else if (road.turn.angle < continue_angle)
{
road.turn.instruction = {
detail::isRampClass(road.turn.eid, node_based_graph) ? TurnType::Ramp
: TurnType::Turn,
(road.turn.angle < 145) ? DirectionModifier::Right
: DirectionModifier::SlightRight};
}
else if (road.turn.angle > continue_angle)
{
road.turn.instruction = {
detail::isRampClass(road.turn.eid, node_based_graph) ? TurnType::Ramp
: TurnType::Turn,
(road.turn.angle > 215) ? DirectionModifier::Left
: DirectionModifier::SlightLeft};
}
}
}
}
// handle motorway forks
else if (exiting_motorways > 1)
{
if (exiting_motorways == 2 && intersection.size() == 2)
{
intersection[1].turn.instruction =
getInstructionForObvious(intersection.size(), via_eid,
isThroughStreet(1,intersection), intersection[1]);
util::SimpleLogger().Write(logDEBUG) << "Disabled U-Turn on a freeway";
intersection[0].entry_allowed = false; // UTURN on the freeway
}
else if (exiting_motorways == 2)
{
// standard fork
std::size_t first_valid = std::numeric_limits<std::size_t>::max(),
second_valid = std::numeric_limits<std::size_t>::max();
for (std::size_t i = 0; i < intersection.size(); ++i)
{
if (intersection[i].entry_allowed &&
detail::isMotorwayClass(intersection[i].turn.eid, node_based_graph))
{
if (first_valid < intersection.size())
{
second_valid = i;
break;
}
else
{
first_valid = i;
}
}
}
assignFork(via_eid, intersection[second_valid], intersection[first_valid]);
}
else if (exiting_motorways == 3)
{
// triple fork
std::size_t first_valid = std::numeric_limits<std::size_t>::max(),
second_valid = std::numeric_limits<std::size_t>::max(),
third_valid = std::numeric_limits<std::size_t>::max();
for (std::size_t i = 0; i < intersection.size(); ++i)
{
if (intersection[i].entry_allowed &&
detail::isMotorwayClass(intersection[i].turn.eid, node_based_graph))
{
if (second_valid < intersection.size())
{
third_valid = i;
break;
}
else if (first_valid < intersection.size())
{
second_valid = i;
}
else
{
first_valid = i;
}
}
}
assignFork(via_eid, intersection[third_valid], intersection[second_valid],
intersection[first_valid]);
}
else
{
util::SimpleLogger().Write(logDEBUG) << "Found motorway junction with more than "
"2 exiting motorways or additional ramps";
return fallback(std::move(intersection));
}
} // done for more than one highway exit
}
return intersection;
}
Intersection MotorwayHandler::fromRamp(const EdgeID via_eid, Intersection intersection) const
{
auto num_valid_turns = countValid(intersection);
// ramp straight into a motorway/ramp
if (intersection.size() == 2 && num_valid_turns == 1)
{
BOOST_ASSERT(!intersection[0].entry_allowed);
BOOST_ASSERT(detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph));
intersection[1].turn.instruction = getInstructionForObvious(
intersection.size(), via_eid, isThroughStreet(1,intersection), intersection[1]);
}
else if (intersection.size() == 3)
{
// merging onto a passing highway / or two ramps merging onto the same highway
if (num_valid_turns == 1)
{
BOOST_ASSERT(!intersection[0].entry_allowed);
// check order of highways
// 4
// 5 3
//
// 6 2
//
// 7 1
// 0
if (intersection[1].entry_allowed)
{
if (detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph) &&
node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id !=
INVALID_NAME_ID &&
node_based_graph.GetEdgeData(intersection[2].turn.eid).name_id ==
node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id)
{
// circular order indicates a merge to the left (0-3 onto 4
if (angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) <
NARROW_TURN_ANGLE)
intersection[1].turn.instruction = {TurnType::Merge,
DirectionModifier::SlightLeft};
else // fallback
intersection[1].turn.instruction = {
TurnType::Merge, getTurnDirection(intersection[1].turn.angle)};
}
else // passing by the end of a motorway
{
intersection[1].turn.instruction =
getInstructionForObvious(intersection.size(), via_eid,
isThroughStreet(1,intersection), intersection[1]);
}
}
else
{
BOOST_ASSERT(intersection[2].entry_allowed);
if (detail::isMotorwayClass(intersection[2].turn.eid, node_based_graph) &&
node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id !=
INVALID_NAME_ID &&
node_based_graph.GetEdgeData(intersection[1].turn.eid).name_id ==
node_based_graph.GetEdgeData(intersection[0].turn.eid).name_id)
{
// circular order (5-0) onto 4
if (angularDeviation(intersection[2].turn.angle, STRAIGHT_ANGLE) <
NARROW_TURN_ANGLE)
intersection[2].turn.instruction = {TurnType::Merge,
DirectionModifier::SlightRight};
else // fallback
intersection[2].turn.instruction = {
TurnType::Merge, getTurnDirection(intersection[2].turn.angle)};
}
else // passing the end of a highway
{
intersection[2].turn.instruction =
getInstructionForObvious(intersection.size(), via_eid,
isThroughStreet(2,intersection), intersection[2]);
}
}
}
else
{
BOOST_ASSERT(num_valid_turns == 2);
// UTurn on ramps is not possible
BOOST_ASSERT(!intersection[0].entry_allowed);
BOOST_ASSERT(intersection[1].entry_allowed);
BOOST_ASSERT(intersection[2].entry_allowed);
// two motorways starting at end of ramp (fork)
// M M
// \ /
// |
// R
if (detail::isMotorwayClass(intersection[1].turn.eid, node_based_graph) &&
detail::isMotorwayClass(intersection[2].turn.eid, node_based_graph))
{
assignFork(via_eid, intersection[2], intersection[1]);
}
else
{
// continued ramp passing motorway entry
// M R
// M R
// | /
// R
if (detail::isMotorwayClass(node_based_graph.GetEdgeData(intersection[1].turn.eid)
.road_classification.road_class))
{
intersection[1].turn.instruction = {TurnType::Turn,
DirectionModifier::SlightRight};
intersection[2].turn.instruction = {TurnType::Continue,
DirectionModifier::SlightLeft};
}
else
{
assignFork(via_eid, intersection[2], intersection[1]);
}
}
}
}
// On - Off Ramp on passing Motorway, Ramp onto Fork(?)
else if (intersection.size() == 4)
{
bool passed_highway_entry = false;
for (auto &road : intersection)
{
const auto &edge_data = node_based_graph.GetEdgeData(road.turn.eid);
if (!road.entry_allowed &&
detail::isMotorwayClass(edge_data.road_classification.road_class))
{
passed_highway_entry = true;
}
else if (detail::isMotorwayClass(edge_data.road_classification.road_class))
{
road.turn.instruction = {TurnType::Merge, passed_highway_entry
? DirectionModifier::SlightRight
: DirectionModifier::SlightLeft};
}
else
{
BOOST_ASSERT(isRampClass(edge_data.road_classification.road_class));
road.turn.instruction = {TurnType::Ramp, getTurnDirection(road.turn.angle)};
}
}
}
else
{ // FALLBACK, hopefully this should never been reached
util::SimpleLogger().Write(logDEBUG) << "Reached fallback on motorway ramp with "
<< intersection.size() << " roads and "
<< countValid(intersection) << " valid turns.";
return fallback(std::move(intersection));
}
return intersection;
}
Intersection MotorwayHandler::fallback(Intersection intersection) const
{
for (auto &road : intersection)
{
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
util::SimpleLogger().Write(logDEBUG)
<< "road: " << toString(road) << " Name: " << out_data.name_id
<< " Road Class: " << (int)out_data.road_classification.road_class;
if (!road.entry_allowed)
continue;
const auto type = detail::isMotorwayClass(out_data.road_classification.road_class)
? TurnType::Merge
: TurnType::Turn;
if (type == TurnType::Turn)
{
if (angularDeviation(road.turn.angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE)
road.turn.instruction = {type, DirectionModifier::Straight};
else
{
road.turn.instruction = {type, road.turn.angle > STRAIGHT_ANGLE
? DirectionModifier::SlightLeft
: DirectionModifier::SlightRight};
}
}
else
{
road.turn.instruction = {type, road.turn.angle < STRAIGHT_ANGLE
? DirectionModifier::SlightLeft
: DirectionModifier::SlightRight};
}
}
return intersection;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm

View File

@ -0,0 +1,257 @@
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/roundabout_handler.hpp"
#include "extractor/guidance/toolkit.hpp"
#include "util/simple_logger.hpp"
#include <cmath>
#include <set>
#include <unordered_set>
#include <boost/assert.hpp>
namespace osrm
{
namespace extractor
{
namespace guidance
{
RoundaboutHandler::RoundaboutHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_info_list,
const util::NameTable &name_table)
: IntersectionHandler(node_based_graph, node_info_list, name_table)
{
}
RoundaboutHandler::~RoundaboutHandler() {}
bool RoundaboutHandler::canProcess(const NodeID from_nid,
const EdgeID via_eid,
const Intersection &intersection) const
{
const auto flags = getRoundaboutFlags(from_nid, via_eid, intersection);
return flags.on_roundabout || flags.can_enter;
}
Intersection RoundaboutHandler::
operator()(const NodeID from_nid, const EdgeID via_eid, Intersection intersection) const
{
const auto flags = getRoundaboutFlags(from_nid, via_eid, intersection);
const bool is_rotary = isRotary(node_based_graph.GetTarget(via_eid));
// find the radius of the roundabout
return handleRoundabouts(is_rotary, via_eid, flags.on_roundabout, flags.can_exit_separately,
std::move(intersection));
}
detail::RoundaboutFlags RoundaboutHandler::getRoundaboutFlags(
const NodeID from_nid, const EdgeID via_eid, const Intersection &intersection) const
{
const auto &in_edge_data = node_based_graph.GetEdgeData(via_eid);
bool on_roundabout = in_edge_data.roundabout;
bool can_enter_roundabout = false;
bool can_exit_roundabout_separately = false;
for (const auto &road : intersection)
{
const auto &edge_data = node_based_graph.GetEdgeData(road.turn.eid);
// only check actual outgoing edges
if (edge_data.reversed)
continue;
if (edge_data.roundabout)
{
can_enter_roundabout = true;
}
// Exiting roundabouts at an entry point is technically a data-modelling issue.
// This workaround handles cases in which an exit follows the entry.
// To correctly represent perceived exits, we only count exits leading to a
// separate vertex than the one we are coming from that are in the direction of
// the roundabout.
// The sorting of the angles represents a problem for left-sided driving, though.
// FIXME in case of left-sided driving, we have to check whether we can enter the
// roundabout later in the cycle, rather than prior.
// FIXME requires consideration of crossing the roundabout
else if (node_based_graph.GetTarget(road.turn.eid) != from_nid && !can_enter_roundabout)
{
can_exit_roundabout_separately = true;
}
}
return {on_roundabout, can_enter_roundabout, can_exit_roundabout_separately};
}
bool RoundaboutHandler::isRotary(const NodeID nid) const
{
// translate a node ID into its respective coordinate stored in the node_info_list
const auto getCoordinate = [this](const NodeID node) {
return util::Coordinate(node_info_list[node].lon, node_info_list[node].lat);
};
unsigned roundabout_name_id = 0;
std::unordered_set<unsigned> connected_names;
const auto getNextOnRoundabout = [this, &roundabout_name_id,
&connected_names](const NodeID node) {
EdgeID continue_edge = SPECIAL_EDGEID;
for (const auto edge : node_based_graph.GetAdjacentEdgeRange(node))
{
const auto &edge_data = node_based_graph.GetEdgeData(edge);
if (!edge_data.reversed && edge_data.roundabout)
{
if (SPECIAL_EDGEID != continue_edge)
{
// fork in roundabout
return SPECIAL_EDGEID;
}
// roundabout does not keep its name
if (roundabout_name_id != 0 && roundabout_name_id != edge_data.name_id &&
requiresNameAnnounced(name_table.GetNameForID(roundabout_name_id),
name_table.GetNameForID(edge_data.name_id)))
{
return SPECIAL_EDGEID;
}
roundabout_name_id = edge_data.name_id;
continue_edge = edge;
}
else if (!edge_data.roundabout)
{
// remember all connected road names
connected_names.insert(edge_data.name_id);
}
}
return continue_edge;
};
// the roundabout radius has to be the same for all locations we look at it from
// to guarantee this, we search the full roundabout for its vertices
// and select the three smalles ids
std::set<NodeID> roundabout_nodes; // needs to be sorted
// this value is a hard abort to deal with potential self-loops
NodeID last_node = nid;
while (0 == roundabout_nodes.count(last_node))
{
roundabout_nodes.insert(last_node);
const auto eid = getNextOnRoundabout(last_node);
if (eid == SPECIAL_EDGEID)
{
util::SimpleLogger().Write(logDEBUG) << "Non-Loop Roundabout found.";
return false;
}
last_node = node_based_graph.GetTarget(eid);
}
// do we have a dedicated name for the rotary, if not its a roundabout
// This function can theoretically fail if the roundabout name is partly
// used with a reference and without. This will be fixed automatically
// when we handle references separately or if the useage is more consistent
if (roundabout_name_id == 0 || connected_names.count(roundabout_name_id))
{
return false;
}
if (roundabout_nodes.size() <= 1)
{
return false;
}
// calculate the radius of the roundabout/rotary. For two coordinates, we assume a minimal
// circle
// with both vertices right at the other side (so half their distance in meters).
// Otherwise, we construct a circle through the first tree vertices.
const auto getRadius = [&roundabout_nodes, &getCoordinate]() {
auto node_itr = roundabout_nodes.begin();
if (roundabout_nodes.size() == 2)
{
const auto first = getCoordinate(*node_itr++), second = getCoordinate(*node_itr++);
return 0.5 * util::coordinate_calculation::haversineDistance(first, second);
}
else
{
const auto first = getCoordinate(*node_itr++), second = getCoordinate(*node_itr++),
third = getCoordinate(*node_itr++);
return util::coordinate_calculation::circleRadius(first, second, third);
}
};
const double radius = getRadius();
// check whether the circle computation has gone wrong
// The radius computation can result in infinity, if the three coordinates are non-distinct.
// To stay on the safe side, we say its not a rotary
if (std::isinf(radius))
return false;
return radius > MAX_ROUNDABOUT_RADIUS;
}
Intersection RoundaboutHandler::handleRoundabouts(const bool is_rotary,
const EdgeID via_eid,
const bool on_roundabout,
const bool can_exit_roundabout_separately,
Intersection intersection) const
{
// TODO requires differentiation between roundabouts and rotaries
// detect via radius (get via circle through three vertices)
NodeID node_v = node_based_graph.GetTarget(via_eid);
if (on_roundabout)
{
// Shoule hopefully have only a single exit and continue
// at least for cars. How about bikes?
for (auto &road : intersection)
{
auto &turn = road.turn;
const auto &out_data = node_based_graph.GetEdgeData(road.turn.eid);
if (out_data.roundabout)
{
// TODO can forks happen in roundabouts? E.g. required lane changes
if (1 == node_based_graph.GetDirectedOutDegree(node_v))
{
// No turn possible.
turn.instruction = TurnInstruction::NO_TURN();
}
else
{
turn.instruction =
TurnInstruction::REMAIN_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle));
}
}
else
{
turn.instruction =
TurnInstruction::EXIT_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle));
}
}
return intersection;
}
else
for (auto &road : intersection)
{
if (!road.entry_allowed)
continue;
auto &turn = road.turn;
const auto &out_data = node_based_graph.GetEdgeData(turn.eid);
if (out_data.roundabout)
{
turn.instruction =
TurnInstruction::ENTER_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle));
if (can_exit_roundabout_separately)
{
if (turn.instruction.type == TurnType::EnterRotary)
turn.instruction.type = TurnType::EnterRotaryAtExit;
if (turn.instruction.type == TurnType::EnterRoundabout)
turn.instruction.type = TurnType::EnterRoundaboutAtExit;
}
}
else
{
turn.instruction = TurnInstruction::ENTER_AND_EXIT_ROUNDABOUT(
is_rotary, getTurnDirection(turn.angle));
}
}
return intersection;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff