diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index 9da73a1b2..8752226e6 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -22,12 +22,7 @@ import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; +import java.util.*; import btools.mapaccess.MatchedWaypoint; import btools.mapaccess.OsmPos; @@ -200,6 +195,29 @@ private List aggregateMessages() { return res; } + public void processWarnings(Set nodeWarnings, Set wayWarnings) { + + // this is just a proof of concept + // details TO BE DONE soon + // the idea is to examine key value pairs (osm tags) + // by 'chain' of 'warning detectors' + + System.out.println("\nOsmTrack: processWarnings"); + System.out.println("NodeWarnings: " + nodeWarnings); + System.out.println("WayWarnings: " + wayWarnings); + for (OsmPathElement n : nodes) { + System.out.println(); + if (n.message.wayKeyValues != null) { + System.out.println("way key values:"); + System.out.println(n.message.wayKeyValues); + } + if (n.message.nodeKeyValues != null) { + System.out.println("node key values:"); + System.out.println(n.message.nodeKeyValues); + } + } + } + private List aggregateSpeedProfile() { ArrayList res = new ArrayList<>(); int vmax = -1; diff --git a/brouter-core/src/main/java/btools/router/RoutingContext.java b/brouter-core/src/main/java/btools/router/RoutingContext.java index 5d7c8f116..d55e681c3 100644 --- a/brouter-core/src/main/java/btools/router/RoutingContext.java +++ b/brouter-core/src/main/java/btools/router/RoutingContext.java @@ -7,9 +7,7 @@ import java.io.DataOutput; import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import btools.expressions.BExpressionContext; import btools.expressions.BExpressionContextNode; @@ -37,6 +35,15 @@ public int getAlternativeIdx(int min, int max) { public Map keyValues; public String rawTrackPath; + public Set nodeWarnings = new HashSet<>(); + public Set wayWarnings = new HashSet<>(); + + // see ProfileCache.releaseProfile() + // we need to keep parsed warnings, node and way contexts are released (nullified) + public void copyWarnings(){ + nodeWarnings.addAll(expctxNode.getNodeWarnings()); + wayWarnings.addAll(expctxWay.getWayWarnings()); + } public String getProfileName() { String name = localFunction == null ? "unknown" : localFunction; diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index 16813878d..033780ad8 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -119,7 +119,8 @@ public RoutingEngine(String outfileBase, String logfileBase, File segmentDir, if (hasInfo()) { logInfo("parsed profile " + rc.localFunction + " cached=" + cachedProfile); } - + // node and way contexts are released (nullified) + routingContext.copyWarnings(); } private boolean hasInfo() { diff --git a/brouter-core/src/main/java/btools/router/warning/MessageDataWarnings.java b/brouter-core/src/main/java/btools/router/warning/MessageDataWarnings.java new file mode 100644 index 000000000..7f9222b00 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/warning/MessageDataWarnings.java @@ -0,0 +1,48 @@ +package btools.router.warning; + +import java.util.HashMap; +import java.util.Map; + +class MessageDataWarnings { + + // maybe empty + Map nodeTags; + // maybe empty + Map wayTags; + + MessageDataWarnings(String nodeKeyValues, String wayKeyValues) { + nodeTags = buildTags(nodeKeyValues); + wayTags = buildTags(wayKeyValues); + } + + // convert wayKeyValues, nodeKeyValues into fast lookup data + // sample of keyValues + // highway=path sac_scale=mountain_hiking route_hiking_rwn=yes + // we assume no multiple values for single key + // TODO Is it possible to use "all values for a key" syntax in the + // TODO lookup files? e.g. highway=* + // TODO this will be necessary for e.g. a seasonal access parsing. + // TODO Values like key=v1;v2;v3 + // TODO https://wiki.openstreetmap.org/wiki/Multiple_values + // TODO If we specify 'processUnusedTags = true', will such a value + // TODO be visible here? + + private static final String WHITESPACE_SEPARATOR = "[ ]"; + private static final String EQUALS_SEPARATOR = "[=]"; + + private Map buildTags(String keyValues) { + Map keyValuesMap = new HashMap<>(); + if (keyValues != null && !keyValues.isBlank()) { + String[] tokens = keyValues.trim().split(WHITESPACE_SEPARATOR); + for (String token : tokens) { + String[] kv = token.split(EQUALS_SEPARATOR); + if (kv.length == 2) { + keyValuesMap.put(kv[0].trim(), kv[1].trim()); + } else { + System.out.println("Unexpected token in: " + keyValues); + } + } + } + return keyValuesMap; + } +} diff --git a/brouter-core/src/main/java/btools/router/warning/impl/Detector.java b/brouter-core/src/main/java/btools/router/warning/impl/Detector.java new file mode 100644 index 000000000..86b6c5bde --- /dev/null +++ b/brouter-core/src/main/java/btools/router/warning/impl/Detector.java @@ -0,0 +1,4 @@ +package btools.router.warning.impl; + +interface Detector { +} diff --git a/brouter-core/src/main/java/btools/router/warning/impl/FordDetector.java b/brouter-core/src/main/java/btools/router/warning/impl/FordDetector.java new file mode 100644 index 000000000..58019f3a7 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/warning/impl/FordDetector.java @@ -0,0 +1,4 @@ +package btools.router.warning.impl; + +class FordDetector implements Detector { +} diff --git a/brouter-core/src/test/java/btools/router/warning/MessageDataWarningsTest.java b/brouter-core/src/test/java/btools/router/warning/MessageDataWarningsTest.java new file mode 100644 index 000000000..e9978795a --- /dev/null +++ b/brouter-core/src/test/java/btools/router/warning/MessageDataWarningsTest.java @@ -0,0 +1,55 @@ +package btools.router.warning; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class MessageDataWarningsTest { + + private static class TestResource { + String n; + String w; + int sizeN; + int sizeW; + String message; + + public TestResource(String n, String w, int sizeN, int sizeW, String message) { + this.n = n; + this.w = w; + this.sizeN = sizeN; + this.sizeW = sizeW; + this.message = message; + } + } + + @Test + public void testParseIntoStructureAssertSizes() { + List resources = new ArrayList<>(); + resources.add(new TestResource(null, null, 0, 0, "Empty for null values")); + resources.add(new TestResource("", " ", 0, 0, "Empty for empty or blank values")); + resources.add(new TestResource("key", "key-value", 0, 0, "Empty for not key=value")); + resources.add(new TestResource("key", "key-value", 0, 0, "Empty for not key=value")); + resources.add(new TestResource("key=value \n\t", " key=value key=value ", 1, 1, "Expected 1")); + for (TestResource tr : resources) { + MessageDataWarnings w = new MessageDataWarnings(tr.n, tr.w); + Assert.assertTrue(tr.message, w.nodeTags.size() == tr.sizeN && w.wayTags.size() == tr.sizeW); + } + } + + @Test + public void testSpecificCase(){ + MessageDataWarnings w = new MessageDataWarnings("key=value \n\t", " key0=value0 key0=value1 key1=value2 "); + Assert.assertEquals("value", w.nodeTags.get("key")); + Assert.assertEquals("value1", w.wayTags.get("key0")); + Assert.assertEquals("value2", w.wayTags.get("key1")); + } + + @Test + public void testNoUnexpectedToken(){ + // eyeball test there is no warning printed for proper syntax + MessageDataWarnings w = new MessageDataWarnings("key10=value10", "key20=value20 key20=value10 key100=value200"); + Assert.assertEquals(2, w.wayTags.size()); + } +} diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpression.java b/brouter-expressions/src/main/java/btools/expressions/BExpression.java index 5bd0ba907..b949384dc 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpression.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpression.java @@ -1,6 +1,8 @@ package btools.expressions; -import java.util.StringTokenizer; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; final class BExpression { private static final int OR_EXP = 10; @@ -25,7 +27,8 @@ final class BExpression { private static final int VARIABLE_EXP = 34; private static final int FOREIGN_VARIABLE_EXP = 35; private static final int VARIABLE_GET_EXP = 36; - + private static final int WARNINGS_EXP = 37; + private static final String WARNINGS_DELIMITERS_REGEX_PATTERN = "[&]"; private int typ; private BExpression op1; private BExpression op2; @@ -34,6 +37,44 @@ final class BExpression { private int variableIdx; private int lookupNameIdx; private int[] lookupValueIdxArray; + private String warnings; + + public BExpression() { + } + + // for tests + protected BExpression(String warnings) { + this.warnings = warnings; + } + + private boolean isValidWarningToken(String token) { + return token != null && !token.isBlank(); + // maybe more validating conditions here? + } + + // Converts the raw value assigned to specially treated variable 'warnings' + // into a set of identifiers. + // See WARNINGS_DELIMITERS_REGEX. + // Always returns a set, maybe an empty set + public Set parseWarnings() { + if (warnings == null) { + return new HashSet<>(); + } else { + // do parse + Map> validityGroups = Stream + .of(warnings.split(WARNINGS_DELIMITERS_REGEX_PATTERN)) + .collect(Collectors.groupingBy(this::isValidWarningToken)); + List invalid = validityGroups.get(false); + if (invalid != null) { + System.out.println("Invalid warning identifier(s): " + invalid); + } + List valid = validityGroups.get(true); + if (valid == null) return new HashSet<>(); + return valid.stream() + .flatMap(v -> Stream.of(v.trim())) // " w1&w2 " + .collect(Collectors.toSet()); + } + } // Parse the expression and all subexpression public static BExpression parse(BExpressionContext ctx, int level) throws Exception { @@ -107,13 +148,24 @@ private static BExpression parse(BExpressionContext ctx, int level, String optio throw new IllegalArgumentException("variable name cannot contain '=': " + variable); if (variable.indexOf(':') >= 0) throw new IllegalArgumentException("cannot assign context-prefixed variable: " + variable); - exp.variableIdx = ctx.getVariableIdx(variable, true); - if (exp.variableIdx < ctx.getMinWriteIdx()) - throw new IllegalArgumentException("cannot assign to readonly variable " + variable); + // specially treated variable: warnings + if ("warnings".equals(variable)) { + String token = ctx.parseToken(); + // we need to support both assign warnings=value and assign warnings value syntax + if (token.equals("=")) { + token = ctx.parseToken(); + } + exp.warnings = token; + exp.typ = WARNINGS_EXP; + } else { + exp.variableIdx = ctx.getVariableIdx(variable, true); + if (exp.variableIdx < ctx.getMinWriteIdx()) + throw new IllegalArgumentException("cannot assign to readonly variable " + variable); + } } else if ("not".equals(operator)) { exp.typ = NOT_EXP; } else { - nops = 0; // check elemantary expressions + nops = 0; // check elementary expressions int idx = operator.indexOf('='); if (idx >= 0) { exp.typ = LOOKUP_EXP; @@ -176,7 +228,9 @@ private static BExpression parse(BExpressionContext ctx, int level, String optio } // parse operands if (nops > 0) { - exp.op1 = BExpression.parse(ctx, level + 1, exp.typ == ASSIGN_EXP ? "=" : null); + if (exp.typ != WARNINGS_EXP) { + exp.op1 = BExpression.parse(ctx, level + 1, exp.typ == ASSIGN_EXP ? "=" : null); + } } if (nops > 1) { if (ifThenElse) checkExpectedToken(ctx, "then"); @@ -228,6 +282,8 @@ public float evaluate(BExpressionContext ctx) { return op1.evaluate(ctx) != 0.f ? op2.evaluate(ctx) : op3.evaluate(ctx); case ASSIGN_EXP: return ctx.assign(variableIdx, op1.evaluate(ctx)); + case WARNINGS_EXP: + return (float) -1.0; case LOOKUP_EXP: return ctx.getLookupMatch(lookupNameIdx, lookupValueIdxArray); case NUMBER_EXP: diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index 2bfd2f01c..fa7dde149 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -9,15 +9,7 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.StringTokenizer; -import java.util.TreeMap; -import java.util.Locale; +import java.util.*; import btools.util.BitCoderContext; import btools.util.Crc32; @@ -54,6 +46,9 @@ public abstract class BExpressionContext implements IByteArrayUnifier { private float[] variableData; + // visible for way, node extending contexts + protected Set warnings = new HashSet<>(); + // hash-cache for function results private CacheNode probeCacheNode = new CacheNode(); @@ -788,6 +783,12 @@ public void parseFile(File file, String readOnlyContext) { expressionList = _parseFile(file); + // There can be multiple warnings assignments + // In the profile files, warnings can be closely associated with e.g. cost factor for ways, e.t.c + for (BExpression bex : expressionList) { + warnings.addAll(bex.parseWarnings()); + } + // determine the build-in variable indices String[] varNames = getBuildInVariableNames(); nBuildInVars = varNames.length; diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContextNode.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContextNode.java index 9120610de..7d4ce7dbd 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContextNode.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContextNode.java @@ -7,6 +7,8 @@ package btools.expressions; +import java.util.Set; + public final class BExpressionContextNode extends BExpressionContext { private static String[] buildInVariables = {"initialcost"}; @@ -19,6 +21,9 @@ public float getInitialcost() { return getBuildInVariable(0); } + public Set getNodeWarnings() { + return warnings; + } public BExpressionContextNode(BExpressionMetaData meta) { super("node", meta); diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContextWay.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContextWay.java index 5b37b7cee..f6ae4a78a 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContextWay.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContextWay.java @@ -8,6 +8,8 @@ import btools.codec.TagValueValidator; +import java.util.Set; + public final class BExpressionContextWay extends BExpressionContext implements TagValueValidator { private boolean decodeForbidden = true; @@ -86,6 +88,10 @@ public BExpressionContextWay(BExpressionMetaData meta) { super("way", meta); } + public Set getWayWarnings(){ + return warnings; + } + /** * Create an Expression-Context for way context * diff --git a/brouter-expressions/src/test/java/btools/expressions/ParseWarningsProfilesTest.java b/brouter-expressions/src/test/java/btools/expressions/ParseWarningsProfilesTest.java new file mode 100644 index 000000000..14d30deef --- /dev/null +++ b/brouter-expressions/src/test/java/btools/expressions/ParseWarningsProfilesTest.java @@ -0,0 +1,30 @@ +package btools.expressions; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; + +import static org.junit.Assert.assertNotNull; + +public class ParseWarningsProfilesTest { + + @Test + public void testWarningsAreParsedAndDistinct() throws IOException { + File workingDir = new File(".").getCanonicalFile(); + File profileDir = new File(workingDir, "../misc/profiles2/"); + File profile = new File(profileDir, "allWithWarn.brf"); + assertNotNull("Missing profile containing warnings", profile); + BExpressionMetaData meta = new BExpressionMetaData(); + BExpressionContext expctxWay = new BExpressionContextWay(meta); + BExpressionContext expctxNode = new BExpressionContextNode(meta); + meta.readMetaData(new File(profileDir, "lookups.dat")); + expctxWay.parseFile(profile, "global"); + expctxNode.parseFile(profile, "global"); + Assert.assertEquals(expctxNode.warnings, new HashSet<>(Arrays.asList("w4", "w5", "w6", "w8", "w7"))); + Assert.assertEquals(expctxWay.warnings, new HashSet<>(Arrays.asList("w3", "w1", "w2", "w0"))); + } +} diff --git a/brouter-expressions/src/test/java/btools/expressions/ParseWarningsTest.java b/brouter-expressions/src/test/java/btools/expressions/ParseWarningsTest.java new file mode 100644 index 000000000..a2d6b79f8 --- /dev/null +++ b/brouter-expressions/src/test/java/btools/expressions/ParseWarningsTest.java @@ -0,0 +1,57 @@ +package btools.expressions; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class ParseWarningsTest { + + private static class TestPair { + String sample; + int expectedResultSize; + + public TestPair(String sample, int expectedResultSize) { + this.sample = sample; + this.expectedResultSize = expectedResultSize; + } + } + + @Test + public void testParsedSamples() { + + // Expected syntax: + // ---context:way + // assign warnings = identifier1&identifier2 + // ... + // ---context:node + // assign warnings = identifier1&identifier2&identifier3 + + List pairs = new ArrayList<>(); + pairs.add(new TestPair(null, 0)); + pairs.add(new TestPair("", 0)); + pairs.add(new TestPair(" ", 0)); + pairs.add(new TestPair(" ", 0)); + pairs.add(new TestPair(" w", 1)); + pairs.add(new TestPair(" w ", 1)); + pairs.add(new TestPair("w1&w2", 2)); + pairs.add(new TestPair(" w1&w2 ", 2)); + pairs.add(new TestPair(" w1&w1 ", 1)); + pairs.add(new TestPair("&&&", 0)); + pairs.add(new TestPair(" &", 0)); + pairs.add(new TestPair("& & & ", 0)); + pairs.add(new TestPair("&w1&", 1)); + pairs.add(new TestPair("w1,w2", 1)); + pairs.add(new TestPair("walk-hike.difficult.very", 1)); + + for (TestPair tp : pairs) { + System.out.println(); + System.out.println(">" + tp.sample + "<"); + Set parsed = new BExpression(tp.sample).parseWarnings(); + System.out.println(parsed); + Assert.assertEquals(parsed.size(), tp.expectedResultSize); + } + } +} diff --git a/brouter-server/src/main/java/btools/server/request/ServerHandler.java b/brouter-server/src/main/java/btools/server/request/ServerHandler.java index fb8f520e6..ed4b1485a 100644 --- a/brouter-server/src/main/java/btools/server/request/ServerHandler.java +++ b/brouter-server/src/main/java/btools/server/request/ServerHandler.java @@ -117,6 +117,12 @@ public List readWayPointList() { @Override public String formatTrack(OsmTrack track) { + + System.out.println("RoutingContext warnings node (format track): " + rc.nodeWarnings); + System.out.println("RoutingContext warnings way: " + rc.wayWarnings); + // this is a suggestion, hint, to be done + track.processWarnings(rc.nodeWarnings, rc.wayWarnings); + String result; // optional, may be null String format = params.get("format"); diff --git a/misc/profiles2/allWithWarn.brf b/misc/profiles2/allWithWarn.brf new file mode 100644 index 000000000..26f6c75af --- /dev/null +++ b/misc/profiles2/allWithWarn.brf @@ -0,0 +1,33 @@ +---context:global # following code refers to global config + +# the elevation parameters + +assign downhillcost 0 +assign downhillcutoff 1.5 +assign uphillcost 0 +assign uphillcutoff 1.5 + +---context:way # following code refers to way-tags + +assign warnings = w0 +assign warnings = w1&w2 + +assign turncost 0 +assign initialcost 0 + +assign warnings = w1&w2&w3 + +assign costfactor + switch not highway= 1 + switch not railway= 1 + switch not or waterway= waterway=unknown 1 + switch not route= 1 + 100000 + +---context:node # following code refers to node tags + +assign initialcost 0 + +assign warnings = w4&w5&w6 +assign warnings = w4&w5&w7 +assign warnings = w4&w5&w8