Repository: graphhopper/graphhopper
Pull request #1586 (2019-03-29 21:04:57)

Fixes some issues with turn cost times.
Fixes two of the three issues mentioned in #1585.

Added some tests and did some cleanup in some tests, but the actual fixes are one-liners.

easbar: Will merge this and open separate issue for the turn cost issues discussed here.

Patch:
diff --git a/core/src/main/java/com/graphhopper/routing/PathBidirRef.java b/core/src/main/java/com/graphhopper/routing/PathBidirRef.java
index 684ecc9dd..1c52802bd 100644
--- a/core/src/main/java/com/graphhopper/routing/PathBidirRef.java
+++ b/core/src/main/java/com/graphhopper/routing/PathBidirRef.java
@@ -83,7 +83,7 @@ public class PathBidirRef extends Path {
         setFromNode(currEdge.adjNode);
         reverseOrder();
         currEdge = edgeTo;
-        int prevEdge = nextEdgeValid ? sptEntry.edge : EdgeIterator.NO_EDGE;
+        int prevEdge = EdgeIterator.Edge.isValid(sptEntry.edge) ? sptEntry.edge : EdgeIterator.NO_EDGE;
         int tmpEdge = currEdge.edge;
         while (EdgeIterator.Edge.isValid(tmpEdge)) {
             currEdge = currEdge.parent;
diff --git a/core/src/main/java/com/graphhopper/routing/util/TurnCostEncoder.java b/core/src/main/java/com/graphhopper/routing/util/TurnCostEncoder.java
index 1a99a8fb4..b745d59bc 100644
--- a/core/src/main/java/com/graphhopper/routing/util/TurnCostEncoder.java
+++ b/core/src/main/java/com/graphhopper/routing/util/TurnCostEncoder.java
@@ -18,7 +18,7 @@
 package com.graphhopper.routing.util;
 
 /**
- * Encodes and decodes a turn restriction and turn costs within a integer flag
+ * Encodes and decodes a turn restriction or turn costs within an integer flag
  *
  * @author Karl Hübner
  */
@@ -35,8 +35,7 @@ public interface TurnCostEncoder {
     double getTurnCost(long flags);
 
     /**
-     * @param restricted true if restricted turn, equivalent to specifying of costs
-     *                   Double.POSITIVE_INFINITY
+     * @param restricted true if restricted turn, equivalent to specifying costs = Double.POSITIVE_INFINITY
      * @param costs      the turn costs, specify 0 or Double.POSITIVE_INFINITY if restricted == true.
      *                   Only used if restricted == false.
      * @return the encoded flags
@@ -44,7 +43,7 @@ public interface TurnCostEncoder {
     long getTurnFlags(boolean restricted, double costs);
 
     /**
-     * No turn costs will be enabled by this encoder, should be used for pedestrians
+     * No turn costs will be enabled by this encoder, should be used for e.g. pedestrians
      */
     class NoTurnCostsEncoder implements TurnCostEncoder {
 
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/TurnWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/TurnWeighting.java
index 24ff798e8..dec115796 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/TurnWeighting.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/TurnWeighting.java
@@ -89,15 +89,14 @@ public class TurnWeighting implements Weighting {
         if (prevOrNextEdgeId == EdgeIterator.NO_EDGE)
             return millis;
 
-        // TODO for now assume turn costs are returned in milliseconds?
         // should we also separate weighting vs. time for turn? E.g. a fast but dangerous turn - is this common?
-        long turnCostsInMillis;
+        long turnCostsInSeconds;
         if (reverse)
-            turnCostsInMillis = (long) calcTurnWeight(edgeState.getEdge(), edgeState.getBaseNode(), prevOrNextEdgeId);
+            turnCostsInSeconds = (long) calcTurnWeight(edgeState.getEdge(), edgeState.getBaseNode(), prevOrNextEdgeId);
         else
-            turnCostsInMillis = (long) calcTurnWeight(prevOrNextEdgeId, edgeState.getBaseNode(), edgeState.getEdge());
+            turnCostsInSeconds = (long) calcTurnWeight(prevOrNextEdgeId, edgeState.getBaseNode(), edgeState.getEdge());
 
-        return millis + turnCostsInMillis;
+        return millis + 1000 * turnCostsInSeconds;
     }
 
     /**
diff --git a/core/src/main/java/com/graphhopper/storage/GraphBuilder.java b/core/src/main/java/com/graphhopper/storage/GraphBuilder.java
index 0636306da..fe2a6b416 100644
--- a/core/src/main/java/com/graphhopper/storage/GraphBuilder.java
+++ b/core/src/main/java/com/graphhopper/storage/GraphBuilder.java
@@ -95,7 +95,7 @@ public class GraphBuilder {
     /**
      * Default graph is a {@link GraphHopperStorage} with an in memory directory and disabled storing on flush.
      * Afterwards you'll need to call {@link GraphHopperStorage#create} to have a usable object. Better use
-     * {@link GraphHopperStorage#create} directly.
+     * {@link #create} directly.
      */
     public GraphHopperStorage build() {
         Directory dir = mmap ?
diff --git a/core/src/main/java/com/graphhopper/util/PathMerger.java b/core/src/main/java/com/graphhopper/util/PathMerger.java
index d775530f2..8faeec1bd 100644
--- a/core/src/main/java/com/graphhopper/util/PathMerger.java
+++ b/core/src/main/java/com/graphhopper/util/PathMerger.java
@@ -29,12 +29,12 @@ import java.util.Collections;
 import java.util.List;
 
 /**
- * This class merges multiple {@link Path} objects into one continues object that
+ * This class merges multiple {@link Path} objects into one continuous object that
  * can be used in the {@link PathWrapper}. There will be a Path between every waypoint.
  * So for two waypoints there will be only one Path object. For three waypoints there will be
  * two Path objects.
  * <p>
- * The instructions are generated per Path object and are merged into one continues InstructionList.
+ * The instructions are generated per Path object and are merged into one continuous InstructionList.
  * The PointList per Path object are merged and optionally simplified.
  *
  * @author Peter Karich


Test patch:
diff --git a/core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java b/core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java
index 67b0751eb..e9e39e371 100644
--- a/core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java
+++ b/core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java
@@ -19,7 +19,10 @@ package com.graphhopper.routing;
 
 import com.carrotsearch.hppc.IntArrayList;
 import com.carrotsearch.hppc.cursors.IntCursor;
-import com.graphhopper.routing.util.*;
+import com.graphhopper.routing.util.CarFlagEncoder;
+import com.graphhopper.routing.util.EncodingManager;
+import com.graphhopper.routing.util.FlagEncoder;
+import com.graphhopper.routing.util.TraversalMode;
 import com.graphhopper.routing.weighting.FastestWeighting;
 import com.graphhopper.routing.weighting.TurnWeighting;
 import com.graphhopper.routing.weighting.Weighting;
@@ -28,6 +31,7 @@ import com.graphhopper.storage.GraphBuilder;
 import com.graphhopper.storage.GraphHopperStorage;
 import com.graphhopper.storage.TurnCostExtension;
 import com.graphhopper.util.EdgeIteratorState;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -55,6 +59,8 @@ public class EdgeBasedRoutingAlgorithmTest {
     @Parameters(name = "{0}")
     public static Collection<Object[]> configs() {
         return Arrays.asList(new Object[][]{
+                // todo: make this test run also for edge-based CH or otherwise make sure time calculation is tested also for edge-based CH (at the moment it will fail!)
+                // todo: make this test run also for ALT or otherwise make sure time calculation is tested also for ALT (at the moment it will fail?!)
                 {DIJKSTRA},
                 {DIJKSTRA_BI},
                 {ASTAR},
@@ -81,7 +87,7 @@ public class EdgeBasedRoutingAlgorithmTest {
         g.edge(6, 7, 1, true);
     }
 
-    EncodingManager createEncodingManager(boolean restrictedOnly) {
+    private EncodingManager createEncodingManager(boolean restrictedOnly) {
         if (restrictedOnly)
             carEncoder = new CarFlagEncoder(5, 5, 1);
         else
@@ -95,38 +101,36 @@ public class EdgeBasedRoutingAlgorithmTest {
         return new RoutingAlgorithmFactorySimple().createAlgo(g, opts);
     }
 
-    protected GraphHopperStorage createStorage(EncodingManager em) {
+    private GraphHopperStorage createStorage(EncodingManager em) {
         return new GraphBuilder(em).create();
     }
 
-    private void initTurnRestrictions(Graph g, TurnCostExtension tcs, TurnCostEncoder tEncoder) {
-        long tflags = tEncoder.getTurnFlags(true, 0);
-
+    private void initTurnRestrictions(Graph g, TurnCostExtension tcs) {
         // only forward from 2-3 to 3-4 => limit 2,3->3,6 and 2,3->3,1
-        tcs.addTurnInfo(getEdge(g, 2, 3).getEdge(), 3, getEdge(g, 3, 6).getEdge(), tflags);
-        tcs.addTurnInfo(getEdge(g, 2, 3).getEdge(), 3, getEdge(g, 3, 1).getEdge(), tflags);
+        addTurnRestriction(g, tcs, 2, 3, 6);
+        addTurnRestriction(g, tcs, 2, 3, 1);
 
         // only right   from 5-2 to 2-3 => limit 5,2->2,0
-        tcs.addTurnInfo(getEdge(g, 5, 2).getEdge(), 2, getEdge(g, 2, 0).getEdge(), tflags);
+        addTurnRestriction(g, tcs, 5, 2, 0);
 
         // only right   from 7-6 to 6-3 => limit 7,6->6,5
-        tcs.addTurnInfo(getEdge(g, 7, 6).getEdge(), 6, getEdge(g, 6, 5).getEdge(), tflags);
+        addTurnRestriction(g, tcs, 7, 6, 5);
 
         // no 5-6 to 6-3
-        tcs.addTurnInfo(getEdge(g, 5, 6).getEdge(), 6, getEdge(g, 6, 3).getEdge(), tflags);
+        addTurnRestriction(g, tcs, 5, 6, 3);
         // no 4-3 to 3-1
-        tcs.addTurnInfo(getEdge(g, 4, 3).getEdge(), 3, getEdge(g, 3, 1).getEdge(), tflags);
+        addTurnRestriction(g, tcs, 4, 3, 1);
         // no 4-3 to 3-2
-        tcs.addTurnInfo(getEdge(g, 4, 3).getEdge(), 3, getEdge(g, 3, 2).getEdge(), tflags);
+        addTurnRestriction(g, tcs, 4, 3, 2);
 
         // no u-turn at 6-7
-        tcs.addTurnInfo(getEdge(g, 6, 7).getEdge(), 7, getEdge(g, 7, 6).getEdge(), tflags);
+        addTurnRestriction(g, tcs, 6, 7, 6);
 
         // no u-turn at 3-6
-        tcs.addTurnInfo(getEdge(g, 3, 6).getEdge(), 6, getEdge(g, 6, 3).getEdge(), tflags);
+        addTurnRestriction(g, tcs, 3, 6, 3);
     }
 
-    Weighting createWeighting(FlagEncoder encoder, TurnCostExtension tcs, double uTurnCosts) {
+    private Weighting createWeighting(FlagEncoder encoder, TurnCostExtension tcs, double uTurnCosts) {
         return new TurnWeighting(new FastestWeighting(encoder), tcs).setDefaultUTurnCost(uTurnCosts);
     }
 
@@ -135,40 +139,81 @@ public class EdgeBasedRoutingAlgorithmTest {
         GraphHopperStorage g = createStorage(createEncodingManager(true));
         initGraph(g);
         TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
-        initTurnRestrictions(g, tcs, carEncoder);
+        initTurnRestrictions(g, tcs);
         Path p = createAlgo(g, AlgorithmOptions.start().
                 weighting(createWeighting(carEncoder, tcs, 40)).
                 traversalMode(TraversalMode.EDGE_BASED_2DIR).build()).
                 calcPath(5, 1);
-        assertEquals(IntArrayList.from(new int[]{5, 2, 3, 4, 7, 6, 3, 1}), p.calcNodes());
+        assertEquals(IntArrayList.from(5, 2, 3, 4, 7, 6, 3, 1), p.calcNodes());
 
         // test 7-6-5 and reverse
         p = createAlgo(g, AlgorithmOptions.start().
                 weighting(createWeighting(carEncoder, tcs, 40)).
                 traversalMode(TraversalMode.EDGE_BASED_1DIR).build()).
                 calcPath(5, 7);
-        assertEquals(IntArrayList.from(new int[]{5, 6, 7}), p.calcNodes());
+        assertEquals(IntArrayList.from(5, 6, 7), p.calcNodes());
 
         p = createAlgo(g, AlgorithmOptions.start().
                 weighting(createWeighting(carEncoder, tcs, 40)).
                 traversalMode(TraversalMode.EDGE_BASED_1DIR).build()).
                 calcPath(7, 5);
-        assertEquals(IntArrayList.from(new int[]{7, 6, 3, 2, 5}), p.calcNodes());
+        assertEquals(IntArrayList.from(7, 6, 3, 2, 5), p.calcNodes());
+    }
+
+    @Test
+    public void testTurnCosts_timeCalculation() {
+        // 0 - 1 - 2 - 3 - 4
+        GraphHopperStorage g = createStorage(createEncodingManager(false));
+        TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
+        final int distance = 100;
+        final int turnCosts = 2;
+        g.edge(0, 1, distance, true);
+        g.edge(1, 2, distance, true);
+        g.edge(2, 3, distance, true);
+        g.edge(3, 4, distance, true);
+        addTurnCost(g, tcs, turnCosts, 1, 2, 3);
+
+        AlgorithmOptions opts = AlgorithmOptions.start()
+                .weighting(createWeighting(carEncoder, tcs, 40))
+                .traversalMode(TraversalMode.EDGE_BASED_2DIR)
+                .build();
+
+        {
+            // simple case where turn cost is encountered during forward search
+            Path p14 = createAlgo(g, opts).calcPath(1, 4);
+            assertDistTimeWeight(p14, 3, distance, 6, turnCosts);
+            assertEquals(20, p14.getWeight(), 1.e-6);
+            assertEquals(20000, p14.getTime());
+        }
+
+        {
+            // this test is more involved for bidir algos: the turn costs have to be taken into account also at the
+            // node where fwd and bwd searches meet
+            Path p04 = createAlgo(g, opts).calcPath(0, 4);
+            assertDistTimeWeight(p04, 4, distance, 6, turnCosts);
+            assertEquals(26, p04.getWeight(), 1.e-6);
+            assertEquals(26000, p04.getTime());
+        }
+    }
+
+    private void assertDistTimeWeight(Path path, int numEdges, double distPerEdge, double weightPerEdge, int turnCost) {
+        assertEquals("wrong distance", numEdges * distPerEdge, path.getDistance(), 1.e-6);
+        assertEquals("wrong weight", numEdges * weightPerEdge + turnCost, path.getWeight(), 1.e-6);
+        assertEquals("wrong time", 1000 * (numEdges * weightPerEdge + turnCost), path.getTime(), 1.e-6);
     }
 
 
-    private void blockNode3(Graph g, TurnCostExtension tcs, TurnCostEncoder tEncoder) {
+    private void blockNode3(Graph g, TurnCostExtension tcs) {
         // Totally block this node (all 9 turn relations)
-        final long BLOCK = tEncoder.getTurnFlags(true, 0);
-        tcs.addTurnInfo(getEdge(g, 2, 3).getEdge(), 3, getEdge(g, 3, 1).getEdge(), BLOCK);
-        tcs.addTurnInfo(getEdge(g, 2, 3).getEdge(), 3, getEdge(g, 3, 4).getEdge(), BLOCK);
-        tcs.addTurnInfo(getEdge(g, 4, 3).getEdge(), 3, getEdge(g, 3, 1).getEdge(), BLOCK);
-        tcs.addTurnInfo(getEdge(g, 4, 3).getEdge(), 3, getEdge(g, 3, 2).getEdge(), BLOCK);
-        tcs.addTurnInfo(getEdge(g, 6, 3).getEdge(), 3, getEdge(g, 3, 1).getEdge(), BLOCK);
-        tcs.addTurnInfo(getEdge(g, 6, 3).getEdge(), 3, getEdge(g, 3, 4).getEdge(), BLOCK);
-        tcs.addTurnInfo(getEdge(g, 1, 3).getEdge(), 3, getEdge(g, 3, 6).getEdge(), BLOCK);
-        tcs.addTurnInfo(getEdge(g, 1, 3).getEdge(), 3, getEdge(g, 3, 2).getEdge(), BLOCK);
-        tcs.addTurnInfo(getEdge(g, 1, 3).getEdge(), 3, getEdge(g, 3, 4).getEdge(), BLOCK);
+        addTurnRestriction(g, tcs, 2, 3, 1);
+        addTurnRestriction(g, tcs, 2, 3, 4);
+        addTurnRestriction(g, tcs, 4, 3, 1);
+        addTurnRestriction(g, tcs, 4, 3, 2);
+        addTurnRestriction(g, tcs, 6, 3, 1);
+        addTurnRestriction(g, tcs, 6, 3, 4);
+        addTurnRestriction(g, tcs, 1, 3, 6);
+        addTurnRestriction(g, tcs, 1, 3, 2);
+        addTurnRestriction(g, tcs, 1, 3, 4);
     }
 
     @Test
@@ -176,11 +221,11 @@ public class EdgeBasedRoutingAlgorithmTest {
         GraphHopperStorage g = createStorage(createEncodingManager(true));
         initGraph(g);
         TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
-        blockNode3(g, tcs, carEncoder);
-        for (int i=0; i<=7; i++) {
-            if (i==3) continue;
-            for (int j=0; j<=7; j++) {
-                if (j==3) continue;
+        blockNode3(g, tcs);
+        for (int i = 0; i <= 7; i++) {
+            if (i == 3) continue;
+            for (int j = 0; j <= 7; j++) {
+                if (j == 3) continue;
                 Path p = createAlgo(g, AlgorithmOptions.start().
                         weighting(createWeighting(carEncoder, tcs, 40)).
                         traversalMode(TraversalMode.EDGE_BASED_2DIR).build()).
@@ -199,31 +244,29 @@ public class EdgeBasedRoutingAlgorithmTest {
         initGraph(g);
         TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
 
-        long tflags = carEncoder.getTurnFlags(true, 0);
-
         // force u-turn via lowering the cost for it
         EdgeIteratorState e3_6 = getEdge(g, 3, 6);
         e3_6.setDistance(0.1);
         getEdge(g, 3, 2).setDistance(864);
         getEdge(g, 1, 0).setDistance(864);
 
-        tcs.addTurnInfo(getEdge(g, 7, 6).getEdge(), 6, getEdge(g, 6, 5).getEdge(), tflags);
-        tcs.addTurnInfo(getEdge(g, 4, 3).getEdge(), 3, e3_6.getEdge(), tflags);
+        addTurnRestriction(g, tcs, 7, 6, 5);
+        addTurnRestriction(g, tcs, 4, 3, 6);
         AlgorithmOptions opts = AlgorithmOptions.start().
                 weighting(createWeighting(carEncoder, tcs, 50)).
                 traversalMode(TraversalMode.EDGE_BASED_2DIR_UTURN).build();
         Path p = createAlgo(g, opts).calcPath(7, 5);
 
-        assertEquals(IntArrayList.from(new int[]{7, 6, 3, 6, 5}), p.calcNodes());
+        assertEquals(IntArrayList.from(7, 6, 3, 6, 5), p.calcNodes());
 
         // no u-turn for 6-3
         opts = AlgorithmOptions.start().
                 weighting(createWeighting(carEncoder, tcs, 100)).
                 traversalMode(TraversalMode.EDGE_BASED_2DIR_UTURN).build();
-        tcs.addTurnInfo(getEdge(g, 6, 3).getEdge(), 3, getEdge(g, 3, 6).getEdge(), tflags);
+        addTurnRestriction(g, tcs, 6, 3, 6);
         p = createAlgo(g, opts).calcPath(7, 5);
 
-        assertEquals(IntArrayList.from(new int[]{7, 6, 3, 2, 5}), p.calcNodes());
+        assertEquals(IntArrayList.from(7, 6, 3, 2, 5), p.calcNodes());
     }
 
     @Test
@@ -237,19 +280,17 @@ public class EdgeBasedRoutingAlgorithmTest {
                 calcPath(5, 1);
 
         // no restriction and costs
-        EdgeIteratorState e3_6 = getEdge(g, 5, 6);
-        e3_6.setDistance(2);
-        assertEquals(IntArrayList.from(new int[]{5, 2, 3, 1}), p.calcNodes());
+        assertEquals(IntArrayList.from(5, 2, 3, 1), p.calcNodes());
 
         // now introduce some turn costs
-        long tflags = carEncoder.getTurnFlags(false, 2);
-        tcs.addTurnInfo(getEdge(g, 5, 2).getEdge(), 2, getEdge(g, 2, 3).getEdge(), tflags);
+        getEdge(g, 5, 6).setDistance(2);
+        addTurnCost(g, tcs, 2, 5, 2, 3);
 
         p = createAlgo(g, AlgorithmOptions.start().
                 weighting(createWeighting(carEncoder, tcs, 40)).
                 traversalMode(TraversalMode.EDGE_BASED_1DIR).build()).
                 calcPath(5, 1);
-        assertEquals(IntArrayList.from(new int[]{5, 6, 3, 1}), p.calcNodes());
+        assertEquals(IntArrayList.from(5, 6, 3, 1), p.calcNodes());
     }
 
     @Test
@@ -258,13 +299,10 @@ public class EdgeBasedRoutingAlgorithmTest {
         initGraph(g);
         TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
 
-        long tflags = carEncoder.getTurnFlags(false, 2);
-        tcs.addTurnInfo(getEdge(g, 5, 2).getEdge(), 2, getEdge(g, 2, 3).getEdge(), tflags);
-        tcs.addTurnInfo(getEdge(g, 2, 0).getEdge(), 0, getEdge(g, 0, 1).getEdge(), tflags);
-        tcs.addTurnInfo(getEdge(g, 5, 6).getEdge(), 6, getEdge(g, 6, 3).getEdge(), tflags);
-
-        tflags = carEncoder.getTurnFlags(false, 1);
-        tcs.addTurnInfo(getEdge(g, 6, 7).getEdge(), 7, getEdge(g, 7, 4).getEdge(), tflags);
+        addTurnCost(g, tcs, 2, 5, 2, 3);
+        addTurnCost(g, tcs, 2, 2, 0, 1);
+        addTurnCost(g, tcs, 2, 5, 6, 3);
+        addTurnCost(g, tcs, 1, 6, 7, 4);
 
         Path p = createAlgo(g, AlgorithmOptions.start().
                 weighting(new TurnWeighting(new FastestWeighting(carEncoder), tcs) {
@@ -279,7 +317,23 @@ public class EdgeBasedRoutingAlgorithmTest {
                 }.setDefaultUTurnCost(40)).
                 traversalMode(TraversalMode.EDGE_BASED_2DIR).build()).
                 calcPath(5, 1);
-        assertEquals(IntArrayList.from(new int[]{5, 6, 7, 4, 3, 1}), p.calcNodes());
-        assertEquals(301, p.getTime(), .1);
+        assertEquals(IntArrayList.from(5, 6, 7, 4, 3, 1), p.calcNodes());
+        assertEquals(5 * 0.06 + 1, p.getWeight(), 1.e-6);
+        assertEquals(1300, p.getTime(), .1);
+    }
+
+    private void addTurnRestriction(Graph g, TurnCostExtension tcs, int from, int via, int to) {
+        long turnFlags = carEncoder.getTurnFlags(true, 0);
+        addTurnFlags(g, tcs, from, via, to, turnFlags);
     }
+
+    private void addTurnCost(Graph g, TurnCostExtension tcs, int costs, int from, int via, int to) {
+        long turnFlags = carEncoder.getTurnFlags(false, costs);
+        addTurnFlags(g, tcs, from, via, to, turnFlags);
+    }
+
+    private void addTurnFlags(Graph g, TurnCostExtension tcs, int from, int via, int to, long turnFlags) {
+        tcs.addTurnInfo(getEdge(g, from, via).getEdge(), via, getEdge(g, via, to).getEdge(), turnFlags);
+    }
+
 }
diff --git a/core/src/test/java/com/graphhopper/routing/PathBidirRefTest.java b/core/src/test/java/com/graphhopper/routing/PathBidirRefTest.java
index 54b1d9f1e..6ee7677a5 100644
--- a/core/src/test/java/com/graphhopper/routing/PathBidirRefTest.java
+++ b/core/src/test/java/com/graphhopper/routing/PathBidirRefTest.java
@@ -18,15 +18,15 @@
 package com.graphhopper.routing;
 
 import com.carrotsearch.hppc.IntArrayList;
-import com.graphhopper.routing.util.DefaultEdgeFilter;
-import com.graphhopper.routing.util.EdgeFilter;
+import com.graphhopper.routing.util.CarFlagEncoder;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.FlagEncoder;
 import com.graphhopper.routing.weighting.FastestWeighting;
+import com.graphhopper.routing.weighting.TurnWeighting;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.GraphBuilder;
 import com.graphhopper.storage.SPTEntry;
-import com.graphhopper.util.EdgeExplorer;
+import com.graphhopper.storage.TurnCostExtension;
 import com.graphhopper.util.EdgeIterator;
 import org.junit.Test;
 
@@ -36,9 +36,8 @@ import static org.junit.Assert.assertEquals;
  * @author Peter Karich
  */
 public class PathBidirRefTest {
-    private final EncodingManager encodingManager = EncodingManager.create("car");
-    private FlagEncoder carEncoder = encodingManager.getEncoder("car");
-    private EdgeFilter carOutEdges = DefaultEdgeFilter.outEdges(carEncoder);
+    private FlagEncoder carEncoder = new CarFlagEncoder(5, 5, 10);
+    private final EncodingManager encodingManager = EncodingManager.create(carEncoder);
 
     Graph createGraph() {
         return new GraphBuilder(encodingManager).create();
@@ -48,37 +47,38 @@ public class PathBidirRefTest {
     public void testExtract() {
         Graph g = createGraph();
         g.edge(1, 2, 10, true);
-        PathBidirRef pw = new PathBidirRef(g, new FastestWeighting(carEncoder));
-        EdgeExplorer explorer = g.createEdgeExplorer(carOutEdges);
-        EdgeIterator iter = explorer.setBaseNode(1);
-        iter.next();
-        pw.sptEntry = new SPTEntry(iter.getEdge(), 2, 0);
-        pw.sptEntry.parent = new SPTEntry(EdgeIterator.NO_EDGE, 1, 10);
-        pw.edgeTo = new SPTEntry(EdgeIterator.NO_EDGE, 2, 0);
-        Path p = pw.extract();
-        assertEquals(IntArrayList.from(new int[]{1, 2}), p.calcNodes());
+        PathBidirRef p = new PathBidirRef(g, new FastestWeighting(carEncoder));
+        p.sptEntry = new SPTEntry(0, 2, 0);
+        p.sptEntry.parent = new SPTEntry(EdgeIterator.NO_EDGE, 1, 10);
+        p.edgeTo = new SPTEntry(EdgeIterator.NO_EDGE, 2, 0);
+        p.extract();
+        assertEquals(IntArrayList.from(1, 2), p.calcNodes());
         assertEquals(10, p.getDistance(), 1e-4);
     }
 
     @Test
     public void testExtract2() {
+        // 1->2->3
         Graph g = createGraph();
         g.edge(1, 2, 10, false);
         g.edge(2, 3, 20, false);
-        EdgeExplorer explorer = g.createEdgeExplorer(carOutEdges);
-        EdgeIterator iter = explorer.setBaseNode(1);
-        iter.next();
-        PathBidirRef pw = new PathBidirRef(g, new FastestWeighting(carEncoder));
-        pw.sptEntry = new SPTEntry(iter.getEdge(), 2, 10);
-        pw.sptEntry.parent = new SPTEntry(EdgeIterator.NO_EDGE, 1, 0);
+        // add some turn costs at node 2 where fwd&bwd searches meet. these costs have to be included in the
+        // weight and the time of the path
+        TurnCostExtension turnCostExtension = (TurnCostExtension) g.getExtension();
+        turnCostExtension.addTurnInfo(0, 2, 1, carEncoder.getTurnFlags(false, 5));
 
-        explorer = g.createEdgeExplorer(DefaultEdgeFilter.inEdges(carEncoder));
-        iter = explorer.setBaseNode(3);
-        iter.next();
-        pw.edgeTo = new SPTEntry(iter.getEdge(), 2, 20);
-        pw.edgeTo.parent = new SPTEntry(EdgeIterator.NO_EDGE, 3, 0);
-        Path p = pw.extract();
-        assertEquals(IntArrayList.from(new int[]{1, 2, 3}), p.calcNodes());
+        PathBidirRef p = new PathBidirRef(g, new TurnWeighting(new FastestWeighting(carEncoder), turnCostExtension));
+        p.sptEntry = new SPTEntry(0, 2, 0.6);
+        p.sptEntry.parent = new SPTEntry(EdgeIterator.NO_EDGE, 1, 0);
+
+        p.edgeTo = new SPTEntry(1, 2, 1.2);
+        p.edgeTo.parent = new SPTEntry(EdgeIterator.NO_EDGE, 3, 0);
+        p.setWeight(5 + 1.8);
+
+        p.extract();
+        assertEquals(IntArrayList.from(1, 2, 3), p.calcNodes());
         assertEquals(30, p.getDistance(), 1e-4);
+        assertEquals(5 + 1.8, p.getWeight(), 1e-4);
+        assertEquals(5000 + 1800, p.getTime(), 1.e-6);
     }
 }
diff --git a/core/src/test/java/com/graphhopper/routing/weighting/TurnWeightingTest.java b/core/src/test/java/com/graphhopper/routing/weighting/TurnWeightingTest.java
new file mode 100644
index 000000000..b8c99fa66
--- /dev/null
+++ b/core/src/test/java/com/graphhopper/routing/weighting/TurnWeightingTest.java
@@ -0,0 +1,62 @@
+package com.graphhopper.routing.weighting;
+
+import com.graphhopper.routing.util.CarFlagEncoder;
+import com.graphhopper.routing.util.EncodingManager;
+import com.graphhopper.routing.util.FlagEncoder;
+import com.graphhopper.storage.Graph;
+import com.graphhopper.storage.GraphBuilder;
+import com.graphhopper.storage.TurnCostExtension;
+import com.graphhopper.util.EdgeIteratorState;
+import org.junit.Before;
+import org.junit.Test;
+
+import static com.graphhopper.util.GHUtility.getEdge;
+import static org.junit.Assert.assertEquals;
+
+public class TurnWeightingTest {
+
+    private Graph graph;
+    private FlagEncoder encoder;
+    private EncodingManager encodingManager;
+    private Weighting weighting;
+    private TurnCostExtension turnCostExt;
+
+    @Before
+    public void setup() {
+        encoder = new CarFlagEncoder(5, 5, 10);
+        encodingManager = EncodingManager.create(encoder);
+        graph = new GraphBuilder(encodingManager).create();
+        weighting = new FastestWeighting(encoder);
+        turnCostExt = (TurnCostExtension) graph.getExtension();
+    }
+
+    @Test
+    public void calcWeightAndTime_withTurnCosts() {
+        graph.edge(0, 1, 100, true);
+        EdgeIteratorState edge = graph.edge(1, 2, 100, true);
+        // turn costs are given in seconds
+        addTurnCost(0, 1, 2, 5);
+        TurnWeighting turnWeighting = new TurnWeighting(weighting, turnCostExt);
+        assertEquals(6 + 5, turnWeighting.calcWeight(edge, false, 0), 1.e-6);
+        assertEquals(6000 + 5000, turnWeighting.calcMillis(edge, false, 0), 1.e-6);
+    }
+
+    @Test
+    public void calcWeightAndTime_withTurnCosts_shortest() {
+        graph.edge(0, 1, 100, true);
+        EdgeIteratorState edge = graph.edge(1, 2, 100, true);
+        // turn costs are given in seconds
+        addTurnCost(0, 1, 2, 5);
+        TurnWeighting turnWeighting = new TurnWeighting(new ShortestWeighting(encoder), turnCostExt);
+        // todo: for the shortest weighting turn costs cannot be interpreted as seconds ? at least when they are added
+        // to the weight ? how much should they contribute ?
+//        assertEquals(105, turnWeighting.calcWeight(edge, false, 0), 1.e-6);
+        assertEquals(6000 + 5000, turnWeighting.calcMillis(edge, false, 0), 1.e-6);
+    }
+
+    private void addTurnCost(int from, int via, int to, double turnCost) {
+        long turnFlags = encoder.getTurnFlags(false, turnCost);
+        turnCostExt.addTurnInfo(getEdge(graph, from, via).getEdge(), via, getEdge(graph, via, to).getEdge(), turnFlags);
+    }
+
+}
\ No newline at end of file