598 lines
20 KiB
Lua
598 lines
20 KiB
Lua
-- 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')
|
|
|
|
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
|
|
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
|
|
|
|
-- 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 that are marked 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
|
|
|
|
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)
|
|
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")
|
|
|
|
if forward_toll == "yes" then
|
|
result.forward_classes["toll"] = true
|
|
end
|
|
if backward_toll == "yes" then
|
|
result.backward_classes["toll"] = true
|
|
end
|
|
|
|
if forward_route == "ferry" then
|
|
result.forward_classes["ferry"] = true
|
|
end
|
|
if backward_route == "ferry" then
|
|
result.backward_classes["ferry"] = true
|
|
end
|
|
|
|
if result.forward_restricted then
|
|
result.forward_classes["restricted"] = true
|
|
end
|
|
if result.backward_restricted then
|
|
result.backward_classes["restricted"] = true
|
|
end
|
|
|
|
if 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 service_penalty = 1.0
|
|
local service = way:get_value_by_key("service")
|
|
if service and profile.service_penalties[service] then
|
|
service_penalty = profile.service_penalties[service]
|
|
end
|
|
|
|
local width_penalty = 1.0
|
|
local width = math.huge
|
|
local lanes = math.huge
|
|
local width_string = way:get_value_by_key("width")
|
|
if width_string and tonumber(width_string:match("%d*")) then
|
|
width = tonumber(width_string:match("%d*"))
|
|
end
|
|
|
|
local lanes_string = way:get_value_by_key("lanes")
|
|
if lanes_string and tonumber(lanes_string:match("%d*")) then
|
|
lanes = tonumber(lanes_string:match("%d*"))
|
|
end
|
|
|
|
local is_bidirectional = result.forward_mode ~= mode.inaccessible and
|
|
result.backward_mode ~= mode.inaccessible
|
|
|
|
if width <= 3 or (lanes <= 1 and is_bidirectional) then
|
|
width_penalty = 0.5
|
|
end
|
|
|
|
-- Handle high frequency reversible oneways (think traffic signal controlled, changing direction every 15 minutes).
|
|
-- Scaling speed to take average waiting time into account plus some more for start / stop.
|
|
local alternating_penalty = 1.0
|
|
if data.oneway == "alternating" then
|
|
alternating_penalty = 0.4
|
|
end
|
|
|
|
local sideroad_penalty = 1.0
|
|
data.sideroad = way:get_value_by_key("side_road")
|
|
if "yes" == data.sideroad or "rotary" == data.sideroad then
|
|
sideroad_penalty = profile.side_road_multiplier
|
|
end
|
|
|
|
local forward_penalty = math.min(service_penalty, width_penalty, alternating_penalty, sideroad_penalty)
|
|
local backward_penalty = math.min(service_penalty, width_penalty, alternating_penalty, sideroad_penalty)
|
|
|
|
if profile.properties.weight_name == 'routability' then
|
|
if result.forward_speed > 0 then
|
|
result.forward_rate = (result.forward_speed * forward_penalty) / 3.6
|
|
end
|
|
if result.backward_speed > 0 then
|
|
result.backward_rate = (result.backward_speed * backward_penalty) / 3.6
|
|
end
|
|
if result.duration > 0 then
|
|
result.weight = result.duration / forward_penalty
|
|
end
|
|
end
|
|
end
|
|
|
|
-- maxspeed and advisory maxspeed
|
|
function WayHandlers.maxspeed(profile,way,result,data)
|
|
local keys = Sequence { 'maxspeed:advisory', 'maxspeed' }
|
|
local forward, backward = Tags.get_forward_backward_by_set(way,data,keys)
|
|
forward = WayHandlers.parse_maxspeed(forward,profile)
|
|
backward = WayHandlers.parse_maxspeed(backward,profile)
|
|
|
|
if forward and forward > 0 then
|
|
result.forward_speed = forward * profile.speed_reduction
|
|
end
|
|
|
|
if backward and backward > 0 then
|
|
result.backward_speed = backward * profile.speed_reduction
|
|
end
|
|
end
|
|
|
|
function WayHandlers.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 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 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.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
|