Add unfolded generation of lua for better performance

This commit is contained in:
Pieter Vander Vennet 2021-03-05 14:48:42 +01:00
parent aae20662e2
commit 3f0793ca22
42 changed files with 7629 additions and 325 deletions

View file

@ -1,16 +0,0 @@
using Xunit;
namespace AspectedRouting.Test
{
public class ExamplesTest
{
[Fact]
public void Integration_TestExamples()
{
var input = "./Examples/";
var output = "./output/";
var err = Program.MainWithError(new[] {input, output, "--no-repl"});
Assert.Null(err);
}
}
}

View file

@ -368,8 +368,18 @@ namespace AspectedRouting.Test
var c = new Context();
var result = f.Evaluate(c, new Constant("01:15"));
Assert.Equal(75, result);
Assert.Equal(75.0, result);
}
[Fact]
public void ApplyDefaultFunctionWithId_ApplicationIsSuccessfull()
{
var e = new Apply(new Apply(Funcs.Default, new Constant("a")), Funcs.Id);
Assert.Single(e.Types);
Assert.Equal("string -> string", e.Types.First().ToString());
}
}
}

View file

@ -0,0 +1,108 @@
using System.Collections.Generic;
using AspectedRouting.IO.LuaSkeleton;
using AspectedRouting.IO.LuaSnippets;
using AspectedRouting.Language;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
using Xunit;
namespace AspectedRouting.Test.Snippets
{
public class SnippetTests
{
[Fact]
public void DefaultSnippet_SimpleDefault_GetsLua()
{
var gen = new DefaultSnippet();
var lua = new LuaSkeleton(new Context());
var code = gen.Convert(lua, "result", new List<IExpression> {
new Constant("the_default_value"),
Funcs.Id,
new Constant("value")
});
Assert.Contains("if (result == nil) then\n result = \"the_default_value\"", code);
}
[Fact]
public void FirstOfSnippet_SimpleFirstOf_GetLua()
{
var gen = new FirstMatchOfSnippet();
var lua = new LuaSkeleton(new Context());
// FirstMatchOf: [a] -> (Tags -> [a]) -> Tags -> a
// Order: [string]
var order = new Constant(new List<IExpression> {
new Constant("bicycle"),
new Constant("access")
});
// Func: (Tags -> [a])
var func = new Apply(
Funcs.StringStringToTags,
new Mapping(
new[] {"bicycle", "access"},
new IExpression[] {
Funcs.Id,
Funcs.Id
}
)
);
var tags = new LuaLiteral(new[] {Typs.Tags}, "tags");
var code = gen.Convert(lua, "result",
new List<IExpression> {
order,
func,
tags
}
);
Assert.Equal(
"if (tags[\"bicycle\"] ~= nil) then\n result = tags[\"bicycle\"]\nelseif (tags[\"access\"] ~= nil) then\n result = tags[\"access\"]\nend\n",
code);
}
[Fact]
public void SimpleMappingSnippet_SimpleMapping_GeneratesLua()
{
var mapping = new Mapping(
new[] {"1", "-1"},
new IExpression[] {
new Constant("with"),
new Constant("against")
}
);
var gen = new SimpleMappingSnippet(mapping);
var code = gen.Convert(new LuaSkeleton(new Context()), "result", new List<IExpression> {
new LuaLiteral(Typs.String, "tags.oneway")
});
var expected =
"local v\nv = tags.oneway\nif (v == \"1\") then\n result = \"with\"\nelseif (v == \"-1\") then\n result = \"against\"\nend";
Assert.Equal(expected, code);
}
[Fact]
public void MultiplySnippet_SimpleMultiply_GeneratesLua()
{
var gen = new MultiplySnippet();
var f = new Apply(
Funcs.Multiply,
null
);
var code = gen.Convert(new LuaSkeleton(new Context()),
"result", new List<IExpression>()
);
var expected =
"local v\nv = tags.oneway\nif (v == \"1\") then\n result = \"with\"\nelseif (v == \"-1\") then\n result = \"against\"\nend";
Assert.Equal(expected, code);
}
}
}

View file

@ -79,10 +79,11 @@ namespace AspectedRouting.Test
public void EitherFunc_SpecializeToString_Const()
{
var a = new Constant("a");
var mconst = new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.Const);
var specialized = new Apply(mconst, a).Specialize(Typs.String);
Assert.Equal("((($const $id) $const) \"a\")", specialized.ToString());
Assert.Equal("((($firstArg $id) $firstArg) \"a\")", specialized.ToString());
Assert.Equal("string; $b -> string", string.Join("; ", new Apply(mconst, a).Types));
Assert.Equal("\"a\"", specialized.Evaluate(null).Pretty());

View file

@ -0,0 +1,37 @@
using System.Linq;
using AspectedRouting.Language;
using AspectedRouting.Language.Typ;
using Xunit;
namespace AspectedRouting.Test
{
public class TypingTests
{
[Fact]
public void JoinApply_Id()
{
var tp =
new Curry(
new Curry(
new Var("x"), Typs.String
), new Curry(
new Var("x"), Typs.String
)
);
Assert.Equal("($x -> string) -> $x -> string", tp.ToString());
/*
* ($x -> string) -> $x -> string
* ($a -> $a )
* should give the unification table
* ($x --> string)
*/
var unificationTable = tp.ArgType.UnificationTable(Funcs.Id.Types.First());
Assert.Equal("string", unificationTable["$a"].ToString());
Assert.Equal("string", unificationTable["$x"].ToString());
Assert.Equal(2, unificationTable.Count);
}
}
}

View file

@ -1,8 +1,17 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=a6a74f48_002D8456_002D43c7_002Dbbee_002Dd3da33a8a4be/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Integration_TestExamples" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=3112587d_002D1c06_002D4ad3_002Da845_002D73105d4b723a/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="DefaultSnippet_SimpleDefault_GetsLua" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.ExamplesTest.Integration_TestExamples&lt;/TestId&gt;
&lt;TestId&gt;xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.Snippets.SnippetTests&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=5e89d9f5_002D24ed_002D4ea2_002Da7ea_002Dcc7faea6d057/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="JoinApply_Id" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.TypingTests.JoinApply_Id&lt;/TestId&gt;
&lt;TestId&gt;xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.FunctionsTest.ApplyDefaultFunctionWithId_ApplicationIsSuccessfull&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=a6a74f48_002D8456_002D43c7_002Dbbee_002Dd3da33a8a4be/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="Integration_TestExamples" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Project Location="/home/pietervdvn/git/AspectedRouting/AspectedRouting.Test" Presentation="&amp;lt;AspectedRouting.Test&amp;gt;" /&gt;
&lt;/SessionState&gt;</s:String>
</wpf:ResourceDictionary>

View file

@ -10,6 +10,11 @@ namespace AspectedRouting.IO.LuaSkeleton
public readonly string Lua;
public IEnumerable<Type> Types { get; }
public LuaLiteral(Type type, string lua):this(new [] {type}, lua)
{
}
public LuaLiteral(IEnumerable<Type> types, string lua)
{
Lua = lua;
@ -31,11 +36,6 @@ namespace AspectedRouting.IO.LuaSkeleton
return this;
}
public IExpression OptimizeWithArgument(IExpression argument)
{
return this.Apply(argument);
}
public void Visit(Func<IExpression, bool> f)
{
throw new NotImplementedException();

View file

@ -1,8 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.IO.itinero1;
using AspectedRouting.IO.LuaSnippets;
using AspectedRouting.Language;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Typ;
namespace AspectedRouting.IO.LuaSkeleton
{
@ -37,7 +39,14 @@ namespace AspectedRouting.IO.LuaSkeleton
return true;
});
var impl = string.Join("\n",
var expression = meta.ExpressionImplementation;
if (expression.Types.First() is Curry c) {
expression = expression.Apply(new LuaLiteral(Typs.Tags, "tags"));
}
var ctx = Context;
this._context = _context.WithAspectName(meta.Name);
var impl = string.Join("\n",
"--[[",
meta.Description,
"",
@ -50,10 +59,13 @@ namespace AspectedRouting.IO.LuaSkeleton
"Returns values: ",
"]]",
"function " + meta.Name.AsLuaIdentifier() + "(parameters, tags, result)" + funcNameDeclaration,
" return " + ToLua(meta.ExpressionImplementation),
" local result",
" "+Snippets.Convert(this, "result", expression).Indent(),
" return result" ,
"end"
);
this._context = ctx;
_functionImplementations.Add(impl);
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -16,7 +17,7 @@ namespace AspectedRouting.IO.LuaSkeleton
private readonly HashSet<string> _alreadyAddedFunctions = new HashSet<string>();
private readonly List<string> _constants = new List<string>();
private readonly Context _context;
private Context _context;
private readonly HashSet<string> _dependencies = new HashSet<string>();
private readonly List<string> _functionImplementations = new List<string>();
@ -29,6 +30,8 @@ namespace AspectedRouting.IO.LuaSkeleton
/// </summary>
private readonly bool _staticTables;
public Context Context => _context;
public LuaSkeleton(Context context, bool staticTables = false)
{
_context = context;
@ -37,6 +40,9 @@ namespace AspectedRouting.IO.LuaSkeleton
internal void AddDep(string name)
{
if (name.StartsWith("mapping")) {
throw new Exception("A mapping was added as dependency - this is a bug");
}
_dependencies.Add(name);
}
@ -90,5 +96,19 @@ namespace AspectedRouting.IO.LuaSkeleton
{
return _constants.Select((c, i) => $"c{i} = {c}");
}
private readonly Dictionary<string, uint> counters = new Dictionary<string, uint>();
public string FreeVar(string key)
{
if (!counters.ContainsKey(key)) {
counters[key] = 0;
return key;
}
var i = counters[key];
counters[key]++;
return key + i;
}
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using AspectedRouting.Language;
namespace AspectedRouting.IO.LuaSnippets
{
public class DefaultSnippet : LuaSnippet
{
public DefaultSnippet() : base(Funcs.Default) { }
public override string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, List<IExpression> args)
{
var defaultValue = args[0];
var func = args[1];
var funcArg = args[2];
return Snippets.Convert(lua, assignTo, func.Apply(funcArg))
+ "\n"
+"if ("+assignTo+" == nil) then\n"
+ " " + assignTo + " = " + lua.ToLua(defaultValue)+"\n"
+"end";
}
}
}

View file

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.IO.LuaSkeleton;
using AspectedRouting.Language;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
using static AspectedRouting.Language.Deconstruct;
namespace AspectedRouting.IO.LuaSnippets
{
public class FirstMatchOfSnippet : LuaSnippet
{
public FirstMatchOfSnippet() : base(Funcs.FirstOf) { }
public override string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, List<IExpression> args)
{
var c = lua.Context;
if (!(args[0].Evaluate(c) is List<IExpression> order)) {
return null;
}
List<Mapping> mappings = new List<Mapping>();
if (!UnApply(
IsFunc(Funcs.StringStringToTags),
IsMapping(mappings)
).Invoke(args[1])) {
return null;
}
if (mappings.Count != 1) {
throw new Exception("Multiple possible implementations at this point - should not happen");
}
if (mappings.Count == 0) {
}
var mapping = mappings.First();
var tags = args[2];
var varName = "tags";
var result = "";
if (tags is LuaLiteral literal) {
varName = literal.Lua;
}
else {
result += Snippets.Convert(lua, "tags", tags);
}
// We _reverse_ the order, so that the _most_ important one is at the _bottom_
// The most important one will then _overwrite_ the result value
order.Reverse();
foreach (var t in order) {
if (!(t.Evaluate(c) is string key)) {
return null;
}
var func = mapping.StringToResultFunctions[key];
result += "if (" + varName + "[\"" + key + "\"] ~= nil) then\n";
result += " "+Snippets.Convert(lua, assignTo, func.Apply(new LuaLiteral(Typs.String, "tags[\""+key+"\"]"))).Indent();
result += "\n";
result += "end\n";
// note: we do not do an 'elseif' as we have to fallthrough
if (result.Contains("tags[\"nil\"]")) {
Console.WriteLine("EUHM");
}
}
return result;
}
}
}

View file

@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.IO.LuaSkeleton;
using AspectedRouting.Language;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
using static AspectedRouting.Language.Deconstruct;
namespace AspectedRouting.IO.LuaSnippets
{
public class HeadSnippet : LuaSnippet
{
public HeadSnippet() : base(Funcs.Head) { }
public override string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, List<IExpression> args)
{
var actualArgs = new List<IExpression>();
var mappings = new List<Mapping>();
if (UnApply(
UnApply(IsFunc(Funcs.StringStringToTags),
IsMapping(mappings)),
Assign(actualArgs)
).Invoke(args[0])) {
var actualArg = actualArgs.First();
var mapping = mappings.First();
if (mapping.StringToResultFunctions.Count != 1) {
return null;
}
var (key, func) = mapping.StringToResultFunctions.ToList().First();
var result = "";
var tags = "";
if (actualArg is LuaLiteral l) {
tags = l.Lua;
}
else {
tags = lua.FreeVar("tags");
result += "local " + tags+"\n";
result += Snippets.Convert(lua, tags, actualArg);
}
var v = lua.FreeVar("value");
result += "local " + v + " = " + tags + "[\"" + key + "\"]\n";
result += Snippets.Convert(lua, assignTo, func.Apply(new LuaLiteral(Typs.String, v)));
return result;
}
return null;
}
}
}

View file

@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language;
using AspectedRouting.Language.Typ;
namespace AspectedRouting.IO.LuaSnippets
{
public class IfThenElseDottedSnippet : LuaSnippet
{
public IfThenElseDottedSnippet() : base(Funcs.IfDotted) { }
public override string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, List<IExpression> args)
{
var fCond = args[0];
var fValue = args[1];
IExpression fElse = null;
var arg = args[2];
if (args.Count == 4) {
arg = args[3];
fElse = args[2];
}
var c = lua.FreeVar("cond");
var result = "";
result += "local " + c+"\n";
var condApplied = fCond.Apply(arg);
var isString = condApplied.Types.First().Equals(Typs.String);
result += Snippets.Convert(lua, c, condApplied)+"\n";
result += "if ( "+c + (isString ? " == \"yes\"" : "") + " ) then \n";
result += " " + Snippets.Convert(lua, assignTo, fValue.Apply(arg)).Indent() ;
if (fElse != null) {
result += "else\n";
result += " " + Snippets.Convert(lua, assignTo, fElse.Apply(arg)).Indent();
}
result += "end\n";
return result;
}
}
}

View file

@ -0,0 +1,17 @@
using System.Collections.Generic;
using AspectedRouting.Language;
namespace AspectedRouting.IO.LuaSnippets
{
public class InvSnippet : LuaSnippet
{
public InvSnippet() : base(Funcs.Inv) { }
public override string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, List<IExpression> args)
{
var result = Snippets.Convert(lua, assignTo, args[0]);
result += " "+ assignTo +" = 1 / " + assignTo;
return result;
}
}
}

View file

@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.IO.LuaSkeleton;
using AspectedRouting.Language;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
using static AspectedRouting.Language.Deconstruct;
namespace AspectedRouting.IO.LuaSnippets
{
public abstract class ListFoldingSnippet : LuaSnippet
{
private readonly string _neutralValue;
public ListFoldingSnippet(Function f, string neutralValue) : base(f)
{
_neutralValue = neutralValue;
}
public override string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, List<IExpression> args)
{
// Multiply multiplies a list of values - we thus have to handle _each_ arg
// Note: we get a single argument which is an expression resulting in a list of values
var listToMultiply = args[0];
var mappings = new List<Mapping>();
var arg = new List<IExpression>();
if (UnApply(UnApply(
IsFunc(Funcs.StringStringToTags),
IsMapping(mappings)),
Assign(arg)
).Invoke(listToMultiply)) {
var mapping = mappings.First();
var result = assignTo + " = " + _neutralValue + "\n";
var mappingArg = arg.First();
if (!Equals(mappingArg.Types.First(), Typs.Tags)) {
return null;
}
string tags;
if (mappingArg is LuaLiteral literal) {
tags = literal.Lua;
}
else {
tags = lua.FreeVar("tags");
result += "local " + tags + "\n";
result += Snippets.Convert(lua, tags, mappingArg);
}
var m = lua.FreeVar("m");
result += " local " + m + "\n";
foreach (var (key, func) in mapping.StringToResultFunctions) {
result += "if (" + tags + "[\"" + key + "\"] ~= nil) then\n";
result += m + " = nil\n";
result += " " +
Snippets.Convert(lua, m,
func.Apply(new LuaLiteral(Typs.String, tags + "[\"" + key + "\"]"))).Indent() + "\n";
result += "\n\n if (" + m + " ~= nil) then\n " +
Combine(assignTo, m) +
"\n end\n";
result += "end\n";
}
return result;
}
var listDotArgs = new List<IExpression>();
if (UnApply(
UnApply(IsFunc(Funcs.ListDot),
Assign(listDotArgs)),
Assign(arg)
).Invoke(listToMultiply)) {
var listDotArg = arg.First();
if (!(listDotArgs.First().Evaluate(lua.Context) is List<IExpression> functionsToApply)) {
return null;
}
var result = " " + assignTo + " = " + _neutralValue + "\n";
string tags;
if (listDotArg is LuaLiteral literal) {
tags = literal.Lua;
}
else {
tags = lua.FreeVar("tags");
result += " local " + tags + "\n";
result += Snippets.Convert(lua, tags, listDotArg);
}
var m = lua.FreeVar("m");
result += " local " + m + "\n";
foreach (var func in functionsToApply) {
result += " " + m + " = nil\n";
var subMapping = ExtractSubMapping(func);
if (subMapping != null) {
var (key, f) = subMapping.Value;
var e = f.Apply(new LuaLiteral(Typs.String, tags + "[\"" + key + "\"]"));
e = e.Optimize();
result += Snippets.Convert(lua, m, e).Indent();
}
else {
result += Snippets.Convert(lua, m, func.Apply(new LuaLiteral(Typs.Tags, "tags")));
}
result += "\n\n if (" + m + " ~= nil) then\n " + Combine(assignTo, m) + "\n end\n";
}
return result;
}
throw new NotImplementedException();
}
/// <summary>
/// Combine both values in lua - both are not nil
/// </summary>
/// <param name="assignTo"></param>
/// <param name="value"></param>
/// <returns></returns>
public abstract string Combine(string assignTo, string value);
private static (string Key, IExpression Value)? ExtractSubMapping(IExpression app)
{
var mappings = new List<Mapping>();
// ($dot $head) (stringToTag (mapping (mapping with single function)
if (UnApply(
UnApply(
IsFunc(Funcs.Dot),
IsFunc(Funcs.Head)),
UnApply(
IsFunc(Funcs.StringStringToTags),
IsMapping(mappings)
)
).Invoke(app)) {
var mapping = mappings.First();
if (mapping.StringToResultFunctions.Count == 1) {
var kv = mapping.StringToResultFunctions.ToList().First();
return (kv.Key, kv.Value);
}
}
return null;
}
}
}

View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using AspectedRouting.Language;
using AspectedRouting.Language.Expression;
namespace AspectedRouting.IO.LuaSnippets
{
/// <summary>
/// A lua snippet is a piece of code which converts a functional expression into an imperative piece of lua code.
/// While the output is less readable then the functional approach, it is more performant
///
/// </summary>
public abstract class LuaSnippet
{
/// <summary>
/// Indicates which function is implemented
/// </summary>
public readonly Function ImplementsFunction;
protected LuaSnippet(Function implements)
{
ImplementsFunction = implements;
}
public abstract string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, List<IExpression> args);
}
}

View file

@ -0,0 +1,15 @@
using AspectedRouting.Language;
namespace AspectedRouting.IO.LuaSnippets
{
public class MaxSnippet : ListFoldingSnippet
{
public MaxSnippet() : base(Funcs.Max, "nil") { }
public override string Combine(string assignTo, string value)
{
return Utils.Lines("if ( "+ assignTo + " == nil or" + assignTo + " < " + value + " ) then",
" " + assignTo + " = " + value,
"end");
}
}
}

View file

@ -0,0 +1,37 @@
using System.Collections.Generic;
using AspectedRouting.IO.LuaSkeleton;
using AspectedRouting.Language;
namespace AspectedRouting.IO.LuaSnippets
{
/// <summary>
/// The member-of snippet breaks the abstraction _completely_
/// It is tied heavily to the preprocessor and use the function name to determine which tag to check
/// </summary>
public class MemberOfSnippet : LuaSnippet
{
public MemberOfSnippet() : base(Funcs.MemberOf) { }
public override string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, List<IExpression> args)
{
// Note how we totally throw away args[0]
var tagsToken = args[1];
var tags = "";
var result = "";
if (tagsToken is LuaLiteral lit) {
tags = lit.Lua;
}
else {
tags = lua.FreeVar("tags");
result += "local " + tags + "\n";
result += Snippets.Convert(lua, tags, tagsToken);
}
var r = lua.FreeVar("relationValue");
result += "local " + r + " = " + tags + "[\"_relation:" + lua.Context.AspectName.Replace(".", "_") + "\"]\n";
result += assignTo + " = " + r + " == \"yes\"";
return result;
}
}
}

View file

@ -0,0 +1,15 @@
using AspectedRouting.Language;
namespace AspectedRouting.IO.LuaSnippets
{
public class MinSnippet : ListFoldingSnippet
{
public MinSnippet() : base(Funcs.Min, "nil") { }
public override string Combine(string assignTo, string value)
{
return Utils.Lines("if ( " + assignTo + " == nil or " + assignTo + " > " + value + " ) then",
" " + assignTo + " = " + value,
"end");
}
}
}

View file

@ -0,0 +1,14 @@
using AspectedRouting.Language;
namespace AspectedRouting.IO.LuaSnippets
{
public class MultiplySnippet : ListFoldingSnippet
{
public MultiplySnippet() : base(Funcs.Multiply, "1") { }
public override string Combine(string assignTo, string value)
{
return assignTo + " = " + assignTo + " * " + value;
}
}
}

View file

@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.IO.LuaSkeleton;
using AspectedRouting.Language;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
namespace AspectedRouting.IO.LuaSnippets
{
/// <summary>
/// Applies a one-on-one-mapping (thus: a function converting one value into another one)
/// </summary>
public class SimpleMappingSnippet : LuaSnippet
{
private readonly Mapping _mapping;
public SimpleMappingSnippet(Mapping mapping) : base(null)
{
_mapping = mapping;
}
public override string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, List<IExpression> args)
{
var arg = args[0];
var v = lua.FreeVar("v");
var vLua = new LuaLiteral(Typs.String, v);
var mappings = new List<string>();
foreach (var kv in _mapping.StringToResultFunctions) {
var f = kv.Value;
if (f.Types.First() is Curry) {
f = f.Apply(vLua);
}
mappings.Add("if (" + v + " == \"" + kv.Key + "\") then\n " + assignTo + " = " + lua.ToLua(f));
}
return Utils.Lines(
"local " + v,
Snippets.Convert(lua, v, arg),
string.Join("\nelse", mappings),
"end"
);
}
}
}

View file

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Functions;
namespace AspectedRouting.IO.LuaSnippets
{
public class Snippets
{
private static readonly List<LuaSnippet> AllSnippets =
new List<LuaSnippet> {
new DefaultSnippet(),
new FirstMatchOfSnippet(),
new MultiplySnippet(),
new SumSnippet(),
new MaxSnippet(),
new MinSnippet(),
new IfThenElseDottedSnippet(),
new InvSnippet(),
new HeadSnippet(),
new MemberOfSnippet()
};
private static readonly Dictionary<string, LuaSnippet> SnippetsIndex = AllSnippets.ToDictionary(
snippet => snippet.ImplementsFunction.Name, snippet => snippet
);
public static string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, IExpression e)
{
var opt = e.Optimize();
if (!Equals(e.Types.First(), opt.Types.First())) {
throw new Exception("Optimization went wrong!");
}
e = opt;
var deconstructed = e.DeconstructApply();
if (deconstructed != null){
if (deconstructed.Value.f is Mapping m) {
return new SimpleMappingSnippet(m).Convert(lua, assignTo, deconstructed.Value.args);
}
if (deconstructed.Value.f is Function f
&& SnippetsIndex.TryGetValue(f.Name, out var snippet)) {
var optimized = snippet.Convert(lua, assignTo, deconstructed.Value.args);
if (optimized != null) {
return optimized + "\n";
}
}
}
try {
return assignTo + " = " + lua.ToLua(e)+"\n";
}
catch (Exception err) {
return "print(\"ERROR COMPILER BUG\");\n";
}
}
}
}

View file

@ -0,0 +1,13 @@
using AspectedRouting.Language;
namespace AspectedRouting.IO.LuaSnippets
{
public class SumSnippet : ListFoldingSnippet
{
public SumSnippet() : base(Funcs.Sum, "0") { }
public override string Combine(string assignTo, string value)
{
return assignTo + " = " + assignTo + " + " + value;
}
}
}

View file

@ -1,4 +1,4 @@
function member_of(calledIn, parameters, tags, result)
function memberOf(calledIn, parameters, tags, result)
local k = "_relation:" .. calledIn
-- This tag is conventiently setup by all the preprocessors, which take the parameters into account
local doesMatch = tags[k]

View file

@ -1,6 +1,6 @@
function parse(string)
if (string == nil) then
return 0
return nil
end
if (type(string) == "number") then
return string

View file

@ -1 +1,3 @@
print("ERROR: stringToTag is needed. This should not happen")
function stringToTags(table, tags)
return table_to_list(tags, {}, table)
end

View file

@ -46,13 +46,13 @@ function unit_test_profile(profile_function, profile_name, index, expected, tags
end
end
if (result.forward_speed ~= expected.speed and result.backward_speed ~= expected.speed) then
if (math.abs(result.forward_speed - expected.speed) >= 0.001 and math.abs(result.backward_speed - expected.speed) >= 0.001) 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
if (math.abs(result.forward - expected.priority) >= 0.001 and math.abs(result.backward - expected.priority) >= 0.001) 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

View file

@ -2,20 +2,22 @@ using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Functions;
namespace AspectedRouting.Language
{
public static class Deconstruct
{
public static readonly Func<IExpression, bool> Any = e => true;
/// <summary>
/// Fully deconstruct nested applies, used to convert from ((f a0) a1) ... an) to f(a0, a1, a2, a3
/// Fully deconstruct nested applies, used to convert from ((f a0) a1) ... an) to f(a0, a1, a2, a3
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public static (IExpression f, List<IExpression> args)? DeconstructApply(this IExpression e)
{
if (!(e is Apply _))
{
if (!(e is Apply _)) {
return null;
}
@ -23,8 +25,7 @@ namespace AspectedRouting.Language
var fs = new List<IExpression>();
while (UnApply(Assign(fs), Assign(argss)).Invoke(e))
{
while (UnApply(Assign(fs), Assign(argss)).Invoke(e)) {
e = fs.First();
fs.Clear();
}
@ -34,11 +35,22 @@ namespace AspectedRouting.Language
return (e, argss);
}
public static Func<IExpression, bool> IsMapping(List<Mapping> collect)
{
return e => {
if (e is Mapping m) {
collect.Add(m);
return true;
}
return false;
};
}
public static Func<IExpression, bool> Assign(List<IExpression> collect)
{
return e =>
{
return e => {
collect.Add(e);
return true;
};
@ -46,10 +58,8 @@ namespace AspectedRouting.Language
public static Func<IExpression, bool> IsFunc(Function f)
{
return e =>
{
if (!(e is Function fe))
{
return e => {
if (!(e is Function fe)) {
return false;
}
@ -63,32 +73,26 @@ namespace AspectedRouting.Language
{
return UnApply(matchFunc, matchArg, true);
}
public static Func<IExpression, bool> UnApply(
Func<IExpression, bool> matchFunc,
Func<IExpression, bool> matchArg,
bool matchAny = false)
{
return e =>
{
if (!(e is Apply apply))
{
return e => {
if (!(e is Apply apply)) {
return false;
}
foreach (var (_, (f, a)) in apply.FunctionApplications)
{
foreach (var (_, (f, a)) in apply.FunctionApplications) {
var doesMatch = matchFunc.Invoke(f) && matchArg.Invoke(a);
if (matchAny)
{
if (doesMatch)
{
if (matchAny) {
if (doesMatch) {
return true;
}
}
else
{
if (!doesMatch)
{
else {
if (!doesMatch) {
// All must match - otherwise we might have registered a wrong collectiin
return false;
}
@ -98,8 +102,5 @@ namespace AspectedRouting.Language
return !matchAny;
};
}
public static readonly Func<IExpression, bool> Any = e => true;
}
}

View file

@ -3,25 +3,21 @@ using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
using static AspectedRouting.Language.Deconstruct;
using Type = AspectedRouting.Language.Typ.Type;
namespace AspectedRouting.Language.Expression
{
public class Apply : IExpression
{
/// <summary>
/// Maps the expected return type onto the argument needed for that.
/// The argument is specialized for this return type
/// </summary>
public readonly Dictionary<Type, (IExpression f, IExpression a)> FunctionApplications;
// Only used for when there is no typechecking possible
private readonly string _debugInfo;
public IExpression F => FunctionApplications.Values.First().f;
public IExpression A => FunctionApplications.Values.First().a;
public IEnumerable<Type> Types => FunctionApplications.Keys;
/// <summary>
/// Maps the expected return type onto the argument needed for that.
/// The argument is specialized for this return type
/// </summary>
public readonly Dictionary<Type, (IExpression f, IExpression a)> FunctionApplications;
private Apply(string debugInfo, Dictionary<Type, (IExpression f, IExpression a)> argument)
{
@ -31,18 +27,15 @@ namespace AspectedRouting.Language.Expression
public Apply(IExpression f, IExpression argument)
{
if (f == null || argument == null)
{
if (f == null || argument == null) {
throw new NullReferenceException();
}
FunctionApplications = new Dictionary<Type, (IExpression f, IExpression a)>();
var typesCleaned = argument.Types.RenameVars(f.Types).ToList();
foreach (var funcType in f.Types)
{
if (!(funcType is Curry c))
{
foreach (var funcType in f.Types) {
if (!(funcType is Curry c)) {
continue;
}
@ -50,12 +43,10 @@ namespace AspectedRouting.Language.Expression
var expectedResultType = c.ResultType;
foreach (var argType in typesCleaned)
{
foreach (var argType in typesCleaned) {
// we try to unify the argType with the expected type
var substitutions = expectedArgType.UnificationTable(argType);
if (substitutions == null)
{
if (substitutions == null) {
continue;
}
@ -65,13 +56,11 @@ namespace AspectedRouting.Language.Expression
var actualFunction = f.Specialize(new Curry(actualArgType, actualResultType));
var actualArgument = argument.Specialize(actualArgType);
if (actualFunction == null || actualArgument == null)
{
if (actualFunction == null || actualArgument == null) {
continue;
}
if (FunctionApplications.ContainsKey(actualResultType))
{
if (FunctionApplications.ContainsKey(actualResultType)) {
continue;
}
@ -79,28 +68,29 @@ namespace AspectedRouting.Language.Expression
}
}
if (!FunctionApplications.Any())
{
try
{
if (!FunctionApplications.Any()) {
try {
_debugInfo = $"\n{f.Optimize().TypeBreakdown().Indent()}\n" +
$"is given the argument: " +
"is given the argument: " +
"(" + argument.Optimize().TypeBreakdown() + ")";
}
catch (Exception)
{
_debugInfo =$"\n (NO OPT) {f.TypeBreakdown().Indent()}\n" +
$"is given the argument: " +
"(" + argument.TypeBreakdown() + ")";
catch (Exception) {
_debugInfo = $"\n (NO OPT) {f.TypeBreakdown().Indent()}\n" +
"is given the argument: " +
"(" + argument.TypeBreakdown() + ")";
}
}
}
public IExpression F => FunctionApplications.Values.First().f;
public IExpression A => FunctionApplications.Values.First().a;
public IEnumerable<Type> Types => FunctionApplications.Keys;
public object Evaluate(Context c, params IExpression[] arguments)
{
if (!Types.Any())
{
if (!Types.Any()) {
throw new ArgumentException("Trying to invoke an invalid expression: " + this);
}
@ -111,8 +101,7 @@ namespace AspectedRouting.Language.Expression
var arg = argExpr;
var allArgs = new IExpression[arguments.Length + 1];
allArgs[0] = arg;
for (var i = 0; i < arguments.Length; i++)
{
for (var i = 0; i < arguments.Length; i++) {
allArgs[i + 1] = arguments[i];
}
@ -125,50 +114,9 @@ namespace AspectedRouting.Language.Expression
return Specialize(allowedTypes);
}
private Apply Specialize(IEnumerable<Type> allowedTypes)
{
var newArgs = new Dictionary<Type, (IExpression f, IExpression a)>();
foreach (var allowedType in allowedTypes)
{
foreach (var (resultType, (funExpr, argExpr)) in FunctionApplications)
{
var substitutions = resultType.UnificationTable(allowedType, true);
if (substitutions == null)
{
continue;
}
var actualResultType = allowedType.Substitute(substitutions);
// f : a -> b
// actualResultType = b (b which was retrieved after a reverse substitution)
var actualFunction = funExpr.Specialize(substitutions);
var actualArgument = argExpr.Specialize(substitutions);
if (actualFunction == null || actualArgument == null)
{
// One of the subexpressions can't be optimized
return null;
}
newArgs[actualResultType] = (actualFunction, actualArgument);
}
}
if (!newArgs.Any())
{
return null;
}
return new Apply(_debugInfo, newArgs);
}
public IExpression Optimize()
{
if (Types.Count() == 0)
{
if (Types.Count() == 0) {
throw new ArgumentException("This application contain no valid types, so cannot be optimized" + this);
}
@ -176,32 +124,29 @@ namespace AspectedRouting.Language.Expression
// => (const dot _) id => dot id => id
// or => (constRight _ id) id => id id => id
if (
Deconstruct.UnApplyAny(
Deconstruct.UnApplyAny(
Deconstruct.UnApplyAny(
Deconstruct.IsFunc(Funcs.Const),
Deconstruct.IsFunc(Funcs.Dot)),
Deconstruct.Any),
Deconstruct.IsFunc(Funcs.Id)
UnApplyAny(
UnApplyAny(
UnApplyAny(
IsFunc(Funcs.Const),
IsFunc(Funcs.Dot)),
Any),
IsFunc(Funcs.Id)
).Invoke(this)
&& Deconstruct.UnApplyAny(Deconstruct.UnApplyAny(
Deconstruct.UnApplyAny(
Deconstruct.IsFunc(Funcs.ConstRight),
Deconstruct.Any),
Deconstruct.IsFunc(Funcs.Id)),
Deconstruct.IsFunc(Funcs.Id)
).Invoke(this))
{
&& UnApplyAny(UnApplyAny(
UnApplyAny(
IsFunc(Funcs.ConstRight),
Any),
IsFunc(Funcs.Id)),
IsFunc(Funcs.Id)
).Invoke(this)) {
return Funcs.Id;
}
if (Types.Count() > 1)
{
if (Types.Count() > 1) {
// Too much types to optimize
var optimized = new Dictionary<Type, (IExpression f, IExpression a)>();
foreach (var (resultType, (f, a)) in FunctionApplications)
{
foreach (var (resultType, (f, a)) in FunctionApplications) {
var fOpt = f.Optimize();
var aOpt = a.Optimize();
optimized.Add(resultType, (fOpt, aOpt));
@ -214,11 +159,10 @@ namespace AspectedRouting.Language.Expression
// id a => a
var arg = new List<IExpression>();
if (
Deconstruct.UnApplyAny(
Deconstruct.IsFunc(Funcs.Id),
Deconstruct.Assign(arg)
).Invoke(this))
{
UnApplyAny(
IsFunc(Funcs.Id),
Assign(arg)
).Invoke(this)) {
return arg.First();
}
}
@ -232,17 +176,16 @@ namespace AspectedRouting.Language.Expression
var arg = new List<IExpression>();
if (
Deconstruct.UnApplyAny(
Deconstruct.UnApply(
Deconstruct.UnApply(
Deconstruct.UnApply(
Deconstruct.IsFunc(Funcs.IfDotted),
Deconstruct.Assign(fcondition)),
Deconstruct.Assign(fthen)),
Deconstruct.Assign(felse)),
Deconstruct.Assign(arg)
).Invoke(this))
{
UnApplyAny(
UnApply(
UnApply(
UnApply(
IsFunc(Funcs.IfDotted),
Assign(fcondition)),
Assign(fthen)),
Assign(felse)),
Assign(arg)
).Invoke(this)) {
var a = arg.First();
return
Funcs.If.Apply(
@ -253,13 +196,11 @@ namespace AspectedRouting.Language.Expression
}
}
{
var (f, a) = FunctionApplications.Values.First();
var (newFa, expr) = OptimizeApplicationPair(f, a);
if (expr != null)
{
if (expr != null) {
return expr;
}
@ -268,6 +209,54 @@ namespace AspectedRouting.Language.Expression
}
}
public void Visit(Func<IExpression, bool> visitor)
{
var continueVisit = visitor(this);
if (!continueVisit) {
return;
}
foreach (var (_, (f, a)) in FunctionApplications) {
f.Visit(visitor);
a.Visit(visitor);
}
}
private Apply Specialize(IEnumerable<Type> allowedTypes)
{
var newArgs = new Dictionary<Type, (IExpression f, IExpression a)>();
foreach (var allowedType in allowedTypes) {
foreach (var (resultType, (funExpr, argExpr)) in FunctionApplications) {
var substitutions = resultType.UnificationTable(allowedType, true);
if (substitutions == null) {
continue;
}
var actualResultType = allowedType.Substitute(substitutions);
// f : a -> b
// actualResultType = b (b which was retrieved after a reverse substitution)
var actualFunction = funExpr.Specialize(substitutions);
var actualArgument = argExpr.Specialize(substitutions);
if (actualFunction == null || actualArgument == null) {
// One of the subexpressions can't be optimized
return null;
}
newArgs[actualResultType] = (actualFunction, actualArgument);
}
}
if (!newArgs.Any()) {
return null;
}
return new Apply(_debugInfo, newArgs);
}
private ((IExpression fOpt, IExpression fArg)?, IExpression result) OptimizeApplicationPair(IExpression f,
IExpression a)
{
@ -275,44 +264,40 @@ namespace AspectedRouting.Language.Expression
a = a.Optimize();
switch (f)
{
switch (f) {
case Id _:
return (null, a);
case Apply apply:
// const x y -> y
var (subF, subArg) = apply.FunctionApplications.Values.First();
if (apply.F is Const _) {
// (const x) y -> y
// apply == (const x) thus we return 'x' and ignore 'a'
if (subF is Const _)
{
return (null, subArg);
return (null, apply.A);
}
if (subF is ConstRight _)
{
if (apply.F is ConstRight _) {
// constRight x y -> y
// apply == (constRight x) so we return a
return (null, a);
}
var f0 = new List<IExpression>();
var f1 = new List<IExpression>();
// ((dot f0) f1)
// ((dot f0) f1) arg is the actual expression, but arg is already split of
if (Deconstruct.UnApply(
Deconstruct.UnApply(
Deconstruct.IsFunc(Funcs.Dot),
Deconstruct.Assign(f0)
if (UnApply(
UnApply(
IsFunc(Funcs.Dot),
Assign(f0)
),
Deconstruct.Assign(f1)).Invoke(f)
)
{
Assign(f1)).Invoke(apply)
) {
// apply == ((dot f0) f1)
// ((dot f0) f1) a is the actual expression, but arg is already split of
// f0 (f1 arg)
// which used to be (f0 . f1) arg
return ((f0.First(), new Apply(f1.First(), subArg)), null);
return ((f0.First(), new Apply(f1.First(), a)), null);
}
@ -322,37 +307,19 @@ namespace AspectedRouting.Language.Expression
return ((f, a), null);
}
public void Visit(Func<IExpression, bool> visitor)
{
var continueVisit = visitor(this);
if (!continueVisit)
{
return;
}
foreach (var (_, (f, a)) in FunctionApplications)
{
f.Visit(visitor);
a.Visit(visitor);
}
}
public override string ToString()
{
if (!FunctionApplications.Any())
{
if (!FunctionApplications.Any()) {
return "NOT-TYPECHECKABLE APPLICATION: " + _debugInfo;
}
var (f, arg) = FunctionApplications.Values.First();
if (f is Id _)
{
if (f is Id _) {
return arg.ToString();
}
var extra = "";
if (FunctionApplications.Count() > 1)
{
if (FunctionApplications.Count() > 1) {
extra = " [" + FunctionApplications.Count + " IMPLEMENTATIONS]";
}

View file

@ -9,8 +9,6 @@ namespace AspectedRouting.Language.Functions
private static Var a = new Var("a");
private static Var b = new Var("b");
// TODO OPTIMIZE AWAY
public override string Description { get; } = "An if_then_else, but one which takes an extra argument and applies it on the condition, then and else.\n" +
"Consider `fc`, `fthen` and `felse` are all functions taking an `a`, then:\n" +
"`(ifDotted fc fthen felse) a` === `(if (fc a) (fthen a) (felse a)" +

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language.Functions;
namespace AspectedRouting.Language.Typ
{
@ -24,25 +25,22 @@ namespace AspectedRouting.Language.Typ
}
public static HashSet<Type> SpecializeTo(this IEnumerable<Type> types0, IEnumerable<Type> allowedTypes, bool reverseSuperSet = true)
public static HashSet<Type> SpecializeTo(this IEnumerable<Type> types0, IEnumerable<Type> allowedTypes,
bool reverseSuperSet = true)
{
var results = new HashSet<Type>();
allowedTypes = allowedTypes.ToList();
foreach (var t0 in types0)
{
foreach (var allowed in allowedTypes)
{
foreach (var t0 in types0) {
foreach (var allowed in allowedTypes) {
var unified = t0.Unify(allowed, reverseSuperSet);
if (unified != null)
{
if (unified != null) {
results.Add(unified);
}
}
}
if (results.Any())
{
if (results.Any()) {
return results;
}
@ -51,8 +49,9 @@ namespace AspectedRouting.Language.Typ
/// <summary>
/// Unifies the two types, where t0 is the allowed, wider type and t1 is the actual, smaller type (unless reverseSuperset is true).
/// Unification will attempt to keep the type as small as possible
/// Unifies the two types, where t0 is the allowed, wider type and t1 is the actual, smaller type (unless
/// reverseSuperset is true).
/// Unification will attempt to keep the type as small as possible
/// </summary>
/// <param name="t0">The expected, wider type</param>
/// <param name="t1">The actual type</param>
@ -61,14 +60,12 @@ namespace AspectedRouting.Language.Typ
public static Type Unify(this Type t0, Type t1, bool reverseSuperset = false)
{
var table = t0.UnificationTable(t1, reverseSuperset);
if (table == null)
{
if (table == null) {
return null;
}
var subbed = t0.Substitute(table);
if (reverseSuperset)
{
if (reverseSuperset) {
return SelectSmallestUnion(t1, subbed);
}
@ -77,8 +74,7 @@ namespace AspectedRouting.Language.Typ
private static Type SelectSmallestUnion(this Type wider, Type smaller, bool reverse = false)
{
switch (wider)
{
switch (wider) {
case Var a:
return a;
case ListType l when smaller is ListType lsmaller:
@ -92,8 +88,7 @@ namespace AspectedRouting.Language.Typ
cWider.ResultType.SelectSmallestUnion(cSmaller.ResultType, reverse);
return new Curry(arg, result);
default:
if (wider.IsSuperSet(smaller) && !(smaller.IsSuperSet(wider)))
{
if (wider.IsSuperSet(smaller) && !smaller.IsSuperSet(wider)) {
return smaller;
}
@ -105,13 +100,12 @@ namespace AspectedRouting.Language.Typ
{
var all = expressions.ToHashSet();
var sorted = new List<IExpression>();
while (all.Any())
{
while (all.Any()) {
var widest = SelectWidestType(all);
if (widest == null)
{
if (widest == null) {
throw new ArgumentException("Can not sort widest to smallest");
}
all.Remove(widest);
sorted.Add(widest);
}
@ -122,20 +116,17 @@ namespace AspectedRouting.Language.Typ
public static IExpression SelectSmallestType(IEnumerable<IExpression> expressions)
{
IExpression smallest = null;
foreach (var current in expressions)
{
if (smallest == null)
{
foreach (var current in expressions) {
if (smallest == null) {
smallest = current;
continue;
}
if (smallest.Types.AllAreSuperset(current.Types))
{
if (smallest.Types.AllAreSuperset(current.Types)) {
smallest = current;
}
}
return smallest;
}
@ -143,16 +134,13 @@ namespace AspectedRouting.Language.Typ
public static IExpression SelectWidestType(IEnumerable<IExpression> expressions)
{
IExpression widest = null;
foreach (var current in expressions)
{
if (widest == null)
{
foreach (var current in expressions) {
if (widest == null) {
widest = current;
continue;
}
if (current.Types.AllAreSuperset(widest.Types))
{
if (current.Types.AllAreSuperset(widest.Types)) {
widest = current;
}
}
@ -162,12 +150,9 @@ namespace AspectedRouting.Language.Typ
public static bool AllAreSuperset(this IEnumerable<Type> shouldBeSuper, IEnumerable<Type> shouldBeSmaller)
{
foreach (var super in shouldBeSuper)
{
foreach (var smaller in shouldBeSmaller)
{
if (!super.IsSuperSet(smaller))
{
foreach (var super in shouldBeSuper) {
foreach (var smaller in shouldBeSmaller) {
if (!super.IsSuperSet(smaller)) {
return false;
}
}
@ -177,8 +162,8 @@ namespace AspectedRouting.Language.Typ
}
/// <summary>
/// Tries to unify t0 with all t1's.
/// Every match is returned
/// Tries to unify t0 with all t1's.
/// Every match is returned
/// </summary>
/// <param name="t0"></param>
/// <param name="t1"></param>
@ -186,8 +171,7 @@ namespace AspectedRouting.Language.Typ
public static IEnumerable<Type> UnifyAny(this Type t0, IEnumerable<Type> t1)
{
var result = t1.Select(t => t0.Unify(t)).Where(unification => unification != null).ToHashSet();
if (!result.Any())
{
if (!result.Any()) {
return null;
}
@ -195,8 +179,8 @@ namespace AspectedRouting.Language.Typ
}
/// <summary>
/// Tries to unify t0 with all t1's.
/// Every match is returned, but only if every unification could be performed
/// Tries to unify t0 with all t1's.
/// Every match is returned, but only if every unification could be performed
/// </summary>
/// <param name="t0"></param>
/// <param name="t1"></param>
@ -204,8 +188,7 @@ namespace AspectedRouting.Language.Typ
public static IEnumerable<Type> UnifyAll(this Type t0, IEnumerable<Type> t1)
{
var result = t1.Select(t => t0.Unify(t)).ToHashSet();
if (result.Any(x => x == null))
{
if (result.Any(x => x == null)) {
return null;
}
@ -215,8 +198,7 @@ namespace AspectedRouting.Language.Typ
public static Type Substitute(this Type t0, Dictionary<string, Type> substitutions)
{
switch (t0)
{
switch (t0) {
case Var a when substitutions.TryGetValue(a.Name, out var t):
return t;
case ListType l:
@ -229,10 +211,11 @@ namespace AspectedRouting.Language.Typ
}
/// <summary>
/// The unification table is built when the type of an argument is introspected to see if it fits in the excpect type
/// t0 here is the **expected** (wider) type, whereas t1 is the **actual** argument type.
/// In other words, if we expect a `double`, a `pdouble` fits in there too.
/// If we expect a function capable of handling pdoubles and giving strings, a function capable of handling doubles and giving bools will work just as well
/// The unification table is built when the type of an argument is introspected to see if it fits in the excpect type
/// t0 here is the **expected** (wider) type, whereas t1 is the **actual** argument type.
/// In other words, if we expect a `double`, a `pdouble` fits in there too.
/// If we expect a function capable of handling pdoubles and giving strings, a function capable of handling doubles and
/// giving bools will work just as well
/// </summary>
/// <param name="t0"></param>
/// <param name="t1"></param>
@ -245,8 +228,7 @@ namespace AspectedRouting.Language.Typ
bool AddSubs(string key, Type valueToAdd)
{
if (substitutionsOn0.TryGetValue(key, out var oldSubs))
{
if (substitutionsOn0.TryGetValue(key, out var oldSubs)) {
return oldSubs.Equals(valueToAdd);
}
@ -256,15 +238,12 @@ namespace AspectedRouting.Language.Typ
bool AddAllSubs(Dictionary<string, Type> table)
{
if (table == null)
{
if (table == null) {
return false;
}
foreach (var (key, tp) in table)
{
if (!AddSubs(key, tp))
{
foreach (var (key, tp) in table) {
if (!AddSubs(key, tp)) {
return false;
}
}
@ -272,33 +251,27 @@ namespace AspectedRouting.Language.Typ
return true;
}
switch (t0)
{
switch (t0) {
case Var a:
if (!AddSubs(a.Name, t1))
{
if (!AddSubs(a.Name, t1)) {
return null;
}
break;
case ListType l0 when t1 is ListType l1:
{
case ListType l0 when t1 is ListType l1: {
var table = l0.InnerType.UnificationTable(l1.InnerType, reverseSupersetRelation);
if (!AddAllSubs(table))
{
if (!AddAllSubs(table)) {
return null;
}
break;
}
case Curry curry0 when t1 is Curry curry1:
{
case Curry curry0 when t1 is Curry curry1: {
// contravariance for arguments: reversed
var tableA = curry0.ArgType.UnificationTable(curry1.ArgType, !reverseSupersetRelation);
var tableB = curry0.ResultType.UnificationTable(curry1.ResultType, reverseSupersetRelation);
if (!(AddAllSubs(tableA) && AddAllSubs(tableB)))
{
if (!(AddAllSubs(tableA) && AddAllSubs(tableB))) {
return null;
}
@ -307,25 +280,55 @@ namespace AspectedRouting.Language.Typ
default:
if (t1 is Var v)
{
if (t1 is Var v) {
AddSubs(v.Name, t0);
break;
}
if (!reverseSupersetRelation && !t0.IsSuperSet(t1))
{
if (!reverseSupersetRelation && !t0.IsSuperSet(t1)) {
return null;
}
if (reverseSupersetRelation && !t1.IsSuperSet(t0))
{
if (reverseSupersetRelation && !t1.IsSuperSet(t0)) {
return null;
}
break;
}
// We have the unification table at this point
// However, the unifications are transitive and this transitivity should be encoded
// E.g. if the unification table is
// { $a --> $x; $x --> string} it should be rewritten to {$a --> string; $x --> string}
// This can happen e.g. when ($a -> string) is unified with ($x -> $x)
// We do not have to worry about overlapping names, as they should be cleaned before calling this method
bool appliedTransitivity;
do {
appliedTransitivity = false;
var keys = substitutionsOn0.Keys.ToList();
foreach (var key in keys) {
var val = substitutionsOn0[key];
var usedVars = val.UsedVariables();
var isContained = keys.Any(usedVars.Contains);
if (!isContained) {
continue;
}
var newVal = val.Substitute(substitutionsOn0);
if (newVal.Equals(val)) {
continue;
}
if (newVal.UsedVariables().Contains(key) && !newVal.Equals(new Var(key))) {
// The substitution contains itself; and it is bigger then itself
// This means that $a is substituted by e.g. ($a -> $x), implying an infinite and contradictory type
return null;
}
substitutionsOn0[key] = newVal;
appliedTransitivity = true;
}
} while (appliedTransitivity);
return substitutionsOn0;
}
@ -333,19 +336,16 @@ namespace AspectedRouting.Language.Typ
public static HashSet<string> UsedVariables(this Type t0, HashSet<string> addTo = null)
{
addTo ??= new HashSet<string>();
switch (t0)
{
switch (t0) {
case Var a:
addTo.Add(a.Name);
break;
case ListType l0:
{
case ListType l0: {
l0.InnerType.UsedVariables(addTo);
break;
}
case Curry curry0:
{
case Curry curry0: {
curry0.ArgType.UsedVariables(addTo);
curry0.ResultType.UsedVariables(addTo);
break;
@ -358,13 +358,11 @@ namespace AspectedRouting.Language.Typ
public static bool IsSuperSet(this Type t0, Type t1)
{
if (t0 is Var || t1 is Var)
{
if (t0 is Var || t1 is Var) {
return true;
}
switch (t0)
{
switch (t0) {
case StringType _ when t1 is BoolType _:
return true;
case DoubleType _ when t1 is PDoubleType _:
@ -383,8 +381,7 @@ namespace AspectedRouting.Language.Typ
case ListType l0 when t1 is ListType l1:
return l0.InnerType.IsSuperSet(l1.InnerType);
case Curry c0 when t1 is Curry c1:
{
case Curry c0 when t1 is Curry c1: {
return c0.ResultType.IsSuperSet(c1.ResultType) &&
c1.ArgType.IsSuperSet(c0.ArgType); // contravariance for arguments: reversed order!
}
@ -408,8 +405,7 @@ namespace AspectedRouting.Language.Typ
// The substitution table
var subsTable = new Dictionary<string, Type>();
foreach (var v in variablesToRename)
{
foreach (var v in variablesToRename) {
var newValue = Var.Fresh(alreadyUsed);
subsTable.Add(v, newValue);
alreadyUsed.Add(newValue.Name);
@ -422,8 +418,7 @@ namespace AspectedRouting.Language.Typ
public static List<Type> Uncurry(this Type t)
{
var args = new List<Type>();
while (t is Curry c)
{
while (t is Curry c) {
args.Add(c.ArgType);
t = c.ResultType;
}

View file

@ -109,6 +109,13 @@ namespace AspectedRouting
if (read.Equals("quit")) return;
if (read.Equals("help")) {
Console.WriteLine(
Utils.Lines("select <behaviourName> to change behaviour or <vehicle.behaviourName> to change vehicle",
""));
continue;
}
if (read.Equals("clear"))
{
for (var i = 0; i < 80; i++) Console.WriteLine();

View file

@ -23,6 +23,11 @@ namespace AspectedRouting
return string.Join("\n", lines);
}
public static string Lines(params string[] lines)
{
return string.Join("\n", lines);
}
public static int Multiply(this IEnumerable<int> ints)
{
var factor = 1;

View file

@ -0,0 +1,10 @@
[{"name": "shortest","type": "pedestrian","author": "","description": "The shortest route, independent of of speed (Profile for someone who is walking)", "usedKeys": ["access", "highway", "service", "foot", "anyways:foot", "anyways:access", "anyways:construction"] }
,
{"name": "fastest","type": "bicycle","author": "","description": "The fastest route to your destination (Profile for a normal bicycle)", "usedKeys": ["access", "highway", "service", "bicycle", "anyways:bicycle", "anyways:access", "anyways:construction", "oneway", "oneway:bicycle", "junction", "cycleway", "cycleway:left", "maxspeed", "designation", "railway", "towpath", "cyclestreet", "bicycle:class", "surface", "route"] }
,
{"name": "shortest","type": "bicycle","author": "","description": "The shortest route, independent of of speed (Profile for a normal bicycle)", "usedKeys": ["access", "highway", "service", "bicycle", "anyways:bicycle", "anyways:access", "anyways:construction", "oneway", "oneway:bicycle", "junction", "cycleway", "cycleway:left", "maxspeed", "designation", "railway", "towpath", "cyclestreet", "bicycle:class", "surface", "route"] }
,
{"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)", "usedKeys": ["access", "highway", "service", "bicycle", "anyways:bicycle", "anyways:access", "anyways:construction", "oneway", "oneway:bicycle", "junction", "cycleway", "cycleway:left", "maxspeed", "designation", "railway", "towpath", "cyclestreet", "bicycle:class", "surface", "route"] }
,
{"name": "electric","type": "bicycle","author": "","description": "An electrical bicycle (Profile for a normal bicycle)", "usedKeys": ["access", "highway", "service", "bicycle", "anyways:bicycle", "anyways:access", "anyways:construction", "oneway", "oneway:bicycle", "junction", "cycleway", "cycleway:left", "maxspeed", "designation", "railway", "towpath", "cyclestreet", "bicycle:class", "surface", "route"] }
]

1257
output-example/bicycle.lua Normal file

File diff suppressed because it is too large Load diff

1081
output-example/helpText.md Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,844 @@
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)
-- Cleanup the relation tags to make them usable with this profile
tags = remove_relation_prefix(tags, "comfort")
-- 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)
local result
if (tags["highway"] ~= nil) then
local v
v = tags["highway"]
if (v == "cycleway") then
result = "designated"
elseif (v == "residential") then
result = "yes"
elseif (v == "living_street") then
result = "yes"
elseif (v == "service") then
result = "yes"
elseif (v == "services") then
result = "yes"
elseif (v == "track") then
result = "yes"
elseif (v == "crossing") then
result = "dismount"
elseif (v == "footway") then
result = "dismount"
elseif (v == "pedestrian") then
result = "dismount"
elseif (v == "corridor") then
result = "dismount"
elseif (v == "construction") then
result = "dismount"
elseif (v == "steps") then
result = "dismount"
elseif (v == "path") then
result = "yes"
elseif (v == "primary") then
result = "yes"
elseif (v == "primary_link") then
result = "yes"
elseif (v == "secondary") then
result = "yes"
elseif (v == "secondary_link") then
result = "yes"
elseif (v == "tertiary") then
result = "yes"
elseif (v == "tertiary_link") then
result = "yes"
elseif (v == "unclassified") then
result = "yes"
elseif (v == "road") then
result = "yes"
end
end
if (tags["service"] ~= nil) then
local v0
v0 = tags["service"]
if (v0 == "parking_aisle") then
result = "permissive"
elseif (v0 == "driveway") then
result = "private"
elseif (v0 == "alley") then
result = "yes"
elseif (v0 == "bus") then
result = "no"
end
end
if (tags["access"] ~= nil) then
local v1
v1 = tags["access"]
if (v1 == "no") then
result = "no"
elseif (v1 == "customers") then
result = "private"
elseif (v1 == "private") then
result = "private"
elseif (v1 == "permissive") then
result = "permissive"
elseif (v1 == "destination") then
result = "destination"
elseif (v1 == "delivery") then
result = "destination"
elseif (v1 == "service") then
result = "destination"
elseif (v1 == "permit") then
result = "destination"
end
end
if (tags["bicycle"] ~= nil) then
local v2
v2 = tags["bicycle"]
if (v2 == "yes") then
result = "yes"
elseif (v2 == "no") then
result = "no"
elseif (v2 == "use_sidepath") then
result = "no"
elseif (v2 == "designated") then
result = "designated"
elseif (v2 == "permissive") then
result = "permissive"
elseif (v2 == "private") then
result = "private"
elseif (v2 == "official") then
result = "designated"
elseif (v2 == "dismount") then
result = "dismount"
elseif (v2 == "permit") then
result = "destination"
end
end
if (tags["anyways:construction"] ~= nil) then
local v3
v3 = tags["anyways:construction"]
if (v3 == "yes") then
result = "no"
end
end
if (tags["anyways:access"] ~= nil) then
local v4
v4 = tags["anyways:access"]
if (v4 == "no") then
result = "no"
elseif (v4 == "destination") then
result = "destination"
elseif (v4 == "yes") then
result = "yes"
end
end
if (tags["anyways:bicycle"] ~= nil) then
result = tags["anyways:bicycle"]
end
if (result == nil) then
result = "no"
end
return result
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)
local result
if (tags["oneway"] ~= nil) then
local v5
v5 = tags["oneway"]
if (v5 == "yes") then
result = "with"
elseif (v5 == "no") then
result = "both"
elseif (v5 == "1") then
result = "with"
elseif (v5 == "-1") then
result = "against"
end
end
if (tags["cycleway:left"] ~= nil) then
local v6
v6 = tags["cycleway:left"]
if (v6 == "no") then
result = "with"
elseif (v6 == "none") then
result = "with"
elseif (v6 == "yes") then
result = "both"
elseif (v6 == "lane") then
result = "both"
elseif (v6 == "track") then
result = "both"
elseif (v6 == "shared_lane") then
result = "both"
elseif (v6 == "share_busway") then
result = "both"
elseif (v6 == "opposite_lane") then
result = "both"
elseif (v6 == "opposite_track") then
result = "both"
elseif (v6 == "opposite") then
result = "both"
end
end
if (tags["cycleway"] ~= nil) then
local v7
v7 = tags["cycleway"]
if (v7 == "right") then
result = "against"
elseif (v7 == "opposite_lane") then
result = "both"
elseif (v7 == "track") then
result = "both"
elseif (v7 == "lane") then
result = "both"
elseif (v7 == "opposite") then
result = "both"
elseif (v7 == "opposite_share_busway") then
result = "both"
elseif (v7 == "opposite_track") then
result = "both"
end
end
if (tags["junction"] ~= nil) then
local v8
v8 = tags["junction"]
if (v8 == "roundabout") then
result = "with"
end
end
if (tags["oneway:bicycle"] ~= nil) then
local v9
v9 = tags["oneway:bicycle"]
if (v9 == "yes") then
result = "with"
elseif (v9 == "no") then
result = "both"
elseif (v9 == "1") then
result = "with"
elseif (v9 == "-1") then
result = "against"
end
end
if (result == nil) then
result = "both"
end
return result
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)
local result
if (tags["highway"] ~= nil) then
local v10
v10 = tags["highway"]
if (v10 == "cycleway") then
result = 30
elseif (v10 == "footway") then
result = 20
elseif (v10 == "crossing") then
result = 20
elseif (v10 == "pedestrian") then
result = 15
elseif (v10 == "path") then
result = 15
elseif (v10 == "corridor") then
result = 5
elseif (v10 == "residential") then
result = 30
elseif (v10 == "living_street") then
result = 20
elseif (v10 == "service") then
result = 30
elseif (v10 == "services") then
result = 30
elseif (v10 == "track") then
result = 50
elseif (v10 == "unclassified") then
result = 50
elseif (v10 == "road") then
result = 50
elseif (v10 == "motorway") then
result = 120
elseif (v10 == "motorway_link") then
result = 120
elseif (v10 == "primary") then
result = 90
elseif (v10 == "primary_link") then
result = 90
elseif (v10 == "secondary") then
result = 50
elseif (v10 == "secondary_link") then
result = 50
elseif (v10 == "tertiary") then
result = 50
elseif (v10 == "tertiary_link") then
result = 50
end
end
if (tags["designation"] ~= nil) then
local v11
v11 = tags["designation"]
if (v11 == "towpath") then
result = 30
end
end
if (tags["maxspeed"] ~= nil) then
result = parse(tags["maxspeed"])
end
if (result == nil) then
result = 30
end
return result
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)
local result
result = 1
if (tags["highway"] ~= nil) then
local m = nil
local v12
v12 = tags["highway"]
if (v12 == "cycleway") then
m = 1.2
elseif (v12 == "primary") then
m = 0.3
elseif (v12 == "secondary") then
m = 0.4
elseif (v12 == "tertiary") then
m = 0.5
elseif (v12 == "unclassified") then
m = 0.8
elseif (v12 == "track") then
m = 0.95
elseif (v12 == "residential") then
m = 1
elseif (v12 == "living_street") then
m = 1.1
elseif (v12 == "footway") then
m = 0.95
elseif (v12 == "path") then
m = 0.5
elseif (v12 == "construction") then
m = 0.5
end
if (m ~= nil) then
result = result * m
end
end
if (tags["railway"] ~= nil) then
local m = nil
local v13
v13 = tags["railway"]
if (v13 == "abandoned") then
m = 2
end
if (m ~= nil) then
result = result * m
end
end
if (tags["towpath"] ~= nil) then
local m = nil
local v14
v14 = tags["towpath"]
if (v14 == "yes") then
m = 2
end
if (m ~= nil) then
result = result * m
end
end
if (tags["cycleway"] ~= nil) then
local m = nil
local v15
v15 = tags["cycleway"]
if (v15 == "track") then
m = 1.2
end
if (m ~= nil) then
result = result * m
end
end
if (tags["cyclestreet"] ~= nil) then
local m = nil
local v16
v16 = tags["cyclestreet"]
if (v16 == "yes") then
m = 1.1
end
if (m ~= nil) then
result = result * m
end
end
if (tags["access"] ~= nil) then
local m = nil
local v17
v17 = tags["access"]
if (v17 == "designated") then
m = 1.2
elseif (v17 == "dismount") then
m = 0.01
end
if (m ~= nil) then
result = result * m
end
end
if (tags["bicycle:class"] ~= nil) then
local m = nil
local v18
v18 = tags["bicycle:class"]
if (v18 == "-3") then
m = 0.5
elseif (v18 == "-2") then
m = 0.7
elseif (v18 == "-1") then
m = 0.9
elseif (v18 == "0") then
m = 1
elseif (v18 == "1") then
m = 1.1
elseif (v18 == "2") then
m = 1.3
elseif (v18 == "3") then
m = 1.5
end
if (m ~= nil) then
result = result * m
end
end
if (tags["surface"] ~= nil) then
local m = nil
local v19
v19 = tags["surface"]
if (v19 == "paved") then
m = 0.99
elseif (v19 == "concrete:lanes") then
m = 0.8
elseif (v19 == "concrete:plates") then
m = 1
elseif (v19 == "sett") then
m = 0.9
elseif (v19 == "unhewn_cobblestone") then
m = 0.75
elseif (v19 == "cobblestone") then
m = 0.8
elseif (v19 == "unpaved") then
m = 0.75
elseif (v19 == "compacted") then
m = 0.95
elseif (v19 == "fine_gravel") then
m = 0.7
elseif (v19 == "gravel") then
m = 0.9
elseif (v19 == "dirt") then
m = 0.6
elseif (v19 == "earth") then
m = 0.6
elseif (v19 == "grass") then
m = 0.6
elseif (v19 == "grass_paver") then
m = 0.9
elseif (v19 == "ground") then
m = 0.7
elseif (v19 == "sand") then
m = 0.5
elseif (v19 == "woodchips") then
m = 0.5
elseif (v19 == "snow") then
m = 0.5
elseif (v19 == "pebblestone") then
m = 0.5
elseif (v19 == "mud") then
m = 0.4
end
if (m ~= nil) then
result = result * m
end
end
if (tags["route"] ~= nil) then
local m = nil
local v20
v20 = tags["route"]
if (v20 == "ferry") then
m = 0.01
end
if (m ~= nil) then
result = result * m
end
end
if (result == nil) then
result = 1
end
return result
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 (math.abs(result.forward_speed - expected.speed) >= 0.001 and math.abs(result.backward_speed - expected.speed) >= 0.001) 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 (math.abs(result.forward - expected.priority) >= 0.001 and math.abs(result.backward - expected.priority) >= 0.001) 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 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 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 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 --
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

@ -0,0 +1,847 @@
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)
-- Cleanup the relation tags to make them usable with this profile
tags = remove_relation_prefix(tags, "electric")
-- 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)
local result
if (tags["highway"] ~= nil) then
local v
v = tags["highway"]
if (v == "cycleway") then
result = "designated"
elseif (v == "residential") then
result = "yes"
elseif (v == "living_street") then
result = "yes"
elseif (v == "service") then
result = "yes"
elseif (v == "services") then
result = "yes"
elseif (v == "track") then
result = "yes"
elseif (v == "crossing") then
result = "dismount"
elseif (v == "footway") then
result = "dismount"
elseif (v == "pedestrian") then
result = "dismount"
elseif (v == "corridor") then
result = "dismount"
elseif (v == "construction") then
result = "dismount"
elseif (v == "steps") then
result = "dismount"
elseif (v == "path") then
result = "yes"
elseif (v == "primary") then
result = "yes"
elseif (v == "primary_link") then
result = "yes"
elseif (v == "secondary") then
result = "yes"
elseif (v == "secondary_link") then
result = "yes"
elseif (v == "tertiary") then
result = "yes"
elseif (v == "tertiary_link") then
result = "yes"
elseif (v == "unclassified") then
result = "yes"
elseif (v == "road") then
result = "yes"
end
end
if (tags["service"] ~= nil) then
local v0
v0 = tags["service"]
if (v0 == "parking_aisle") then
result = "permissive"
elseif (v0 == "driveway") then
result = "private"
elseif (v0 == "alley") then
result = "yes"
elseif (v0 == "bus") then
result = "no"
end
end
if (tags["access"] ~= nil) then
local v1
v1 = tags["access"]
if (v1 == "no") then
result = "no"
elseif (v1 == "customers") then
result = "private"
elseif (v1 == "private") then
result = "private"
elseif (v1 == "permissive") then
result = "permissive"
elseif (v1 == "destination") then
result = "destination"
elseif (v1 == "delivery") then
result = "destination"
elseif (v1 == "service") then
result = "destination"
elseif (v1 == "permit") then
result = "destination"
end
end
if (tags["bicycle"] ~= nil) then
local v2
v2 = tags["bicycle"]
if (v2 == "yes") then
result = "yes"
elseif (v2 == "no") then
result = "no"
elseif (v2 == "use_sidepath") then
result = "no"
elseif (v2 == "designated") then
result = "designated"
elseif (v2 == "permissive") then
result = "permissive"
elseif (v2 == "private") then
result = "private"
elseif (v2 == "official") then
result = "designated"
elseif (v2 == "dismount") then
result = "dismount"
elseif (v2 == "permit") then
result = "destination"
end
end
if (tags["anyways:construction"] ~= nil) then
local v3
v3 = tags["anyways:construction"]
if (v3 == "yes") then
result = "no"
end
end
if (tags["anyways:access"] ~= nil) then
local v4
v4 = tags["anyways:access"]
if (v4 == "no") then
result = "no"
elseif (v4 == "destination") then
result = "destination"
elseif (v4 == "yes") then
result = "yes"
end
end
if (tags["anyways:bicycle"] ~= nil) then
result = tags["anyways:bicycle"]
end
if (result == nil) then
result = "no"
end
return result
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)
local result
if (tags["oneway"] ~= nil) then
local v5
v5 = tags["oneway"]
if (v5 == "yes") then
result = "with"
elseif (v5 == "no") then
result = "both"
elseif (v5 == "1") then
result = "with"
elseif (v5 == "-1") then
result = "against"
end
end
if (tags["cycleway:left"] ~= nil) then
local v6
v6 = tags["cycleway:left"]
if (v6 == "no") then
result = "with"
elseif (v6 == "none") then
result = "with"
elseif (v6 == "yes") then
result = "both"
elseif (v6 == "lane") then
result = "both"
elseif (v6 == "track") then
result = "both"
elseif (v6 == "shared_lane") then
result = "both"
elseif (v6 == "share_busway") then
result = "both"
elseif (v6 == "opposite_lane") then
result = "both"
elseif (v6 == "opposite_track") then
result = "both"
elseif (v6 == "opposite") then
result = "both"
end
end
if (tags["cycleway"] ~= nil) then
local v7
v7 = tags["cycleway"]
if (v7 == "right") then
result = "against"
elseif (v7 == "opposite_lane") then
result = "both"
elseif (v7 == "track") then
result = "both"
elseif (v7 == "lane") then
result = "both"
elseif (v7 == "opposite") then
result = "both"
elseif (v7 == "opposite_share_busway") then
result = "both"
elseif (v7 == "opposite_track") then
result = "both"
end
end
if (tags["junction"] ~= nil) then
local v8
v8 = tags["junction"]
if (v8 == "roundabout") then
result = "with"
end
end
if (tags["oneway:bicycle"] ~= nil) then
local v9
v9 = tags["oneway:bicycle"]
if (v9 == "yes") then
result = "with"
elseif (v9 == "no") then
result = "both"
elseif (v9 == "1") then
result = "with"
elseif (v9 == "-1") then
result = "against"
end
end
if (result == nil) then
result = "both"
end
return result
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)
local result
if (tags["highway"] ~= nil) then
local v10
v10 = tags["highway"]
if (v10 == "cycleway") then
result = 30
elseif (v10 == "footway") then
result = 20
elseif (v10 == "crossing") then
result = 20
elseif (v10 == "pedestrian") then
result = 15
elseif (v10 == "path") then
result = 15
elseif (v10 == "corridor") then
result = 5
elseif (v10 == "residential") then
result = 30
elseif (v10 == "living_street") then
result = 20
elseif (v10 == "service") then
result = 30
elseif (v10 == "services") then
result = 30
elseif (v10 == "track") then
result = 50
elseif (v10 == "unclassified") then
result = 50
elseif (v10 == "road") then
result = 50
elseif (v10 == "motorway") then
result = 120
elseif (v10 == "motorway_link") then
result = 120
elseif (v10 == "primary") then
result = 90
elseif (v10 == "primary_link") then
result = 90
elseif (v10 == "secondary") then
result = 50
elseif (v10 == "secondary_link") then
result = 50
elseif (v10 == "tertiary") then
result = 50
elseif (v10 == "tertiary_link") then
result = 50
end
end
if (tags["designation"] ~= nil) then
local v11
v11 = tags["designation"]
if (v11 == "towpath") then
result = 30
end
end
if (tags["maxspeed"] ~= nil) then
result = parse(tags["maxspeed"])
end
if (result == nil) then
result = 30
end
return result
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)
local result
result = 1
if (tags["highway"] ~= nil) then
local m = nil
local v12
v12 = tags["highway"]
if (v12 == "cycleway") then
m = 1.2
elseif (v12 == "primary") then
m = 0.3
elseif (v12 == "secondary") then
m = 0.4
elseif (v12 == "tertiary") then
m = 0.5
elseif (v12 == "unclassified") then
m = 0.8
elseif (v12 == "track") then
m = 0.95
elseif (v12 == "residential") then
m = 1
elseif (v12 == "living_street") then
m = 1.1
elseif (v12 == "footway") then
m = 0.95
elseif (v12 == "path") then
m = 0.5
elseif (v12 == "construction") then
m = 0.5
end
if (m ~= nil) then
result = result * m
end
end
if (tags["railway"] ~= nil) then
local m = nil
local v13
v13 = tags["railway"]
if (v13 == "abandoned") then
m = 2
end
if (m ~= nil) then
result = result * m
end
end
if (tags["towpath"] ~= nil) then
local m = nil
local v14
v14 = tags["towpath"]
if (v14 == "yes") then
m = 2
end
if (m ~= nil) then
result = result * m
end
end
if (tags["cycleway"] ~= nil) then
local m = nil
local v15
v15 = tags["cycleway"]
if (v15 == "track") then
m = 1.2
end
if (m ~= nil) then
result = result * m
end
end
if (tags["cyclestreet"] ~= nil) then
local m = nil
local v16
v16 = tags["cyclestreet"]
if (v16 == "yes") then
m = 1.1
end
if (m ~= nil) then
result = result * m
end
end
if (tags["access"] ~= nil) then
local m = nil
local v17
v17 = tags["access"]
if (v17 == "designated") then
m = 1.2
elseif (v17 == "dismount") then
m = 0.01
end
if (m ~= nil) then
result = result * m
end
end
if (tags["bicycle:class"] ~= nil) then
local m = nil
local v18
v18 = tags["bicycle:class"]
if (v18 == "-3") then
m = 0.5
elseif (v18 == "-2") then
m = 0.7
elseif (v18 == "-1") then
m = 0.9
elseif (v18 == "0") then
m = 1
elseif (v18 == "1") then
m = 1.1
elseif (v18 == "2") then
m = 1.3
elseif (v18 == "3") then
m = 1.5
end
if (m ~= nil) then
result = result * m
end
end
if (tags["surface"] ~= nil) then
local m = nil
local v19
v19 = tags["surface"]
if (v19 == "paved") then
m = 0.99
elseif (v19 == "concrete:lanes") then
m = 0.8
elseif (v19 == "concrete:plates") then
m = 1
elseif (v19 == "sett") then
m = 0.9
elseif (v19 == "unhewn_cobblestone") then
m = 0.75
elseif (v19 == "cobblestone") then
m = 0.8
elseif (v19 == "unpaved") then
m = 0.75
elseif (v19 == "compacted") then
m = 0.95
elseif (v19 == "fine_gravel") then
m = 0.7
elseif (v19 == "gravel") then
m = 0.9
elseif (v19 == "dirt") then
m = 0.6
elseif (v19 == "earth") then
m = 0.6
elseif (v19 == "grass") then
m = 0.6
elseif (v19 == "grass_paver") then
m = 0.9
elseif (v19 == "ground") then
m = 0.7
elseif (v19 == "sand") then
m = 0.5
elseif (v19 == "woodchips") then
m = 0.5
elseif (v19 == "snow") then
m = 0.5
elseif (v19 == "pebblestone") then
m = 0.5
elseif (v19 == "mud") then
m = 0.4
end
if (m ~= nil) then
result = result * m
end
end
if (tags["route"] ~= nil) then
local m = nil
local v20
v20 = tags["route"]
if (v20 == "ferry") then
m = 0.01
end
if (m ~= nil) then
result = result * m
end
end
if (result == nil) then
result = 1
end
return result
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 (math.abs(result.forward_speed - expected.speed) >= 0.001 and math.abs(result.backward_speed - expected.speed) >= 0.001) 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 (math.abs(result.forward - expected.priority) >= 0.001 and math.abs(result.backward - expected.priority) >= 0.001) 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 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 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 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 --
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

@ -0,0 +1,672 @@
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)
-- Cleanup the relation tags to make them usable with this profile
tags = remove_relation_prefix(tags, "fastest")
-- 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)
local result
if (tags["highway"] ~= nil) then
local v
v = tags["highway"]
if (v == "cycleway") then
result = "designated"
elseif (v == "residential") then
result = "yes"
elseif (v == "living_street") then
result = "yes"
elseif (v == "service") then
result = "yes"
elseif (v == "services") then
result = "yes"
elseif (v == "track") then
result = "yes"
elseif (v == "crossing") then
result = "dismount"
elseif (v == "footway") then
result = "dismount"
elseif (v == "pedestrian") then
result = "dismount"
elseif (v == "corridor") then
result = "dismount"
elseif (v == "construction") then
result = "dismount"
elseif (v == "steps") then
result = "dismount"
elseif (v == "path") then
result = "yes"
elseif (v == "primary") then
result = "yes"
elseif (v == "primary_link") then
result = "yes"
elseif (v == "secondary") then
result = "yes"
elseif (v == "secondary_link") then
result = "yes"
elseif (v == "tertiary") then
result = "yes"
elseif (v == "tertiary_link") then
result = "yes"
elseif (v == "unclassified") then
result = "yes"
elseif (v == "road") then
result = "yes"
end
end
if (tags["service"] ~= nil) then
local v0
v0 = tags["service"]
if (v0 == "parking_aisle") then
result = "permissive"
elseif (v0 == "driveway") then
result = "private"
elseif (v0 == "alley") then
result = "yes"
elseif (v0 == "bus") then
result = "no"
end
end
if (tags["access"] ~= nil) then
local v1
v1 = tags["access"]
if (v1 == "no") then
result = "no"
elseif (v1 == "customers") then
result = "private"
elseif (v1 == "private") then
result = "private"
elseif (v1 == "permissive") then
result = "permissive"
elseif (v1 == "destination") then
result = "destination"
elseif (v1 == "delivery") then
result = "destination"
elseif (v1 == "service") then
result = "destination"
elseif (v1 == "permit") then
result = "destination"
end
end
if (tags["bicycle"] ~= nil) then
local v2
v2 = tags["bicycle"]
if (v2 == "yes") then
result = "yes"
elseif (v2 == "no") then
result = "no"
elseif (v2 == "use_sidepath") then
result = "no"
elseif (v2 == "designated") then
result = "designated"
elseif (v2 == "permissive") then
result = "permissive"
elseif (v2 == "private") then
result = "private"
elseif (v2 == "official") then
result = "designated"
elseif (v2 == "dismount") then
result = "dismount"
elseif (v2 == "permit") then
result = "destination"
end
end
if (tags["anyways:construction"] ~= nil) then
local v3
v3 = tags["anyways:construction"]
if (v3 == "yes") then
result = "no"
end
end
if (tags["anyways:access"] ~= nil) then
local v4
v4 = tags["anyways:access"]
if (v4 == "no") then
result = "no"
elseif (v4 == "destination") then
result = "destination"
elseif (v4 == "yes") then
result = "yes"
end
end
if (tags["anyways:bicycle"] ~= nil) then
result = tags["anyways:bicycle"]
end
if (result == nil) then
result = "no"
end
return result
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)
local result
if (tags["oneway"] ~= nil) then
local v5
v5 = tags["oneway"]
if (v5 == "yes") then
result = "with"
elseif (v5 == "no") then
result = "both"
elseif (v5 == "1") then
result = "with"
elseif (v5 == "-1") then
result = "against"
end
end
if (tags["cycleway:left"] ~= nil) then
local v6
v6 = tags["cycleway:left"]
if (v6 == "no") then
result = "with"
elseif (v6 == "none") then
result = "with"
elseif (v6 == "yes") then
result = "both"
elseif (v6 == "lane") then
result = "both"
elseif (v6 == "track") then
result = "both"
elseif (v6 == "shared_lane") then
result = "both"
elseif (v6 == "share_busway") then
result = "both"
elseif (v6 == "opposite_lane") then
result = "both"
elseif (v6 == "opposite_track") then
result = "both"
elseif (v6 == "opposite") then
result = "both"
end
end
if (tags["cycleway"] ~= nil) then
local v7
v7 = tags["cycleway"]
if (v7 == "right") then
result = "against"
elseif (v7 == "opposite_lane") then
result = "both"
elseif (v7 == "track") then
result = "both"
elseif (v7 == "lane") then
result = "both"
elseif (v7 == "opposite") then
result = "both"
elseif (v7 == "opposite_share_busway") then
result = "both"
elseif (v7 == "opposite_track") then
result = "both"
end
end
if (tags["junction"] ~= nil) then
local v8
v8 = tags["junction"]
if (v8 == "roundabout") then
result = "with"
end
end
if (tags["oneway:bicycle"] ~= nil) then
local v9
v9 = tags["oneway:bicycle"]
if (v9 == "yes") then
result = "with"
elseif (v9 == "no") then
result = "both"
elseif (v9 == "1") then
result = "with"
elseif (v9 == "-1") then
result = "against"
end
end
if (result == nil) then
result = "both"
end
return result
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)
local result
if (tags["highway"] ~= nil) then
local v10
v10 = tags["highway"]
if (v10 == "cycleway") then
result = 30
elseif (v10 == "footway") then
result = 20
elseif (v10 == "crossing") then
result = 20
elseif (v10 == "pedestrian") then
result = 15
elseif (v10 == "path") then
result = 15
elseif (v10 == "corridor") then
result = 5
elseif (v10 == "residential") then
result = 30
elseif (v10 == "living_street") then
result = 20
elseif (v10 == "service") then
result = 30
elseif (v10 == "services") then
result = 30
elseif (v10 == "track") then
result = 50
elseif (v10 == "unclassified") then
result = 50
elseif (v10 == "road") then
result = 50
elseif (v10 == "motorway") then
result = 120
elseif (v10 == "motorway_link") then
result = 120
elseif (v10 == "primary") then
result = 90
elseif (v10 == "primary_link") then
result = 90
elseif (v10 == "secondary") then
result = 50
elseif (v10 == "secondary_link") then
result = 50
elseif (v10 == "tertiary") then
result = 50
elseif (v10 == "tertiary_link") then
result = 50
end
end
if (tags["designation"] ~= nil) then
local v11
v11 = tags["designation"]
if (v11 == "towpath") then
result = 30
end
end
if (tags["maxspeed"] ~= nil) then
result = parse(tags["maxspeed"])
end
if (result == nil) then
result = 30
end
return result
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 (math.abs(result.forward_speed - expected.speed) >= 0.001 and math.abs(result.backward_speed - expected.speed) >= 0.001) 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 (math.abs(result.forward - expected.priority) >= 0.001 and math.abs(result.backward - expected.priority) >= 0.001) 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 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 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 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

@ -0,0 +1,667 @@
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)
-- Cleanup the relation tags to make them usable with this profile
tags = remove_relation_prefix(tags, "shortest")
-- 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)
local result
if (tags["highway"] ~= nil) then
local v
v = tags["highway"]
if (v == "cycleway") then
result = "designated"
elseif (v == "residential") then
result = "yes"
elseif (v == "living_street") then
result = "yes"
elseif (v == "service") then
result = "yes"
elseif (v == "services") then
result = "yes"
elseif (v == "track") then
result = "yes"
elseif (v == "crossing") then
result = "dismount"
elseif (v == "footway") then
result = "dismount"
elseif (v == "pedestrian") then
result = "dismount"
elseif (v == "corridor") then
result = "dismount"
elseif (v == "construction") then
result = "dismount"
elseif (v == "steps") then
result = "dismount"
elseif (v == "path") then
result = "yes"
elseif (v == "primary") then
result = "yes"
elseif (v == "primary_link") then
result = "yes"
elseif (v == "secondary") then
result = "yes"
elseif (v == "secondary_link") then
result = "yes"
elseif (v == "tertiary") then
result = "yes"
elseif (v == "tertiary_link") then
result = "yes"
elseif (v == "unclassified") then
result = "yes"
elseif (v == "road") then
result = "yes"
end
end
if (tags["service"] ~= nil) then
local v0
v0 = tags["service"]
if (v0 == "parking_aisle") then
result = "permissive"
elseif (v0 == "driveway") then
result = "private"
elseif (v0 == "alley") then
result = "yes"
elseif (v0 == "bus") then
result = "no"
end
end
if (tags["access"] ~= nil) then
local v1
v1 = tags["access"]
if (v1 == "no") then
result = "no"
elseif (v1 == "customers") then
result = "private"
elseif (v1 == "private") then
result = "private"
elseif (v1 == "permissive") then
result = "permissive"
elseif (v1 == "destination") then
result = "destination"
elseif (v1 == "delivery") then
result = "destination"
elseif (v1 == "service") then
result = "destination"
elseif (v1 == "permit") then
result = "destination"
end
end
if (tags["bicycle"] ~= nil) then
local v2
v2 = tags["bicycle"]
if (v2 == "yes") then
result = "yes"
elseif (v2 == "no") then
result = "no"
elseif (v2 == "use_sidepath") then
result = "no"
elseif (v2 == "designated") then
result = "designated"
elseif (v2 == "permissive") then
result = "permissive"
elseif (v2 == "private") then
result = "private"
elseif (v2 == "official") then
result = "designated"
elseif (v2 == "dismount") then
result = "dismount"
elseif (v2 == "permit") then
result = "destination"
end
end
if (tags["anyways:construction"] ~= nil) then
local v3
v3 = tags["anyways:construction"]
if (v3 == "yes") then
result = "no"
end
end
if (tags["anyways:access"] ~= nil) then
local v4
v4 = tags["anyways:access"]
if (v4 == "no") then
result = "no"
elseif (v4 == "destination") then
result = "destination"
elseif (v4 == "yes") then
result = "yes"
end
end
if (tags["anyways:bicycle"] ~= nil) then
result = tags["anyways:bicycle"]
end
if (result == nil) then
result = "no"
end
return result
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)
local result
if (tags["oneway"] ~= nil) then
local v5
v5 = tags["oneway"]
if (v5 == "yes") then
result = "with"
elseif (v5 == "no") then
result = "both"
elseif (v5 == "1") then
result = "with"
elseif (v5 == "-1") then
result = "against"
end
end
if (tags["cycleway:left"] ~= nil) then
local v6
v6 = tags["cycleway:left"]
if (v6 == "no") then
result = "with"
elseif (v6 == "none") then
result = "with"
elseif (v6 == "yes") then
result = "both"
elseif (v6 == "lane") then
result = "both"
elseif (v6 == "track") then
result = "both"
elseif (v6 == "shared_lane") then
result = "both"
elseif (v6 == "share_busway") then
result = "both"
elseif (v6 == "opposite_lane") then
result = "both"
elseif (v6 == "opposite_track") then
result = "both"
elseif (v6 == "opposite") then
result = "both"
end
end
if (tags["cycleway"] ~= nil) then
local v7
v7 = tags["cycleway"]
if (v7 == "right") then
result = "against"
elseif (v7 == "opposite_lane") then
result = "both"
elseif (v7 == "track") then
result = "both"
elseif (v7 == "lane") then
result = "both"
elseif (v7 == "opposite") then
result = "both"
elseif (v7 == "opposite_share_busway") then
result = "both"
elseif (v7 == "opposite_track") then
result = "both"
end
end
if (tags["junction"] ~= nil) then
local v8
v8 = tags["junction"]
if (v8 == "roundabout") then
result = "with"
end
end
if (tags["oneway:bicycle"] ~= nil) then
local v9
v9 = tags["oneway:bicycle"]
if (v9 == "yes") then
result = "with"
elseif (v9 == "no") then
result = "both"
elseif (v9 == "1") then
result = "with"
elseif (v9 == "-1") then
result = "against"
end
end
if (result == nil) then
result = "both"
end
return result
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)
local result
if (tags["highway"] ~= nil) then
local v10
v10 = tags["highway"]
if (v10 == "cycleway") then
result = 30
elseif (v10 == "footway") then
result = 20
elseif (v10 == "crossing") then
result = 20
elseif (v10 == "pedestrian") then
result = 15
elseif (v10 == "path") then
result = 15
elseif (v10 == "corridor") then
result = 5
elseif (v10 == "residential") then
result = 30
elseif (v10 == "living_street") then
result = 20
elseif (v10 == "service") then
result = 30
elseif (v10 == "services") then
result = 30
elseif (v10 == "track") then
result = 50
elseif (v10 == "unclassified") then
result = 50
elseif (v10 == "road") then
result = 50
elseif (v10 == "motorway") then
result = 120
elseif (v10 == "motorway_link") then
result = 120
elseif (v10 == "primary") then
result = 90
elseif (v10 == "primary_link") then
result = 90
elseif (v10 == "secondary") then
result = 50
elseif (v10 == "secondary_link") then
result = 50
elseif (v10 == "tertiary") then
result = 50
elseif (v10 == "tertiary_link") then
result = 50
end
end
if (tags["designation"] ~= nil) then
local v11
v11 = tags["designation"]
if (v11 == "towpath") then
result = 30
end
end
if (tags["maxspeed"] ~= nil) then
result = parse(tags["maxspeed"])
end
if (result == nil) then
result = 30
end
return result
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 (math.abs(result.forward_speed - expected.speed) >= 0.001 and math.abs(result.backward_speed - expected.speed) >= 0.001) 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 (math.abs(result.forward - expected.priority) >= 0.001 and math.abs(result.backward - expected.priority) >= 0.001) 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 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 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 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

@ -0,0 +1,444 @@
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)
-- Cleanup the relation tags to make them usable with this profile
tags = remove_relation_prefix(tags, "shortest")
-- 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 = firstArg("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 = firstArg(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 = firstArg(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)
local result
if (tags["highway"] ~= nil) then
local v
v = tags["highway"]
if (v == "pedestrian") then
result = "designated"
elseif (v == "footway") then
result = "designated"
elseif (v == "living_street") then
result = "designated"
elseif (v == "steps") then
result = "yes"
elseif (v == "corridor") then
result = "designated"
elseif (v == "residential") then
result = "yes"
elseif (v == "service") then
result = "yes"
elseif (v == "services") then
result = "yes"
elseif (v == "track") then
result = "yes"
elseif (v == "crossing") then
result = "yes"
elseif (v == "construction") then
result = "permissive"
elseif (v == "path") then
result = "yes"
elseif (v == "primary") then
result = "yes"
elseif (v == "primary_link") then
result = "yes"
elseif (v == "secondary") then
result = "yes"
elseif (v == "secondary_link") then
result = "yes"
elseif (v == "tertiary") then
result = "yes"
elseif (v == "tertiary_link") then
result = "yes"
elseif (v == "unclassified") then
result = "yes"
elseif (v == "road") then
result = "yes"
end
end
if (tags["service"] ~= nil) then
local v0
v0 = tags["service"]
if (v0 == "parking_aisle") then
result = "permissive"
elseif (v0 == "driveway") then
result = "private"
elseif (v0 == "alley") then
result = "yes"
elseif (v0 == "bus") then
result = "no"
end
end
if (tags["access"] ~= nil) then
local v1
v1 = tags["access"]
if (v1 == "no") then
result = "no"
elseif (v1 == "customers") then
result = "private"
elseif (v1 == "private") then
result = "private"
elseif (v1 == "permissive") then
result = "permissive"
elseif (v1 == "destination") then
result = "destination"
elseif (v1 == "delivery") then
result = "destination"
elseif (v1 == "service") then
result = "destination"
elseif (v1 == "permit") then
result = "destination"
end
end
if (tags["foot"] ~= nil) then
local v2
v2 = tags["foot"]
if (v2 == "yes") then
result = "yes"
elseif (v2 == "no") then
result = "no"
elseif (v2 == "use_sidepath") then
result = "no"
elseif (v2 == "designated") then
result = "designated"
elseif (v2 == "permissive") then
result = "permissive"
elseif (v2 == "private") then
result = "private"
elseif (v2 == "official") then
result = "designated"
elseif (v2 == "dismount") then
result = "dismount"
elseif (v2 == "permit") then
result = "destination"
end
end
if (tags["anyways:construction"] ~= nil) then
local v3
v3 = tags["anyways:construction"]
if (v3 == "yes") then
result = "no"
end
end
if (tags["anyways:access"] ~= nil) then
local v4
v4 = tags["anyways:access"]
if (v4 == "no") then
result = "no"
elseif (v4 == "destination") then
result = "destination"
elseif (v4 == "yes") then
result = "yes"
end
end
if (tags["anyways:foot"] ~= nil) then
result = tags["anyways:foot"]
end
if (result == nil) then
result = "no"
end
return result
end
--[[
Gives 0 on private roads, 0.1 on destination-only roads, and 0.9 on permissive roads; gives 1 by default. This helps to select roads with no access retrictions on them
Unit:
Created by
Originally defined in clean_permission_score.json
Uses tags: access
Used parameters:
Number of combintations: 5
Returns values:
]]
function clean_permission_score(parameters, tags, result)
local result
result = head(stringToTags({
access = {
private = -50,
destination = -3,
permissive = -1
}
}, tags))
if (result == nil) then
result = 0
end
return result
end
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 (math.abs(result.forward_speed - expected.speed) >= 0.001 and math.abs(result.backward_speed - expected.speed) >= 0.001) 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 (math.abs(result.forward - expected.priority) >= 0.001 and math.abs(result.backward - expected.priority) >= 0.001) 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 eq(a, b)
if (a == b) then
return "yes"
else
return "no"
end
end
function string_start(strt, s)
return string.sub(s, 1, string.len(strt)) == strt
end
-- every key starting with "_relation:<name>:XXX" is rewritten to "_relation:XXX"
function remove_relation_prefix(tags, name)
local new_tags = {}
for k, v in pairs(tags) do
local prefix = "_relation:" .. name .. ":";
if (string_start(prefix, k)) then
local new_key = "_relation:" .. string.sub(k, string.len(prefix) + 1) -- plus 1: sub uses one-based indexing to select the start
new_tags[new_key] = v
else
new_tags[k] = v
end
end
return new_tags
end
function firstArg(a, b)
-- it turns out that 'const' is a reserved token in some lua implementations
return a
end
function head(ls)
if(ls == nil) then
return nil
end
for _, v in pairs(ls) do
if(v ~= nil) then
return v
end
end
return nil
end
print("ERROR: stringToTag is needed. This should not happen")
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

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