- How OSRM Calculate Weight and Duration
This document will try to explain how does OSRM calculate weight and duration of a route. All the calculation of static weight and duration will be done in osrm-extract
, which converts original map data (i.e. OSM) to an OSRM edge-expanded graph.
- Refer to Understanding OSRM Graph Representation for the Terminology OSRM edge-expanded graph.
- Refer to osrm-extract Startup and Process Call Graph to get a whole picture of
osrm-extract
.
Below are some miscellaneous for this document:
- This document will focus on how to calculate weight and duration for a route for car, since generating route for car is one of the most important and most complex feature of OSRM. It means:
- Only
car.lua
will be focused on, other profiles will be ignored. - Only default behavior of car related codes will be read, ignore other unrelated codes.
- Only
- Many of description will be represented by comments
--[Jay] ...
inLua
code snippets or//[Jay] ...
inC++
code snippets.
Name | Description |
---|---|
speed |
Unit: km/h. This will result in the best estimated travel times. |
duration |
Estimated travel times. Unit: seconds. |
rate |
The rate is an abstract measure that you can assign to ways as you like to make some ways preferable to others. Routing will prefer ways with high rate . It represents weight per meter. The unit will be meters per second if the weight is time based. |
weight |
The weight of a way is normally computed as length / rate . The weight can be thought of as the resistance or cost when passing the way. Routing will prefer ways with low weight . You can also set the weight of a way to a fixed value. In this case it's not calculated based on the length or rate , and the rate is ignored. |
weight_name |
Name used in output for the routing weight property, i.e. indicating which weight was used. - weight_name=routability : For routing based on duration, but weighted for preferring certain roads. - weight_name=duration : For shortest duration without penalties for accessibility. - weight_name=distance : For shortest distance without penalties for accessibility. |
Refer to OSRM Profiles - Understanding speed, weight and rate for more explaination of these concepts.
It's a good idea to read OSRM Profiles first. It explains why need profiles and how does it work. Meanwhile, it also describes structure and elements details of profiles.
A profile describes whether or not it's possible to route along a particular type of way, whether we can pass a particular node, and how quickly we'll be traveling when we do.
A profile provided functions to process for OSM node/way/turn, refer to Interaction Between C++ and Lua In OSRM for what are these functions, what they actually do and where they're invoked.
Most of handles in the Lua
function process_way()
are dealing with whether a way routable. Also, some of them are related on speed
/rate
processing.
In below codes, the values of several OSM keys Key:highway, Key:route and Key:bridge have been retrieved first.
function process_way(profile, way, result, relations)
-- [Jay] retrieve values of `highway`,`bridge`,`route` for post way handlers
-- data table for storing intermediate values during processing
local data = {
-- prefetch tags
highway = way:get_value_by_key('highway'),
bridge = way:get_value_by_key('bridge'),
route = way:get_value_by_key('route')
}
handlers = Sequence {
-- [Jay] define way handlers
-- ...
}
-- [Jay] run way handlers sequentially
WayHandlers.run(profile, way, result, data, handlers, relations)
-- [Jay] others
end
Let's look inside speed
/rate
related sub-functions of process_way()
one-by-one.
There're two kinds of values will be got after these sub-functions:
forward_speed/backward_speed/forward_rate/backward_rate
for a normal and routable way;- or
weight/duration
for routable ferry/shuttle_train/movable bridge.
-
Referenced OSM keys/values:
- Key:route: route=ferry, route=shuttle_train
- For route=ferry and route=shuttle_train, there're pre-defined
route_speed
incar.lua
. All other values of Key:route will be ignored.
- For route=ferry and route=shuttle_train, there're pre-defined
- Key:duration
- If route=ferry or route=shuttle_train exists,
- firstly will try to get value of Key:duration as
duration
directly, since it's highly recommended for indicating how long the route takes ('00:05' is 5 minutes, '1:15' an hour fifteen, or '48:00' two days). - otherwise use the pre-defined
route_speed
to calculateweight
/duration
later, similar with normal ways.
- firstly will try to get value of Key:duration as
- If route=ferry or route=shuttle_train exists,
- Key:route: route=ferry, route=shuttle_train
-
Codes
-- handling ferries and piers
function WayHandlers.ferries(profile,way,result,data)
local route = data.route
if route then
local route_speed = profile.route_speeds[route]
if route_speed and route_speed > 0 then
local duration = way:get_value_by_key("duration")
if duration and durationIsValid(duration) then
result.duration = math.max( parseDuration(duration), 1 )
end
result.forward_mode = mode.ferry
result.backward_mode = mode.ferry
result.forward_speed = route_speed
result.backward_speed = route_speed
end
end
end
route_speeds = {
ferry = 5,
shuttle_train = 10
},
-
Referenced OSM keys/values:
- Key:bridge: bridge:movable
- For bridge:movable, there're pre-defined
bridge_speeds
incar.lua
. All other values of Key:bridge will be ignored.
- For bridge:movable, there're pre-defined
- capacity:car
- Key:duration
- If bridge:movable exists and can be passed by car, i.e. capacity:car valid,
- firstly will try to get value of Key:duration as
duration
directly, since it's highly recommended for indicating how long the route takes ('00:05' is 5 minutes, '1:15' an hour fifteen, or '48:00' two days). - otherwise use the pre-defined
bridge_speeds
to calculateweight
/duration
later, similar with normal ways.
- firstly will try to get value of Key:duration as
- If bridge:movable exists and can be passed by car, i.e. capacity:car valid,
- Key:bridge: bridge:movable
-
Codes
-- handling movable bridges
function WayHandlers.movables(profile,way,result,data)
local bridge = data.bridge
if bridge then
local bridge_speed = profile.bridge_speeds[bridge]
if bridge_speed and bridge_speed > 0 then
local capacity_car = way:get_value_by_key("capacity:car")
if capacity_car ~= 0 then
result.forward_mode = profile.default_mode
result.backward_mode = profile.default_mode
local duration = way:get_value_by_key("duration")
if duration and durationIsValid(duration) then
result.duration = math.max( parseDuration(duration), 1 )
else
result.forward_speed = bridge_speed
result.backward_speed = bridge_speed
end
end
end
end
end
bridge_speeds = {
movable = 5
},
- Referenced OSM keys/values:
- Key:highway
- If value of Key:highway exist in pre-defined
speeds
, prefer to use it. - otherwise use
default_speed
instead if routable.
- If value of Key:highway exist in pre-defined
- Key:highway
- Codes
- WayHandlers.speed in way_handlers.lua
- Tags.get_constant_by_key_value in tags.lua
- Tags.get_forward_backward_by_set in tags.lua
- speed definition in car.lua
- access_tag_whitelist definition in car.lua
- access_tag_blacklist definition in car.lua
- default_speed definition in car.lua
- access_tags_hierarchy definition in car.lua
-- handle speed (excluding maxspeed)
function WayHandlers.speed(profile,way,result,data)
if result.forward_speed ~= -1 then
return -- abort if already set, eg. by a route
end
local key,value,speed = Tags.get_constant_by_key_value(way,profile.speeds)
if speed then
-- set speed by way type
result.forward_speed = speed
result.backward_speed = speed
else
-- Set the avg speed on ways that are marked accessible
if profile.access_tag_whitelist[data.forward_access] then
result.forward_speed = profile.default_speed
elseif data.forward_access and not profile.access_tag_blacklist[data.forward_access] then
result.forward_speed = profile.default_speed -- fallback to the avg speed if access tag is not blacklisted
elseif not data.forward_access and data.backward_access then
result.forward_mode = mode.inaccessible
end
if profile.access_tag_whitelist[data.backward_access] then
result.backward_speed = profile.default_speed
elseif data.backward_access and not profile.access_tag_blacklist[data.backward_access] then
result.backward_speed = profile.default_speed -- fallback to the avg speed if access tag is not blacklisted
elseif not data.backward_access and data.forward_access then
result.backward_mode = mode.inaccessible
end
end
if result.forward_speed == -1 and result.backward_speed == -1 and result.duration <= 0 then
return false
end
end
-- [Jay] i.e. static speeds if no better choice
speeds = Sequence {
highway = {
motorway = 90,
motorway_link = 45,
trunk = 85,
trunk_link = 40,
primary = 65,
primary_link = 30,
secondary = 55,
secondary_link = 25,
tertiary = 40,
tertiary_link = 20,
unclassified = 25,
residential = 25,
living_street = 10,
service = 15
}
},
-- [Jay] default_speed for routable ways which value of Key:highway is not exist in speeds
default_speed = 10,
- Referenced OSM keys/values:
- Key:surface, Key:tracktype, Key:smoothness
- Limit speeds by these values and pre-defined surface_speeds,tracktype_speeds,smoothness_speeds.
- Key:surface, Key:tracktype, Key:smoothness
- Codes
-- reduce speed on bad surfaces
function WayHandlers.surface(profile,way,result,data)
local surface = way:get_value_by_key("surface")
local tracktype = way:get_value_by_key("tracktype")
local smoothness = way:get_value_by_key("smoothness")
if surface and profile.surface_speeds[surface] then
result.forward_speed = math.min(profile.surface_speeds[surface], result.forward_speed)
result.backward_speed = math.min(profile.surface_speeds[surface], result.backward_speed)
end
if tracktype and profile.tracktype_speeds[tracktype] then
result.forward_speed = math.min(profile.tracktype_speeds[tracktype], result.forward_speed)
result.backward_speed = math.min(profile.tracktype_speeds[tracktype], result.backward_speed)
end
if smoothness and profile.smoothness_speeds[smoothness] then
result.forward_speed = math.min(profile.smoothness_speeds[smoothness], result.forward_speed)
result.backward_speed = math.min(profile.smoothness_speeds[smoothness], result.backward_speed)
end
end
- Referenced OSM keys/values:
- Key:maxspeed:advisory, Key:maxspeed
- Prefer to use 80% of max speed.
- Key:maxspeed:advisory, Key:maxspeed
- Codes
-- maxspeed and advisory maxspeed
function WayHandlers.maxspeed(profile,way,result,data)
local keys = Sequence { 'maxspeed:advisory', 'maxspeed' }
local forward, backward = Tags.get_forward_backward_by_set(way,data,keys)
forward = WayHandlers.parse_maxspeed(forward,profile)
backward = WayHandlers.parse_maxspeed(backward,profile)
if forward and forward > 0 then
result.forward_speed = forward * profile.speed_reduction
end
if backward and backward > 0 then
result.backward_speed = backward * profile.speed_reduction
end
end
-- [Jay] reduce maxspeed factor
speed_reduction = 0.8,
-
Referenced OSM keys/values:
- Key:service: Tag:service=alley, Tag:service=parking_aisle, Tag:service=driveway, Tag:service=drive-through
- Key:width, Key:lanes, Tag:oneway=alternating, Key:side_road
scale speeds to get better average driving times if these tags exist.
-
Codes
-- scale speeds to get better average driving times
function WayHandlers.penalties(profile,way,result,data)
-- heavily penalize a way tagged with all HOV lanes
-- in order to only route over them if there is no other option
local service_penalty = 1.0
local service = way:get_value_by_key("service")
if service and profile.service_penalties[service] then
service_penalty = profile.service_penalties[service]
end
local width_penalty = 1.0
local width = math.huge
local lanes = math.huge
local width_string = way:get_value_by_key("width")
if width_string and tonumber(width_string:match("%d*")) then
width = tonumber(width_string:match("%d*"))
end
local lanes_string = way:get_value_by_key("lanes")
if lanes_string and tonumber(lanes_string:match("%d*")) then
lanes = tonumber(lanes_string:match("%d*"))
end
local is_bidirectional = result.forward_mode ~= mode.inaccessible and
result.backward_mode ~= mode.inaccessible
if width <= 3 or (lanes <= 1 and is_bidirectional) then
width_penalty = 0.5
end
-- Handle high frequency reversible oneways (think traffic signal controlled, changing direction every 15 minutes).
-- Scaling speed to take average waiting time into account plus some more for start / stop.
local alternating_penalty = 1.0
if data.oneway == "alternating" then
alternating_penalty = 0.4
end
local sideroad_penalty = 1.0
data.sideroad = way:get_value_by_key("side_road")
if "yes" == data.sideroad or "rotary" == data.sideroad then
sideroad_penalty = profile.side_road_multiplier
end
local forward_penalty = math.min(service_penalty, width_penalty, alternating_penalty, sideroad_penalty)
local backward_penalty = math.min(service_penalty, width_penalty, alternating_penalty, sideroad_penalty)
if profile.properties.weight_name == 'routability' then
if result.forward_speed > 0 then
result.forward_rate = (result.forward_speed * forward_penalty) / 3.6
end
if result.backward_speed > 0 then
result.backward_rate = (result.backward_speed * backward_penalty) / 3.6
end
if result.duration > 0 then
result.weight = result.duration / forward_penalty
end
end
end
service_penalties = {
alley = 0.5,
parking = 0.5,
parking_aisle = 0.5,
driveway = 0.5,
["drive-through"] = 0.5,
["drive-thru"] = 0.5
},
Once Lua
function process_way()
finished, the C++
code ExtractorCallbacks::ProcessWay()
will be called to handle this way in memory.
The terminology NodeBasedEdge can refer to Understanding OSRM Graph Representation - Terminology.
For weight/duration
related handle, there will be 3 steps:
- construct
WeightData/DurationData
TheWeightData/DurationData
will be represented bydetail::ByEdgeOrByMeterValue
. It's a tricky way to store a single value but with different labels.
void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const ExtractionWay &parsed_way)
{
//[Jay] ignored unrelated codes ...
//[Jay] both DurationData and WeightData are alias of detail::ByEdgeOrByMeterValue
InternalExtractorEdge::DurationData forward_duration_data;
InternalExtractorEdge::DurationData backward_duration_data;
InternalExtractorEdge::WeightData forward_weight_data;
InternalExtractorEdge::WeightData backward_weight_data;
//[Jay] construct WeightData/DurationData
//[Jay] 1. the `by_way` means `weight/duration` available for this way(i.e. ferry/shuttle_train/movable bridge),
//[Jay] so store the value for each nodes pair.
//[Jay] 2. the `by_meter` means `forward_speed/backward_speed/forward_rate/backward_rate` available for this way,
//[Jay] so store the value with unit meter per second.
const auto toValueByEdgeOrByMeter = [&nodes](const double by_way, const double by_meter) {
using Value = detail::ByEdgeOrByMeterValue;
// get value by weight per edge
if (by_way >= 0)
{
// FIXME We divide by the number of edges here, but should rather consider
// the length of each segment. We would either have to compute the length
// of the whole way here (we can't: no node coordinates) or push that back
// to the container and keep a reference to the way.
const std::size_t num_edges = (nodes.size() - 1);
return Value(Value::by_edge, by_way / num_edges);
}
else
{
// get value by deriving weight from speed per edge
return Value(Value::by_meter, by_meter);
}
};
if (parsed_way.forward_travel_mode != extractor::TRAVEL_MODE_INACCESSIBLE)
{
//[Jay] ignored unimportant codes here...
forward_duration_data = toValueByEdgeOrByMeter(parsed_way.duration, parsed_way.forward_speed / 3.6);
forward_weight_data = toValueByEdgeOrByMeter(parsed_way.weight, parsed_way.forward_rate);
}
if (parsed_way.backward_travel_mode != extractor::TRAVEL_MODE_INACCESSIBLE)
{
//[Jay] ignored unimportant codes here...
backward_duration_data = toValueByEdgeOrByMeter(parsed_way.duration, parsed_way.backward_speed / 3.6);
backward_weight_data = toValueByEdgeOrByMeter(parsed_way.weight, parsed_way.backward_rate);
}
//[Jay] ignored unrelated codes ...
void ExtractorCallbacks::ProcessWay(const osmium::Way &input_way, const ExtractionWay &parsed_way)
{
//[Jay] ignored unrelated codes ...
if (in_forward_direction) //[Jay] forward edge
{
//[Jay] ignored unrelated codes ...
util::for_each_pair(
nodes.cbegin(),
nodes.cend(),
[&](const osmium::NodeRef &first_node, const osmium::NodeRef &last_node) {
NodeBasedEdgeWithOSM edge = {
OSMNodeID{static_cast<std::uint64_t>(first_node.ref())},
OSMNodeID{static_cast<std::uint64_t>(last_node.ref())},
0, // weight
0, // duration
{}, // geometry id
static_cast<AnnotationID>(annotation_data_id),
{true,
in_backward_direction && !split_edge,
split_edge,
parsed_way.roundabout,
parsed_way.circular,
parsed_way.is_startpoint,
parsed_way.forward_restricted,
road_classification,
parsed_way.highway_turn_classification,
parsed_way.access_turn_classification}};
//[Jay] store WeightData/DurationData with NodeBasedEdge in memory
external_memory.all_edges_list.push_back(InternalExtractorEdge(
std::move(edge), forward_weight_data, forward_duration_data, {}));
});
}
if (in_backward_direction && (!in_forward_direction || split_edge)) //[Jay] backward edge if necessary
{
//[Jay] similar as forward, no need to copy code snip again.
}
//[Jay] ignored unrelated codes ...
NOTE: be aware that the unit
of weight/duration
will be 100ms
start from here.
{
// Compute edge weights
//[Jay] ignored unrelated codes ...
//[Jay] weight/duration = distance / meter_per_second
const auto distance =
util::coordinate_calculation::greatCircleDistance(source_coord, target_coord);
const auto weight = edge_iterator->weight_data(distance);
const auto duration = edge_iterator->duration_data(distance);
//[Jay] `process_segment()` is not currently used in the car profile.
ExtractionSegment segment(source_coord, target_coord, distance, weight, duration);
scripting_environment.ProcessSegment(segment);
//[Jay] HIGHLIGHT: scale the weight/duration value by multiply 10, because it will use 100ms as unit for them.
auto &edge = edge_iterator->result;
edge.weight = std::max<EdgeWeight>(1, std::round(segment.weight * weight_multiplier));
edge.duration = std::max<EdgeWeight>(1, std::round(segment.duration * 10.));
//[Jay] ignored unrelated codes ...
}
There're two actions for handle weight/duration applied during Compress NodeBasedGraph.
For what compress NodeBasedGraph do, please refer to Understanding OSRM Graph Representation - Basic Changes of Convert OSM to OSRM Edge-expanded Graph.
By current implementation, there'll be 2s
(defined by traffic_light_penalty = 2
in car.lua
) penalty added to both weight and duration on each compressible traffic_signals
.
- graph_compressor.cpp#L218
Please be aware that comments in graph_compressor.cpp#L209 is not correct. it will be fixed by PR-5384.
//[Jay] ignored unrelated codes ...
const bool has_node_penalty = traffic_signals.find(node_v) != traffic_signals.end();
EdgeDuration node_duration_penalty = MAXIMAL_EDGE_DURATION;
EdgeWeight node_weight_penalty = INVALID_EDGE_WEIGHT;
if (has_node_penalty) //[Jay] if traffic_signals appeared, add some penalty for it
{
// we cannot handle this as node penalty, if it depends on turn direction
if (fwd_edge_data1.flags.restricted != fwd_edge_data2.flags.restricted)
continue;
// generate an artifical turn for the turn penalty generation
std::vector<ExtractionTurnLeg> roads_on_the_right;
std::vector<ExtractionTurnLeg> roads_on_the_left;
ExtractionTurn extraction_turn(0,
2,
false,
true,
false,
false,
TRAVEL_MODE_DRIVING,
false,
false,
1,
0,
0,
0,
0,
false,
TRAVEL_MODE_DRIVING,
false,
false,
1,
0,
0,
0,
0,
roads_on_the_right,
roads_on_the_left);
//[Jay] call Lua function `process_turn()` to add penalty.
//[Jay] But actually only the `traffic_signals` related codes in `process_turn()` will be touched,
//[Jay] since no other parameters pass in.
scripting_environment.ProcessTurn(extraction_turn);
//[Jay] HIGHLIGHT: same as before, scale the weight/duration value by multiply 10, because it will use 100ms as unit for them.
node_duration_penalty = extraction_turn.duration * 10;
node_weight_penalty = extraction_turn.weight * weight_multiplier;
}
//[Jay] ignored unrelated codes ...
function process_turn(profile, turn)
-- [Jay] ignored unrelated codes ...
if turn.has_traffic_light then
turn.duration = profile.properties.traffic_light_penalty
end
-- [Jay] ignored unrelated codes ...
-- for distance based routing we don't want to have penalties based on turn angle
if profile.properties.weight_name == 'distance' then
turn.weight = 0
else
turn.weight = turn.duration
end
-- [Jay] ignored unrelated codes ...
end
traffic_light_penalty = 2,
//[Jay] ignored unrelated codes ...
// add weight of e2's to e1
graph.GetEdgeData(forward_e1).weight += forward_weight2;
graph.GetEdgeData(reverse_e1).weight += reverse_weight2;
// add duration of e2's to e1
graph.GetEdgeData(forward_e1).duration += forward_duration2;
graph.GetEdgeData(reverse_e1).duration += reverse_duration2;
if (node_weight_penalty != INVALID_EDGE_WEIGHT &&
node_duration_penalty != MAXIMAL_EDGE_DURATION)
{
//[Jay] add weight/duration penalty caused by traffic_signals
graph.GetEdgeData(forward_e1).weight += node_weight_penalty;
graph.GetEdgeData(reverse_e1).weight += node_weight_penalty;
graph.GetEdgeData(forward_e1).duration += node_duration_penalty;
graph.GetEdgeData(reverse_e1).duration += node_duration_penalty;
}
//[Jay] ignored unrelated codes ...
The terminology GraphEdge can refer to Understanding OSRM Graph Representation - Terminology.
Turn related weight/duration
penalties will be calculated while each GraphEdge generating.
//[Jay] ignored unrelated codes ...
// compute weight and duration penalties
const auto is_traffic_light = m_traffic_lights.count(intersection_node);
const auto is_uturn =
guidance::getTurnDirection(turn_angle) == guidance::DirectionModifier::UTurn;
//[Jay] fill in parameters for Lua function `process_turn()`
//[Jay] Actually most of these parameters have not been used in `process_turn()`.
ExtractionTurn extracted_turn(
// general info
turn_angle,
road_legs_on_the_right.size() + road_legs_on_the_left.size() + 2 - is_uturn, //[Jay] `turn.number_of_roads` in `process_turn()`
is_uturn,
is_traffic_light,
m_edge_based_node_container.GetAnnotation(edge_data1.annotation_data)
.is_left_hand_driving,
// source info
edge_data1.flags.restricted,
m_edge_based_node_container.GetAnnotation(edge_data1.annotation_data).travel_mode, //[Jay] `turn.source_mode` in `process_turn()`
edge_data1.flags.road_classification.IsMotorwayClass(),
edge_data1.flags.road_classification.IsLinkClass(),
edge_data1.flags.road_classification.GetNumberOfLanes(),
edge_data1.flags.highway_turn_classification,
edge_data1.flags.access_turn_classification,
((double)intersection::findEdgeLength(edge_geometries, node_based_edge_from) /
edge_data1.duration) *
36,
edge_data1.flags.road_classification.GetPriority(),
// target info
edge_data2.flags.restricted,
m_edge_based_node_container.GetAnnotation(edge_data2.annotation_data).travel_mode, //[Jay] `turn.target_node` in `process_turn()`
edge_data2.flags.road_classification.IsMotorwayClass(),
edge_data2.flags.road_classification.IsLinkClass(),
edge_data2.flags.road_classification.GetNumberOfLanes(),
edge_data2.flags.highway_turn_classification,
edge_data2.flags.access_turn_classification,
((double)intersection::findEdgeLength(edge_geometries, node_based_edge_to) /
edge_data2.duration) *
36,
edge_data2.flags.road_classification.GetPriority(),
// connected roads
road_legs_on_the_right,
road_legs_on_the_left);
//[Jay] call Lua function `process_turn()` for turn weight/duration penalties
scripting_environment.ProcessTurn(extracted_turn);
//[Jay] HIGHLIGHT: same as before, scale the weight/duration value by multiply 10, because it will use 100ms as unit for them.
// turn penalties are limited to [-2^15, 2^15) which roughly translates to 54 minutes
// and fits signed 16bit deci-seconds
auto weight_penalty =
boost::numeric_cast<TurnPenalty>(extracted_turn.weight * weight_multiplier);
auto duration_penalty = boost::numeric_cast<TurnPenalty>(extracted_turn.duration * 10.);
BOOST_ASSERT(SPECIAL_NODEID != nbe_to_ebn_mapping[node_based_edge_from]);
BOOST_ASSERT(SPECIAL_NODEID != nbe_to_ebn_mapping[node_based_edge_to]);
//[Jay] weight/duration of GraphEdge = weight/duration of NodeBasedEdge + turn_penalty
auto weight = boost::numeric_cast<EdgeWeight>(edge_data1.weight + weight_penalty);
auto duration = boost::numeric_cast<EdgeWeight>(edge_data1.duration + duration_penalty);
//[Jay] ignored unrelated codes ...
-
sigmoid function
A sigmoid function is a mathematical function having a characteristic "S"-shaped curve or sigmoid curve. Often, sigmoid function refers to the special case of the logistic function shown in below figure and defined by the formula:
-
process_turn()
in car.lua
Calculate turnweight/duration
penalities byLua
functionprocess_turn()
.
function process_turn(profile, turn)
-- 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.
local turn_penalty = profile.turn_penalty
local turn_bias = turn.is_left_hand_driving and 1. / profile.turn_bias or profile.turn_bias
if turn.has_traffic_light then
-- [Jay] default 2s for `traffic_signals` on each turn
turn.duration = profile.properties.traffic_light_penalty
end
if turn.number_of_roads > 2 or turn.source_mode ~= turn.target_mode or turn.is_u_turn then
if turn.angle >= 0 then
-- [Jay] Let's say `sig_value` is the return value of the sigmoid function,
-- [Jay] i.e. `sig_value = 1 / (1 + math.exp( -((13 / turn_bias) * turn.angle/180 - 6.5*turn_bias)))`,
-- [Jay] `sig_value` will in range (0,1),
-- [Jay] so the formula is equal to `turn.duration = turn.duration + turn_penalty * sig_value`,
-- [Jay] and the `turn_penalty * sig_value` will in range (0,7.5),
-- [Jay] which means extra (0,7.5) seconds penalty will be applied by turn.
turn.duration = turn.duration + turn_penalty / (1 + math.exp( -((13 / turn_bias) * turn.angle/180 - 6.5*turn_bias)))
else
turn.duration = turn.duration + turn_penalty / (1 + math.exp( -((13 * turn_bias) * -turn.angle/180 - 6.5/turn_bias)))
end
if turn.is_u_turn then
-- [Jay] default 20s for `u_turn` on each turn
turn.duration = turn.duration + profile.properties.u_turn_penalty
end
end
-- for distance based routing we don't want to have penalties based on turn angle
if profile.properties.weight_name == 'distance' then
turn.weight = 0
else
turn.weight = turn.duration
end
if profile.properties.weight_name == 'routability' then
-- penalize turns from non-local access only segments onto local access only tags
if not turn.source_restricted and turn.target_restricted then
turn.weight = constants.max_turn_weight
end
end
end
u_turn_penalty = 20,
traffic_light_penalty = 2,
turn_penalty = 7.5,
turn_bias = 1.075,
read more about the turn_penalty
calculation by sigmoid function: