Repository: graphhopper/graphhopper (java)

Issue:
incorrect leg_distance entries if two stops are identical
If there are two identical stops the number of leg entries in the leg_distance (or leg_time) array will be incorrect because there is no separate edge created between these identical stops.

E.g. in RouteResourceClientHCTest.testWaypointIndicesAndLegDetails you can add `new GHPoint(42.495611, 1.498368)` to the point list and there will be an error `IndexOutOfBoundsException: Index 8 out of bounds for length 8` because the entry number of `leg_distance` is 8 and not 9 as it should be:

```
assertEquals(9, path.getPathDetails().get("leg_distance").size());
```

Pull request description:
Create leg_time/distance entries for duplicate via-points
Fixes #2915. This makes sure there are always N+1 leg_time/distance entries for requests with N via-points, even when some locations are duplicated. The interval of the path detail is empty for duplicate locations, because there are no edges inbetween. Here is the leg_time path detail list for the test mentioned in #2915:

![image](https://github.com/graphhopper/graphhopper/assets/17603532/2e4d972e-d665-4979-b0e3-1a3adfff2031)


Patch:
diff --git a/core/src/main/java/com/graphhopper/routing/ev/EnumEncodedValue.java b/core/src/main/java/com/graphhopper/routing/ev/EnumEncodedValue.java
index 555e1a369..1bec91183 100644
--- a/core/src/main/java/com/graphhopper/routing/ev/EnumEncodedValue.java
+++ b/core/src/main/java/com/graphhopper/routing/ev/EnumEncodedValue.java
@@ -61,8 +61,8 @@ public final class EnumEncodedValue<E extends Enum> extends IntEncodedValueImpl
         arr = enumType.getEnumConstants();
     }
 
-    public String getEnumSimpleName() {
-        return enumType.getSimpleName();
+    public Class<E> getEnumType() {
+        return enumType;
     }
 
     public E[] getValues() {
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/ClassHelper.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/ClassHelper.java
new file mode 100644
index 000000000..dc6318b61
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/ClassHelper.java
@@ -0,0 +1,5 @@
+package com.graphhopper.routing.weighting.custom;
+
+interface ClassHelper {
+    String getClassName(String encVal);
+}
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitor.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitor.java
index 7ad89132c..e030898a1 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitor.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitor.java
@@ -17,7 +17,6 @@
  */
 package com.graphhopper.routing.weighting.custom;
 
-import com.graphhopper.routing.ev.RouteNetwork;
 import com.graphhopper.util.Helper;
 import org.codehaus.janino.Scanner;
 import org.codehaus.janino.*;
@@ -38,11 +37,13 @@ class ConditionalExpressionVisitor implements Visitor.AtomVisitor<Boolean, Excep
     private final ParseResult result;
     private final TreeMap<Integer, Replacement> replacements = new TreeMap<>();
     private final NameValidator variableValidator;
+    private final ClassHelper classHelper;
     private String invalidMessage;
 
-    public ConditionalExpressionVisitor(ParseResult result, NameValidator variableValidator) {
+    public ConditionalExpressionVisitor(ParseResult result, NameValidator variableValidator, ClassHelper classHelper) {
         this.result = result;
         this.variableValidator = variableValidator;
+        this.classHelper = classHelper;
     }
 
     // allow only methods and other identifiers (constants and encoded values)
@@ -123,7 +124,7 @@ class ConditionalExpressionVisitor implements Visitor.AtomVisitor<Boolean, Excep
                     if (variableValidator.isValid(lhVarAsString) && Helper.toUpperCase(rhValueAsString).equals(rhValueAsString)) {
                         if (!eqOps)
                             throw new IllegalArgumentException("Operator " + binOp.operator + " not allowed for enum");
-                        String value = toEncodedValueClassName(binOp.lhs.toString());
+                        String value = classHelper.getClassName(binOp.lhs.toString());
                         replacements.put(startRH, new Replacement(startRH, rhValueAsString.length(), value + "." + rhValueAsString));
                     }
                 }
@@ -155,7 +156,7 @@ class ConditionalExpressionVisitor implements Visitor.AtomVisitor<Boolean, Excep
      * converted expression that includes class names for constants to avoid conflicts e.g. when doing "toll == Toll.NO"
      * instead of "toll == NO".
      */
-    static ParseResult parse(String expression, NameValidator validator) {
+    static ParseResult parse(String expression, NameValidator validator, ClassHelper helper) {
         ParseResult result = new ParseResult();
         try {
             Parser parser = new Parser(new Scanner("ignore", new StringReader(expression)));
@@ -163,7 +164,7 @@ class ConditionalExpressionVisitor implements Visitor.AtomVisitor<Boolean, Excep
             // after parsing the expression the input should end (otherwise it is not "simple")
             if (parser.peek().type == TokenType.END_OF_INPUT) {
                 result.guessedVariables = new LinkedHashSet<>();
-                ConditionalExpressionVisitor visitor = new ConditionalExpressionVisitor(result, validator);
+                ConditionalExpressionVisitor visitor = new ConditionalExpressionVisitor(result, validator, helper);
                 result.ok = atom.accept(visitor);
                 result.invalidMessage = visitor.invalidMessage;
                 if (result.ok) {
@@ -181,13 +182,6 @@ class ConditionalExpressionVisitor implements Visitor.AtomVisitor<Boolean, Excep
         return result;
     }
 
-    static String toEncodedValueClassName(String arg) {
-        if (arg.isEmpty()) throw new IllegalArgumentException("Cannot be empty");
-        if (arg.endsWith(RouteNetwork.key(""))) return RouteNetwork.class.getSimpleName();
-        String clazz = Helper.underScoreToCamelCase(arg);
-        return Character.toUpperCase(clazz.charAt(0)) + clazz.substring(1);
-    }
-
     static class Replacement {
         int start;
         int oldLength;
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java
index eebf6a760..75e580359 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java
@@ -245,8 +245,11 @@ public class CustomModelParser {
 
     private static String getReturnType(EncodedValue encodedValue) {
         // order is important
-        if (encodedValue instanceof EnumEncodedValue)
-            return ((EnumEncodedValue) encodedValue).getEnumSimpleName();
+        if (encodedValue instanceof EnumEncodedValue) {
+            Class cl = ((EnumEncodedValue) encodedValue).getEnumType();
+            // use getSimpleName for inbuilt EncodedValues and more readability of generated source
+            return cl.getPackage().equals(EnumEncodedValue.class.getPackage()) ? cl.getSimpleName() : cl.getName();
+        }
         if (encodedValue instanceof StringEncodedValue) return "int"; // we use indexOf
         if (encodedValue instanceof DecimalEncodedValue) return "double";
         if (encodedValue instanceof BooleanEncodedValue) return "boolean";
@@ -356,14 +359,16 @@ public class CustomModelParser {
         NameValidator nameInConditionValidator = name -> lookup.hasEncodedValue(name)
                 || name.toUpperCase(Locale.ROOT).equals(name) || name.startsWith(IN_AREA_PREFIX)
                 || name.startsWith(BACKWARD_PREFIX) && lookup.hasEncodedValue(name.substring(BACKWARD_PREFIX.length()));
+        ClassHelper helper = key -> getReturnType(lookup.getEncodedValue(key, EncodedValue.class));
 
-        parseExpressions(expressions, nameInConditionValidator, info, createObjects, list);
+        parseExpressions(expressions, nameInConditionValidator, info, createObjects, list, helper);
         return new Parser(new org.codehaus.janino.Scanner(info, new StringReader(expressions.toString()))).
                 parseBlockStatements();
     }
 
     static void parseExpressions(StringBuilder expressions, NameValidator nameInConditionValidator,
-                                 String exceptionInfo, Set<String> createObjects, List<Statement> list) {
+                                 String exceptionInfo, Set<String> createObjects, List<Statement> list,
+                                 ClassHelper helper) {
 
         for (Statement statement : list) {
             // avoid parsing the RHS value expression again as we just did it to get the maximum values in createClazz
@@ -373,7 +378,7 @@ public class CustomModelParser {
 
                 expressions.append("else {").append(statement.getOperation().build(statement.getValue())).append("; }\n");
             } else if (statement.getKeyword() == Statement.Keyword.ELSEIF || statement.getKeyword() == Statement.Keyword.IF) {
-                ParseResult parseResult = ConditionalExpressionVisitor.parse(statement.getCondition(), nameInConditionValidator);
+                ParseResult parseResult = ConditionalExpressionVisitor.parse(statement.getCondition(), nameInConditionValidator, helper);
                 if (!parseResult.ok)
                     throw new IllegalArgumentException(exceptionInfo + " invalid condition \"" + statement.getCondition() + "\"" +
                             (parseResult.invalidMessage == null ? "" : ": " + parseResult.invalidMessage));
diff --git a/core/src/main/java/com/graphhopper/util/details/ConstantDetailsBuilder.java b/core/src/main/java/com/graphhopper/util/details/ConstantDetailsBuilder.java
index d308e22ac..ff3e24ac9 100644
--- a/core/src/main/java/com/graphhopper/util/details/ConstantDetailsBuilder.java
+++ b/core/src/main/java/com/graphhopper/util/details/ConstantDetailsBuilder.java
@@ -18,8 +18,12 @@
 
 package com.graphhopper.util.details;
 
+import com.graphhopper.coll.MapEntry;
 import com.graphhopper.util.EdgeIteratorState;
 
+import java.util.List;
+import java.util.Map;
+
 /**
  * Simply returns the same value everywhere, useful to represent values that are the same between two (via-)points
  */
@@ -45,4 +49,12 @@ public class ConstantDetailsBuilder extends AbstractPathDetailsBuilder {
         } else
             return false;
     }
+
+    @Override
+    public Map.Entry<String, List<PathDetail>> build() {
+        if (firstEdge)
+            // #2915 if there was no edge at all we need to add a single entry manually here
+            return new MapEntry<>(getName(), List.of(new PathDetail(value)));
+        return super.build();
+    }
 }
diff --git a/pom.xml b/pom.xml
index a196a99ff..2ac8eb874 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,7 +75,7 @@
             <dependency>
                 <groupId>io.dropwizard</groupId>
                 <artifactId>dropwizard-dependencies</artifactId>
-                <version>2.1.6</version>
+                <version>2.1.11</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>


Test patch:
diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitorTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitorTest.java
index 04f9f3de6..8b65fc4ca 100644
--- a/core/src/test/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitorTest.java
+++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitorTest.java
@@ -43,33 +43,33 @@ public class ConditionalExpressionVisitorTest {
                 ") edge (",
                 "in(area_blup(), edge)",
                 "s -> truevalue")) {
-            ParseResult res = parse(toParse, allNamesInvalid);
+            ParseResult res = parse(toParse, allNamesInvalid, k -> "");
             assertFalse(res.ok, "should not be simple condition: " + toParse);
             assertTrue(res.guessedVariables == null || res.guessedVariables.isEmpty());
         }
 
-        assertFalse(parse("edge; getClass()", allNamesInvalid).ok);
+        assertFalse(parse("edge; getClass()", allNamesInvalid, k -> "").ok);
     }
 
     @Test
     public void testConvertExpression() {
         NameValidator validVariable = s -> Helper.toUpperCase(s).equals(s) || s.equals("road_class") || s.equals("toll");
 
-        ParseResult result = parse("toll == NO", validVariable);
+        ParseResult result = parse("toll == NO", validVariable, k -> "");
         assertTrue(result.ok);
         assertEquals("[toll]", result.guessedVariables.toString());
 
-        assertEquals("road_class == RoadClass.PRIMARY",
-                parse("road_class == PRIMARY", validVariable).converted.toString());
-        assertEquals("toll == Toll.NO", parse("toll == NO", validVariable).converted.toString());
-        assertEquals("toll == Toll.NO || road_class == RoadClass.NO", parse("toll == NO || road_class == NO", validVariable).converted.toString());
+        assertEquals("road_class == Hello.PRIMARY",
+                parse("road_class == PRIMARY", validVariable, k -> "Hello").converted.toString());
+        assertEquals("toll == Toll.NO", parse("toll == NO", validVariable, k -> "Toll").converted.toString());
+        assertEquals("toll == Toll.NO || road_class == RoadClass.NO", parse("toll == NO || road_class == NO", validVariable, k -> k.equals("toll") ? "Toll" : "RoadClass").converted.toString());
 
         // convert in_area variable to function call:
         assertEquals(CustomWeightingHelper.class.getSimpleName() + ".in(this.in_custom_1, edge)",
-                parse("in_custom_1", validVariable).converted.toString());
+                parse("in_custom_1", validVariable, k -> "").converted.toString());
 
         // no need to inject:
-        assertNull(parse("toll == Toll.NO", validVariable).converted);
+        assertNull(parse("toll == Toll.NO", validVariable, k -> "").converted);
     }
 
     @Test
@@ -77,59 +77,59 @@ public class ConditionalExpressionVisitorTest {
         NameValidator validVariable = s -> Helper.toUpperCase(s).equals(s)
                 || s.equals("road_class") || s.equals("toll") || s.equals("my_speed") || s.equals("backward_my_speed");
 
-        ParseResult result = parse("in_something", validVariable);
+        ParseResult result = parse("in_something", validVariable, k -> "");
         assertTrue(result.ok);
         assertEquals("[in_something]", result.guessedVariables.toString());
 
-        result = parse("edge == edge", validVariable);
+        result = parse("edge == edge", validVariable, k -> "");
         assertFalse(result.ok);
 
-        result = parse("Math.sqrt(my_speed)", validVariable);
+        result = parse("Math.sqrt(my_speed)", validVariable, k -> "");
         assertTrue(result.ok);
         assertEquals("[my_speed]", result.guessedVariables.toString());
 
-        result = parse("Math.sqrt(2)", validVariable);
+        result = parse("Math.sqrt(2)", validVariable, k -> "");
         assertTrue(result.ok);
         assertTrue(result.guessedVariables.isEmpty());
 
-        result = parse("edge.blup()", validVariable);
+        result = parse("edge.blup()", validVariable, k -> "");
         assertFalse(result.ok);
         assertTrue(result.guessedVariables.isEmpty());
 
-        result = parse("edge.getDistance()", validVariable);
+        result = parse("edge.getDistance()", validVariable, k -> "");
         assertTrue(result.ok);
         assertEquals("[edge]", result.guessedVariables.toString());
-        assertFalse(parse("road_class == PRIMARY", s -> false).ok);
-        result = parse("road_class == PRIMARY", validVariable);
+        assertFalse(parse("road_class == PRIMARY", s -> false, k -> "").ok);
+        result = parse("road_class == PRIMARY", validVariable, k -> "");
         assertTrue(result.ok);
         assertEquals("[road_class]", result.guessedVariables.toString());
 
-        result = parse("toll == Toll.NO", validVariable);
+        result = parse("toll == Toll.NO", validVariable, k -> "");
         assertFalse(result.ok);
         assertEquals("[toll]", result.guessedVariables.toString());
 
-        assertTrue(parse("road_class.ordinal()*2 == PRIMARY.ordinal()*2", validVariable).ok);
-        assertTrue(parse("Math.sqrt(road_class.ordinal()) > 1", validVariable).ok);
+        assertTrue(parse("road_class.ordinal()*2 == PRIMARY.ordinal()*2", validVariable, k -> "").ok);
+        assertTrue(parse("Math.sqrt(road_class.ordinal()) > 1", validVariable, k -> "").ok);
 
-        result = parse("(toll == NO || road_class == PRIMARY) && toll == NO", validVariable);
+        result = parse("(toll == NO || road_class == PRIMARY) && toll == NO", validVariable, k -> "");
         assertTrue(result.ok);
         assertEquals("[toll, road_class]", result.guessedVariables.toString());
 
-        result = parse("backward_my_speed", validVariable);
+        result = parse("backward_my_speed", validVariable, k -> "");
         assertTrue(result.ok);
         assertEquals("[backward_my_speed]", result.guessedVariables.toString());
     }
 
     @Test
     public void testNegativeConstant() {
-        ParseResult result = parse("average_slope < -0.5", "average_slope"::equals);
+        ParseResult result = parse("average_slope < -0.5", "average_slope"::equals, k -> "");
         assertTrue(result.ok);
         assertEquals("[average_slope]", result.guessedVariables.toString());
-        result = parse("-average_slope > -0.5", "average_slope"::equals);
+        result = parse("-average_slope > -0.5", "average_slope"::equals, k -> "");
         assertTrue(result.ok);
         assertEquals("[average_slope]", result.guessedVariables.toString());
 
-        result = parse("Math.sqrt(-2)", (var) -> false);
+        result = parse("Math.sqrt(-2)", (var) -> false, k -> "");
         assertTrue(result.ok);
         assertTrue(result.guessedVariables.isEmpty());
     }
diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java
index f91307ac9..0b927fa7d 100644
--- a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java
@@ -51,13 +51,17 @@ class CustomModelParserTest {
     EnumEncodedValue<State> stateEnc;
     double maxSpeed;
 
+    public enum MyBus {
+        MISSING, YES, DESIGNATED, DESTINATION, NO
+    }
+
     @BeforeEach
     void setup() {
         accessEnc = VehicleAccess.create("car");
         avgSpeedEnc = VehicleSpeed.create("car", 5, 5, false);
         countryEnc = Country.create();
         stateEnc = State.create();
-        encodingManager = new EncodingManager.Builder().add(accessEnc).add(avgSpeedEnc)
+        encodingManager = new EncodingManager.Builder().add(accessEnc).add(avgSpeedEnc).add(new EnumEncodedValue<>("bus", MyBus.class))
                 .add(stateEnc).add(countryEnc).add(MaxSpeed.create()).add(Surface.create()).build();
         graph = new BaseGraph.Builder(encodingManager).create();
         roadClassEnc = encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class);
@@ -286,21 +290,24 @@ class CustomModelParserTest {
         IllegalArgumentException ret = assertThrows(IllegalArgumentException.class,
                 () -> parseExpressions(new StringBuilder(),
                         validVariable, "[HERE]", new HashSet<>(),
-                        Arrays.asList(If("max_weight > 10", MULTIPLY, "0"))));
+                        Arrays.asList(If("max_weight > 10", MULTIPLY, "0")),
+                        key -> encodingManager.getEncodedValue(key, EncodedValue.class).getName()));
         assertTrue(ret.getMessage().startsWith("[HERE] invalid condition \"max_weight > 10\": 'max_weight' not available"), ret.getMessage());
 
         // invalid variable or constant (NameValidator returns false)
         ret = assertThrows(IllegalArgumentException.class,
                 () -> parseExpressions(new StringBuilder(),
                         validVariable, "[HERE]", new HashSet<>(),
-                        Arrays.asList(If("country == GERMANY", MULTIPLY, "0"))));
+                        Arrays.asList(If("country == GERMANY", MULTIPLY, "0")),
+                        key -> encodingManager.getEncodedValue(key, EncodedValue.class).getName()));
         assertTrue(ret.getMessage().startsWith("[HERE] invalid condition \"country == GERMANY\": 'GERMANY' not available"), ret.getMessage());
 
         // not whitelisted method
         ret = assertThrows(IllegalArgumentException.class,
                 () -> parseExpressions(new StringBuilder(),
                         validVariable, "[HERE]", new HashSet<>(),
-                        Arrays.asList(If("edge.fetchWayGeometry().size() > 2", MULTIPLY, "0"))));
+                        Arrays.asList(If("edge.fetchWayGeometry().size() > 2", MULTIPLY, "0")),
+                        key -> encodingManager.getEncodedValue(key, EncodedValue.class).getName()));
         assertTrue(ret.getMessage().startsWith("[HERE] invalid condition \"edge.fetchWayGeometry().size() > 2\": size is an illegal method"), ret.getMessage());
     }
 
@@ -319,4 +326,18 @@ class CustomModelParserTest {
         assertEquals(1.0, priorityMapping.get(edge2, false), 1.e-6);
     }
 
+    @Test
+    void testExternalEV() {
+        CustomModel customModel = new CustomModel();
+        customModel.addToPriority(If("bus == NO", MULTIPLY, "0.5"));
+        CustomWeighting.EdgeToDoubleMapping priorityMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager,
+                avgSpeedEnc, null).getEdgeToPriorityMapping();
+
+        BaseGraph graph = new BaseGraph.Builder(encodingManager).create();
+        EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(100).set(encodingManager.getEnumEncodedValue("bus", MyBus.class), MyBus.NO);
+        EdgeIteratorState edge2 = graph.edge(1, 2).setDistance(100);
+
+        assertEquals(0.5, priorityMapping.get(edge1, false), 1.e-6);
+        assertEquals(1.0, priorityMapping.get(edge2, false), 1.e-6);
+    }
 }
diff --git a/web/src/test/java/com/graphhopper/application/resources/RouteResourceClientHCTest.java b/web/src/test/java/com/graphhopper/application/resources/RouteResourceClientHCTest.java
index fb9b59fed..b5996af6b 100644
--- a/web/src/test/java/com/graphhopper/application/resources/RouteResourceClientHCTest.java
+++ b/web/src/test/java/com/graphhopper/application/resources/RouteResourceClientHCTest.java
@@ -397,18 +397,24 @@ public class RouteResourceClientHCTest {
                 addPoint(new GHPoint(42.49833, 1.504619)).
                 addPoint(new GHPoint(42.498217, 1.504377)).
                 addPoint(new GHPoint(42.495611, 1.498368)).
+                // #2915: duplicating the last point yields an empty leg, but there should still be path details for it
+                        addPoint(new GHPoint(42.495611, 1.498368)).
                 setPathDetails(legDetails).
                 setProfile("bike");
 
         GHResponse response = gh.route(req);
         ResponsePath path = response.getBest();
         assertEquals(5428, path.getDistance(), 5);
-        assertEquals(9, path.getWaypoints().size());
+        assertEquals(10, path.getWaypoints().size());
 
         assertEquals(path.getTime(), path.getPathDetails().get("leg_time").stream().mapToLong(d -> (long) d.getValue()).sum(), 1);
         assertEquals(path.getDistance(), path.getPathDetails().get("leg_distance").stream().mapToDouble(d -> (double) d.getValue()).sum(), 1);
         assertEquals(path.getRouteWeight(), path.getPathDetails().get("leg_weight").stream().mapToDouble(d -> (double) d.getValue()).sum(), 1);
 
+        assertEquals(9, path.getPathDetails().get("leg_time").size());
+        assertEquals(9, path.getPathDetails().get("leg_distance").size());
+        assertEquals(9, path.getPathDetails().get("leg_weight").size());
+
         List<PointList> pointListFromInstructions = getPointListFromInstructions(path);
         for (String detail : legDetails) {
             List<PathDetail> pathDetails = path.getPathDetails().get(detail);
@@ -423,7 +429,7 @@ public class RouteResourceClientHCTest {
                 assertEquals(path.getWaypoints().get(i), path.getPoints().get(pathDetails.get(i - 1).getLast()));
 
             List<PointList> pointListFromLegDetails = getPointListFromLegDetails(path, detail);
-            assertEquals(8, pointListFromLegDetails.size());
+            assertEquals(9, pointListFromLegDetails.size());
             assertPointListsEquals(pointListFromInstructions, pointListFromLegDetails);
         }
     }