Regenerating the examples, add documentation

This commit is contained in:
Pieter Vander Vennet 2021-02-16 11:58:31 +01:00
parent ac948b4471
commit 2e1389ebd9
13 changed files with 86 additions and 5522 deletions

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Typ;
@ -7,41 +6,42 @@ namespace AspectedRouting.Language.Functions
{
public class HeadFunction : Function
{
public HeadFunction() : base("head", true,
new[] {
new Curry(new ListType(new Var("a")),
new Var("a")),
new Curry(
new Curry(new Var("b"), new ListType(new Var("a"))),
new Curry(new Var("b"), new Var("a")))
}
) { }
private HeadFunction(IEnumerable<Type> unified) : base("head", unified) { }
public override string Description { get; } =
"Select the first non-null value of a list; returns 'null' on empty list or on null";
public override List<string> ArgNames { get; } = new List<string> {"ls"};
public HeadFunction() : base("head", true,
new[]
{
new Curry(new ListType(new Var("a")),
new Var("a"))
}
)
{
}
private HeadFunction(IEnumerable<Type> unified) : base("head", unified)
{
}
public override object Evaluate(Context c, params IExpression[] arguments)
{
var o = arguments[0].Evaluate(c);
while (o is IExpression e)
{
object o = arguments[0];
if (o is Apply app) {
o = app.Evaluate(c, arguments.SubArray(1));
}
while (o is IExpression e) {
o = e.Evaluate(c);
}
if (!(o is IEnumerable<object> ls)) return null;
foreach (var a in ls)
{
if (a != null)
{
return a;
if (!(o is IEnumerable<object> ls)) {
return null;
}
foreach (var v in ls) {
if (v != null) {
return v;
}
}
@ -51,8 +51,7 @@ namespace AspectedRouting.Language.Functions
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
{
var unified = Types.SpecializeTo(allowedTypes);
if (unified == null)
{
if (unified == null) {
return null;
}

View file

@ -7,7 +7,8 @@ namespace AspectedRouting.Language.Functions
public class StringStringToTagsFunction : Function
{
public override string Description { get; } =
"stringToTags converts a function `string -> string -> a` into a function `tags -> [a]`";
"*stringToTags* converts a function `string -> string -> a` into a function `tags -> [a]`. " +
"It is used internally to convert a hash of functions. `stringToTags` shouldn't be needed when implementing profiles.";
public override List<string> ArgNames { get; } = new List<string> {"f", "tags"};

View file

@ -12,6 +12,18 @@ namespace AspectedRouting.Tests
public readonly AspectMetadata FunctionToApply;
public readonly IEnumerable<(string expected, Dictionary<string, string> tags)> Tests;
public AspectTestSuite(
AspectMetadata functionToApply,
IEnumerable<(string expected, Dictionary<string, string> tags)> tests)
{
if (functionToApply == null) {
throw new NullReferenceException("functionToApply is null");
}
FunctionToApply = functionToApply;
Tests = tests;
}
public static AspectTestSuite FromString(AspectMetadata function, string csvContents)
{
var all = csvContents.Split("\n").ToList();
@ -20,10 +32,8 @@ namespace AspectedRouting.Tests
var tests = new List<(string, Dictionary<string, string>)>();
foreach (var test in all.GetRange(1, all.Count - 1))
{
if (string.IsNullOrEmpty(test.Trim()))
{
foreach (var test in all.GetRange(1, all.Count - 1)) {
if (string.IsNullOrEmpty(test.Trim())) {
continue;
}
@ -31,10 +41,8 @@ namespace AspectedRouting.Tests
var expected = testData[0];
var vals = testData.GetRange(1, testData.Count - 1);
var tags = new Dictionary<string, string>();
for (int i = 0; i < keys.Count; i++)
{
if (i < vals.Count && !string.IsNullOrEmpty(vals[i]))
{
for (var i = 0; i < keys.Count; i++) {
if (i < vals.Count && !string.IsNullOrEmpty(vals[i])) {
tags[keys[i]] = vals[i];
}
}
@ -45,57 +53,54 @@ namespace AspectedRouting.Tests
return new AspectTestSuite(function, tests);
}
public AspectTestSuite(
AspectMetadata functionToApply,
IEnumerable<(string expected, Dictionary<string, string> tags)> tests)
{
if (functionToApply == null)
{
throw new NullReferenceException("functionToApply is null");
}
FunctionToApply = functionToApply;
Tests = tests;
}
public bool Run()
{
var failed = false;
var testCase = 0;
foreach (var test in Tests)
{
foreach (var test in Tests) {
testCase++;
var context = new Context();
foreach (var (key, value) in test.tags)
{
if (key.StartsWith("#"))
{
foreach (var (key, value) in test.tags) {
if (key.StartsWith("#")) {
context.AddParameter(key, value);
}
}
try
{
try {
var actual = FunctionToApply.Evaluate(context, new Constant(test.tags));
if (test.expected == "null" && actual == null)
{
// Test ok
if (string.IsNullOrWhiteSpace(test.expected)) {
failed = true;
Console.WriteLine(
$"[{FunctionToApply.Name}] Line {testCase + 1} failed:\n The expected value is not defined or only whitespace. Do you want null? Write null in your test as expected value\n");
continue;
}
else if (!actual.ToString().Equals(test.expected) &&
!(actual is double actualD && Math.Abs(double.Parse(test.expected) - actualD) < 0.0001)
)
{
if (test.expected == "null" && actual == null) {
// Test ok
continue;
}
if (actual == null) {
Console.WriteLine(
$"[{FunctionToApply.Name}] Line {testCase + 1} failed:\n Expected: {test.expected}\n actual value is not defined (null)\n tags: {test.tags.Pretty()}\n");
failed = true;
continue;
}
if (!actual.ToString().Equals(test.expected) &&
!(actual is double actualD && Math.Abs(double.Parse(test.expected) - actualD) < 0.0001)
) {
failed = true;
Console.WriteLine(
$"[{FunctionToApply.Name}] Line {testCase + 1} failed:\n Expected: {test.expected}\n actual: {actual}\n tags: {test.tags.Pretty()}\n");
}
}
catch (Exception e)
{
catch (Exception e) {
Console.WriteLine(
$"[{FunctionToApply.Name}] Line {testCase + 1} ERROR:\n Expected: {test.expected}\n error message: {e.Message}\n tags: {test.tags.Pretty()}\n");
Console.WriteLine(e);
failed = true;
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language;
@ -33,6 +34,18 @@ namespace AspectedRouting
return factor;
}
public static T[] SubArray<T>(this T[] data, int index, int length)
{
T[] result = new T[length];
Array.Copy(data, index, result, 0, length);
return result;
}
public static T[] SubArray<T>(this T[] data, int index)
{
return data.SubArray(index, data.Length - index);
}
/// <summary>
/// Generates a JSON file where all the profiles are listed, together with descriptions and other metadata.
/// Useful for other apps, e.g. the routing api to have

View file

@ -1,6 +0,0 @@
[{"name": "shortest","type": "pedestrian","author": "","description": "The shortest route, independent of of speed (Profile for someone who is walking)"}
,{"name": "fastest","type": "bicycle","author": "","description": "The fastest route to your destination (Profile for a normal bicycle)"}
,{"name": "shortest","type": "bicycle","author": "","description": "The shortest route, independent of of speed (Profile for a normal bicycle)"}
,{"name": "comfort","type": "bicycle","author": "","description": "A comfortable route preferring well-paved roads, smaller roads and a bit of scenery at the cost of speed (Profile for a normal bicycle)"}
,{"name": "electric","type": "bicycle","author": "","description": "An electrical bicycle (Profile for a normal bicycle)"}
]

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,604 +0,0 @@
name = "bicycle.comfort"
generationDate = "2021-01-27T15:51:22"
description = "A comfortable route preferring well-paved roads, smaller roads and a bit of scenery at the cost of speed (Profile for a normal bicycle)"
--[[
Calculate the actual factor.forward and factor.backward for a segment with the given properties
]]
function factor(tags, result)
-- initialize the result table on the default values
result.forward_speed = 0
result.backward_speed = 0
result.forward = 0
result.backward = 0
result.canstop = true
result.attributes_to_keep = {} -- not actually used anymore, but the code generation still uses this
local parameters = default_parameters()
parameters.comfort = 1
local oneway = bicycle_oneway(parameters, tags, result)
tags.oneway = oneway
-- An aspect describing oneway should give either 'both', 'against' or 'width'
-- forward calculation. We set the meta tag '_direction' to 'width' to indicate that we are going forward. The other functions will pick this up
tags["_direction"] = "with"
local access_forward = bicycle_legal_access(parameters, tags, result)
if(oneway == "against") then
-- no 'oneway=both' or 'oneway=with', so we can only go back over this segment
-- we overwrite the 'access_forward'-value with no; whatever it was...
access_forward = "no"
end
if(access_forward ~= nil and access_forward ~= "no") then
tags.access = access_forward -- might be relevant, e.g. for 'access=dismount' for bicycles
result.forward_speed =
min({
legal_maxspeed_be(parameters, tags, result),
parameters["defaultSpeed"]
})
tags.speed = result.forward_speed
local priority = calculate_priority(parameters, tags, result, access_forward, oneway, result.forward_speed)
if (priority <= 0) then
result.forward_speed = 0
else
result.forward = 1 / priority
end
end
-- backward calculation
tags["_direction"] = "against" -- indicate the backward direction to priority calculation
local access_backward = bicycle_legal_access(parameters, tags, result)
if(oneway == "with") then
-- no 'oneway=both' or 'oneway=against', so we can only go forward over this segment
-- we overwrite the 'access_forward'-value with no; whatever it was...
access_backward = "no"
end
if(access_backward ~= nil and access_backward ~= "no") then
tags.access = access_backward
result.backward_speed =
min({
legal_maxspeed_be(parameters, tags, result),
parameters["defaultSpeed"]
})
tags.speed = result.backward_speed
local priority = calculate_priority(parameters, tags, result, access_backward, oneway, result.backward_speed)
if (priority <= 0) then
result.backward_speed = 0
else
result.backward = 1 / priority
end
end
end
--[[
Generates the factor according to the priorities and the parameters for this behaviour
Note: 'result' is not actually used
]]
function calculate_priority(parameters, tags, result, access, oneway, speed)
local distance = 1
local priority =
1 * bicycle_comfort(parameters, tags, result)
return priority
end
function default_parameters()
local parameters = {}
parameters.defaultSpeed = 15
parameters.timeNeeded = 0
parameters.distance = 0
parameters.comfort = 0
return parameters
end
--[[
Gives, for each type of highway, whether or not a normal bicycle 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 bicycles
'yes': bicycles are allowed here
'permissive': bicycles 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
'dismount': cycling here is not allowed, but walking with the bicycle is
'destination': cycling is allowed here, but only if truly necessary to reach the destination
'private': this is a private road, only go here if the destination is here
'no': do not cycle here
Created by
Originally defined in bicycle.legal_access.json
Uses tags: access, highway, service, bicycle, anyways:bicycle, anyways:access, anyways:construction
Used parameters:
Number of combintations: 54
Returns values:
]]
function bicycle_legal_access(parameters, tags, result)
return default("no", first_match_of(tags, result,
{"anyways:bicycle", "anyways:access", "anyways:construction", "bicycle", "access", "service", "highway"},
{
access = {
no = "no",
customers = "private",
private = "private",
permissive = "permissive",
destination = "destination",
delivery = "destination",
service = "destination",
permit = "destination"
},
highway = {
cycleway = "designated",
residential = "yes",
living_street = "yes",
service = "yes",
services = "yes",
track = "yes",
crossing = "dismount",
footway = "dismount",
pedestrian = "dismount",
corridor = "dismount",
construction = "dismount",
steps = "dismount",
path = "yes",
primary = "yes",
primary_link = "yes",
secondary = "yes",
secondary_link = "yes",
tertiary = "yes",
tertiary_link = "yes",
unclassified = "yes",
road = "yes"
},
service = {
parking_aisle = "permissive",
driveway = "private",
alley = "yes",
bus = "no"
},
bicycle = {
yes = "yes",
no = "no",
use_sidepath = "no",
designated = "designated",
permissive = "permissive",
private = "private",
official = "designated",
dismount = "dismount",
permit = "destination"
},
["anyways:bicycle"] = tags["anyways:bicycle"],
["anyways:access"] = {
no = "no",
destination = "destination",
yes = "yes"
},
["anyways:construction"] = {
yes = "no"
}
}))
end
--[[
Determines wether or not a bicycle can go in both ways in this street, and if it is oneway, in what direction
Unit: both: direction is allowed in both direction
with: this is a oneway street with direction allowed with the grain of the way
against: oneway street with direction against the way
Created by
Originally defined in bicycle.oneway.json
Uses tags: oneway, oneway:bicycle, junction, cycleway, cycleway:left
Used parameters:
Number of combintations: 32
Returns values:
]]
function bicycle_oneway(parameters, tags, result)
return default("both", first_match_of(tags, result,
{"oneway:bicycle", "junction", "cycleway", "cycleway:left", "oneway"},
{
oneway = {
yes = "with",
no = "both",
["1"] = "with",
["-1"] = "against"
},
["oneway:bicycle"] = {
yes = "with",
no = "both",
["1"] = "with",
["-1"] = "against"
},
junction = {
roundabout = "with"
},
cycleway = {
right = "against",
opposite_lane = "both",
track = "both",
lane = "both",
opposite = "both",
opposite_share_busway = "both",
opposite_track = "both"
},
["cycleway:left"] = {
no = "with",
none = "with",
yes = "both",
lane = "both",
track = "both",
shared_lane = "both",
share_busway = "both",
opposite_lane = "both",
opposite_track = "both",
opposite = "both"
}
}))
end
--[[
Gives, for each type of highway, which the default legal maxspeed is in Belgium. This file is intended to be reused for in all vehicles, from pedestrian to car. In some cases, a legal maxspeed is not really defined (e.g. on footways). In that case, a socially acceptable speed should be taken (e.g.: a bicycle on a pedestrian path will go say around 12km/h)
Unit: km/h
Created by
Originally defined in legal_maxspeed_be.json
Uses tags: maxspeed, highway, designation
Used parameters:
Number of combintations: 26
Returns values:
]]
function legal_maxspeed_be(parameters, tags, result)
return default(30, first_match_of(tags, result,
{"maxspeed", "designation", "highway"},
{
maxspeed = parse(tags["maxspeed"]),
highway = {
cycleway = 30,
footway = 20,
crossing = 20,
pedestrian = 15,
path = 15,
corridor = 5,
residential = 30,
living_street = 20,
service = 30,
services = 30,
track = 50,
unclassified = 50,
road = 50,
motorway = 120,
motorway_link = 120,
primary = 90,
primary_link = 90,
secondary = 50,
secondary_link = 50,
tertiary = 50,
tertiary_link = 50
},
designation = {
towpath = 30
}
}))
end
--[[
Gives a comfort factor for a road, purely based on physical aspects of the road, which is a bit subjective; this takes a bit of scnery into account with a preference for `railway=abandoned` and `towpath=yes`
Unit: [0, 2]
Created by
Originally defined in bicycle.comfort.json
Uses tags: highway, railway, towpath, cycleway, cyclestreet, access, bicycle:class, surface, route
Used parameters:
Number of combintations: 55
Returns values:
]]
function bicycle_comfort(parameters, tags, result)
return default(1, multiply(table_to_list(tags, result,
{
highway = {
cycleway = 1.2,
primary = 0.3,
secondary = 0.4,
tertiary = 0.5,
unclassified = 0.8,
track = 0.95,
residential = 1,
living_street = 1.1,
footway = 0.95,
path = 0.5,
construction = 0.5
},
railway = {
abandoned = 2
},
towpath = {
yes = 2
},
cycleway = {
track = 1.2
},
cyclestreet = {
yes = 1.1
},
access = {
designated = 1.2,
dismount = 0.01
},
["bicycle:class"] = {
["-3"] = 0.5,
["-2"] = 0.7,
["-1"] = 0.9,
["0"] = 1,
["1"] = 1.1,
["2"] = 1.3,
["3"] = 1.5
},
surface = {
paved = 0.99,
["concrete:lanes"] = 0.8,
["concrete:plates"] = 1,
sett = 0.9,
unhewn_cobblestone = 0.75,
cobblestone = 0.8,
unpaved = 0.75,
compacted = 0.95,
fine_gravel = 0.7,
gravel = 0.9,
dirt = 0.6,
earth = 0.6,
grass = 0.6,
grass_paver = 0.9,
ground = 0.7,
sand = 0.5,
woodchips = 0.5,
snow = 0.5,
pebblestone = 0.5,
mud = 0.4
},
route = {
ferry = 0.01
}
})))
end
failed_profile_tests = false
--[[
Unit test of a behaviour function for an itinero 2.0 profile
]]
function unit_test_profile(profile_function, profile_name, index, expected, tags)
-- Note: we don't actually use 'profile_function'
local result = {}
local profile_failed = false
factor(tags, result)
local forward_access = result.forward_speed > 0 and result.forward > 0;
local backward_access = result.backward_speed > 0 and result.backward > 0;
if (not forward_access and not backward_access) then
if (expected.access == "no" or expected.speed <= 0 or expected.priority <= 0) then
-- All is fine, we can't access this thing anyway
return
end
profile_failed = true
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".access: expected " .. expected.access .. " but forward and backward are 0 (for either speed or factor)")
end
if (expected.oneway == "with") then
if (backward_access) then
-- we can go against the direction, not good
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is possible")
profile_failed = true;
end
if (not forward_access) then
print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is not possible")
end
end
if (expected.oneway == "against") then
if (forward_access) then
-- we can go against the direction, not good
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is possible")
end
if (not backward_access) then
print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is not possible")
end
end
if (result.forward_speed ~= expected.speed and result.backward_speed ~= expected.speed) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.forward_speed .. " forward and " .. result.backward_speed .. " backward")
profile_failed = true;
end
if (result.forward ~= expected.priority and result.backward ~= expected.priority) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".priority: expected " .. expected.priority .. " but got " .. result.forward .. " forward and " .. result.backward .. " backward")
profile_failed = true;
end
if(profile_failed) then
failed_profile_tests = true;
debug_table(tags, "tags: ")
debug_table(expected, "expected: ")
debug_table(result, "result: ")
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 default(defaultValue, realValue)
if(realValue ~= nil) then
return realValue
end
return defaultValue
end
function first_match_of(tags, result, order_of_keys, table)
for _, key in pairs(order_of_keys) do
local v = tags[key]
if (v ~= nil) then
local mapping = table[key]
if (type(mapping) == "table") then
local resultValue = mapping[v]
if (resultValue ~= nil) then
result.attributes_to_keep[key] = v
return resultValue
end
else
result.attributes_to_keep[key] = v
return mapping
end
end
end
return nil;
end
function parse(string)
if (string == nil) then
return 0
end
if (type(string) == "number") then
return string
end
if (string == "yes" or string == "true") then
return 1
end
if (string == "no" or string == "false") then
return 0
end
if (type(string) == "boolean") then
if (string) then
return 1
else
return 0
end
end
if(string:match("%d+:%d+")) then
-- duration in minute
local duration = 0
for part in string:gmatch "%d+" do
duration = duration * 60 + tonumber(part)
end
return duration
end
return tonumber(string)
end
function eq(a, b)
if (a == b) then
return "yes"
else
return "no"
end
end
function min(list)
local min
for _, value in pairs(list) do
if(value ~= nil) then
if (min == nil) then
min = value
elseif (value < min) then
min = value
end
end
end
return min;
end
function multiply(list)
local factor = 1
for _, value in pairs(list) do
if (value ~= nil) then
factor = factor * value
end
end
return factor;
end
function table_to_list(tags, result, factor_table)
local list = {}
for key, mapping in pairs(factor_table) do
local v = tags[key]
if (v ~= nil) then
if (type(mapping) == "table") then
local f = mapping[v]
if (f ~= nil) then
table.insert(list, f);
result.attributes_to_keep[key] = v
end
else
table.insert(list, mapping);
result.attributes_to_keep[key] = v
end
end
end
return list;
end
function test_all()
-- 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 and print ~= nil) then
print("Tests OK")
end

View file

@ -1,607 +0,0 @@
name = "bicycle.electric"
generationDate = "2021-01-27T15:51:22"
description = "An electrical bicycle (Profile for a normal bicycle)"
--[[
Calculate the actual factor.forward and factor.backward for a segment with the given properties
]]
function factor(tags, result)
-- initialize the result table on the default values
result.forward_speed = 0
result.backward_speed = 0
result.forward = 0
result.backward = 0
result.canstop = true
result.attributes_to_keep = {} -- not actually used anymore, but the code generation still uses this
local parameters = default_parameters()
parameters.defaultSpeed = 25
parameters.comfort = 1
parameters.timeNeeded = 5
local oneway = bicycle_oneway(parameters, tags, result)
tags.oneway = oneway
-- An aspect describing oneway should give either 'both', 'against' or 'width'
-- forward calculation. We set the meta tag '_direction' to 'width' to indicate that we are going forward. The other functions will pick this up
tags["_direction"] = "with"
local access_forward = bicycle_legal_access(parameters, tags, result)
if(oneway == "against") then
-- no 'oneway=both' or 'oneway=with', so we can only go back over this segment
-- we overwrite the 'access_forward'-value with no; whatever it was...
access_forward = "no"
end
if(access_forward ~= nil and access_forward ~= "no") then
tags.access = access_forward -- might be relevant, e.g. for 'access=dismount' for bicycles
result.forward_speed =
min({
legal_maxspeed_be(parameters, tags, result),
parameters["defaultSpeed"]
})
tags.speed = result.forward_speed
local priority = calculate_priority(parameters, tags, result, access_forward, oneway, result.forward_speed)
if (priority <= 0) then
result.forward_speed = 0
else
result.forward = 1 / priority
end
end
-- backward calculation
tags["_direction"] = "against" -- indicate the backward direction to priority calculation
local access_backward = bicycle_legal_access(parameters, tags, result)
if(oneway == "with") then
-- no 'oneway=both' or 'oneway=against', so we can only go forward over this segment
-- we overwrite the 'access_forward'-value with no; whatever it was...
access_backward = "no"
end
if(access_backward ~= nil and access_backward ~= "no") then
tags.access = access_backward
result.backward_speed =
min({
legal_maxspeed_be(parameters, tags, result),
parameters["defaultSpeed"]
})
tags.speed = result.backward_speed
local priority = calculate_priority(parameters, tags, result, access_backward, oneway, result.backward_speed)
if (priority <= 0) then
result.backward_speed = 0
else
result.backward = 1 / priority
end
end
end
--[[
Generates the factor according to the priorities and the parameters for this behaviour
Note: 'result' is not actually used
]]
function calculate_priority(parameters, tags, result, access, oneway, speed)
local distance = 1
local priority =
1 * bicycle_comfort(parameters, tags, result) +
5 * speed
return priority
end
function default_parameters()
local parameters = {}
parameters.defaultSpeed = 15
parameters.timeNeeded = 0
parameters.distance = 0
parameters.comfort = 0
return parameters
end
--[[
Gives, for each type of highway, whether or not a normal bicycle 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 bicycles
'yes': bicycles are allowed here
'permissive': bicycles 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
'dismount': cycling here is not allowed, but walking with the bicycle is
'destination': cycling is allowed here, but only if truly necessary to reach the destination
'private': this is a private road, only go here if the destination is here
'no': do not cycle here
Created by
Originally defined in bicycle.legal_access.json
Uses tags: access, highway, service, bicycle, anyways:bicycle, anyways:access, anyways:construction
Used parameters:
Number of combintations: 54
Returns values:
]]
function bicycle_legal_access(parameters, tags, result)
return default("no", first_match_of(tags, result,
{"anyways:bicycle", "anyways:access", "anyways:construction", "bicycle", "access", "service", "highway"},
{
access = {
no = "no",
customers = "private",
private = "private",
permissive = "permissive",
destination = "destination",
delivery = "destination",
service = "destination",
permit = "destination"
},
highway = {
cycleway = "designated",
residential = "yes",
living_street = "yes",
service = "yes",
services = "yes",
track = "yes",
crossing = "dismount",
footway = "dismount",
pedestrian = "dismount",
corridor = "dismount",
construction = "dismount",
steps = "dismount",
path = "yes",
primary = "yes",
primary_link = "yes",
secondary = "yes",
secondary_link = "yes",
tertiary = "yes",
tertiary_link = "yes",
unclassified = "yes",
road = "yes"
},
service = {
parking_aisle = "permissive",
driveway = "private",
alley = "yes",
bus = "no"
},
bicycle = {
yes = "yes",
no = "no",
use_sidepath = "no",
designated = "designated",
permissive = "permissive",
private = "private",
official = "designated",
dismount = "dismount",
permit = "destination"
},
["anyways:bicycle"] = tags["anyways:bicycle"],
["anyways:access"] = {
no = "no",
destination = "destination",
yes = "yes"
},
["anyways:construction"] = {
yes = "no"
}
}))
end
--[[
Determines wether or not a bicycle can go in both ways in this street, and if it is oneway, in what direction
Unit: both: direction is allowed in both direction
with: this is a oneway street with direction allowed with the grain of the way
against: oneway street with direction against the way
Created by
Originally defined in bicycle.oneway.json
Uses tags: oneway, oneway:bicycle, junction, cycleway, cycleway:left
Used parameters:
Number of combintations: 32
Returns values:
]]
function bicycle_oneway(parameters, tags, result)
return default("both", first_match_of(tags, result,
{"oneway:bicycle", "junction", "cycleway", "cycleway:left", "oneway"},
{
oneway = {
yes = "with",
no = "both",
["1"] = "with",
["-1"] = "against"
},
["oneway:bicycle"] = {
yes = "with",
no = "both",
["1"] = "with",
["-1"] = "against"
},
junction = {
roundabout = "with"
},
cycleway = {
right = "against",
opposite_lane = "both",
track = "both",
lane = "both",
opposite = "both",
opposite_share_busway = "both",
opposite_track = "both"
},
["cycleway:left"] = {
no = "with",
none = "with",
yes = "both",
lane = "both",
track = "both",
shared_lane = "both",
share_busway = "both",
opposite_lane = "both",
opposite_track = "both",
opposite = "both"
}
}))
end
--[[
Gives, for each type of highway, which the default legal maxspeed is in Belgium. This file is intended to be reused for in all vehicles, from pedestrian to car. In some cases, a legal maxspeed is not really defined (e.g. on footways). In that case, a socially acceptable speed should be taken (e.g.: a bicycle on a pedestrian path will go say around 12km/h)
Unit: km/h
Created by
Originally defined in legal_maxspeed_be.json
Uses tags: maxspeed, highway, designation
Used parameters:
Number of combintations: 26
Returns values:
]]
function legal_maxspeed_be(parameters, tags, result)
return default(30, first_match_of(tags, result,
{"maxspeed", "designation", "highway"},
{
maxspeed = parse(tags["maxspeed"]),
highway = {
cycleway = 30,
footway = 20,
crossing = 20,
pedestrian = 15,
path = 15,
corridor = 5,
residential = 30,
living_street = 20,
service = 30,
services = 30,
track = 50,
unclassified = 50,
road = 50,
motorway = 120,
motorway_link = 120,
primary = 90,
primary_link = 90,
secondary = 50,
secondary_link = 50,
tertiary = 50,
tertiary_link = 50
},
designation = {
towpath = 30
}
}))
end
--[[
Gives a comfort factor for a road, purely based on physical aspects of the road, which is a bit subjective; this takes a bit of scnery into account with a preference for `railway=abandoned` and `towpath=yes`
Unit: [0, 2]
Created by
Originally defined in bicycle.comfort.json
Uses tags: highway, railway, towpath, cycleway, cyclestreet, access, bicycle:class, surface, route
Used parameters:
Number of combintations: 55
Returns values:
]]
function bicycle_comfort(parameters, tags, result)
return default(1, multiply(table_to_list(tags, result,
{
highway = {
cycleway = 1.2,
primary = 0.3,
secondary = 0.4,
tertiary = 0.5,
unclassified = 0.8,
track = 0.95,
residential = 1,
living_street = 1.1,
footway = 0.95,
path = 0.5,
construction = 0.5
},
railway = {
abandoned = 2
},
towpath = {
yes = 2
},
cycleway = {
track = 1.2
},
cyclestreet = {
yes = 1.1
},
access = {
designated = 1.2,
dismount = 0.01
},
["bicycle:class"] = {
["-3"] = 0.5,
["-2"] = 0.7,
["-1"] = 0.9,
["0"] = 1,
["1"] = 1.1,
["2"] = 1.3,
["3"] = 1.5
},
surface = {
paved = 0.99,
["concrete:lanes"] = 0.8,
["concrete:plates"] = 1,
sett = 0.9,
unhewn_cobblestone = 0.75,
cobblestone = 0.8,
unpaved = 0.75,
compacted = 0.95,
fine_gravel = 0.7,
gravel = 0.9,
dirt = 0.6,
earth = 0.6,
grass = 0.6,
grass_paver = 0.9,
ground = 0.7,
sand = 0.5,
woodchips = 0.5,
snow = 0.5,
pebblestone = 0.5,
mud = 0.4
},
route = {
ferry = 0.01
}
})))
end
failed_profile_tests = false
--[[
Unit test of a behaviour function for an itinero 2.0 profile
]]
function unit_test_profile(profile_function, profile_name, index, expected, tags)
-- Note: we don't actually use 'profile_function'
local result = {}
local profile_failed = false
factor(tags, result)
local forward_access = result.forward_speed > 0 and result.forward > 0;
local backward_access = result.backward_speed > 0 and result.backward > 0;
if (not forward_access and not backward_access) then
if (expected.access == "no" or expected.speed <= 0 or expected.priority <= 0) then
-- All is fine, we can't access this thing anyway
return
end
profile_failed = true
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".access: expected " .. expected.access .. " but forward and backward are 0 (for either speed or factor)")
end
if (expected.oneway == "with") then
if (backward_access) then
-- we can go against the direction, not good
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is possible")
profile_failed = true;
end
if (not forward_access) then
print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is not possible")
end
end
if (expected.oneway == "against") then
if (forward_access) then
-- we can go against the direction, not good
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is possible")
end
if (not backward_access) then
print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is not possible")
end
end
if (result.forward_speed ~= expected.speed and result.backward_speed ~= expected.speed) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.forward_speed .. " forward and " .. result.backward_speed .. " backward")
profile_failed = true;
end
if (result.forward ~= expected.priority and result.backward ~= expected.priority) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".priority: expected " .. expected.priority .. " but got " .. result.forward .. " forward and " .. result.backward .. " backward")
profile_failed = true;
end
if(profile_failed) then
failed_profile_tests = true;
debug_table(tags, "tags: ")
debug_table(expected, "expected: ")
debug_table(result, "result: ")
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 default(defaultValue, realValue)
if(realValue ~= nil) then
return realValue
end
return defaultValue
end
function first_match_of(tags, result, order_of_keys, table)
for _, key in pairs(order_of_keys) do
local v = tags[key]
if (v ~= nil) then
local mapping = table[key]
if (type(mapping) == "table") then
local resultValue = mapping[v]
if (resultValue ~= nil) then
result.attributes_to_keep[key] = v
return resultValue
end
else
result.attributes_to_keep[key] = v
return mapping
end
end
end
return nil;
end
function parse(string)
if (string == nil) then
return 0
end
if (type(string) == "number") then
return string
end
if (string == "yes" or string == "true") then
return 1
end
if (string == "no" or string == "false") then
return 0
end
if (type(string) == "boolean") then
if (string) then
return 1
else
return 0
end
end
if(string:match("%d+:%d+")) then
-- duration in minute
local duration = 0
for part in string:gmatch "%d+" do
duration = duration * 60 + tonumber(part)
end
return duration
end
return tonumber(string)
end
function eq(a, b)
if (a == b) then
return "yes"
else
return "no"
end
end
function min(list)
local min
for _, value in pairs(list) do
if(value ~= nil) then
if (min == nil) then
min = value
elseif (value < min) then
min = value
end
end
end
return min;
end
function multiply(list)
local factor = 1
for _, value in pairs(list) do
if (value ~= nil) then
factor = factor * value
end
end
return factor;
end
function table_to_list(tags, result, factor_table)
local list = {}
for key, mapping in pairs(factor_table) do
local v = tags[key]
if (v ~= nil) then
if (type(mapping) == "table") then
local f = mapping[v]
if (f ~= nil) then
table.insert(list, f);
result.attributes_to_keep[key] = v
end
else
table.insert(list, mapping);
result.attributes_to_keep[key] = v
end
end
end
return list;
end
function test_all()
-- 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 and print ~= nil) then
print("Tests OK")
end

View file

@ -1,531 +0,0 @@
name = "bicycle.fastest"
generationDate = "2021-01-27T15:51:22"
description = "The fastest route to your destination (Profile for a normal bicycle)"
--[[
Calculate the actual factor.forward and factor.backward for a segment with the given properties
]]
function factor(tags, result)
-- initialize the result table on the default values
result.forward_speed = 0
result.backward_speed = 0
result.forward = 0
result.backward = 0
result.canstop = true
result.attributes_to_keep = {} -- not actually used anymore, but the code generation still uses this
local parameters = default_parameters()
parameters.timeNeeded = 1
local oneway = bicycle_oneway(parameters, tags, result)
tags.oneway = oneway
-- An aspect describing oneway should give either 'both', 'against' or 'width'
-- forward calculation. We set the meta tag '_direction' to 'width' to indicate that we are going forward. The other functions will pick this up
tags["_direction"] = "with"
local access_forward = bicycle_legal_access(parameters, tags, result)
if(oneway == "against") then
-- no 'oneway=both' or 'oneway=with', so we can only go back over this segment
-- we overwrite the 'access_forward'-value with no; whatever it was...
access_forward = "no"
end
if(access_forward ~= nil and access_forward ~= "no") then
tags.access = access_forward -- might be relevant, e.g. for 'access=dismount' for bicycles
result.forward_speed =
min({
legal_maxspeed_be(parameters, tags, result),
parameters["defaultSpeed"]
})
tags.speed = result.forward_speed
local priority = calculate_priority(parameters, tags, result, access_forward, oneway, result.forward_speed)
if (priority <= 0) then
result.forward_speed = 0
else
result.forward = 1 / priority
end
end
-- backward calculation
tags["_direction"] = "against" -- indicate the backward direction to priority calculation
local access_backward = bicycle_legal_access(parameters, tags, result)
if(oneway == "with") then
-- no 'oneway=both' or 'oneway=against', so we can only go forward over this segment
-- we overwrite the 'access_forward'-value with no; whatever it was...
access_backward = "no"
end
if(access_backward ~= nil and access_backward ~= "no") then
tags.access = access_backward
result.backward_speed =
min({
legal_maxspeed_be(parameters, tags, result),
parameters["defaultSpeed"]
})
tags.speed = result.backward_speed
local priority = calculate_priority(parameters, tags, result, access_backward, oneway, result.backward_speed)
if (priority <= 0) then
result.backward_speed = 0
else
result.backward = 1 / priority
end
end
end
--[[
Generates the factor according to the priorities and the parameters for this behaviour
Note: 'result' is not actually used
]]
function calculate_priority(parameters, tags, result, access, oneway, speed)
local distance = 1
local priority =
1 * speed
return priority
end
function default_parameters()
local parameters = {}
parameters.defaultSpeed = 15
parameters.timeNeeded = 0
parameters.distance = 0
parameters.comfort = 0
return parameters
end
--[[
Gives, for each type of highway, whether or not a normal bicycle 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 bicycles
'yes': bicycles are allowed here
'permissive': bicycles 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
'dismount': cycling here is not allowed, but walking with the bicycle is
'destination': cycling is allowed here, but only if truly necessary to reach the destination
'private': this is a private road, only go here if the destination is here
'no': do not cycle here
Created by
Originally defined in bicycle.legal_access.json
Uses tags: access, highway, service, bicycle, anyways:bicycle, anyways:access, anyways:construction
Used parameters:
Number of combintations: 54
Returns values:
]]
function bicycle_legal_access(parameters, tags, result)
return default("no", first_match_of(tags, result,
{"anyways:bicycle", "anyways:access", "anyways:construction", "bicycle", "access", "service", "highway"},
{
access = {
no = "no",
customers = "private",
private = "private",
permissive = "permissive",
destination = "destination",
delivery = "destination",
service = "destination",
permit = "destination"
},
highway = {
cycleway = "designated",
residential = "yes",
living_street = "yes",
service = "yes",
services = "yes",
track = "yes",
crossing = "dismount",
footway = "dismount",
pedestrian = "dismount",
corridor = "dismount",
construction = "dismount",
steps = "dismount",
path = "yes",
primary = "yes",
primary_link = "yes",
secondary = "yes",
secondary_link = "yes",
tertiary = "yes",
tertiary_link = "yes",
unclassified = "yes",
road = "yes"
},
service = {
parking_aisle = "permissive",
driveway = "private",
alley = "yes",
bus = "no"
},
bicycle = {
yes = "yes",
no = "no",
use_sidepath = "no",
designated = "designated",
permissive = "permissive",
private = "private",
official = "designated",
dismount = "dismount",
permit = "destination"
},
["anyways:bicycle"] = tags["anyways:bicycle"],
["anyways:access"] = {
no = "no",
destination = "destination",
yes = "yes"
},
["anyways:construction"] = {
yes = "no"
}
}))
end
--[[
Determines wether or not a bicycle can go in both ways in this street, and if it is oneway, in what direction
Unit: both: direction is allowed in both direction
with: this is a oneway street with direction allowed with the grain of the way
against: oneway street with direction against the way
Created by
Originally defined in bicycle.oneway.json
Uses tags: oneway, oneway:bicycle, junction, cycleway, cycleway:left
Used parameters:
Number of combintations: 32
Returns values:
]]
function bicycle_oneway(parameters, tags, result)
return default("both", first_match_of(tags, result,
{"oneway:bicycle", "junction", "cycleway", "cycleway:left", "oneway"},
{
oneway = {
yes = "with",
no = "both",
["1"] = "with",
["-1"] = "against"
},
["oneway:bicycle"] = {
yes = "with",
no = "both",
["1"] = "with",
["-1"] = "against"
},
junction = {
roundabout = "with"
},
cycleway = {
right = "against",
opposite_lane = "both",
track = "both",
lane = "both",
opposite = "both",
opposite_share_busway = "both",
opposite_track = "both"
},
["cycleway:left"] = {
no = "with",
none = "with",
yes = "both",
lane = "both",
track = "both",
shared_lane = "both",
share_busway = "both",
opposite_lane = "both",
opposite_track = "both",
opposite = "both"
}
}))
end
--[[
Gives, for each type of highway, which the default legal maxspeed is in Belgium. This file is intended to be reused for in all vehicles, from pedestrian to car. In some cases, a legal maxspeed is not really defined (e.g. on footways). In that case, a socially acceptable speed should be taken (e.g.: a bicycle on a pedestrian path will go say around 12km/h)
Unit: km/h
Created by
Originally defined in legal_maxspeed_be.json
Uses tags: maxspeed, highway, designation
Used parameters:
Number of combintations: 26
Returns values:
]]
function legal_maxspeed_be(parameters, tags, result)
return default(30, first_match_of(tags, result,
{"maxspeed", "designation", "highway"},
{
maxspeed = parse(tags["maxspeed"]),
highway = {
cycleway = 30,
footway = 20,
crossing = 20,
pedestrian = 15,
path = 15,
corridor = 5,
residential = 30,
living_street = 20,
service = 30,
services = 30,
track = 50,
unclassified = 50,
road = 50,
motorway = 120,
motorway_link = 120,
primary = 90,
primary_link = 90,
secondary = 50,
secondary_link = 50,
tertiary = 50,
tertiary_link = 50
},
designation = {
towpath = 30
}
}))
end
failed_profile_tests = false
--[[
Unit test of a behaviour function for an itinero 2.0 profile
]]
function unit_test_profile(profile_function, profile_name, index, expected, tags)
-- Note: we don't actually use 'profile_function'
local result = {}
local profile_failed = false
factor(tags, result)
local forward_access = result.forward_speed > 0 and result.forward > 0;
local backward_access = result.backward_speed > 0 and result.backward > 0;
if (not forward_access and not backward_access) then
if (expected.access == "no" or expected.speed <= 0 or expected.priority <= 0) then
-- All is fine, we can't access this thing anyway
return
end
profile_failed = true
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".access: expected " .. expected.access .. " but forward and backward are 0 (for either speed or factor)")
end
if (expected.oneway == "with") then
if (backward_access) then
-- we can go against the direction, not good
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is possible")
profile_failed = true;
end
if (not forward_access) then
print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is not possible")
end
end
if (expected.oneway == "against") then
if (forward_access) then
-- we can go against the direction, not good
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is possible")
end
if (not backward_access) then
print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is not possible")
end
end
if (result.forward_speed ~= expected.speed and result.backward_speed ~= expected.speed) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.forward_speed .. " forward and " .. result.backward_speed .. " backward")
profile_failed = true;
end
if (result.forward ~= expected.priority and result.backward ~= expected.priority) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".priority: expected " .. expected.priority .. " but got " .. result.forward .. " forward and " .. result.backward .. " backward")
profile_failed = true;
end
if(profile_failed) then
failed_profile_tests = true;
debug_table(tags, "tags: ")
debug_table(expected, "expected: ")
debug_table(result, "result: ")
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
function default(defaultValue, realValue)
if(realValue ~= nil) then
return realValue
end
return defaultValue
end
function first_match_of(tags, result, order_of_keys, table)
for _, key in pairs(order_of_keys) do
local v = tags[key]
if (v ~= nil) then
local mapping = table[key]
if (type(mapping) == "table") then
local resultValue = mapping[v]
if (resultValue ~= nil) then
result.attributes_to_keep[key] = v
return resultValue
end
else
result.attributes_to_keep[key] = v
return mapping
end
end
end
return nil;
end
function parse(string)
if (string == nil) then
return 0
end
if (type(string) == "number") then
return string
end
if (string == "yes" or string == "true") then
return 1
end
if (string == "no" or string == "false") then
return 0
end
if (type(string) == "boolean") then
if (string) then
return 1
else
return 0
end
end
if(string:match("%d+:%d+")) then
-- duration in minute
local duration = 0
for part in string:gmatch "%d+" do
duration = duration * 60 + tonumber(part)
end
return duration
end
return tonumber(string)
end
function eq(a, b)
if (a == b) then
return "yes"
else
return "no"
end
end
function min(list)
local min
for _, value in pairs(list) do
if(value ~= nil) then
if (min == nil) then
min = value
elseif (value < min) then
min = value
end
end
end
return min;
end
function test_all()
-- Behaviour tests --
unit_test_profile(behaviour_bicycle_fastest, "fastest", 0, {access = "no", speed = 0, oneway = "both", priority = inv(0) }, {})
unit_test_profile(behaviour_bicycle_fastest, "fastest", 1, {access = "designated", speed = 15, oneway = "both", priority = inv(15) }, {highway = "cycleway"})
unit_test_profile(behaviour_bicycle_fastest, "fastest", 2, {access = "yes", speed = 15, oneway = "both", priority = inv(15) }, {highway = "residential"})
unit_test_profile(behaviour_bicycle_fastest, "fastest", 3, {access = "yes", speed = 15, oneway = "both", priority = inv(15) }, {highway = "pedestrian", bicycle = "yes"})
unit_test_profile(behaviour_bicycle_fastest, "fastest", 4, {access = "yes", speed = 15, oneway = "both", priority = inv(15) }, {highway = "unclassified", ["cycleway:left"] = "track", oneway = "yes", ["oneway:bicycle"] = "no"})
unit_test_profile(behaviour_bicycle_fastest, "fastest", 5, {access = "yes", speed = 15, oneway = "both", priority = inv(15) }, {highway = "service"})
unit_test_profile(behaviour_bicycle_fastest, "fastest", 6, {access = "yes", speed = 15, oneway = "both", priority = inv(15) }, {highway = "tertiary", access = "yes", maxspeed = "50"})
unit_test_profile(behaviour_bicycle_fastest, "fastest", 7, {access = "yes", speed = 15, oneway = "with", priority = inv(15) }, {highway = "residential", junction = "roundabout"})
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 and print ~= nil) then
print("Tests OK")
end

View file

@ -1,526 +0,0 @@
name = "bicycle.shortest"
generationDate = "2021-01-27T15:51:22"
description = "The shortest route, independent of of speed (Profile for a normal bicycle)"
--[[
Calculate the actual factor.forward and factor.backward for a segment with the given properties
]]
function factor(tags, result)
-- initialize the result table on the default values
result.forward_speed = 0
result.backward_speed = 0
result.forward = 0
result.backward = 0
result.canstop = true
result.attributes_to_keep = {} -- not actually used anymore, but the code generation still uses this
local parameters = default_parameters()
parameters.distance = 1
local oneway = bicycle_oneway(parameters, tags, result)
tags.oneway = oneway
-- An aspect describing oneway should give either 'both', 'against' or 'width'
-- forward calculation. We set the meta tag '_direction' to 'width' to indicate that we are going forward. The other functions will pick this up
tags["_direction"] = "with"
local access_forward = bicycle_legal_access(parameters, tags, result)
if(oneway == "against") then
-- no 'oneway=both' or 'oneway=with', so we can only go back over this segment
-- we overwrite the 'access_forward'-value with no; whatever it was...
access_forward = "no"
end
if(access_forward ~= nil and access_forward ~= "no") then
tags.access = access_forward -- might be relevant, e.g. for 'access=dismount' for bicycles
result.forward_speed =
min({
legal_maxspeed_be(parameters, tags, result),
parameters["defaultSpeed"]
})
tags.speed = result.forward_speed
local priority = calculate_priority(parameters, tags, result, access_forward, oneway, result.forward_speed)
if (priority <= 0) then
result.forward_speed = 0
else
result.forward = 1 / priority
end
end
-- backward calculation
tags["_direction"] = "against" -- indicate the backward direction to priority calculation
local access_backward = bicycle_legal_access(parameters, tags, result)
if(oneway == "with") then
-- no 'oneway=both' or 'oneway=against', so we can only go forward over this segment
-- we overwrite the 'access_forward'-value with no; whatever it was...
access_backward = "no"
end
if(access_backward ~= nil and access_backward ~= "no") then
tags.access = access_backward
result.backward_speed =
min({
legal_maxspeed_be(parameters, tags, result),
parameters["defaultSpeed"]
})
tags.speed = result.backward_speed
local priority = calculate_priority(parameters, tags, result, access_backward, oneway, result.backward_speed)
if (priority <= 0) then
result.backward_speed = 0
else
result.backward = 1 / priority
end
end
end
--[[
Generates the factor according to the priorities and the parameters for this behaviour
Note: 'result' is not actually used
]]
function calculate_priority(parameters, tags, result, access, oneway, speed)
local distance = 1
local priority =
1 * distance
return priority
end
function default_parameters()
local parameters = {}
parameters.defaultSpeed = 15
parameters.timeNeeded = 0
parameters.distance = 0
parameters.comfort = 0
return parameters
end
--[[
Gives, for each type of highway, whether or not a normal bicycle 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 bicycles
'yes': bicycles are allowed here
'permissive': bicycles 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
'dismount': cycling here is not allowed, but walking with the bicycle is
'destination': cycling is allowed here, but only if truly necessary to reach the destination
'private': this is a private road, only go here if the destination is here
'no': do not cycle here
Created by
Originally defined in bicycle.legal_access.json
Uses tags: access, highway, service, bicycle, anyways:bicycle, anyways:access, anyways:construction
Used parameters:
Number of combintations: 54
Returns values:
]]
function bicycle_legal_access(parameters, tags, result)
return default("no", first_match_of(tags, result,
{"anyways:bicycle", "anyways:access", "anyways:construction", "bicycle", "access", "service", "highway"},
{
access = {
no = "no",
customers = "private",
private = "private",
permissive = "permissive",
destination = "destination",
delivery = "destination",
service = "destination",
permit = "destination"
},
highway = {
cycleway = "designated",
residential = "yes",
living_street = "yes",
service = "yes",
services = "yes",
track = "yes",
crossing = "dismount",
footway = "dismount",
pedestrian = "dismount",
corridor = "dismount",
construction = "dismount",
steps = "dismount",
path = "yes",
primary = "yes",
primary_link = "yes",
secondary = "yes",
secondary_link = "yes",
tertiary = "yes",
tertiary_link = "yes",
unclassified = "yes",
road = "yes"
},
service = {
parking_aisle = "permissive",
driveway = "private",
alley = "yes",
bus = "no"
},
bicycle = {
yes = "yes",
no = "no",
use_sidepath = "no",
designated = "designated",
permissive = "permissive",
private = "private",
official = "designated",
dismount = "dismount",
permit = "destination"
},
["anyways:bicycle"] = tags["anyways:bicycle"],
["anyways:access"] = {
no = "no",
destination = "destination",
yes = "yes"
},
["anyways:construction"] = {
yes = "no"
}
}))
end
--[[
Determines wether or not a bicycle can go in both ways in this street, and if it is oneway, in what direction
Unit: both: direction is allowed in both direction
with: this is a oneway street with direction allowed with the grain of the way
against: oneway street with direction against the way
Created by
Originally defined in bicycle.oneway.json
Uses tags: oneway, oneway:bicycle, junction, cycleway, cycleway:left
Used parameters:
Number of combintations: 32
Returns values:
]]
function bicycle_oneway(parameters, tags, result)
return default("both", first_match_of(tags, result,
{"oneway:bicycle", "junction", "cycleway", "cycleway:left", "oneway"},
{
oneway = {
yes = "with",
no = "both",
["1"] = "with",
["-1"] = "against"
},
["oneway:bicycle"] = {
yes = "with",
no = "both",
["1"] = "with",
["-1"] = "against"
},
junction = {
roundabout = "with"
},
cycleway = {
right = "against",
opposite_lane = "both",
track = "both",
lane = "both",
opposite = "both",
opposite_share_busway = "both",
opposite_track = "both"
},
["cycleway:left"] = {
no = "with",
none = "with",
yes = "both",
lane = "both",
track = "both",
shared_lane = "both",
share_busway = "both",
opposite_lane = "both",
opposite_track = "both",
opposite = "both"
}
}))
end
--[[
Gives, for each type of highway, which the default legal maxspeed is in Belgium. This file is intended to be reused for in all vehicles, from pedestrian to car. In some cases, a legal maxspeed is not really defined (e.g. on footways). In that case, a socially acceptable speed should be taken (e.g.: a bicycle on a pedestrian path will go say around 12km/h)
Unit: km/h
Created by
Originally defined in legal_maxspeed_be.json
Uses tags: maxspeed, highway, designation
Used parameters:
Number of combintations: 26
Returns values:
]]
function legal_maxspeed_be(parameters, tags, result)
return default(30, first_match_of(tags, result,
{"maxspeed", "designation", "highway"},
{
maxspeed = parse(tags["maxspeed"]),
highway = {
cycleway = 30,
footway = 20,
crossing = 20,
pedestrian = 15,
path = 15,
corridor = 5,
residential = 30,
living_street = 20,
service = 30,
services = 30,
track = 50,
unclassified = 50,
road = 50,
motorway = 120,
motorway_link = 120,
primary = 90,
primary_link = 90,
secondary = 50,
secondary_link = 50,
tertiary = 50,
tertiary_link = 50
},
designation = {
towpath = 30
}
}))
end
failed_profile_tests = false
--[[
Unit test of a behaviour function for an itinero 2.0 profile
]]
function unit_test_profile(profile_function, profile_name, index, expected, tags)
-- Note: we don't actually use 'profile_function'
local result = {}
local profile_failed = false
factor(tags, result)
local forward_access = result.forward_speed > 0 and result.forward > 0;
local backward_access = result.backward_speed > 0 and result.backward > 0;
if (not forward_access and not backward_access) then
if (expected.access == "no" or expected.speed <= 0 or expected.priority <= 0) then
-- All is fine, we can't access this thing anyway
return
end
profile_failed = true
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".access: expected " .. expected.access .. " but forward and backward are 0 (for either speed or factor)")
end
if (expected.oneway == "with") then
if (backward_access) then
-- we can go against the direction, not good
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is possible")
profile_failed = true;
end
if (not forward_access) then
print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is not possible")
end
end
if (expected.oneway == "against") then
if (forward_access) then
-- we can go against the direction, not good
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is possible")
end
if (not backward_access) then
print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is not possible")
end
end
if (result.forward_speed ~= expected.speed and result.backward_speed ~= expected.speed) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.forward_speed .. " forward and " .. result.backward_speed .. " backward")
profile_failed = true;
end
if (result.forward ~= expected.priority and result.backward ~= expected.priority) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".priority: expected " .. expected.priority .. " but got " .. result.forward .. " forward and " .. result.backward .. " backward")
profile_failed = true;
end
if(profile_failed) then
failed_profile_tests = true;
debug_table(tags, "tags: ")
debug_table(expected, "expected: ")
debug_table(result, "result: ")
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
function default(defaultValue, realValue)
if(realValue ~= nil) then
return realValue
end
return defaultValue
end
function first_match_of(tags, result, order_of_keys, table)
for _, key in pairs(order_of_keys) do
local v = tags[key]
if (v ~= nil) then
local mapping = table[key]
if (type(mapping) == "table") then
local resultValue = mapping[v]
if (resultValue ~= nil) then
result.attributes_to_keep[key] = v
return resultValue
end
else
result.attributes_to_keep[key] = v
return mapping
end
end
end
return nil;
end
function parse(string)
if (string == nil) then
return 0
end
if (type(string) == "number") then
return string
end
if (string == "yes" or string == "true") then
return 1
end
if (string == "no" or string == "false") then
return 0
end
if (type(string) == "boolean") then
if (string) then
return 1
else
return 0
end
end
if(string:match("%d+:%d+")) then
-- duration in minute
local duration = 0
for part in string:gmatch "%d+" do
duration = duration * 60 + tonumber(part)
end
return duration
end
return tonumber(string)
end
function eq(a, b)
if (a == b) then
return "yes"
else
return "no"
end
end
function min(list)
local min
for _, value in pairs(list) do
if(value ~= nil) then
if (min == nil) then
min = value
elseif (value < min) then
min = value
end
end
end
return min;
end
function test_all()
-- Behaviour tests --
unit_test_profile(behaviour_bicycle_shortest, "shortest", 0, {access = "no", speed = 0, oneway = "both", priority = inv(0) }, {})
unit_test_profile(behaviour_bicycle_shortest, "shortest", 1, {access = "designated", speed = 15, oneway = "both", priority = inv(1) }, {highway = "cycleway"})
unit_test_profile(behaviour_bicycle_shortest, "shortest", 2, {access = "yes", speed = 15, oneway = "both", priority = inv(1) }, {highway = "path", surface = "ground"})
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 and print ~= nil) then
print("Tests OK")
end

View file

@ -1,393 +0,0 @@
name = "pedestrian.shortest"
generationDate = "2021-01-27T15:51:22"
description = "The shortest route, independent of of speed (Profile for someone who is walking)"
--[[
Calculate the actual factor.forward and factor.backward for a segment with the given properties
]]
function factor(tags, result)
-- initialize the result table on the default values
result.forward_speed = 0
result.backward_speed = 0
result.forward = 0
result.backward = 0
result.canstop = true
result.attributes_to_keep = {} -- not actually used anymore, but the code generation still uses this
local parameters = default_parameters()
parameters.distance = 1
parameters.leastSafetyPenalty = 2
local oneway = const("both")
tags.oneway = oneway
-- An aspect describing oneway should give either 'both', 'against' or 'width'
-- forward calculation. We set the meta tag '_direction' to 'width' to indicate that we are going forward. The other functions will pick this up
tags["_direction"] = "with"
local access_forward = pedestrian_legal_access(parameters, tags, result)
if(oneway == "against") then
-- no 'oneway=both' or 'oneway=with', so we can only go back over this segment
-- we overwrite the 'access_forward'-value with no; whatever it was...
access_forward = "no"
end
if(access_forward ~= nil and access_forward ~= "no") then
tags.access = access_forward -- might be relevant, e.g. for 'access=dismount' for bicycles
result.forward_speed = const(parameters["defaultSpeed"])
tags.speed = result.forward_speed
local priority = calculate_priority(parameters, tags, result, access_forward, oneway, result.forward_speed)
if (priority <= 0) then
result.forward_speed = 0
else
result.forward = 1 / priority
end
end
-- backward calculation
tags["_direction"] = "against" -- indicate the backward direction to priority calculation
local access_backward = pedestrian_legal_access(parameters, tags, result)
if(oneway == "with") then
-- no 'oneway=both' or 'oneway=against', so we can only go forward over this segment
-- we overwrite the 'access_forward'-value with no; whatever it was...
access_backward = "no"
end
if(access_backward ~= nil and access_backward ~= "no") then
tags.access = access_backward
result.backward_speed = const(parameters["defaultSpeed"])
tags.speed = result.backward_speed
local priority = calculate_priority(parameters, tags, result, access_backward, oneway, result.backward_speed)
if (priority <= 0) then
result.backward_speed = 0
else
result.backward = 1 / priority
end
end
end
--[[
Generates the factor according to the priorities and the parameters for this behaviour
Note: 'result' is not actually used
]]
function calculate_priority(parameters, tags, result, access, oneway, speed)
local distance = 1
local priority =
1 * distance +
1 * clean_permission_score(parameters, tags, result)
return priority
end
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
--[[
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)
return default("no", first_match_of(tags, result,
{"anyways:foot", "anyways:access", "anyways:construction", "foot", "access", "service", "highway"},
{
access = {
no = "no",
customers = "private",
private = "private",
permissive = "permissive",
destination = "destination",
delivery = "destination",
service = "destination",
permit = "destination"
},
highway = {
pedestrian = "designated",
footway = "designated",
living_street = "designated",
steps = "yes",
corridor = "designated",
residential = "yes",
service = "yes",
services = "yes",
track = "yes",
crossing = "yes",
construction = "permissive",
path = "yes",
primary = "yes",
primary_link = "yes",
secondary = "yes",
secondary_link = "yes",
tertiary = "yes",
tertiary_link = "yes",
unclassified = "yes",
road = "yes"
},
service = {
parking_aisle = "permissive",
driveway = "private",
alley = "yes",
bus = "no"
},
foot = {
yes = "yes",
no = "no",
use_sidepath = "no",
designated = "designated",
permissive = "permissive",
private = "private",
official = "designated",
dismount = "dismount",
permit = "destination"
},
["anyways:foot"] = tags["anyways:foot"],
["anyways:access"] = {
no = "no",
destination = "destination",
yes = "yes"
},
["anyways:construction"] = {
yes = "no"
}
}))
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)
return default(0, head(table_to_list(tags, result,
{
access = {
private = -50,
destination = -3,
permissive = -1
}
})))
end
failed_profile_tests = false
--[[
Unit test of a behaviour function for an itinero 2.0 profile
]]
function unit_test_profile(profile_function, profile_name, index, expected, tags)
-- Note: we don't actually use 'profile_function'
local result = {}
local profile_failed = false
factor(tags, result)
local forward_access = result.forward_speed > 0 and result.forward > 0;
local backward_access = result.backward_speed > 0 and result.backward > 0;
if (not forward_access and not backward_access) then
if (expected.access == "no" or expected.speed <= 0 or expected.priority <= 0) then
-- All is fine, we can't access this thing anyway
return
end
profile_failed = true
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".access: expected " .. expected.access .. " but forward and backward are 0 (for either speed or factor)")
end
if (expected.oneway == "with") then
if (backward_access) then
-- we can go against the direction, not good
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is possible")
profile_failed = true;
end
if (not forward_access) then
print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is not possible")
end
end
if (expected.oneway == "against") then
if (forward_access) then
-- we can go against the direction, not good
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is possible")
end
if (not backward_access) then
print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is not possible")
end
end
if (result.forward_speed ~= expected.speed and result.backward_speed ~= expected.speed) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.forward_speed .. " forward and " .. result.backward_speed .. " backward")
profile_failed = true;
end
if (result.forward ~= expected.priority and result.backward ~= expected.priority) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".priority: expected " .. expected.priority .. " but got " .. result.forward .. " forward and " .. result.backward .. " backward")
profile_failed = true;
end
if(profile_failed) then
failed_profile_tests = true;
debug_table(tags, "tags: ")
debug_table(expected, "expected: ")
debug_table(result, "result: ")
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 default(defaultValue, realValue)
if(realValue ~= nil) then
return realValue
end
return defaultValue
end
function first_match_of(tags, result, order_of_keys, table)
for _, key in pairs(order_of_keys) do
local v = tags[key]
if (v ~= nil) then
local mapping = table[key]
if (type(mapping) == "table") then
local resultValue = mapping[v]
if (resultValue ~= nil) then
result.attributes_to_keep[key] = v
return resultValue
end
else
result.attributes_to_keep[key] = v
return mapping
end
end
end
return nil;
end
function eq(a, b)
if (a == b) then
return "yes"
else
return "no"
end
end
function const(a, b)
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
function table_to_list(tags, result, factor_table)
local list = {}
for key, mapping in pairs(factor_table) do
local v = tags[key]
if (v ~= nil) then
if (type(mapping) == "table") then
local f = mapping[v]
if (f ~= nil) then
table.insert(list, f);
result.attributes_to_keep[key] = v
end
else
table.insert(list, mapping);
result.attributes_to_keep[key] = v
end
end
end
return list;
end
function test_all()
-- 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 and print ~= nil) then
print("Tests OK")
end

View file

@ -1,697 +0,0 @@
-- 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 = const("both")
tags.oneway = oneway
local speed = const(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)
return default("no", first_match_of(tags, result,
{"anyways:foot", "anyways:access", "anyways:construction", "foot", "access", "service", "highway"},
{
access = {
no = "no",
customers = "private",
private = "private",
permissive = "permissive",
destination = "destination",
delivery = "destination",
service = "destination",
permit = "destination"
},
highway = {
pedestrian = "designated",
footway = "designated",
living_street = "designated",
steps = "yes",
corridor = "designated",
residential = "yes",
service = "yes",
services = "yes",
track = "yes",
crossing = "yes",
construction = "permissive",
path = "yes",
primary = "yes",
primary_link = "yes",
secondary = "yes",
secondary_link = "yes",
tertiary = "yes",
tertiary_link = "yes",
unclassified = "yes",
road = "yes"
},
service = {
parking_aisle = "permissive",
driveway = "private",
alley = "yes",
bus = "no"
},
foot = {
yes = "yes",
no = "no",
use_sidepath = "no",
designated = "designated",
permissive = "permissive",
private = "private",
official = "designated",
dismount = "dismount",
permit = "destination"
},
["anyways:foot"] = tags["anyways:foot"],
["anyways:access"] = {
no = "no",
destination = "destination",
yes = "yes"
},
["anyways:construction"] = {
yes = "no"
}
}))
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)
return default(0, head(table_to_list(tags, result,
{
access = {
private = -50,
destination = -3,
permissive = -1
}
})))
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)
return 15
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)
return 1
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 default(defaultValue, realValue)
if(realValue ~= nil) then
return realValue
end
return defaultValue
end
function first_match_of(tags, result, order_of_keys, table)
for _, key in pairs(order_of_keys) do
local v = tags[key]
if (v ~= nil) then
local mapping = table[key]
if (type(mapping) == "table") then
local resultValue = mapping[v]
if (resultValue ~= nil) then
result.attributes_to_keep[key] = v
return resultValue
end
else
result.attributes_to_keep[key] = v
return mapping
end
end
end
return nil;
end
function const(a, b)
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
function table_to_list(tags, result, factor_table)
local list = {}
for key, mapping in pairs(factor_table) do
local v = tags[key]
if (v ~= nil) then
if (type(mapping) == "table") then
local f = mapping[v]
if (f ~= nil) then
table.insert(list, f);
result.attributes_to_keep[key] = v
end
else
table.insert(list, mapping);
result.attributes_to_keep[key] = v
end
end
end
return list;
end
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