Add unfolded generation of lua for better performance
This commit is contained in:
parent
aae20662e2
commit
3f0793ca22
42 changed files with 7629 additions and 325 deletions
AspectedRouting.Test
AspectedRouting.sln.DotSettings.userAspectedRouting
IO
LuaSkeleton
LuaSnippets
DefaultSnippet.csFirstMatchOfSnippet.csHeadSnippet.csIfThenElseDottedSnippet.csInvSnippet.csListFoldingSnippet.csLuaSnippet.csMaxSnippet.csMemberOfSnippet.csMinSnippet.csMultiplySnippet.csSimpleMappingSnippet.csSnippets.csSumSnippet.cs
lua
Language
Program.csUtils.csoutput-example
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
108
AspectedRouting.Test/Snippets/SnippetTests.cs
Normal file
108
AspectedRouting.Test/Snippets/SnippetTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
||||
|
|
37
AspectedRouting.Test/TypingTests.cs
Normal file
37
AspectedRouting.Test/TypingTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"><SessionState ContinuousTestingMode="0" IsActive="True" Name="Integration_TestExamples" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=3112587d_002D1c06_002D4ad3_002Da845_002D73105d4b723a/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="DefaultSnippet_SimpleDefault_GetsLua" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<TestAncestor>
|
||||
<TestId>xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.ExamplesTest.Integration_TestExamples</TestId>
|
||||
<TestId>xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.Snippets.SnippetTests</TestId>
|
||||
</TestAncestor>
|
||||
</SessionState></s:String>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=5e89d9f5_002D24ed_002D4ea2_002Da7ea_002Dcc7faea6d057/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" Name="JoinApply_Id" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<TestAncestor>
|
||||
<TestId>xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.TypingTests.JoinApply_Id</TestId>
|
||||
<TestId>xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.FunctionsTest.ApplyDefaultFunctionWithId_ApplicationIsSuccessfull</TestId>
|
||||
</TestAncestor>
|
||||
</SessionState></s:String>
|
||||
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=a6a74f48_002D8456_002D43c7_002Dbbee_002Dd3da33a8a4be/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" Name="Integration_TestExamples" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<Project Location="/home/pietervdvn/git/AspectedRouting/AspectedRouting.Test" Presentation="&lt;AspectedRouting.Test&gt;" />
|
||||
</SessionState></s:String>
|
||||
</wpf:ResourceDictionary>
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
25
AspectedRouting/IO/LuaSnippets/DefaultSnippet.cs
Normal file
25
AspectedRouting/IO/LuaSnippets/DefaultSnippet.cs
Normal 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";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
74
AspectedRouting/IO/LuaSnippets/FirstMatchOfSnippet.cs
Normal file
74
AspectedRouting/IO/LuaSnippets/FirstMatchOfSnippet.cs
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
55
AspectedRouting/IO/LuaSnippets/HeadSnippet.cs
Normal file
55
AspectedRouting/IO/LuaSnippets/HeadSnippet.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
41
AspectedRouting/IO/LuaSnippets/IfThenElseDottedSnippet.cs
Normal file
41
AspectedRouting/IO/LuaSnippets/IfThenElseDottedSnippet.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
17
AspectedRouting/IO/LuaSnippets/InvSnippet.cs
Normal file
17
AspectedRouting/IO/LuaSnippets/InvSnippet.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
151
AspectedRouting/IO/LuaSnippets/ListFoldingSnippet.cs
Normal file
151
AspectedRouting/IO/LuaSnippets/ListFoldingSnippet.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
27
AspectedRouting/IO/LuaSnippets/LuaSnippet.cs
Normal file
27
AspectedRouting/IO/LuaSnippets/LuaSnippet.cs
Normal 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);
|
||||
}
|
||||
}
|
15
AspectedRouting/IO/LuaSnippets/MaxSnippet.cs
Normal file
15
AspectedRouting/IO/LuaSnippets/MaxSnippet.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
37
AspectedRouting/IO/LuaSnippets/MemberOfSnippet.cs
Normal file
37
AspectedRouting/IO/LuaSnippets/MemberOfSnippet.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
15
AspectedRouting/IO/LuaSnippets/MinSnippet.cs
Normal file
15
AspectedRouting/IO/LuaSnippets/MinSnippet.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
14
AspectedRouting/IO/LuaSnippets/MultiplySnippet.cs
Normal file
14
AspectedRouting/IO/LuaSnippets/MultiplySnippet.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
46
AspectedRouting/IO/LuaSnippets/SimpleMappingSnippet.cs
Normal file
46
AspectedRouting/IO/LuaSnippets/SimpleMappingSnippet.cs
Normal 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
65
AspectedRouting/IO/LuaSnippets/Snippets.cs
Normal file
65
AspectedRouting/IO/LuaSnippets/Snippets.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
AspectedRouting/IO/LuaSnippets/SumSnippet.cs
Normal file
13
AspectedRouting/IO/LuaSnippets/SumSnippet.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
function parse(string)
|
||||
if (string == nil) then
|
||||
return 0
|
||||
return nil
|
||||
end
|
||||
if (type(string) == "number") then
|
||||
return string
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
print("ERROR: stringToTag is needed. This should not happen")
|
||||
function stringToTags(table, tags)
|
||||
return table_to_list(tags, {}, table)
|
||||
end
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
|
@ -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]";
|
||||
}
|
||||
|
||||
|
|
|
@ -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)" +
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
10
output-example/ProfileMetadata.json
Normal file
10
output-example/ProfileMetadata.json
Normal 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
1257
output-example/bicycle.lua
Normal file
File diff suppressed because it is too large
Load diff
1081
output-example/helpText.md
Normal file
1081
output-example/helpText.md
Normal file
File diff suppressed because it is too large
Load diff
844
output-example/itinero2/bicycle.comfort.lua
Normal file
844
output-example/itinero2/bicycle.comfort.lua
Normal 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
|
847
output-example/itinero2/bicycle.electric.lua
Normal file
847
output-example/itinero2/bicycle.electric.lua
Normal 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
|
672
output-example/itinero2/bicycle.fastest.lua
Normal file
672
output-example/itinero2/bicycle.fastest.lua
Normal 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
|
667
output-example/itinero2/bicycle.shortest.lua
Normal file
667
output-example/itinero2/bicycle.shortest.lua
Normal 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
|
444
output-example/itinero2/pedestrian.shortest.lua
Normal file
444
output-example/itinero2/pedestrian.shortest.lua
Normal 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
|
731
output-example/pedestrian.lua
Normal file
731
output-example/pedestrian.lua
Normal 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
|
Loading…
Add table
Reference in a new issue