Let's say we have a tertiary road with the following tags: - maxspeed=60 - surface=gravel - smoothness=horrible The maxspeed tag tells us the legal speed limit, but the surface and smoothness tags have much more effect on the real-world speed of a car. In the default car profile, the gravel surface sets a driving speed of 40kph, and the horrible smoothness sets a driving speed of 10kph. It seems to me that the tags should be processed in this order: 1. maxspeed 2. surface 3. smoothness That is, maxspeed can be overridden by the surface tag, which can in turn be overridden by the smoothness tag. In the case of our secondary road, it would start with a driving speed of 60kph (from maxspeed), which would be reduced to 40kph (from surface), and end at 10kph (from smoothness). However, before this change, the maxspeed tag is processed *after* surface and smoothness: our road starts with a driving speed of 40kph (from surface), it's reduced to 10kph (from smoothness), and then *increased* to 60kph (from maxspeed). This change alters the processing order so that maxspeed is processed first, giving it the lowest priority of these three tags.
512 lines
14 KiB
Lua
512 lines
14 KiB
Lua
-- Car profile
|
|
|
|
api_version = 4
|
|
|
|
Set = require('lib/set')
|
|
Sequence = require('lib/sequence')
|
|
Handlers = require("lib/way_handlers")
|
|
Relations = require("lib/relations")
|
|
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 = 180/3.6, -- 180kmph -> 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 = 20,
|
|
continue_straight_at_waypoint = true,
|
|
use_turn_restrictions = true,
|
|
left_hand_driving = false,
|
|
traffic_light_penalty = 2,
|
|
},
|
|
|
|
default_mode = mode.driving,
|
|
default_speed = 10,
|
|
oneway_handling = true,
|
|
side_road_multiplier = 0.8,
|
|
turn_penalty = 7.5,
|
|
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
|
|
|
|
-- 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
|
|
|
|
-- 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
|
|
}
|
|
},
|
|
|
|
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,
|
|
["by:urban"] = 60,
|
|
["by:motorway"] = 110,
|
|
["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,
|
|
["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,
|
|
['pl:rural'] = 100,
|
|
['pl:trunk'] = 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
|
|
result.barrier = true
|
|
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
|
|
result.barrier = true
|
|
end
|
|
end
|
|
end
|
|
|
|
-- check if node is a traffic light
|
|
local tag = node:get_value_by_key("highway")
|
|
if "traffic_signals" == tag then
|
|
result.traffic_lights = true
|
|
end
|
|
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.maxspeed,
|
|
WayHandlers.speed,
|
|
WayHandlers.surface,
|
|
WayHandlers.penalties,
|
|
|
|
-- 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
|
|
|
|
if turn.has_traffic_light then
|
|
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
|
|
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
|
|
}
|