adds distinction between rotaries/rounabouts

This commit is contained in:
Moritz Kobitzsch 2016-03-23 15:28:53 +01:00 committed by Patrick Niklaus
parent 278ec04f5e
commit f2443c64db
10 changed files with 270 additions and 42 deletions

View File

@ -44,6 +44,7 @@ std::vector<RouteStep> assembleSteps(const DataFacadeT &facade,
const bool target_traversed_in_reverse)
{
const double constexpr ZERO_DURATION = 0., ZERO_DISTANCE = 0.;
const constexpr char* NO_ROTARY_NAME = "";
const EdgeWeight source_duration =
source_traversed_in_reverse ? source_node.reverse_weight : source_node.forward_weight;
const auto source_mode = source_traversed_in_reverse ? source_node.backward_travel_mode
@ -86,6 +87,7 @@ std::vector<RouteStep> assembleSteps(const DataFacadeT &facade,
const auto distance = leg_geometry.segment_distances[segment_index];
steps.push_back(RouteStep{path_point.name_id,
name,
NO_ROTARY_NAME,
segment_duration / 10.0,
distance,
path_point.travel_mode,
@ -103,6 +105,7 @@ std::vector<RouteStep> assembleSteps(const DataFacadeT &facade,
BOOST_ASSERT(duration >= 0);
steps.push_back(RouteStep{target_node.name_id,
facade.GetNameForID(target_node.name_id),
NO_ROTARY_NAME,
duration / 10.,
distance,
target_mode,
@ -126,6 +129,7 @@ std::vector<RouteStep> assembleSteps(const DataFacadeT &facade,
steps.push_back(RouteStep{source_node.name_id,
facade.GetNameForID(source_node.name_id),
NO_ROTARY_NAME,
duration / 10.,
leg_geometry.segment_distances[segment_index],
source_mode,
@ -140,6 +144,7 @@ std::vector<RouteStep> assembleSteps(const DataFacadeT &facade,
extractor::guidance::TurnInstruction::NO_TURN(), WaypointType::Arrive, leg_geometry);
steps.push_back(RouteStep{target_node.name_id,
facade.GetNameForID(target_node.name_id),
NO_ROTARY_NAME,
ZERO_DURATION,
ZERO_DISTANCE,
target_mode,

View File

@ -25,6 +25,7 @@ struct RouteStep
{
unsigned name_id;
std::string name;
std::string rotary_name;
double duration;
double distance;
extractor::TravelMode mode;

View File

@ -350,7 +350,8 @@ inline bool requiresNameAnnounced(const std::string &from, const std::string &to
const auto ref_begin = name.find_first_of('(');
if (ref_begin != std::string::npos)
{
out_name = name.substr(0, ref_begin);
//we might need to trim a space, if there is a reference
out_name = name.substr(0, ref_begin - (ref_begin > 0 && name[ref_begin-1] == ' '));
out_ref = name.substr(ref_begin + 1, name.find_first_of(')') - 1);
}
else

View File

@ -112,7 +112,8 @@ class TurnAnalysis
// Processing of roundabouts
// Produces instructions to enter/exit a roundabout or to stay on it.
// Performs the distinction between roundabout and rotaries.
std::vector<ConnectedRoad> handleRoundabouts(const EdgeID via_edge,
std::vector<ConnectedRoad> handleRoundabouts(const bool is_rotary,
const EdgeID via_edge,
const bool on_roundabout,
const bool can_exit_roundabout,
std::vector<ConnectedRoad> intersection) const;
@ -152,8 +153,8 @@ class TurnAnalysis
std::vector<ConnectedRoad> intersection) const;
// Any Junction containing motorways
std::vector<ConnectedRoad> handleMotorwayJunction(
const EdgeID via_edge, std::vector<ConnectedRoad> intersection) const;
std::vector<ConnectedRoad>
handleMotorwayJunction(const EdgeID via_edge, std::vector<ConnectedRoad> intersection) const;
std::vector<ConnectedRoad> handleFromMotorway(const EdgeID via_edge,
std::vector<ConnectedRoad> intersection) const;
@ -193,6 +194,7 @@ class TurnAnalysis
std::vector<ConnectedRoad> intersection,
const std::size_t up_to) const;
bool isRotary(const NodeID nid) const;
}; // class TurnAnalysis
} // namespace guidance

View File

@ -89,24 +89,32 @@ struct TurnInstruction
return TurnInstruction(TurnType::NoTurn, DirectionModifier::UTurn);
}
static TurnInstruction REMAIN_ROUNDABOUT(const DirectionModifier modifier)
static TurnInstruction REMAIN_ROUNDABOUT(bool is_rotary, const DirectionModifier modifier)
{
(void)is_rotary; // staying does not require a different instruction at the moment
return TurnInstruction(TurnType::StayOnRoundabout, modifier);
}
static TurnInstruction ENTER_ROUNDABOUT(const DirectionModifier modifier)
static TurnInstruction ENTER_ROUNDABOUT(bool is_rotary, const DirectionModifier modifier)
{
return TurnInstruction(TurnType::EnterRoundabout, modifier);
return {is_rotary ? TurnType::EnterRotary : TurnType::EnterRoundabout, modifier};
}
static TurnInstruction EXIT_ROUNDABOUT(const DirectionModifier modifier)
static TurnInstruction EXIT_ROUNDABOUT(bool is_rotary, const DirectionModifier modifier)
{
return TurnInstruction(TurnType::ExitRoundabout, modifier);
return {is_rotary ? TurnType::ExitRotary : TurnType::ExitRoundabout, modifier};
}
static TurnInstruction ENTER_AND_EXIT_ROUNDABOUT(bool is_rotary,
const DirectionModifier modifier)
{
return {is_rotary ? TurnType::EnterAndExitRotary : TurnType::EnterAndExitRoundabout,
modifier};
}
static TurnInstruction SUPPRESSED(const DirectionModifier modifier)
{
return TurnInstruction{TurnType::Suppressed, modifier};
return {TurnType::Suppressed, modifier};
}
};

View File

@ -4,6 +4,7 @@
#include "util/coordinate.hpp"
#include <boost/math/constants/constants.hpp>
#include <boost/optional.hpp>
#include <utility>
@ -61,6 +62,16 @@ double bearing(const Coordinate first_coordinate, const Coordinate second_coordi
// Get angle of line segment (A,C)->(C,B)
double computeAngle(const Coordinate first, const Coordinate second, const Coordinate third);
// find the center of a circle through three coordinates
boost::optional<Coordinate> circleCenter(const Coordinate first_coordinate,
const Coordinate second_coordinate,
const Coordinate third_coordinate);
// find the radius of a circle through three coordinates
double circleRadius(const Coordinate first_coordinate,
const Coordinate second_coordinate,
const Coordinate third_coordinate);
// factor in [0,1]. Returns point along the straight line between from and to. 0 returns from, 1
// returns to
Coordinate interpolateLinear(double factor, const Coordinate from, const Coordinate to);

View File

@ -1,16 +1,16 @@
#include "engine/api/json_factory.hpp"
#include "engine/polyline_compressor.hpp"
#include "engine/hint.hpp"
#include "engine/polyline_compressor.hpp"
#include <boost/assert.hpp>
#include <boost/range/irange.hpp>
#include <boost/optional.hpp>
#include <boost/range/irange.hpp>
#include <string>
#include <utility>
#include <algorithm>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
using TurnType = osrm::extractor::guidance::TurnType;
@ -28,23 +28,17 @@ namespace json
namespace detail
{
const constexpr char *modifier_names[] = {"uturn",
"sharp right",
"right",
"slight right",
"straight",
"slight left",
"left",
"sharp left"};
const constexpr char *modifier_names[] = {"uturn", "sharp right", "right", "slight right",
"straight", "slight left", "left", "sharp left"};
// translations of TurnTypes. Not all types are exposed to the outside world.
// invalid types should never be returned as part of the API
const constexpr char *turn_type_names[] = {
"invalid", "no turn", "invalid", "new name", "continue", "turn",
"turn", "turn", "turn", "turn", "merge", "ramp",
"ramp", "ramp", "ramp", "ramp", "fork", "end of road",
"roundabout", "invalid", "roundabout", "invalid", "traffic circle", "invalid",
"traffic circle", "invalid", "invalid", "restriction", "notification"};
"invalid", "no turn", "invalid", "new name", "continue", "turn",
"turn", "turn", "turn", "turn", "merge", "ramp",
"ramp", "ramp", "ramp", "ramp", "fork", "end of road",
"roundabout", "invalid", "roundabout", "invalid", "rotary", "invalid",
"rotary", "invalid", "invalid", "restriction", "notification"};
const constexpr char *waypoint_type_names[] = {"invalid", "arrive", "depart"};
// Check whether to include a modifier in the result of the API
@ -144,6 +138,7 @@ util::json::Object makeStepManeuver(const guidance::StepManeuver &maneuver)
if (detail::isValidModifier(maneuver))
step_maneuver.values["modifier"] =
detail::instructionModifierToString(maneuver.instruction.direction_modifier);
step_maneuver.values["location"] = detail::coordinateToLonLat(maneuver.location);
step_maneuver.values["bearing_before"] = std::round(maneuver.bearing_before);
step_maneuver.values["bearing_after"] = std::round(maneuver.bearing_after);
@ -164,6 +159,9 @@ util::json::Object makeRouteStep(guidance::RouteStep step, util::json::Value geo
route_step.values["distance"] = std::round(step.distance * 10) / 10.;
route_step.values["duration"] = std::round(step.duration * 10) / 10.;
route_step.values["name"] = std::move(step.name);
if (!step.rotary_name.empty())
route_step.values["rotary_name"] = std::move(step.rotary_name);
route_step.values["mode"] = detail::modeToString(std::move(step.mode));
route_step.values["maneuver"] = makeStepManeuver(std::move(step.maneuver));
route_step.values["geometry"] = std::move(geometry);
@ -215,8 +213,7 @@ util::json::Array makeRouteLegs(std::vector<guidance::RouteLeg> legs,
json_steps.values.reserve(leg.steps.size());
std::transform(
std::make_move_iterator(leg.steps.begin()), std::make_move_iterator(leg.steps.end()),
std::back_inserter(json_steps.values), [&step_geometry_iter](guidance::RouteStep step)
{
std::back_inserter(json_steps.values), [&step_geometry_iter](guidance::RouteStep step) {
return makeRouteStep(std::move(step), std::move(*step_geometry_iter++));
});
json_legs.values.push_back(makeRouteLeg(std::move(leg), std::move(json_steps)));

View File

@ -54,6 +54,11 @@ void fixFinalRoundabout(std::vector<RouteStep> &steps)
{
propagation_step.maneuver.exit = 0;
propagation_step.geometry_end = steps.back().geometry_begin;
if( propagation_step.maneuver.instruction.type == TurnType::EnterRotary ||
propagation_step.maneuver.instruction.type == TurnType::EnterRotaryAtExit )
propagation_step.rotary_name = propagation_step.name;
break;
}
else if (propagation_step.maneuver.instruction.type == TurnType::StayOnRoundabout)
@ -80,18 +85,20 @@ bool setUpRoundabout(RouteStep &step)
step.maneuver.exit = 1;
// prevent futher special case handling of these two.
if (instruction.type == TurnType::EnterRotaryAtExit)
step.maneuver.instruction = TurnType::EnterRotary;
step.maneuver.instruction.type = TurnType::EnterRotary;
else
step.maneuver.instruction = TurnType::EnterRoundabout;
step.maneuver.instruction.type = TurnType::EnterRoundabout;
}
if (leavesRoundabout(instruction))
{
step.maneuver.exit = 1; // count the otherwise missing exit
if (instruction.type == TurnType::EnterRotaryAtExit)
step.maneuver.instruction = TurnType::EnterRotary;
// prevent futher special case handling of these two.
if (instruction.type == TurnType::EnterAndExitRotary)
step.maneuver.instruction.type = TurnType::EnterRotary;
else
step.maneuver.instruction = TurnType::EnterRoundabout;
step.maneuver.instruction.type = TurnType::EnterRoundabout;
return false;
}
else
@ -123,6 +130,8 @@ void closeOffRoundabout(const bool on_roundabout,
steps[1].maneuver.instruction.type = step.maneuver.instruction.type == TurnType::ExitRotary
? TurnType::EnterRotary
: TurnType::EnterRoundabout;
if( steps[1].maneuver.instruction.type == TurnType::EnterRotary )
steps[1].rotary_name = steps[0].name;
}
// Normal exit from the roundabout, or exit from a previously fixed roundabout.
@ -144,6 +153,11 @@ void closeOffRoundabout(const bool on_roundabout,
propagation_step.maneuver.exit = step.maneuver.exit;
propagation_step.geometry_end = step.geometry_end;
// remember rotary name
if( propagation_step.maneuver.instruction.type == TurnType::EnterRotary ||
propagation_step.maneuver.instruction.type == TurnType::EnterRotaryAtExit )
propagation_step.rotary_name = propagation_step.name;
propagation_step.name = step.name;
propagation_step.name_id = step.name_id;
break;
@ -482,7 +496,7 @@ LegGeometry resyncGeometry(LegGeometry leg_geometry, const std::vector<RouteStep
leg_geometry.segment_offsets.push_back(step.geometry_end - 1);
}
// remove the data fromt the reached-target step again
// remove the data from the reached-target step again
leg_geometry.segment_offsets.pop_back();
leg_geometry.segment_distances.pop_back();

View File

@ -2,10 +2,13 @@
#include "util/simple_logger.hpp"
#include "util/coordinate.hpp"
#include "util/coordinate_calculation.hpp"
#include <cstddef>
#include <limits>
#include <iomanip>
#include <set>
#include <unordered_set>
namespace osrm
{
@ -95,6 +98,111 @@ inline bool isRampClass(EdgeID eid, const util::NodeBasedDynamicGraph &node_base
} // namespace detail
bool TurnAnalysis::isRotary(const NodeID nid) const
{
// translate a node ID into its respective coordinate stored in the node_info_list
const auto getCoordinate = [this](const NodeID node)
{
return util::Coordinate(node_info_list[node].lon, node_info_list[node].lat);
};
unsigned roundabout_name_id = 0;
std::unordered_set<unsigned> connected_names;
const auto getNextOnRoundabout =
[this, &roundabout_name_id, &connected_names](const NodeID node)
{
EdgeID continue_edge = SPECIAL_EDGEID;
for (const auto edge : node_based_graph.GetAdjacentEdgeRange(node))
{
const auto &edge_data = node_based_graph.GetEdgeData(edge);
if (!edge_data.reversed && edge_data.roundabout)
{
if (SPECIAL_EDGEID != continue_edge)
{
// fork in roundabout
return SPECIAL_EDGEID;
}
// roundabout does not keep its name
if (roundabout_name_id != 0 && roundabout_name_id != edge_data.name_id &&
requiresNameAnnounced(name_table.GetNameForID(roundabout_name_id),
name_table.GetNameForID(edge_data.name_id)))
{
return SPECIAL_EDGEID;
}
roundabout_name_id = edge_data.name_id;
continue_edge = edge;
}
else if (!edge_data.roundabout)
{
// remember all connected road names
connected_names.insert(edge_data.name_id);
}
}
return continue_edge;
};
// the roundabout radius has to be the same for all locations we look at it from
// to guarantee this, we search the full roundabout for its vertices
// and select the three smalles ids
std::set<NodeID> roundabout_nodes; // needs to be sorted
// this value is a hard abort to deal with potential self-loops
NodeID last_node = nid;
while (0 == roundabout_nodes.count(last_node))
{
roundabout_nodes.insert(last_node);
const auto eid = getNextOnRoundabout(last_node);
if (eid == SPECIAL_EDGEID)
{
util::SimpleLogger().Write(logDEBUG) << "Non-Loop Roundabout found.";
return false;
}
last_node = node_based_graph.GetTarget(eid);
}
// do we have a dedicated name for the rotary, if not its a roundabout
// This function can theoretically fail if the roundabout name is partly
// used with a reference and without. This will be fixed automatically
// when we handle references separately or if the useage is more consistent
if (roundabout_name_id == 0 || connected_names.count(roundabout_name_id))
{
return false;
}
if (roundabout_nodes.size() <= 1)
{
return false;
}
// calculate the radius of the roundabout/rotary. For two coordinates, we assume a minimal
// circle
// with both vertices right at the other side (so half their distance in meters).
// Otherwise, we construct a circle through the first tree vertices.
auto node_itr = roundabout_nodes.begin();
const double radius = roundabout_nodes.size() >= 3
? util::coordinate_calculation::circleRadius(
getCoordinate(*node_itr++),
getCoordinate(*node_itr++),
getCoordinate(*node_itr))
: 0.5 * util::coordinate_calculation::haversineDistance(
getCoordinate(*node_itr++),
getCoordinate(*node_itr));
// check whether the circle computation has gone wrong
// The radius computation can result in infinity, if the three coordinates are non-distinct.
// To stay on the safe side, we say its not a rotary
if (std::isinf(radius))
return false;
const double constexpr MAX_ROUNDABOUT_RADIUS = 15; // 30 m diameter as final distinction
return radius > MAX_ROUNDABOUT_RADIUS;
}
std::vector<TurnOperation> TurnAnalysis::getTurns(const NodeID from, const EdgeID via_edge) const
{
localizer.node_info_list = &node_info_list;
@ -132,7 +240,9 @@ std::vector<TurnOperation> TurnAnalysis::getTurns(const NodeID from, const EdgeI
}
if (on_roundabout || can_enter_roundabout)
{
intersection = handleRoundabouts(via_edge, on_roundabout, can_exit_roundabout_separately,
bool is_rotary = isRotary(node_based_graph.GetTarget(via_edge));
// find the radius of the roundabout
intersection = handleRoundabouts(is_rotary, via_edge, on_roundabout, can_exit_roundabout_separately,
std::move(intersection));
}
else
@ -180,7 +290,8 @@ inline std::size_t countValid(const std::vector<ConnectedRoad> &intersection)
}
std::vector<ConnectedRoad>
TurnAnalysis::handleRoundabouts(const EdgeID via_edge,
TurnAnalysis::handleRoundabouts(const bool is_rotary,
const EdgeID via_edge,
const bool on_roundabout,
const bool can_exit_roundabout_separately,
std::vector<ConnectedRoad> intersection) const
@ -207,12 +318,13 @@ TurnAnalysis::handleRoundabouts(const EdgeID via_edge,
else
{
turn.instruction =
TurnInstruction::REMAIN_ROUNDABOUT(getTurnDirection(turn.angle));
TurnInstruction::REMAIN_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle));
}
}
else
{
turn.instruction = TurnInstruction::EXIT_ROUNDABOUT(getTurnDirection(turn.angle));
turn.instruction =
TurnInstruction::EXIT_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle));
}
}
return intersection;
@ -227,7 +339,8 @@ TurnAnalysis::handleRoundabouts(const EdgeID via_edge,
const auto &out_data = node_based_graph.GetEdgeData(turn.eid);
if (out_data.roundabout)
{
turn.instruction = TurnInstruction::ENTER_ROUNDABOUT(getTurnDirection(turn.angle));
turn.instruction =
TurnInstruction::ENTER_ROUNDABOUT(is_rotary, getTurnDirection(turn.angle));
if (can_exit_roundabout_separately)
{
if (turn.instruction.type == TurnType::EnterRotary)
@ -238,7 +351,8 @@ TurnAnalysis::handleRoundabouts(const EdgeID via_edge,
}
else
{
turn.instruction = {TurnType::EnterAndExitRoundabout, getTurnDirection(turn.angle)};
turn.instruction = TurnInstruction::ENTER_AND_EXIT_ROUNDABOUT(
is_rotary, getTurnDirection(turn.angle));
}
}
return intersection;
@ -1059,7 +1173,7 @@ void TurnAnalysis::handleDistinctConflict(const EdgeID via_edge,
}
else
{
// FIXME this should possibly know about the actual roads?
// FIXME this should possibly know aboat the actual roads?
left.turn.instruction = getInstructionForObvious(4, via_edge, left);
right.turn.instruction = {findBasicTurnType(via_edge, right),
DirectionModifier::SlightRight};

View File

@ -209,6 +209,81 @@ double computeAngle(const Coordinate first, const Coordinate second, const Coord
return angle;
}
boost::optional<Coordinate>
circleCenter(const Coordinate C1, const Coordinate C2, const Coordinate C3)
{
// free after http://paulbourke.net/geometry/circlesphere/
// require three distinct points
if (C1 == C2 || C2 == C3 || C1 == C3)
return boost::none;
// define line through c1, c2 and c2,c3
const double C2C1_lat = static_cast<double>(toFloating(C2.lat - C1.lat)); // yDelta_a
const double C2C1_lon = static_cast<double>(toFloating(C2.lon - C1.lon)); // xDelta_a
const double C3C2_lat = static_cast<double>(toFloating(C3.lat - C2.lat)); // yDelta_b
const double C3C2_lon = static_cast<double>(toFloating(C3.lon - C2.lon)); // xDelta_b
// check for collinear points in X-Direction
if (std::abs(C2C1_lon) < std::numeric_limits<double>::epsilon() &&
std::abs(C3C2_lon) < std::numeric_limits<double>::epsilon())
{
return boost::none;
}
else if (std::abs(C2C1_lon) < std::numeric_limits<double>::epsilon())
{
// vertical line C2C1
// due to c1.lon == c2.lon && c1.lon != c3.lon we can rearrange this way
BOOST_ASSERT(std::abs(static_cast<double>(toFloating(C3.lon - C1.lon))) >=
std::numeric_limits<double>::epsilon() &&
std::abs(static_cast<double>(toFloating(C2.lon - C3.lon))) >=
std::numeric_limits<double>::epsilon());
return circleCenter(C1, C3, C2);
}
else if (std::abs(C3C2_lon) < std::numeric_limits<double>::epsilon())
{
// vertical line C3C2
// due to c2.lon == c3.lon && c1.lon != c3.lon we can rearrange this way
// after rearrangement both deltas will be zero
BOOST_ASSERT(std::abs(static_cast<double>(toFloating(C1.lon - C2.lon))) >=
std::numeric_limits<double>::epsilon() &&
std::abs(static_cast<double>(toFloating(C3.lon - C1.lon))) >=
std::numeric_limits<double>::epsilon());
return circleCenter(C2, C1, C3);
}
else
{ // valid slope values for both lines, calculate the center as intersection of the lines
const double C2C1_slope = C2C1_lat / C2C1_lon;
const double C3C2_slope = C3C2_lat / C3C2_lon;
//can this ever happen?
if (std::abs(C2C1_slope - C3C2_slope) < std::numeric_limits<double>::epsilon())
return boost::none;
const double C1_y = static_cast<double>(toFloating(C1.lat));
const double C1_x = static_cast<double>(toFloating(C1.lon));
const double C2_y = static_cast<double>(toFloating(C2.lat));
const double C2_x = static_cast<double>(toFloating(C2.lon));
const double C3_y = static_cast<double>(toFloating(C3.lat));
const double C3_x = static_cast<double>(toFloating(C3.lon));
const double lon = (C2C1_slope * C3C2_slope * (C1_y - C3_y) + C3C2_slope * (C1_x + C2_x) -
C2C1_slope * (C2_x + C3_x)) /
(2 * (C3C2_slope - C2C1_slope));
const double lat = (0.5 * (C1_x + C2_x) - lon) / C2C1_slope + 0.5 * (C1_y + C2_y);
return Coordinate(FloatLongitude(lon), FloatLatitude(lat));
}
}
double circleRadius(const Coordinate C1, const Coordinate C2, const Coordinate C3)
{
// a circle by three points requires thee distinct points
auto center = circleCenter(C1, C2, C3);
if (center)
return haversineDistance(C1, *center);
else
return std::numeric_limits<double>::infinity();
}
Coordinate interpolateLinear(double factor, const Coordinate from, const Coordinate to)
{
BOOST_ASSERT(0 <= factor && factor <= 1.0);