diff --git a/CHANGELOG.md b/CHANGELOG.md index d5a00bc78..28572d8b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ - `properties.uturn_penalty` is deprecated. Set it in the `turn_function`. The turn type is exposed as `ExtractionTurn::direction_modifier`. - `properties.traffic_light_penalty` is deprecated. Traffic light penalties now need to be set over in the turn function. Each turn with a traffic light is marked with `ExtractionTurn::has_traffic_light = true`. + - Renamed the helper file `profiles/lib/directional.lua` to `profiles/lib/tags.lua` since it now provides more general tags parsing utility functions. + - The car and foot profiles now depend on the helper file `profiles/lib/handlers.lua`. - Infrastructure - Disabled link-time optimized (LTO) builds by default. Enable by passing `-DENABLE_LTO=ON` to `cmake` if you need the performance and know what you are doing. - Datafile versioning is now based on OSRM semver values, rather than source code checksums. diff --git a/features/car/side_bias.feature b/features/car/side_bias.feature index 8bfe541e5..65fb5e5f7 100644 --- a/features/car/side_bias.feature +++ b/features/car/side_bias.feature @@ -12,7 +12,7 @@ Feature: Testbot - side bias Given the profile file "car" extended with """ properties.left_hand_driving = true - turn_bias = properties.left_hand_driving and 1/1.075 or 1.075 + profile.turn_bias = properties.left_hand_driving and 1/1.075 or 1.075 """ Given the node map """ @@ -35,7 +35,7 @@ Feature: Testbot - side bias Given the profile file "car" extended with """ properties.left_hand_driving = false - turn_bias = properties.left_hand_driving and 1/1.075 or 1.075 + profile.turn_bias = properties.left_hand_driving and 1/1.075 or 1.075 """ And the node map """ diff --git a/profiles/car.lua b/profiles/car.lua index 062a4465c..bf8a848cf 100644 --- a/profiles/car.lua +++ b/profiles/car.lua @@ -1,193 +1,12 @@ +-- Car profile + api_version = 1 --- Car profile local find_access_tag = require("lib/access").find_access_tag -local get_destination = require("lib/destination").get_destination -local set_classification = require("lib/guidance").set_classification -local get_turn_lanes = require("lib/guidance").get_turn_lanes local Set = require('lib/set') local Sequence = require('lib/sequence') -local Directional = require('lib/directional') - --- Begin of globals -barrier_whitelist = Set { - 'cattle_grid', - 'border_control', - 'checkpoint', - 'toll_booth', - 'sally_port', - 'gate', - 'lift_gate', - 'no', - 'entrance' -} - -access_tag_whitelist = Set { - 'yes', - 'motorcar', - 'motor_vehicle', - 'vehicle', - 'permissive', - 'designated', - 'destination' -} - -access_tag_blacklist = Set { - 'no', - 'private', - 'agricultural', - 'forestry', - 'emergency', - 'psv', - 'delivery' -} - -access_tags_hierarchy = Sequence { - 'motorcar', - 'motor_vehicle', - 'vehicle', - 'access' -} - -service_tag_forbidden = Set { - 'emergency_access' -} - -restrictions = Sequence { - 'motorcar', "motor_vehicle", "vehicle" } - --- A list of suffixes to suppress in name change instructions --- Note: a Set does not work here because it's read from C++ -suffix_list = { - 'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'North', 'South', 'West', 'East' -} - -speed_profile = { - 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, ---track = 5, - ferry = 5, - movable = 5, - shuttle_train = 10, - default = 10 -} - --- service speeds -service_speeds = { - alley = 5, - parking = 5, - parking_aisle = 5, - driveway = 5, - ["drive-through"] = 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 = { - ["ch:rural"] = 80, - ["ch:trunk"] = 100, - ["ch:motorway"] = 120, - ["de:living_street"] = 7, - ["ru:living_street"] = 20, - ["ru:urban"] = 60, - ["ua:urban"] = 60, - ["at:rural"] = 100, - ["de:rural"] = 100, - ["at:trunk"] = 100, - ["cz:trunk"] = 0, - ["ro:trunk"] = 100, - ["cz:motorway"] = 0, - ["de:motorway"] = 0, - ["ru:motorway"] = 110, - ["gb:nsl_single"] = (60*1609)/1000, - ["gb:nsl_dual"] = (70*1609)/1000, - ["gb:motorway"] = (70*1609)/1000, - ["uk:nsl_single"] = (60*1609)/1000, - ["uk:nsl_dual"] = (70*1609)/1000, - ["uk:motorway"] = (70*1609)/1000, - ["nl:rural"] = 80, - ["nl:trunk"] = 100, - ["none"] = 140 -} +local Handlers = require("lib/handlers") +local next = next -- bind to local for speed -- set profile properties properties.max_speed_for_map_matching = 180/3.6 -- 180kmph -> m/s @@ -198,67 +17,233 @@ properties.left_hand_driving = false properties.weight_name = 'duration' --properties.weight_name = 'distance' -local side_road_speed_multiplier = 0.8 -local turn_penalty = 7.5 -local traffic_light_penalty = 2 -local u_turn_penalty = 20 +local profile = { + default_mode = mode.driving, + default_speed = 10, + oneway_handling = true, + + side_road_speed_multiplier = 0.8, + turn_penalty = 7.5, + speed_reduction = 0.8, + traffic_light_penalty = 2, + u_turn_penalty = 20, + + -- Note: this biases right-side driving. + -- Should be inverted for left-driving countries. + turn_bias = properties.left_hand_driving and 1/1.075 or 1.075, --- Note: this biases right-side driving. Should be --- inverted for left-driving countries. -local turn_bias = properties.left_hand_driving and 1/1.075 or 1.075 + -- a list of suffixes to suppress in name change instructions + suffix_list = { + 'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'North', 'South', 'West', 'East' + }, + + barrier_whitelist = Set { + 'cattle_grid', + 'border_control', + 'checkpoint', + 'toll_booth', + 'sally_port', + 'gate', + 'lift_gate', + 'no', + 'entrance' + }, -local obey_oneway = true -local ignore_areas = true -local ignore_hov_ways = true -local ignore_toll_ways = false + access_tag_whitelist = Set { + 'yes', + 'motorcar', + 'motor_vehicle', + 'vehicle', + 'permissive', + 'designated', + 'destination' + }, -local abs = math.abs -local min = math.min -local max = math.max + access_tag_blacklist = Set { + 'no', + 'private', + 'agricultural', + 'forestry', + 'emergency', + 'psv', + 'delivery' + }, -local speed_reduction = 0.8 + access_tags_hierarchy = Sequence { + 'motorcar', + 'motor_vehicle', + 'vehicle', + 'access' + }, + + service_tag_forbidden = Set { + 'emergency_access' + }, + + restrictions = Sequence { + 'motorcar', + 'motor_vehicle', + 'vehicle' + }, + + avoid = Set { + 'area', + 'toll', + 'reversible', + 'impassable', + 'hov_lanes' + }, + + 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_speeds = { + alley = 5, + parking = 5, + parking_aisle = 5, + driveway = 5, + ["drive-through"] = 5 + }, + + 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 = { + ["ch:rural"] = 80, + ["ch:trunk"] = 100, + ["ch:motorway"] = 120, + ["de:living_street"] = 7, + ["ru:living_street"] = 20, + ["ru:urban"] = 60, + ["ua:urban"] = 60, + ["at:rural"] = 100, + ["de:rural"] = 100, + ["at:trunk"] = 100, + ["cz:trunk"] = 0, + ["ro:trunk"] = 100, + ["cz:motorway"] = 0, + ["de:motorway"] = 0, + ["ru:motorway"] = 110, + ["gb:nsl_single"] = (60*1609)/1000, + ["gb:nsl_dual"] = (70*1609)/1000, + ["gb:motorway"] = (70*1609)/1000, + ["uk:nsl_single"] = (60*1609)/1000, + ["uk:nsl_dual"] = (70*1609)/1000, + ["uk:motorway"] = (70*1609)/1000, + ["nl:rural"] = 80, + ["nl:trunk"] = 100, + ["none"] = 140 + } +} function get_name_suffix_list(vector) - for index,suffix in ipairs(suffix_list) do + for index,suffix in ipairs(profile.suffix_list) do vector:Add(suffix) end end function get_restrictions(vector) - for i,v in ipairs(restrictions) do + for i,v in ipairs(profile.restrictions) do vector:Add(v) end end -local function parse_maxspeed(source) - if not source then - return 0 - end - local n = tonumber(source:match("%d*")) - if n then - if string.match(source, "mph") or string.match(source, "mp/h") then - n = (n*1609)/1000 - end - else - -- parse maxspeed like FR:urban - source = string.lower(source) - n = maxspeed_table[source] - if not n then - local highway_type = string.match(source, "%a%a:(%a+)") - n = maxspeed_table_default[highway_type] - if not n then - n = 0 - end - end - end - return n -end - function node_function (node, result) -- parse access and barrier tags - local access = find_access_tag(node, access_tags_hierarchy) + local access = find_access_tag(node, profile.access_tags_hierarchy) if access then - if access_tag_blacklist[access] then + if profile.access_tag_blacklist[access] then result.barrier = true end else @@ -268,7 +253,7 @@ function node_function (node, result) local bollard = node:get_value_by_key("bollard") local rising_bollard = bollard and "rising" == bollard - if not barrier_whitelist[barrier] and not rising_bollard then + if not profile.barrier_whitelist[barrier] and not rising_bollard then result.barrier = true end end @@ -281,459 +266,95 @@ function node_function (node, result) end end --- abort early if this way is obviouslt not routable -function initial_routability_check(way,result,data) - data.highway = way:get_value_by_key('highway') - return data.highway ~= nil or - way:get_value_by_key('route') ~= nil or - way:get_value_by_key('bridge') ~= nil -end +function way_function(way, result) + -- 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. --- all lanes restricted to hov vehicles? -local function 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 + -- 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. --- handle high occupancy vehicle tags -function handle_hov(way,result,data) - -- respect user-preference for HOV - if not ignore_hov_ways then + -- 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. here we require at least one + -- of the prefetched tags to be present, ie. the data table + -- cannot be empty + if next(data) == nil then -- is the data table empty? return end - -- check if way is hov only - local hov = way:get_value_by_key("hov") - if "designated" == hov then - return false - 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 + 'handle_default_mode', - -- check if all lanes are hov only - local hov_lanes_forward, hov_lanes_backward = Directional.get_values_by_key(way,data,'hov:lanes') - local inaccessible_forward = has_all_designated_hov_lanes(hov_lanes_forward) - local inaccessible_backward = has_all_designated_hov_lanes(hov_lanes_backward) + -- check various tags that could indicate that the way is not + -- routable. this includes things like status=impassable, + -- toll=yes and oneway=reversible + 'handle_blocked_ways', - if inaccessible_forward then - result.forward_mode = mode.inaccessible - end - if inaccessible_backward then - result.backward_mode = mode.inaccessible - end -end + -- determine access status by checking our hierarchy of + -- access tags, e.g: motorcar, motor_vehicle, vehicle + 'handle_access', --- handle various that can block access -function is_way_blocked(way,result) - -- we dont route over areas - local area = way:get_value_by_key("area") - if ignore_areas and "yes" == area then - return false - end + -- check whether forward/backward directons are routable + 'handle_oneway', - -- respect user-preference for toll=yes ways - local toll = way:get_value_by_key("toll") - if ignore_toll_ways and "yes" == toll then - return false - end + -- check whether forward/backward directons are routable + 'handle_destinations', - -- 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. - local oneway = way:get_value_by_key("oneway") - if "reversible" == oneway then - return false - end + -- check whether we're using a special transport mode + 'handle_ferries', + 'handle_movables', - local impassable = way:get_value_by_key("impassable") - if "yes" == impassable then - return false - end + -- handle service road restrictions + 'handle_service', - local status = way:get_value_by_key("status") - if "impassable" == status then - return false - end -end + -- check high occupancy vehicle restrictions + 'handle_hov', --- set default mode -function set_default_mode(way,result) - result.forward_mode = mode.driving - result.backward_mode = mode.driving -end + -- compute speed taking into account way type, maxspeed tags, etc. + 'handle_speed', + 'handle_side_roads', + 'handle_surface', + 'handle_maxspeed', + 'handle_speed_scaling', + 'handle_alternating_speed', --- check accessibility by traversing our acces tag hierarchy -function handle_access(way,result,data) - data.forward_access, data.backward_access = - Directional.get_values_by_set(way,data,access_tags_hierarchy) + -- handle turn lanes and road classification, used for guidance + 'handle_turn_lanes', + 'handle_classification', - if access_tag_blacklist[data.forward_access] then - result.forward_mode = mode.inaccessible - end + -- handle various other flags + 'handle_roundabouts', + 'handle_startpoint', - if access_tag_blacklist[data.backward_access] then - result.backward_mode = mode.inaccessible - end - - if result.forward_mode == mode.inaccessible and result.backward_mode == mode.inaccessible then - return false - end -end - --- handling ferries and piers -function handle_ferries(way,result) - local route = way:get_value_by_key("route") - if route then - local route_speed = speed_profile[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 = 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 handle_movables(way,result) - local bridge = way:get_value_by_key("bridge") - if bridge then - local bridge_speed = speed_profile[bridge] - if bridge_speed and bridge_speed > 0 then - local capacity_car = way:get_value_by_key("capacity:car") - if capacity_car ~= 0 then - local duration = way:get_value_by_key("duration") - if duration and durationIsValid(duration) then - result.duration = max( parseDuration(duration), 1 ) - end - result.forward_speed = bridge_speed - result.backward_speed = bridge_speed - end - end - end -end - --- handle speed (excluding maxspeed) -function handle_speed(way,result,data) - if result.forward_speed == -1 then - local highway_speed = speed_profile[data.highway] - -- Set the avg speed on the way if it is accessible by road class - if highway_speed then - result.forward_speed = highway_speed - result.backward_speed = highway_speed - else - -- Set the avg speed on ways that are marked accessible - if access_tag_whitelist[data.forward_access] then - result.forward_speed = speed_profile["default"] - end - - if access_tag_whitelist[data.backward_access] then - result.backward_speed = speed_profile["default"] - end - end - end - - if -1 == result.forward_speed and -1 == result.backward_speed then - return false - end - - if handle_side_roads(way,result) == false then return false end - if handle_surface(way,result) == false then return false end - if handle_maxspeed(way,data,result) == false then return false end - if handle_speed_scaling(way,result) == false then return false end - if handle_alternating_speed(way,result) == false then return false end -end - --- reduce speed on special side roads -function handle_side_roads(way,result) - local sideway = way:get_value_by_key("side_road") - if "yes" == sideway or "rotary" == sideway then - result.forward_speed = result.forward_speed * side_road_speed_multiplier - result.backward_speed = result.backward_speed * side_road_speed_multiplier - end -end - --- reduce speed on bad surfaces -function handle_surface(way,result) - 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 surface_speeds[surface] then - result.forward_speed = math.min(surface_speeds[surface], result.forward_speed) - result.backward_speed = math.min(surface_speeds[surface], result.backward_speed) - end - if tracktype and tracktype_speeds[tracktype] then - result.forward_speed = math.min(tracktype_speeds[tracktype], result.forward_speed) - result.backward_speed = math.min(tracktype_speeds[tracktype], result.backward_speed) - end - if smoothness and smoothness_speeds[smoothness] then - result.forward_speed = math.min(smoothness_speeds[smoothness], result.forward_speed) - result.backward_speed = math.min(smoothness_speeds[smoothness], result.backward_speed) - end -end - --- handles name, including ref and pronunciation -function handle_names(way,result) - -- 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") - - -- 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 -end - --- handle turn lanes -function handle_turn_lanes(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 - --- junctions -function handle_roundabouts(way,result) - 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 - --- service roads -function handle_service(way,result) - local service = way:get_value_by_key("service") - if service then - -- Set don't allow access to certain service roads - if service_tag_forbidden[service] then - result.forward_mode = mode.inaccessible - result.backward_mode = mode.inaccessible - return false - end - end -end - --- scale speeds to get better average driving times -function handle_speed_scaling(way,result) - local width = math.huge - local lanes = math.huge - if result.forward_speed > 0 or result.backward_speed > 0 then - 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 - end - - local is_bidirectional = result.forward_mode ~= mode.inaccessible and - result.backward_mode ~= mode.inaccessible - - local service = way:get_value_by_key("service") - if result.forward_speed > 0 then - local scaled_speed = result.forward_speed * speed_reduction - local penalized_speed = math.huge - if service and service_speeds[service] then - penalized_speed = service_speeds[service] - elseif width <= 3 or (lanes <= 1 and is_bidirectional) then - penalized_speed = result.forward_speed / 2 - end - result.forward_speed = math.min(penalized_speed, scaled_speed) - end - - if result.backward_speed > 0 then - local scaled_speed = result.backward_speed * speed_reduction - local penalized_speed = math.huge - if service and service_speeds[service]then - penalized_speed = service_speeds[service] - elseif width <= 3 or (lanes <= 1 and is_bidirectional) then - penalized_speed = result.backward_speed / 2 - end - result.backward_speed = math.min(penalized_speed, scaled_speed) - end -end - --- handle oneways tags -function handle_oneway(way,result,data) - local oneway = way:get_value_by_key("oneway") - data.oneway = oneway - if obey_oneway then - 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 - else - 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 -end - --- handle destination tags -function handle_destinations(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 - --- maxspeed and advisory maxspeed -function handle_maxspeed(way,data,result) - local keys = Sequence { 'maxspeed:advisory', 'maxspeed' } - local forward, backward = Directional.get_values_by_set(way,data,keys) - forward = parse_maxspeed(forward) - backward = parse_maxspeed(backward) - - if forward and forward > 0 then - result.forward_speed = forward - end - - if backward and backward > 0 then - result.backward_speed = backward - end -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. -function handle_alternating_speed(way,result) - if "alternating" == way:get_value_by_key('oneway') then - local scaling_factor = 0.4 - if result.forward_speed ~= math.huge then - result.forward_speed = result.forward_speed * scaling_factor - end - if result.backward_speed ~= math.huge then - result.backward_speed = result.backward_speed * scaling_factor - end - end -end - - --- determine if this way can be used as a start/end point for routing -function handle_startpoint(way,result) - -- only allow this road as start point if it not a ferry - result.is_startpoint = result.forward_mode == mode.driving or - result.backward_mode == mode.driving -end - --- set the road classification based on guidance globals configuration -function handle_classification(way,result,data) - set_classification(data.highway,result,way) -end - --- main entry point for processsing a way -function way_function(way, result) - -- intermediate values used during processing - local data = {} - - -- to optimize processing, 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 complicated edge - -- cases later. - - -- perform an quick initial check and abort if way is obviously - -- not routable, e.g. because it does not have any of the key - -- tags indicating routability - if initial_routability_check(way,result,data) == false then return end - - -- set the default mode for this profile. if can be changed later - -- in case it turns we're e.g. on a ferry - if set_default_mode(way,result) == false then return end - - -- check various tags that could indicate that the way is not - -- routable. this includes things like status=impassable, - -- toll=yes and oneway=reversible - if is_way_blocked(way,result) == false then return end - - -- determine access status by checking our hierarchy of - -- access tags, e.g: motorcar, motor_vehicle, vehicle - if handle_access(way,result,data) == false then return end - - -- check whether forward/backward directons are routable - if handle_oneway(way,result,data) == false then return end - - -- check whether forward/backward directons are routable - if handle_destinations(way,result,data) == false then return end - - -- check whether we're using a special transport mode - if handle_ferries(way,result) == false then return end - if handle_movables(way,result) == false then return end - - -- handle service road restrictions - if handle_service(way,result) == false then return end - - -- check high occupancy vehicle restrictions - if handle_hov(way,result,data) == false then return end - - -- compute speed taking into account way type, maxspeed tags, etc. - if handle_speed(way,result,data) == false then return end - - -- handle turn lanes and road classification, used for guidance - if handle_turn_lanes(way,result,data) == false then return end - if handle_classification(way,result,data) == false then return end - - -- handle various other flags - if handle_roundabouts(way,result) == false then return end - if handle_startpoint(way,result) == false then return end - - -- set name, ref and pronunciation - if handle_names(way,result) == false then return end + -- set name, ref and pronunciation + 'handle_names' + } + + Handlers.run(handlers,way,result,data,profile) end function turn_function (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 = profile.turn_bias + if turn.turn_type ~= turn_type.no_turn then if turn.angle >= 0 then turn.duration = turn_penalty / (1 + math.exp( -((13 / turn_bias) * turn.angle/180 - 6.5*turn_bias))) @@ -742,11 +363,11 @@ function turn_function (turn) end if turn.direction_modifier == direction_modifier.u_turn then - turn.duration = turn.duration + u_turn_penalty + turn.duration = turn.duration + profile.u_turn_penalty end if turn.has_traffic_light then - turn.duration = turn.duration + traffic_light_penalty + turn.duration = turn.duration + profile.traffic_light_penalty end -- for distance based routing we don't want to have penalties based on turn angle diff --git a/profiles/foot.lua b/profiles/foot.lua index f57b0a21e..4e6c8b25d 100644 --- a/profiles/foot.lua +++ b/profiles/foot.lua @@ -1,167 +1,131 @@ +-- Foot profile + api_version = 1 --- Foot profile local find_access_tag = require("lib/access").find_access_tag -local get_destination = require("lib/destination").get_destination -local set_classification = require("lib/guidance").set_classification local Set = require('lib/set') local Sequence = require('lib/sequence') -local Directional = require('lib/directional') - -barrier_whitelist = Set { - 'cycle_barrier', - 'bollard', - 'entrance', - 'cattle_grid', - 'border_control', - 'toll_booth', - 'sally_port', - 'gate', - 'no', - 'block' -} -access_tag_whitelist = Set { - 'yes', - 'foot', - 'permissive', - 'designated' -} -access_tag_blacklist = Set { - 'no', - 'private', - 'agricultural', - 'forestry', - 'delivery' -} - -access_tags_hierarchy = Sequence { - 'foot', - 'access' -} - -restrictions = Sequence { 'foot' } - --- A list of suffixes to suppress in name change instructions --- Note: a Set does not work here because it's read from C++ -suffix_list = { - 'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'North', 'South', 'West', 'East' -} - -walking_speed = 5 - -speed_profile = { - primary = walking_speed, - primary_link = walking_speed, - secondary = walking_speed, - secondary_link = walking_speed, - tertiary = walking_speed, - tertiary_link = walking_speed, - unclassified = walking_speed, - residential = walking_speed, - road = walking_speed, - living_street = walking_speed, - service = walking_speed, - track = walking_speed, - path = walking_speed, - steps = walking_speed, - pedestrian = walking_speed, - footway = walking_speed, - pier = walking_speed, - default = walking_speed -} - -route_speeds = { - ferry = 5 -} - -platform_speeds = { - platform = walking_speed -} - -amenity_speeds = { - parking = walking_speed, - parking_entrance = walking_speed -} - -man_made_speeds = { - pier = walking_speed -} - -surface_speeds = { - fine_gravel = walking_speed*0.75, - gravel = walking_speed*0.75, - pebblestone = walking_speed*0.75, - mud = walking_speed*0.5, - sand = walking_speed*0.5 -} - -tracktype_speeds = {} - -smoothness_speeds = {} - -leisure_speeds = { - track = walking_speed -} +local Handlers = require("lib/handlers") +local next = next -- bind to local for speed properties.max_speed_for_map_matching = 40/3.6 -- kmph -> m/s properties.use_turn_restrictions = false properties.continue_straight_at_waypoint = false properties.weight_name = 'duration' -local traffic_light_penalty = 2 -local u_turn_penalty = 2 +local walking_speed = 5 +local profile = { + default_mode = mode.walking, + default_speed = walking_speed, + oneway_handling = 'specific', -- respect 'oneway:foot' but not 'oneway' + traffic_light_penalty = 2, + u_turn_penalty = 2, + + barrier_whitelist = Set { + 'cycle_barrier', + 'bollard', + 'entrance', + 'cattle_grid', + 'border_control', + 'toll_booth', + 'sally_port', + 'gate', + 'no', + 'block' + }, + + access_tag_whitelist = Set { + 'yes', + 'foot', + 'permissive', + 'designated' + }, + + access_tag_blacklist = Set { + 'no', + 'private', + 'agricultural', + 'forestry', + 'delivery' + }, + access_tags_hierarchy = Sequence { + 'foot', + 'access' + }, --- setting oneway_handling to 'specific' means that the plain 'oneway' tag is ignored, --- but oneway:foot (or or more specific modes) is respected. + restrictions = Sequence { + 'foot' + }, -local oneway_handling = 'specific' + -- list of suffixes to suppress in name change instructions + suffix_list = Set { + 'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'North', 'South', 'West', 'East' + }, + + avoid = Set { + 'impassable' + }, + + speeds = Sequence { + highway = { + primary = walking_speed, + primary_link = walking_speed, + secondary = walking_speed, + secondary_link = walking_speed, + tertiary = walking_speed, + tertiary_link = walking_speed, + unclassified = walking_speed, + residential = walking_speed, + road = walking_speed, + living_street = walking_speed, + service = walking_speed, + track = walking_speed, + path = walking_speed, + steps = walking_speed, + pedestrian = walking_speed, + footway = walking_speed, + pier = walking_speed, + }, -local ignore_areas = false + railway = { + platform = walking_speed + }, + amenity = { + parking = walking_speed, + parking_entrance= walking_speed + }, -function get_name_suffix_list(vector) - for index,suffix in ipairs(suffix_list) do - vector:Add(suffix) - end -end + man_made = { + pier = walking_speed + }, + + leisure = { + track = walking_speed + } + }, + + route_speeds = { + ferry = 5 + }, + + surface_speeds = { + fine_gravel = walking_speed*0.75, + gravel = walking_speed*0.75, + pebblestone = walking_speed*0.75, + mud = walking_speed*0.5, + sand = walking_speed*0.5 + } +} -function get_restrictions(vector) - for i,v in ipairs(restrictions) do - vector:Add(v) - end -end - -local function parse_maxspeed(source) - if not source then - return 0 - end - local n = tonumber(source:match("%d*")) - if n then - if string.match(source, "mph") or string.match(source, "mp/h") then - n = (n*1609)/1000 - end - else - -- parse maxspeed like FR:urban - source = string.lower(source) - n = maxspeed_table[source] - if not n then - local highway_type = string.match(source, "%a%a:(%a+)") - n = maxspeed_table_default[highway_type] - if not n then - n = 0 - end - end - end - return n -end function node_function (node, result) -- parse access and barrier tags - local access = find_access_tag(node, access_tags_hierarchy) + local access = find_access_tag(node, profile.access_tags_hierarchy) if access then - if access_tag_blacklist[access] then + if profile.access_tag_blacklist[access] then result.barrier = true end else @@ -171,7 +135,7 @@ function node_function (node, result) local bollard = node:get_value_by_key("bollard") local rising_bollard = bollard and "rising" == bollard - if not barrier_whitelist[barrier] and not rising_bollard then + if not profile.barrier_whitelist[barrier] and not rising_bollard then result.barrier = true end end @@ -184,330 +148,91 @@ function node_function (node, result) end end --- abort early if this way is obviouslt not routable -function initial_routability_check(way,result,data) - data.highway = way:get_value_by_key('highway') - data.leisure = way:get_value_by_key("leisure") - data.route = way:get_value_by_key("route") - data.bridge = way:get_value_by_key("bridge") - data.man_made = way:get_value_by_key("man_made") - data.railway = way:get_value_by_key("railway") - data.platform = way:get_value_by_key("platform") - data.amenity = way:get_value_by_key("amenity") - data.public_transport = way:get_value_by_key("public_transport") - - return data.highway ~= nil or - data.leisure ~= nil or - data.route ~= nil or - data.bridge ~= nil or - data.railway ~= nil or - data.platform ~= nil or - data.amenity ~= nil or - data.man_made ~= nil -end - --- handle various that can block access -function is_way_blocked(way,result) - -- we dont route over areas - local area = way:get_value_by_key("area") - if ignore_areas and "yes" == area then - return false - end - - local impassable = way:get_value_by_key("impassable") - if "yes" == impassable then - return false - end - - local status = way:get_value_by_key("status") - if "impassable" == status then - return false - end -end - --- set default mode -function set_default_mode(way,result) - result.forward_mode = mode.walking - result.backward_mode = mode.walking -end - --- check accessibility by traversing our acces tag hierarchy -function handle_access(way,result,data) - data.forward_access, data.backward_access = - Directional.get_values_by_set(way,data,access_tags_hierarchy) - - if access_tag_blacklist[data.forward_access] then - result.forward_mode = mode.inaccessible - end - - if access_tag_blacklist[data.backward_access] then - result.backward_mode = mode.inaccessible - end - - if result.forward_mode == mode.inaccessible and result.backward_mode == mode.inaccessible then - return false - end -end - --- handling ferries and piers -function handle_ferries(way,result) - local route = way:get_value_by_key("route") - if route then - local route_speed = 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 handle_movables(way,result) - local bridge = way:get_value_by_key("bridge") - if bridge then - local bridge_speed = speed_profile[bridge] - if bridge_speed and bridge_speed > 0 then - local capacity_car = way:get_value_by_key("capacity:car") - if capacity_car ~= 0 then - local duration = way:get_value_by_key("duration") - if duration and durationIsValid(duration) then - result.duration = max( parseDuration(duration), 1 ) - end - result.forward_speed = bridge_speed - result.backward_speed = bridge_speed - end - end - end -end - --- handle speed (excluding maxspeed) -function handle_speed(way,result,data) - if result.forward_speed == -1 then - local speed = speed_profile[data.highway] or - platform_speeds[data.railway] or -- old tagging scheme - platform_speeds[data.platform] or - amenity_speeds[data.amenity] or - man_made_speeds[data.man_made] or - leisure_speeds[data.leisure] - - if speed then - -- set speed by way type - result.forward_speed = highway_speed - result.backward_speed = highway_speed - result.forward_speed = speed - result.backward_speed = speed - else - -- Set the avg speed on ways that are marked accessible - if access_tag_whitelist[data.forward_access] then - result.forward_speed = speed_profile["default"] - end - - if access_tag_whitelist[data.backward_access] then - result.backward_speed = speed_profile["default"] - end - end - end - - if -1 == result.forward_speed and -1 == result.backward_speed then - return false - end - - if handle_surface(way,result) == false then return false end -end - - --- reduce speed on bad surfaces -function handle_surface(way,result) - 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 surface_speeds[surface] then - result.forward_speed = math.min(surface_speeds[surface], result.forward_speed) - result.backward_speed = math.min(surface_speeds[surface], result.backward_speed) - end - if tracktype and tracktype_speeds[tracktype] then - result.forward_speed = math.min(tracktype_speeds[tracktype], result.forward_speed) - result.backward_speed = math.min(tracktype_speeds[tracktype], result.backward_speed) - end - if smoothness and smoothness_speeds[smoothness] then - result.forward_speed = math.min(smoothness_speeds[smoothness], result.forward_speed) - result.backward_speed = math.min(smoothness_speeds[smoothness], result.backward_speed) - end -end - --- handles name, including ref and pronunciation -function handle_names(way,result) - -- 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") - - -- 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 -end - --- handle turn lanes -function handle_turn_lanes(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 - --- junctions -function handle_roundabouts(way,result) - 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 - --- handle oneways tags -function handle_oneway(way,result,data) - local oneway - if oneway_handling == true then - oneway = Directional.get_value_by_prefixed_sequence(way,restrictions,'oneway') or way:get_value_by_key('oneway') - elseif oneway_handling == 'specific' then - oneway = Directional.get_value_by_prefixed_sequence(way,restrictions,'oneway') - end - - data.oneway = oneway - - if oneway then - 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 - else - 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 -end - --- handle destination tags -function handle_destinations(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 - --- determine if this way can be used as a start/end point for routing -function handle_startpoint(way,result) - -- only allow this road as start point if it not a ferry - result.is_startpoint = result.forward_mode == mode.walking or - result.backward_mode == mode.walking -end - --- set the road classification based on guidance globals configuration -function handle_classification(way,result,data) - set_classification(data.highway,result,way) -end - -- main entry point for processsing a way function way_function(way, result) - -- intermediate values used during processing - local data = {} + -- 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. - -- to optimize processing, we should try to abort as soon as + -- 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 complicated edge - -- cases later. + -- commonly forbids access early, and handle edge cases later. - -- perform an quick initial check and abort if way is obviously - -- not routable, e.g. because it does not have any of the key - -- tags indicating routability - if initial_routability_check(way,result,data) == false then return end + -- 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'), + leisure = way:get_value_by_key('leisure'), + man_made = way:get_value_by_key('man_made'), + railway = way:get_value_by_key('railway'), + platform = way:get_value_by_key('platform'), + amenity = way:get_value_by_key('amenity'), + public_transport = way:get_value_by_key('public_transport') + } - -- set the default mode for this profile. if can be changed later - -- in case it turns we're e.g. on a ferry - if set_default_mode(way,result) == false then return end + -- perform an quick initial check and abort if the way is + -- obviously not routable. here we require at least one + -- of the prefetched tags to be present, ie. the data table + -- cannot be empty + if next(data) == nil then -- is the data table empty? + return + end - -- check various tags that could indicate that the way is not - -- routable. this includes things like status=impassable, - -- toll=yes and oneway=reversible - if is_way_blocked(way,result) == false then return end + local 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 + 'handle_default_mode', - -- determine access status by checking our hierarchy of - -- access tags, e.g: motorcar, motor_vehicle, vehicle - if handle_access(way,result,data) == false then return end + -- check various tags that could indicate that the way is not + -- routable. this includes things like status=impassable, + -- toll=yes and oneway=reversible + 'handle_blocked_ways', - -- check whether forward/backward directons are routable - if handle_oneway(way,result,data) == false then return end + -- determine access status by checking our hierarchy of + -- access tags, e.g: motorcar, motor_vehicle, vehicle + 'handle_access', - -- check whether forward/backward directons are routable - if handle_destinations(way,result,data) == false then return end + -- check whether forward/backward directons are routable + 'handle_oneway', - -- check whether we're using a special transport mode - if handle_ferries(way,result) == false then return end - if handle_movables(way,result) == false then return end + -- check whether forward/backward directons are routable + 'handle_destinations', - -- compute speed taking into account way type, maxspeed tags, etc. - if handle_speed(way,result,data) == false then return end + -- check whether we're using a special transport mode + 'handle_ferries', + 'handle_movables', - -- handle turn lanes and road classification, used for guidance - if handle_classification(way,result,data) == false then return end + -- compute speed taking into account way type, maxspeed tags, etc. + 'handle_speed', + 'handle_surface', - -- handle various other flags - if handle_roundabouts(way,result) == false then return end - if handle_startpoint(way,result) == false then return end + -- handle turn lanes and road classification, used for guidance + 'handle_classification', - -- set name, ref and pronunciation - if handle_names(way,result) == false then return end + -- handle various other flags + 'handle_roundabouts', + 'handle_startpoint', + + -- set name, ref and pronunciation + 'handle_names' + } + + Handlers.run(handlers,way,result,data,profile) end function turn_function (turn) turn.duration = 0. if turn.direction_modifier == direction_modifier.u_turn then - turn.duration = turn.duration + u_turn_penalty + turn.duration = turn.duration + profile.u_turn_penalty end if turn.has_traffic_light then - turn.duration = traffic_light_penalty + turn.duration = profile.traffic_light_penalty end end diff --git a/profiles/lib/guidance.lua b/profiles/lib/guidance.lua index c1bc5b808..6c25ed69b 100644 --- a/profiles/lib/guidance.lua +++ b/profiles/lib/guidance.lua @@ -1,4 +1,4 @@ -local Directional = require('lib/directional') +local Tags = require('lib/tags') local Set = require('lib/set') local Guidance = {} @@ -108,7 +108,7 @@ end -- returns forward,backward psv lane count local function get_psv_counts(way,data) - local psv_forward, psv_backward = Directional.get_values_by_key(way,data,'lanes:psv') + local psv_forward, psv_backward = Tags.get_forward_backward_by_key(way,data,'lanes:psv') if psv_forward then psv_forward = tonumber(psv_forward) end @@ -135,8 +135,8 @@ end -- this is broken for left-sided driving. It needs to switch left and right in case of left-sided driving function Guidance.get_turn_lanes(way,data) local psv_fw, psv_bw = get_psv_counts(way,data) - local turn_lanes_fw, turn_lanes_bw = Directional.get_values_by_key(way,data,'turn:lanes') - local vehicle_lanes_fw, vehicle_lanes_bw = Directional.get_values_by_key(way,data,'vehicle:lanes') + local turn_lanes_fw, turn_lanes_bw = Tags.get_forward_backward_by_key(way,data,'turn:lanes') + local vehicle_lanes_fw, vehicle_lanes_bw = Tags.get_forward_backward_by_key(way,data,'vehicle:lanes') --note: backward lanes swap psv_bw and psv_fw return process_lanes(turn_lanes_fw,vehicle_lanes_fw,psv_bw,psv_fw) or turn_lanes, diff --git a/profiles/lib/handlers.lua b/profiles/lib/handlers.lua new file mode 100644 index 000000000..d8f3166e1 --- /dev/null +++ b/profiles/lib/handlers.lua @@ -0,0 +1,461 @@ +-- 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') + +Handlers = {} + +-- 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 Handlers.handle_tag_prefetch(way,result,data,profile) + 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 Handlers.handle_default_mode(way,result,data,profile) + result.forward_mode = profile.default_mode + result.backward_mode = profile.default_mode +end + +-- handles name, including ref and pronunciation +function Handlers.handle_names(way,result,data,profile) + -- 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") + + -- 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 +end + +-- junctions +function Handlers.handle_roundabouts(way,result,data,profile) + 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 Handlers.handle_startpoint(way,result,data,profile) + -- only allow this road as start point if it not a ferry + result.is_startpoint = result.forward_mode == profile.default_mode or + result.backward_mode == profile.default_mode +end + +-- handle turn lanes +function Handlers.handle_turn_lanes(way,result,data,profile) + 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 Handlers.handle_classification(way,result,data,profile) + set_classification(data.highway,result,way) +end + +-- handle destination tags +function Handlers.handle_destinations(way,result,data,profile) + 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 Handlers.handle_ferries(way,result,data,profile) + 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 Handlers.handle_movables(way,result,data,profile) + 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 + local duration = way:get_value_by_key("duration") + if duration and durationIsValid(duration) then + result.duration = math.max( parseDuration(duration), 1 ) + end + result.forward_speed = bridge_speed + result.backward_speed = bridge_speed + end + end + end +end + +-- service roads +function Handlers.handle_service(way,result,data,profile) + 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 Handlers.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 Handlers.handle_hov(way,result,data,profile) + -- respect user-preference for HOV + if not profile.avoid.hov_lanes then + return + end + + -- check if way is hov only + local hov = way:get_value_by_key("hov") + if "designated" == hov then + return false + end + + -- check if all lanes are hov only + local hov_lanes_forward, hov_lanes_backward = Tags.get_forward_backward_by_key(way,data,'hov:lanes') + local inaccessible_forward = Handlers.has_all_designated_hov_lanes(hov_lanes_forward) + local inaccessible_backward = Handlers.has_all_designated_hov_lanes(hov_lanes_backward) + + if inaccessible_forward then + result.forward_mode = mode.inaccessible + end + if inaccessible_backward then + result.backward_mode = mode.inaccessible + end +end + +-- check accessibility by traversing our acces tag hierarchy +function Handlers.handle_access(way,result,data,profile) + data.forward_access, data.backward_access = + Tags.get_forward_backward_by_set(way,data,profile.access_tags_hierarchy) + + if profile.access_tag_blacklist[data.forward_access] then + result.forward_mode = mode.inaccessible + end + + if profile.access_tag_blacklist[data.backward_access] 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 Handlers.handle_speed(way,result,data,profile) + 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 = highway_speed + result.backward_speed = highway_speed + 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 + end + + if profile.access_tag_whitelist[data.backward_access] then + result.backward_speed = profile.default_speed + end + end + + if result.forward_speed == -1 and result.backward_speed == -1 then + return false + end +end + +-- reduce speed on special side roads +function Handlers.handle_side_roads(way,result,data,profile) + local sideway = way:get_value_by_key("side_road") + if "yes" == sideway or + "rotary" == sideway then + result.forward_speed = result.forward_speed * side_road_speed_multiplier + result.backward_speed = result.backward_speed * side_road_speed_multiplier + end +end + +-- reduce speed on bad surfaces +function Handlers.handle_surface(way,result,data,profile) + 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 Handlers.handle_speed_scaling(way,result,data,profile) + local width = math.huge + local lanes = math.huge + if result.forward_speed > 0 or result.backward_speed > 0 then + 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 + end + + local is_bidirectional = result.forward_mode ~= mode.inaccessible and + result.backward_mode ~= mode.inaccessible + + local service = way:get_value_by_key("service") + if result.forward_speed > 0 then + local scaled_speed = result.forward_speed * profile.speed_reduction + local penalized_speed = math.huge + if service and profile.service_speeds[service] then + penalized_speed = profile.service_speeds[service] + elseif width <= 3 or (lanes <= 1 and is_bidirectional) then + penalized_speed = result.forward_speed / 2 + end + result.forward_speed = math.min(penalized_speed, scaled_speed) + end + + if result.backward_speed > 0 then + local scaled_speed = result.backward_speed * profile.speed_reduction + local penalized_speed = math.huge + if service and profile.service_speeds[service]then + penalized_speed = profile.service_speeds[service] + elseif width <= 3 or (lanes <= 1 and is_bidirectional) then + penalized_speed = result.backward_speed / 2 + end + result.backward_speed = math.min(penalized_speed, scaled_speed) + end +end + +-- maxspeed and advisory maxspeed +function Handlers.handle_maxspeed(way,result,data,profile) + local keys = Sequence { 'maxspeed:advisory', 'maxspeed' } + local forward, backward = Tags.get_forward_backward_by_set(way,data,keys) + forward = Handlers.parse_maxspeed(forward,profile) + backward = Handlers.parse_maxspeed(backward,profile) + + if forward and forward > 0 then + result.forward_speed = forward + end + + if backward and backward > 0 then + result.backward_speed = backward + end +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. +function Handlers.handle_alternating_speed(way,result,data,profile) + if "alternating" == data.oneway then + local scaling_factor = 0.4 + if result.forward_speed ~= math.huge then + result.forward_speed = result.forward_speed * scaling_factor + end + if result.backward_speed ~= math.huge then + result.backward_speed = result.backward_speed * scaling_factor + end + end +end + +function Handlers.parse_maxspeed(source,profile) + if not source then + return 0 + end + local n = tonumber(source:match("%d*")) + if n then + if string.match(source, "mph") or string.match(source, "mp/h") then + n = (n*1609)/1000 + end + else + -- 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 oneways tags +function Handlers.handle_oneway(way,result,data,profile) + 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') + 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 + + +-- handle various that can block access +function Handlers.handle_blocked_ways(way,result,data,profile) + + -- 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 + + -- 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 + +-- Call a sequence of handlers, aborting in case a handler returns false. Example: +-- +-- handlers = Sequence { +-- 'handle_tag_prefetch', +-- 'handle_default_mode', +-- 'handle_blocked_ways', +-- 'handle_access', +-- 'handle_speed', +-- 'handle_names' +-- } +-- +-- Handlers.run(handlers,way,result,data,profile) +-- +-- Each method in the list will be called on the Handlers object. +-- All handlers must accept the parameteres (way,result,data,profile) 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 Handlers.run(handlers,way,result,data,profile) + for i,handler in ipairs(handlers) do + if Handlers[handler](way,result,data,profile) == false then + return false + end + end +end + +return Handlers \ No newline at end of file diff --git a/profiles/lib/directional.lua b/profiles/lib/tags.lua similarity index 65% rename from profiles/lib/directional.lua rename to profiles/lib/tags.lua index 4ffac0f2a..8ea55eac2 100644 --- a/profiles/lib/directional.lua +++ b/profiles/lib/tags.lua @@ -1,4 +1,6 @@ -local Directional = {} +-- Helpers for searching and parsing tags + +local Tags = {} -- return [forward,backward] values for a specific tag. -- e.g. for maxspeed search forward: @@ -8,7 +10,7 @@ local Directional = {} -- maxspeed:backward -- maxspeed -function Directional.get_values_by_key(way,data,key) +function Tags.get_forward_backward_by_key(way,data,key) local forward = way:get_value_by_key(key .. ':forward') local backward = way:get_value_by_key(key .. ':backward') @@ -34,7 +36,7 @@ end -- advisory:backward -- advisory -function Directional.get_values_by_set(way,data,keys) +function Tags.get_forward_backward_by_set(way,data,keys) local forward, backward for i,key in ipairs(keys) do if not forward then @@ -62,7 +64,7 @@ end -- oneway:motor_vehicle -- oneway:vehicle -function Directional.get_value_by_prefixed_sequence(way,seq,prefix) +function Tags.get_value_by_prefixed_sequence(way,seq,prefix) local v for i,key in ipairs(seq) do v = way:get_value_by_key(prefix .. ':' .. key) @@ -78,7 +80,7 @@ end -- motor_vehicle:oneway -- vehicle:oneway -function Directional.get_value_by_postfixed_sequence(way,seq,postfix) +function Tags.get_value_by_postfixed_sequence(way,seq,postfix) local v for i,key in ipairs(seq) do v = way:get_value_by_key(key .. ':' .. postfix) @@ -88,5 +90,35 @@ function Directional.get_value_by_postfixed_sequence(way,seq,postfix) end end +-- check if key-value pairs are set in a way and return a +-- corresponding constant if it is. e.g. for this input: +-- +-- local speeds = { +-- highway = { +-- residential = 20, +-- primary = 40 +-- }, +-- amenity = { +-- parking = 10 +-- } +-- } +-- +-- we would check whether the following key-value combinations +-- are set, and return the corresponding constant: +-- +-- highway = residential => 20 +-- highway = primary => 40 +-- amenity = parking => 10 -return Directional \ No newline at end of file +function Tags.get_constant_by_key_value(way,lookup) + for key,set in pairs(lookup) do + local way_value = way:get_value_by_key(key) + for value,t in pairs(set) do + if way_value == value then + return key,value,t + end + end + end +end + +return Tags \ No newline at end of file