Restructuring of profile dir, more work on porting bicycle, still a bug for relation handling

This commit is contained in:
Pieter Vander Vennet 2020-05-05 03:21:37 +02:00
parent 287dde6cee
commit e9e41e170c
40 changed files with 233 additions and 1303 deletions

View file

@ -232,7 +232,7 @@ namespace AspectedRouting.IO.itinero1
case ValueTuple<string, string> unpack:
return unpack.Item1 + "[" + unpack.Item2 + "]";
case IEnumerable<object> ls:
var t = (c.Types.First() as ListType).InnerType;
var t = ((ListType) c.Types.First()).InnerType;
return "{" + string.Join(", ", ls.Select(obj =>
{
var objInConstant = new Constant(t, obj);

View file

@ -49,9 +49,8 @@ namespace AspectedRouting.IO.itinero1
foreach (var (calledInFunction, expr) in memberships)
{
func.Add($"\n\n -- {calledInFunction} ---");
var usedParameters = expr.UsedParameters().Select(param => param.ParamName.TrimStart('#')).ToHashSet();
// First, we calculate the value for the default parameters
@ -65,7 +64,8 @@ namespace AspectedRouting.IO.itinero1
var tagKey = "_relation:" + calledInFunction.FunctionName();
_neededKeys.Add(tagKey);
func.Add(
" -- " + tagKey +" is the default value, which will be overwritten in 'remove_relation_prefix' for behaviours having a different parameter settign");
" -- " + tagKey +
" is the default value, which will be overwritten in 'remove_relation_prefix' for behaviours having a different parameter settign");
func.Add($" result.attributes_to_keep[\"{tagKey}\"] = \"yes\"");
func.Add(" end");
@ -76,7 +76,7 @@ namespace AspectedRouting.IO.itinero1
func.Add(" -- No parameter dependence for aspect " + calledInFunction);
continue;
}
foreach (var (behaviourName, parameters) in profile.Behaviours)
{
if (usedParameters.Except(parameters.Keys.ToHashSet()).Any())
@ -99,8 +99,6 @@ namespace AspectedRouting.IO.itinero1
func.Add($" result.attributes_to_keep[\"{tagKey}\"] = \"yes\"");
func.Add(" end");
}
}
func.Add("end");
@ -173,7 +171,7 @@ namespace AspectedRouting.IO.itinero1
foreach (var (parameterName, expression) in profile.Priority)
{
var paramInLua = ToLua(new Parameter(parameterName));
var exprInLua = ToLua(expression);
var subs = new Curry(Typs.Tags, new Var(("a"))).UnificationTable(expression.Types.First());
@ -184,21 +182,22 @@ namespace AspectedRouting.IO.itinero1
exprInLua = "parse(" + exprInLua + ")";
}
impl += "\n "+string.Join("\n ",
$"if({paramInLua} ~= 0) then",
$" priority = priority + {paramInLua} * {exprInLua}",
"end"
);
impl += "\n " + string.Join("\n ",
$"if({paramInLua} ~= 0) then",
$" priority = priority + {paramInLua} * {exprInLua}",
"end"
);
}
impl += string.Join("\n",
"",
"",
" if (priority <= 0) then",
" result.access = 0",
" return",
" end",
"",
" -- put all the values into the result-table, as needed for itinero",
" result.access = 1",
" result.speed = speed",
" result.factor = 1 / priority",
@ -248,8 +247,8 @@ namespace AspectedRouting.IO.itinero1
subParams.TryGetValue("description", out var description);
profiles.Add(
string.Join(",\n ",
$" name = \"{name}\"",
" function_name = \"profile_" + functionName + "\"",
$" name = \"{name.FunctionName()}\"",
" function_name = \"behaviour_" + functionName.FunctionName() + "\"",
" metric = \"custom\""
)
);
@ -260,9 +259,10 @@ namespace AspectedRouting.IO.itinero1
"--[[",
description,
"]]",
"function profile_" + functionName + "(tags, result)",
"function behaviour_" + functionName.FunctionName() + "(tags, result)",
$" tags = remove_relation_prefix(tags, \"{name.FunctionName()}\")",
" local parameters = default_parameters()",
" parameters.name = \"" + functionName + "\"",
""
);

View file

@ -46,7 +46,7 @@ namespace AspectedRouting.IO.itinero1
}
// function unit_test_profile(profile_function, profile_name, index, expected, tags)
return $"unit_test_profile(profile_bicycle_{testSuite.BehaviourName.FunctionName()}, " +
return $"unit_test_profile(behaviour_{testSuite.Profile.Name.FunctionName()}_{testSuite.BehaviourName.FunctionName()}, " +
$"\"{testSuite.BehaviourName}\", " +
$"{index}, " +
$"{{access = \"{D(expected.Access)}\", speed = {expected.Speed}, oneway = \"{D(expected.Oneway)}\", weight = {expected.Weight} }}, " +
@ -65,7 +65,7 @@ namespace AspectedRouting.IO.itinero1
}
public void AddTestSuite(FunctionTestSuite testSuite)
public void AddTestSuite(AspectTestSuite testSuite)
{
var fName = testSuite.FunctionToApply.Name;
var tests = string.Join("\n",

View file

@ -51,14 +51,15 @@ namespace AspectedRouting.IO.jsonParser
}
}
private static ProfileMetaData ParseProfile(this JsonElement e, Context context, FileInfo filepath)
{
if (!e.TryGetProperty("priority",out _))
if (!e.TryGetProperty("speed", out _))
{
// not a profile
return null;
}
var name = e.Get("name");
var author = e.TryGet("author");
if (filepath != null && !(name + ".json").ToLower().Equals(filepath.Name.ToLower()))
@ -67,9 +68,20 @@ namespace AspectedRouting.IO.jsonParser
$"filename is {filepath.Name}, declared name is {name}");
}
var vehicleTypes = e.GetProperty("vehicletypes").EnumerateArray().Select(
JsonElement GetTopProperty(string name)
{
if (e.TryGetProperty(name, out var p))
{
return p;
}
throw new ArgumentException(
filepath + " is not a valid profile; it does not contain the obligated parameter " + name);
}
var vehicleTypes = GetTopProperty("vehicletypes").EnumerateArray().Select(
el => el.GetString()).ToList();
var metadata = e.GetProperty("metadata").EnumerateArray().Select(
var metadata = GetTopProperty("metadata").EnumerateArray().Select(
el => el.GetString()).ToList();
@ -111,7 +123,7 @@ namespace AspectedRouting.IO.jsonParser
var profiles = new Dictionary<string, Dictionary<string, IExpression>>();
foreach (var profile in e.GetProperty("behaviours").EnumerateObject())
foreach (var profile in GetTopProperty("behaviours").EnumerateObject())
{
profiles[profile.Name] = ParseParameters(profile.Value);
}
@ -169,7 +181,18 @@ namespace AspectedRouting.IO.jsonParser
try
{
var simpleMapping = new Mapping(keys, exprs);
return new Apply(_mappingWrapper, simpleMapping);
var wrapped = (IExpression) new Apply(_mappingWrapper, simpleMapping);
if (keys.Count == 1)
{
// We can interpret this directly without going through a list
wrapped = Funcs.Either(Funcs.Id,
new Apply(Funcs.Dot, Funcs.Head),
wrapped);
}
return wrapped;
}
catch (Exception e)
{
@ -446,7 +469,7 @@ namespace AspectedRouting.IO.jsonParser
throw new ArgumentException($"Filename does not match the defined name: " +
$"filename is {filepath}, declared name is {name}");
}
var keys = (IEnumerable<string>) expr.PossibleTags()?.Keys ?? new List<string>();
foreach (var key in keys)
{

View file

@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using AspectedRouting.Language;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
@ -14,9 +12,14 @@ namespace AspectedRouting.IO.jsonParser
{
private static IExpression ParseProfileProperty(JsonElement e, Context c, string property)
{
if (!e.TryGetProperty(property, out var prop))
{
throw new ArgumentException("Not a valid profile: the declaration expression for '" + property +
"' is missing");
}
try
{
var prop = e.GetProperty(property);
return ParseExpression(prop, c)
.Specialize(new Curry(Typs.Tags, new Var("a")))
.Optimize();
@ -57,7 +60,7 @@ namespace AspectedRouting.IO.jsonParser
ps[nm] = new Constant(Typs.Bool, "yes");
break;
case JsonValueKind.Array:
var list = obj.Value.EnumerateArray().Select(e => e.ToString()).ToList();
var list = obj.Value.EnumerateArray().Select(x => x.ToString()).ToList();
ps[nm] = new Constant(new ListType(Typs.String),list);
break;
default:

View file

@ -11,4 +11,20 @@ function debug_table(table, prefix)
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

View file

@ -0,0 +1,11 @@
function head(ls)
if(ls == nil) then
return nil
end
for _, v in ipairs(ls) do
if(v ~= nil) then
return v
end
end
return nil
end

View file

@ -129,7 +129,7 @@ namespace AspectedRouting.Language
public static Dictionary<string, (List<Type> Types, string inFunction)> UsedParameters(
this ProfileMetaData profile, Context context)
{
var parameters = new Dictionary<string, (List<Type> Types, string inFunction)>();
var parameters = new Dictionary<string, (List<Type> Types, string usageLocation)>();
void AddParams(IExpression e, string inFunction)
@ -145,8 +145,8 @@ namespace AspectedRouting.Language
{
throw new ArgumentException("Inconsistent parameter usage: the paremeter " +
param.ParamName + " is used\n" +
$" in function {oldUsage} as {string.Join(",", types)}\n" +
$" in function {inFunction} as {string.Join(",", param.Types)}\n" +
$" in {oldUsage} as {string.Join(",", types)}\n" +
$" in {inFunction} as {string.Join(",", param.Types)}\n" +
$"which can not be unified");
}
}
@ -158,9 +158,9 @@ namespace AspectedRouting.Language
}
AddParams(profile.Access, profile.Name + ".access");
AddParams(profile.Oneway, profile.Name + ".oneway");
AddParams(profile.Speed, profile.Name + ".speed");
AddParams(profile.Access, "profile definition for " + profile.Name + ".access");
AddParams(profile.Oneway, "profile definition for " + profile.Name + ".oneway");
AddParams(profile.Speed, "profile definition for " + profile.Name + ".speed");
foreach (var (key, expr) in profile.Priority)
{
@ -173,7 +173,12 @@ namespace AspectedRouting.Language
foreach (var calledFunction in calledFunctions)
{
var func = context.GetFunction(calledFunction);
AddParams(func, calledFunction);
if (func is AspectMetadata meta && meta.ProfileInternal)
{
continue;
}
AddParams(func, "function " + calledFunction);
}
@ -257,7 +262,7 @@ namespace AspectedRouting.Language
public static void SanityCheckProfile(this ProfileMetaData pmd, Context context)
{
var defaultParameters = pmd.DefaultParameters.Keys;
var defaultParameters = pmd.DefaultParameters.Keys.Select(k => k.TrimStart('#'));
var usedMetadata = pmd.UsedParameters(context);
@ -275,7 +280,7 @@ namespace AspectedRouting.Language
return metaInfo;
}
var usedParameters = usedMetadata.Keys.Select(key => key.TrimStart('#'));
var usedParameters = usedMetadata.Keys.Select(key => key.TrimStart('#')).ToList();
var diff = usedParameters.ToHashSet().Except(defaultParameters).ToList();
if (diff.Any())
@ -296,9 +301,9 @@ namespace AspectedRouting.Language
{
var sum = 0.0;
var explanation = "";
paramsUsedInBehaviour.UnionWith(behaviourParams.Keys.Select(k => k.Trim('#')));
foreach (var (paramName, _) in pmd.Priority)
{
paramsUsedInBehaviour.Add(paramName);
if (!pmd.DefaultParameters.ContainsKey(paramName))
{
throw new ArgumentException(

View file

@ -32,12 +32,7 @@ namespace AspectedRouting.Language.Expression
public object Evaluate(Context c, params IExpression[] arguments)
{
if (ProfileInternal && arguments.Length > 0)
{
var tags = (Dictionary<string, string>) arguments[0].Evaluate(c);
}
return ExpressionImplementation.Evaluate(c, arguments);
}

View file

@ -49,6 +49,7 @@ namespace AspectedRouting.Language
public static readonly Function EitherFunc = new EitherFunc();
public static readonly Function StringStringToTags = new StringStringToTagsFunction();
public static readonly Function Head = new HeadFunction();
public static void AddBuiltin(Function f, string name = null)
{

View file

@ -40,7 +40,9 @@ namespace AspectedRouting.Language.Functions
}
catch (Exception e)
{
throw new Exception($"While creating a list with members {string.Join(", ", exprs.Select(e => e.Optimize()))} {e.Message}", e);
throw new Exception($"While creating a list with members "+
string.Join(", ", exprs.Select(x => x.Optimize()))+
$" {e.Message}", e);
}
}

View file

@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Typ;
namespace AspectedRouting.Language.Functions
{
public class HeadFunction : Function
{
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)
{
o = e.Evaluate(c);
}
if (!(o is IEnumerable<object> ls)) return null;
foreach (var a in ls)
{
if (a != null)
{
return a;
}
}
return null;
}
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
{
var unified = Types.SpecializeTo(allowedTypes);
if (unified == null)
{
return null;
}
return new HeadFunction(unified);
}
}
}

View file

@ -11,7 +11,6 @@ namespace AspectedRouting.Language.Functions
{
public readonly Dictionary<string, IExpression> StringToResultFunctions;
public Mapping(IReadOnlyList<string> keys, IEnumerable<IExpression> expressions) :
base(
$"mapping ({MappingToString(keys, expressions.SpecializeToCommonTypes(out var specializedTypes, out var specializedExpressions))})",
@ -69,11 +68,13 @@ namespace AspectedRouting.Language.Functions
public override object Evaluate(Context c, params IExpression[] arguments)
{
var s = arguments[0].Evaluate(c);
while (s is Constant constant)
{
s = constant.Evaluate(c);
}
var key = (string) s;
var otherARgs = arguments.ToList().GetRange(1, arguments.Length - 1);

View file

@ -4,23 +4,20 @@ using AspectedRouting.Language.Typ;
namespace AspectedRouting.Language.Functions
{
/// <summary>
/// Converts a function 'string -> string -> a' onto a function 'tags -> [a]'
/// </summary>
public class StringStringToTagsFunction : Function
{
public override string Description { get; } =
"stringToTags converts a function `string -> string -> a` into a function `tags -> [a]`";
public override List<string> ArgNames { get; } = new List<string>{"f","tags"};
private static Type baseFunction =
private static readonly Type _baseFunction =
Curry.ConstructFrom(new Var("a"), Typs.String, Typs.String);
public StringStringToTagsFunction() : base("stringToTags", true,
new[]
{
new Curry(baseFunction,
new Curry(_baseFunction,
new Curry(Typs.Tags, new ListType(new Var("a"))))
}
)

View file

@ -20,6 +20,9 @@ namespace AspectedRouting.Language.Functions
new Curry(new ListType(Typs.Bool), Typs.Int),
})
{
Funcs.AddBuiltin(this, "plus");
Funcs.AddBuiltin(this, "add");
}

View file

@ -1,23 +0,0 @@
# Functions
- `$funcName` indicates a builtin function
- `#parameter` indicates a configurable parameter
A profile is a function which maps a set of tags onto a value. It is basically Haskell, except that functions can have _multiple_ types. During typechecking, some types will turn out not to be possible and they will dissappear, potentially fixing the implementation of the function itself.
# Profile
- Metdata: these tags will be copied to the routerdb, but can not be used for routeplanning. A prime example is `name` (the streetname), as it is useful for routeplanning but very useful for navigation afterwards
- vehciletypes: used for turn restrictions, legacy for use with itinero 1.0
- defaults: a dictionary of `{"#paramName": "value"}`, used in determining the weight of an edge. note: the `#` has to be included
- `access`, `oneway`, `speed`: three expressions indicating respectively if access is allowed (if not equals to no), in what direction one can drive (one of `with`, `against` or `both`) and how fast one will go there. (Hint: this should be capped on legal_max_speed)
- `weights`: a table of `{'#paramName', expression}` determining the weight (aka COST) of a way, per meter. The formula used is `paramName * expression + paramName0 * expression0 + ...` (`$speed`, `$access` and `$oneway` can be used here to indicate the earlier defined respective aspects). Use a weight == 1 to get the shortest route or `$inv: $speed` to get the fastest route
# Pitfalls
"$all" should not be used together with a mapping: it checks if all _present_ keys return true or yes (or some other value); it does _not_ check that all the specified keys in the mapping are present.
For this, an additional 'mustHaveKeys' should be added added

View file

@ -1,539 +0,0 @@
-- The different profiles
profiles = {
{
name = "b2w",
description = "[Custom] Route for bike2work. Same as 'commute' ATM. Migth diverge in the future. In use.",
function_name = "determine_weights_commute",
metric = "custom"
},
{
name = "networks",
description = "A recreative route following existing cycling networks. Might make longer detours",
function_name = "determine_weights_networks",
metric = "custom"
},
{
name = "node_network",
description = "A recreative route following existing cycle node networks. Might make longer detours",
function_name = "determine_weights_networks_node_network",
metric = "custom"
},
{
name = "cycle_highway",
description = "A functional route, preferring cycle_highways or the Brussels Mobility network. If none are availale, will favour speed",
function_name = "determine_weights_cycle_highways",
metric = "custom"
},
{
name = "commute",
description = "A functional route which is aimed to commuters. It is a mix of safety, comfort, a pinch of speed and cycle_highway",
function_name = "determine_weights_commute",
metric = "custom"
},
{
name = "anyways.network", -- prefers the anyways network, thus where `operator=Anyways` -
description = "A route following the cycle network with the operator 'Anyways'. This is for use in ShortCut/Impact as there are no such networks existing in OSM.",
function_name = "determine_weights_anyways_network",
metric = "custom"
},
{
name = "commute.race",
description = "[Deprecated] Same as commute, please use that one instead. Might be unused",
function_name = "determine_weights_cycle_highways", -- TODO tweak this profile
metric = "custom"
},
{
name = "opa",
description = "[Deprecated][Custom] Same as fastest, please use that one instead. Might be unused. Note: all profiles take anyways:* tags into account",
function_name = "determine_weights",
metric = "custom"
},
}
-- Returns 1 if no access restrictions, 0 if it is permissive
function determine_permissive_score(attributes, result)
if (attributes.access == "permissive") then
return 0
end
return 1
end
--[[ Gives 1 if this is on a cycling network.
If attributes.settings.cycle_network_operator is defined, then only follow these networks
If attributes.settings.cycle_network_highway is defined, then only follow the 'Fietssnelwegen'
The colour _will only be copied_ if the score is one
]]
cycle_network_attributes_to_match = { "cycle_network_highway", "cycle_network_node_network" }
function determine_network_score(attributes, result)
if (attributes.cycle_network == nil) then
return 0
end
for i = 1, #cycle_network_attributes_to_match do
local key = cycle_network_attributes_to_match[i]
local expected = attributes.settings[key]
if (expected ~= nil) then
-- we have to check that this tag matches
local value = attributes[key]
if (value == nil) then
-- the waysegment doesn't have this attribute - abort
return 0
end
if (value ~= expected) then
-- the way segment doesn't have the expected value - abort
return 0
end
-- hooray, we have a match!
result.attributes_to_keep[key] = value
end
end
if (attributes.settings.cycle_network_operator ~= nil) then
local expected = attributes.settings.cycle_network_operator;
expected = expected:gsub(" ", "");
if (attributes[expected] ~= "yes") then
return 0
end
end
result.attributes_to_keep.cycle_network = "yes";
return 1
end
function determine_weights_comfort_safety(attributes, result)
-- we add a 'settings' element to attributes, they can be used by other profiles
if (attributes.settings == nil) then
attributes.settings = {}
attributes.settings.default_speed = 15
attributes.settings.min_speed = 3
attributes.settings.max_speed = 30
attributes.settings.safety_weight = 1
attributes.settings.time_weight = 0
attributes.settings.comfort_weight = 1
attributes.settings.network_weight = 0
attributes.settings.clear_restrictions_preference = 1;
end
determine_weights(attributes, result)
end
function determine_weights_commute(attributes, result)
-- lots of safety and comfort, but also slightly prefers 'fietssnelwegen' and 'Brussels Mobility' 'route cyclable', as they are functional
if (attributes.settings == nil) then
attributes.settings = {}
attributes.settings.default_speed = 15
attributes.settings.min_speed = 3
attributes.settings.max_speed = 30
attributes.settings.clear_restrictions_preference = 1
attributes.settings.safety_weight = 3
attributes.settings.time_weight = 1
attributes.settings.comfort_weight = 2
attributes.settings.network_weight = 3
end
determine_weights(attributes, result)
-- commute is a big exception to the other profiles, as in that we overwrite the result.factor here
-- this is in order to support _two_ cycling networks
local safety = determine_safety(attributes, result);
local comfort = determine_comfort(attributes, result);
attributes.cycle_network = "yes"
attributes.settings.cycle_network_highway = "yes"
local cycle_highway_score = determine_network_score(attributes, result);
attributes.settings.cycle_network_highway = nil
attributes.cycle_network = "yes"
attributes.settings.cycle_network_operator = "Brussels Mobility"
local brussels_mobility_score = determine_network_score(attributes, result);
attributes.settings.cycle_network_operator = nil
local network = math.min(1, cycle_highway_score + brussels_mobility_score);
local clear_restrictions = determine_permissive_score(attributes, result);
result.factor = 1 /
(safety * attributes.settings.safety_weight +
result.speed * attributes.settings.time_weight +
comfort * attributes.settings.comfort_weight +
network * attributes.settings.network_weight +
clear_restrictions * attributes.settings.clear_restrictions_preference);
end
function determine_weights_cycle_highways(attributes, result)
-- heavily prefers 'fietssnelwegen' and 'Brussels Mobility' 'route cyclable', as they are functional
if (attributes.settings == nil) then
attributes.settings = {}
attributes.settings.default_speed = 15
attributes.settings.min_speed = 3
attributes.settings.max_speed = 30
attributes.settings.clear_restrictions_preference = 1
attributes.settings.safety_weight = 1
attributes.settings.time_weight = 0
attributes.settings.comfort_weight = 0
attributes.settings.network_weight = 20
end
determine_weights(attributes, result)
-- commute is a big exception to the other profiles, as in that we overwrite the result.factor here
-- this is in order to support _two_ cycling networks
local safety = determine_safety(attributes, result);
local comfort = determine_comfort(attributes, result);
attributes.cycle_network = "yes"
attributes.settings.cycle_network_highway = "yes"
local cycle_highway_score = determine_network_score(attributes, result);
attributes.settings.cycle_network_highway = nil
attributes.cycle_network = "yes"
attributes.settings.cycle_network_operator = "Brussels Mobility"
local brussels_mobility_score = determine_network_score(attributes, result);
attributes.settings.cycle_network_operator = nil
local network = math.min(1, cycle_highway_score + brussels_mobility_score);
local clear_restrictions = determine_permissive_score(attributes, result);
result.factor = 1 /
(safety * attributes.settings.safety_weight +
result.speed * attributes.settings.time_weight +
comfort * attributes.settings.comfort_weight +
network * attributes.settings.network_weight +
clear_restrictions * attributes.settings.clear_restrictions_preference);
end
function determine_weights_networks(attributes, result)
-- we add a 'settings' element to attributes, they can be used by other profiles
if (attributes.settings == nil) then
attributes.settings = {}
attributes.settings.default_speed = 15
attributes.settings.min_speed = 3
attributes.settings.max_speed = 30
attributes.settings.safety_weight = 1
attributes.settings.time_weight = 0
attributes.settings.comfort_weight = 0
attributes.settings.network_weight = 3
attributes.settings.clear_restrictions_preference = 1;
end
determine_weights(attributes, result)
end
function determine_weights_networks_node_network(attributes, result)
-- we add a 'settings' element to attributes, they can be used by other profiles
if (attributes.settings == nil) then
attributes.settings = {}
attributes.settings.default_speed = 15
attributes.settings.min_speed = 3
attributes.settings.max_speed = 30
attributes.settings.safety_weight = 1
attributes.settings.time_weight = 0
attributes.settings.comfort_weight = 0
attributes.settings.network_weight = 10
attributes.settings.clear_restrictions_preference = 1
attributes.cycle_network = "yes"
attributes.settings.cycle_network_node_network = "yes"
end
determine_weights(attributes, result)
end
function determine_weights_anyways_network(attributes, result)
-- we add a 'settings' element to attributes, they can be used by other profiles
if (attributes.settings == nil) then
attributes.settings = {}
attributes.settings.default_speed = 15
attributes.settings.min_speed = 3
attributes.settings.max_speed = 30
attributes.settings.safety_weight = 0
attributes.settings.time_weight = 0
attributes.settings.comfort_weight = 0
attributes.settings.network_weight = 10
attributes.settings.clear_restrictions_preference = 1;
attributes.settings.cycle_network_operator = "Anyways"
end
attributes.access = nil -- little hack: remove ALL access restrictions in order to go onto private parts
determine_weights(attributes, result)
end
function determine_weights(attributes, result)
-- we add a 'settings' element to attributes, they can be used by other profiles
if (attributes.settings == nil) then
attributes.settings = {}
attributes.settings.default_speed = 15
attributes.settings.min_speed = 3
attributes.settings.max_speed = 30
attributes.settings.safety_weight = 0
attributes.settings.time_weight = 0
attributes.settings.comfort_weight = 0
attributes.settings.network_weight = 0
attributes.settings.clear_restrictions_preference = 1; -- if 1: discourage 'permissive' access tags
attributes.settings.cycle_network_operator = nil -- e.g. "Stad Genk" of "Brussels Mobility"
attributes.settings.cycle_network_highway = nil -- if "yes", will only follow the 'fietsostrades'
attributes.settings.cycle_network_node_network = nil -- if "yes", will only follow the 'node_networks'
end
-- Init default values
result.access = 0
result.speed = 0
result.factor = 1
result.direction = 0
result.canstop = true
result.attributes_to_keep = {}
-- Do misc preprocessing, such as handling the ferry case
preprocess(attributes, result);
-- 1) Can we enter this segment legally?
if (not can_access_legally(attributes)) then
return
end
result.access = 1;
result.attributes_to_keep.highway = attributes.highway
result.attributes_to_keep.access = attributes.access
-- 2) Is this a oneway?
determine_oneway(attributes, result)
-- 3) How fast would one drive on average on this segment?
determine_speed(attributes, result);
-- Cap using settings and legal max speed
result.speed = math.max(attributes.settings.min_speed, result.speed)
result.speed = math.min(attributes.settings.max_speed, result.speed)
local legal_max_speed = highway_types[attributes.highway].speed
result.speed = math.min(legal_max_speed, result.speed)
-- 4) What is the factor of this segment?
--[[
This is determined by multiple factors and the weight that is given to them by the settings
Factors are:
- safety
- comfort
- ...
]]
local safety = determine_safety(attributes, result);
local comfort = determine_comfort(attributes, result);
local network = determine_network_score(attributes, result);
local clear_restrictions = determine_permissive_score(attributes, result);
result.factor = 1 /
(safety * attributes.settings.safety_weight +
result.speed * attributes.settings.time_weight +
comfort * attributes.settings.comfort_weight +
network * attributes.settings.network_weight +
clear_restrictions * attributes.settings.clear_restrictions_preference);
end
-- Unit test: are all tags in profile_whitelist
cycling_network_operators_to_tag = { "Stad Genk", "Anyways", "Brussels Mobility" }
cycling_network_tags = { "cycle_network", "cycle_network_highway", "cycle_network_operator", "cycle_network_colour", "cycle_network_node_network", "operator", "StadGenk", "Anyways", "BrusselsMobility" }
--[[ Copies all relevant relation tags onto the way
All these tags start with 'cycle_network', e.g. cycle_network_colour
Some of them are exclusively for the metadata (cyclecolour as prime example)
Note that all the tags used are listed in 'cycling_network_tags' as well, for unit testing purposes
]]
function cycling_network_tag_processor(attributes, result)
result.attributes_to_keep.cycle_network = "yes"
if (attributes.cycle_network == "cycle_highway"
and attributes.state ~= "proposed") then
result.attributes_to_keep.cycle_network_highway = "yes"
end
if (attributes.cycle_network == "node_network") then
result.cycle_network_node_network = "yes"
end
if (attributes.operator ~= nil) then
for k, v in pairs(cycling_network_operators_to_tag) do
v = v:gsub(" ", "") -- remove spaces from the operator as lua can't handle them
result.attributes_to_keep[v] = "yes"
end
end
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
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(attributes, result)
result.attributes_to_keep = {}
if (attributes.route == "bicycle") then
-- This is a cycling network!
cycling_network_tag_processor(attributes, result)
end
end
-----------------------------------------------------------------------------------------------------------------------
function unit_tests()
unit_test_relation_tag_processor({ route = "bicycle", operator = "Stad Genk", color = "red", type = "route" },
{ StadGenk = "yes", cycle_network_colour = "red", cycle_network = "yes" });
unit_test_relation_tag_processor({ route = "bicycle", cycle_network = "cycle_highway", color = "red", type = "route" },
{ cycle_network_colour = "red", cycle_network = "yes", cycle_network_highway = "yes" });
unit_test_cycle_networks({ highway = "residential", settings = {} }, 0)
unit_test_cycle_networks({ highway = "residential", cycle_network = "yes", settings = {} }, 1)
unit_test_cycle_networks({
highway = "residential",
cycle_network = "yes",
settings = { cycle_network_operator = "Stad Genk" }
}, 0 --[[Not the right network, not Genk]])
unit_test_weights(determine_weights_speed_first, { highway = "residential" }, 0.0625);
unit_test_weights(determine_weights_speed_first, { highway = "cycleway" }, 0.0625); -- unit_test_weights({ highway = "residential", access = "destination", surface = "sett" });
unit_test_weights(determine_weights_speed_first, { highway = "primary", bicycle="yes", surface = "sett" }, 0.068965517241379);
unit_test_weights(determine_weights_speed_first, { highway = "primary" , bicycle="yes"}, 0.0625);
unit_test_weights(determine_weights_safety_first, { highway = "primary" , bicycle="yes"}, 0.76923076923077);
-- regression test
unit_test_weights(determine_weights_safety_first, { ["cycleway:left"] = "track", ["cycleway:left:oneway"] = "no", highway="unclassified", oneway="yes", ["oneway:bicycle"]=no}, 0.45454545454545);
unit_test_weights(determine_weights_speed_first, { ["cycleway:left"] = "track", ["cycleway:left:oneway"] = "no", highway="unclassified", oneway="yes", ["oneway:bicycle"]=no}, 0.0625);
unit_test_weights(determine_weights_genk, { highway = "residential" }, 0.52631578947368);
unit_test_weights(determine_weights_genk,
{ highway = "residential", StadGenk = "yes", cycle_network = "yes" },
0.20408163265306); -- factor = 1 / preference, should be lower
unit_test_weights(determine_weights_genk,
{ highway = "residential", cycle_network_operator = nil, cycle_network = "yes" },
0.52631578947368);
unit_test_weights(determine_weights_genk, {
highway = "residential",
cycle_network = "yes",
cycle_network_operator = "Niet Stad Genk"
}, 0.52631578947368);
unit_test_weights(determine_weights_networks_node_network, {
highway = "residential",
cycle_network = "yes",
cycle_network_operator = "Niet Stad Genk"
}, 0.52631578947368);
unit_test_weights(determine_weights_networks_node_network, {
highway = "residential",
cycle_network = "yes",
cycle_network_node_network = "yes",
}, 0.084033613445378);
unit_test_weights(determine_weights_anyways_network, { highway = "residential", cycle_network = "yes", Anyways = "yes" }, 0.090909090909091);
unit_test_weights(determine_weights_anyways_network, { highway = "residential" }, 1.0);
unit_test_weights(determine_weights_networks_node_network, { highway = "residential" }, 0.52631578947368);
unit_test_weights(determine_weights_networks_node_network, { highway = "residential", cycle_network="yes", cycle_network_node_network="yes" }, 0.084033613445378);
end

View file

@ -1,5 +0,0 @@
access,oneway,speed,priority,highway,_relation:bicycle.network_by_operator
no,both,0,0,,
yes,both,15,1.9,residential,
yes,both,15,6.9,residential,yes
dismount,both,2.25,0.0195,footway,
1 access oneway speed priority highway _relation:bicycle.network_by_operator
2 no both 0 0
3 yes both 15 1.9 residential
4 yes both 15 6.9 residential yes
5 dismount both 2.25 0.0195 footway

View file

@ -1,61 +0,0 @@
{
"name": "bicycle.comfort",
"description": "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]",
"$default": 1,
"value": {
"$multiply": {
"highway": {
"cycleway": 1.2,
"primary": 0.3,
"secondary": 0.4,
"tertiary": 0.5,
"unclassified": 0.8,
"track": 0.95,
"residential": 1.0,
"living_street": 1.1,
"footway": 0.95,
"path": 0.5
},
"railway": {
"abandoned": 2
},
"towpath": {
"yes": 2
},
"cycleway": {
"track": 1.2
},
"cyclestreet": {
"yes": 1.1
},
"access": {
"designated": 1.2,
"dismount": 0.01
},
"surface": {
"#": "The surface mapping heavily resembles the one in speed_factor, but it is not entirely the same",
"paved": 0.99,
"concrete:lanes": 0.8,
"concrete:plates": 1.0,
"sett": 0.9,
"unhewn_cobblestone": 0.75,
"cobblestone": 0.8,
"unpaved": 0.75,
"compacted": 1.1,
"fine_gravel": 0.99,
"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
}
}
}
}

View file

@ -1,18 +0,0 @@
expected,highway,foot,bicycle,cyclestreet,cycleway,cycleway:right,surface,railway,towpath
1,,,,,,,,,
1,residential,,,,,,,,
1.1,residential,,,yes,,,,,
1.2,cycleway,,,,,,,,
1.2,cycleway,designated,,,,,,,
0.5,path,designated,designated,,,,,,
0.5,path,,designated,,,,,,
0.95,footway,designated,,,,,,,
0.3,primary,,,,no,,,,
0.3,primary,,,,yes,,,,
0.36,primary,,,,track,,,,
0.4,secondary,,,,lane,,,,
0.4,secondary,,,,,lane,asphalt,,
1.1,residential,,,yes,,,asphalt,,
2,,,,,,,,abandoned,
2,,,,,,,,,yes
4,,,,,,,,abandoned,yes
1 expected highway foot bicycle cyclestreet cycleway cycleway:right surface railway towpath
2 1
3 1 residential
4 1.1 residential yes
5 1.2 cycleway
6 1.2 cycleway designated
7 0.5 path designated designated
8 0.5 path designated
9 0.95 footway designated
10 0.3 primary no
11 0.3 primary yes
12 0.36 primary track
13 0.4 secondary lane
14 0.4 secondary lane asphalt
15 1.1 residential yes asphalt
16 2 abandoned
17 2 yes
18 4 abandoned yes

View file

@ -1,6 +0,0 @@
access,oneway,speed,priority,highway,_relation:bicycle.network_by_operator,_relation:bicycle.network_is_cyclehighway
no,,,,,,
yes,both,15,1.9,residential,,
yes,both,15,6.9,residential,yes,
yes,both,15,6.9,residential,,yes
yes,both,15,11.9,residential,yes,yes
1 access oneway speed priority highway _relation:bicycle.network_by_operator _relation:bicycle.network_is_cyclehighway
2 no
3 yes both 15 1.9 residential
4 yes both 15 6.9 residential yes
5 yes both 15 6.9 residential yes
6 yes both 15 11.9 residential yes yes

View file

@ -1,4 +0,0 @@
access,oneway,speed,priority,highway,_relation:bicycle.network_score
designated,both,15,22,cycleway,yes
no,,,,,
designated,both,15,2,cycleway,
1 access oneway speed priority highway _relation:bicycle.network_score
2 designated both 15 22 cycleway yes
3 no
4 designated both 15 2 cycleway

View file

@ -1,7 +0,0 @@
access,oneway,speed,priority,highway,bicycle
no,both,0,0,,
designated,both,15,15,cycleway,
no,both,0,0,primary,
dismount,both,2.25,2.25,pedestrian,
yes,both,15,15,pedestrian,yes
yes,both,15,15,residential,
1 access oneway speed priority highway bicycle
2 no both 0 0
3 designated both 15 15 cycleway
4 no both 0 0 primary
5 dismount both 2.25 2.25 pedestrian
6 yes both 15 15 pedestrian yes
7 yes both 15 15 residential

View file

@ -1,107 +0,0 @@
{
"name": "bicycle",
"description": "A simple profile which routes a normal bicycle",
"vehicletypes": [
"vehicle",
"bicycle"
],
"metadata": [
"name",
"bridge",
"tunnel",
"colour",
"cycle_network_colour",
"ref",
"status",
"network"
],
"defaults": {
"#defaultSpeed": 15,
"#maxspeed": 30,
"#timeNeeded": 0,
"#distance": 0,
"#comfort": 0,
"#safety": 0,
"#operatorNetworkScore": 0,
"#networkOperator": [],
"#cycleHighwayNetworkScore": 0
},
"behaviours": {
"fastest": {
"description": "The fastest route to your destination",
"#timeNeeded": 1
},
"shortest": {
"description": "The shortest route, independent of of speed",
"#distance": 1
},
"safety": {
"description": "A defensive route shying away from big roads with lots of cars",
"#safety": 1
},
"comfort": {
"description": "A comfortable route preferring well-paved roads, smaller roads and a bit of scenery at the cost of speed",
"#comfort": 1
},
"comfort_safety": {
"description": "A route which aims to be both safe and comfortable at the cost of speed",
"#comfort": 1,
"#safety": 1
},
"brussels": {
"description": "A route preferring the cycling network by operator 'Brussels Mobility'",
"#operatorNetworkScore": 5,
"#networkOperator": ["Brussels Mobility"],
"#comfort": 1,
"#safety": 1
},
"genk": {
"description": "A route preferring the cycling network by operator 'Stad Genk'",
"#operatorNetworkScore": 3,
"#networkOperator": ["Stad Genk"],
"#comfort": 0,
"#safety": 1
},
"cycle_highway": {
"description": "A route preferring the 'cycle-highways' ",
"#cycleHighwayNetworkScore": 5,
"#comfort": 1,
"#safety": 1
},
"commute": {
"description": "A route preferring the 'cycle-highways' or the cycling network by operator 'Brussels Mobility'",
"#operatorNetworkScore": 5,
"#networkOperator": ["Brussels Mobility"],
"#cycleHighwayNetworkScore": 5,
"#comfort": 1,
"#safety": 1
}
},
"access": "$bicycle.legal_access",
"oneway": "$bicycle.oneway",
"speed": {
"$min": [
"$legal_maxspeed_be",
"#maxspeed",
{
"$multiply": [
"#defaultSpeed",
"$bicycle.speed_factor"
]
}
]
},
"priority": {
"#comfort": "$bicycle.comfort",
"#safety": "$bicycle.safety",
"#operatorNetworkScore": "$bicycle.network_by_operator",
"#cycleHighwayNetworkScore": "$bicycle.network_is_cyclehighway",
"#timeNeeded": "$speed",
"#distance": "$distance"
}
}

View file

@ -1,80 +0,0 @@
{
"name": "bicycle.legal_access",
"description": "Gives, for each type of highway, whether or not a normal bicycle can enter legally.\nNote 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\n'yes': bicycles are allowed here\n'permissive': bicycles are allowed here, but this might be a private road or service where usage is allowed, but uncommon\n'dismount': cycling here is not allowed, but walking with the bicycle is\n'destination': cycling is allowed here, but only if truly necessary to reach the destination\n'private': this is a private road, only go here if the destination is here\n'no': do not cycle here",
"$default": "no",
"value": {
"$firstMatchOf": [
"anyways:bicycle",
"anyways:access",
"anyways:construction",
"bicycle",
"access",
"cycleway:right",
"cycleway:left",
"cycleway",
"highway"
],
"value": {
"access": {
"no": "no",
"customers": "no",
"private": "no",
"#": "Note that we leave out 'yes', as it is meaningless - the default assumption is that everything on OSM is accessible! This forces to fall through to the road type, in order to force `highway=motorway; access=yes` as not to trigger access for bicycles",
"permissive": "permissive",
"destination": "destination",
"delivery": "destination",
"service": "destination"
},
"highway": {
"cycleway": "designated",
"residential": "yes",
"living_street": "yes",
"service": "permissive",
"services": "permissive",
"track": "yes",
"crossing": "dismount",
"footway": "dismount",
"pedestrian": "dismount",
"corridor": "dismount",
"path": "permissive",
"primary": "no",
"primary_link": "no",
"secondary": "yes",
"secondary_link": "yes",
"tertiary": "yes",
"tertiary_link": "yes",
"unclassified": "yes",
"road": "yes"
},
"bicycle": {
"yes": "yes",
"no": "no",
"use_sidepath": "no",
"designated": "designated",
"permissive": "permissive",
"private": "private",
"official": "designated",
"dismount": "dismount"
},
"cycleway:right": {
"$not": "no"
},
"cycleway:left": {
"$not": "no"
},
"cycleway": {
"$not": "no"
},
"anyways:bicycle": "$id",
"anyways:access": {
"no": "no",
"destination": "destination",
"yes": "yes"
},
"anyways:construction": {
"yes": "no"
}
}
}
}

View file

@ -1,20 +0,0 @@
expected,highway,bicycle,access,anyways:access,cycleway:right,cycleway:right
no,,,,,,
no,,,no,,,
yes,,yes,no,,,
permissive,path,,,,,
yes,pedestrian,yes,,,,
dismount,pedestrian,,,,,
designated,cycleway,,,,,
destination,residential,,destination,,,
no,residential,,private,,,
designated,residential,designated,,,,
designated,motorway,designated,,,,
no,residential,use_sidepath,,,,
yes,residential,,no,yes,,
no,primary,,,,,
yes,primary,,,,yes,
yes,primary,,,,,yes
yes,secondary,,,,track,
destination,service,,destination,,,
no,residential,use_sidepath,,,,,
Can't render this file because it has a wrong number of fields in line 20.

View file

@ -1,13 +0,0 @@
{
"name": "bicycle.network_by_operator",
"description": "The 'bicycle.network_score' returns true if the way is part of a cycling network of a certain (group of) operators",
"$memberOf": {
"$mustMatch": {
"type": "route",
"route": "bicycle",
"state": {"$not": "proposed"},
"operator": {"$containedIn": "#networkOperator"}
}
}
}

View file

@ -1,13 +0,0 @@
{
"name": "bicycle.network_is_cyclehighway",
"description": "Returns true if the highway is part of a [cycle highway](https://wiki.openstreetmap.org/wiki/Tag:cycle_network%3Dcycle_highway)",
"$memberOf": {
"$mustMatch": {
"type": "route",
"route": "bicycle",
"state": {"$not": "proposed"},
"cycle_network": "cycle_highway"
}
}
}

View file

@ -1,12 +0,0 @@
{
"name": "bicycle.network_score",
"description": "The 'bicycle.network_score' returns true if the way is part of a cycling network",
"$memberOf": {
"$mustMatch": {
"type": "route",
"route": "bicycle",
"state": {"$notEq": "proposed"}
}
}
}

View file

@ -1,50 +0,0 @@
{
"name": "bicycle.oneway",
"description": "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\nwith: this is a oneway street with direction allowed with the grain of the way\nagainst: oneway street with direction against the way",
"$default": "both",
"value": {
"$firstMatchOf": [
"oneway:bicycle",
"junction",
"cycleway",
"cycleway:left",
"oneway"
],
"value": {
"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",
"#": "We ignore 'no' as it has no meaning and is the default assumption",
"opposite_lane": "both",
"track": "both",
"lane": "both",
"opposite": "both",
"opposite_share_busway": "both",
"opposite_track": "both"
},
"cycleway:left": {
"no": "with",
"yes": "both",
"lane": "both",
"track": "both",
"shared_lane": "both",
"share_busway": "both"
}
}
}
}

View file

@ -1,19 +0,0 @@
expected,highway,oneway,oneway:bicycle,junction,cycleway,cycleway:right,cycleway:left
both,,,,,,,
both,,no,,,,,
with,,no,yes,,,,
with,,,,roundabout,,,
both,,yes,,,opposite,,
against,,yes,-1,,,,
with,residential,yes,,,,,
both,residential,no,,,,,
both,residential,yes,no,,,,
with,residential,,,roundabout,,,
both,residential,,no,roundabout,,,
against,residential,,-1,,,,
both,residential,invalidKey,no,,,,
with,secondary,yes,,,,track,
both,secondary,yes,,,,,track
both,secondary,yes,,,track,,
with,,yes,,,,,no
both,residential,yes,,,,,lane
1 expected highway oneway oneway:bicycle junction cycleway cycleway:right cycleway:left
2 both
3 both no
4 with no yes
5 with roundabout
6 both yes opposite
7 against yes -1
8 with residential yes
9 both residential no
10 both residential yes no
11 with residential roundabout
12 both residential no roundabout
13 against residential -1
14 both residential invalidKey no
15 with secondary yes track
16 both secondary yes track
17 both secondary yes track
18 with yes no
19 both residential yes lane

View file

@ -1,72 +0,0 @@
{
"name": "bicycle.safety",
"description": "Determines how safe a cyclist feels on a certain road, mostly based on car pressure. This is a relatively ",
"unit": "safety",
"$default": 1,
"value": {
"$multiply": {
"access": {
"#": "access=no implies no access for cars too!",
"no": 1.1,
"dismount": 0.01
},
"motor_vehicle": {
"no": 1.5
},
"foot": {
"designated": 0.95
},
"bicycle": {
"designated": 1.5
},
"cyclestreet": {
"yes": 1.5
},
"towpath": {
"yes": 1.1
},
"designation": {
"towpath": 1.5
},
"highway": {
"cycleway": 1.0,
"primary": 0.3,
"secondary": 0.4,
"tertiary": 0.5,
"unclassified": 0.8,
"track": 0.95,
"residential": 0.9,
"living_street": 1,
"footway": 1,
"path": 1
},
"cycleway": {
"yes": 0.95,
"no": 0.5,
"lane": 1,
"shared": 0.8,
"shared_lane": 0.8,
"share_busway": 0.9,
"track": 1.5
},
"cycleway:left": {
"yes": 0.95,
"no": 0.5,
"lane": 1,
"shared": 0.8,
"shared_lane": 0.8,
"share_busway": 0.9,
"track": 1.5
},
"cycleway:right": {
"yes": 0.95,
"no": 0.5,
"lane": 1,
"shared": 0.8,
"shared_lane": 0.8,
"share_busway": 0.9,
"track": 1.5
}
}
}
}

View file

@ -1,13 +0,0 @@
expected,highway,cycleway,cyclestreet,foot,bicycle,cycleway:right
0.15,primary,no,,,,
0.285,primary,yes,,,,
0.45,primary,track,,,,
0.4,secondary,lane,,,,
0.9,residential,,,,,
1,cycleway,,,,,
1.35,residential,,yes,,,
0.95,cycleway,,,designated,,
0.95,footway,,,designated,,
1.425,path,,,designated,designated,
1.5,path,,,,designated,
0.4,secondary,,,,,lane
1 expected highway cycleway cyclestreet foot bicycle cycleway:right
2 0.15 primary no
3 0.285 primary yes
4 0.45 primary track
5 0.4 secondary lane
6 0.9 residential
7 1 cycleway
8 1.35 residential yes
9 0.95 cycleway designated
10 0.95 footway designated
11 1.425 path designated designated
12 1.5 path designated
13 0.4 secondary lane

View file

@ -1,62 +0,0 @@
{
"name": "bicycle.speed_factor",
"description": "Calculates a speed factor for bicycles based on physical features, e.g. a sand surface will slow a cyclist down; going over pedestrian areas even more, ...",
"$multiply": {
"access": {
"#": "We have to go by foot. Default speed of 20km/h * 0.15 = 3km/h",
"dismount": 0.15
},
"highway": {
"#": "A small forest path is typically slow",
"path": 0.5,
"#": "an unmaintained track (in Belgium: tractor path) is slower as well",
"track": 0.7
},
"surface": {
"paved": 0.99,
"asphalt": 1,
"concrete": 1,
"metal": 1,
"wood": 1,
"concrete:lanes": 0.95,
"concrete:plates": 1,
"paving_stones": 1,
"sett": 0.9,
"unhewn_cobblestone": 0.75,
"cobblestone": 0.8,
"unpaved": 0.75,
"compacted": 0.99,
"fine_gravel": 0.99,
"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
},
"tracktype": {
"grade1": 0.99,
"grade2": 0.8,
"grade3": 0.6,
"grade4": 0.3,
"grade5": 0.1
},
"incline": {
"up": 0.75,
"down": 1.25,
"0": 1,
"0%": 1,
"10%": 0.9,
"-10%": 1.1,
"20%": 0.8,
"-20%": 1.2,
"30%": 0.7,
"-30%": 1.3
}
}
}

View file

@ -1,12 +0,0 @@
expected,incline,surface,highway,access
1,,,,
1,,,residential,
0.75,up,,residential,
1.25,down,,residential,
0.3,up,mud,residential,
1.125,down,sett,residential,
0.675,up,sett,residential,
0.9,,sett,residential,
1,,asphalt,residential,
0.15,,,residential,dismount
0.0315,up,mud,track,dismount
1 expected incline surface highway access
2 1
3 1 residential
4 0.75 up residential
5 1.25 down residential
6 0.3 up mud residential
7 1.125 down sett residential
8 0.675 up sett residential
9 0.9 sett residential
10 1 asphalt residential
11 0.15 residential dismount
12 0.0315 up mud track dismount

View file

@ -1,37 +0,0 @@
{
"name": "small",
"description": "A minimal example, to start designing a profile or to debug",
"vehicletypes": [
"vehicle",
"bicycle"
],
"metadata": [
"name"
],
"defaults": {
"#defaultSpeed": 15,
"#distance": 0,
"#cycleHighwayNetworkScore": 0
},
"behaviours": {
"shortest": {
"description": "The shortest route, independent of of speed",
"#distance": 1
},
"fastest": {
"description": "The shortest route, independent of of speed",
"#distance": 1
},
"commute": {
"description": "The shortest route, independent of of speed",
"#cycleHighwayNetworkScore": 3
}
},
"access": "$bicycle.legal_access",
"oneway": "$bicycle.oneway",
"speed": "#defaultSpeed",
"priority": {
"#distance": "$distance",
"#cycleHighwayNetworkScore": "$bicycle.network_is_cyclehighway"
}
}

View file

@ -1,44 +0,0 @@
{
"name": "legal_maxspeed_be",
"description": "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",
"$default": 30,
"value": {
"$firstMatchOf": [
"maxspeed",
"designation",
"highway",
"ferry"
],
"value": {
"maxspeed": "$parse",
"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
},
"ferry": 5
}
}
}

View file

@ -13,21 +13,22 @@ namespace AspectedRouting
{
static class Program
{
public static List<(AspectMetadata aspect, FunctionTestSuite tests)> ParseAspects(
this List<string> jsonFileNames, Context context)
public static IEnumerable<(AspectMetadata aspect, AspectTestSuite tests)> ParseAspects(
this IEnumerable<string> jsonFileNames, Context context)
{
var aspects = new List<(AspectMetadata aspect, FunctionTestSuite tests)>();
var aspects = new List<(AspectMetadata aspect, AspectTestSuite tests)>();
foreach (var file in jsonFileNames)
{
var fi = new FileInfo(file);
Console.WriteLine("Parsing " + file);
var aspect = JsonParser.AspectFromJson(context, File.ReadAllText(file), fi.Name);
if (aspect != null)
{
var testPath = fi.DirectoryName + "/" + aspect.Name + ".test.csv";
FunctionTestSuite tests = null;
AspectTestSuite tests = null;
if (File.Exists(testPath))
{
tests = FunctionTestSuite.FromString(aspect, File.ReadAllText(testPath));
tests = AspectTestSuite.FromString(aspect, File.ReadAllText(testPath));
}
aspects.Add((aspect, tests));
@ -38,7 +39,7 @@ namespace AspectedRouting
}
private static LuaPrinter GenerateLua(Context context,
List<(AspectMetadata aspect, FunctionTestSuite tests)> aspects,
IEnumerable<(AspectMetadata aspect, AspectTestSuite tests)> aspects,
ProfileMetaData profile, List<ProfileTestSuite> profileTests)
{
var luaPrinter = new LuaPrinter(context);
@ -69,7 +70,7 @@ namespace AspectedRouting
return luaPrinter;
}
private static List<(ProfileMetaData profile, List<ProfileTestSuite> profileTests)> ParseProfiles(
private static IEnumerable<(ProfileMetaData profile, List<ProfileTestSuite> profileTests)> ParseProfiles(
IEnumerable<string> jsonFiles, Context context)
{
var result = new List<(ProfileMetaData profile, List<ProfileTestSuite> profileTests)>();
@ -90,7 +91,7 @@ namespace AspectedRouting
var profileTests = new List<ProfileTestSuite>();
foreach (var behaviourName in profile.Behaviours.Keys)
{
var path = profileFi.DirectoryName + "/" + profile.Name + "." + behaviourName + ".csv";
var path = profileFi.DirectoryName + "/" + profile.Name + "." + behaviourName + ".behaviour_test.csv";
if (File.Exists(path))
{
var test = ProfileTestSuite.FromString(context, profile, behaviourName,
@ -107,7 +108,8 @@ namespace AspectedRouting
}
catch (Exception e)
{
PrintError(jsonFile, e);
// PrintError(jsonFile, e);
throw new Exception("In the file " + jsonFile, e);
}
}
@ -116,18 +118,35 @@ namespace AspectedRouting
private static void PrintError(string file, Exception exception)
{
Console.WriteLine($"Error in the file {file}:\n {exception.Message}");
var msg = exception.Message;
while (exception.InnerException != null)
{
exception = exception.InnerException;
msg += "\n " + exception.Message;
}
Console.WriteLine($"Error in the file {file}:\n {msg}");
}
public static void Main(string[] args)
{
MdPrinter.GenerateHelpText("IO/md/helpText.md");
if (args.Length < 2)
{
Console.WriteLine("Usage: <directory where all aspects and profiles can be found> <outputdirectory>");
return;
}
var inputDir = args[0];
var outputDir = args[1];
MdPrinter.GenerateHelpText(outputDir + "helpText.md");
var files = Directory.EnumerateFiles("Profiles", "*.json", SearchOption.AllDirectories)
var files = Directory.EnumerateFiles(inputDir, "*.json", SearchOption.AllDirectories)
.ToList();
var context = new Context();
var aspects = ParseAspects(files, context);
@ -161,7 +180,7 @@ namespace AspectedRouting
}
var luaPrinter = GenerateLua(context, aspects, profile, profileTests);
File.WriteAllText(profile.Name + ".lua", luaPrinter.ToLua());
File.WriteAllText(outputDir + "/" + profile.Name + ".lua", luaPrinter.ToLua());
}
}
}

View file

@ -7,12 +7,12 @@ using AspectedRouting.Language.Functions;
namespace AspectedRouting.Tests
{
public class FunctionTestSuite
public class AspectTestSuite
{
public readonly AspectMetadata FunctionToApply;
public readonly IEnumerable<(string expected, Dictionary<string, string> tags)> Tests;
public static FunctionTestSuite FromString(AspectMetadata function, string csvContents)
public static AspectTestSuite FromString(AspectMetadata function, string csvContents)
{
var all = csvContents.Split("\n").ToList();
var keys = all[0].Split(",").ToList();
@ -42,10 +42,10 @@ namespace AspectedRouting.Tests
tests.Add((expected, tags));
}
return new FunctionTestSuite(function, tests);
return new AspectTestSuite(function, tests);
}
public FunctionTestSuite(
public AspectTestSuite(
AspectMetadata functionToApply,
IEnumerable<(string expected, Dictionary<string, string> tags)> tests)
{
@ -84,7 +84,7 @@ namespace AspectedRouting.Tests
{
failed = true;
Console.WriteLine(
$"[{FunctionToApply.Name}] Testcase {testCase} failed:\n Expected: {test.expected}\n actual: {actual}\n tags: {test.tags.Pretty()}");
$"[{FunctionToApply.Name}] Line {testCase+1} failed:\n Expected: {test.expected}\n actual: {actual}\n tags: {test.tags.Pretty()}\n");
}
}

View file

@ -148,11 +148,11 @@ namespace AspectedRouting.Tests
{
c = new Context(c);
tags = new Dictionary<string, string>(tags);
void Err(string message, object exp, object act)
{
Console.WriteLine(
$"[{Profile.Name}.{BehaviourName}]: Test {i} failed: {message}; expected {exp} but got {act}\n {{{tags.Pretty()}}}");
$"[{Profile.Name}.{BehaviourName}]: Test on line {i + 1} failed: {message}; expected {exp} but got {act}\n {{{tags.Pretty()}}}");
}
var success = true;
@ -183,12 +183,13 @@ namespace AspectedRouting.Tests
var speed = (double) Profile.Speed.Run(c, tags);
tags["speed"] = "" + speed;
c.AddFunction("speed", new AspectMetadata(new Constant(Typs.Double, speed),
"speed", "Actual speed of this function", "NA","NA","NA", true));
c.AddFunction("oneway", new AspectMetadata(new Constant(Typs.String, oneway),
"oneway", "Actual direction of this function", "NA","NA","NA", true));
c.AddFunction("access", new AspectMetadata(new Constant(Typs.String, canAccess),
"access", "Actual access of this function", "NA","NA","NA", true));
c.AddFunction("speed", new AspectMetadata(new Constant(Typs.Double, speed),
"speed", "Actual speed of this function", "NA", "NA", "NA", true));
c.AddFunction("oneway", new AspectMetadata(new Constant(Typs.String, oneway),
"oneway", "Actual direction of this function", "NA", "NA", "NA", true));
c.AddFunction("access", new AspectMetadata(new Constant(Typs.String, canAccess),
"access", "Actual access of this function", "NA", "NA", "NA", true));
if (Math.Abs(speed - expected.Speed) > 0.0001)
{
@ -198,6 +199,7 @@ namespace AspectedRouting.Tests
var priority = 0.0;
var weightExplanation = new List<string>();
foreach (var (paramName, expression) in Profile.Priority)
{
var aspectInfluence = (double) c.Parameters[paramName].Evaluate(c);
@ -210,7 +212,7 @@ namespace AspectedRouting.Tests
var aspectWeightObj = new Apply(
Funcs.EitherFunc.Apply(Funcs.Id, Funcs.Const, expression)
, new Constant((tags))).Evaluate(c);
, new Constant(tags)).Evaluate(c);
double aspectWeight;
switch (aspectWeightObj)
@ -241,15 +243,22 @@ namespace AspectedRouting.Tests
throw new Exception($"Invalid value as result for {paramName}: got object {aspectWeightObj}");
}
weightExplanation.Add($"({paramName} = {aspectInfluence}) * {aspectWeight}");
priority += aspectInfluence * aspectWeight;
}
if (Math.Abs(priority - expected.Weight) > 0.0001)
{
Err("weight incorrect", expected.Weight, priority);
Err($"weight incorrect. Calculation is {string.Join(" + ", weightExplanation)}", expected.Weight,
priority);
success = false;
}
if (!success)
{
Console.WriteLine();
}
return success;
}
@ -292,7 +301,7 @@ namespace AspectedRouting.Tests
}
else
{
Console.WriteLine($"[{BehaviourName}] {Tests.Count()} tests successful");
Console.WriteLine($"[{Profile.Name}] {Tests.Count()} tests successful for behaviour {BehaviourName}");
}
}
}