731 lines
No EOL
20 KiB
Lua
731 lines
No EOL
20 KiB
Lua
-- Itinero 1.0-profile, generated by AspectedRouting. Last source file change is 2021-01-27T15:51:22
|
|
name = "pedestrian"
|
|
normalize = false
|
|
vehicle_type = {"vehicle", "foot"}
|
|
meta_whitelist = {
|
|
"name"
|
|
, "bridge"
|
|
, "tunnel"
|
|
, "colour"
|
|
, "ref"
|
|
, "status"
|
|
, "network" }
|
|
profile_whitelist = {
|
|
"access"
|
|
, "highway"
|
|
, "service"
|
|
, "foot"
|
|
, "anyways:foot"
|
|
, "anyways:access"
|
|
, "anyways:construction"
|
|
}
|
|
|
|
|
|
|
|
profiles = {
|
|
{
|
|
name = "shortest",
|
|
function_name = "behaviour_pedestrian_shortest",
|
|
metric = "custom"
|
|
}
|
|
}
|
|
|
|
function default_parameters()
|
|
local parameters = {}
|
|
parameters.defaultSpeed = 4
|
|
parameters.maxspeed = 6
|
|
parameters.timeNeeded = 0
|
|
parameters.distance = 0
|
|
parameters.slow_road_preference = 0
|
|
parameters.trespassingPenalty = 1
|
|
|
|
return parameters
|
|
end
|
|
|
|
|
|
|
|
--[[
|
|
"The shortest route, independent of of speed"
|
|
]]
|
|
function behaviour_pedestrian_shortest(tags, result)
|
|
tags = remove_relation_prefix(tags, "shortest")
|
|
local parameters = default_parameters()
|
|
parameters.name = "pedestrian_shortest"
|
|
parameters.distance = 1
|
|
parameters.leastSafetyPenalty = 2
|
|
pedestrian(parameters, tags, result)
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--[[
|
|
pedestrian
|
|
This is the main function called to calculate the access, oneway and speed.
|
|
Comfort is calculated as well, based on the parameters which are padded in
|
|
|
|
Created by
|
|
Originally defined in /home/pietervdvn/git/AspectedRouting/Examples/pedestrian
|
|
]]
|
|
function pedestrian(parameters, tags, result)
|
|
|
|
-- initialize the result table on the default values
|
|
result.access = 0
|
|
result.speed = 0
|
|
result.factor = 1
|
|
result.direction = 0
|
|
result.canstop = true
|
|
result.attributes_to_keep = {}
|
|
|
|
local access = pedestrian_legal_access(parameters, tags, result)
|
|
if (access == nil or access == "no") then
|
|
return
|
|
end
|
|
tags.access = access
|
|
local oneway = firstArg("both")
|
|
tags.oneway = oneway
|
|
local speed = firstArg(parameters["defaultSpeed"])
|
|
tags.speed = speed
|
|
local distance = 1 -- the weight per meter for distance travelled is, well, 1m/m
|
|
|
|
local priority = 0
|
|
|
|
if(parameters["timeNeeded"] ~= 0) then
|
|
priority = priority + parameters["timeNeeded"] * speed
|
|
end
|
|
if(parameters["distance"] ~= 0) then
|
|
priority = priority + parameters["distance"] * distance
|
|
end
|
|
if(parameters["trespassingPenalty"] ~= 0) then
|
|
priority = priority + parameters["trespassingPenalty"] * clean_permission_score(parameters, tags, result)
|
|
end
|
|
|
|
if (priority <= 0) then
|
|
result.access = 0
|
|
return
|
|
end
|
|
|
|
result.access = 1
|
|
result.speed = speed
|
|
result.factor = 1 / priority
|
|
|
|
result.direction = 0
|
|
if (oneway == "with" or oneway == "yes") then
|
|
result.direction = 1
|
|
elseif (oneway == "against" or oneway == "-1") then
|
|
result.direction = 2
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Processes the relation. All tags which are added to result.attributes_to_keep will be copied to 'attributes' of each individual way
|
|
function relation_tag_processor(relation_tags, result)
|
|
local parameters = {}
|
|
local subresult = {}
|
|
local matched = false
|
|
result.attributes_to_keep = {}
|
|
|
|
-- Legacy to add colours to the bike networks
|
|
legacy_relation_preprocessor(relation_tags, result)
|
|
end
|
|
|
|
|
|
---------------------- ASPECTS ----------------------
|
|
|
|
|
|
--[[
|
|
Gives, for each type of highway, whether or not someone can enter legally.
|
|
Note that legal access is a bit 'grey' in the case of roads marked private and permissive, in which case these values are returned
|
|
|
|
Unit: 'designated': Access is allowed and even specifically for pedestrian
|
|
'yes': pedestrians are allowed here
|
|
'permissive': pedestrians are allowed here, but this might be a private road or service where usage is allowed, but could be retracted one day by the owner
|
|
'destination': walking is allowed here, but only if truly necessary to reach the destination (e.g. a service road)
|
|
'private': this is a private road, only go here if the destination is here
|
|
'no': do not walk here
|
|
Created by
|
|
Originally defined in pedestrian.legal_access.json
|
|
Uses tags: access, highway, service, foot, anyways:foot, anyways:access, anyways:construction
|
|
Used parameters:
|
|
Number of combintations: 53
|
|
Returns values:
|
|
]]
|
|
function pedestrian_legal_access(parameters, tags, result)
|
|
local result
|
|
if (tags["highway"] ~= nil) then
|
|
local v
|
|
v = tags["highway"]
|
|
if (v == "pedestrian") then
|
|
result = "designated"
|
|
elseif (v == "footway") then
|
|
result = "designated"
|
|
elseif (v == "living_street") then
|
|
result = "designated"
|
|
elseif (v == "steps") then
|
|
result = "yes"
|
|
elseif (v == "corridor") then
|
|
result = "designated"
|
|
elseif (v == "residential") then
|
|
result = "yes"
|
|
elseif (v == "service") then
|
|
result = "yes"
|
|
elseif (v == "services") then
|
|
result = "yes"
|
|
elseif (v == "track") then
|
|
result = "yes"
|
|
elseif (v == "crossing") then
|
|
result = "yes"
|
|
elseif (v == "construction") then
|
|
result = "permissive"
|
|
elseif (v == "path") then
|
|
result = "yes"
|
|
elseif (v == "primary") then
|
|
result = "yes"
|
|
elseif (v == "primary_link") then
|
|
result = "yes"
|
|
elseif (v == "secondary") then
|
|
result = "yes"
|
|
elseif (v == "secondary_link") then
|
|
result = "yes"
|
|
elseif (v == "tertiary") then
|
|
result = "yes"
|
|
elseif (v == "tertiary_link") then
|
|
result = "yes"
|
|
elseif (v == "unclassified") then
|
|
result = "yes"
|
|
elseif (v == "road") then
|
|
result = "yes"
|
|
end
|
|
end
|
|
if (tags["service"] ~= nil) then
|
|
local v0
|
|
v0 = tags["service"]
|
|
if (v0 == "parking_aisle") then
|
|
result = "permissive"
|
|
elseif (v0 == "driveway") then
|
|
result = "private"
|
|
elseif (v0 == "alley") then
|
|
result = "yes"
|
|
elseif (v0 == "bus") then
|
|
result = "no"
|
|
end
|
|
end
|
|
if (tags["access"] ~= nil) then
|
|
local v1
|
|
v1 = tags["access"]
|
|
if (v1 == "no") then
|
|
result = "no"
|
|
elseif (v1 == "customers") then
|
|
result = "private"
|
|
elseif (v1 == "private") then
|
|
result = "private"
|
|
elseif (v1 == "permissive") then
|
|
result = "permissive"
|
|
elseif (v1 == "destination") then
|
|
result = "destination"
|
|
elseif (v1 == "delivery") then
|
|
result = "destination"
|
|
elseif (v1 == "service") then
|
|
result = "destination"
|
|
elseif (v1 == "permit") then
|
|
result = "destination"
|
|
end
|
|
end
|
|
if (tags["foot"] ~= nil) then
|
|
local v2
|
|
v2 = tags["foot"]
|
|
if (v2 == "yes") then
|
|
result = "yes"
|
|
elseif (v2 == "no") then
|
|
result = "no"
|
|
elseif (v2 == "use_sidepath") then
|
|
result = "no"
|
|
elseif (v2 == "designated") then
|
|
result = "designated"
|
|
elseif (v2 == "permissive") then
|
|
result = "permissive"
|
|
elseif (v2 == "private") then
|
|
result = "private"
|
|
elseif (v2 == "official") then
|
|
result = "designated"
|
|
elseif (v2 == "dismount") then
|
|
result = "dismount"
|
|
elseif (v2 == "permit") then
|
|
result = "destination"
|
|
end
|
|
end
|
|
if (tags["anyways:construction"] ~= nil) then
|
|
local v3
|
|
v3 = tags["anyways:construction"]
|
|
if (v3 == "yes") then
|
|
result = "no"
|
|
end
|
|
end
|
|
if (tags["anyways:access"] ~= nil) then
|
|
local v4
|
|
v4 = tags["anyways:access"]
|
|
if (v4 == "no") then
|
|
result = "no"
|
|
elseif (v4 == "destination") then
|
|
result = "destination"
|
|
elseif (v4 == "yes") then
|
|
result = "yes"
|
|
end
|
|
end
|
|
if (tags["anyways:foot"] ~= nil) then
|
|
result = tags["anyways:foot"]
|
|
end
|
|
|
|
if (result == nil) then
|
|
result = "no"
|
|
end
|
|
return result
|
|
end
|
|
--[[
|
|
Gives 0 on private roads, 0.1 on destination-only roads, and 0.9 on permissive roads; gives 1 by default. This helps to select roads with no access retrictions on them
|
|
|
|
Unit:
|
|
Created by
|
|
Originally defined in clean_permission_score.json
|
|
Uses tags: access
|
|
Used parameters:
|
|
Number of combintations: 5
|
|
Returns values:
|
|
]]
|
|
function clean_permission_score(parameters, tags, result)
|
|
local result
|
|
result = head(stringToTags({
|
|
access = {
|
|
private = -50,
|
|
destination = -3,
|
|
permissive = -1
|
|
}
|
|
}, tags))
|
|
if (result == nil) then
|
|
result = 0
|
|
end
|
|
return result
|
|
end
|
|
--[[
|
|
Actual speed of this function
|
|
|
|
Unit: NA
|
|
Created by NA
|
|
Originally defined in NA
|
|
Uses tags:
|
|
Used parameters:
|
|
Number of combintations: 1
|
|
Returns values:
|
|
]]
|
|
function speed(parameters, tags, result)
|
|
local result
|
|
result = 15
|
|
return result
|
|
end
|
|
--[[
|
|
The distance travelled of this profile
|
|
|
|
Unit:
|
|
Created by
|
|
Originally defined in bicycle.json
|
|
Uses tags:
|
|
Used parameters:
|
|
Number of combintations: 1
|
|
Returns values:
|
|
]]
|
|
function distance(parameters, tags, result)
|
|
local result
|
|
result = 1
|
|
return result
|
|
end
|
|
|
|
|
|
---------------------- UTILS ------------------------
|
|
|
|
|
|
-- instruction generators
|
|
instruction_generators = {
|
|
{
|
|
applies_to = "", -- applies to all profiles when empty
|
|
generators = {
|
|
{
|
|
name = "start",
|
|
function_name = "get_start"
|
|
},
|
|
{
|
|
name = "stop",
|
|
function_name = "get_stop"
|
|
},
|
|
{
|
|
name = "roundabout",
|
|
function_name = "get_roundabout"
|
|
},
|
|
{
|
|
name = "turn",
|
|
function_name = "get_turn"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
-- gets the first instruction
|
|
function get_start(route_position, language_reference, instruction)
|
|
if route_position.is_first() then
|
|
local direction = route_position.direction()
|
|
instruction.text = itinero.format(language_reference.get("Start {0}."), language_reference.get(direction));
|
|
instruction.shape = route_position.shape
|
|
return 1
|
|
end
|
|
return 0
|
|
end
|
|
|
|
-- gets the last instruction
|
|
function get_stop(route_position, language_reference, instruction)
|
|
if route_position.is_last() then
|
|
instruction.text = language_reference.get("Arrived at destination.");
|
|
instruction.shape = route_position.shape
|
|
return 1
|
|
end
|
|
return 0
|
|
end
|
|
|
|
|
|
-- gets a roundabout instruction
|
|
function get_roundabout(route_position, language_reference, instruction)
|
|
if route_position.attributes.junction == "roundabout" and
|
|
(not route_position.is_last()) then
|
|
local attributes = route_position.next().attributes
|
|
if attributes.junction then
|
|
else
|
|
local exit = 1
|
|
local count = 1
|
|
local previous = route_position.previous()
|
|
while previous and previous.attributes.junction == "roundabout" do
|
|
local branches = previous.branches
|
|
if branches then
|
|
branches = branches.get_traversable()
|
|
if branches.count > 0 then
|
|
exit = exit + 1
|
|
end
|
|
end
|
|
count = count + 1
|
|
previous = previous.previous()
|
|
end
|
|
|
|
instruction.text = itinero.format(language_reference.get("Take the {0}th exit at the next roundabout."), "" .. exit)
|
|
if exit == 1 then
|
|
instruction.text = itinero.format(language_reference.get("Take the first exit at the next roundabout."))
|
|
elseif exit == 2 then
|
|
instruction.text = itinero.format(language_reference.get("Take the second exit at the next roundabout."))
|
|
elseif exit == 3 then
|
|
instruction.text = itinero.format(language_reference.get("Take the third exit at the next roundabout."))
|
|
end
|
|
instruction.type = "roundabout"
|
|
instruction.shape = route_position.shape
|
|
return count
|
|
end
|
|
end
|
|
return 0
|
|
end
|
|
|
|
-- gets a turn
|
|
function get_turn(route_position, language_reference, instruction)
|
|
local relative_direction = route_position.relative_direction().direction
|
|
|
|
local turn_relevant = false
|
|
local branches = route_position.branches
|
|
if branches then
|
|
branches = branches.get_traversable()
|
|
if relative_direction == "straighton" and
|
|
branches.count >= 2 then
|
|
turn_relevant = true -- straight on at cross road
|
|
end
|
|
if relative_direction ~= "straighton" and
|
|
branches.count > 0 then
|
|
turn_relevant = true -- an actual normal turn
|
|
end
|
|
end
|
|
|
|
if relative_direction == "unknown" then
|
|
turn_relevant = false -- turn could not be calculated.
|
|
end
|
|
|
|
if turn_relevant then
|
|
local next = route_position.next()
|
|
local name = nil
|
|
if next then
|
|
name = next.attributes.name
|
|
end
|
|
if name then
|
|
instruction.text = itinero.format(language_reference.get("Go {0} on {1}."),
|
|
language_reference.get(relative_direction), name)
|
|
instruction.shape = route_position.shape
|
|
else
|
|
instruction.text = itinero.format(language_reference.get("Go {0}."),
|
|
language_reference.get(relative_direction))
|
|
instruction.shape = route_position.shape
|
|
end
|
|
|
|
return 1
|
|
end
|
|
return 0
|
|
end
|
|
|
|
--[[
|
|
Legacy function to add cycle_colour
|
|
]]
|
|
|
|
function legacy_relation_preprocessor(attributes, result)
|
|
if (attributes.route == "bicycle") then
|
|
-- This is a cycling network, the colour is copied
|
|
if (attributes.colour ~= nil) then
|
|
result.attributes_to_keep.cycle_network_colour = attributes.colour
|
|
end
|
|
|
|
if (attributes.color ~= nil) then
|
|
-- for the americans!
|
|
result.attributes_to_keep.cycle_network_colour = attributes.color
|
|
end
|
|
|
|
if (attributes.ref ~= nil and attributes.operator == "Stad Genk") then
|
|
-- This is pure legacy: we need the ref number only of stad Genk
|
|
result.attributes_to_keep.cycle_network_ref = attributes.ref
|
|
end
|
|
end
|
|
end
|
|
|
|
function string_start(strt, s)
|
|
return string.sub(s, 1, string.len(strt)) == strt
|
|
end
|
|
|
|
|
|
-- every key starting with "_relation:<name>:XXX" is rewritten to "_relation:XXX"
|
|
function remove_relation_prefix(tags, name)
|
|
|
|
local new_tags = {}
|
|
for k, v in pairs(tags) do
|
|
local prefix = "_relation:" .. name .. ":";
|
|
if (string_start(prefix, k)) then
|
|
local new_key = "_relation:" .. string.sub(k, string.len(prefix) + 1) -- plus 1: sub uses one-based indexing to select the start
|
|
new_tags[new_key] = v
|
|
else
|
|
new_tags[k] = v
|
|
end
|
|
end
|
|
return new_tags
|
|
end
|
|
function firstArg(a, b)
|
|
-- it turns out that 'const' is a reserved token in some lua implementations
|
|
return a
|
|
end
|
|
function head(ls)
|
|
if(ls == nil) then
|
|
return nil
|
|
end
|
|
for _, v in pairs(ls) do
|
|
if(v ~= nil) then
|
|
return v
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
print("ERROR: stringToTag is needed. This should not happen")
|
|
failed_tests = false
|
|
function unit_test(f, fname, index, expected, parameters, tags)
|
|
if (f == nil) then
|
|
print("Trying to unit test " .. fname .. " but this function is not defined")
|
|
failed_tests = true
|
|
return
|
|
end
|
|
local result = {attributes_to_keep = {}}
|
|
local actual = f(parameters, tags, result)
|
|
if(expected == "null" and actual == nil) then
|
|
-- OK!
|
|
elseif(tonumber(actual) and tonumber(expected) and math.abs(tonumber(actual) - tonumber(expected)) < 0.1) then
|
|
-- OK!
|
|
elseif (tostring(actual) ~= expected) then
|
|
print("[" .. fname .. "] " .. index .. " failed: expected " .. expected .. " but got " .. tostring(actual))
|
|
failed_tests = true
|
|
end
|
|
end
|
|
failed_profile_tests = false
|
|
--[[
|
|
expected should be a table containing 'access', 'speed' and 'priority'
|
|
]]
|
|
function unit_test_profile(profile_function, profile_name, index, expected, tags)
|
|
local result = { attributes_to_keep = {} }
|
|
local profile_failed = false
|
|
profile_function(tags, result)
|
|
|
|
local accessCorrect = (result.access == 0 and (expected.access == "no" or expected.priority <= 0)) or result.access == 1
|
|
if (not accessCorrect) then
|
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".access: expected " .. expected.access .. " but got " .. result.access)
|
|
profile_failed = true
|
|
failed_profile_tests = true
|
|
end
|
|
|
|
if (expected.access == "no" or expected.priority <= 0) then
|
|
-- we cannot access this road, the other results are irrelevant
|
|
if (profile_failed) then
|
|
print("The used tags for test " .. tostring(index) .. " are:")
|
|
debug_table(tags)
|
|
end
|
|
return
|
|
end
|
|
|
|
if (not double_compare(result.speed, expected.speed)) then
|
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.speed)
|
|
failed_profile_tests = true
|
|
profile_failed = true
|
|
end
|
|
|
|
|
|
local actualOneway = result.direction
|
|
|
|
if(actualOneway == nil) then
|
|
print("Fail: result.direction is nil")
|
|
profile_failed = true;
|
|
end
|
|
|
|
if (result.direction == 0) then
|
|
actualOneway = "both"
|
|
elseif (result.direction == 1) then
|
|
actualOneway = "with"
|
|
elseif (result.direction == 2) then
|
|
actualOneway = "against"
|
|
end
|
|
|
|
if (expected.oneway ~= actualOneway) then
|
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. actualOneway)
|
|
failed_profile_tests = true
|
|
profile_failed = true
|
|
end
|
|
|
|
|
|
if (not double_compare(result.factor, 1/expected.priority)) then
|
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".factor: expected " .. expected.priority .. " but got " .. 1/result.factor)
|
|
failed_profile_tests = true
|
|
profile_failed = true
|
|
end
|
|
|
|
if (profile_failed == true) then
|
|
print("The used tags for test " .. tostring(index) .. " are:")
|
|
debug_table(tags)
|
|
end
|
|
end
|
|
function inv(n)
|
|
return 1/n
|
|
end
|
|
function double_compare(a, b)
|
|
if (b == nil) then
|
|
return false
|
|
end
|
|
|
|
if (type(a) ~= "number") then
|
|
a = parse(a)
|
|
end
|
|
|
|
if(type(b) ~= "number") then
|
|
b = parse(b)
|
|
end
|
|
if (a == b) then
|
|
return true
|
|
end
|
|
|
|
return math.abs(a - b) < 0.0001
|
|
end
|
|
function debug_table(table, prefix)
|
|
if (prefix == nil) then
|
|
prefix = ""
|
|
end
|
|
for k, v in pairs(table) do
|
|
|
|
if (type(v) == "table") then
|
|
debug_table(v, " ")
|
|
else
|
|
print(prefix .. tostring(k) .. " = " .. tostring(v))
|
|
end
|
|
end
|
|
print("")
|
|
end
|
|
|
|
function debug_table_str(table, prefix)
|
|
if (prefix == nil) then
|
|
prefix = ""
|
|
end
|
|
local str = "";
|
|
for k, v in pairs(table) do
|
|
|
|
if (type(v) == "table") then
|
|
str = str .. "," .. debug_table_str(v, " ")
|
|
else
|
|
str = str .. "," .. (prefix .. tostring(k) .. " = " .. tostring(v))
|
|
end
|
|
end
|
|
return str
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
----------------------- TESTS ------------------------
|
|
|
|
|
|
function test_all()
|
|
|
|
|
|
|
|
|
|
unit_test(clean_permission_score, "clean_permission_score", 0, "0", {}, {access = "yes"})
|
|
unit_test(clean_permission_score, "clean_permission_score", 1, "-1", {}, {access = "permissive"})
|
|
unit_test(clean_permission_score, "clean_permission_score", 2, "-50", {}, {access = "private"})
|
|
unit_test(clean_permission_score, "clean_permission_score", 3, "0", {}, {access = "qmsldkfjqsmldkfj"})
|
|
|
|
|
|
|
|
unit_test(pedestrian_legal_access, "pedestrian.legal_access", 0, "no", {}, {})
|
|
unit_test(pedestrian_legal_access, "pedestrian.legal_access", 1, "yes", {}, {highway = "residential"})
|
|
unit_test(pedestrian_legal_access, "pedestrian.legal_access", 2, "designated", {}, {highway = "pedestrian"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Behaviour tests --
|
|
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
if (itinero == nil) then
|
|
itinero = {}
|
|
itinero.log = print
|
|
|
|
-- Itinero is not defined -> we are running from a lua interpreter -> the tests are intended
|
|
runTests = true
|
|
|
|
|
|
else
|
|
print = itinero.log
|
|
end
|
|
|
|
test_all()
|
|
if (not failed_tests and not failed_profile_tests) then
|
|
print("Tests OK")
|
|
end |