Skip to content

Latest commit

 

History

History
803 lines (677 loc) · 41.6 KB

how_osrm_calculate_weight_and_duration.md

File metadata and controls

803 lines (677 loc) · 41.6 KB

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.

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.
  • Many of description will be represented by comments --[Jay] ... in Lua code snippets or //[Jay] ... in C++ code snippets.

Basic Concepts

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.

Process Speed/Rate in Lua Profiles

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.

process_way

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.

process_way in car.lua

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.

WayHandlers.ferries

-- 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
    },

WayHandlers.movables

-- 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
    },

WayHandlers.speed

-- 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,

WayHandlers.surface

-- 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

WayHandlers.maxspeed

-- 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,

WayHandlers.penalties

-- 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
    },

Construct and Store Weight/Duration for NodeBasedEdge

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:

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 ... 
    }

Compress NodeBasedEdge

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.

Add Penalty for Compressible traffic_signals

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.


    
    //[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,

Sum Weight/Duration of Compressed NodeBasedEdges

    //[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 ... 

Process Turn While Build GraphEdge

The terminology GraphEdge can refer to Understanding OSRM Graph Representation - Terminology.

compute weight and duration penalties

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 ... 
    

process_turn in Lua profile

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:

Read More