diff --git a/6/0/truck-soft/README.md b/6/0/truck-soft/README.md new file mode 100644 index 0000000..6fc7600 --- /dev/null +++ b/6/0/truck-soft/README.md @@ -0,0 +1,19 @@ +# Truck - Soft +Profile based on default car profile. Use diff file to review the changes. + +## Why +Trucks, aka Heavy Vehicles, have particularities: like speed, vehicle size or restrictions. + +## How +The tags about Heavy Vehicles are largely missing on OSM. +This profile is based on four aspects: +- Adjusted Physical size restrictions, strictly respected: height and width. +- Legal size restrictions, softly respected: weight and length. The profile try to avoid restricted ways. +- Heavy Vehicle conditional restrictions, softly respected. +- Highway penalty: stay on major highways and only go on lower highways when needed. + +## Warning +OSM data are poor regardly to Heavy Vehicle attributes. The computed route is mainly based on penalties and not rules enforcement. The computed routes may not respect the legal access defined on the ground. + +## Disclaimer +This is based on the https://github.com/Project-OSRM/osrm-profiles-contrib/tree/master/5/21/truck-soft and updated for 6.0.0 version of OSRM backend. diff --git a/6/0/truck-soft/car.lua b/6/0/truck-soft/car.lua new file mode 100644 index 0000000..d616933 --- /dev/null +++ b/6/0/truck-soft/car.lua @@ -0,0 +1,552 @@ +-- Car profile + +api_version = 4 + +Set = require('lib/set') +Sequence = require('lib/sequence') +Handlers = require("lib/way_handlers") +Relations = require("lib/relations") +Obstacles = require("lib/obstacles") +find_access_tag = require("lib/access").find_access_tag +limit = require("lib/maxspeed").limit +Utils = require("lib/utils") +Measure = require("lib/measure") + +function setup() + return { + properties = { + max_speed_for_map_matching = 150/3.6, -- 150kmph -> m/s + -- For routing based on duration, but weighted for preferring certain roads + weight_name = 'routability', + -- For shortest duration without penalties for accessibility + -- weight_name = 'duration', + -- For shortest distance without penalties for accessibility + -- weight_name = 'distance', + process_call_tagless_node = false, + u_turn_penalty = 30, + continue_straight_at_waypoint = true, + use_turn_restrictions = true, + left_hand_driving = false, + }, + + default_mode = mode.driving, + default_speed = 10, + oneway_handling = true, + side_road_multiplier = 0.8, + turn_penalty = 25, + speed_reduction = 0.8, + turn_bias = 1.075, + cardinal_directions = false, + + -- Size of the vehicle, to be limited by physical restriction of the way + vehicle_height = 2.8, -- in meters + vehicle_width = 2.2, -- in meters + + -- Size of the vehicle, to be limited mostly by legal restriction of the way + vehicle_length = 6.0, -- in meters, 4.8m is the length of large or familly car + vehicle_weight = 6000, -- in kilograms + + -- a list of suffixes to suppress in name change instructions. The suffixes also include common substrings of each other + suffix_list = { + 'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'North', 'South', 'West', 'East', 'Nor', 'Sou', 'We', 'Ea' + }, + + barrier_whitelist = Set { + 'cattle_grid', + 'border_control', + 'toll_booth', + 'sally_port', + 'gate', + 'lift_gate', + 'no', + 'entrance', + 'height_restrictor', + 'arch' + }, + + access_tag_whitelist = Set { + 'yes', + 'motorcar', + 'motor_vehicle', + 'vehicle', + 'permissive', + 'designated', + 'hov' + }, + + access_tag_blacklist = Set { + 'no', + 'agricultural', + 'forestry', + 'emergency', + 'psv', + 'customers', + 'private', + 'delivery', + 'destination' + }, + + -- tags disallow access to in combination with highway=service + service_access_tag_blacklist = Set { + 'private' + }, + + restricted_access_tag_list = Set { + 'private', + 'delivery', + 'destination', + 'customers', + }, + + access_tags_hierarchy = Sequence { + 'motorcar', + 'motor_vehicle', + 'vehicle', + 'access' + }, + + service_tag_forbidden = Set { + 'emergency_access' + }, + + restrictions = Sequence { + 'motorcar', + 'motor_vehicle', + 'vehicle' + }, + + classes = Sequence { + 'toll', 'motorway', 'ferry', 'restricted', 'tunnel' + }, + + -- classes to support for exclude flags + excludable = Sequence { + Set {'toll'}, + Set {'motorway'}, + Set {'ferry'} + }, + + avoid = Set { + 'area', + -- 'toll', -- uncomment this to avoid tolls + 'reversible', + 'impassable', + 'hov_lanes', + 'steps', + 'construction', + 'proposed' + }, + + 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 + } + }, + + highway_penalties = { + motorway = 1, + motorway_link = 1, + trunk = 1, + trunk_link = 1, + primary = 1, + primary_link = 1, + secondary = 1, + secondary_link = 1, + tertiary = 0.9, + tertiary_link = 0.9, + unclassified = 0.8, + residential = 0.7, + living_street = 0.3, + service = 0.2, + track = 0.1 + }, + + service_penalties = { + alley = 0.5, + parking = 0.5, + parking_aisle = 0.5, + driveway = 0.5, + ["drive-through"] = 0.5, + ["drive-thru"] = 0.5 + }, + + restricted_highway_whitelist = Set { + 'motorway', + 'motorway_link', + 'trunk', + 'trunk_link', + 'primary', + 'primary_link', + 'secondary', + 'secondary_link', + 'tertiary', + 'tertiary_link', + 'residential', + 'living_street', + 'unclassified', + 'service' + }, + + construction_whitelist = Set { + 'no', + 'widening', + 'minor', + }, + + route_speeds = { + ferry = 5, + shuttle_train = 10 + }, + + bridge_speeds = { + movable = 5 + }, + + -- surface/trackype/smoothness + -- values were estimated from looking at the photos at the relevant wiki pages + + -- max speed for surfaces + surface_speeds = { + asphalt = nil, -- nil mean no limit. removing the line has the same effect + concrete = nil, + ["concrete:plates"] = nil, + ["concrete:lanes"] = nil, + paved = nil, + + cement = 80, + compacted = 80, + fine_gravel = 80, + + paving_stones = 60, + metal = 60, + bricks = 60, + + grass = 40, + wood = 40, + sett = 40, + grass_paver = 40, + gravel = 40, + unpaved = 40, + ground = 40, + dirt = 40, + pebblestone = 40, + tartan = 40, + + cobblestone = 30, + clay = 30, + + earth = 20, + stone = 20, + rocky = 20, + sand = 20, + + mud = 10 + }, + + -- max speed for tracktypes + tracktype_speeds = { + grade1 = 60, + grade2 = 40, + grade3 = 30, + grade4 = 25, + grade5 = 20 + }, + + -- max speed for smoothnesses + smoothness_speeds = { + intermediate = 80, + bad = 40, + very_bad = 20, + horrible = 10, + very_horrible = 5, + impassable = 0 + }, + + -- http://wiki.openstreetmap.org/wiki/Speed_limits + maxspeed_table_default = { + urban = 50, + rural = 90, + trunk = 110, + motorway = 130 + }, + + -- List only exceptions + maxspeed_table = { + ["at:rural"] = 100, + ["at:trunk"] = 100, + ["be:motorway"] = 120, + ["be-bru:rural"] = 70, + ["be-bru:urban"] = 30, + ["be-vlg:rural"] = 70, + ["bg:motorway"] = 140, + ["by:urban"] = 60, + ["by:motorway"] = 110, + ["ca-on:rural"] = 80, + ["ch:rural"] = 80, + ["ch:trunk"] = 100, + ["ch:motorway"] = 120, + ["cz:trunk"] = 0, + ["cz:motorway"] = 0, + ["de:living_street"] = 7, + ["de:rural"] = 100, + ["de:motorway"] = 0, + ["dk:rural"] = 80, + ["es:trunk"] = 90, + ["fr:rural"] = 80, + ["gb:nsl_single"] = (60*1609)/1000, + ["gb:nsl_dual"] = (70*1609)/1000, + ["gb:motorway"] = (70*1609)/1000, + ["nl:rural"] = 80, + ["nl:trunk"] = 100, + ['no:rural'] = 80, + ['no:motorway'] = 110, + ['ph:urban'] = 40, + ['ph:rural'] = 80, + ['ph:motorway'] = 100, + ['pl:rural'] = 100, + ['pl:expressway'] = 120, + ['pl:motorway'] = 140, + ["ro:trunk"] = 100, + ["ru:living_street"] = 20, + ["ru:urban"] = 60, + ["ru:motorway"] = 110, + ["uk:nsl_single"] = (60*1609)/1000, + ["uk:nsl_dual"] = (70*1609)/1000, + ["uk:motorway"] = (70*1609)/1000, + ['za:urban'] = 60, + ['za:rural'] = 100, + ["none"] = 140 + }, + + relation_types = Sequence { + "route" + }, + + -- classify highway tags when necessary for turn weights + highway_turn_classification = { + }, + + -- classify access tags when necessary for turn weights + access_turn_classification = { + } + } +end + +function process_node(profile, node, result, relations) + -- parse access and barrier tags + local access = find_access_tag(node, profile.access_tags_hierarchy) + if access then + if profile.access_tag_blacklist[access] and not profile.restricted_access_tag_list[access] then + obstacle_map:add(node, Obstacle.new(obstacle_type.barrier)) + end + else + local barrier = node:get_value_by_key("barrier") + if barrier then + -- check height restriction barriers + local restricted_by_height = false + if barrier == 'height_restrictor' then + local maxheight = Measure.get_max_height(node:get_value_by_key("maxheight"), node) + restricted_by_height = maxheight and maxheight < profile.vehicle_height + end + + -- make an exception for rising bollard barriers + local bollard = node:get_value_by_key("bollard") + local rising_bollard = bollard and "rising" == bollard + + -- make an exception for lowered/flat barrier=kerb + -- and incorrect tagging of highway crossing kerb as highway barrier + local kerb = node:get_value_by_key("kerb") + local highway = node:get_value_by_key("highway") + local flat_kerb = kerb and ("lowered" == kerb or "flush" == kerb) + local highway_crossing_kerb = barrier == "kerb" and highway and highway == "crossing" + + if not profile.barrier_whitelist[barrier] + and not rising_bollard + and not flat_kerb + and not highway_crossing_kerb + or restricted_by_height then + obstacle_map:add(node, Obstacle.new(obstacle_type.barrier)) + end + end + end + + Obstacles.process_node(profile, node) +end + +function process_way(profile, way, result, relations) + -- the intial filtering of ways based on presence of tags + -- affects processing times significantly, because all ways + -- have to be checked. + -- to increase performance, prefetching and intial tag check + -- is done in directly instead of via a handler. + + -- in general we should try to abort as soon as + -- possible if the way is not routable, to avoid doing + -- unnecessary work. this implies we should check things that + -- commonly forbids access early, and handle edge cases later. + + -- 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') + } + + -- perform an quick initial check and abort if the way is + -- obviously not routable. + -- highway or route tags must be in data table, bridge is optional + if (not data.highway or data.highway == '') and + (not data.route or data.route == '') + then + return + end + + handlers = Sequence { + -- set the default mode for this profile. if can be changed later + -- in case it turns we're e.g. on a ferry + WayHandlers.default_mode, + + -- check various tags that could indicate that the way is not + -- routable. this includes things like status=impassable, + -- toll=yes and oneway=reversible + WayHandlers.blocked_ways, + WayHandlers.avoid_ways, + WayHandlers.handle_height, + WayHandlers.handle_width, + WayHandlers.handle_length, + WayHandlers.handle_weight, + + -- determine access status by checking our hierarchy of + -- access tags, e.g: motorcar, motor_vehicle, vehicle + WayHandlers.access, + + -- check whether forward/backward directions are routable + WayHandlers.oneway, + + -- check a road's destination + WayHandlers.destinations, + + -- check whether we're using a special transport mode + WayHandlers.ferries, + WayHandlers.movables, + + -- handle service road restrictions + WayHandlers.service, + + -- handle hov + WayHandlers.hov, + + -- compute speed taking into account way type, maxspeed tags, etc. + WayHandlers.speed, + WayHandlers.maxspeed, + WayHandlers.surface, + WayHandlers.penalties, + + -- set penalty to try to follow legal access restriction + WayHandlers.handle_weight, + WayHandlers.handle_length, + WayHandlers.handle_hgv_access, + + -- compute class labels + WayHandlers.classes, + + -- handle turn lanes and road classification, used for guidance + WayHandlers.turn_lanes, + WayHandlers.classification, + + -- handle various other flags + WayHandlers.roundabouts, + WayHandlers.startpoint, + WayHandlers.driving_side, + + -- set name, ref and pronunciation + WayHandlers.names, + + -- set weight properties of the way + WayHandlers.weights, + + -- set classification of ways relevant for turns + WayHandlers.way_classification_for_turn + } + + WayHandlers.run(profile, way, result, data, handlers, relations) + + if profile.cardinal_directions then + Relations.process_way_refs(way, relations, result) + end +end + +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 + + for _, obs in pairs(obstacle_map:get(turn.from, turn.via)) do + -- disregard a minor stop if entering by the major road + -- rationale: if a stop sign is tagged at the center of the intersection with stop=minor + -- it should only penalize the minor roads entering the intersection + if obs.type == obstacle_type.stop_minor and not Obstacles.entering_by_minor_road(turn) then + goto skip + end + -- heuristic to infer the direction of a stop without an explicit direction tag + -- rationale: a stop sign should not be placed farther than 20m from the intersection + if turn.number_of_roads == 2 + and obs.type == obstacle_type.stop + and obs.direction == obstacle_direction.none + and turn.source_road.distance < 20 + and turn.target_road.distance > 20 then + goto skip + end + turn.duration = turn.duration + obs.duration + ::skip:: + 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 + 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 + 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 + +return { + setup = setup, + process_way = process_way, + process_node = process_node, + process_turn = process_turn +} diff --git a/6/0/truck-soft/lib/way_handlers.lua b/6/0/truck-soft/lib/way_handlers.lua new file mode 100644 index 0000000..356cf57 --- /dev/null +++ b/6/0/truck-soft/lib/way_handlers.lua @@ -0,0 +1,772 @@ +-- Profile handlers dealing with various aspects of tag parsing +-- +-- You can run a selection you find useful in your profile, +-- or do you own processing if/when required. + + +local get_turn_lanes = require("lib/guidance").get_turn_lanes +local set_classification = require("lib/guidance").set_classification +local get_destination = require("lib/destination").get_destination +local Tags = require('lib/tags') +local Measure = require("lib/measure") + +WayHandlers = {} + +-- check that way has at least one tag that could imply routability- +-- we store the checked tags in data, to avoid fetching again later +function WayHandlers.tag_prefetch(profile,way,result,data) + for key,v in pairs(profile.prefetch) do + data[key] = way:get_value_by_key( key ) + end + + return next(data) ~= nil +end + +-- set default mode +function WayHandlers.default_mode(profile,way,result,data) + result.forward_mode = profile.default_mode + result.backward_mode = profile.default_mode +end + +-- handles name, including ref and pronunciation +function WayHandlers.names(profile,way,result,data) + -- parse the remaining tags + local name = way:get_value_by_key("name") + local pronunciation = way:get_value_by_key("name:pronunciation") + local ref = way:get_value_by_key("ref") + local exits = way:get_value_by_key("junction:ref") + + -- Set the name that will be used for instructions + if name then + result.name = name + end + + if ref then + result.ref = canonicalizeStringList(ref, ";") + end + + if pronunciation then + result.pronunciation = pronunciation + end + + if exits then + result.exits = canonicalizeStringList(exits, ";") + end +end + +-- junctions +function WayHandlers.roundabouts(profile,way,result,data) + local junction = way:get_value_by_key("junction"); + + if junction == "roundabout" then + result.roundabout = true + end + + -- See Issue 3361: roundabout-shaped not following roundabout rules. + -- This will get us "At Strausberger Platz do Maneuver X" instead of multiple quick turns. + -- In a new API version we can think of having a separate type passing it through to the user. + if junction == "circular" then + result.circular = true + end +end + +-- determine if this way can be used as a start/end point for routing +function WayHandlers.startpoint(profile,way,result,data) + -- if profile specifies set of allowed start modes, then check for that + -- otherwise require default mode + if profile.allowed_start_modes then + result.is_startpoint = profile.allowed_start_modes[result.forward_mode] == true or + profile.allowed_start_modes[result.backward_mode] == true + else + result.is_startpoint = result.forward_mode == profile.default_mode or + result.backward_mode == profile.default_mode + end + -- highway=service and access tags check + local is_service = data.highway == "service" + if is_service then + if profile.service_access_tag_blacklist[data.forward_access] then + result.is_startpoint = false + end + end +end + +-- handle turn lanes +function WayHandlers.turn_lanes(profile,way,result,data) + local forward, backward = get_turn_lanes(way,data) + + if forward then + result.turn_lanes_forward = forward + end + + if backward then + result.turn_lanes_backward = backward + end +end + +-- set the road classification based on guidance globals configuration +function WayHandlers.classification(profile,way,result,data) + set_classification(data.highway,result,way) +end + +-- handle destination tags +function WayHandlers.destinations(profile,way,result,data) + if data.is_forward_oneway or data.is_reverse_oneway then + local destination = get_destination(way, data.is_forward_oneway) + result.destinations = canonicalizeStringList(destination, ",") + end +end + +-- 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 + +-- 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 + +-- service roads +function WayHandlers.service(profile,way,result,data) + local service = way:get_value_by_key("service") + if service then + -- Set don't allow access to certain service roads + if profile.service_tag_forbidden[service] then + result.forward_mode = mode.inaccessible + result.backward_mode = mode.inaccessible + return false + end + end +end + +-- all lanes restricted to hov vehicles? +function WayHandlers.has_all_designated_hov_lanes(lanes) + if not lanes then + return false + end + -- This gmatch call effectively splits the string on | chars. + -- we append an extra | to the end so that we can match the final part + for lane in (lanes .. '|'):gmatch("([^|]*)|") do + if lane and lane ~= "designated" then + return false + end + end + return true +end + +-- handle high occupancy vehicle tags +function WayHandlers.hov(profile,way,result,data) + -- respect user-preference for HOV + if not profile.avoid.hov_lanes then + return + end + + local hov = way:get_value_by_key("hov") + if "designated" == hov then + result.forward_restricted = true + result.backward_restricted = true + end + + data.hov_lanes_forward, data.hov_lanes_backward = Tags.get_forward_backward_by_key(way,data,'hov:lanes') + local all_hov_forward = WayHandlers.has_all_designated_hov_lanes(data.hov_lanes_forward) + local all_hov_backward = WayHandlers.has_all_designated_hov_lanes(data.hov_lanes_backward) + + -- in this case we will use turn penalties instead of filtering out + if profile.properties.weight_name == 'routability' then + if (all_hov_forward) then + result.forward_restricted = true + end + if (all_hov_backward) then + result.backward_restricted = true + end + return + end + + -- filter out ways where all lanes are hov only + if all_hov_forward then + result.forward_mode = mode.inaccessible + end + if all_hov_backward then + result.backward_mode = mode.inaccessible + end +end + + +-- set highway and access classification by user preference +function WayHandlers.way_classification_for_turn(profile,way,result,data) + local highway = way:get_value_by_key("highway") + local access = way:get_value_by_key("access") + + if highway and profile.highway_turn_classification[highway] then + assert(profile.highway_turn_classification[highway] < 16, "highway_turn_classification must be smaller than 16") + result.highway_turn_classification = profile.highway_turn_classification[highway] + end + if access and profile.access_turn_classification[access] then + assert(profile.access_turn_classification[access] < 16, "access_turn_classification must be smaller than 16") + result.access_turn_classification = profile.access_turn_classification[access] + end +end + + +-- check accessibility by traversing our access tag hierarchy +function WayHandlers.access(profile,way,result,data) + data.forward_access, data.backward_access = + Tags.get_forward_backward_by_set(way,data,profile.access_tags_hierarchy) + + -- only allow a subset of roads to be treated as restricted + if profile.restricted_highway_whitelist[data.highway] then + if profile.restricted_access_tag_list[data.forward_access] then + result.forward_restricted = true + end + + if profile.restricted_access_tag_list[data.backward_access] then + result.backward_restricted = true + end + end + + -- blacklist access tags that aren't marked as restricted + if profile.access_tag_blacklist[data.forward_access] and not result.forward_restricted then + result.forward_mode = mode.inaccessible + end + + if profile.access_tag_blacklist[data.backward_access] and not result.backward_restricted then + result.backward_mode = mode.inaccessible + end + + if result.forward_mode == mode.inaccessible and result.backward_mode == mode.inaccessible then + return false + end +end + +-- 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 + +-- add class information +function WayHandlers.classes(profile,way,result,data) + if not profile.classes then + return + end + + local allowed_classes = Set {} + for k, v in pairs(profile.classes) do + allowed_classes[v] = true + end + + local forward_toll, backward_toll = Tags.get_forward_backward_by_key(way, data, "toll") + local forward_route, backward_route = Tags.get_forward_backward_by_key(way, data, "route") + local tunnel = way:get_value_by_key("tunnel") + + if allowed_classes["tunnel"] and tunnel and tunnel ~= "no" then + result.forward_classes["tunnel"] = true + result.backward_classes["tunnel"] = true + end + + if allowed_classes["toll"] and forward_toll == "yes" then + result.forward_classes["toll"] = true + end + if allowed_classes["toll"] and backward_toll == "yes" then + result.backward_classes["toll"] = true + end + + if allowed_classes["ferry"] and forward_route == "ferry" then + result.forward_classes["ferry"] = true + end + if allowed_classes["ferry"] and backward_route == "ferry" then + result.backward_classes["ferry"] = true + end + + if allowed_classes["restricted"] and result.forward_restricted then + result.forward_classes["restricted"] = true + end + if allowed_classes["restricted"] and result.backward_restricted then + result.backward_classes["restricted"] = true + end + + if allowed_classes["motorway"] and (data.highway == "motorway" or data.highway == "motorway_link") then + result.forward_classes["motorway"] = true + result.backward_classes["motorway"] = true + end +end + +-- 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 + +-- 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 highway_penalty = 1.0 + local highway = way:get_value_by_key("highway") + if highway and profile.highway_penalties[highway] then + highway_penalty = profile.highway_penalties[highway] + end + + 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(highway_penalty, service_penalty, width_penalty, alternating_penalty, sideroad_penalty) + local backward_penalty = math.min(highway_penalty, 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 + +-- maxspeed and advisory maxspeed +function WayHandlers.maxspeed(profile,way,result,data) + local keys = Sequence { 'maxspeed:advisory', 'maxspeed', 'source:maxspeed', 'maxspeed:type' } + 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 + +function WayHandlers.parse_maxspeed(source,profile) + if not source then + return 0 + end + + local n = Measure.get_max_speed(source) + if not n then + -- parse maxspeed like FR:urban + source = string.lower(source) + n = profile.maxspeed_table[source] + if not n then + local highway_type = string.match(source, "%a%a:(%a+)") + n = profile.maxspeed_table_default[highway_type] + if not n then + n = 0 + end + end + end + return n +end + +-- handle maxheight tags +function WayHandlers.handle_height(profile,way,result,data) + local keys = Sequence { 'maxheight:physical', 'maxheight' } + local forward, backward = Tags.get_forward_backward_by_set(way,data,keys) + forward = Measure.get_max_height(forward,way) + backward = Measure.get_max_height(backward,way) + + if forward and forward < profile.vehicle_height then + result.forward_mode = mode.inaccessible + end + + if backward and backward < profile.vehicle_height then + result.backward_mode = mode.inaccessible + end +end + +-- handle maxwidth tags +function WayHandlers.handle_width(profile,way,result,data) + local keys = Sequence { 'maxwidth:physical', 'maxwidth', 'width', 'est_width' } + local forward, backward = Tags.get_forward_backward_by_set(way,data,keys) + local narrow = way:get_value_by_key('narrow') + + if ((forward and forward == 'narrow') or (narrow and narrow == 'yes')) and profile.vehicle_width > 2.2 then + result.forward_mode = mode.inaccessible + elseif forward then + forward = Measure.get_max_width(forward) + if forward and forward <= profile.vehicle_width then + result.forward_mode = mode.inaccessible + end + end + + if ((backward and backward == 'narrow') or (narrow and narrow == 'yes')) and profile.vehicle_width > 2.2 then + result.backward_mode = mode.inaccessible + elseif backward then + backward = Measure.get_max_width(backward) + if backward and backward <= profile.vehicle_width then + result.backward_mode = mode.inaccessible + end + end +end + +-- handle maxlength tags +function WayHandlers.handle_length(profile,way,result,data) + local keys = Sequence { 'maxlength' } + local forward, backward = Tags.get_forward_backward_by_set(way,data,keys) + forward = Measure.get_max_length(forward) + backward = Measure.get_max_length(backward) + + local keys_conditional = Sequence { 'maxlength:conditional' } + local forward_conditional, backward_conditional = Tags.get_forward_backward_by_set(way,data,keys_conditional) + + if forward and forward < profile.vehicle_length then + if forward_conditional and string.match(forward_conditional, 'no(ne)? ?@') and (string.match(forward_conditional, 'destination') or string.match(forward_conditional, 'delivery')) then + -- Discourage usage + result.forward_rate = math.min(result.forward_rate, (result.forward_speed * 0.7) / 3.6) + else + -- No legal access at any condition, set a large weight + result.forward_rate = math.min(result.forward_rate, (result.forward_speed * 0.2) / 3.6) + end + end + + if backward and backward < profile.vehicle_length then + if backward_conditional and string.match(backward_conditional, 'no(ne)? ?@') and (string.match(backward_conditional, 'destination') or string.match(backward_conditional, 'delivery')) then + -- Discourage usage + result.backward_rate = math.min(result.backward_rate, (result.backward_speed * 0.7) / 3.6) + else + -- No legal access at any condition, set a large weight + result.backward_rate = math.min(result.backward_rate, (result.backward_speed * 0.2) / 3.6) + end + end +end + +-- handle maxweight tags +function WayHandlers.handle_weight(profile,way,result,data) + local keys = Sequence { 'maxweight' } + local forward, backward = Tags.get_forward_backward_by_set(way,data,keys) + forward = Measure.get_max_weight(forward) + backward = Measure.get_max_weight(backward) + + local keys_conditional = Sequence { 'maxweight:conditional' } + local forward_conditional, backward_conditional = Tags.get_forward_backward_by_set(way,data,keys_conditional) + + if forward and forward < profile.vehicle_weight then + if forward_conditional and string.match(forward_conditional, 'no(ne)? ?@') and (string.match(forward_conditional, 'destination') or string.match(forward_conditional, 'delivery')) then + -- Discourage usage + result.forward_rate = math.max(1, math.min(result.forward_rate, (result.forward_speed * 0.7) / 3.6)) + else + -- No legal access at any condition, set a large weight + result.forward_rate = math.max(1, math.min(result.forward_rate, (result.forward_speed * 0.2) / 3.6)) + end + end + + if backward and backward < profile.vehicle_weight then + if backward_conditional and string.match(backward_conditional, 'no(ne)? ?@') and (string.match(backward_conditional, 'destination') or string.match(backward_conditional, 'delivery')) then + -- Discourage usage + result.backward_rate = math.max(1, math.min(result.backward_rate, (result.backward_speed * 0.7) / 3.6)) + else + -- No legal access at any condition, set a large weight + result.backward_rate = math.max(1, math.min(result.backward_rate, (result.backward_speed * 0.2) / 3.6)) + end + end +end + +-- handle hgv access tags +function WayHandlers.handle_hgv_access(profile,way,result,data) + local keys = Sequence { 'hgv', 'goods' } + local forward, backward = Tags.get_forward_backward_by_set(way,data,keys) + + local keys_conditional = Sequence { 'hgv:conditional', 'goods:conditional' } + local forward_conditional, backward_conditional = Tags.get_forward_backward_by_set(way,data,keys_conditional) + + if forward == 'no' and (not forward_conditional or not(string.match(forward_conditional, 'yes') or string.match(forward_conditional, 'destination') or string.match(forward_conditional, 'delivery'))) then + -- No legal access at any condition, set a large weight + result.forward_rate = math.min(result.forward_rate, (result.forward_speed * 0.1) / 3.6) + end + + if backward == 'no' and (not backward_conditional or not(string.match(backward_conditional, 'yes') or string.match(backward_conditional, 'destination') or string.match(backward_conditional, 'delivery'))) then + -- No legal access at any condition, set a large weight + result.backward_rate = math.min(result.backward_rate, (result.backward_speed * 0.1) / 3.6) + end +end + +-- handle oneways tags +function WayHandlers.oneway(profile,way,result,data) + if not profile.oneway_handling then + return + end + + local oneway + if profile.oneway_handling == true then + oneway = Tags.get_value_by_prefixed_sequence(way,profile.restrictions,'oneway') or way:get_value_by_key("oneway") + elseif profile.oneway_handling == 'specific' then + oneway = Tags.get_value_by_prefixed_sequence(way,profile.restrictions,'oneway') + elseif profile.oneway_handling == 'conditional' then + -- Following code assumes that `oneway` and `oneway:conditional` tags have opposite values and takes weakest (always `no`). + -- So if we will have: + -- oneway=yes, oneway:conditional=no @ (condition1) + -- oneway=no, oneway:conditional=yes @ (condition2) + -- condition1 will be always true and condition2 will be always false. + if way:get_value_by_key("oneway:conditional") then + oneway = "no" + else + oneway = Tags.get_value_by_prefixed_sequence(way,profile.restrictions,'oneway') or way:get_value_by_key("oneway") + end + end + + data.oneway = oneway + + if oneway == "-1" then + data.is_reverse_oneway = true + result.forward_mode = mode.inaccessible + elseif oneway == "yes" or + oneway == "1" or + oneway == "true" then + data.is_forward_oneway = true + result.backward_mode = mode.inaccessible + elseif profile.oneway_handling == true then + local junction = way:get_value_by_key("junction") + if data.highway == "motorway" or + junction == "roundabout" or + junction == "circular" then + if oneway ~= "no" then + -- implied oneway + data.is_forward_oneway = true + result.backward_mode = mode.inaccessible + end + end + end +end + +function WayHandlers.weights(profile,way,result,data) + if profile.properties.weight_name == 'distance' then + result.weight = -1 + -- set weight rates to 1 for the distance weight, edge weights are distance / rate + if (result.forward_mode ~= mode.inaccessible and result.forward_speed > 0) then + result.forward_rate = 1 + end + if (result.backward_mode ~= mode.inaccessible and result.backward_speed > 0) then + result.backward_rate = 1 + end + end +end + + +-- handle general avoid rules + +function WayHandlers.avoid_ways(profile,way,result,data) + if profile.avoid[data.highway] then + return false + end +end + +-- handle various that can block access +function WayHandlers.blocked_ways(profile,way,result,data) + + -- areas + if profile.avoid.area and way:get_value_by_key("area") == "yes" then + return false + end + + -- toll roads + if profile.avoid.toll and way:get_value_by_key("toll") == "yes" then + return false + end + + -- don't route over steps + if profile.avoid.steps and data.highway == "steps" then + return false + end + + -- construction + -- TODO if highway is valid then we shouldn't check railway, and vica versa + if profile.avoid.construction and (data.highway == 'construction' or way:get_value_by_key('railway') == 'construction') then + return false + end + + -- In addition to the highway=construction tag above handle the construction=* tag + -- http://wiki.openstreetmap.org/wiki/Key:construction + -- https://taginfo.openstreetmap.org/keys/construction#values + if profile.avoid.construction then + local construction = way:get_value_by_key('construction') + + -- Of course there are negative tags to handle, too + if construction and not profile.construction_whitelist[construction] then + return false + end + end + + -- Not only are there multiple construction tags there is also a proposed=* tag. + -- http://wiki.openstreetmap.org/wiki/Key:proposed + -- https://taginfo.openstreetmap.org/keys/proposed#values + if profile.avoid.proposed and way:get_value_by_key('proposed') then + return false + end + + -- Reversible oneways change direction with low frequency (think twice a day): + -- do not route over these at all at the moment because of time dependence. + -- Note: alternating (high frequency) oneways are handled below with penalty. + if profile.avoid.reversible and way:get_value_by_key("oneway") == "reversible" then + return false + end + + -- impassables + if profile.avoid.impassable then + if way:get_value_by_key("impassable") == "yes" then + return false + end + + if way:get_value_by_key("status") == "impassable" then + return false + end + end +end + +function WayHandlers.driving_side(profile, way, result, data) + local driving_side = way:get_value_by_key('driving_side') + if driving_side == nil then + driving_side = way:get_location_tag('driving_side') + end + + if driving_side == 'left' then + result.is_left_hand_driving = true + elseif driving_side == 'right' then + result.is_left_hand_driving = false + else + result.is_left_hand_driving = profile.properties.left_hand_driving + end +end + + +-- Call a sequence of handlers, aborting in case a handler returns false. Example: +-- +-- handlers = Sequence { +-- WayHandlers.tag_prefetch, +-- WayHandlers.default_mode, +-- WayHandlers.blocked_ways, +-- WayHandlers.access, +-- WayHandlers.speed, +-- WayHandlers.names +-- } +-- +-- WayHandlers.run(handlers,way,result,data,profile) +-- +-- Each method in the list will be called on the WayHandlers object. +-- All handlers must accept the parameteres (profile, way, result, data, relations) and return false +-- if the handler chain should be aborted. +-- To ensure the correct order of method calls, use a Sequence of handler names. + +function WayHandlers.run(profile, way, result, data, handlers, relations) + for i,handler in ipairs(handlers) do + if handler(profile, way, result, data, relations) == false then + return false + end + end +end + +return WayHandlers diff --git a/6/0/truck-soft/truck-soft.diff b/6/0/truck-soft/truck-soft.diff new file mode 100644 index 0000000..476352b --- /dev/null +++ b/6/0/truck-soft/truck-soft.diff @@ -0,0 +1,83 @@ +diff --git a/profiles/truck_7_5t.lua b/profiles/truck_7_5t.lua +index 35b4178..d616933 100644 +--- a/profiles/truck_7_5t.lua ++++ b/profiles/truck_7_5t.lua +@@ -15,7 +15,7 @@ Measure = require("lib/measure") + function setup() + return { + properties = { +- max_speed_for_map_matching = 180/3.6, -- 180kmph -> m/s ++ max_speed_for_map_matching = 150/3.6, -- 150kmph -> m/s + -- For routing based on duration, but weighted for preferring certain roads + weight_name = 'routability', + -- For shortest duration without penalties for accessibility +@@ -23,7 +23,7 @@ function setup() + -- For shortest distance without penalties for accessibility + -- weight_name = 'distance', + process_call_tagless_node = false, +- u_turn_penalty = 20, ++ u_turn_penalty = 30, + continue_straight_at_waypoint = true, + use_turn_restrictions = true, + left_hand_driving = false, +@@ -33,18 +33,18 @@ function setup() + default_speed = 10, + oneway_handling = true, + side_road_multiplier = 0.8, +- turn_penalty = 7.5, ++ turn_penalty = 25, + speed_reduction = 0.8, + turn_bias = 1.075, + cardinal_directions = false, + + -- Size of the vehicle, to be limited by physical restriction of the way +- vehicle_height = 2.0, -- in meters, 2.0m is the height slightly above biggest SUVs +- vehicle_width = 1.9, -- in meters, ways with narrow tag are considered narrower than 2.2m ++ vehicle_height = 2.8, -- in meters ++ vehicle_width = 2.2, -- in meters + + -- Size of the vehicle, to be limited mostly by legal restriction of the way +- vehicle_length = 4.8, -- in meters, 4.8m is the length of large or family car +- vehicle_weight = 2000, -- in kilograms ++ vehicle_length = 6.0, -- in meters, 4.8m is the length of large or familly car ++ vehicle_weight = 6000, -- in kilograms + + -- a list of suffixes to suppress in name change instructions. The suffixes also include common substrings of each other + suffix_list = { +@@ -156,6 +156,24 @@ function setup() + } + }, + ++ highway_penalties = { ++ motorway = 1, ++ motorway_link = 1, ++ trunk = 1, ++ trunk_link = 1, ++ primary = 1, ++ primary_link = 1, ++ secondary = 1, ++ secondary_link = 1, ++ tertiary = 0.9, ++ tertiary_link = 0.9, ++ unclassified = 0.8, ++ residential = 0.7, ++ living_street = 0.3, ++ service = 0.2, ++ track = 0.1 ++ }, ++ + service_penalties = { + alley = 0.5, + parking = 0.5, +@@ -438,6 +456,11 @@ function process_way(profile, way, result, relations) + WayHandlers.surface, + WayHandlers.penalties, + ++ -- set penalty to try to follow legal access restriction ++ WayHandlers.handle_weight, ++ WayHandlers.handle_length, ++ WayHandlers.handle_hgv_access, ++ + -- compute class labels + WayHandlers.classes, +