basic turn lane handling

This commit is contained in:
Moritz Kobitzsch
2016-05-13 19:18:00 +02:00
parent 2a05b70dfc
commit efa29edf09
68 changed files with 3010 additions and 207 deletions
+21 -12
View File
@@ -1,5 +1,5 @@
#include "extractor/edge_based_graph_factory.hpp"
#include "extractor/edge_based_edge.hpp"
#include "extractor/edge_based_graph_factory.hpp"
#include "util/coordinate.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/exception.hpp"
@@ -11,6 +11,7 @@
#include "extractor/guidance/toolkit.hpp"
#include "extractor/guidance/turn_analysis.hpp"
#include "extractor/guidance/turn_lane_handler.hpp"
#include "extractor/suffix_table.hpp"
#include <boost/assert.hpp>
@@ -39,12 +40,14 @@ EdgeBasedGraphFactory::EdgeBasedGraphFactory(
std::shared_ptr<const RestrictionMap> restriction_map,
const std::vector<QueryNode> &node_info_list,
ProfileProperties profile_properties,
const util::NameTable &name_table)
const util::NameTable &name_table,
const util::NameTable &turn_lanes)
: m_max_edge_id(0), m_node_info_list(node_info_list),
m_node_based_graph(std::move(node_based_graph)),
m_restriction_map(std::move(restriction_map)), m_barrier_nodes(barrier_nodes),
m_traffic_lights(traffic_lights), m_compressed_edge_container(compressed_edge_container),
profile_properties(std::move(profile_properties)), name_table(name_table)
profile_properties(std::move(profile_properties)), name_table(name_table),
turn_lanes(turn_lanes)
{
}
@@ -339,6 +342,8 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges(
m_compressed_edge_container,
name_table,
street_name_suffix_table);
guidance::lanes::TurnLaneHandler turn_lane_handler(
*m_node_based_graph, turn_lanes, m_node_info_list, turn_analysis);
bearing_class_by_node_based_node.resize(m_node_based_graph->GetNumberOfNodes(),
std::numeric_limits<std::uint32_t>::max());
@@ -353,19 +358,23 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges(
continue;
}
++node_based_edge_counter;
auto possible_turns = turn_analysis.getTurns(node_u, edge_from_u);
const NodeID node_v = m_node_based_graph->GetTarget(edge_from_u);
++node_based_edge_counter;
auto intersection = turn_analysis.getIntersection(node_u, edge_from_u);
intersection =
turn_analysis.assignTurnTypes(node_u, edge_from_u, std::move(intersection));
intersection =
turn_lane_handler.assignTurnLanes(node_u, edge_from_u, std::move(intersection));
const auto possible_turns = turn_analysis.transformIntersectionIntoTurns(intersection);
// the entry class depends on the turn, so we have to classify the interesction for
// every edge
const auto turn_classification =
classifyIntersection(node_v,
turn_analysis.getIntersection(node_u, edge_from_u),
*m_node_based_graph,
m_compressed_edge_container,
m_node_info_list);
const auto turn_classification = classifyIntersection(node_v,
intersection,
*m_node_based_graph,
m_compressed_edge_container,
m_node_info_list);
const auto entry_class_id = [&](const util::guidance::EntryClass entry_class) {
if (0 == entry_class_hash.count(entry_class))
+21 -14
View File
@@ -51,6 +51,9 @@ ExtractionContainers::ExtractionContainers()
name_lengths.push_back(0);
name_lengths.push_back(0);
name_lengths.push_back(0);
name_lengths.push_back(0);
name_lengths.push_back(0);
turn_lane_lengths.push_back(0);
}
/**
@@ -66,6 +69,7 @@ ExtractionContainers::ExtractionContainers()
void ExtractionContainers::PrepareData(const std::string &output_file_name,
const std::string &restrictions_file_name,
const std::string &name_file_name,
const std::string &turn_lane_file_name,
lua_State *segment_state)
{
try
@@ -83,7 +87,8 @@ void ExtractionContainers::PrepareData(const std::string &output_file_name,
PrepareRestrictions();
WriteRestrictions(restrictions_file_name);
WriteNames(name_file_name);
WriteCharData(name_file_name,name_lengths,name_char_data);
WriteCharData(turn_lane_file_name,turn_lane_lengths,turn_lane_char_data);
}
catch (const std::exception &e)
{
@@ -91,44 +96,46 @@ void ExtractionContainers::PrepareData(const std::string &output_file_name,
}
}
void ExtractionContainers::WriteNames(const std::string &names_file_name) const
void ExtractionContainers::WriteCharData(const std::string &file_name,
const stxxl::vector<unsigned> &offsets,
const stxxl::vector<char> &char_data) const
{
std::cout << "[extractor] writing street name index ... " << std::flush;
TIMER_START(write_name_index);
boost::filesystem::ofstream name_file_stream(names_file_name, std::ios::binary);
TIMER_START(write_index);
boost::filesystem::ofstream file_stream(file_name, std::ios::binary);
unsigned total_length = 0;
for (const auto name_length : name_lengths)
for (const auto length : offsets)
{
total_length += name_length;
total_length += length;
}
// builds and writes the index
util::RangeTable<> name_index_range(name_lengths);
name_file_stream << name_index_range;
util::RangeTable<> index_range(offsets);
file_stream << index_range;
name_file_stream.write((char *)&total_length, sizeof(unsigned));
file_stream.write((char *)&total_length, sizeof(unsigned));
// write all chars consecutively
char write_buffer[WRITE_BLOCK_BUFFER_SIZE];
unsigned buffer_len = 0;
for (const auto c : name_char_data)
for (const auto c : char_data)
{
write_buffer[buffer_len++] = c;
if (buffer_len >= WRITE_BLOCK_BUFFER_SIZE)
{
name_file_stream.write(write_buffer, WRITE_BLOCK_BUFFER_SIZE);
file_stream.write(write_buffer, WRITE_BLOCK_BUFFER_SIZE);
buffer_len = 0;
}
}
name_file_stream.write(write_buffer, buffer_len);
file_stream.write(write_buffer, buffer_len);
TIMER_STOP(write_name_index);
std::cout << "ok, after " << TIMER_SEC(write_name_index) << "s" << std::endl;
TIMER_STOP(write_index);
std::cout << "ok, after " << TIMER_SEC(write_index) << "s" << std::endl;
}
void ExtractionContainers::PrepareNodes()
+4 -1
View File
@@ -239,6 +239,7 @@ int Extractor::run()
extraction_containers.PrepareData(config.output_file_name,
config.restriction_file_name,
config.names_file_name,
config.turn_lane_file_name,
main_context.state);
WriteProfileProperties(config.profile_properties_output_path, main_context.properties);
@@ -503,6 +504,7 @@ Extractor::BuildEdgeExpandedGraph(lua_State *lua_state,
compressed_edge_container.SerializeInternalVector(config.geometry_output_path);
util::NameTable name_table(config.names_file_name);
util::NameTable turn_lanes(config.turn_lane_file_name);
EdgeBasedGraphFactory edge_based_graph_factory(
node_based_graph,
@@ -512,7 +514,8 @@ Extractor::BuildEdgeExpandedGraph(lua_State *lua_state,
std::const_pointer_cast<RestrictionMap const>(restriction_map),
internal_to_external_node_map,
profile_properties,
name_table);
name_table,
turn_lanes);
edge_based_graph_factory.Run(config.edge_output_path,
lua_state,
+33 -1
View File
@@ -1,13 +1,16 @@
#include "extractor/extraction_containers.hpp"
#include "extractor/extraction_node.hpp"
#include "extractor/extraction_way.hpp"
#include "extractor/extractor_callbacks.hpp"
#include "extractor/external_memory_node.hpp"
#include "extractor/restriction.hpp"
#include "util/for_each_pair.hpp"
#include "util/guidance/turn_lanes.hpp"
#include "util/simple_logger.hpp"
#include "extractor/extractor_callbacks.hpp"
#include <boost/numeric/conversion/cast.hpp>
#include <boost/optional/optional.hpp>
#include <osmium/osm.hpp>
@@ -29,6 +32,7 @@ ExtractorCallbacks::ExtractorCallbacks(ExtractionContainers &extraction_containe
{
// we reserved 0, 1, 2 for the empty case
string_map[MapKey("", "")] = 0;
lane_map[""] = 0;
}
/**
@@ -145,6 +149,30 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
// Otherwise fetches the id based on the name and returns it without insertion.
const constexpr auto MAX_STRING_LENGTH = 255u;
const auto requestId = [this,MAX_STRING_LENGTH](const std::string turn_lane_string) {
if( turn_lane_string == "" )
return INVALID_LANE_STRINGID;
const auto &lane_map_iterator = lane_map.find(turn_lane_string);
if (lane_map.end() == lane_map_iterator)
{
LaneStringID turn_lane_id =
boost::numeric_cast<LaneStringID>(external_memory.turn_lane_lengths.size());
auto turn_lane_length = std::min<unsigned>(MAX_STRING_LENGTH, turn_lane_string.size());
std::copy(turn_lane_string.c_str(),
turn_lane_string.c_str() + turn_lane_length,
std::back_inserter(external_memory.turn_lane_char_data));
external_memory.turn_lane_lengths.push_back(turn_lane_length);
lane_map.insert(std::make_pair(turn_lane_string, turn_lane_id));
return turn_lane_id;
}
else
{
return lane_map_iterator->second;
}
};
const auto turn_lane_id_forward = requestId(parsed_way.turn_lanes_forward);
const auto turn_lane_id_backward = requestId(parsed_way.turn_lanes_backward);
// Get the unique identifier for the street name
// Get the unique identifier for the street name and destination
@@ -191,7 +219,8 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
(parsed_way.backward_speed > 0) &&
(TRAVEL_MODE_INACCESSIBLE != parsed_way.backward_travel_mode) &&
((parsed_way.forward_speed != parsed_way.backward_speed) ||
(parsed_way.forward_travel_mode != parsed_way.backward_travel_mode));
(parsed_way.forward_travel_mode != parsed_way.backward_travel_mode) ||
(turn_lane_id_forward != turn_lane_id_backward));
external_memory.used_node_id_list.reserve(external_memory.used_node_id_list.size() +
input_way.nodes().size());
@@ -224,6 +253,7 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
parsed_way.is_startpoint,
parsed_way.backward_travel_mode,
false,
turn_lane_id_backward,
road_classification));
});
@@ -254,6 +284,7 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
parsed_way.is_startpoint,
parsed_way.forward_travel_mode,
split_edge,
turn_lane_id_forward,
road_classification));
});
if (split_edge)
@@ -275,6 +306,7 @@ void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const Extracti
parsed_way.is_startpoint,
parsed_way.backward_travel_mode,
true,
turn_lane_id_backward,
road_classification));
});
}
+37 -3
View File
@@ -113,10 +113,8 @@ void GraphCompressor::Compress(const std::unordered_set<NodeID> &barrier_nodes,
// traffic signals in the `traffic_lights` list, which EdgeData
// doesn't have access to.
const bool has_node_penalty = traffic_lights.find(node_v) != traffic_lights.end();
if (has_node_penalty)
{
if( has_node_penalty )
continue;
}
// Get distances before graph is modified
const int forward_weight1 = graph.GetEdgeData(forward_e1).distance;
@@ -139,6 +137,42 @@ void GraphCompressor::Compress(const std::unordered_set<NodeID> &barrier_nodes,
graph.SetTarget(forward_e1, node_w);
graph.SetTarget(reverse_e1, node_u);
/*
* Remember Lane Data for compressed parts. This handles scenarios where lane-data is
* only kept up until a traffic light.
*
* | |
* ---------------- |
* -^ | |
* ----------- |
* -v | |
* --------------- |
* | |
*
* u ------- v ---- w
*
* Since the edge is compressable, we can transfer:
* "left|right" (uv) and "" (uw) into a string with "left|right" (uw) for the compressed
* edge.
* Doing so, we might mess up the point from where the lanes are shown. It should be
* reasonable, since the announcements have to come early anyhow. So there is a
* potential danger in here, but it saves us from adding a lot of additional edges for
* turn-lanes. Without this,we would have to treat any turn-lane beginning/ending just
* like a barrier.
*/
const auto selectLaneID = [](const LaneStringID front, const LaneStringID back) {
// A lane has tags: u - (front) - v - (back) - w
// During contraction, we keep only one of the tags. Usually the one closer to the
// intersection is preferred. If its empty, however, we keep the non-empty one
if (back == INVALID_LANE_STRINGID)
return front;
return back;
};
graph.GetEdgeData(forward_e1).lane_string_id =
selectLaneID(graph.GetEdgeData(forward_e1).lane_string_id, fwd_edge_data2.lane_string_id);
graph.GetEdgeData(reverse_e1).lane_string_id =
selectLaneID(graph.GetEdgeData(reverse_e1).lane_string_id, rev_edge_data2.lane_string_id);
// remove e2's (if bidir, otherwise only one)
graph.DeleteEdge(node_v, forward_e2);
graph.DeleteEdge(node_v, reverse_e2);
+26 -2
View File
@@ -1,4 +1,5 @@
#include "extractor/guidance/intersection.hpp"
#include "extractor/guidance/toolkit.hpp"
namespace osrm
{
@@ -21,11 +22,34 @@ std::string toString(const ConnectedRoad &road)
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));
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)) + " " +
std::to_string(static_cast<std::int32_t>(road.turn.instruction.lane_tupel.lanes_in_turn)) +
" " + std::to_string(static_cast<std::int32_t>(
road.turn.instruction.lane_tupel.first_lane_from_the_right));
return result;
}
Intersection::iterator findClosestTurn(Intersection &intersection, const double angle)
{
return std::min_element(intersection.begin(),
intersection.end(),
[angle](const ConnectedRoad &lhs, const ConnectedRoad &rhs) {
return angularDeviation(lhs.turn.angle, angle) <
angularDeviation(rhs.turn.angle, angle);
});
}
Intersection::const_iterator findClosestTurn(const Intersection &intersection, const double angle)
{
return std::min_element(intersection.cbegin(),
intersection.cend(),
[angle](const ConnectedRoad &lhs, const ConnectedRoad &rhs) {
return angularDeviation(lhs.turn.angle, angle) <
angularDeviation(rhs.turn.angle, angle);
});
}
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -1,5 +1,5 @@
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/intersection_generator.hpp"
#include "extractor/guidance/toolkit.hpp"
#include <algorithm>
+2 -4
View File
@@ -42,10 +42,8 @@ inline bool isRampClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_base
MotorwayHandler::MotorwayHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const std::vector<QueryNode> &node_info_list,
const util::NameTable &name_table,
const SuffixTable &street_name_suffix_table,
const IntersectionGenerator &intersection_generator)
: IntersectionHandler(node_based_graph, node_info_list, name_table, street_name_suffix_table),
intersection_generator(intersection_generator)
const SuffixTable &street_name_suffix_table)
: IntersectionHandler(node_based_graph, node_info_list, name_table, street_name_suffix_table)
{
}
@@ -373,7 +373,13 @@ Intersection RoundaboutHandler::handleRoundabouts(const RoundaboutType roundabou
if (1 == node_based_graph.GetDirectedOutDegree(node_v))
{
// No turn possible.
turn.instruction = TurnInstruction::NO_TURN();
if( intersection.size() == 2 )
turn.instruction = TurnInstruction::NO_TURN();
else
{
turn.instruction.type = TurnType::Suppressed; //make sure to report intersection
turn.instruction.direction_modifier = getTurnDirection(turn.angle);
}
}
else
{
+12 -9
View File
@@ -48,19 +48,15 @@ TurnAnalysis::TurnAnalysis(const util::NodeBasedDynamicGraph &node_based_graph,
compressed_edge_container,
name_table,
street_name_suffix_table),
motorway_handler(node_based_graph,
node_info_list,
name_table,
street_name_suffix_table,
intersection_generator),
motorway_handler(node_based_graph, node_info_list, name_table, street_name_suffix_table),
turn_handler(node_based_graph, node_info_list, name_table, street_name_suffix_table)
{
}
std::vector<TurnOperation> TurnAnalysis::getTurns(const NodeID from_nid, const EdgeID via_eid) const
Intersection TurnAnalysis::assignTurnTypes(const NodeID from_nid,
const EdgeID via_eid,
Intersection intersection) const
{
auto intersection = intersection_generator(from_nid, via_eid);
// Roundabouts are a main priority. If there is a roundabout instruction present, we process the
// turn as a roundabout
if (roundabout_handler.canProcess(from_nid, via_eid, intersection))
@@ -81,7 +77,6 @@ std::vector<TurnOperation> TurnAnalysis::getTurns(const NodeID from_nid, const E
intersection = turn_handler(from_nid, via_eid, std::move(intersection));
}
}
// Handle sliproads
intersection = handleSliproads(via_eid, std::move(intersection));
@@ -94,6 +89,12 @@ std::vector<TurnOperation> TurnAnalysis::getTurns(const NodeID from_nid, const E
});
}
return intersection;
}
std::vector<TurnOperation>
TurnAnalysis::transformIntersectionIntoTurns(const Intersection &intersection) const
{
std::vector<TurnOperation> turns;
for (auto road : intersection)
if (road.entry_allowed)
@@ -234,6 +235,8 @@ Intersection TurnAnalysis::handleSliproads(const EdgeID source_edge_id,
return intersection;
}
const IntersectionGenerator &TurnAnalysis::getGenerator() const { return intersection_generator; }
} // namespace guidance
} // namespace extractor
} // namespace osrm
+85
View File
@@ -0,0 +1,85 @@
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/turn_discovery.hpp"
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace lanes
{
bool findPreviousIntersection(const NodeID node_v,
const EdgeID via_edge,
const Intersection intersection,
const TurnAnalysis &turn_analysis,
const util::NodeBasedDynamicGraph &node_based_graph,
// output parameters
NodeID &result_node,
EdgeID &result_via_edge,
Intersection &result_intersection)
{
/* We need to find the intersection that is located prior to via_edge.
*
* NODE_U -> PREVIOUS_ID -> NODE_V -> VIA_EDGE -> NODE_W:INTERSECTION
* NODE_U? <- STRAIGHTMOST <- NODE_V <- UTURN
* NODE_U? -> UTURN == PREVIOUSE_ID? -> NODE_V -> VIA_EDGE
*
* To do so, we first get the intersection atNODE and find the straightmost turn from that
* node. This will result in NODE_X. The uturn in the intersection at NODE_X should be
* PREVIOUS_ID. To verify that find, we check the intersection using our PREVIOUS_ID candidate
* to check the intersection at NODE for via_edge
*/
const constexpr double COMBINE_DISTANCE_CUTOFF = 30;
// we check if via-edge is too short. In this case the previous turn cannot influence the turn
// at via_edge and the intersection at NODE_W
if (node_based_graph.GetEdgeData(via_edge).distance > COMBINE_DISTANCE_CUTOFF)
return false;
// Node -> Via_Edge -> Intersection[0 == UTURN] -> reverse_of(via_edge) -> Intersection at node
// (looking at the reverse direction).
const auto node_w = node_based_graph.GetTarget(via_edge);
const auto u_turn_at_node_w = intersection[0].turn.eid;
const auto node_v_reverse_intersection =
turn_analysis.getIntersection(node_w, u_turn_at_node_w);
// Continue along the straightmost turn. If there is no straight turn, we cannot find a valid
// previous intersection.
const auto straightmost_at_v_in_reverse =
findClosestTurn(node_v_reverse_intersection, STRAIGHT_ANGLE);
if (angularDeviation(straightmost_at_v_in_reverse->turn.angle, STRAIGHT_ANGLE) >
FUZZY_ANGLE_DIFFERENCE)
return false;
const auto node_u = node_based_graph.GetTarget(straightmost_at_v_in_reverse->turn.eid);
const auto node_u_reverse_intersection =
turn_analysis.getIntersection(node_v, straightmost_at_v_in_reverse->turn.eid);
// now check that the u-turn at the given intersection connects to via-edge
// The u-turn at the now found intersection should, hopefully, represent the previous edge.
result_node = node_u;
result_via_edge = node_u_reverse_intersection[0].turn.eid;
// if the edge is not traversable, we obviously don't have a previous intersection or couldn't
// find it.
if (node_based_graph.GetEdgeData(result_via_edge).reversed)
return false;
result_intersection = turn_analysis.getIntersection(node_u, result_via_edge);
const auto check_via_edge = findClosestTurn(result_intersection, STRAIGHT_ANGLE)->turn.eid;
if (check_via_edge != via_edge)
return false;
result_intersection =
turn_analysis.assignTurnTypes(node_u, result_via_edge, std::move(result_intersection));
return true;
}
} // namespace lanes
} // namespace guidance
} // namespace extractor
} // namespace osrm
+4 -2
View File
@@ -78,7 +78,8 @@ Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection
const auto &first_data = node_based_graph.GetEdgeData(intersection[1].turn.eid);
const auto &second_data = node_based_graph.GetEdgeData(intersection[2].turn.eid);
BOOST_ASSERT(intersection[0].turn.angle < 0.001);
const auto isObviousOfTwo = [this](const ConnectedRoad road, const ConnectedRoad other) {
const auto isObviousOfTwo = [this, in_data](const ConnectedRoad road,
const ConnectedRoad other) {
const auto first_class =
node_based_graph.GetEdgeData(road.turn.eid).road_classification.road_class;
@@ -104,7 +105,8 @@ Intersection TurnHandler::handleThreeWayTurn(const EdgeID via_edge, Intersection
const bool turn_is_perfectly_straight = angularDeviation(road.turn.angle, STRAIGHT_ANGLE) <
std::numeric_limits<double>::epsilon();
if (turn_is_perfectly_straight)
if (turn_is_perfectly_straight && in_data.name_id != EMPTY_NAMEID &&
in_data.name_id == node_based_graph.GetEdgeData(road.turn.eid).name_id)
return true;
const bool is_much_narrower_than_other =
@@ -0,0 +1,304 @@
#include "extractor/guidance/turn_lane_augmentation.hpp"
#include "util/simple_logger.hpp"
#include <algorithm>
#include <cstddef>
#include <utility>
#include <boost/assert.hpp>
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace lanes
{
namespace
{
const constexpr char *tag_by_modifier[] = {"reverse",
"sharp_right",
"right",
"slight_right",
"through",
"slight_left",
"left",
"sharp_left"};
std::size_t getNumberOfTurns(const Intersection &intersection)
{
return std::count_if(intersection.begin(), intersection.end(), [](const ConnectedRoad &road) {
return road.entry_allowed;
});
}
LaneDataVector augmentMultiple(const std::size_t none_index,
const std::size_t connection_count,
LaneDataVector lane_data,
const Intersection &intersection)
{
// a none-turn is allowing multiple turns. we have to add a lane-data entry for
// every possible turn. This should, hopefully, only be the case for single lane
// entries?
// looking at the left side first
const auto range = [&]() {
if (none_index == 0)
{
// find first connection_count - lane_data.size() valid turns
std::size_t count = 0;
for (std::size_t intersection_index = 1; intersection_index < intersection.size();
++intersection_index)
{
count += static_cast<int>(intersection[intersection_index].entry_allowed);
if (count > connection_count - lane_data.size())
return std::make_pair(std::size_t{1}, intersection_index + 1);
}
}
else if (none_index + 1 == lane_data.size())
{
BOOST_ASSERT(!lane_data.empty());
// find last connection-count - last_data.size() valid turns
std::size_t count = 0;
for (std::size_t intersection_index = intersection.size() - 1; intersection_index > 0;
--intersection_index)
{
count += static_cast<int>(intersection[intersection_index].entry_allowed);
if (count > connection_count - lane_data.size())
return std::make_pair(intersection_index, intersection.size());
}
}
else
{
// skip the first #index valid turns, find next connection_count -
// lane_data.size() valid ones
std::size_t begin = 1, count = 0, intersection_index;
for (intersection_index = 1; intersection_index < intersection.size();
++intersection_index)
{
count += static_cast<int>(intersection[intersection_index].entry_allowed);
// if we reach the amount of
if (count >= none_index)
{
begin = intersection_index + 1;
break;
}
}
// reset count to find the number of necessary entries
count = 0;
for (intersection_index = begin; intersection_index < intersection.size();
++intersection_index)
{
count += static_cast<int>(intersection[intersection_index].entry_allowed);
if (count > connection_count - lane_data.size())
{
return std::make_pair(begin, intersection_index + 1);
}
}
}
// this should, theoretically, never be reached
util::SimpleLogger().Write(logWARNING) << "Failed lane assignment. Reached bad situation.";
return std::make_pair(std::size_t{0}, std::size_t{0});
}();
for (auto intersection_index = range.first; intersection_index < range.second;
++intersection_index)
{
if (intersection[intersection_index].entry_allowed)
{
// FIXME this probably can be only a subset of these turns here?
lane_data.push_back({tag_by_modifier[intersection[intersection_index]
.turn.instruction.direction_modifier],
lane_data[none_index].from,
lane_data[none_index].to});
}
}
lane_data.erase(lane_data.begin() + none_index);
return std::move(lane_data);
}
// Merging none-tag into its neighboring fields
// This handles situations like "left | | | right".
LaneDataVector mergeNoneTag(const std::size_t none_index, LaneDataVector lane_data)
{
if (none_index == 0 || none_index + 1 == lane_data.size())
{
if (none_index == 0)
{
lane_data[1].from = lane_data[0].from;
}
else
{
lane_data[none_index - 1].to = lane_data[none_index].to;
}
lane_data.erase(lane_data.begin() + none_index);
}
else if (lane_data[none_index].to - lane_data[none_index].from <= 1)
{
lane_data[none_index - 1].to = lane_data[none_index].from;
lane_data[none_index + 1].from = lane_data[none_index].to;
lane_data.erase(lane_data.begin() + none_index);
}
return std::move(lane_data);
}
LaneDataVector handleRenamingSituations(const std::size_t none_index,
LaneDataVector lane_data,
const Intersection &intersection)
{
bool has_right = false;
bool has_through = false;
bool has_left = false;
for (const auto &road : intersection)
{
if (!road.entry_allowed)
continue;
const auto modifier = road.turn.instruction.direction_modifier;
has_right |= modifier == DirectionModifier::Right;
has_right |= modifier == DirectionModifier::SlightRight;
has_right |= modifier == DirectionModifier::SharpRight;
has_through |= modifier == DirectionModifier::Straight;
has_left |= modifier == DirectionModifier::Left;
has_left |= modifier == DirectionModifier::SlightLeft;
has_left |= modifier == DirectionModifier::SharpLeft;
}
// find missing tag and augment neighboring, if possible
if (none_index == 0)
{
if (has_right &&
(lane_data.size() == 1 || (lane_data[none_index + 1].tag != "sharp_right" &&
lane_data[none_index + 1].tag != "right")))
{
lane_data[none_index].tag = "right";
if (lane_data.size() > 1 && lane_data[none_index + 1].tag == "through")
{
lane_data[none_index + 1].from = lane_data[none_index].from;
// turning right through a possible through lane is not possible
lane_data[none_index].to = lane_data[none_index].from;
}
}
else if (has_through &&
(lane_data.size() == 1 || lane_data[none_index + 1].tag != "through"))
{
lane_data[none_index].tag = "through";
}
}
else if (none_index + 1 == lane_data.size())
{
if (has_left && ((lane_data[none_index - 1].tag != "sharp_left" &&
lane_data[none_index - 1].tag != "left")))
{
lane_data[none_index].tag = "left";
if (lane_data[none_index - 1].tag == "through")
{
lane_data[none_index - 1].to = lane_data[none_index].to;
// turning left through a possible through lane is not possible
lane_data[none_index].from = lane_data[none_index].to;
}
}
else if (has_through && lane_data[none_index - 1].tag != "through")
{
lane_data[none_index].tag = "through";
}
}
else
{
if ((lane_data[none_index + 1].tag == "left" ||
lane_data[none_index + 1].tag == "slight_left" ||
lane_data[none_index + 1].tag == "sharp_left") &&
(lane_data[none_index - 1].tag == "right" ||
lane_data[none_index - 1].tag == "slight_right" ||
lane_data[none_index - 1].tag == "sharp_right"))
{
lane_data[none_index].tag = "through";
}
}
return std::move(lane_data);
}
} // namespace
/*
Lanes can have the tag none. While a nice feature for visibility, it is a terrible feature
for parsing. None can be part of neighboring turns, or not. We have to look at both the
intersection and the lane data to see what turns we have to augment by the none-lanes
*/
LaneDataVector handleNoneValueAtSimpleTurn(LaneDataVector lane_data,
const Intersection &intersection)
{
const bool needs_no_processing =
(intersection.empty() || lane_data.empty() || !hasTag("none", lane_data));
if (needs_no_processing)
return std::move(lane_data);
// FIXME all this needs to consider the number of lanes at the target to ensure that we
// augment lanes correctly, if the target lane allows for more turns
//
// -----------------
//
// ----- ----
// -v |
// ----- |
// | | |
//
// A situation like this would allow a right turn from the through lane.
//
// -----------------
//
// ----- --------
// -v |
// ----- |
// | |
//
// Here, the number of lanes in the right road would not allow turns from both lanes, but
// only from the right one.
const std::size_t connection_count =
getNumberOfTurns(intersection) -
((intersection[0].entry_allowed && lane_data.back().tag != "reverse") ? 1 : 0);
// TODO check for impossible turns to see whether the turn lane is at the correct place
const std::size_t none_index = std::distance(lane_data.begin(), findTag("none", lane_data));
BOOST_ASSERT(none_index != lane_data.size());
// we have to create multiple turns
if (connection_count > lane_data.size())
{
lane_data =
augmentMultiple(none_index, connection_count, std::move(lane_data), intersection);
}
// we have to reduce it, assigning it to neighboring turns
else if (connection_count < lane_data.size())
{
// a prerequisite is simple turns. Larger differences should not end up here
// an additional line at the side is only reasonable if it is targeting public
// service vehicles. Otherwise, we should not have it
BOOST_ASSERT(connection_count + 1 == lane_data.size());
lane_data = mergeNoneTag(none_index, std::move(lane_data));
}
// we have to rename and possibly augment existing ones. The pure count remains the
// same.
else
{
lane_data = handleRenamingSituations(none_index, std::move(lane_data), intersection);
}
// finally make sure we are still sorted
std::sort(lane_data.begin(), lane_data.end());
return std::move(lane_data);
}
} // namespace lanes
} // namespace guidance
} // namespace extractor
} // namespace osrm
+145
View File
@@ -0,0 +1,145 @@
#include "extractor/guidance/turn_lane_data.hpp"
#include "util/guidance/turn_lanes.hpp"
#include <boost/numeric/conversion/cast.hpp>
#include <algorithm>
#include <cstddef>
#include <string>
#include <unordered_map>
#include <utility>
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace lanes
{
bool TurnLaneData::operator<(const TurnLaneData &other) const
{
if (from < other.from)
return true;
if (from > other.from)
return false;
if (to < other.to)
return true;
if (to > other.to)
return false;
const constexpr char *tag_by_modifier[] = {"sharp_right",
"right",
"slight_right",
"through",
"slight_left",
"left",
"sharp_left",
"reverse"};
return std::find(tag_by_modifier, tag_by_modifier + 8, this->tag) <
std::find(tag_by_modifier, tag_by_modifier + 8, other.tag);
}
LaneDataVector laneDataFromString(std::string turn_lane_string)
{
typedef std::unordered_map<std::string, std::pair<LaneID, LaneID>> LaneMap;
// FIXME this is a workaround due to https://github.com/cucumber/cucumber-js/issues/417,
// need to switch statements when fixed
// const auto num_lanes = std::count(turn_lane_string.begin(), turn_lane_string.end(), '|') + 1;
// count the number of lanes
const auto num_lanes = [](const std::string &turn_lane_string) {
return boost::numeric_cast<LaneID>(
std::count(turn_lane_string.begin(), turn_lane_string.end(), '|') + 1 +
std::count(turn_lane_string.begin(), turn_lane_string.end(), '&'));
}(turn_lane_string);
const auto getNextTag = [](std::string &string, const char *separators) {
auto pos = string.find_last_of(separators);
auto result = pos != std::string::npos ? string.substr(pos + 1) : string;
string.resize(pos == std::string::npos ? 0 : pos);
return result;
};
const auto setLaneData = [&](LaneMap &map, std::string lane, const LaneID current_lane) {
do
{
auto identifier = getNextTag(lane, ";");
if (identifier.empty())
identifier = "none";
auto map_iterator = map.find(identifier);
if (map_iterator == map.end())
map[identifier] = std::make_pair(current_lane, current_lane);
else
{
map_iterator->second.second = current_lane;
}
} while (!lane.empty());
};
// check whether a given turn lane string resulted in valid lane data
const auto hasValidOverlaps = [](const LaneDataVector &lane_data) {
// Allow an overlap of at most one. Larger overlaps would result in crossing another turn,
// which is invalid
for (std::size_t index = 1; index < lane_data.size(); ++index)
{
if (lane_data[index - 1].to > lane_data[index].from)
return false;
}
return true;
};
LaneMap lane_map;
LaneID lane_nr = 0;
LaneDataVector lane_data;
if (turn_lane_string.empty())
return lane_data;
do
{
// FIXME this is a cucumber workaround, since escaping does not work properly in
// cucumber.js (see https://github.com/cucumber/cucumber-js/issues/417). Needs to be
// changed to "|" only, when the bug is fixed
auto lane = getNextTag(turn_lane_string, "|&");
setLaneData(lane_map, lane, lane_nr);
++lane_nr;
} while (lane_nr < num_lanes);
for (const auto tag : lane_map)
{
lane_data.push_back({tag.first, tag.second.first, tag.second.second});
}
std::sort(lane_data.begin(), lane_data.end());
if (!hasValidOverlaps(lane_data))
{
lane_data.clear();
}
return lane_data;
}
LaneDataVector::iterator findTag(const std::string &tag, LaneDataVector &data)
{
return std::find_if(data.begin(), data.end(), [&](const TurnLaneData &lane_data) {
return tag == lane_data.tag;
});
}
LaneDataVector::const_iterator findTag(const std::string &tag, const LaneDataVector &data)
{
return std::find_if(data.cbegin(), data.cend(), [&](const TurnLaneData &lane_data) {
return tag == lane_data.tag;
});
}
bool hasTag(const std::string &tag, const LaneDataVector &data){
return findTag(tag,data) != data.cend();
}
} // namespace lanes
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -0,0 +1,505 @@
#include "extractor/guidance/constants.hpp"
#include "extractor/guidance/turn_discovery.hpp"
#include "extractor/guidance/turn_lane_handler.hpp"
#include "extractor/guidance/turn_lane_matcher.hpp"
#include "extractor/guidance/turn_lane_augmentation.hpp"
#include "util/simple_logger.hpp"
#include "util/typedefs.hpp"
#include <cstdint>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/numeric/conversion/cast.hpp>
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace lanes
{
namespace
{
std::size_t getNumberOfTurns(const Intersection &intersection)
{
return std::count_if(intersection.begin(), intersection.end(), [](const ConnectedRoad &road) {
return road.entry_allowed;
});
}
} // namespace
TurnLaneHandler::TurnLaneHandler(const util::NodeBasedDynamicGraph &node_based_graph,
const util::NameTable &turn_lane_strings,
const std::vector<QueryNode> &node_info_list,
const TurnAnalysis &turn_analysis)
: node_based_graph(node_based_graph), turn_lane_strings(turn_lane_strings),
node_info_list(node_info_list), turn_analysis(turn_analysis)
{
}
/*
Turn lanes are given in the form of strings that closely correspond to the direction modifiers
we use for our turn types. However, we still cannot simply perform a 1:1 assignment.
This function parses the turn_lane_strings of a format that describes an intersection as:
----------
A -^
----------
B -> -v
----------
C -v
----------
with a string like |left|through;right|right| and performs an assignment onto the turns:
for example: (130, turn slight right), (180, ramp straight), (320, turn sharp left)
*/
Intersection TurnLaneHandler::assignTurnLanes(const NodeID at,
const EdgeID via_edge,
Intersection intersection) const
{
// initialize to invalid
for (auto &road : intersection)
road.turn.instruction.lane_tupel = {0, INVALID_LANEID};
const auto &data = node_based_graph.GetEdgeData(via_edge);
const auto turn_lane_string = data.lane_string_id != INVALID_LANE_STRINGID
? turn_lane_strings.GetNameForID(data.lane_string_id)
: "";
// going straight, due to traffic signals, we can have uncompressed geometry
if (intersection.size() == 2 &&
((data.lane_string_id != INVALID_LANE_STRINGID &&
data.lane_string_id ==
node_based_graph.GetEdgeData(intersection[1].turn.eid).lane_string_id) ||
angularDeviation(intersection[1].turn.angle, STRAIGHT_ANGLE) < FUZZY_ANGLE_DIFFERENCE))
return std::move(intersection);
auto lane_data = laneDataFromString(turn_lane_string);
// if we see an invalid conversion, we stop immediately
if (!turn_lane_string.empty() && lane_data.empty())
return std::move(intersection);
// might be reasonable to handle multiple turns, if we know of a sequence of lanes
// e.g. one direction per lane, if three lanes and right, through, left available
if (!turn_lane_string.empty() && lane_data.size() == 1 && lane_data[0].tag == "none")
return std::move(intersection);
const std::size_t possible_entries = getNumberOfTurns(intersection);
// merge does not justify an instruction
const bool has_merge_lane = (hasTag("merge_to_left", lane_data) ||
hasTag("merge_to_right", lane_data));
// Dead end streets that don't have any left-tag. This can happen due to the fallbacks for
// broken data/barriers.
const bool has_non_usable_u_turn =
(intersection[0].entry_allowed && !hasTag("none", lane_data) &&
!hasTag("left", lane_data) &&
!hasTag("sharp_left", lane_data) &&
!hasTag("reverse", lane_data) &&
lane_data.size() + 1 == possible_entries);
if (has_merge_lane || has_non_usable_u_turn)
return std::move(intersection);
if (!lane_data.empty() && canMatchTrivially(intersection, lane_data) &&
lane_data.size() !=
static_cast<std::size_t>(
lane_data.back().tag != "reverse" && intersection[0].entry_allowed ? 1 : 0) +
possible_entries &&
intersection[0].entry_allowed && !hasTag("none", lane_data))
lane_data.push_back({"reverse", lane_data.back().to, lane_data.back().to});
bool is_simple = isSimpleIntersection(lane_data, intersection);
// simple intersections can be assigned directly
if (is_simple)
{
lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection);
return simpleMatchTuplesToTurns(std::move(intersection), lane_data);
}
// if the intersection is not simple but we have lane data, we check for intersections with
// middle islands. We have two cases. The first one is providing lane data on the current
// segment and we only need to consider the part of the current segment. In this case we
// partition the data and only consider the first part.
else if (!lane_data.empty())
{
if (lane_data.size() >= possible_entries)
{
lane_data = partitionLaneData(node_based_graph.GetTarget(via_edge),
std::move(lane_data),
intersection)
.first;
// check if we were successfull in trimming
if (lane_data.size() == possible_entries &&
isSimpleIntersection(lane_data, intersection))
{
lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection);
return simpleMatchTuplesToTurns(std::move(intersection), lane_data);
}
}
}
// The second part does not provide lane data on the current segment, but on the segment prior
// to the turn. We try to partition the data and only consider the second part.
else if (turn_lane_string.empty())
{
// acquire the lane data of a previous segment and, if possible, use it for the current
// intersection.
return handleTurnAtPreviousIntersection(at, via_edge, std::move(intersection));
}
return std::move(intersection);
}
// At segregated intersections, turn lanes will often only be specified up until the first turn. To
// actually take the turn, we need to look back to the edge we drove onto the intersection with.
Intersection TurnLaneHandler::handleTurnAtPreviousIntersection(const NodeID at,
const EdgeID via_edge,
Intersection intersection) const
{
NodeID previous_node = SPECIAL_NODEID;
Intersection previous_intersection;
EdgeID previous_id = SPECIAL_EDGEID;
// Get the previous lane string. We only accept strings that stem from a not-simple intersection
// and are not empty.
const auto previous_lane_string = [&]() -> std::string {
if (!findPreviousIntersection(at,
via_edge,
intersection,
turn_analysis,
node_based_graph,
previous_node,
previous_id,
previous_intersection))
return "";
const auto &previous_data = node_based_graph.GetEdgeData(previous_id);
auto previous_string = previous_data.lane_string_id != INVALID_LANE_STRINGID
? turn_lane_strings.GetNameForID(previous_data.lane_string_id)
: "";
if (previous_string.empty())
return "";
previous_intersection = turn_analysis.assignTurnTypes(
previous_node, previous_id, std::move(previous_intersection));
auto previous_lane_data = laneDataFromString(previous_string);
if (isSimpleIntersection(previous_lane_data, previous_intersection))
return "";
return previous_string;
}();
// no lane string, no problems
if (previous_lane_string.empty())
return std::move(intersection);
auto lane_data = laneDataFromString(previous_lane_string);
// stop on invalid lane data conversion
if (lane_data.empty())
return std::move(intersection);
const auto is_simple = isSimpleIntersection(lane_data, intersection);
if (is_simple)
{
lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection);
return simpleMatchTuplesToTurns(std::move(intersection), lane_data);
}
else
{
if (lane_data.size() >= getNumberOfTurns(previous_intersection) &&
previous_intersection.size() != 2)
{
lane_data = partitionLaneData(node_based_graph.GetTarget(previous_id),
std::move(lane_data),
previous_intersection)
.second;
std::sort(lane_data.begin(), lane_data.end());
// check if we were successfull in trimming
if (lane_data.size() == getNumberOfTurns(intersection) &&
isSimpleIntersection(lane_data, intersection))
{
lane_data = handleNoneValueAtSimpleTurn(std::move(lane_data), intersection);
return simpleMatchTuplesToTurns(std::move(intersection), lane_data);
}
}
}
return std::move(intersection);
}
/* A simple intersection does not depend on the next intersection coming up. This is important
* for turn lanes, since traffic signals and/or segregated a intersection can influence the
* interpretation of turn-lanes at a given turn.
*
* Here we check for a simple intersection. A simple intersection has a long enough segment
* followin the turn, offers no straight turn, or only non-trivial turn operations.
*/
bool TurnLaneHandler::isSimpleIntersection(const LaneDataVector &lane_data,
const Intersection &intersection) const
{
if (lane_data.empty())
return false;
// if we are on a straight road, turn lanes are only reasonable in connection to the next
// intersection, or in case of a merge. If not all but one (straight) are merges, we don't
// consider the intersection simple
if (intersection.size() == 2)
{
return std::count_if(
lane_data.begin(),
lane_data.end(),
[](const TurnLaneData &data) { return boost::starts_with(data.tag, "merge"); }) +
std::size_t{1} >=
lane_data.size();
}
// in case an intersection offers far more lane data items than actual turns, some of them
// have
// to be for another intersection. A single additional item can be for an invalid bus lane.
const auto num_turns = [&]() {
auto count = getNumberOfTurns(intersection);
if (count < lane_data.size() && !intersection[0].entry_allowed &&
lane_data.back().tag == "reverse")
return count + 1;
return count;
}();
// more than two additional lane data entries -> lanes target a different intersection
if (num_turns + std::size_t{2} <= lane_data.size())
{
return false;
}
// single additional lane data entry is alright, if it is none at the side. This usually
// refers to a bus-lane
if (num_turns + std::size_t{1} == lane_data.size() && lane_data.front().tag != "none" &&
lane_data.back().tag != "none")
{
return false;
}
// more turns than lane data
if (num_turns > lane_data.size() &&
lane_data.end() ==
std::find_if(lane_data.begin(), lane_data.end(), [](const TurnLaneData &data) {
return data.tag == "none";
}))
{
return false;
}
if (num_turns > lane_data.size() && intersection[0].entry_allowed &&
!( hasTag("reverse", lane_data) ||
(lane_data.back().tag != "left" && lane_data.back().tag != "sharp_left")))
{
return false;
}
// check if we can find a valid 1:1 mapping in a straightforward manner
bool all_simple = true;
bool has_none = false;
std::unordered_set<std::size_t> matched_indices;
for (const auto &data : lane_data)
{
if (data.tag == "none")
{
has_none = true;
continue;
}
const auto best_match = [&]() {
if (data.tag != "reverse" || lane_data.size() == 1)
return findBestMatch(data.tag, intersection);
// lane_data.size() > 1
if (lane_data.back().tag == "reverse")
return findBestMatchForReverse(lane_data[lane_data.size() - 2].tag, intersection);
BOOST_ASSERT(lane_data.front().tag == "reverse");
return findBestMatchForReverse(lane_data[1].tag, intersection);
}();
std::size_t match_index = std::distance(intersection.begin(), best_match);
all_simple &= (matched_indices.count(match_index) == 0);
matched_indices.insert(match_index);
// in case of u-turns, we might need to activate them first
all_simple &= (best_match->entry_allowed || match_index == 0);
all_simple &= isValidMatch(data.tag, best_match->turn.instruction);
}
// either all indices are matched, or we have a single none-value
if (all_simple && (matched_indices.size() == lane_data.size() ||
(matched_indices.size() + 1 == lane_data.size() && has_none)))
return true;
// better save than sorry
return false;
}
std::pair<LaneDataVector, LaneDataVector> TurnLaneHandler::partitionLaneData(
const NodeID at, LaneDataVector turn_lane_data, const Intersection &intersection) const
{
BOOST_ASSERT(turn_lane_data.size() >= getNumberOfTurns(intersection));
/*
* A Segregated intersection can provide turn lanes for turns that are not yet possible.
* The straightforward example would be coming up to the following situation:
* (1) (2)
* | A | | A |
* | | | | ^ |
* | v | | | |
* ------- ----------- ------
* B ->-^ B
* ------- ----------- ------
* B ->-v B
* ------- ----------- ------
* | A | | A |
*
* Traveling on road B, we have to pass A at (1) to turn left onto A at (2). The turn
* lane itself may only be specified prior to (1) and/or could be repeated between (1)
* and (2). To make sure to announce the lane correctly, we need to treat the (in this
* case left) turn lane as if it were to continue straight onto the intersection and
* look back between (1) and (2) to make sure we find the correct lane for the left-turn.
*
* Intersections like these have two parts. Turns that can be made at the first intersection and
* turns that have to be made at the second. The partitioning returns the lane data split into
* two parts, one for the first and one for the second intersection.
*/
// Try and maitch lanes to available turns. For Turns that are not directly matchable, check
// whether we can match them at the upcoming intersection.
const auto straightmost = findClosestTurn(intersection, STRAIGHT_ANGLE);
BOOST_ASSERT(straightmost < intersection.cend());
// we need to be able to enter the straightmost turn
if (!straightmost->entry_allowed)
return {turn_lane_data, {}};
std::vector<bool> matched_at_first(turn_lane_data.size(), false);
std::vector<bool> matched_at_second(turn_lane_data.size(), false);
// find out about the next intersection. To check for valid matches, we also need the turn types
auto next_intersection = turn_analysis.getIntersection(at, straightmost->turn.eid);
next_intersection =
turn_analysis.assignTurnTypes(at, straightmost->turn.eid, std::move(next_intersection));
// check where we can match turn lanes
std::size_t straightmost_tag_index = turn_lane_data.size();
for (std::size_t lane = 0; lane < turn_lane_data.size(); ++lane)
{
if (turn_lane_data[lane].tag == "none" || turn_lane_data[lane].tag == "reverse")
continue;
const auto best_match = findBestMatch(turn_lane_data[lane].tag, intersection);
if (isValidMatch(turn_lane_data[lane].tag, best_match->turn.instruction))
{
matched_at_first[lane] = true;
if (straightmost == best_match)
straightmost_tag_index = lane;
}
const auto best_match_at_next_intersection =
findBestMatch(turn_lane_data[lane].tag, next_intersection);
if (isValidMatch(turn_lane_data[lane].tag,
best_match_at_next_intersection->turn.instruction))
matched_at_second[lane] = true;
// we need to match all items to either the current or the next intersection
if (!(matched_at_first[lane] || matched_at_second[lane]))
return {turn_lane_data, {}};
}
std::size_t none_index = std::distance(turn_lane_data.begin(),findTag("none", turn_lane_data));
// if the turn lanes are pull forward, we might have to add an additional straight tag
// did we find something that matches against the straightmost road?
if (straightmost_tag_index == turn_lane_data.size())
{
if (none_index != turn_lane_data.size())
straightmost_tag_index = none_index;
}
// TODO handle reverse
// handle none values
if (none_index != turn_lane_data.size())
{
if (static_cast<std::size_t>(
std::count(matched_at_first.begin(), matched_at_first.end(), true)) <=
getNumberOfTurns(intersection))
matched_at_first[none_index] = true;
if (static_cast<std::size_t>(
std::count(matched_at_second.begin(), matched_at_second.end(), true)) <=
getNumberOfTurns(next_intersection))
matched_at_second[none_index] = true;
}
const auto augmentEntry = [&](TurnLaneData &data) {
for (std::size_t lane = 0; lane < turn_lane_data.size(); ++lane)
if (matched_at_second[lane])
{
data.from = std::min(turn_lane_data[lane].from, data.from);
data.to = std::max(turn_lane_data[lane].to, data.to);
}
};
LaneDataVector first, second;
for (std::size_t lane = 0; lane < turn_lane_data.size(); ++lane)
{
if (matched_at_second[lane])
second.push_back(turn_lane_data[lane]);
// augment straightmost at this intersection to match all turns that happen at the next
if (lane == straightmost_tag_index)
{
augmentEntry(turn_lane_data[straightmost_tag_index]);
}
if (matched_at_first[lane])
first.push_back(turn_lane_data[lane]);
}
if (straightmost_tag_index == turn_lane_data.size() &&
static_cast<std::size_t>(
std::count(matched_at_second.begin(), matched_at_second.end(), true)) ==
getNumberOfTurns(next_intersection))
{
TurnLaneData data = {"through", 255, 0};
augmentEntry(data);
first.push_back(data);
std::sort(first.begin(), first.end());
}
// TODO augment straightmost turn
return {std::move(first), std::move(second)};
}
Intersection TurnLaneHandler::simpleMatchTuplesToTurns(Intersection intersection,
const LaneDataVector &lane_data) const
{
if (lane_data.empty() || !canMatchTrivially(intersection, lane_data))
return std::move(intersection);
BOOST_ASSERT(!hasTag("none", lane_data));
BOOST_ASSERT(std::count_if(lane_data.begin(), lane_data.end(), [](const TurnLaneData &data) {
return boost::starts_with(data.tag, "merge");
}) == 0);
return triviallyMatchLanesToTurns(std::move(intersection), lane_data, node_based_graph);
}
} // namespace lanes
} // namespace guidance
} // namespace extractor
} // namespace osrm
@@ -0,0 +1,221 @@
#include "extractor/guidance/toolkit.hpp"
#include "extractor/guidance/turn_lane_matcher.hpp"
#include "util/guidance/toolkit.hpp"
#include <boost/assert.hpp>
namespace osrm
{
namespace extractor
{
namespace guidance
{
namespace lanes
{
// Translate Turn Tags into a Matching Direction Modifier
DirectionModifier::Enum getMatchingModifier(const std::string &tag)
{
const constexpr char *tag_by_modifier[] = {"reverse",
"sharp_right",
"right",
"slight_right",
"through",
"slight_left",
"left",
"sharp_left",
"merge_to_left",
"merge_to_right"};
const auto index =
std::distance(tag_by_modifier, std::find(tag_by_modifier, tag_by_modifier + 10, tag));
BOOST_ASSERT(index <= 10);
const constexpr DirectionModifier::Enum modifiers[11] = {
DirectionModifier::UTurn,
DirectionModifier::SharpRight,
DirectionModifier::Right,
DirectionModifier::SlightRight,
DirectionModifier::Straight,
DirectionModifier::SlightLeft,
DirectionModifier::Left,
DirectionModifier::SharpLeft,
DirectionModifier::Straight,
DirectionModifier::Straight,
DirectionModifier::UTurn}; // fallback for invalid tags
return modifiers[index];
}
// check whether a match of a given tag and a turn instruction can be seen as valid
bool isValidMatch(const std::string &tag, const TurnInstruction instruction)
{
using util::guidance::hasLeftModifier;
using util::guidance::hasRightModifier;
const auto isMirroredModifier = [](const TurnInstruction instruction) {
return instruction.type == TurnType::Merge;
};
if (tag == "reverse")
{
return hasLeftModifier(instruction) ||
instruction.direction_modifier == DirectionModifier::UTurn;
}
else if (tag == "sharp_right" || tag == "right" || tag == "slight_right")
{
if (isMirroredModifier(instruction))
return hasLeftModifier(instruction);
else
// needs to be adjusted for left side driving
return leavesRoundabout(instruction) || hasRightModifier(instruction);
}
else if (tag == "through")
{
return instruction.direction_modifier == DirectionModifier::Straight ||
instruction.type == TurnType::Suppressed || instruction.type == TurnType::NewName ||
instruction.type == TurnType::StayOnRoundabout || entersRoundabout(instruction) ||
(instruction.type ==
TurnType::Fork && // Forks can be experienced, even for straight segments
(instruction.direction_modifier == DirectionModifier::SlightLeft ||
instruction.direction_modifier == DirectionModifier::SlightRight)) ||
(instruction.type ==
TurnType::Continue && // Forks can be experienced, even for straight segments
(instruction.direction_modifier == DirectionModifier::SlightLeft ||
instruction.direction_modifier == DirectionModifier::SlightRight)) ||
instruction.type == TurnType::UseLane;
}
else if (tag == "slight_left" || tag == "left" || tag == "sharp_left")
{
if (isMirroredModifier(instruction))
return hasRightModifier(instruction);
else
{
// Needs to be fixed for left side driving
return (instruction.type == TurnType::StayOnRoundabout) || hasLeftModifier(instruction);
}
}
return false;
}
typename Intersection::const_iterator findBestMatch(const std::string &tag,
const Intersection &intersection)
{
const constexpr double idealized_turn_angles[] = {0, 35, 90, 135, 180, 225, 270, 315};
const auto idealized_angle = idealized_turn_angles[getMatchingModifier(tag)];
return std::min_element(
intersection.begin(),
intersection.end(),
[idealized_angle, &tag](const ConnectedRoad &lhs, const ConnectedRoad &rhs) {
// prefer valid matches
if (isValidMatch(tag, lhs.turn.instruction) != isValidMatch(tag, rhs.turn.instruction))
return isValidMatch(tag, lhs.turn.instruction);
// if the entry allowed flags don't match, we select the one with
// entry allowed set to true
if (lhs.entry_allowed != rhs.entry_allowed)
return lhs.entry_allowed;
return angularDeviation(idealized_angle, lhs.turn.angle) <
angularDeviation(idealized_angle, rhs.turn.angle);
});
}
typename Intersection::const_iterator findBestMatchForReverse(const std::string &leftmost_tag,
const Intersection &intersection)
{
const auto leftmost_itr = findBestMatch(leftmost_tag, intersection);
if (leftmost_itr + 1 == intersection.cend())
return intersection.begin();
const constexpr double idealized_turn_angles[] = {0, 35, 90, 135, 180, 225, 270, 315};
const std::string tag = "reverse";
const auto idealized_angle = idealized_turn_angles[getMatchingModifier(tag)];
return std::min_element(
intersection.begin() + std::distance(intersection.begin(), leftmost_itr),
intersection.end(),
[idealized_angle, &tag](const ConnectedRoad &lhs, const ConnectedRoad &rhs) {
// prefer valid matches
if (isValidMatch(tag, lhs.turn.instruction) != isValidMatch(tag, rhs.turn.instruction))
return isValidMatch(tag, lhs.turn.instruction);
// if the entry allowed flags don't match, we select the one with
// entry allowed set to true
if (lhs.entry_allowed != rhs.entry_allowed)
return lhs.entry_allowed;
return angularDeviation(idealized_angle, lhs.turn.angle) <
angularDeviation(idealized_angle, rhs.turn.angle);
});
}
bool canMatchTrivially(const Intersection &intersection, const LaneDataVector &lane_data)
{
std::size_t road_index = 1, lane = 0;
for (; road_index < intersection.size() && lane < lane_data.size(); ++road_index)
{
if (intersection[road_index].entry_allowed)
{
BOOST_ASSERT(lane_data[lane].from != INVALID_LANEID);
if (!isValidMatch(lane_data[lane].tag, intersection[road_index].turn.instruction))
return false;
if (findBestMatch(lane_data[lane].tag, intersection) !=
intersection.begin() + road_index)
return false;
++lane;
}
}
return lane == lane_data.size() ||
(lane + 1 == lane_data.size() && lane_data.back().tag == "reverse");
}
Intersection triviallyMatchLanesToTurns(Intersection intersection,
const LaneDataVector &lane_data,
const util::NodeBasedDynamicGraph &node_based_graph)
{
std::size_t road_index = 1, lane = 0;
for (; road_index < intersection.size() && lane < lane_data.size(); ++road_index)
{
if (intersection[road_index].entry_allowed)
{
BOOST_ASSERT(lane_data[lane].from != INVALID_LANEID);
BOOST_ASSERT(
isValidMatch(lane_data[lane].tag, intersection[road_index].turn.instruction));
BOOST_ASSERT(findBestMatch(lane_data[lane].tag, intersection) ==
intersection.begin() + road_index);
if (TurnType::Suppressed == intersection[road_index].turn.instruction.type)
intersection[road_index].turn.instruction.type = TurnType::UseLane;
intersection[road_index].turn.instruction.lane_tupel = {
LaneID(lane_data[lane].to - lane_data[lane].from + 1), lane_data[lane].from};
++lane;
}
}
// handle reverse tag, if present
if (lane + 1 == lane_data.size() && lane_data.back().tag == "reverse")
{
std::size_t u_turn = 0;
if (node_based_graph.GetEdgeData(intersection[0].turn.eid).reversed)
{
if (intersection.back().entry_allowed ||
intersection.back().turn.instruction.direction_modifier !=
DirectionModifier::SharpLeft)
{
// cannot match u-turn in a valid way
return std::move(intersection);
}
u_turn = intersection.size() - 1;
}
intersection[u_turn].entry_allowed = true;
intersection[u_turn].turn.instruction.type = TurnType::Turn;
intersection[u_turn].turn.instruction.direction_modifier = DirectionModifier::UTurn;
intersection[u_turn].turn.instruction.lane_tupel = {
LaneID(lane_data.back().to - lane_data.back().from + 1), lane_data.back().from};
}
return std::move(intersection);
}
} // namespace lane_matching
} // namespace guidance
} // namespace extractor
} // namespace osrm
+3
View File
@@ -77,6 +77,7 @@ void ScriptingEnvironment::InitContext(ScriptingEnvironment::Context &context)
luabind::module(context.state)
[luabind::def("durationIsValid", durationIsValid),
luabind::def("parseDuration", parseDuration),
luabind::def("trimLaneString", trimLaneString),
luabind::class_<TravelMode>("mode").enum_(
"enums")[luabind::value("inaccessible", TRAVEL_MODE_INACCESSIBLE),
luabind::value("driving", TRAVEL_MODE_DRIVING),
@@ -141,6 +142,8 @@ void ScriptingEnvironment::InitContext(ScriptingEnvironment::Context &context)
.def_readwrite("is_access_restricted", &ExtractionWay::is_access_restricted)
.def_readwrite("is_startpoint", &ExtractionWay::is_startpoint)
.def_readwrite("duration", &ExtractionWay::duration)
.def_readwrite("turn_lanes_forward", &ExtractionWay::turn_lanes_forward)
.def_readwrite("turn_lanes_backward", &ExtractionWay::turn_lanes_backward)
.property(
"forward_mode", &ExtractionWay::get_forward_mode, &ExtractionWay::set_forward_mode)
.property("backward_mode",