Refactors and improves the Sliproad Handler, resolves #3109

This commit is contained in:
Daniel J. Hofmann 2016-10-26 14:32:29 -07:00 committed by Patrick Niklaus
parent df3c39cef5
commit 875f482203
16 changed files with 1797 additions and 759 deletions

View File

@ -19,6 +19,7 @@
- Improved turn angle calculation, detecting offsets due to lanes / minor variations due to inaccuracies
- Corrected the bearings returned for intermediate steps - requires reprocessing
- Improved turn locations for collapsed turns
- Sliproad classification refinements: the situations we detect as Sliproads now resemble more closely the reality
- Trip Plugin
- changed internal behaviour to prefer the smallest lexicographic result over the largest one
- Bugfixes

View File

@ -78,24 +78,24 @@ Feature: Car - Street names in instructions
Scenario: Inner city expressway with on road
Given the node map
"""
a b c g
f
a b . . . c g
`f .
`
.
.
d
.
.
.
e
"""
And the ways
| nodes | highway | name | name:pronunciation |
| abc | primary | road | roooaad |
| cg | primary | road | roooaad |
| bfd | trunk_link | | |
| cde | trunk | trunk | truank |
| nodes | highway | name | name:pronunciation | oneway |
| abc | primary | road | roooaad | |
| cg | primary | road | roooaad | |
| bfd | trunk_link | sliproad | | yes |
| cde | trunk | trunk | truank | yes |
And the relations
| type | way:from | way:to | node:via | restriction |

View File

@ -5,13 +5,17 @@ Feature: Turn Lane Guidance
Given the profile "car"
Given a grid size of 3 meters
@sliproads
Scenario: Separate Turn Lanes
Given the node map
"""
e
a b c g
d
.
a ... b ..... c . g
` .
`... d
.
f
"""
@ -41,8 +45,10 @@ Feature: Turn Lane Guidance
Given the node map
"""
e
a b c g
d
a . . b . . . c g
` .
` .
` d
f
"""
@ -67,21 +73,22 @@ Feature: Turn Lane Guidance
| a,g | in,straight,straight | depart,new name straight,arrive | ,left:false straight:true right:false, |
| a,f | in,cross,cross | depart,turn right,arrive | ,left:false straight:false right:true, |
@sliproads
Scenario: Separate Turn Lanes Next to other turns
Given the node map
"""
e
a - - b.-.- - c-g
| ' 'd
| f
|
|
|
|
|
|
i - - h - - - j
. e
a . . b . . . c g
. ` .
. ` .
. d
. f
.
.
.
.
i . . h . . . j
"""
And the ways
@ -109,6 +116,7 @@ Feature: Turn Lane Guidance
| a,j | in,turn,other,other | depart,turn right,turn left,arrive | ,,left:true right:false, |
| a,i | in,turn,other,other | depart,turn right,turn right,arrive | ,,left:false right:true, |
@todo @2654 @none
#https://github.com/Project-OSRM/osrm-backend/issues/2645
#http://www.openstreetmap.org/export#map=19/52.56054/13.32152
@ -231,6 +239,7 @@ Feature: Turn Lane Guidance
| a,j | ghough,market,market | depart,turn left,arrive | ,none:true straight:false straight:false straight:false, |
| a,f | ghough,ghough,ghough | depart,continue slight left,arrive | ,none:true straight:true straight:false straight:false, |
Scenario: Check sliproad handler loop's exit condition, Issue #2896
# http://www.openstreetmap.org/way/198481519
Given the node locations

View File

@ -55,3 +55,38 @@ Feature: Collapse
| waypoints | route | turns |
| a,g | road,road,road | depart,continue uturn,arrive |
| d,c | road,road,road | depart,continue uturn,arrive |
Scenario: Forking before a turn (forky)
Given the node map
"""
g
.
c
a . . b .'
`d.
f e
"""
# note: check clooapse.feature for a similar test case where we do not
# classify the situation as Sliproad and therefore keep the fork inst.
And the ways
| nodes | name | oneway | highway |
| ab | road | yes | primary |
| bd | road | yes | primary |
| bc | road | yes | primary |
| de | road | yes | primary |
| fd | cross | no | secondary |
| dc | cross | no | secondary |
| cg | cross | no | secondary |
And the relations
| type | way:from | way:to | node:via | restriction |
| restriction | bd | dc | d | no_left_turn |
| restriction | bc | dc | c | no_right_turn |
When I route I should get
| waypoints | route | turns |
| a,g | road,cross,cross | depart,turn left,arrive |
| a,e | road,road,road | depart,continue right,arrive |
# We should discuss whether the next item should be collapsed to depart,turn right,arrive.
| a,f | road,road,cross,cross | depart,continue slight right,turn right,arrive |

View File

@ -701,12 +701,15 @@ Feature: Collapse
Given the node map
"""
g
.
c
a b
d
a . . b .'
` d.
f e
"""
# as it is right now we don't classify this as a sliproad,
# check collapse-detail.feature for a similar test case
# which removes the fork here due to it being a Sliproad.
And the ways
| nodes | name | oneway | highway |
@ -725,10 +728,9 @@ Feature: Collapse
When I route I should get
| waypoints | route | turns |
| a,g | road,cross,cross | depart,turn left,arrive |
| a,e | road,road,road | depart,continue straight,arrive |
# We should discuss whether the next item should be collapsed to depart,turn right,arrive.
| a,f | road,road,cross,cross | depart,continue straight,turn right,arrive |
| a,g | road,cross,cross | depart,fork left,arrive |
| a,e | road,road,road | depart,fork slight right,arrive |
| a,f | road,road,cross,cross | depart,fork slight right,turn right,arrive |
Scenario: On-Off on Highway
Given the node map

View File

@ -9,23 +9,23 @@ Feature: Slipways and Dedicated Turn Lanes
Given the node map
"""
e
a b c d
h
1
a b . . c d
`h .
`
1 `
.
f
.
g
"""
And the ways
| nodes | highway | name |
| abc | trunk | first |
| cd | trunk | first |
| bhf | trunk_link | |
| cfg | primary | second |
| ec | primary | second |
| nodes | highway | name | oneway |
| abc | trunk | first | |
| cd | trunk | first | |
| bhf | trunk_link | | yes |
| cfg | primary | second | yes |
| ec | primary | second | |
And the relations
| type | way:from | way:to | node:via | restriction |
@ -51,12 +51,12 @@ Feature: Slipways and Dedicated Turn Lanes
"""
And the ways
| nodes | highway | name | maxspeed |
| abc | trunk | first | 70 |
| cd | trunk | first | 2 |
| bhf | trunk_link | | 2 |
| cfg | primary | second | 50 |
| ec | primary | second | 50 |
| nodes | highway | name | maxspeed | oneway |
| abc | trunk | first | 70 | |
| cd | trunk | first | 2 | |
| bhf | trunk_link | | 2 | yes |
| cfg | primary | second | 50 | yes |
| ec | primary | second | 50 | |
And the relations
| type | way:from | way:to | node:via | restriction |
@ -125,24 +125,24 @@ Feature: Slipways and Dedicated Turn Lanes
Scenario: Inner city expressway with on road
Given the node map
"""
a b c g
f
a b . . . c g
`f .
`
.
.
d
.
.
.
e
"""
And the ways
| nodes | highway | name |
| abc | primary | road |
| cg | primary | road |
| bfd | trunk_link | |
| cde | trunk | trunk |
| nodes | highway | name | oneway |
| abc | primary | road | |
| cg | primary | road | |
| bfd | trunk_link | | yes |
| cde | trunk | trunk | yes |
And the relations
| type | way:from | way:to | node:via | restriction |
@ -240,8 +240,8 @@ Feature: Slipways and Dedicated Turn Lanes
| qe | secondary_link | Ettlinger Allee | | yes |
When I route I should get
| waypoints | route | turns | ref |
| a,o | Schwarzwaldstrasse,Ettlinger Allee,Ettlinger Allee | depart,turn right,arrive | L561,L561, |
| waypoints | route | turns | ref |
| a,o | Schwarzwaldstrasse,Ettlinger Allee,Ettlinger Allee | depart,turn right,arrive | L561,L561, |
Scenario: Traffic Lights everywhere
#http://map.project-osrm.org/?z=18&center=48.995336%2C8.383813&loc=48.995467%2C8.384548&loc=48.995115%2C8.382761&hl=en&alt=0
@ -431,3 +431,354 @@ Feature: Slipways and Dedicated Turn Lanes
When I route I should get
| waypoints | route | turns |
| a,i | road,road,road | depart,fork slight left,arrive |
# The following tests are current false positives / false negatives #3199
@sliproads
# http://www.openstreetmap.org/#map=19/52.59847/13.14815
Scenario: Sliproad Detection
Given the node map
"""
a . . .
. .
b . . . . . . c . . . d
` . .
e . .
` . .
f . .
` . .
g i
` h .
"""
And the ways
| nodes | highway | name |
| abefgh | residential | Nachtigallensteig |
| bcd | residential | Kiebitzsteig |
| cg | residential | Haenflingsteig |
| hid | residential | Waldkauzsteig |
When I route I should get
| waypoints | route | turns |
| a,d | Nachtigallensteig,Kiebitzsteig,Kiebitzsteig | depart,turn left,arrive |
| a,h | Nachtigallensteig,Nachtigallensteig | depart,arrive |
@sliproads
Scenario: Not a obvious Sliproad
Given the node map
"""
d
.
s . a . . b . . c
` .
` e
.`
. `
f g
"""
And the ways
| nodes | highway | name | oneway |
| sabc | primary | sabc | |
| dbef | primary | dbef | yes |
| aeg | primary | aeg | yes |
When I route I should get
| waypoints | route | turns |
| s,f | sabc,aeg,dbef,dbef | depart,turn right,turn right,arrive |
@sliproads
Scenario: Through Street, not a Sliproad although obvious
Given the node map
"""
d
.
s . a . . b . . c
` .
` e
. `
. `
f g
"""
And the ways
| nodes | highway | name | oneway |
| sabc | primary | sabc | |
| dbef | primary | dbef | yes |
| aeg | primary | aeg | yes |
When I route I should get
| waypoints | route | turns |
| s,f | sabc,aeg,dbef,dbef | depart,turn right,turn right,arrive |
@sliproads
Scenario: Sliproad target turn is restricted
Given the node map
"""
d
.
s . a . . . . b . . c
` .
` .
` .
` .
`.
e
.`
f `
. ` g
"""
And the ways
| nodes | highway | name | oneway |
| sa | primary | sabc | |
| abc | primary | sabc | |
| dbe | primary | dbef | yes |
| ef | primary | dbef | |
| ae | primary | aeg | yes |
| eg | primary | aeg | |
# the reason we have to split ways at e is that otherwise we can't handle restrictions via e
And the relations
| type | way:from | way:to | node:via | restriction |
| restriction | ae | ef | e | no_right_turn |
When I route I should get
| waypoints | route | turns |
| s,f | sabc,dbef,dbef | depart,turn right,arrive |
| s,g | sabc,aeg,aeg | depart,turn right,arrive |
@sliproads
Scenario: Not a Sliproad, road not continuing straight
Given the node map
"""
d
.
s . a . . b . . c
` .
` e . . g
"""
And the ways
| nodes | highway | name | oneway |
| sabc | primary | sabc | |
| dbe | primary | dbe | yes |
| aeg | primary | aeg | yes |
When I route I should get
| waypoints | route | turns |
| s,c | sabc,sabc | depart,arrive |
| s,g | sabc,aeg,aeg | depart,turn right,arrive |
@sliproads
Scenario: Intersection too far away with Traffic Light shortly after initial split
Given the node map
"""
d
.
s . a . . . . . . . . . . . . . t . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . b . . c
` . . . . . . . . . . . .
` . . . . . . . . . . . .
` . . . . . . . . . . . .
` . . . . . . . . . . . .
` . . . . . . . . . . . .
` . . . . . . . . . . . .
` . . .
` e
.
f
.
"""
And the nodes
| node | highway |
| t | traffic_signals |
And the ways
| nodes | highway | name | oneway |
| satbc | primary | sabc | |
| dbef | primary | dbef | yes |
| ae | primary | ae | yes |
When I route I should get
| waypoints | route | turns |
| s,f | sabc,ae,dbef,dbef | depart,turn slight right,turn right,arrive |
@sliproads
Scenario: Traffic Signal on Sliproad
Given the node map
"""
d
.
s . a . . . . . b . . c
` .
` .
` .
t .
` .
e
.
.
f
"""
And the nodes
| node | highway |
| t | traffic_signals |
And the ways
| nodes | highway | name | oneway |
| sabc | primary | sabc | |
| dbe | primary | dbe | yes |
| ef | primary | ef | |
| ate | primary | ate | yes |
When I route I should get
| waypoints | route | turns |
| s,f | sabc,ef,ef | depart,turn right,arrive |
@sliproads
Scenario: Sliproad tagged as link
Given the node map
"""
d
.
s . a . . . . . b . . c
` .
` .
` .
` .
` .
e
.
.
f
"""
And the ways
| nodes | highway | name | oneway |
| sabc | motorway | sabc | |
| dbef | motorway | dbef | yes |
| ae | motorway_link | ae | yes |
When I route I should get
| waypoints | route | turns |
| s,f | sabc,dbef,dbef | depart,turn right,arrive |
@sliproads
Scenario: Sliproad with same-ish names
Given the node map
"""
d
.
s . a . . b . . c
` .
. e
..
.
f
.
t
"""
And the ways
| nodes | highway | name | ref | oneway |
| sabc | primary | main | | |
| dbe | primary | crossing | r0 | yes |
| eft | primary | crossing | r0;r1 | yes |
| af | primary | sliproad | | yes |
When I route I should get
| waypoints | route | turns |
| s,t | main,crossing,crossing | depart,turn right,arrive |
@sliproads
Scenario: Not a Sliproad, name mismatch
Given the node map
"""
d
.
s . a . . b . . c
` .
. e
. .
..
.
f
.
t
"""
And the ways
| nodes | highway | name | oneway |
| sabc | primary | main | |
| dbe | primary | top | yes |
| ef | primary | bottom | yes |
| ft | primary | away | yes |
| af | primary | sliproad | yes |
When I route I should get
| waypoints | route | turns |
| s,t | main,away,away | depart,turn right,arrive |
@sliproads
Scenario: Not a Sliproad, low road priority
Given the node map
"""
d
.
s . a . . b . . c
` .
. e
. .
..
.
f
.
t
"""
And the ways
# maxspeed otherwise service road will never be routed over and we won't see instructions
| nodes | highway | name | maxspeed | oneway |
| sabc | primary | main | 30 km/h | |
| dbe | primary | crossing | 30 km/h | yes |
| eft | primary | crossing | 30 km/h | yes |
| ft | primary | away | 30 km/h | yes |
| af | service | sliproad | 30 km/h | yes |
When I route I should get
| waypoints | route | turns |
| s,t | main,away,away | depart,turn right,arrive |
@sliproads
Scenario: Not a Sliproad, more than three roads at target intersection
Given the node map
"""
d
.
s . a . . b . . c
` .
. e
. .
..
. h
f .
. g
t
"""
And the ways
| nodes | highway | name | oneway |
| sabc | primary | main | |
| dbe | primary | top | yes |
| eft | primary | bottom | yes |
| fh | primary | another | |
| fg | primary | another | |
| af | primary | sliproad | yes |
When I route I should get
| waypoints | route | turns |
| s,g | main,sliproad,another,another | depart,turn right,turn left,arrive |

View File

@ -1,6 +1,9 @@
#ifndef OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HPP_
#define OSRM_EXTRACTOR_GUIDANCE_INTERSECTION_HPP_
#include <algorithm>
#include <functional>
#include <limits>
#include <string>
#include <type_traits>
#include <vector>
@ -10,6 +13,11 @@
#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 <boost/assert.hpp>
namespace osrm
{
namespace extractor
@ -60,7 +68,7 @@ struct IntersectionViewData : IntersectionShapeData
// 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:
// instructions themselves. An possible example can be described like this:
//
// aaa(2)aa
// a - bbbbb
@ -68,8 +76,7 @@ struct IntersectionViewData : IntersectionShapeData
//
// 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.
// turn-operation, but rather a name change.
//
// If this were a normal intersection with
//
@ -103,6 +110,8 @@ struct ConnectedRoad final : IntersectionViewData
// small helper function to print the content of a connected road
std::string toString(const ConnectedRoad &road);
// Intersections are sorted roads: [0] being the UTurn road, then from sharp right to sharp left.
using IntersectionShape = std::vector<IntersectionShapeData>;
// Common operations shared among IntersectionView and Intersections.
@ -117,6 +126,84 @@ template <typename Self> struct EnableIntersectionOps
return std::min_element(self()->begin(), self()->end(), 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
auto valid() const
{
if (self()->empty())
return false;
auto comp = [](const auto &lhs, const auto &rhs) { return lhs.CompareByAngle(rhs); };
const auto ordered = std::is_sorted(self()->begin(), self()->end(), comp);
if (!ordered)
return false;
const auto uturn = self()->operator[](0).angle < std::numeric_limits<double>::epsilon();
if (!uturn)
return false;
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); }
// Returns the right-most road at this intersection.
const auto &getRightmostRoad() const
{
return self()->size() > 1 ? self()->operator[](1) : self()->getUTurnRoad();
}
// Returns the left-most road at this intersection.
const auto &getLeftmostRoad() const
{
return self()->size() > 1 ? self()->back() : self()->getUTurnRoad();
}
// Can this be skipped over?
auto isTrafficSignalOrBarrier() const { return self()->size() == 2; }
// Checks if there is at least one road available (except UTurn road) on which to continue.
auto isDeadEnd() const
{
auto pred = [](const auto &road) { return road.entry_allowed; };
return !std::any_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);
}
// Returns the number of roads we can not enter at this intersection, respectively.
auto countNonEnterable() const { return self()->size() - self()->countEnterable(); }
private:
auto self() { return static_cast<Self *>(this); }
auto self() const { return static_cast<const Self *>(this); }
@ -126,28 +213,12 @@ struct IntersectionView final : std::vector<IntersectionViewData>, //
EnableIntersectionOps<IntersectionView> //
{
using Base = std::vector<IntersectionViewData>;
bool valid() const
{
return std::is_sorted(begin(), end(), std::mem_fn(&IntersectionViewData::CompareByAngle));
};
};
struct Intersection final : std::vector<ConnectedRoad>, //
EnableIntersectionOps<Intersection> //
{
using Base = std::vector<ConnectedRoad>;
/*
* 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
*/
bool valid() const;
// given all possible turns, which is the highest connected number of lanes per turn. This value
// is used, for example, during generation of intersections.
std::uint8_t getHighestConnectedLaneCount(const util::NodeBasedDynamicGraph &) const;
};
} // namespace guidance

View File

@ -35,6 +35,12 @@ class IntersectionGenerator
const std::vector<QueryNode> &node_info_list,
const CompressedEdgeContainer &compressed_edge_container);
// For a source node `a` and a via edge `ab` creates an intersection at target `b`.
//
// a . . . b . .
// .
// .
//
IntersectionView operator()(const NodeID nid, const EdgeID via_eid) const;
/*

View File

@ -3,15 +3,22 @@
#include "extractor/guidance/intersection.hpp"
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/guidance/node_based_graph_walker.hpp"
#include "extractor/query_node.hpp"
#include "extractor/suffix_table.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/guidance/name_announcements.hpp"
#include "util/name_table.hpp"
#include "util/node_based_graph.hpp"
#include <algorithm>
#include <cstddef>
#include <utility>
#include <vector>
#include <boost/optional.hpp>
namespace osrm
{
namespace extractor
@ -48,9 +55,7 @@ class IntersectionHandler
const util::NameTable &name_table;
const SuffixTable &street_name_suffix_table;
const IntersectionGenerator &intersection_generator;
// counts the number on allowed entry roads
std::size_t countValid(const Intersection &intersection) const;
const NodeBasedGraphWalker graph_walker; // for skipping traffic signal, distances etc.
// Decide on a basic turn types
TurnType::Enum findBasicTurnType(const EdgeID via_edge, const ConnectedRoad &candidate) const;
@ -59,7 +64,8 @@ class IntersectionHandler
// determining whether there is a road that can be seen as obvious turn in the presence of many
// other possible turns. The function will consider road categories and other inputs like the
// turn angles.
std::size_t findObviousTurn(const EdgeID via_edge, const Intersection &intersection) const;
template <typename IntersectionType> // works with Intersection and IntersectionView
std::size_t findObviousTurn(const EdgeID via_edge, const IntersectionType &intersection) const;
// Obvious turns can still take multiple forms. This function looks at the turn onto a road
// candidate when coming from a via_edge and determines the best instruction to emit.
@ -83,9 +89,457 @@ class IntersectionHandler
const std::size_t begin,
const std::size_t end) const;
// Checks the intersection for a through street connected to `intersection[index]`
bool isThroughStreet(const std::size_t index, const Intersection &intersection) const;
// See `getNextIntersection`
struct IntersectionViewAndNode final
{
IntersectionView intersection; // < actual intersection
NodeID node; // < node at this intersection
};
// Skips over artificial intersections i.e. traffic lights, barriers etc.
// Returns the next non-artificial intersection and its node in the node based
// graph if an intersection could be found or none otherwise.
//
// a ... tl ... b .. c
// .
// .
// d
//
// ^ at
// ^ via
//
// For this scenario returns intersection at `b` and `b`.
boost::optional<IntersectionHandler::IntersectionViewAndNode>
getNextIntersection(const NodeID at, const EdgeID via) const;
};
// Impl.
template <typename IntersectionType> // works with Intersection and IntersectionView
std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge,
const IntersectionType &intersection) const
{
using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData;
using osrm::util::angularDeviation;
// no obvious road
if (intersection.size() == 1)
return 0;
// a single non u-turn is obvious
if (intersection.size() == 2)
return 1;
const EdgeData &in_way_data = node_based_graph.GetEdgeData(via_edge);
// the strategy for picking the most obvious turn involves deciding between
// an overall best candidate and a best candidate that shares the same name
// as the in road, i.e. a continue road
std::size_t best_option = 0;
double best_option_deviation = 180;
std::size_t best_continue = 0;
double best_continue_deviation = 180;
/* helper functions */
const auto IsContinueRoad = [&](const EdgeData &way_data) {
return !util::guidance::requiresNameAnnounced(
in_way_data.name_id, way_data.name_id, name_table, street_name_suffix_table);
};
auto sameOrHigherPriority = [&in_way_data](const auto &way_data) {
return way_data.road_classification.GetPriority() <=
in_way_data.road_classification.GetPriority();
};
auto IsLowPriority = [](const auto &way_data) {
return way_data.road_classification.IsLowPriorityRoadClass();
};
// These two Compare functions are used for sifting out best option and continue
// candidates by evaluating all the ways in an intersection by what they share
// with the in way. Ideal candidates are of similar road class with the in way
// and are require relatively straight turns.
const auto RoadCompare = [&](const auto &lhs, const auto &rhs) {
const EdgeData &lhs_data = node_based_graph.GetEdgeData(lhs.eid);
const EdgeData &rhs_data = node_based_graph.GetEdgeData(rhs.eid);
const auto lhs_deviation = angularDeviation(lhs.angle, STRAIGHT_ANGLE);
const auto rhs_deviation = angularDeviation(rhs.angle, STRAIGHT_ANGLE);
const bool rhs_same_classification =
rhs_data.road_classification == in_way_data.road_classification;
const bool lhs_same_classification =
lhs_data.road_classification == in_way_data.road_classification;
const bool rhs_same_or_higher_priority = sameOrHigherPriority(rhs_data);
const bool rhs_low_priority = IsLowPriority(rhs_data);
const bool lhs_same_or_higher_priority = sameOrHigherPriority(lhs_data);
const bool lhs_low_priority = IsLowPriority(lhs_data);
auto left_tie = std::tie(lhs.entry_allowed,
lhs_same_or_higher_priority,
rhs_low_priority,
rhs_deviation,
lhs_same_classification);
auto right_tie = std::tie(rhs.entry_allowed,
rhs_same_or_higher_priority,
lhs_low_priority,
lhs_deviation,
rhs_same_classification);
return left_tie > right_tie;
};
const auto RoadCompareSameName = [&](const auto &lhs, const auto &rhs) {
const EdgeData &lhs_data = node_based_graph.GetEdgeData(lhs.eid);
const EdgeData &rhs_data = node_based_graph.GetEdgeData(rhs.eid);
const auto lhs_continues = IsContinueRoad(lhs_data);
const auto rhs_continues = IsContinueRoad(rhs_data);
const auto left_tie = std::tie(lhs.entry_allowed, lhs_continues);
const auto right_tie = std::tie(rhs.entry_allowed, rhs_continues);
return left_tie > right_tie || (left_tie == right_tie && RoadCompare(lhs, rhs));
};
auto best_option_it = std::min_element(begin(intersection), end(intersection), RoadCompare);
// min element should only return end() when vector is empty
BOOST_ASSERT(best_option_it != end(intersection));
best_option = std::distance(begin(intersection), best_option_it);
best_option_deviation = angularDeviation(intersection[best_option].angle, STRAIGHT_ANGLE);
const auto &best_option_data = node_based_graph.GetEdgeData(intersection[best_option].eid);
// Unless the in way is also low priority, it is generally undesirable to
// indicate that a low priority road is obvious
if (IsLowPriority(best_option_data) &&
best_option_data.road_classification != in_way_data.road_classification)
{
best_option = 0;
best_option_deviation = 180;
}
// double check if the way with the lowest deviation from straight is still be better choice
const auto straightest = intersection.findClosestTurn(STRAIGHT_ANGLE);
if (straightest != best_option_it)
{
const EdgeData &straightest_data = node_based_graph.GetEdgeData(straightest->eid);
double straightest_data_deviation = angularDeviation(straightest->angle, STRAIGHT_ANGLE);
const auto deviation_diff =
std::abs(best_option_deviation - straightest_data_deviation) > FUZZY_ANGLE_DIFFERENCE;
const auto not_ramp_class = !straightest_data.road_classification.IsRampClass();
const auto not_link_class = !straightest_data.road_classification.IsLinkClass();
if (deviation_diff && !IsLowPriority(straightest_data) && not_ramp_class &&
not_link_class && !IsContinueRoad(best_option_data))
{
best_option = std::distance(begin(intersection), straightest);
best_option_deviation =
angularDeviation(intersection[best_option].angle, STRAIGHT_ANGLE);
}
}
// No non-low priority roads? Declare no obvious turn
if (best_option == 0)
return 0;
auto best_continue_it =
std::min_element(begin(intersection), end(intersection), RoadCompareSameName);
const auto best_continue_data = node_based_graph.GetEdgeData(best_continue_it->eid);
if (IsContinueRoad(best_continue_data) ||
(in_way_data.name_id == EMPTY_NAMEID && best_continue_data.name_id == EMPTY_NAMEID))
{
best_continue = std::distance(begin(intersection), best_continue_it);
best_continue_deviation =
angularDeviation(intersection[best_continue].angle, STRAIGHT_ANGLE);
}
// if the best angle is going straight but the road is turning, declare no obvious turn
if (0 != best_continue && best_option != best_continue &&
best_option_deviation < MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
node_based_graph.GetEdgeData(intersection[best_continue].eid).road_classification ==
best_option_data.road_classification)
{
return 0;
}
// get a count of number of ways from that intersection that qualify to have
// continue instruction because they share a name with the approaching way
const std::int64_t continue_count =
count_if(++begin(intersection), end(intersection), [&](const auto &way) {
return IsContinueRoad(node_based_graph.GetEdgeData(way.eid));
});
const std::int64_t continue_count_valid =
count_if(++begin(intersection), end(intersection), [&](const auto &way) {
return IsContinueRoad(node_based_graph.GetEdgeData(way.eid)) && way.entry_allowed;
});
// checks if continue candidates are sharp turns
const bool all_continues_are_narrow = [&]() {
return std::count_if(begin(intersection), end(intersection), [&](const auto &road) {
const EdgeData &road_data = node_based_graph.GetEdgeData(road.eid);
const double &road_angle = angularDeviation(road.angle, STRAIGHT_ANGLE);
return IsContinueRoad(road_data) && (road_angle < NARROW_TURN_ANGLE);
}) == continue_count;
}();
// return true if the best_option candidate is more promising than the best_continue candidate
// otherwise return false, the best_continue candidate is more promising
const auto best_over_best_continue = [&]() {
// no continue road exists
if (best_continue == 0)
return true;
// we have multiple continues and not all are narrow. This suggests that
// the continue candidates are ambiguous
if (!all_continues_are_narrow && (continue_count >= 2 && intersection.size() >= 4))
return true;
// if the best continue is not narrow and we also have at least 2 possible choices, the
// intersection size does not matter anymore
if (continue_count_valid >= 2 && best_continue_deviation >= 2 * NARROW_TURN_ANGLE)
return true;
// continue data now most certainly exists
const auto &continue_data = node_based_graph.GetEdgeData(intersection[best_continue].eid);
// best_continue is obvious by road class
if (obviousByRoadClass(in_way_data.road_classification,
continue_data.road_classification,
best_option_data.road_classification))
return false;
// best_option is obvious by road class
if (obviousByRoadClass(in_way_data.road_classification,
best_option_data.road_classification,
continue_data.road_classification))
return true;
// the best_option deviation is very straight and not a ramp
if (best_option_deviation < best_continue_deviation &&
best_option_deviation < FUZZY_ANGLE_DIFFERENCE &&
!best_option_data.road_classification.IsRampClass())
return true;
// the continue road is of a lower priority, while the road continues on the same priority
// with a better angle
if (best_option_deviation < best_continue_deviation &&
in_way_data.road_classification == best_option_data.road_classification &&
continue_data.road_classification.GetPriority() >
best_option_data.road_classification.GetPriority())
return true;
return false;
}();
if (best_over_best_continue)
{
// Find left/right deviation
// skipping over service roads
const std::size_t left_index = [&]() {
const auto index_candidate = (best_option + 1) % intersection.size();
if (index_candidate == 0)
return index_candidate;
const auto &candidate_data =
node_based_graph.GetEdgeData(intersection[index_candidate].eid);
if (obviousByRoadClass(in_way_data.road_classification,
best_option_data.road_classification,
candidate_data.road_classification))
return (index_candidate + 1) % intersection.size();
else
return index_candidate;
}();
const auto right_index = [&]() {
BOOST_ASSERT(best_option > 0);
const auto index_candidate = best_option - 1;
if (index_candidate == 0)
return index_candidate;
const auto candidate_data =
node_based_graph.GetEdgeData(intersection[index_candidate].eid);
if (obviousByRoadClass(in_way_data.road_classification,
best_option_data.road_classification,
candidate_data.road_classification))
return index_candidate - 1;
else
return index_candidate;
}();
const double left_deviation =
angularDeviation(intersection[left_index].angle, STRAIGHT_ANGLE);
const double right_deviation =
angularDeviation(intersection[right_index].angle, STRAIGHT_ANGLE);
// return best_option candidate if it is nearly straight and distinct from the nearest other
// out
// way
if (best_option_deviation < MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
std::min(left_deviation, right_deviation) > FUZZY_ANGLE_DIFFERENCE)
return best_option;
const auto &left_data = node_based_graph.GetEdgeData(intersection[left_index].eid);
const auto &right_data = node_based_graph.GetEdgeData(intersection[right_index].eid);
const bool obvious_to_left =
left_index == 0 || obviousByRoadClass(in_way_data.road_classification,
best_option_data.road_classification,
left_data.road_classification);
const bool obvious_to_right =
right_index == 0 || obviousByRoadClass(in_way_data.road_classification,
best_option_data.road_classification,
right_data.road_classification);
// if the best_option turn isn't narrow, but there is a nearly straight turn, we don't
// consider the
// turn obvious
const auto check_narrow = [&intersection, best_option_deviation](const std::size_t index) {
return angularDeviation(intersection[index].angle, STRAIGHT_ANGLE) <=
FUZZY_ANGLE_DIFFERENCE &&
(best_option_deviation > NARROW_TURN_ANGLE || intersection[index].entry_allowed);
};
// other narrow turns?
if (check_narrow(right_index) && !obvious_to_right)
return 0;
if (check_narrow(left_index) && !obvious_to_left)
return 0;
// checks if a given way in the intersection is distinct enough from the best_option
// candidate
const auto isDistinct = [&](const std::size_t index, const double deviation) {
/*
If the neighbor is not possible to enter, we allow for a lower
distinction rate. If the road category is smaller, its also adjusted. Only
roads of the same priority require the full distinction ratio.
*/
const auto &best_option_data =
node_based_graph.GetEdgeData(intersection[best_option].eid);
const auto adjusted_distinction_ratio = [&]() {
// not allowed competitors are easily distinct
if (!intersection[index].entry_allowed)
return 0.7 * DISTINCTION_RATIO;
// a bit less obvious are road classes
else if (in_way_data.road_classification == best_option_data.road_classification &&
best_option_data.road_classification.GetPriority() <
node_based_graph.GetEdgeData(intersection[index].eid)
.road_classification.GetPriority())
return 0.8 * DISTINCTION_RATIO;
// if road classes are the same, we use the full ratio
else
return DISTINCTION_RATIO;
}();
return index == 0 || deviation / best_option_deviation >= adjusted_distinction_ratio ||
(deviation <= NARROW_TURN_ANGLE && !intersection[index].entry_allowed);
};
const bool distinct_to_left = isDistinct(left_index, left_deviation);
const bool distinct_to_right = isDistinct(right_index, right_deviation);
// Well distinct turn that is nearly straight
if ((distinct_to_left || obvious_to_left) && (distinct_to_right || obvious_to_right))
return best_option;
}
else
{
const auto &continue_data = node_based_graph.GetEdgeData(intersection[best_continue].eid);
if (std::abs(best_continue_deviation) < 1)
return best_continue;
// check if any other similar best continues exist
std::size_t i, last = intersection.size();
for (i = 1; i < last; ++i)
{
if (i == best_continue || !intersection[i].entry_allowed)
continue;
const auto &turn_data = node_based_graph.GetEdgeData(intersection[i].eid);
const bool is_obvious_by_road_class =
obviousByRoadClass(in_way_data.road_classification,
continue_data.road_classification,
turn_data.road_classification);
// if the main road is obvious by class, we ignore the current road as a potential
// prevention of obviousness
if (is_obvious_by_road_class)
continue;
// continuation could be grouped with a straight turn and the turning road is a ramp
if (turn_data.road_classification.IsRampClass() &&
best_continue_deviation < GROUP_ANGLE &&
!continue_data.road_classification.IsRampClass())
continue;
// perfectly straight turns prevent obviousness
const auto turn_deviation = angularDeviation(intersection[i].angle, STRAIGHT_ANGLE);
if (turn_deviation < FUZZY_ANGLE_DIFFERENCE)
return 0;
const auto deviation_ratio = turn_deviation / best_continue_deviation;
// in comparison to normal deviations, a continue road can offer a smaller distinction
// ratio. Other roads close to the turn angle are not as obvious, if one road continues.
if (deviation_ratio < DISTINCTION_RATIO / 1.5)
return 0;
/* in comparison to another continuing road, we need a better distinction. This prevents
situations where the turn is probably less obvious. An example are places that have a
road with the same name entering/exiting:
d
/
/
a -- b
\
\
c
*/
const auto same_name = !util::guidance::requiresNameAnnounced(
turn_data.name_id, continue_data.name_id, name_table, street_name_suffix_table);
if (same_name && deviation_ratio < 1.5 * DISTINCTION_RATIO)
return 0;
}
// Segregated intersections can result in us finding an obvious turn, even though its only
// obvious due to a very short segment in between. So if the segment coming in is very
// short, we check the previous intersection for other continues in the opposite bearing.
const auto node_at_intersection = node_based_graph.GetTarget(via_edge);
const double constexpr MAX_COLLAPSE_DISTANCE = 30;
const auto distance_at_u_turn = intersection[0].segment_length;
if (distance_at_u_turn < MAX_COLLAPSE_DISTANCE)
{
// this request here actually goes against the direction of the ingoing edgeid. This can
// 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(
node_at_intersection, intersection[0].eid);
return intersection_generator.GetConnectedRoads(new_node, turn_edge);
}();
if (new_node != node_at_intersection)
{
const auto continue_road = intersection[best_continue];
for (const auto &comparison_road : previous_intersection)
{
// since we look at the intersection in the wrong direction, a similar angle
// actually represents a near 180 degree different in bearings between the two
// roads. So if there is a road that is enterable in the opposite direction just
// prior, a turn is not obvious
const auto &turn_data = node_based_graph.GetEdgeData(comparison_road.eid);
if (angularDeviation(comparison_road.angle, STRAIGHT_ANGLE) > GROUP_ANGLE &&
angularDeviation(comparison_road.angle, continue_road.angle) <
FUZZY_ANGLE_DIFFERENCE &&
!turn_data.reversed && continue_data.CanCombineWith(turn_data))
return 0;
}
}
}
return best_continue;
}
return 0;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm

View File

@ -4,6 +4,7 @@
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/intersection_generator.hpp"
#include "util/coordinate.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/node_based_graph.hpp"
#include "util/typedefs.hpp"
@ -41,7 +42,7 @@ class NodeBasedGraphWalker
boost::optional<std::pair<NodeID, EdgeID>> TraverseRoad(NodeID starting_at_node_id,
EdgeID following_edge_id,
accumulator_type &accumulator,
const selector_type &selector);
const selector_type &selector) const;
private:
const util::NodeBasedDynamicGraph &node_based_graph;
@ -139,7 +140,7 @@ boost::optional<std::pair<NodeID, EdgeID>>
NodeBasedGraphWalker::TraverseRoad(NodeID current_node_id,
EdgeID current_edge_id,
accumulator_type &accumulator,
const selector_type &selector)
const selector_type &selector) const
{
/*
* since graph hopping is used in many ways, we don't generate an adjusted intersection
@ -190,6 +191,60 @@ NodeBasedGraphWalker::TraverseRoad(NodeID current_node_id,
return {};
}
struct SkipTrafficSignalBarrierRoadSelector
{
boost::optional<EdgeID> operator()(const NodeID,
const EdgeID,
const IntersectionView &intersection,
const util::NodeBasedDynamicGraph &) const
{
if (intersection.isTrafficSignalOrBarrier())
{
return boost::make_optional(intersection[1].eid);
}
else
{
return boost::none;
}
}
};
struct DistanceToNextIntersectionAccumulator
{
DistanceToNextIntersectionAccumulator(
const extractor::guidance::CoordinateExtractor &extractor_,
const util::NodeBasedDynamicGraph &graph_,
const double threshold)
: extractor{extractor_}, graph{graph_}, threshold{threshold}
{
}
bool terminate()
{
if (distance > threshold)
{
too_far_away = true;
return true;
}
return false;
}
void update(const NodeID start, const EdgeID onto, const NodeID)
{
using namespace util::coordinate_calculation;
const auto coords = extractor.GetForwardCoordinatesAlongRoad(start, onto);
distance += getLength(coords, &haversineDistance);
}
const extractor::guidance::CoordinateExtractor &extractor;
const util::NodeBasedDynamicGraph &graph;
const double threshold;
bool too_far_away = false;
double distance = 0.;
};
} // namespace guidance
} // namespace extractor
} // namespace osrm

View File

@ -11,6 +11,8 @@
#include <vector>
#include <boost/optional.hpp>
namespace osrm
{
namespace extractor
@ -20,7 +22,7 @@ namespace guidance
// Intersection handlers deal with all issues related to intersections.
// They assign appropriate turn operations to the TurnOperations.
class SliproadHandler : public IntersectionHandler
class SliproadHandler final : public IntersectionHandler
{
public:
SliproadHandler(const IntersectionGenerator &intersection_generator,
@ -40,6 +42,37 @@ class SliproadHandler : public IntersectionHandler
Intersection operator()(const NodeID nid,
const EdgeID via_eid,
Intersection intersection) const override final;
private:
boost::optional<std::size_t> getObviousIndexWithSliproads(const EdgeID from,
const Intersection &intersection,
const NodeID at) const;
// Next intersection from `start` onto `onto` is too far away for a Siproad scenario
bool nextIntersectionIsTooFarAway(const NodeID start, const EdgeID onto) const;
// Through street: does a road continue with from's name at the intersection
bool isThroughStreet(const EdgeID from, const IntersectionView &intersection) const;
// Does the road from `current` to `next` continue
bool roadContinues(const EdgeID current, const EdgeID next) const;
// Is the area under the triangle a valid Sliproad triangle
bool isValidSliproadArea(const double max_area, const NodeID, const NodeID, const NodeID) const;
// Is the Sliproad a link the both roads it shortcuts must not be links
bool isValidSliproadLink(const IntersectionViewData &sliproad,
const IntersectionViewData &first,
const IntersectionViewData &second) const;
// Could a Sliproad reach this intersection?
static bool canBeTargetOfSliproad(const IntersectionView &intersection);
// Scales a threshold based on the underlying road classification.
// Example: a 100 m threshold for a highway if different on living streets.
// The return value is guaranteed to not be larger than `threshold`.
static double scaledThresholdByRoadClass(const double max_threshold,
const RoadClassification &classification);
};
} // namespace guidance

View File

@ -1,13 +1,7 @@
#include "extractor/guidance/intersection.hpp"
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include <boost/assert.hpp>
#include <algorithm>
#include <functional>
#include <limits>
#include <string>
using osrm::util::angularDeviation;
@ -71,34 +65,6 @@ std::string toString(const ConnectedRoad &road)
return result;
}
bool Intersection::valid() const
{
return !empty() &&
std::is_sorted(begin(), end(), std::mem_fn(&ConnectedRoad::compareByAngle)) &&
operator[](0).angle < std::numeric_limits<double>::epsilon();
}
std::uint8_t
Intersection::getHighestConnectedLaneCount(const util::NodeBasedDynamicGraph &graph) const
{
BOOST_ASSERT(valid()); // non empty()
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 = *this | boost::adaptors::transformed(to_lane_count);
boost::range::find_if(view, extract_maximal_value);
return max_lanes;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm

View File

@ -1,9 +1,8 @@
#include "extractor/guidance/intersection_handler.hpp"
#include "extractor/guidance/constants.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/log.hpp"
#include "util/guidance/name_announcements.hpp"
#include "util/log.hpp"
#include <algorithm>
#include <cstddef>
@ -33,17 +32,11 @@ IntersectionHandler::IntersectionHandler(const util::NodeBasedDynamicGraph &node
const IntersectionGenerator &intersection_generator)
: node_based_graph(node_based_graph), node_info_list(node_info_list), name_table(name_table),
street_name_suffix_table(street_name_suffix_table),
intersection_generator(intersection_generator)
intersection_generator(intersection_generator),
graph_walker(node_based_graph, intersection_generator)
{
}
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::Enum IntersectionHandler::findBasicTurnType(const EdgeID via_edge,
const ConnectedRoad &road) const
{
@ -371,423 +364,44 @@ bool IntersectionHandler::isThroughStreet(const std::size_t index,
return false;
}
std::size_t IntersectionHandler::findObviousTurn(const EdgeID via_edge,
const Intersection &intersection) const
boost::optional<IntersectionHandler::IntersectionViewAndNode>
IntersectionHandler::getNextIntersection(const NodeID at, const EdgeID via) const
{
// no obvious road
if (intersection.size() == 1)
return 0;
// We use the intersection generator to jump over traffic signals, barriers. The intersection
// generater takes a starting node and a corresponding edge starting at this node. It returns
// the next non-artificial intersection writing as out param. the source node and the edge
// for which the target is the next intersection.
//
// . .
// a . . tl . . c .
// . .
//
// e0 ^ ^ e1
//
// Starting at node `a` via edge `e0` the intersection generator returns the intersection at `c`
// writing `tl` (traffic signal) node and the edge `e1` which has the intersection as target.
// a single non u-turn is obvious
if (intersection.size() == 2)
return 1;
NodeID node = SPECIAL_NODEID;
EdgeID edge = SPECIAL_EDGEID;
const EdgeData &in_way_data = node_based_graph.GetEdgeData(via_edge);
std::tie(node, edge) = intersection_generator.SkipDegreeTwoNodes(at, via);
auto intersection = intersection_generator(node, edge);
// the strategy for picking the most obvious turn involves deciding between
// an overall best candidate and a best candidate that shares the same name
// as the in road, i.e. a continue road
std::size_t best_option = 0;
double best_option_deviation = 180;
std::size_t best_continue = 0;
double best_continue_deviation = 180;
/* helper functions */
const auto IsContinueRoad = [&](const EdgeData &way_data) {
return !util::guidance::requiresNameAnnounced(
in_way_data.name_id, way_data.name_id, name_table, street_name_suffix_table);
};
auto sameOrHigherPriority = [&in_way_data](const auto &way_data) {
return way_data.road_classification.GetPriority() <=
in_way_data.road_classification.GetPriority();
};
auto IsLowPriority = [](const auto &way_data) {
return way_data.road_classification.IsLowPriorityRoadClass();
};
// These two Compare functions are used for sifting out best option and continue
// candidates by evaluating all the ways in an intersection by what they share
// with the in way. Ideal candidates are of similar road class with the in way
// and are require relatively straight turns.
const auto RoadCompare = [&](const ConnectedRoad &lhs, const ConnectedRoad &rhs) {
const EdgeData &lhs_data = node_based_graph.GetEdgeData(lhs.eid);
const EdgeData &rhs_data = node_based_graph.GetEdgeData(rhs.eid);
const auto lhs_deviation = angularDeviation(lhs.angle, STRAIGHT_ANGLE);
const auto rhs_deviation = angularDeviation(rhs.angle, STRAIGHT_ANGLE);
const bool rhs_same_classification =
rhs_data.road_classification == in_way_data.road_classification;
const bool lhs_same_classification =
lhs_data.road_classification == in_way_data.road_classification;
const bool rhs_same_or_higher_priority = sameOrHigherPriority(rhs_data);
const bool rhs_low_priority = IsLowPriority(rhs_data);
const bool lhs_same_or_higher_priority = sameOrHigherPriority(lhs_data);
const bool lhs_low_priority = IsLowPriority(lhs_data);
auto left_tie = std::tie(lhs.entry_allowed,
lhs_same_or_higher_priority,
rhs_low_priority,
rhs_deviation,
lhs_same_classification);
auto right_tie = std::tie(rhs.entry_allowed,
rhs_same_or_higher_priority,
lhs_low_priority,
lhs_deviation,
rhs_same_classification);
return left_tie > right_tie;
};
const auto RoadCompareSameName = [&](const ConnectedRoad &lhs, const ConnectedRoad &rhs) {
const EdgeData &lhs_data = node_based_graph.GetEdgeData(lhs.eid);
const EdgeData &rhs_data = node_based_graph.GetEdgeData(rhs.eid);
const auto lhs_continues = IsContinueRoad(lhs_data);
const auto rhs_continues = IsContinueRoad(rhs_data);
const auto left_tie = std::tie(lhs.entry_allowed, lhs_continues);
const auto right_tie = std::tie(rhs.entry_allowed, rhs_continues);
return left_tie > right_tie || (left_tie == right_tie && RoadCompare(lhs, rhs));
};
auto best_option_it = std::min_element(begin(intersection), end(intersection), RoadCompare);
// min element should only return end() when vector is empty
BOOST_ASSERT(best_option_it != end(intersection));
best_option = std::distance(begin(intersection), best_option_it);
best_option_deviation = angularDeviation(intersection[best_option].angle, STRAIGHT_ANGLE);
const auto &best_option_data = node_based_graph.GetEdgeData(intersection[best_option].eid);
// Unless the in way is also low priority, it is generally undesirable to
// indicate that a low priority road is obvious
if (IsLowPriority(best_option_data) &&
best_option_data.road_classification != in_way_data.road_classification)
// This should never happen, guard against nevertheless
if (node == SPECIAL_NODEID || edge == SPECIAL_EDGEID)
{
best_option = 0;
best_option_deviation = 180;
return boost::none;
}
// double check if the way with the lowest deviation from straight is still be better choice
const auto straightest = intersection.findClosestTurn(STRAIGHT_ANGLE);
if (straightest != best_option_it)
auto intersection_node = node_based_graph.GetTarget(edge);
if (intersection.size() <= 2 || intersection.isTrafficSignalOrBarrier())
{
const EdgeData &straightest_data = node_based_graph.GetEdgeData(straightest->eid);
double straightest_data_deviation = angularDeviation(straightest->angle, STRAIGHT_ANGLE);
const auto deviation_diff =
std::abs(best_option_deviation - straightest_data_deviation) > FUZZY_ANGLE_DIFFERENCE;
const auto not_ramp_class = !straightest_data.road_classification.IsRampClass();
const auto not_link_class = !straightest_data.road_classification.IsLinkClass();
if (deviation_diff && !IsLowPriority(straightest_data) && not_ramp_class &&
not_link_class && !IsContinueRoad(best_option_data))
{
best_option = std::distance(begin(intersection), straightest);
best_option_deviation =
angularDeviation(intersection[best_option].angle, STRAIGHT_ANGLE);
}
return boost::none;
}
// No non-low priority roads? Declare no obvious turn
if (best_option == 0)
return 0;
auto best_continue_it =
std::min_element(begin(intersection), end(intersection), RoadCompareSameName);
const auto best_continue_data = node_based_graph.GetEdgeData(best_continue_it->eid);
if (IsContinueRoad(best_continue_data) ||
(in_way_data.name_id == EMPTY_NAMEID && best_continue_data.name_id == EMPTY_NAMEID))
{
best_continue = std::distance(begin(intersection), best_continue_it);
best_continue_deviation =
angularDeviation(intersection[best_continue].angle, STRAIGHT_ANGLE);
}
// if the best angle is going straight but the road is turning, declare no obvious turn
if (0 != best_continue && best_option != best_continue &&
best_option_deviation < MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
node_based_graph.GetEdgeData(intersection[best_continue].eid).road_classification ==
best_option_data.road_classification)
{
return 0;
}
// get a count of number of ways from that intersection that qualify to have
// continue instruction because they share a name with the approaching way
const std::int64_t continue_count =
count_if(++begin(intersection), end(intersection), [&](const ConnectedRoad &way) {
return IsContinueRoad(node_based_graph.GetEdgeData(way.eid));
});
const std::int64_t continue_count_valid =
count_if(++begin(intersection), end(intersection), [&](const ConnectedRoad &way) {
return IsContinueRoad(node_based_graph.GetEdgeData(way.eid)) && way.entry_allowed;
});
// checks if continue candidates are sharp turns
const bool all_continues_are_narrow = [&]() {
return std::count_if(
begin(intersection), end(intersection), [&](const ConnectedRoad &road) {
const EdgeData &road_data = node_based_graph.GetEdgeData(road.eid);
const double &road_angle = angularDeviation(road.angle, STRAIGHT_ANGLE);
return IsContinueRoad(road_data) && (road_angle < NARROW_TURN_ANGLE);
}) == continue_count;
}();
// return true if the best_option candidate is more promising than the best_continue candidate
// otherwise return false, the best_continue candidate is more promising
const auto best_over_best_continue = [&]() {
// no continue road exists
if (best_continue == 0)
return true;
// we have multiple continues and not all are narrow. This suggests that
// the continue candidates are ambiguous
if (!all_continues_are_narrow && (continue_count >= 2 && intersection.size() >= 4))
return true;
// if the best continue is not narrow and we also have at least 2 possible choices, the
// intersection size does not matter anymore
if (continue_count_valid >= 2 && best_continue_deviation >= 2 * NARROW_TURN_ANGLE)
return true;
// continue data now most certainly exists
const auto &continue_data = node_based_graph.GetEdgeData(intersection[best_continue].eid);
// best_continue is obvious by road class
if (obviousByRoadClass(in_way_data.road_classification,
continue_data.road_classification,
best_option_data.road_classification))
return false;
// best_option is obvious by road class
if (obviousByRoadClass(in_way_data.road_classification,
best_option_data.road_classification,
continue_data.road_classification))
return true;
// the best_option deviation is very straight and not a ramp
if (best_option_deviation < best_continue_deviation &&
best_option_deviation < FUZZY_ANGLE_DIFFERENCE &&
!best_option_data.road_classification.IsRampClass())
return true;
// the continue road is of a lower priority, while the road continues on the same priority
// with a better angle
if (best_option_deviation < best_continue_deviation &&
in_way_data.road_classification == best_option_data.road_classification &&
continue_data.road_classification.GetPriority() >
best_option_data.road_classification.GetPriority())
return true;
return false;
}();
if (best_over_best_continue)
{
// Find left/right deviation
// skipping over service roads
const std::size_t left_index = [&]() {
const auto index_candidate = (best_option + 1) % intersection.size();
if (index_candidate == 0)
return index_candidate;
const auto &candidate_data =
node_based_graph.GetEdgeData(intersection[index_candidate].eid);
if (obviousByRoadClass(in_way_data.road_classification,
best_option_data.road_classification,
candidate_data.road_classification))
return (index_candidate + 1) % intersection.size();
else
return index_candidate;
}();
const auto right_index = [&]() {
BOOST_ASSERT(best_option > 0);
const auto index_candidate = best_option - 1;
if (index_candidate == 0)
return index_candidate;
const auto candidate_data =
node_based_graph.GetEdgeData(intersection[index_candidate].eid);
if (obviousByRoadClass(in_way_data.road_classification,
best_option_data.road_classification,
candidate_data.road_classification))
return index_candidate - 1;
else
return index_candidate;
}();
const double left_deviation =
angularDeviation(intersection[left_index].angle, STRAIGHT_ANGLE);
const double right_deviation =
angularDeviation(intersection[right_index].angle, STRAIGHT_ANGLE);
// return best_option candidate if it is nearly straight and distinct from the nearest other
// out
// way
if (best_option_deviation < MAXIMAL_ALLOWED_NO_TURN_DEVIATION &&
std::min(left_deviation, right_deviation) > FUZZY_ANGLE_DIFFERENCE)
return best_option;
const auto &left_data = node_based_graph.GetEdgeData(intersection[left_index].eid);
const auto &right_data = node_based_graph.GetEdgeData(intersection[right_index].eid);
const bool obvious_to_left =
left_index == 0 || obviousByRoadClass(in_way_data.road_classification,
best_option_data.road_classification,
left_data.road_classification);
const bool obvious_to_right =
right_index == 0 || obviousByRoadClass(in_way_data.road_classification,
best_option_data.road_classification,
right_data.road_classification);
// if the best_option turn isn't narrow, but there is a nearly straight turn, we don't
// consider the
// turn obvious
const auto check_narrow = [&intersection, best_option_deviation](const std::size_t index) {
return angularDeviation(intersection[index].angle, STRAIGHT_ANGLE) <=
FUZZY_ANGLE_DIFFERENCE &&
(best_option_deviation > NARROW_TURN_ANGLE || intersection[index].entry_allowed);
};
// other narrow turns?
if (check_narrow(right_index) && !obvious_to_right)
return 0;
if (check_narrow(left_index) && !obvious_to_left)
return 0;
// checks if a given way in the intersection is distinct enough from the best_option
// candidate
const auto isDistinct = [&](const std::size_t index, const double deviation) {
/*
If the neighbor is not possible to enter, we allow for a lower
distinction rate. If the road category is smaller, its also adjusted. Only
roads of the same priority require the full distinction ratio.
*/
const auto &best_option_data =
node_based_graph.GetEdgeData(intersection[best_option].eid);
const auto adjusted_distinction_ratio = [&]() {
// not allowed competitors are easily distinct
if (!intersection[index].entry_allowed)
return 0.7 * DISTINCTION_RATIO;
// a bit less obvious are road classes
else if (in_way_data.road_classification == best_option_data.road_classification &&
best_option_data.road_classification.GetPriority() <
node_based_graph.GetEdgeData(intersection[index].eid)
.road_classification.GetPriority())
return 0.8 * DISTINCTION_RATIO;
// if road classes are the same, we use the full ratio
else
return DISTINCTION_RATIO;
}();
return index == 0 || deviation / best_option_deviation >= adjusted_distinction_ratio ||
(deviation <= NARROW_TURN_ANGLE && !intersection[index].entry_allowed);
};
const bool distinct_to_left = isDistinct(left_index, left_deviation);
const bool distinct_to_right = isDistinct(right_index, right_deviation);
// Well distinct turn that is nearly straight
if ((distinct_to_left || obvious_to_left) && (distinct_to_right || obvious_to_right))
return best_option;
}
else
{
const auto &continue_data = node_based_graph.GetEdgeData(intersection[best_continue].eid);
if (std::abs(best_continue_deviation) < 1)
return best_continue;
// check if any other similar best continues exist
std::size_t i, last = intersection.size();
for (i = 1; i < last; ++i)
{
if (i == best_continue || !intersection[i].entry_allowed)
continue;
const auto &turn_data = node_based_graph.GetEdgeData(intersection[i].eid);
const bool is_obvious_by_road_class =
obviousByRoadClass(in_way_data.road_classification,
continue_data.road_classification,
turn_data.road_classification);
// if the main road is obvious by class, we ignore the current road as a potential
// prevention of obviousness
if (is_obvious_by_road_class)
continue;
// continuation could be grouped with a straight turn and the turning road is a ramp
if (turn_data.road_classification.IsRampClass() &&
best_continue_deviation < GROUP_ANGLE &&
!continue_data.road_classification.IsRampClass())
continue;
// perfectly straight turns prevent obviousness
const auto turn_deviation = angularDeviation(intersection[i].angle, STRAIGHT_ANGLE);
if (turn_deviation < FUZZY_ANGLE_DIFFERENCE)
return 0;
const auto deviation_ratio = turn_deviation / best_continue_deviation;
// in comparison to normal deviations, a continue road can offer a smaller distinction
// ratio. Other roads close to the turn angle are not as obvious, if one road continues.
if (deviation_ratio < DISTINCTION_RATIO / 1.5)
return 0;
/* in comparison to another continuing road, we need a better distinction. This prevents
situations where the turn is probably less obvious. An example are places that have a
road with the same name entering/exiting:
d
/
/
a -- b
\
\
c
*/
const auto same_name = !util::guidance::requiresNameAnnounced(
turn_data.name_id, continue_data.name_id, name_table, street_name_suffix_table);
if (same_name && deviation_ratio < 1.5 * DISTINCTION_RATIO)
return 0;
}
// Segregated intersections can result in us finding an obvious turn, even though its only
// obvious due to a very short segment in between. So if the segment coming in is very
// short, we check the previous intersection for other continues in the opposite bearing.
const auto node_at_intersection = node_based_graph.GetTarget(via_edge);
const double constexpr MAX_COLLAPSE_DISTANCE = 30;
const auto distance_at_u_turn = intersection[0].segment_length;
if (distance_at_u_turn < MAX_COLLAPSE_DISTANCE)
{
// this request here actually goes against the direction of the ingoing edgeid. This can
// 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(
node_at_intersection, intersection[0].eid);
return intersection_generator.GetConnectedRoads(new_node, turn_edge);
}();
if (new_node != node_at_intersection)
{
const auto continue_road = intersection[best_continue];
for (const auto &comparison_road : previous_intersection)
{
// since we look at the intersection in the wrong direction, a similar angle
// actually represents a near 180 degree different in bearings between the two
// roads. So if there is a road that is enterable in the opposite direction just
// prior, a turn is not obvious
const auto &turn_data = node_based_graph.GetEdgeData(comparison_road.eid);
if (angularDeviation(comparison_road.angle, STRAIGHT_ANGLE) > GROUP_ANGLE &&
angularDeviation(comparison_road.angle, continue_road.angle) <
FUZZY_ANGLE_DIFFERENCE &&
!turn_data.reversed && continue_data.CanCombineWith(turn_data))
return 0;
}
}
}
return best_continue;
}
return 0;
return boost::make_optional(
IntersectionViewAndNode{std::move(intersection), intersection_node});
}
} // namespace guidance

View File

@ -193,7 +193,7 @@ Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection in
assignFork(via_eid, intersection[3], intersection[2], intersection[1]);
}
else if (countValid(intersection) > 0) // check whether turns exist at all
else if (intersection.countEnterable() > 0) // check whether turns exist at all
{
// FALLBACK, this should hopefully never be reached
return fallback(std::move(intersection));
@ -338,7 +338,7 @@ Intersection MotorwayHandler::fromMotorway(const EdgeID via_eid, Intersection in
Intersection MotorwayHandler::fromRamp(const EdgeID via_eid, Intersection intersection) const
{
auto num_valid_turns = countValid(intersection);
auto num_valid_turns = intersection.countEnterable();
// ramp straight into a motorway/ramp
if (intersection.size() == 2 && num_valid_turns == 1)
{

View File

@ -1,5 +1,4 @@
#include "extractor/guidance/node_based_graph_walker.hpp"
#include "util/coordinate_calculation.hpp"
#include <utility>
using osrm::util::angularDeviation;

View File

@ -4,11 +4,14 @@
#include "util/coordinate_calculation.hpp"
#include "util/guidance/name_announcements.hpp"
#include <cmath>
#include <algorithm>
#include <iterator>
#include <limits>
#include <boost/assert.hpp>
using EdgeData = osrm::util::NodeBasedDynamicGraph::EdgeData;
using osrm::extractor::guidance::getTurnDirection;
using osrm::util::angularDeviation;
@ -32,7 +35,8 @@ SliproadHandler::SliproadHandler(const IntersectionGenerator &intersection_gener
{
}
// included for interface reasons only
// The intersection has to connect a Sliproad, see the example scenario below:
// Intersection at `d`: Sliproad `bd` connecting `cd` and the road starting at `d`.
bool SliproadHandler::canProcess(const NodeID /*nid*/,
const EdgeID /*via_eid*/,
const Intersection &intersection) const
@ -40,261 +44,699 @@ bool SliproadHandler::canProcess(const NodeID /*nid*/,
return intersection.size() > 2;
}
// Detect sliproad b-d in the following example:
//
// .
// e
// .
// .
// a ... b .... c .
// ` .
// ` .
// ` .
// d
// .
//
// ^ a nid
// ^ ab source_edge_id
// ^ b intersection
Intersection SliproadHandler::
operator()(const NodeID, const EdgeID source_edge_id, Intersection intersection) const
operator()(const NodeID /*nid*/, const EdgeID source_edge_id, Intersection intersection) const
{
BOOST_ASSERT(intersection.size() > 2);
// Potential splitting / start of a Sliproad (b)
auto intersection_node_id = node_based_graph.GetTarget(source_edge_id);
// if there is no turn, there is no sliproad
if (intersection.size() <= 2)
return intersection;
// Road index prefering non-sliproads (bc)
auto obvious = getObviousIndexWithSliproads(source_edge_id, intersection, intersection_node_id);
const auto findNextIntersectionForRoad =
[&](const NodeID at_node, const ConnectedRoad &road, NodeID &output_node) {
auto intersection = intersection_generator(at_node, road.eid);
auto in_edge = road.eid;
// skip over traffic lights
// to prevent ending up in an endless loop, we remember all visited nodes. This is
// necessary, since merging of roads can actually create enterable loops of degree two
std::unordered_set<NodeID> visited_nodes;
auto node = at_node;
while (intersection.size() == 2 && visited_nodes.count(node) == 0)
{
visited_nodes.insert(node);
node = node_based_graph.GetTarget(in_edge);
if (node == at_node)
{
// we ended up in a loop without exit
output_node = SPECIAL_NODEID;
intersection.clear();
return intersection;
}
in_edge = intersection[1].eid;
output_node = node_based_graph.GetTarget(in_edge);
intersection = intersection_generator(node, in_edge);
}
if (intersection.size() <= 2)
{
output_node = SPECIAL_NODEID;
intersection.clear();
}
return intersection;
};
const std::size_t obvious_turn_index = [&]() -> std::size_t {
const auto index = findObviousTurn(source_edge_id, intersection);
if (index != 0)
return index;
else if (intersection.size() == 3 && intersection[1].instruction.type == TurnType::Fork)
{
// Forks themselves do not contain a `obvious` turn index. If we look at a fork that has
// a one-sided sliproad, however, the non-sliproad can be considered `obvious`. Here we
// assume that this could be the case and check for a potential sliproad/non-sliproad
// situation.
NodeID intersection_node_one = SPECIAL_NODEID, intersection_node_two = SPECIAL_NODEID;
const auto intersection_following_index_one = findNextIntersectionForRoad(
intersection_node_id, intersection[1], intersection_node_one);
const auto intersection_following_index_two = findNextIntersectionForRoad(
intersection_node_id, intersection[2], intersection_node_two);
// in case of broken roads, we return
if (intersection_following_index_one.empty() ||
intersection_following_index_two.empty())
return 0;
// In case of loops at the end of the road, we will arrive back at the intersection
// itself. If that is the case, the road is obviously not a sliproad.
// a sliproad has to enter a road without choice
const auto couldBeSliproad = [](const IntersectionView &intersection) {
if (intersection.size() != 3)
return false;
if ((intersection[1].entry_allowed && intersection[2].entry_allowed) ||
intersection[0].entry_allowed)
return false;
return true;
};
if (couldBeSliproad(intersection_following_index_one) &&
intersection_node_id != intersection_node_two)
return 2;
else if (couldBeSliproad(intersection_following_index_two) &&
intersection_node_id != intersection_node_one)
return 1;
else
return 0;
}
else
return 0;
}();
if (obvious_turn_index == 0)
return intersection;
const auto &next_road = intersection[obvious_turn_index];
const auto linkTest = [this, next_road](const ConnectedRoad &road) {
return !node_based_graph.GetEdgeData(road.eid).roundabout && road.entry_allowed &&
angularDeviation(road.angle, STRAIGHT_ANGLE) <= 2 * NARROW_TURN_ANGLE &&
!hasRoundaboutType(road.instruction) &&
angularDeviation(next_road.angle, road.angle) >
std::numeric_limits<double>::epsilon();
};
bool hasNarrow =
std::find_if(intersection.begin(), intersection.end(), linkTest) != intersection.end();
if (!hasNarrow)
return intersection;
const auto source_edge_data = node_based_graph.GetEdgeData(source_edge_id);
// check whether the continue road is valid
const auto check_valid = [this, source_edge_data](const ConnectedRoad &road) {
const auto road_edge_data = node_based_graph.GetEdgeData(road.eid);
// Test to see if the source edge and the one we're looking at are the same road
const auto same_name = !util::guidance::requiresNameAnnounced(
road_edge_data.name_id, source_edge_data.name_id, name_table, street_name_suffix_table);
return road_edge_data.road_classification == source_edge_data.road_classification &&
road_edge_data.name_id != EMPTY_NAMEID && source_edge_data.name_id != EMPTY_NAMEID &&
same_name && road.entry_allowed;
};
if (!check_valid(next_road))
return intersection;
// Threshold check, if the intersection is too far away, don't bother continuing
const auto coordinate_extractor = intersection_generator.GetCoordinateExtractor();
const auto next_road_length = util::coordinate_calculation::getLength(
coordinate_extractor.GetForwardCoordinatesAlongRoad(
node_based_graph.GetTarget(source_edge_id), next_road.eid),
&util::coordinate_calculation::haversineDistance);
if (next_road_length > MAX_SLIPROAD_THRESHOLD)
if (!obvious)
{
return intersection;
}
auto next_intersection_node = node_based_graph.GetTarget(next_road.eid);
const auto next_road_next_intersection =
findNextIntersectionForRoad(intersection_node_id, next_road, next_intersection_node);
// Potential non-sliproad road (bc), leading to the intersection (c) the Sliproad (bd) shortcuts
const auto &main_road = intersection[*obvious];
if (next_road_next_intersection.empty())
// The road leading to the intersection (bc) has to continue from our source
if (!roadContinues(source_edge_id, main_road.eid))
{
return intersection;
}
// Link-check for (bc) and later on (cd) which both are getting shortcutted by Sliproad
const auto is_potential_link = [this, main_road](const ConnectedRoad &road) {
if (!road.entry_allowed)
{
return false;
}
// Prevent from starting in or going onto a roundabout
auto onto_roundabout = hasRoundaboutType(road.instruction);
if (onto_roundabout)
{
return false;
}
// Narrow turn angle for road (bd) and guard against data issues (overlapping roads)
auto is_narrow = angularDeviation(road.angle, STRAIGHT_ANGLE) <= 2 * NARROW_TURN_ANGLE;
auto same_angle = angularDeviation(main_road.angle, road.angle) //
<= std::numeric_limits<double>::epsilon();
if (!is_narrow || same_angle)
{
return false;
}
const auto &road_data = node_based_graph.GetEdgeData(road.eid);
auto is_roundabout = road_data.roundabout;
if (is_roundabout)
{
return false;
}
return true;
};
if (!std::any_of(begin(intersection), end(intersection), is_potential_link))
{
return intersection;
}
// If the intersection is too far away, don't bother continuing
if (nextIntersectionIsTooFarAway(intersection_node_id, main_road.eid))
{
return intersection;
}
// Try to find the intersection at (c) which the Sliproad shortcuts
const auto main_road_intersection = getNextIntersection(intersection_node_id, main_road.eid);
if (!main_road_intersection)
{
return intersection;
}
if (main_road_intersection->intersection.isDeadEnd())
{
return intersection;
}
// If we are at a traffic loop at the end of a road, don't consider it a sliproad
if (intersection_node_id == next_intersection_node)
return intersection;
std::unordered_set<NameID> target_road_names;
for (const auto &road : next_road_next_intersection)
if (intersection_node_id == main_road_intersection->node)
{
const auto &target_data = node_based_graph.GetEdgeData(road.eid);
target_road_names.insert(target_data.name_id);
return intersection;
}
for (auto &road : intersection)
std::vector<NameID> target_road_name_ids;
target_road_name_ids.reserve(main_road_intersection->intersection.size());
for (const auto &road : main_road_intersection->intersection)
{
if (linkTest(road))
const auto &target_data = node_based_graph.GetEdgeData(road.eid);
target_road_name_ids.push_back(target_data.name_id);
}
auto sliproad_found = false;
// For all roads at the main intersection except the UTurn road: check Sliproad scenarios.
for (std::size_t road_index = 1, last = intersection.size(); road_index < last; ++road_index)
{
const auto index_left_of_main_road = (*obvious - 1) % intersection.size();
const auto index_right_of_main_road = (*obvious + 1) % intersection.size();
// Be strict and require the Sliproad to be either left or right of the main road.
if (road_index != index_left_of_main_road && road_index != index_right_of_main_road)
continue;
auto &sliproad = intersection[road_index]; // this is what we're checking and assigning to
const auto &sliproad_data = node_based_graph.GetEdgeData(sliproad.eid);
// Intersection is orderd: 0 is UTurn, then from sharp right to sharp left.
// We already have an obvious index (bc) for going straight-ish.
const auto is_right_sliproad_turn = road_index < *obvious;
const auto is_left_sliproad_turn = road_index > *obvious;
// Road at the intersection the main road leads onto which the sliproad arrives onto
const auto crossing_road = [&] {
if (is_left_sliproad_turn)
return main_road_intersection->intersection.getLeftmostRoad();
if (is_right_sliproad_turn)
return main_road_intersection->intersection.getRightmostRoad();
BOOST_ASSERT_MSG(false, "Sliproad is neither a left nor right of obvious main road");
return main_road_intersection->intersection.getLeftmostRoad();
}();
const auto &crossing_road_data = node_based_graph.GetEdgeData(crossing_road.eid);
// The crossing road at the main road's intersection must not be incoming-only
if (crossing_road_data.reversed)
{
EdgeID candidate_in = road.eid;
const auto target_intersection = [&](NodeID node) {
auto intersection = intersection_generator(node, candidate_in);
// skip over traffic lights
if (intersection.size() == 2)
{
node = node_based_graph.GetTarget(candidate_in);
candidate_in = intersection[1].eid;
intersection = intersection_generator(node, candidate_in);
}
return intersection;
}(intersection_node_id);
continue;
}
const auto link_data = node_based_graph.GetEdgeData(road.eid);
// Check if the road continues here
const bool is_through_street =
!target_intersection.empty() && link_data.name_id != EMPTY_NAMEID &&
target_intersection.end() !=
std::find_if(
target_intersection.begin() + 1,
target_intersection.end(),
[this, &link_data](const IntersectionViewData &road) {
const auto &road_edge_data = node_based_graph.GetEdgeData(road.eid);
// Discard service and other low priority roads - never Sliproad candidate
if (sliproad_data.road_classification.IsLowPriorityRoadClass())
{
continue;
}
const auto same_name =
road_edge_data.name_id != EMPTY_NAMEID &&
!util::guidance::requiresNameAnnounced(road_edge_data.name_id,
link_data.name_id,
name_table,
street_name_suffix_table);
// Incoming-only can never be a Sliproad
if (sliproad_data.reversed)
{
continue;
}
return same_name;
});
// This is what we know so far:
//
// .
// e
// .
// .
// a ... b .... c . < `main_road_intersection` is intersection at `c`
// ` .
// ` .
// ` .
// d < `target_intersection` is intersection at `d`
// . `sliproad_edge_target` is node `d`
// e
//
//
// ^ `sliproad` is `bd`
// ^ `intersection` is intersection at `b`
// if the sliproad candidate is a through street, we cannot handle it as a sliproad
if (is_through_street)
continue;
if (!is_potential_link(sliproad))
{
continue;
}
for (const auto &candidate_road : target_intersection)
// The Sliproad graph edge - in the following we use the graph walker to
// adjust this edge forward jumping over artificial intersections.
auto sliproad_edge = sliproad.eid;
// Starting out at the intersection and going onto the Sliproad we skip artificial
// degree two intersections and limit the max hop count in doing so.
IntersectionFinderAccumulator intersection_finder{10, intersection_generator};
const SkipTrafficSignalBarrierRoadSelector road_selector{};
(void)graph_walker.TraverseRoad(intersection_node_id, // start node
sliproad_edge, // onto edge
intersection_finder, // accumulator
road_selector); // selector
sliproad_edge = intersection_finder.via_edge_id;
const auto target_intersection = intersection_finder.intersection;
// Constrain the Sliproad's target to sliproad, outgoing, incoming from main intersection
if (target_intersection.size() != 3)
{
continue;
}
const NodeID sliproad_edge_target = node_based_graph.GetTarget(sliproad_edge);
// Distinct triangle nodes `bcd`
if (intersection_node_id == main_road_intersection->node ||
intersection_node_id == sliproad_edge_target ||
main_road_intersection->node == sliproad_edge_target)
{
continue;
}
// If the sliproad candidate is a through street, we cannot handle it as a sliproad.
if (isThroughStreet(sliproad_edge, target_intersection))
{
continue;
}
// The turn off of the Sliproad has to be obvious and a narrow turn
{
const auto index = findObviousTurn(sliproad_edge, target_intersection);
if (index == 0)
{
const auto &candidate_data = node_based_graph.GetEdgeData(candidate_road.eid);
if (target_road_names.count(candidate_data.name_id) > 0)
{
if (node_based_graph.GetTarget(candidate_road.eid) == next_intersection_node)
{
road.instruction.type = TurnType::Sliproad;
break;
}
else
{
const auto skip_traffic_light_intersection = intersection_generator(
node_based_graph.GetTarget(candidate_in), candidate_road.eid);
if (skip_traffic_light_intersection.size() == 2 &&
node_based_graph.GetTarget(skip_traffic_light_intersection[1].eid) ==
next_intersection_node)
{
continue;
}
road.instruction.type = TurnType::Sliproad;
break;
}
}
const auto onto = target_intersection[index];
const auto angle_deviation = angularDeviation(onto.angle, STRAIGHT_ANGLE);
const auto is_narrow_turn = angle_deviation <= 2 * NARROW_TURN_ANGLE;
if (!is_narrow_turn)
{
continue;
}
}
// Check for curvature. Depending on the turn's direction at `c`. Scenario for right turn:
//
// a ... b .... c . a ... b .... c . a ... b .... c .
// ` . ` . . . .
// ` . . . . .
// ` . .. . .
// d d . d
//
// Sliproad Not a Sliproad
{
const auto &extractor = intersection_generator.GetCoordinateExtractor();
const NodeID start = intersection_node_id; // b
const EdgeID edge = sliproad_edge; // bd
const auto coords = extractor.GetForwardCoordinatesAlongRoad(start, edge);
BOOST_ASSERT(coords.size() >= 2);
// Now keep start and end coordinate fix and check for curvature
const auto start_coord = coords.front();
const auto end_coord = coords.back();
const auto first = std::begin(coords) + 1;
const auto last = std::end(coords) - 1;
auto snuggles = false;
using namespace util::coordinate_calculation;
// In addition, if it's a right/left turn we expect the rightmost/leftmost
// turn at `c` to be more or less ~90 degree for a Sliproad scenario.
double deviation_from_straight = 0;
if (is_right_sliproad_turn)
{
snuggles = std::all_of(first, last, [=](auto each) { //
return !isCCW(start_coord, each, end_coord);
});
const auto rightmost = main_road_intersection->intersection.getRightmostRoad();
deviation_from_straight = angularDeviation(rightmost.angle, STRAIGHT_ANGLE);
}
else if (is_left_sliproad_turn)
{
snuggles = std::all_of(first, last, [=](auto each) { //
return isCCW(start_coord, each, end_coord);
});
const auto leftmost = main_road_intersection->intersection.getLeftmostRoad();
deviation_from_straight = angularDeviation(leftmost.angle, STRAIGHT_ANGLE);
}
// The data modelling for small Sliproads is not reliable enough.
// Only check for curvature and ~90 degree when it makes sense to do so.
const constexpr auto MIN_LENGTH = 3.;
const auto length = haversineDistance(node_info_list[intersection_node_id],
node_info_list[main_road_intersection->node]);
const double perpendicular_angle = 90 + FUZZY_ANGLE_DIFFERENCE;
if (length >= MIN_LENGTH)
{
if (!snuggles)
{
continue;
}
if (deviation_from_straight > perpendicular_angle)
{
continue;
}
}
}
// Check for area under triangle `bdc`.
//
// a ... b .... c .
// ` .
// ` .
// ` .
// d
//
const auto area_threshold = std::pow(
scaledThresholdByRoadClass(MAX_SLIPROAD_THRESHOLD, sliproad_data.road_classification),
2.);
if (!isValidSliproadArea(area_threshold,
intersection_node_id,
main_road_intersection->node,
sliproad_edge_target))
{
continue;
}
// Check all roads at `d` if one is connected to `c`, is so `bd` is Sliproad.
for (const auto &candidate_road : target_intersection)
{
const auto &candidate_data = node_based_graph.GetEdgeData(candidate_road.eid);
// Name mismatch: check roads at `c` and `d` for same name
const auto name_mismatch = [&](const NameID road_name_id) {
const auto unnamed = road_name_id == EMPTY_NAMEID;
return unnamed ||
util::guidance::requiresNameAnnounced(road_name_id, //
candidate_data.name_id, //
name_table, //
street_name_suffix_table); //
};
const auto candidate_road_name_mismatch = std::all_of(begin(target_road_name_ids), //
end(target_road_name_ids), //
name_mismatch); //
if (candidate_road_name_mismatch)
{
continue;
}
// If the Sliproad `bd` is a link, `bc` and `cd` must not be links.
if (!isValidSliproadLink(sliproad, main_road, candidate_road))
{
continue;
}
if (node_based_graph.GetTarget(candidate_road.eid) == main_road_intersection->node)
{
sliproad.instruction.type = TurnType::Sliproad;
sliproad_found = true;
break;
}
else
{
const auto skip_traffic_light_intersection = intersection_generator(
node_based_graph.GetTarget(sliproad_edge), candidate_road.eid);
if (skip_traffic_light_intersection.isTrafficSignalOrBarrier() &&
node_based_graph.GetTarget(skip_traffic_light_intersection[1].eid) ==
main_road_intersection->node)
{
sliproad.instruction.type = TurnType::Sliproad;
sliproad_found = true;
break;
}
}
}
}
if (next_road.instruction.type == TurnType::Fork)
// Now in case we found a Sliproad and assigned the corresponding type to the road,
// it could be that the intersection from which the Sliproad splits off was a Fork before.
// In those cases the obvious non-Sliproad is now obvious and we discard the Fork turn type.
if (sliproad_found && main_road.instruction.type == TurnType::Fork)
{
const auto &next_data = node_based_graph.GetEdgeData(next_road.eid);
const auto &source_edge_data = node_based_graph.GetEdgeData(source_edge_id);
const auto &main_road_data = node_based_graph.GetEdgeData(main_road.eid);
const auto same_name =
next_data.name_id != EMPTY_NAMEID && source_edge_data.name_id != EMPTY_NAMEID &&
!util::guidance::requiresNameAnnounced(
next_data.name_id, source_edge_data.name_id, name_table, street_name_suffix_table);
const auto same_name = source_edge_data.name_id != EMPTY_NAMEID && //
main_road_data.name_id != EMPTY_NAMEID && //
!util::guidance::requiresNameAnnounced(source_edge_data.name_id,
main_road_data.name_id,
name_table,
street_name_suffix_table); //
if (same_name)
{
if (angularDeviation(next_road.angle, STRAIGHT_ANGLE) < 5)
intersection[obvious_turn_index].instruction.type = TurnType::Suppressed;
if (angularDeviation(main_road.angle, STRAIGHT_ANGLE) < 5)
intersection[*obvious].instruction.type = TurnType::Suppressed;
else
intersection[obvious_turn_index].instruction.type = TurnType::Continue;
intersection[obvious_turn_index].instruction.direction_modifier =
getTurnDirection(intersection[obvious_turn_index].angle);
intersection[*obvious].instruction.type = TurnType::Continue;
intersection[*obvious].instruction.direction_modifier =
getTurnDirection(intersection[*obvious].angle);
}
else if (next_data.name_id != EMPTY_NAMEID)
else if (main_road_data.name_id != EMPTY_NAMEID)
{
intersection[obvious_turn_index].instruction.type = TurnType::NewName;
intersection[obvious_turn_index].instruction.direction_modifier =
getTurnDirection(intersection[obvious_turn_index].angle);
intersection[*obvious].instruction.type = TurnType::NewName;
intersection[*obvious].instruction.direction_modifier =
getTurnDirection(intersection[*obvious].angle);
}
else
{
intersection[obvious_turn_index].instruction.type = TurnType::Suppressed;
intersection[*obvious].instruction.type = TurnType::Suppressed;
}
}
return intersection;
}
// Implementation details
boost::optional<std::size_t> SliproadHandler::getObviousIndexWithSliproads(
const EdgeID from, const Intersection &intersection, const NodeID at) const
{
BOOST_ASSERT(from != SPECIAL_EDGEID);
BOOST_ASSERT(at != SPECIAL_NODEID);
// If a turn is obvious without taking Sliproads into account use this
const auto index = findObviousTurn(from, intersection);
if (index != 0)
{
return boost::make_optional(index);
}
// Otherwise check if the road is forking into two and one of them is a Sliproad;
// then the non-Sliproad is the obvious one.
if (intersection.size() != 3)
{
return boost::none;
}
const auto forking = intersection[1].instruction.type == TurnType::Fork &&
intersection[2].instruction.type == TurnType::Fork;
if (!forking)
{
return boost::none;
}
const auto first = getNextIntersection(at, intersection.getRightmostRoad().eid);
const auto second = getNextIntersection(at, intersection.getLeftmostRoad().eid);
if (!first || !second)
{
return boost::none;
}
if (first->intersection.isDeadEnd() || second->intersection.isDeadEnd())
{
return boost::none;
}
// In case of loops at the end of the road, we will arrive back at the intersection
// itself. If that is the case, the road is obviously not a sliproad.
if (canBeTargetOfSliproad(first->intersection) && at != second->node)
{
return boost::make_optional(std::size_t{2});
}
if (canBeTargetOfSliproad(second->intersection) && at != first->node)
{
return boost::make_optional(std::size_t{1});
}
return boost::none;
}
bool SliproadHandler::nextIntersectionIsTooFarAway(const NodeID start, const EdgeID onto) const
{
BOOST_ASSERT(start != SPECIAL_NODEID);
BOOST_ASSERT(onto != SPECIAL_EDGEID);
const auto &extractor = intersection_generator.GetCoordinateExtractor();
// Base max distance threshold on the current road class we're on
const auto &data = node_based_graph.GetEdgeData(onto);
const auto threshold = scaledThresholdByRoadClass(MAX_SLIPROAD_THRESHOLD, // <- scales down
data.road_classification);
DistanceToNextIntersectionAccumulator accumulator{extractor, node_based_graph, threshold};
const SkipTrafficSignalBarrierRoadSelector selector{};
(void)graph_walker.TraverseRoad(start, onto, accumulator, selector);
return accumulator.too_far_away;
}
bool SliproadHandler::isThroughStreet(const EdgeID from, const IntersectionView &intersection) const
{
BOOST_ASSERT(from != SPECIAL_EDGEID);
BOOST_ASSERT(!intersection.empty());
const auto &edge_name_id = node_based_graph.GetEdgeData(from).name_id;
auto first = begin(intersection) + 1; // Skip UTurn road
auto last = end(intersection);
auto same_name = [&](const auto &road) {
const auto &road_name_id = node_based_graph.GetEdgeData(road.eid).name_id;
return edge_name_id != EMPTY_NAMEID && //
road_name_id != EMPTY_NAMEID && //
!util::guidance::requiresNameAnnounced(edge_name_id,
road_name_id,
name_table,
street_name_suffix_table); //
};
return std::find_if(first, last, same_name) != last;
}
bool SliproadHandler::roadContinues(const EdgeID current, const EdgeID next) const
{
const auto &current_data = node_based_graph.GetEdgeData(current);
const auto &next_data = node_based_graph.GetEdgeData(next);
auto same_road_category = current_data.road_classification == next_data.road_classification;
auto same_travel_mode = current_data.travel_mode == next_data.travel_mode;
auto same_name = current_data.name_id != EMPTY_NAMEID && //
next_data.name_id != EMPTY_NAMEID && //
!util::guidance::requiresNameAnnounced(current_data.name_id,
next_data.name_id,
name_table,
street_name_suffix_table); //
const auto continues = same_road_category && same_travel_mode && same_name;
return continues;
}
bool SliproadHandler::isValidSliproadArea(const double max_area,
const NodeID a,
const NodeID b,
const NodeID c) const
{
using namespace util::coordinate_calculation;
const auto first = node_info_list[a];
const auto second = node_info_list[b];
const auto third = node_info_list[c];
const auto length = haversineDistance(first, second);
const auto heigth = haversineDistance(second, third);
const auto area = (length * heigth) / 2.;
// Everything below is data issue - there are some weird situations where
// nodes are really close to each other and / or tagging ist just plain off.
const constexpr auto MIN_SLIPROAD_AREA = 3.;
if (area < MIN_SLIPROAD_AREA || area > max_area)
{
return false;
}
return true;
}
bool SliproadHandler::isValidSliproadLink(const IntersectionViewData &sliproad,
const IntersectionViewData &first,
const IntersectionViewData &second) const
{
// If the Sliproad is not a link we don't care
const auto &sliproad_data = node_based_graph.GetEdgeData(sliproad.eid);
if (!sliproad_data.road_classification.IsLinkClass())
{
return true;
}
// otherwise neither the first road leading to the intersection we shortcut
const auto &first_road_data = node_based_graph.GetEdgeData(first.eid);
if (first_road_data.road_classification.IsLinkClass())
{
return false;
}
// nor the second road coming from the intersection we shotcut must be links
const auto &second_road_data = node_based_graph.GetEdgeData(second.eid);
if (second_road_data.road_classification.IsLinkClass())
{
return false;
}
return true;
}
bool SliproadHandler::canBeTargetOfSliproad(const IntersectionView &intersection)
{
// Example to handle:
// .
// a . . b .
// ` .
// ` .
// c < intersection
// .
//
// One outgoing two incoming
if (intersection.size() != 3)
{
return false;
}
const auto backwards = intersection[0].entry_allowed;
const auto multiple_allowed = intersection[1].entry_allowed && intersection[2].entry_allowed;
if (backwards || multiple_allowed)
{
return false;
}
return true;
}
double SliproadHandler::scaledThresholdByRoadClass(const double max_threshold,
const RoadClassification &classification)
{
double factor = 1.0;
switch (classification.GetPriority())
{
case RoadPriorityClass::MOTORWAY:
factor = 1.0;
break;
case RoadPriorityClass::TRUNK:
factor = 0.8;
break;
case RoadPriorityClass::PRIMARY:
factor = 0.8;
break;
case RoadPriorityClass::SECONDARY:
factor = 0.6;
break;
case RoadPriorityClass::TERTIARY:
factor = 0.5;
break;
case RoadPriorityClass::MAIN_RESIDENTIAL:
factor = 0.4;
break;
case RoadPriorityClass::SIDE_RESIDENTIAL:
factor = 0.3;
break;
case RoadPriorityClass::LINK_ROAD:
factor = 0.3;
break;
case RoadPriorityClass::CONNECTIVITY:
factor = 0.1;
break;
// What
case RoadPriorityClass::BIKE_PATH:
case RoadPriorityClass::FOOT_PATH:
default:
factor = 0.1;
}
const auto scaled = max_threshold * factor;
BOOST_ASSERT(scaled <= max_threshold);
return scaled;
}
} // namespace guidance
} // namespace extractor
} // namespace osrm