Skip to content

Commit d946022

Browse files
authored
support BlockStatement in custom model (graphhopper#3004)
* initial version to support BlockStatement * single->leaf * use 'do' instead of 'then' * merge LeafStatement and BlockStatement back into a single class * stricter Statement ctor * minor doc fix * minor formatting
1 parent c00c089 commit d946022

File tree

23 files changed

+518
-210
lines changed

23 files changed

+518
-210
lines changed

client-hc/src/test/java/com/graphhopper/api/GraphHopperWebTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919

2020
import java.util.Arrays;
2121
import java.util.HashMap;
22-
import java.util.LinkedHashMap;
2322

23+
import static com.graphhopper.json.Statement.If;
2424
import static org.junit.jupiter.api.Assertions.*;
2525

2626
/**
@@ -92,9 +92,9 @@ public void customModel() throws JsonProcessingException {
9292
new GeometryFactory().createPolygon(area_2_coordinates),
9393
new HashMap<>()));
9494
CustomModel customModel = new CustomModel()
95-
.addToSpeed(Statement.If("road_class == MOTORWAY", Statement.Op.LIMIT, "80"))
96-
.addToPriority(Statement.If("surface == DIRT", Statement.Op.MULTIPLY, "0.7"))
97-
.addToPriority(Statement.If("surface == SAND", Statement.Op.MULTIPLY, "0.6"))
95+
.addToSpeed(If("road_class == MOTORWAY", Statement.Op.LIMIT, "80"))
96+
.addToPriority(If("surface == DIRT", Statement.Op.MULTIPLY, "0.7"))
97+
.addToPriority(If("surface == SAND", Statement.Op.MULTIPLY, "0.6"))
9898
.setDistanceInfluence(69d)
9999
.setHeadingPenalty(22)
100100
.setAreas(areas);
@@ -109,7 +109,7 @@ public void customModel() throws JsonProcessingException {
109109
JsonNode customModelJson = postRequest.get("custom_model");
110110
ObjectMapper objectMapper = Jackson.newObjectMapper();
111111
JsonNode expected = objectMapper.readTree("{\"distance_influence\":69.0,\"heading_penalty\":22.0,\"internal\":false,\"areas\":{" +
112-
"\"type\":\"FeatureCollection\",\"features\":["+
112+
"\"type\":\"FeatureCollection\",\"features\":[" +
113113
"{\"id\":\"area_1\",\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[48.019324184801185,11.28021240234375],[48.019324184801185,11.53564453125],[48.11843396091691,11.53564453125],[48.11843396091691,11.28021240234375],[48.019324184801185,11.28021240234375]]]},\"properties\":{}}," +
114114
"{\"id\":\"area_2\",\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[48.15509285476017,11.53289794921875],[48.15509285476017,11.8212890625],[48.281365151571755,11.8212890625],[48.281365151571755,11.53289794921875],[48.15509285476017,11.53289794921875]]]},\"properties\":{}}]}," +
115115
"\"speed\":[{\"if\":\"road_class == MOTORWAY\",\"limit_to\":\"80\"}]," +

core/src/main/java/com/graphhopper/routing/TestProfiles.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static Profile constantSpeed(String name) {
3535
public static Profile constantSpeed(String name, double speed) {
3636
Profile profile = new Profile(name);
3737
CustomModel customModel = new CustomModel();
38-
customModel.addToSpeed(Statement.If("true", Statement.Op.LIMIT, String.valueOf(speed)));
38+
customModel.addToSpeed(If("true", Statement.Op.LIMIT, String.valueOf(speed)));
3939
profile.setCustomModel(customModel);
4040
return profile;
4141
}

core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import com.graphhopper.json.Statement;
2121
import com.graphhopper.routing.ev.*;
22-
import com.graphhopper.routing.util.EncodingManager;
2322
import com.graphhopper.routing.weighting.TurnCostProvider;
2423
import com.graphhopper.util.*;
2524
import com.graphhopper.util.shapes.BBox;
@@ -71,7 +70,7 @@ private CustomModelParser() {
7170
/**
7271
* This method creates a weighting from a CustomModel that must limit the speed. Either as an
7372
* unconditional statement <code>{ "if": "true", "limit_to": "car_average_speed" }<code/> or as
74-
* an if-else block.
73+
* an if-elseif-else group.
7574
*/
7675
public static CustomWeighting createWeighting(EncodedValueLookup lookup, TurnCostProvider turnCostProvider, CustomModel customModel) {
7776
if (customModel == null)
@@ -142,15 +141,15 @@ private static Class<?> createClazz(CustomModel customModel, EncodedValueLookup
142141
if (customModel.getSpeed().isEmpty())
143142
throw new IllegalArgumentException("At least one initial statement under 'speed' is required.");
144143

145-
List<Statement> firstBlock = splitIntoBlocks(customModel.getSpeed()).get(0);
146-
if (firstBlock.size() > 1) {
147-
Statement lastSt = firstBlock.get(firstBlock.size() - 1);
148-
if (lastSt.getOperation() != Statement.Op.LIMIT || lastSt.getKeyword() != Statement.Keyword.ELSE)
149-
throw new IllegalArgumentException("The first block needs to end with an 'else' (or contain a single unconditional 'if' statement).");
144+
List<Statement> firstGroup = splitIntoGroup(customModel.getSpeed()).get(0);
145+
if (firstGroup.size() > 1) {
146+
Statement lastSt = firstGroup.get(firstGroup.size() - 1);
147+
if (lastSt.operation() != Statement.Op.LIMIT || lastSt.keyword() != Statement.Keyword.ELSE)
148+
throw new IllegalArgumentException("The first group needs to end with an 'else' (or contain a single unconditional 'if' statement).");
150149
} else {
151-
Statement firstSt = firstBlock.get(0);
152-
if (!"true".equals(firstSt.getCondition()) || firstSt.getOperation() != Statement.Op.LIMIT || firstSt.getKeyword() != Statement.Keyword.IF)
153-
throw new IllegalArgumentException("The first block needs to contain a single unconditional 'if' statement (or end with an 'else').");
150+
Statement firstSt = firstGroup.get(0);
151+
if (!"true".equals(firstSt.condition()) || firstSt.operation() != Statement.Op.LIMIT || firstSt.keyword() != Statement.Keyword.IF)
152+
throw new IllegalArgumentException("The first group needs to contain a single unconditional 'if' statement (or end with an 'else').");
154153
}
155154

156155
Set<String> speedVariables = ValueExpressionVisitor.findVariables(customModel.getSpeed(), lookup);
@@ -191,27 +190,32 @@ public static List<String> findVariablesForEncodedValuesString(CustomModel model
191190
}
192191

193192
private static void findVariablesForEncodedValuesString(List<Statement> statements, NameValidator nameValidator, ClassHelper classHelper) {
194-
List<List<Statement>> blocks = CustomModelParser.splitIntoBlocks(statements);
195-
for (List<Statement> block : blocks) {
196-
for (Statement statement : block) {
197-
// ignore potential problems; collect only variables in this step
198-
ConditionalExpressionVisitor.parse(statement.getCondition(), nameValidator, classHelper);
199-
ValueExpressionVisitor.parse(statement.getValue(), nameValidator);
193+
List<List<Statement>> groups = CustomModelParser.splitIntoGroup(statements);
194+
for (List<Statement> group : groups) {
195+
for (Statement statement : group) {
196+
if (statement.isBlock()) {
197+
findVariablesForEncodedValuesString(statement.doBlock(), nameValidator, classHelper);
198+
} else {
199+
// ignore potential problems; collect only variables in this step
200+
ConditionalExpressionVisitor.parse(statement.condition(), nameValidator, classHelper);
201+
ValueExpressionVisitor.parse(statement.value(), nameValidator);
202+
}
200203
}
201204
}
202205
}
203206

204207
/**
205-
* Splits the specified list into several list of statements starting with if
208+
* Splits the specified list into several lists of statements starting with if.
209+
* I.e. a group consists of one 'if' and zero or more 'else_if' and 'else' statements.
206210
*/
207-
static List<List<Statement>> splitIntoBlocks(List<Statement> statements) {
211+
static List<List<Statement>> splitIntoGroup(List<Statement> statements) {
208212
List<List<Statement>> result = new ArrayList<>();
209-
List<Statement> block = null;
213+
List<Statement> group = null;
210214
for (Statement st : statements) {
211-
if (IF.equals(st.getKeyword())) result.add(block = new ArrayList<>());
212-
if (block == null)
213-
throw new IllegalArgumentException("Every block must start with an if-statement");
214-
block.add(st);
215+
if (IF.equals(st.keyword())) result.add(group = new ArrayList<>());
216+
if (group == null)
217+
throw new IllegalArgumentException("Every group must start with an if-statement");
218+
group.add(st);
215219
}
216220
return result;
217221
}
@@ -406,36 +410,52 @@ private static List<Java.BlockStatement> verifyExpressions(StringBuilder express
406410
|| name.startsWith(BACKWARD_PREFIX) && lookup.hasEncodedValue(name.substring(BACKWARD_PREFIX.length()));
407411
ClassHelper helper = key -> getReturnType(lookup.getEncodedValue(key, EncodedValue.class));
408412

409-
parseExpressions(expressions, nameInConditionValidator, info, createObjects, list, helper);
413+
parseExpressions(expressions, nameInConditionValidator, info, createObjects, list, helper, "");
414+
expressions.append("return value;\n");
410415
return new Parser(new org.codehaus.janino.Scanner(info, new StringReader(expressions.toString()))).
411416
parseBlockStatements();
412417
}
413418

414419
static void parseExpressions(StringBuilder expressions, NameValidator nameInConditionValidator,
415420
String exceptionInfo, Set<String> createObjects, List<Statement> list,
416-
ClassHelper classHelper) {
421+
ClassHelper classHelper, String indentation) {
417422

418423
for (Statement statement : list) {
419424
// avoid parsing the RHS value expression again as we just did it to get the maximum values in createClazz
420-
if (statement.getKeyword() == Statement.Keyword.ELSE) {
421-
if (!Helper.isEmpty(statement.getCondition()))
422-
throw new IllegalArgumentException("condition must be empty but was " + statement.getCondition());
423-
424-
expressions.append("else {").append(statement.getOperation().build(statement.getValue())).append("; }\n");
425-
} else if (statement.getKeyword() == Statement.Keyword.ELSEIF || statement.getKeyword() == Statement.Keyword.IF) {
426-
ParseResult parseResult = ConditionalExpressionVisitor.parse(statement.getCondition(), nameInConditionValidator, classHelper);
425+
if (statement.keyword() == Statement.Keyword.ELSE) {
426+
if (!Helper.isEmpty(statement.condition()))
427+
throw new IllegalArgumentException("condition must be empty but was " + statement.condition());
428+
429+
expressions.append(indentation);
430+
if (statement.isBlock()) {
431+
expressions.append("else {");
432+
parseExpressions(expressions, nameInConditionValidator, exceptionInfo, createObjects, statement.doBlock(), classHelper, indentation + " ");
433+
expressions.append(indentation).append("}\n");
434+
} else {
435+
expressions.append("else {").append(statement.operation().build(statement.value())).append("; }\n");
436+
}
437+
} else if (statement.keyword() == Statement.Keyword.ELSEIF || statement.keyword() == Statement.Keyword.IF) {
438+
ParseResult parseResult = ConditionalExpressionVisitor.parse(statement.condition(), nameInConditionValidator, classHelper);
427439
if (!parseResult.ok)
428-
throw new IllegalArgumentException(exceptionInfo + " invalid condition \"" + statement.getCondition() + "\"" +
440+
throw new IllegalArgumentException(exceptionInfo + " invalid condition \"" + statement.condition() + "\"" +
429441
(parseResult.invalidMessage == null ? "" : ": " + parseResult.invalidMessage));
430442
createObjects.addAll(parseResult.guessedVariables);
431-
if (statement.getKeyword() == Statement.Keyword.ELSEIF)
432-
expressions.append("else ");
433-
expressions.append("if (").append(parseResult.converted).append(") {").append(statement.getOperation().build(statement.getValue())).append(";}\n");
443+
if (statement.keyword() == Statement.Keyword.ELSEIF)
444+
expressions.append(indentation).append("else ");
445+
446+
expressions.append(indentation);
447+
if (statement.isBlock()) {
448+
expressions.append("if (").append(parseResult.converted).append(") {\n");
449+
parseExpressions(expressions, nameInConditionValidator, exceptionInfo, createObjects, statement.doBlock(), classHelper, indentation + " ");
450+
expressions.append(indentation).append("}\n");
451+
} else {
452+
expressions.append("if (").append(parseResult.converted).append(") {").
453+
append(statement.operation().build(statement.value())).append(";}\n");
454+
}
434455
} else {
435456
throw new IllegalArgumentException("The statement must be either 'if', 'else_if' or 'else'");
436457
}
437458
}
438-
expressions.append("return value;\n");
439459
}
440460

441461
/**

core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelper.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import com.graphhopper.json.MinMax;
2121
import com.graphhopper.json.Statement;
22-
import com.graphhopper.routing.ev.DecimalEncodedValue;
2322
import com.graphhopper.routing.ev.EncodedValueLookup;
2423
import com.graphhopper.util.*;
2524
import com.graphhopper.util.shapes.BBox;
@@ -79,8 +78,8 @@ public final double calcMaxSpeed() {
7978
public final double calcMaxPriority() {
8079
MinMax minMaxPriority = new MinMax(0, GLOBAL_PRIORITY);
8180
List<Statement> statements = customModel.getPriority();
82-
if (!statements.isEmpty() && "true".equals(statements.get(0).getCondition())) {
83-
String value = statements.get(0).getValue();
81+
if (!statements.isEmpty() && "true".equals(statements.get(0).condition())) {
82+
String value = statements.get(0).value();
8483
if (lookup.hasEncodedValue(value))
8584
minMaxPriority.max = lookup.getDecimalEncodedValue(value).getMaxOrMaxStorableDecimal();
8685
}

0 commit comments

Comments
 (0)