diff --git a/include/extractor/profile_properties.hpp b/include/extractor/profile_properties.hpp index 6c2a7596d49..58a191d4a5b 100644 --- a/include/extractor/profile_properties.hpp +++ b/include/extractor/profile_properties.hpp @@ -30,18 +30,8 @@ struct ProfileProperties traffic_signal_penalty = boost::numeric_cast(traffic_signal_penalty_ * 10.); } - double GetCrossingTrafficPenalty() const { return crossing_through_traffic_penalty / 10.; } - - void SetCrossingTrafficPenalty(const double crossing_through_traffic_penalty_) - { - crossing_through_traffic_penalty = boost::numeric_cast(crossing_through_traffic_penalty_ * 10.); - } - //! penalty to cross a traffic light in deci-seconds int traffic_signal_penalty; - // if a turn crosses through traffic, this additional penalty (in deci-seconds) describes the - // expected average delay. At some point this should be integrated into the turn function - int crossing_through_traffic_penalty; //! penalty to do a uturn in deci-seconds int u_turn_penalty; bool continue_straight_at_waypoint; diff --git a/include/extractor/scripting_environment.hpp b/include/extractor/scripting_environment.hpp index a450b3154f1..666d2f39fa9 100644 --- a/include/extractor/scripting_environment.hpp +++ b/include/extractor/scripting_environment.hpp @@ -5,6 +5,7 @@ #include "extractor/internal_extractor_edge.hpp" #include "extractor/profile_properties.hpp" #include "extractor/restriction.hpp" +#include "extractor/turn_penalty.hpp" #include @@ -12,6 +13,7 @@ #include +#include #include #include @@ -53,7 +55,12 @@ class ScriptingEnvironment virtual std::vector GetNameSuffixList() = 0; virtual std::vector GetExceptions() = 0; virtual void SetupSources() = 0; - virtual int32_t GetTurnPenalty(double angle) = 0; + virtual std::int32_t GetTurnPenalty(double angle) = 0; + virtual std::int32_t + GetDetailedTurnPenalty(const TurnProperties &turn_properties, + const IntersectionProperties &intersection_properties, + const TurnSegment &approach_segment, + const TurnSegment &exit_segment) = 0; virtual void ProcessSegment(const osrm::util::Coordinate &source, const osrm::util::Coordinate &target, double distance, diff --git a/include/extractor/scripting_environment_lua.hpp b/include/extractor/scripting_environment_lua.hpp index c55a4b67cc7..632b37d2713 100644 --- a/include/extractor/scripting_environment_lua.hpp +++ b/include/extractor/scripting_environment_lua.hpp @@ -2,6 +2,7 @@ #define SCRIPTING_ENVIRONMENT_LUA_HPP #include "extractor/scripting_environment.hpp" +#include "extractor/turn_penalty.hpp" #include "extractor/raster_source.hpp" @@ -9,6 +10,7 @@ #include +#include #include #include #include @@ -30,6 +32,7 @@ struct LuaScriptingContext final util::LuaState state; bool has_turn_penalty_function; + bool has_detailed_penalty_function; bool has_node_function; bool has_way_function; bool has_segment_function; @@ -55,7 +58,11 @@ class LuaScriptingEnvironment final : public ScriptingEnvironment std::vector GetNameSuffixList() override; std::vector GetExceptions() override; void SetupSources() override; - int32_t GetTurnPenalty(double angle) override; + std::int32_t GetTurnPenalty(double angle) override; + std::int32_t GetDetailedTurnPenalty(const TurnProperties &turn_properties, + const IntersectionProperties &intersection_properties, + const TurnSegment &approach_segment, + const TurnSegment &exit_segment) override; void ProcessSegment(const osrm::util::Coordinate &source, const osrm::util::Coordinate &target, double distance, diff --git a/include/extractor/turn_penalty.hpp b/include/extractor/turn_penalty.hpp new file mode 100644 index 00000000000..93ee7705288 --- /dev/null +++ b/include/extractor/turn_penalty.hpp @@ -0,0 +1,46 @@ +#ifndef OSRM_TURN_PENALTY_HPP_ +#define OSRM_TURN_PENALTY_HPP_ + +/* The turn functions offered by osrm come in two variations. One uses a basic angle. The other one + * offers a list of additional entries to improve the turn penalty */ + +namespace osrm +{ +namespace extractor +{ + +// properties describing the turn +struct TurnProperties +{ + // the turn angle between the ingoing/outgoing segments + double angle; + // the radius of the turn at the curve (approximated) + double radius; + // the turn is crossing through opposing traffic + bool crossing_through_traffic; + // indicate if the turn needs to be announced or if it is a turn following the obvious road + bool requires_announcement; +}; + +// properties describing intersection related penalties +struct IntersectionProperties +{ + // is the turn at a traffic light + bool traffic_light; + // is the turn at a stop-light + bool stop_sign; + // is the turn following a right-of-way situation + bool right_of_way; +}; + +// information about the segments coming in/out of the intersection +struct TurnSegment +{ + double length_in_meters; + double speed_in_meters_per_second; +}; + +} // namespace extractor +} // namespace osrm + +#endif // OSRM_TURN_PENALTY_HPP_ diff --git a/profiles/car.lua b/profiles/car.lua index 0c225d3ca7a..21683835c82 100644 --- a/profiles/car.lua +++ b/profiles/car.lua @@ -145,7 +145,6 @@ maxspeed_table = { properties.u_turn_penalty = 20 properties.traffic_signal_penalty = 2 -- if we are crossing through traffic on a turn, we add this penalty -properties.crossing_traffic_penalty = 5 properties.use_turn_restrictions = true properties.continue_straight_at_waypoint = true properties.left_hand_driving = false @@ -153,6 +152,8 @@ properties.left_hand_driving = false local side_road_speed_multiplier = 0.8 local turn_penalty = 7.5 +local crossing_through_traffic_penalty = 5 +local turn_announcement_penalty = 3 -- Note: this biases right-side driving. Should be -- inverted for left-driving countries. local turn_bias = properties.left_hand_driving and 1/1.075 or 1.075 @@ -541,14 +542,37 @@ function way_function (way, result) result.is_startpoint = result.forward_mode == mode.driving or result.backward_mode == mode.driving end +-- a detailed turn function as an alternative to turn function, offering more properties to decide on a good penalty +-- returns penalty in seconds +function detailed_turn_function(turn_properties, intersection_properties, approach_segment, exit_segment) + local penalty = 0; + if turn_properties.angle>=0 then + penalty = turn_penalty / (1 + 2.718 ^ - ((13 / turn_bias) * angle/180 - 6.5*turn_bias)) + else + penalty = turn_penalty / (1 + 2.718 ^ - ((13 * turn_bias) * - angle/180 - 6.5/turn_bias)) + end + + -- we make announced turns more expensive while, at the same time, we reduce the cost of suppressed turns + if turn_properties.requires_announcement then + penalty = penalty + turn_announcement_penalty + else + -- unanounced turns are cheaper than normal turns + penalty = penalty * 0.8; + end + + -- crossing through traffic is expensive + if turn_properties.crossing_through_traffic then + penalty = penalty + crossing_through_traffic_penalty + end + + return penalty; +end + + function turn_function (angle) -- Use a sigmoid function to return a penalty that maxes out at turn_penalty -- over the space of 0-180 degrees. Values here were chosen by fitting -- the function to some turn penalty samples from real driving. -- multiplying by 10 converts to deci-seconds see issue #1318 - if angle>=0 then - return 10 * turn_penalty / (1 + 2.718 ^ - ((13 / turn_bias) * angle/180 - 6.5*turn_bias)) - else - return 10 * turn_penalty / (1 + 2.718 ^ - ((13 * turn_bias) * - angle/180 - 6.5/turn_bias)) - end + return 0; end diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp index 42028760337..f522caac9c0 100644 --- a/src/extractor/edge_based_graph_factory.cpp +++ b/src/extractor/edge_based_graph_factory.cpp @@ -1,5 +1,6 @@ #include "extractor/edge_based_edge.hpp" #include "extractor/edge_based_graph_factory.hpp" +#include "extractor/turn_penalty.hpp" #include "util/coordinate.hpp" #include "util/coordinate_calculation.hpp" #include "util/exception.hpp" @@ -415,40 +416,35 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( }(turn_classification.second); bearing_class_by_node_based_node[node_v] = bearing_class_id; - bool crosses_through_traffic = false; + const bool crosses_traffic_light = + m_traffic_lights.find(node_v) != m_traffic_lights.end(); + bool crosses_through_traffic = false; + const EdgeData &edge_data_from_u = m_node_based_graph->GetEdgeData(edge_from_u); + const TurnSegment approach_segment = {static_cast(edge_data_from_u.distance), + static_cast(edge_data_from_u.distance)}; + const IntersectionProperties intersection_properties = { + crosses_traffic_light, false, false}; for (const auto turn : possible_turns) { // only add an edge if turn is not prohibited - const EdgeData &edge_data1 = m_node_based_graph->GetEdgeData(edge_from_u); + const EdgeData &edge_data2 = m_node_based_graph->GetEdgeData(turn.eid); - BOOST_ASSERT(edge_data1.edge_id != edge_data2.edge_id); - BOOST_ASSERT(!edge_data1.reversed); + BOOST_ASSERT(edge_data_from_u.edge_id != edge_data2.edge_id); + BOOST_ASSERT(!edge_data_from_u.reversed); BOOST_ASSERT(!edge_data2.reversed); // the following is the core of the loop. - unsigned distance = edge_data1.distance; - if (m_traffic_lights.find(node_v) != m_traffic_lights.end()) - { - distance += profile_properties.traffic_signal_penalty; - } - - const int32_t turn_penalty = - scripting_environment.GetTurnPenalty(180. - turn.angle); - const auto turn_instruction = turn.instruction; + unsigned distance = edge_data_from_u.distance; - if (turn_instruction.direction_modifier == guidance::DirectionModifier::UTurn) + if (crosses_traffic_light) { - distance += profile_properties.u_turn_penalty; + distance += profile_properties.traffic_signal_penalty; } - distance += turn_penalty; - - if (crosses_through_traffic) - distance += profile_properties.crossing_through_traffic_penalty; - - // a through street is an obvious turn on which we can expect traffic coming onto our direction + // a through street is an obvious turn on which we can expect traffic coming onto + // our direction const auto isThrough = [&](const guidance::TurnOperation &turn) { if (!((turn.instruction.type == guidance::TurnType::Suppressed) || (turn.instruction.type == guidance::TurnType::NewName) || @@ -461,6 +457,27 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( return !m_node_based_graph->GetEdgeData(eid).reversed; }; + const bool requires_announcement = isThrough(turn); + + const TurnProperties turn_properties = { + 180. - turn.angle, turn.angle, crosses_through_traffic, requires_announcement}; + + const TurnSegment exit_segment = {static_cast(edge_data2.distance), + static_cast(edge_data2.distance)}; + + const int32_t turn_penalty = + scripting_environment.GetTurnPenalty(180. - turn.angle) + + scripting_environment.GetDetailedTurnPenalty( + turn_properties, intersection_properties, approach_segment, exit_segment); + const auto turn_instruction = turn.instruction; + + if (turn_instruction.direction_modifier == guidance::DirectionModifier::UTurn) + { + distance += profile_properties.u_turn_penalty; + } + + distance += turn_penalty; + // if a turn is a through turn, all following turns are crossing through the // opposite traffic if (isThrough(turn)) @@ -469,11 +486,11 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( BOOST_ASSERT(m_compressed_edge_container.HasEntryForID(edge_from_u)); original_edge_data_vector.emplace_back( m_compressed_edge_container.GetPositionForID(edge_from_u), - edge_data1.name_id, + edge_data_from_u.name_id, turn.lane_data_id, turn_instruction, entry_class_id, - edge_data1.travel_mode); + edge_data_from_u.travel_mode); ++original_edges_counter; @@ -482,12 +499,12 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( FlushVectorToStream(edge_data_file, original_edge_data_vector); } - BOOST_ASSERT(SPECIAL_NODEID != edge_data1.edge_id); + BOOST_ASSERT(SPECIAL_NODEID != edge_data_from_u.edge_id); BOOST_ASSERT(SPECIAL_NODEID != edge_data2.edge_id); // NOTE: potential overflow here if we hit 2^32 routable edges BOOST_ASSERT(m_edge_based_edge_list.size() <= std::numeric_limits::max()); - m_edge_based_edge_list.emplace_back(edge_data1.edge_id, + m_edge_based_edge_list.emplace_back(edge_data_from_u.edge_id, edge_data2.edge_id, m_edge_based_edge_list.size(), distance, @@ -498,7 +515,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // the node-based edges that are originally used to calculate the `distance` // for the edge-expanded edges. About 40 lines back, there is: // - // unsigned distance = edge_data1.distance; + // unsigned distance = edge_data_from_u.distance; // // This tells us that the weight for an edge-expanded-edge is based on the weight // of the *source* node-based edge. Therefore, we will look up the individual @@ -558,7 +575,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( m_node_info_list[m_compressed_edge_container.GetFirstEdgeTargetID( turn.eid)]; - const unsigned fixed_penalty = distance - edge_data1.distance; + const unsigned fixed_penalty = distance - edge_data_from_u.distance; lookup::PenaltyBlock penaltyblock = { fixed_penalty, from_node.node_id, via_node.node_id, to_node.node_id}; edge_penalty_file.write(reinterpret_cast(&penaltyblock), diff --git a/src/extractor/scripting_environment_lua.cpp b/src/extractor/scripting_environment_lua.cpp index a1ff8b0d584..38850816870 100644 --- a/src/extractor/scripting_environment_lua.cpp +++ b/src/extractor/scripting_environment_lua.cpp @@ -194,6 +194,7 @@ void LuaScriptingEnvironment::InitContext(LuaScriptingContext &context) &ExtractionWay::get_backward_mode, &ExtractionWay::set_backward_mode), luabind::class_("WayNodeList").def(luabind::constructor<>()), + luabind::class_("NodeRef") .def(luabind::constructor<>()) // Dear ambitious reader: registering .location() as in: @@ -201,22 +202,43 @@ void LuaScriptingEnvironment::InitContext(LuaScriptingContext &context) // will crash at runtime, since we're not (yet?) using libosnmium's // NodeLocationsForWays cache .def("id", &osmium::NodeRef::ref), + luabind::class_("Way") .def("get_value_by_key", &osmium::Way::get_value_by_key) .def("get_value_by_key", &get_value_by_key) .def("id", &osmium::Way::id) .def("get_nodes", get_nodes_for_way, luabind::return_stl_iterator), + + luabind::class_("TurnProperties") + .def_readonly("angle", &TurnProperties::angle) + .def_readonly("radius", &TurnProperties::radius) + .def_readonly("crossing_through_traffic", &TurnProperties::crossing_through_traffic) + .def_readonly("requires_announcement", &TurnProperties::requires_announcement), + + luabind::class_("IntersectionProperties") + .def_readonly("traffic_light", &IntersectionProperties::traffic_light) + .def_readonly("stop_sign", &IntersectionProperties::stop_sign) + .def_readonly("right_of_way", &IntersectionProperties::right_of_way), + + luabind::class_("TurnSegment") + .def_readonly("length_in_meters", &TurnSegment::length_in_meters) + .def_readonly("speed_in_meters_per_second", &TurnSegment::speed_in_meters_per_second), + luabind::class_("EdgeSource") .def_readonly("source_coordinate", &InternalExtractorEdge::source_coordinate) .def_readwrite("weight_data", &InternalExtractorEdge::weight_data), + luabind::class_("WeightData") .def_readwrite("speed", &InternalExtractorEdge::WeightData::speed), + luabind::class_("EdgeTarget") .property("lon", &lonToDouble) .property("lat", &latToDouble), + luabind::class_("Coordinate") .property("lon", &lonToDouble) .property("lat", &latToDouble), + luabind::class_("RasterDatum") .def_readonly("datum", &RasterDatum::datum) .def("invalid_data", &RasterDatum::get_invalid)]; @@ -233,6 +255,8 @@ void LuaScriptingEnvironment::InitContext(LuaScriptingContext &context) } context.has_turn_penalty_function = util::luaFunctionExists(context.state, "turn_function"); + context.has_detailed_penalty_function = + util::luaFunctionExists(context.state, "detailed_turn_function"); context.has_node_function = util::luaFunctionExists(context.state, "node_function"); context.has_way_function = util::luaFunctionExists(context.state, "way_function"); context.has_segment_function = util::luaFunctionExists(context.state, "segment_function"); @@ -354,7 +378,7 @@ void LuaScriptingEnvironment::SetupSources() } } -int32_t LuaScriptingEnvironment::GetTurnPenalty(const double angle) +std::int32_t LuaScriptingEnvironment::GetTurnPenalty(const double angle) { auto &context = GetLuaContext(); if (context.has_turn_penalty_function) @@ -365,8 +389,36 @@ int32_t LuaScriptingEnvironment::GetTurnPenalty(const double angle) // call lua profile to compute turn penalty const double penalty = luabind::call_function(context.state, "turn_function", angle); - BOOST_ASSERT(penalty < std::numeric_limits::max()); - BOOST_ASSERT(penalty > std::numeric_limits::min()); + return boost::numeric_cast(penalty); + } + catch (const luabind::error &er) + { + util::SimpleLogger().Write(logWARNING) << er.what(); + } + } + return 0; +} + +std::int32_t LuaScriptingEnvironment::GetDetailedTurnPenalty( + const TurnProperties &turn_properties, + const IntersectionProperties &intersection_properties, + const TurnSegment &approach_segment, + const TurnSegment &exit_segment) +{ + auto &context = GetLuaContext(); + if (context.has_turn_penalty_function) + { + BOOST_ASSERT(context.state != nullptr); + try + { + // call lua profile to compute turn penalty + const double penalty = + 10.0 * luabind::call_function(context.state, + "detailed_turn_function", + boost::cref(turn_properties), + boost::cref(intersection_properties), + boost::cref(approach_segment), + boost::cref(exit_segment)); return boost::numeric_cast(penalty); } catch (const luabind::error &er)