Repository: graphhopper/graphhopper
Pull request #1611 (2019-05-07 09:00:10)

Implements correct time calculation for edge-based CH.
This PR adjusts the path unpacking such that turn cost times are included (previously they were ignored) for edge-based CH, see #1585. 


Patch:
diff --git a/api/src/main/java/com/graphhopper/util/Helper.java b/api/src/main/java/com/graphhopper/util/Helper.java
index 11c227046..e7ed1df51 100644
--- a/api/src/main/java/com/graphhopper/util/Helper.java
+++ b/api/src/main/java/com/graphhopper/util/Helper.java
@@ -32,7 +32,6 @@ import java.util.*;
 import java.util.Map.Entry;
 
 /**
- *
  * @author Peter Karich
  */
 public class Helper {
@@ -64,11 +63,11 @@ public class Helper {
         return new Locale(param.substring(0, index), param.substring(index + 1));
     }
 
-    public static String toLowerCase(String string){
+    public static String toLowerCase(String string) {
         return string.toLowerCase(Locale.ROOT);
     }
 
-    public static String toUpperCase(String string){
+    public static String toUpperCase(String string) {
         return string.toUpperCase(Locale.ROOT);
     }
 
@@ -458,4 +457,18 @@ public class Helper {
 
         return sb.toString();
     }
+
+    /**
+     * Equivalent to java 8 String#join
+     */
+    public static String join(String delimiter, List<String> strings) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < strings.size(); i++) {
+            if (i > 0) {
+                sb.append(delimiter);
+            }
+            sb.append(strings.get(i));
+        }
+        return sb.toString();
+    }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/AbstractBidirectionEdgeCHNoSOD.java b/core/src/main/java/com/graphhopper/routing/AbstractBidirectionEdgeCHNoSOD.java
index 1fc1788be..ed9d5b9bf 100644
--- a/core/src/main/java/com/graphhopper/routing/AbstractBidirectionEdgeCHNoSOD.java
+++ b/core/src/main/java/com/graphhopper/routing/AbstractBidirectionEdgeCHNoSOD.java
@@ -18,7 +18,7 @@
 package com.graphhopper.routing;
 
 import com.graphhopper.routing.ch.CHEntry;
-import com.graphhopper.routing.ch.Path4CH;
+import com.graphhopper.routing.ch.EdgeBasedPathCH;
 import com.graphhopper.routing.util.DefaultEdgeFilter;
 import com.graphhopper.routing.util.EdgeFilter;
 import com.graphhopper.routing.util.TraversalMode;
@@ -93,7 +93,7 @@ public abstract class AbstractBidirectionEdgeCHNoSOD extends AbstractBidirAlgo {
             if (entry.getWeightOfVisitedPath() < bestPath.getWeight()) {
                 bestPath.setSwitchToFrom(reverse);
                 bestPath.setSPTEntry(entry);
-                bestPath.setSPTEntryTo(new SPTEntry(EdgeIterator.NO_EDGE, oppositeNode, 0));
+                bestPath.setSPTEntryTo(new CHEntry(oppositeNode, 0));
                 bestPath.setWeight(entry.getWeightOfVisitedPath());
                 return;
             }
@@ -134,7 +134,7 @@ public abstract class AbstractBidirectionEdgeCHNoSOD extends AbstractBidirAlgo {
 
     @Override
     protected Path createAndInitPath() {
-        bestPath = new Path4CH(graph, graph.getBaseGraph(), weighting);
+        bestPath = new EdgeBasedPathCH(graph, graph.getBaseGraph(), weighting);
         return bestPath;
     }
 
diff --git a/core/src/main/java/com/graphhopper/routing/Path.java b/core/src/main/java/com/graphhopper/routing/Path.java
index 2f32dcacf..f3fe8c985 100644
--- a/core/src/main/java/com/graphhopper/routing/Path.java
+++ b/core/src/main/java/com/graphhopper/routing/Path.java
@@ -234,8 +234,7 @@ public class Path {
     /**
      * Calculates the distance and time of the specified edgeId. Also it adds the edgeId to the path list.
      *
-     * @param prevEdgeId here the edge that comes before edgeId is necessary. I.e. for the reverse search we need the
-     *                   next edge.
+     * @param prevEdgeId the edge that comes before edgeId: --prevEdgeId-x-edgeId-->adjNode
      */
     protected void processEdge(int edgeId, int adjNode, int prevEdgeId) {
         EdgeIteratorState iter = graph.getEdgeIteratorState(edgeId, adjNode);
diff --git a/core/src/main/java/com/graphhopper/routing/PathBidirRef.java b/core/src/main/java/com/graphhopper/routing/PathBidirRef.java
index 1c52802bd..2c8dc99f3 100644
--- a/core/src/main/java/com/graphhopper/routing/PathBidirRef.java
+++ b/core/src/main/java/com/graphhopper/routing/PathBidirRef.java
@@ -17,10 +17,14 @@
  */
 package com.graphhopper.routing;
 
+import com.graphhopper.routing.weighting.TurnWeighting;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.SPTEntry;
 import com.graphhopper.util.EdgeIterator;
+import com.graphhopper.util.EdgeIteratorState;
+
+import static com.graphhopper.util.EdgeIterator.NO_EDGE;
 
 /**
  * This class creates a DijkstraPath from two Edge's resulting from a BidirectionalDijkstra
@@ -69,30 +73,77 @@ public class PathBidirRef extends Path {
             sptEntry = edgeTo;
             edgeTo = ee;
         }
+        extractFwdPath();
+        processTurnAtMeetingPoint();
+        extractBwdPath();
+        extractSW.stop();
+        return setFound(true);
+    }
+
+    private void extractFwdPath() {
+        // we take the 'edgeFrom'/sptEntry that points at the meeting node and follow its parent pointers back to
+        // the source
         SPTEntry currEdge = sptEntry;
-        boolean nextEdgeValid = EdgeIterator.Edge.isValid(currEdge.edge);
-        int nextEdge;
-        while (nextEdgeValid) {
-            // the reverse search needs the next edge
-            nextEdgeValid = EdgeIterator.Edge.isValid(currEdge.parent.edge);
-            nextEdge = nextEdgeValid ? currEdge.parent.edge : EdgeIterator.NO_EDGE;
-            processEdge(currEdge.edge, currEdge.adjNode, nextEdge);
-            currEdge = currEdge.parent;
+        SPTEntry prevEdge = currEdge.parent;
+        while (EdgeIterator.Edge.isValid(currEdge.edge)) {
+            processEdge(currEdge.edge, currEdge.adjNode, getIncEdge(prevEdge));
+            currEdge = prevEdge;
+            prevEdge = currEdge.parent;
         }
-
         setFromNode(currEdge.adjNode);
+        // since we followed the fwd path in backward direction we need to reverse the edge ids
         reverseOrder();
-        currEdge = edgeTo;
-        int prevEdge = EdgeIterator.Edge.isValid(sptEntry.edge) ? sptEntry.edge : EdgeIterator.NO_EDGE;
-        int tmpEdge = currEdge.edge;
-        while (EdgeIterator.Edge.isValid(tmpEdge)) {
-            currEdge = currEdge.parent;
-            processEdge(tmpEdge, currEdge.adjNode, prevEdge);
-            prevEdge = tmpEdge;
-            tmpEdge = currEdge.edge;
+    }
+
+    private void extractBwdPath() {
+        // we take the edgeTo at the meeting node and follow its parent pointers to the target
+        SPTEntry currEdge = edgeTo;
+        SPTEntry nextEdge = currEdge.parent;
+        while (EdgeIterator.Edge.isValid(currEdge.edge)) {
+            processEdgeBwd(currEdge.edge, currEdge.adjNode, getIncEdge(nextEdge));
+            currEdge = nextEdge;
+            nextEdge = nextEdge.parent;
         }
         setEndNode(currEdge.adjNode);
-        extractSW.stop();
-        return setFound(true);
+    }
+
+    private void processTurnAtMeetingPoint() {
+        processTurn(getIncEdge(sptEntry), sptEntry.adjNode, getIncEdge(edgeTo));
+    }
+
+    /**
+     * Similar to {@link #processEdge(int, int, int)}, but with the situation we encounter when doing a backward
+     * search: nextEdgeId--x<--edgeId--adjNode
+     */
+    protected void processEdgeBwd(int edgeId, int adjNode, int nextEdgeId) {
+        EdgeIteratorState edge = graph.getEdgeIteratorState(edgeId, adjNode);
+        distance += edge.getDistance();
+        // special case for loop edges: since they do not have a meaningful direction we always need to read them
+        // in the 'fwd' direction, but be careful the turn costs have to be applied with reverse = true, see also
+        // same comment in EdgeBasedPath4CH
+        // todonow: consolidate this!
+        if (edge.getBaseNode() == edge.getAdjNode()) {
+            long millis = weighting.calcMillis(edge, false, NO_EDGE);
+            if (weighting instanceof TurnWeighting && EdgeIterator.Edge.isValid(nextEdgeId)) {
+                millis += 1000 * (long) ((TurnWeighting) weighting).calcTurnWeight(edge.getEdge(), edge.getBaseNode(), nextEdgeId);
+            }
+            time += millis;
+        } else {
+            time += weighting.calcMillis(edge, true, nextEdgeId);
+        }
+        addEdge(edgeId);
+    }
+
+    private void processTurn(int inEdge, int viaNode, int outEdge) {
+        if (!EdgeIterator.Edge.isValid(inEdge) || !EdgeIterator.Edge.isValid(outEdge)) {
+            return;
+        }
+        if (weighting instanceof TurnWeighting) {
+            time += ((TurnWeighting) weighting).calcTurnWeight(inEdge, viaNode, outEdge) * 1000;
+        }
+    }
+
+    protected int getIncEdge(SPTEntry entry) {
+        return entry.edge;
     }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/QueryGraph.java b/core/src/main/java/com/graphhopper/routing/QueryGraph.java
index be54acba1..7e96ad677 100644
--- a/core/src/main/java/com/graphhopper/routing/QueryGraph.java
+++ b/core/src/main/java/com/graphhopper/routing/QueryGraph.java
@@ -749,6 +749,15 @@ public class QueryGraph implements Graph {
         return mainGraph.getOtherNode(edge, node);
     }
 
+    @Override
+    public boolean isAdjacentToNode(int edge, int node) {
+        if (isVirtualEdge(edge)) {
+            EdgeIteratorState virtualEdge = getEdgeIteratorState(edge, node);
+            return virtualEdge.getBaseNode() == node || virtualEdge.getAdjNode() == node;
+        }
+        return mainGraph.isAdjacentToNode(edge, node);
+    }
+
     private UnsupportedOperationException exc() {
         return new UnsupportedOperationException("QueryGraph cannot be modified.");
     }
diff --git a/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedPathCH.java b/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedPathCH.java
new file mode 100644
index 000000000..3b97f366b
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedPathCH.java
@@ -0,0 +1,55 @@
+package com.graphhopper.routing.ch;
+
+import com.graphhopper.routing.weighting.TurnWeighting;
+import com.graphhopper.routing.weighting.Weighting;
+import com.graphhopper.storage.Graph;
+import com.graphhopper.storage.SPTEntry;
+import com.graphhopper.storage.ShortcutUnpacker;
+import com.graphhopper.util.CHEdgeIteratorState;
+import com.graphhopper.util.EdgeIterator;
+import com.graphhopper.util.EdgeIteratorState;
+
+import static com.graphhopper.util.EdgeIterator.NO_EDGE;
+
+
+public class EdgeBasedPathCH extends Path4CH {
+
+    private final TurnWeighting turnWeighting;
+
+    public EdgeBasedPathCH(Graph routingGraph, Graph baseGraph, final Weighting weighting) {
+        super(routingGraph, baseGraph, weighting);
+        if (!(weighting instanceof TurnWeighting)) {
+            throw new IllegalArgumentException("Need a TurnWeighting for edge-based CH");
+        }
+        turnWeighting = (TurnWeighting) weighting;
+    }
+
+    @Override
+    protected ShortcutUnpacker getShortcutUnpacker(Graph routingGraph, final Weighting weighting) {
+        return new ShortcutUnpacker(routingGraph, new ShortcutUnpacker.Visitor() {
+            @Override
+            public void visit(EdgeIteratorState edge, boolean reverse, int prevOrNextEdgeId) {
+                distance += edge.getDistance();
+                // a one-way loop that is not a shortcut cannot possibly be read in the 'right' direction, because
+                // there is no way to distinguish the two directions. therefore we always read it in fwd direction.
+                // reverse still has to be considered to decide how to calculate the turn weight
+                // todo: turn cost clean-up, should we move this inside calcMillis ?
+                if (reverse && edge.getBaseNode() == edge.getAdjNode() && !((CHEdgeIteratorState) edge).isShortcut()) {
+                    long millis = weighting.calcMillis(edge, false, NO_EDGE);
+                    if (EdgeIterator.Edge.isValid(prevOrNextEdgeId)) {
+                        millis += 1000 * (long) turnWeighting.calcTurnWeight(edge.getEdge(), edge.getBaseNode(), prevOrNextEdgeId);
+                    }
+                    time += millis;
+                } else {
+                    time += weighting.calcMillis(edge, reverse, prevOrNextEdgeId);
+                }
+                addEdge(edge.getEdge());
+            }
+        }, true);
+    }
+
+    @Override
+    protected int getIncEdge(SPTEntry entry) {
+        return ((CHEntry) entry).incEdge;
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/routing/ch/Path4CH.java b/core/src/main/java/com/graphhopper/routing/ch/Path4CH.java
index d2d38ab9c..e77cadf5d 100644
--- a/core/src/main/java/com/graphhopper/routing/ch/Path4CH.java
+++ b/core/src/main/java/com/graphhopper/routing/ch/Path4CH.java
@@ -21,28 +21,38 @@ import com.graphhopper.routing.PathBidirRef;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.ShortcutUnpacker;
-import com.graphhopper.util.EdgeIterator;
 import com.graphhopper.util.EdgeIteratorState;
 
+import static com.graphhopper.util.EdgeIterator.NO_EDGE;
+
 public class Path4CH extends PathBidirRef {
     private final ShortcutUnpacker shortcutUnpacker;
 
     public Path4CH(Graph routingGraph, Graph baseGraph, final Weighting weighting) {
         super(baseGraph, weighting);
-        this.shortcutUnpacker = new ShortcutUnpacker(routingGraph, new ShortcutUnpacker.Visitor() {
-            @Override
-            public void visit(EdgeIteratorState edge, boolean reverse, int prevOrNextEdgeId) {
-                distance += edge.getDistance();
-                time += weighting.calcMillis(edge, reverse, EdgeIterator.NO_EDGE);
-                addEdge(edge.getEdge());
-            }
-        });
+        this.shortcutUnpacker = getShortcutUnpacker(routingGraph, weighting);
     }
 
     @Override
-    protected final void processEdge(int edgeId, int endNode, int prevEdgeId) {
+    protected final void processEdge(int edgeId, int adjNode, int prevEdgeId) {
         // Shortcuts do only contain valid weight so first expand before adding
         // to distance and time
-        shortcutUnpacker.visitOriginalEdges(edgeId, endNode, reverseOrder);
+        shortcutUnpacker.visitOriginalEdgesFwd(edgeId, adjNode, true, prevEdgeId);
+    }
+
+    @Override
+    protected void processEdgeBwd(int edgeId, int adjNode, int nextEdgeId) {
+        shortcutUnpacker.visitOriginalEdgesBwd(edgeId, adjNode, true, nextEdgeId);
+    }
+
+    protected ShortcutUnpacker getShortcutUnpacker(Graph routingGraph, final Weighting weighting) {
+        return new ShortcutUnpacker(routingGraph, new ShortcutUnpacker.Visitor() {
+            @Override
+            public void visit(EdgeIteratorState edge, boolean reverse, int prevOrNextEdgeId) {
+                distance += edge.getDistance();
+                time += weighting.calcMillis(edge, reverse, NO_EDGE);
+                addEdge(edge.getEdge());
+            }
+        }, false);
     }
 }
diff --git a/core/src/main/java/com/graphhopper/storage/BaseGraph.java b/core/src/main/java/com/graphhopper/storage/BaseGraph.java
index f2a938d17..7afcea9a1 100644
--- a/core/src/main/java/com/graphhopper/storage/BaseGraph.java
+++ b/core/src/main/java/com/graphhopper/storage/BaseGraph.java
@@ -800,6 +800,12 @@ class BaseGraph implements Graph {
         return edgeAccess.getOtherNode(node, edgePointer);
     }
 
+    @Override
+    public boolean isAdjacentToNode(int edge, int node) {
+        long edgePointer = edgeAccess.toPointer(edge);
+        return edgeAccess.isAdjacentToNode(node, edgePointer);
+    }
+
     public void setAdditionalEdgeField(long edgePointer, int value) {
         if (extStorage.isRequireEdgeField() && E_ADDITIONAL >= 0)
             edges.setInt(edgePointer + E_ADDITIONAL, value);
diff --git a/core/src/main/java/com/graphhopper/storage/CHGraphImpl.java b/core/src/main/java/com/graphhopper/storage/CHGraphImpl.java
index c142383c4..578a93404 100644
--- a/core/src/main/java/com/graphhopper/storage/CHGraphImpl.java
+++ b/core/src/main/java/com/graphhopper/storage/CHGraphImpl.java
@@ -60,7 +60,6 @@ public class CHGraphImpl implements CHGraph, Storable<CHGraph> {
     int shortcutEntryBytes;
     // the nodesCH storage is limited via baseGraph.nodeCount too
     int nodeCHEntryBytes;
-    final int shortcutBytesForFlags = 4;
     private int N_LEVEL;
     // shortcut memory layout is synced with edges indices until E_FLAGS, then:
     private int S_SKIP_EDGE1, S_SKIP_EDGE2, S_ORIG_FIRST, S_ORIG_LAST;
@@ -241,6 +240,13 @@ public class CHGraphImpl implements CHGraph, Storable<CHGraph> {
         return edgeAccess.getOtherNode(node, edgePointer);
     }
 
+    @Override
+    public boolean isAdjacentToNode(int edge, int node) {
+        EdgeAccess edgeAccess = isShortcut(edge) ? chEdgeAccess : baseGraph.edgeAccess;
+        long edgePointer = edgeAccess.toPointer(edge);
+        return edgeAccess.isAdjacentToNode(node, edgePointer);
+    }
+
     void _prepareForContraction() {
         if (isReadyForContraction) {
             return;
diff --git a/core/src/main/java/com/graphhopper/storage/EdgeAccess.java b/core/src/main/java/com/graphhopper/storage/EdgeAccess.java
index 54f4e429c..2231f73c4 100644
--- a/core/src/main/java/com/graphhopper/storage/EdgeAccess.java
+++ b/core/src/main/java/com/graphhopper/storage/EdgeAccess.java
@@ -148,6 +148,10 @@ abstract class EdgeAccess {
         return nodeThis == nodeA ? getNodeB(edgePointer) : nodeA;
     }
 
+    final boolean isAdjacentToNode(int node, long edgePointer) {
+        return getNodeA(edgePointer) == node || getNodeB(edgePointer) == node;
+    }
+
     /**
      * Writes plain edge information to the edges index
      */
diff --git a/core/src/main/java/com/graphhopper/storage/Graph.java b/core/src/main/java/com/graphhopper/storage/Graph.java
index 1713447e2..51f9c4d55 100644
--- a/core/src/main/java/com/graphhopper/storage/Graph.java
+++ b/core/src/main/java/com/graphhopper/storage/Graph.java
@@ -87,6 +87,11 @@ public interface Graph {
      */
     int getOtherNode(int edge, int node);
 
+    /**
+     * @return true if the edge with id edge is adjacent to node, false otherwise
+     */
+    boolean isAdjacentToNode(int edge, int node);
+
     /**
      * @return all edges in this graph, where baseNode will be the smaller node.
      */
diff --git a/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java b/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java
index 2d7a4509a..3c00cd840 100644
--- a/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java
+++ b/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java
@@ -86,12 +86,12 @@ public final class GraphHopperStorage implements GraphStorage, Graph {
             }
         };
 
-        this.baseGraph = new BaseGraph(dir, encodingManager, withElevation, listener, extendedStorage);
+        baseGraph = new BaseGraph(dir, encodingManager, withElevation, listener, extendedStorage);
         for (Weighting w : nodeBasedCHWeightings) {
-            nodeBasedCHGraphs.add(new CHGraphImpl(w, dir, this.baseGraph, false));
+            nodeBasedCHGraphs.add(new CHGraphImpl(w, dir, baseGraph, false));
         }
         for (Weighting w : edgeBasedCHWeightings) {
-            edgeBasedCHGraphs.add(new CHGraphImpl(w, dir, this.baseGraph, true));
+            edgeBasedCHGraphs.add(new CHGraphImpl(w, dir, baseGraph, true));
         }
     }
 
@@ -467,6 +467,11 @@ public final class GraphHopperStorage implements GraphStorage, Graph {
         return baseGraph.getOtherNode(edge, node);
     }
 
+    @Override
+    public boolean isAdjacentToNode(int edge, int node) {
+        return baseGraph.isAdjacentToNode(edge, node);
+    }
+
     private Collection<CHGraphImpl> getAllCHGraphs() {
         // todo: this method is only used to have a 'view' on the two collections. we could also create this only once
         // as long as the graph collections are only modified in the constructor (otherwise we would have to make sure
diff --git a/core/src/main/java/com/graphhopper/storage/ShortcutUnpacker.java b/core/src/main/java/com/graphhopper/storage/ShortcutUnpacker.java
index cf94ba55b..5effacd34 100644
--- a/core/src/main/java/com/graphhopper/storage/ShortcutUnpacker.java
+++ b/core/src/main/java/com/graphhopper/storage/ShortcutUnpacker.java
@@ -2,11 +2,12 @@ package com.graphhopper.storage;
 
 import com.graphhopper.routing.ch.PrepareContractionHierarchies;
 import com.graphhopper.util.CHEdgeIteratorState;
-import com.graphhopper.util.EdgeIterator;
 import com.graphhopper.util.EdgeIteratorState;
 
 import java.util.Locale;
 
+import static com.graphhopper.util.EdgeIterator.NO_EDGE;
+
 /**
  * Recursively unpack shortcuts.
  * <p>
@@ -18,11 +19,13 @@ import java.util.Locale;
 public class ShortcutUnpacker {
     private final Graph graph;
     private final Visitor visitor;
+    private final boolean edgeBased;
     private boolean reverseOrder;
 
-    public ShortcutUnpacker(Graph graph, Visitor visitor) {
+    public ShortcutUnpacker(Graph graph, Visitor visitor, boolean edgeBased) {
         this.graph = graph;
         this.visitor = visitor;
+        this.edgeBased = edgeBased;
     }
 
     /**
@@ -31,60 +34,85 @@ public class ShortcutUnpacker {
      *
      * @param reverseOrder if true the original edges will be traversed in reverse order
      */
-    public void visitOriginalEdges(int edgeId, int adjNode, boolean reverseOrder) {
+    public void visitOriginalEdgesFwd(int edgeId, int adjNode, boolean reverseOrder, int prevOrNextEdgeId) {
+        doVisitOriginalEdges(edgeId, adjNode, reverseOrder, false, prevOrNextEdgeId);
+    }
+
+    public void visitOriginalEdgesBwd(int edgeId, int adjNode, boolean reverseOrder, int prevOrNextEdgeId) {
+        doVisitOriginalEdges(edgeId, adjNode, reverseOrder, true, prevOrNextEdgeId);
+    }
+
+    private void doVisitOriginalEdges(int edgeId, int adjNode, boolean reverseOrder, boolean reverse, int prevOrNextEdgeId) {
         this.reverseOrder = reverseOrder;
         CHEdgeIteratorState edge = getEdge(edgeId, adjNode);
         if (edge == null) {
             throw new IllegalArgumentException("Edge with id: " + edgeId + " does not exist or does not touch node " + adjNode);
         }
-        expandEdge(edge, false);
+        expandEdge(edge, reverse, prevOrNextEdgeId);
     }
 
-    private void expandEdge(CHEdgeIteratorState edge, boolean reverse) {
+    private void expandEdge(CHEdgeIteratorState edge, boolean reverse, int prevOrNextEdgeId) {
         if (!edge.isShortcut()) {
-            // todo: should properly pass previous edge here. for example this is important for turn cost time evaluation
-            // with edge-based CH, #1585
-            visitor.visit(edge, reverse, EdgeIterator.NO_EDGE);
+            visitor.visit(edge, reverse, prevOrNextEdgeId);
             return;
         }
-        expandSkippedEdges(edge.getSkippedEdge1(), edge.getSkippedEdge2(), edge.getBaseNode(), edge.getAdjNode(), reverse);
+        if (edgeBased) {
+            expandSkippedEdgesEdgeBased(edge.getSkippedEdge1(), edge.getSkippedEdge2(), edge.getBaseNode(), edge.getAdjNode(), reverse, prevOrNextEdgeId);
+        } else {
+            expandSkippedEdgesNodeBased(edge.getSkippedEdge1(), edge.getSkippedEdge2(), edge.getBaseNode(), edge.getAdjNode(), reverse);
+        }
     }
 
-    private void expandSkippedEdges(int skippedEdge1, int skippedEdge2, int from, int to, boolean reverse) {
-        // for edge-based CH we need to take special care for loop shortcuts
-        if (from == to) {
-            CHEdgeIteratorState sk1 = getEdge(skippedEdge1, from);
-            CHEdgeIteratorState sk2 = getEdge(skippedEdge2, from);
-            if (sk1.getAdjNode() == sk1.getBaseNode() || sk2.getAdjNode() == sk2.getBaseNode()) {
-                // this is a loop where both skipped edges are loops. but this should never happen.
-                throw new IllegalStateException(String.format(Locale.ROOT,
-                        "error: detected edge where both skipped edges are loops. from: %d, to: %d, " +
-                                "skip-edge1: %d, skip-edge2: %d, reverse: %b", from, to, skippedEdge1, skippedEdge2, reverse));
-            }
+    private void expandSkippedEdgesEdgeBased(int skippedEdge1, int skippedEdge2, int base, int adj, boolean reverse, int prevOrNextEdgeId) {
+        if (reverse) {
+            int tmp = skippedEdge1;
+            skippedEdge1 = skippedEdge2;
+            skippedEdge2 = tmp;
+        }
+        CHEdgeIteratorState sk2 = getEdge(skippedEdge2, adj);
+        assert sk2 != null : "skipped edge " + skippedEdge2 + " + is not attached to adjNode " + adj + ". this should " +
+                "never happen because edge-based CH does not use bidirectional shortcuts at the moment";
+        CHEdgeIteratorState sk1 = getEdge(skippedEdge1, sk2.getBaseNode());
+        if (base == adj && (sk1.getAdjNode() == sk1.getBaseNode() || sk2.getAdjNode() == sk2.getBaseNode())) {
+            throw new IllegalStateException(String.format(Locale.ROOT,
+                    "error: detected edge where a skipped edges is a loop. this should never happen. base: %d, adj: %d, " +
+                            "skip-edge1: %d, skip-edge2: %d, reverse: %b", base, adj, skippedEdge1, skippedEdge2, reverse));
+        }
+        int adjEdge = getOppositeEdge(sk1, base);
+        if (reverseOrder) {
+            expandEdge(sk2, reverse, adjEdge);
+            expandEdge(sk1, reverse, prevOrNextEdgeId);
+        } else {
+            expandEdge(sk1, reverse, prevOrNextEdgeId);
+            expandEdge(sk2, reverse, adjEdge);
+        }
+    }
 
-            if (reverseOrder == reverse) {
-                expandEdge(sk1, !reverseOrder);
-                expandEdge(sk2, reverseOrder);
-            } else {
-                expandEdge(sk2, !reverseOrder);
-                expandEdge(sk1, reverseOrder);
-            }
+    private void expandSkippedEdgesNodeBased(int skippedEdge1, int skippedEdge2, int base, int adj, boolean reverse) {
+        CHEdgeIteratorState sk2 = getEdge(skippedEdge2, adj);
+        CHEdgeIteratorState sk1;
+        if (sk2 == null) {
+            sk2 = getEdge(skippedEdge1, adj);
+            sk1 = getEdge(skippedEdge2, sk2.getBaseNode());
         } else {
-            // get properties like speed of the edge in the correct direction
-            if (reverseOrder != reverse) {
-                int tmp = from;
-                from = to;
-                to = tmp;
-            }
-            CHEdgeIteratorState sk2to = getEdge(skippedEdge2, to);
-            if (sk2to != null) {
-                expandEdge(getEdge(skippedEdge1, from), !reverseOrder);
-                expandEdge(sk2to, reverseOrder);
-            } else {
-                expandEdge(getEdge(skippedEdge2, from), !reverseOrder);
-                expandEdge(getEdge(skippedEdge1, to), reverseOrder);
-            }
+            sk1 = getEdge(skippedEdge1, sk2.getBaseNode());
         }
+        if (reverseOrder) {
+            expandEdge(sk2, reverse, NO_EDGE);
+            expandEdge(sk1, reverse, NO_EDGE);
+        } else {
+            expandEdge(sk1, reverse, NO_EDGE);
+            expandEdge(sk2, reverse, NO_EDGE);
+        }
+    }
+
+    private int getOppositeEdge(CHEdgeIteratorState edgeState, int adjNode) {
+        assert edgeState.getBaseNode() == adjNode || edgeState.getAdjNode() == adjNode : "adjNode " + adjNode + " must be one of adj/base of edgeState: " + edgeState;
+        // since the first/last orig edge is not stateful (just like skipped1/2) we have to find out which one
+        // is attached to adjNode, similar as we do for skipped1/2.
+        return graph.isAdjacentToNode(edgeState.getOrigEdgeLast(), adjNode)
+                ? edgeState.getOrigEdgeFirst()
+                : edgeState.getOrigEdgeLast();
     }
 
     private CHEdgeIteratorState getEdge(int edgeId, int adjNode) {
diff --git a/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/GraphSupport.java b/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/GraphSupport.java
index 53b85c0ed..ad73a38a6 100644
--- a/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/GraphSupport.java
+++ b/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/GraphSupport.java
@@ -312,6 +312,11 @@ class GraphSupport {
             public int getOtherNode(int edge, int node) {
                 throw new UnsupportedOperationException();
             }
+
+            @Override
+            public boolean isAdjacentToNode(int edge, int node) {
+                throw new UnsupportedOperationException();
+            }
         };
     }
 }
diff --git a/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/RealtimeFeed.java b/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/RealtimeFeed.java
index dce55a2d2..e605759a3 100644
--- a/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/RealtimeFeed.java
+++ b/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/RealtimeFeed.java
@@ -254,6 +254,11 @@ public class RealtimeFeed {
             public int getOtherNode(int edge, int node) {
                 throw new UnsupportedOperationException();
             }
+
+            @Override
+            public boolean isAdjacentToNode(int edge, int node) {
+                throw new UnsupportedOperationException();
+            }
         };
 
         Map<GtfsStorage.Validity, Integer> operatingDayPatterns = new HashMap<>(staticGtfs.getOperatingDayPatterns());
diff --git a/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/WrapperGraph.java b/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/WrapperGraph.java
index 5f3605d91..adbb9bc77 100644
--- a/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/WrapperGraph.java
+++ b/reader-gtfs/src/main/java/com/graphhopper/reader/gtfs/WrapperGraph.java
@@ -544,4 +544,9 @@ public class WrapperGraph implements Graph {
     public int getOtherNode(int edge, int node) {
         return mainGraph.getOtherNode(edge, node);
     }
+
+    @Override
+    public boolean isAdjacentToNode(int edge, int node) {
+        return mainGraph.isAdjacentToNode(edge, node);
+    }
 }


Test patch:
diff --git a/core/src/test/java/com/graphhopper/routing/CHQueryWithTurnCostsTest.java b/core/src/test/java/com/graphhopper/routing/CHQueryWithTurnCostsTest.java
index d0b3452fe..f5d85bdfd 100644
--- a/core/src/test/java/com/graphhopper/routing/CHQueryWithTurnCostsTest.java
+++ b/core/src/test/java/com/graphhopper/routing/CHQueryWithTurnCostsTest.java
@@ -21,13 +21,17 @@ package com.graphhopper.routing;
 import com.carrotsearch.hppc.IntArrayList;
 import com.graphhopper.routing.ch.PreparationWeighting;
 import com.graphhopper.routing.ch.PrepareEncoder;
-import com.graphhopper.routing.util.CarFlagEncoder;
 import com.graphhopper.routing.util.EncodingManager;
+import com.graphhopper.routing.util.FlagEncoder;
 import com.graphhopper.routing.util.LevelEdgeFilter;
+import com.graphhopper.routing.util.MotorcycleFlagEncoder;
 import com.graphhopper.routing.weighting.ShortestWeighting;
 import com.graphhopper.routing.weighting.TurnWeighting;
 import com.graphhopper.routing.weighting.Weighting;
-import com.graphhopper.storage.*;
+import com.graphhopper.storage.CHGraph;
+import com.graphhopper.storage.GraphBuilder;
+import com.graphhopper.storage.GraphHopperStorage;
+import com.graphhopper.storage.TurnCostExtension;
 import com.graphhopper.util.CHEdgeIteratorState;
 import com.graphhopper.util.EdgeIteratorState;
 import com.graphhopper.util.GHUtility;
@@ -50,7 +54,7 @@ import static org.junit.Assert.assertFalse;
 @RunWith(Parameterized.class)
 public class CHQueryWithTurnCostsTest {
     private final int maxCost = 10;
-    private final CarFlagEncoder encoder = new CarFlagEncoder(5, 5, maxCost);
+    private final FlagEncoder encoder = new MotorcycleFlagEncoder(5, 5, maxCost);
     private final EncodingManager encodingManager = EncodingManager.create(encoder);
     private final Weighting weighting = new ShortestWeighting(encoder);
     private final GraphHopperStorage graph = new GraphBuilder(encodingManager).setCHGraph(weighting).setEdgeBasedCH(true).create();
@@ -82,7 +86,7 @@ public class CHQueryWithTurnCostsTest {
         for (int i = 0; i < 3; ++i) {
             testPathCalculation(i, i, 0, IntArrayList.from(i));
         }
-        testPathCalculation(1, 2, 11, IntArrayList.from(1, 0, 2));
+        testPathCalculation(1, 2, 8, IntArrayList.from(1, 0, 2), 3);
         testPathCalculation(2, 1, 8, IntArrayList.from(2, 0, 1));
         testPathCalculation(0, 1, 3, IntArrayList.from(0, 1));
         testPathCalculation(0, 2, 5, IntArrayList.from(0, 2));
@@ -109,11 +113,18 @@ public class CHQueryWithTurnCostsTest {
         setLevelEqualToNodeIdForAllNodes();
 
         // note that we are using the shortest weighting but turn cost times are included whatsoever, see #1590
-        testPathCalculation(0, 1, 40, IntArrayList.from(0, 2, 4, 6, 5, 3, 1));
-        testPathCalculation(1, 0, 28, IntArrayList.from(1, 3, 5, 6, 4, 2, 0));
-        testPathCalculation(4, 3, 23, IntArrayList.from(4, 6, 5, 3));
+        testPathCalculation(0, 1, 26, IntArrayList.from(0, 2, 4, 6, 5, 3, 1), 14);
+        testPathCalculation(1, 0, 26, IntArrayList.from(1, 3, 5, 6, 4, 2, 0), 2);
+        testPathCalculation(4, 3, 17, IntArrayList.from(4, 6, 5, 3), 6);
         testPathCalculation(0, 0, 0, IntArrayList.from(0));
         testPathCalculation(4, 4, 0, IntArrayList.from(4));
+
+        // also check if distance and times (including turn costs) are calculated correctly
+        Path path = createAlgo().calcPath(0, 1);
+        assertEquals("wrong weight", 40, path.getWeight(), 1.e-3);
+        assertEquals("wrong distance", 26, path.getDistance(), 1.e-3);
+        double weightPerMeter = 0.06;
+        assertEquals("wrong time", (26 * weightPerMeter + 14) * 1000, path.getTime(), 1.e-3);
     }
 
     @Test
@@ -209,13 +220,13 @@ public class CHQueryWithTurnCostsTest {
         setLevelEqualToNodeIdForAllNodes();
 
         // when we are searching a path to the highest level node, the backward search will not expand any edges
-        testPathCalculation(1, 4, 19, IntArrayList.from(1, 2, 0, 3, 4));
-        testPathCalculation(2, 4, 10, IntArrayList.from(2, 0, 3, 4));
-        testPathCalculation(0, 4, 6, IntArrayList.from(0, 3, 4));
+        testPathCalculation(1, 4, 11, IntArrayList.from(1, 2, 0, 3, 4), 8);
+        testPathCalculation(2, 4, 7, IntArrayList.from(2, 0, 3, 4), 3);
+        testPathCalculation(0, 4, 5, IntArrayList.from(0, 3, 4), 1);
 
         // when we search a path to or start the search from a low level node both forward and backward searches run
-        testPathCalculation(1, 0, 11, IntArrayList.from(1, 2, 0));
-        testPathCalculation(0, 4, 6, IntArrayList.from(0, 3, 4));
+        testPathCalculation(1, 0, 6, IntArrayList.from(1, 2, 0), 5);
+        testPathCalculation(0, 4, 5, IntArrayList.from(0, 3, 4), 1);
     }
 
     @Test
@@ -236,7 +247,7 @@ public class CHQueryWithTurnCostsTest {
         addShortcut(3, 2, 1, 2, 1, 2, 4);
         setLevelEqualToNodeIdForAllNodes();
 
-        testPathCalculation(1, 4, 15, IntArrayList.from(1, 3, 0, 2, 4));
+        testPathCalculation(1, 4, 9, IntArrayList.from(1, 3, 0, 2, 4), 6);
     }
 
     @Test
@@ -260,11 +271,11 @@ public class CHQueryWithTurnCostsTest {
         setLevelEqualToNodeIdForAllNodes();
 
         // the turn costs have to be accounted for also when the shortcuts are used
-        testPathCalculation(2, 4, 19, IntArrayList.from(2, 3, 1, 0, 4));
-        testPathCalculation(1, 4, 6, IntArrayList.from(1, 0, 4));
-        testPathCalculation(2, 0, 16, IntArrayList.from(2, 3, 1, 0));
-        testPathCalculation(3, 4, 10, IntArrayList.from(3, 1, 0, 4));
-        testPathCalculation(2, 1, 11, IntArrayList.from(2, 3, 1));
+        testPathCalculation(2, 4, 11, IntArrayList.from(2, 3, 1, 0, 4), 8);
+        testPathCalculation(1, 4, 5, IntArrayList.from(1, 0, 4), 1);
+        testPathCalculation(2, 0, 9, IntArrayList.from(2, 3, 1, 0), 7);
+        testPathCalculation(3, 4, 7, IntArrayList.from(3, 1, 0, 4), 3);
+        testPathCalculation(2, 1, 6, IntArrayList.from(2, 3, 1), 5);
     }
 
     @Test
@@ -283,7 +294,7 @@ public class CHQueryWithTurnCostsTest {
 
         // no shortcuts here
         setLevelEqualToNodeIdForAllNodes();
-        testPathCalculation(0, 1, 18, IntArrayList.from(0, 2, 3, 1));
+        testPathCalculation(0, 1, 14, IntArrayList.from(0, 2, 3, 1), 4);
     }
 
     @Test
@@ -422,7 +433,7 @@ public class CHQueryWithTurnCostsTest {
         setLevelEqualToNodeIdForAllNodes();
 
         // without u-turns we need to take the loop
-        testPathCalculation(0, 1, 18, IntArrayList.from(0, 2, 3, 2, 1));
+        testPathCalculation(0, 1, 15, IntArrayList.from(0, 2, 3, 2, 1), 3);
 
         // additional check
         testPathCalculation(3, 1, 4, IntArrayList.from(3, 2, 1));
@@ -451,7 +462,7 @@ public class CHQueryWithTurnCostsTest {
 
         // going via 2, 3 and 4 is possible, but we want the shortest path taking into account turn costs also at
         // the bridge node
-        testPathCalculation(0, 1, 7, IntArrayList.from(0, 3, 1));
+        testPathCalculation(0, 1, 5, IntArrayList.from(0, 3, 1), 2);
     }
 
     @Test
@@ -679,6 +690,13 @@ public class CHQueryWithTurnCostsTest {
     }
 
     private void testPathCalculation(int from, int to, int expectedWeight, IntArrayList expectedNodes) {
+        testPathCalculation(from, to, expectedWeight, expectedNodes, 0);
+    }
+
+    private void testPathCalculation(int from, int to, int expectedEdgeWeight, IntArrayList expectedNodes, int expectedTurnCost) {
+        int expectedWeight = expectedEdgeWeight + expectedTurnCost;
+        int expectedDistance = expectedEdgeWeight;
+        int expectedTime = expectedEdgeWeight * 60 + expectedTurnCost * 1000;
         AbstractBidirectionEdgeCHNoSOD algo = createAlgo();
         Path path = algo.calcPath(from, to);
         if (expectedWeight < 0) {
@@ -688,6 +706,8 @@ public class CHQueryWithTurnCostsTest {
                 assertEquals(String.format(Locale.ROOT, "Unexpected path from %d to %d", from, to), expectedNodes, path.calcNodes());
             }
             assertEquals(String.format(Locale.ROOT, "Unexpected path weight from %d to %d", from, to), expectedWeight, path.getWeight(), 1.e-6);
+            assertEquals(String.format(Locale.ROOT, "Unexpected path distance from %d to %d", from, to), expectedDistance, path.getDistance(), 1.e-6);
+            assertEquals(String.format(Locale.ROOT, "Unexpected path time from %d to %d", from, to), expectedTime, path.getTime());
         }
     }
 
diff --git a/core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java b/core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java
index 6ed16ab82..08dac53db 100644
--- a/core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java
+++ b/core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java
@@ -31,13 +31,15 @@ import com.graphhopper.storage.GraphBuilder;
 import com.graphhopper.storage.GraphHopperStorage;
 import com.graphhopper.storage.TurnCostExtension;
 import com.graphhopper.util.EdgeIteratorState;
+import com.graphhopper.util.GHUtility;
+import com.graphhopper.util.Helper;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-import java.util.Arrays;
-import java.util.Collection;
+import java.util.*;
 
 import static com.graphhopper.util.GHUtility.getEdge;
 import static com.graphhopper.util.Parameters.Algorithms.*;
@@ -133,6 +135,62 @@ public class EdgeBasedRoutingAlgorithmTest {
         return new TurnWeighting(new FastestWeighting(encoder), tcs).setDefaultUTurnCost(uTurnCosts);
     }
 
+    @Test
+    public void testRandomGraph() {
+        long seed = System.nanoTime();
+        final int numQueries = 100;
+        Random rnd = new Random(seed);
+        GraphHopperStorage g = createStorage(createEncodingManager(false));
+        TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
+        GHUtility.buildRandomGraph(g, rnd, 50, 2.2, true, true, carEncoder.getAverageSpeedEnc(), 0.8, 0.8, 0.8);
+        GHUtility.addRandomTurnCosts(g, seed, carEncoder, 3, tcs);
+        g.freeze();
+        int numPathsNotFound = 0;
+        // todo: reduce redundancy with RandomCHRoutingTest
+        List<String> strictViolations = new ArrayList<>();
+        for (int i = 0; i < numQueries; i++) {
+            int from = rnd.nextInt(g.getNodes());
+            int to = rnd.nextInt(g.getNodes());
+            Weighting w = createWeighting(carEncoder, tcs, 40);
+            RoutingAlgorithm refAlgo = new Dijkstra(g, w, TraversalMode.EDGE_BASED_2DIR);
+            Path refPath = refAlgo.calcPath(from, to);
+            double refWeight = refPath.getWeight();
+            if (!refPath.isFound()) {
+                numPathsNotFound++;
+                continue;
+            }
+
+            RoutingAlgorithm algo = createAlgo(g, AlgorithmOptions.start()
+                    .weighting(w)
+                    .traversalMode(TraversalMode.EDGE_BASED_2DIR)
+                    .build());
+            Path path = algo.calcPath(from, to);
+            if (!path.isFound()) {
+                fail("path not found for " + from + "->" + to + ", expected weight: " + refWeight);
+            }
+
+            double weight = path.getWeight();
+            if (Math.abs(refWeight - weight) > 1.e-2) {
+                System.out.println("expected: " + refPath.calcNodes());
+                System.out.println("given:    " + path.calcNodes());
+                fail("wrong weight: " + from + "->" + to + ", dijkstra: " + refWeight + " vs. " + algoStr + ": " + path.getWeight());
+            }
+            if (Math.abs(path.getDistance() - refPath.getDistance()) > 1.e-1) {
+                strictViolations.add("wrong distance " + from + "->" + to + ", expected: " + refPath.getDistance() + ", given: " + path.getDistance());
+            }
+            if (Math.abs(path.getTime() - refPath.getTime()) > 50) {
+                strictViolations.add("wrong time " + from + "->" + to + ", expected: " + refPath.getTime() + ", given: " + path.getTime());
+            }
+        }
+        if (numPathsNotFound > 0.9 * numQueries) {
+            fail("Too many paths not found: " + numPathsNotFound + "/" + numQueries);
+        }
+        if (strictViolations.size() > 0.05 * numQueries) {
+            fail("Too many strict violations: " + strictViolations.size() + "/" + numQueries + "\n" +
+                    Helper.join("\n", strictViolations));
+        }
+    }
+
     @Test
     public void testBasicTurnRestriction() {
         GraphHopperStorage g = createStorage(createEncodingManager(true));
@@ -346,12 +404,64 @@ public class EdgeBasedRoutingAlgorithmTest {
         assertEquals(1300, p.getTime(), .1);
     }
 
+    @Test
+    public void testLoopEdge() {
+        //   o
+        // 3-2-4
+        //  \|
+        //   0
+        final GraphHopperStorage g = createStorage(createEncodingManager(false));
+        g.edge(3, 2, 188, false);
+        g.edge(3, 0, 182, true);
+        g.edge(4, 2, 690, true);
+        g.edge(2, 2, 121, false);
+        g.edge(2, 0, 132, true);
+        TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
+        addTurnRestriction(g, tcs, 2, 2, 0);
+        addTurnRestriction(g, tcs, 3, 2, 4);
+
+        Path p = createAlgo(g, AlgorithmOptions.start().
+                weighting(createWeighting(carEncoder, tcs, 40)).
+                traversalMode(TraversalMode.EDGE_BASED_2DIR).build()).
+                calcPath(3, 4);
+        assertEquals(IntArrayList.from(3, 2, 2, 4), p.calcNodes());
+        assertEquals(999, p.getDistance(), 1.e-3);
+    }
+
+    @Test
+    public void testDoubleLoopPTurn() {
+        // we cannot go 1-4-5, but taking the loop at 4 is cheaper than taking the one at 3
+        //  0-1
+        //    |
+        // o3-4o
+        //    |
+        //    5
+        final GraphHopperStorage g = createStorage(createEncodingManager(false));
+        g.edge(0, 1, 1, true);
+        g.edge(3, 4, 2, true);
+        g.edge(4, 4, 4, true);
+        g.edge(3, 3, 1, true);
+        g.edge(1, 4, 5, true);
+        g.edge(5, 4, 1, true);
+        TurnCostExtension tcs = (TurnCostExtension) g.getExtension();
+        addTurnRestriction(g, tcs, 1, 4, 5);
+
+        Path p = createAlgo(g, AlgorithmOptions.start().
+                weighting(createWeighting(carEncoder, tcs, 40)).
+                traversalMode(TraversalMode.EDGE_BASED_2DIR).build()).
+                calcPath(0, 5);
+        assertEquals(IntArrayList.from(0, 1, 4, 4, 5), p.calcNodes());
+        assertEquals(11, p.getDistance(), 1.e-3);
+        assertEquals(11 * 0.06, p.getWeight(), 1.e-3);
+        assertEquals(11 * 0.06 * 1000, p.getTime(), 1.e-3);
+    }
+
     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) {
+    private void addTurnCost(Graph g, TurnCostExtension tcs, double costs, int from, int via, int to) {
         long turnFlags = carEncoder.getTurnFlags(false, costs);
         addTurnFlags(g, tcs, from, via, to, turnFlags);
     }
diff --git a/core/src/test/java/com/graphhopper/routing/RandomCHRoutingTest.java b/core/src/test/java/com/graphhopper/routing/RandomCHRoutingTest.java
index 7fadb425b..ab85c50f9 100644
--- a/core/src/test/java/com/graphhopper/routing/RandomCHRoutingTest.java
+++ b/core/src/test/java/com/graphhopper/routing/RandomCHRoutingTest.java
@@ -2,10 +2,7 @@ package com.graphhopper.routing;
 
 import com.graphhopper.routing.ch.PrepareContractionHierarchies;
 import com.graphhopper.routing.profiles.DecimalEncodedValue;
-import com.graphhopper.routing.util.CarFlagEncoder;
-import com.graphhopper.routing.util.EdgeFilter;
-import com.graphhopper.routing.util.EncodingManager;
-import com.graphhopper.routing.util.TraversalMode;
+import com.graphhopper.routing.util.*;
 import com.graphhopper.routing.weighting.FastestWeighting;
 import com.graphhopper.routing.weighting.TurnWeighting;
 import com.graphhopper.routing.weighting.Weighting;
@@ -14,6 +11,7 @@ import com.graphhopper.storage.index.LocationIndexTree;
 import com.graphhopper.storage.index.QueryResult;
 import com.graphhopper.util.EdgeIteratorState;
 import com.graphhopper.util.GHUtility;
+import com.graphhopper.util.Helper;
 import com.graphhopper.util.PMap;
 import com.graphhopper.util.shapes.BBox;
 import org.junit.Assume;
@@ -147,6 +145,7 @@ public class RandomCHRoutingTest {
 
             int numQueries = 100;
             int numPathsNotFound = 0;
+            List<String> strictViolations = new ArrayList<>();
             for (int i = 0; i < numQueries; i++) {
                 assertEquals("queryGraph and chQueryGraph should have equal number of nodes", queryGraph.getNodes(), chQueryGraph.getNodes());
                 int from = rnd.nextInt(queryGraph.getNodes());
@@ -170,15 +169,25 @@ public class RandomCHRoutingTest {
                 }
 
                 double weight = path.getWeight();
-                if (Math.abs(refWeight - weight) > 1.e-1) {
+                if (Math.abs(refWeight - weight) > 1.e-2) {
                     System.out.println("expected: " + refPath.calcNodes());
                     System.out.println("given:    " + path.calcNodes());
                     fail("wrong weight: " + from + "->" + to + ", dijkstra: " + refWeight + " vs. ch: " + path.getWeight());
                 }
+                if (Math.abs(path.getDistance() - refPath.getDistance()) > 1.e-1) {
+                    strictViolations.add("wrong distance " + from + "->" + to + ", expected: " + refPath.getDistance() + ", given: " + path.getDistance());
+                }
+                if (Math.abs(path.getTime() - refPath.getTime()) > 50) {
+                    strictViolations.add("wrong time " + from + "->" + to + ", expected: " + refPath.getTime() + ", given: " + path.getTime());
+                }
             }
             if (numPathsNotFound > 0.9 * numQueries) {
                 fail("Too many paths not found: " + numPathsNotFound + "/" + numQueries);
             }
+            if (strictViolations.size() > 0.05 * numQueries) {
+                fail("Too many strict violations: " + strictViolations.size() + "/" + numQueries + "\n" +
+                        Helper.join("\n", strictViolations));
+            }
         }
     }
 
diff --git a/core/src/test/java/com/graphhopper/routing/ch/CHTurnCostTest.java b/core/src/test/java/com/graphhopper/routing/ch/CHTurnCostTest.java
index 9b0c1f9d9..14d0e3283 100644
--- a/core/src/test/java/com/graphhopper/routing/ch/CHTurnCostTest.java
+++ b/core/src/test/java/com/graphhopper/routing/ch/CHTurnCostTest.java
@@ -61,6 +61,7 @@ public class CHTurnCostTest {
     private TurnCostExtension turnCostExtension;
     private TurnWeighting turnWeighting;
     private CHGraph chGraph;
+    private boolean checkStrict;
 
     @Rule
     public RepeatRule repeatRule = new RepeatRule();
@@ -76,6 +77,7 @@ public class CHTurnCostTest {
         turnCostExtension = (TurnCostExtension) graph.getExtension();
         turnWeighting = new TurnWeighting(weighting, turnCostExtension);
         chGraph = graph.getGraph(CHGraph.class);
+        checkStrict = true;
     }
 
     @Test
@@ -87,17 +89,9 @@ public class CHTurnCostTest {
         graph.edge(0, 3, 1, true);
         graph.edge(3, 4, 3, true);
         graph.freeze();
-
         addTurnCost(2, 1, 0, 2);
         addTurnCost(0, 3, 4, 4);
-
-        final IntArrayList expectedPath = IntArrayList.from(2, 1, 0, 3, 4);
-        final int expectedWeight = 15;
-
-        int from = 2;
-        int to = 4;
-
-        checkPathUsingRandomContractionOrder(expectedPath, expectedWeight, from, to);
+        checkPathUsingRandomContractionOrder(IntArrayList.from(2, 1, 0, 3, 4), 9, 6, 2, 4);
     }
 
     @Test
@@ -173,6 +167,7 @@ public class CHTurnCostTest {
 
         RoutingAlgorithmFactory factory = prepareCH(Arrays.asList(6, 0, 1, 2, 8, 9, 10, 5, 3, 4, 7));
         // run queries for all cases (target/source edge possibly restricted/has costs)
+        checkStrict = false;
         compareCHQueryWithDijkstra(factory, 2, 10);
         compareCHQueryWithDijkstra(factory, 1, 10);
         compareCHQueryWithDijkstra(factory, 2, 9);
@@ -237,10 +232,7 @@ public class CHTurnCostTest {
         addTurnCost(4, 2, 3, 4);
         addTurnCost(3, 2, 4, 2);
 
-        final IntArrayList expectedPath = IntArrayList.from(0, 4, 3, 2, 4, 1);
-        final int expectedWeight = 9;
-
-        checkPathUsingRandomContractionOrder(expectedPath, expectedWeight, 0, 1);
+        checkPathUsingRandomContractionOrder(IntArrayList.from(0, 4, 3, 2, 4, 1), 7, 2, 0, 1);
     }
 
     @Test
@@ -268,7 +260,7 @@ public class CHTurnCostTest {
         final int roadCosts = 12;
         final int turnCosts = 2;
 
-        checkPathUsingRandomContractionOrder(expectedPath, roadCosts + turnCosts, 3, 4);
+        checkPathUsingRandomContractionOrder(expectedPath, roadCosts, turnCosts, 3, 4);
     }
 
     @Test
@@ -297,7 +289,7 @@ public class CHTurnCostTest {
         final int roadCosts = 10;
         final int turnCosts = 2;
 
-        checkPathUsingRandomContractionOrder(expectedPath, roadCosts + turnCosts, 0, 6);
+        checkPathUsingRandomContractionOrder(expectedPath, roadCosts, turnCosts, 0, 6);
     }
 
     @Test
@@ -353,7 +345,7 @@ public class CHTurnCostTest {
         final int roadCosts = 15;
         final int turnCosts = 2;
 
-        checkPathUsingRandomContractionOrder(expectedPath, roadCosts + turnCosts, 0, 14);
+        checkPathUsingRandomContractionOrder(expectedPath, roadCosts, turnCosts, 0, 14);
     }
 
     @Test
@@ -447,7 +439,7 @@ public class CHTurnCostTest {
         final int roadCosts = 49;
         final int turnCosts = 4;
 
-        checkPathUsingRandomContractionOrder(expectedPath, roadCosts + turnCosts, 0, 26);
+        checkPathUsingRandomContractionOrder(expectedPath, roadCosts, turnCosts, 0, 26);
     }
 
     @Test
@@ -469,7 +461,7 @@ public class CHTurnCostTest {
         addRestriction(5, 6, 1);
 
         final IntArrayList expectedPath = IntArrayList.from(5, 6, 4, 0, 3, 2, 4, 6, 1);
-        checkPath(expectedPath, 8, 5, 1, Arrays.asList(0, 1, 2, 3, 4, 5, 6));
+        checkPath(expectedPath, 8, 0, 5, 1, Arrays.asList(0, 1, 2, 3, 4, 5, 6));
     }
 
 
@@ -494,7 +486,7 @@ public class CHTurnCostTest {
         addRestriction(5, 6, 7);
 
         final IntArrayList expectedPath = IntArrayList.from(5, 6, 1, 4, 0, 3, 2, 4, 1, 6, 7);
-        checkPath(expectedPath, 10, 5, 7, Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7));
+        checkPath(expectedPath, 10, 0, 5, 7, Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7));
     }
 
     @Test
@@ -630,6 +622,25 @@ public class CHTurnCostTest {
         compareCHQueryWithDijkstra(factory, 0, 5);
     }
 
+    @Test
+    public void testFindPath_calcTurnCostTime() {
+        // here there will be a shortcut from 1 to 4 and when the path is unpacked it is important that
+        // the turn costs are included at node 1 even though the unpacked original edge 1-0 might be in the
+        // reverted state
+        // 2-1--3
+        //   |  |
+        //   0->4
+        EdgeIteratorState edge0 = graph.edge(1, 2, 1, true);
+        EdgeIteratorState edge1 = graph.edge(0, 4, 1, false);
+        EdgeIteratorState edge2 = graph.edge(4, 3, 1, true);
+        EdgeIteratorState edge3 = graph.edge(1, 3, 1, true);
+        EdgeIteratorState edge4 = graph.edge(1, 0, 1, true);
+        addTurnCost(edge0, edge4, 1, 8);
+        addRestriction(edge0, edge3, 1);
+        graph.freeze();
+        checkPath(IntArrayList.from(2, 1, 0, 4), 3, 8, 2, 4, Arrays.asList(2, 0, 1, 3, 4));
+    }
+
     @Test
     public void testFindPath_loopsMustAlwaysBeAccepted() {
         //     ---
@@ -643,7 +654,7 @@ public class CHTurnCostTest {
         addRestriction(edge0, edge2, 1);
         graph.freeze();
         final IntArrayList expectedPath = IntArrayList.from(0, 1, 1, 2, 3);
-        checkPath(expectedPath, 5, 0, 3, Arrays.asList(0, 2, 1, 3));
+        checkPath(expectedPath, 4, 1, 0, 3, Arrays.asList(0, 2, 1, 3));
     }
 
     @Test
@@ -675,7 +686,7 @@ public class CHTurnCostTest {
         graph.freeze();
         IntArrayList expectedPath = IntArrayList.from(0, 1, 2, 3, 4);
         List<Integer> contractionOrder = Arrays.asList(2, 0, 4, 1, 3);
-        checkPath(expectedPath, 4, 0, 4, contractionOrder);
+        checkPath(expectedPath, 4, 0, 0, 4, contractionOrder);
     }
 
     @Test
@@ -697,7 +708,42 @@ public class CHTurnCostTest {
         graph.freeze();
         IntArrayList expectedPath = IntArrayList.from(0, 1, 2, 3, 3, 4);
         List<Integer> contractionOrder = Arrays.asList(2, 0, 4, 1, 3);
-        checkPath(expectedPath, 8, 0, 4, contractionOrder);
+        checkPath(expectedPath, 4, 4, 0, 4, contractionOrder);
+    }
+
+    @Test
+    public void testFindPath_oneWayLoop() {
+        //     o
+        // 0-1-2-3-4
+        graph.edge(0, 1, 1, false);
+        graph.edge(1, 2, 1, false);
+        graph.edge(2, 2, 1, false);
+        graph.edge(2, 3, 1, false);
+        graph.edge(3, 4, 1, false);
+        addRestriction(1, 2, 3);
+        graph.freeze();
+        RoutingAlgorithmFactory pch = automaticPrepareCH();
+        compareCHQueryWithDijkstra(pch, 0, 3);
+        compareCHQueryWithDijkstra(pch, 1, 4);
+        automaticCompareCHWithDijkstra(100);
+    }
+
+    @Test
+    public void testFindPath_loopEdge() {
+        // 1-0
+        // | |
+        // 4-2o
+        graph.edge(1, 0, 802.964000, false);
+        graph.edge(1, 4, 615.195000, true);
+        graph.edge(2, 2, 181.788000, true);
+        graph.edge(0, 2, 191.996000, true);
+        graph.edge(2, 4, 527.821000, false);
+        addRestriction(0, 2, 4);
+        addTurnCost(0, 2, 2, 3);
+        addTurnCost(2, 2, 4, 4);
+        graph.freeze();
+        RoutingAlgorithmFactory pch = automaticPrepareCH();
+        compareCHQueryWithDijkstra(pch, 0, 4);
     }
 
     @Test
@@ -872,6 +918,7 @@ public class CHTurnCostTest {
         GHUtility.buildRandomGraph(graph, rnd, 20, 3.0, true, true, encoder.getAverageSpeedEnc(), 0.7, 0.9, 0.8);
         GHUtility.addRandomTurnCosts(graph, seed, encoder, maxCost, turnCostExtension);
         graph.freeze();
+        checkStrict = false;
         List<Integer> contractionOrder = getRandomIntegerSequence(chGraph.getNodes(), rnd);
         compareCHWithDijkstra(100, contractionOrder);
     }
@@ -887,6 +934,7 @@ public class CHTurnCostTest {
         GHUtility.buildRandomGraph(graph, new Random(seed), 20, 3.0, true, true, encoder.getAverageSpeedEnc(), 0.7, 0.9, 0.8);
         GHUtility.addRandomTurnCosts(graph, seed, encoder, maxCost, turnCostExtension);
         graph.freeze();
+        checkStrict = false;
         automaticCompareCHWithDijkstra(100);
     }
 
@@ -899,26 +947,36 @@ public class CHTurnCostTest {
         return rnd.nextDouble() * maxDist;
     }
 
-    private void checkPathUsingRandomContractionOrder(IntArrayList expectedPath, int expectedWeight, int from, int to) {
+    private void checkPathUsingRandomContractionOrder(IntArrayList expectedPath, int expectedWeight, int expectedTurnCosts, int from, int to) {
         List<Integer> contractionOrder = getRandomIntegerSequence(chGraph.getNodes());
-        checkPath(expectedPath, expectedWeight, from, to, contractionOrder);
+        checkPath(expectedPath, expectedWeight, expectedTurnCosts, from, to, contractionOrder);
     }
 
-    private void checkPath(IntArrayList expectedPath, int expectedWeight, int from, int to, List<Integer> contractionOrder) {
-        checkPathUsingDijkstra(expectedPath, expectedWeight, from, to);
-        checkPathUsingCH(expectedPath, expectedWeight, from, to, contractionOrder);
+    private void checkPath(IntArrayList expectedPath, int expectedEdgeWeight, int expectedTurnCosts, int from, int to, List<Integer> contractionOrder) {
+        checkPathUsingDijkstra(expectedPath, expectedEdgeWeight, expectedTurnCosts, from, to);
+        checkPathUsingCH(expectedPath, expectedEdgeWeight, expectedTurnCosts, from, to, contractionOrder);
     }
 
-    private void checkPathUsingDijkstra(IntArrayList expectedPath, int expectedWeight, int from, int to) {
+    private void checkPathUsingDijkstra(IntArrayList expectedPath, int expectedEdgeWeight, int expectedTurnCosts, int from, int to) {
         Path dijkstraPath = findPathUsingDijkstra(from, to);
+        int expectedWeight = expectedEdgeWeight + expectedTurnCosts;
+        int expectedDistance = expectedEdgeWeight;
+        int expectedTime = expectedEdgeWeight * 60 + expectedTurnCosts * 1000;
         assertEquals("Normal Dijkstra did not find expected path.", expectedPath, dijkstraPath.calcNodes());
         assertEquals("Normal Dijkstra did not calculate expected weight.", expectedWeight, dijkstraPath.getWeight(), 1.e-6);
+        assertEquals("Normal Dijkstra did not calculate expected distance.", expectedDistance, dijkstraPath.getDistance(), 1.e-6);
+        assertEquals("Normal Dijkstra did not calculate expected time.", expectedTime, dijkstraPath.getTime(), 1.e-6);
     }
 
-    private void checkPathUsingCH(IntArrayList expectedPath, int expectedWeight, int from, int to, List<Integer> contractionOrder) {
+    private void checkPathUsingCH(IntArrayList expectedPath, int expectedEdgeWeight, int expectedTurnCosts, int from, int to, List<Integer> contractionOrder) {
         Path chPath = findPathUsingCH(from, to, contractionOrder);
+        int expectedWeight = expectedEdgeWeight + expectedTurnCosts;
+        int expectedDistance = expectedEdgeWeight;
+        int expectedTime = expectedEdgeWeight * 60 + expectedTurnCosts * 1000;
         assertEquals("Contraction Hierarchies did not find expected path. contraction order=" + contractionOrder, expectedPath, chPath.calcNodes());
         assertEquals("Contraction Hierarchies did not calculate expected weight.", expectedWeight, chPath.getWeight(), 1.e-6);
+        assertEquals("Contraction Hierarchies did not calculate expected distance.", expectedDistance, chPath.getDistance(), 1.e-6);
+        assertEquals("Contraction Hierarchies did not calculate expected time.", expectedTime, chPath.getTime(), 1.e-6);
     }
 
     private Path findPathUsingDijkstra(int from, int to) {
@@ -990,12 +1048,19 @@ public class CHTurnCostTest {
         RoutingAlgorithm chAlgo = factory.createAlgo(chGraph, AlgorithmOptions.start().build());
         Path chPath = chAlgo.calcPath(from, to);
         boolean algosDisagree = Math.abs(dijkstraPath.getWeight() - chPath.getWeight()) > 1.e-2;
+        if (checkStrict) {
+            algosDisagree = algosDisagree
+                    || Math.abs(dijkstraPath.getDistance() - chPath.getDistance()) > 1.e-2
+                    || Math.abs(dijkstraPath.getTime() - chPath.getTime()) > 1;
+        }
         if (algosDisagree) {
             System.out.println("Graph that produced error:");
             GHUtility.printGraphForUnitTest(graph, encoder);
             fail("Dijkstra and CH did not find equal shortest paths for route from " + from + " to " + to + "\n" +
-                    " dijkstra: weight: " + dijkstraPath.getWeight() + ", nodes: " + dijkstraPath.calcNodes() + "\n" +
-                    "       ch: weight: " + chPath.getWeight() + ", nodes: " + chPath.calcNodes());
+                    " dijkstra: weight: " + dijkstraPath.getWeight() + ", distance: " + dijkstraPath.getDistance() +
+                    ", time: " + dijkstraPath.getTime() + ", nodes: " + dijkstraPath.calcNodes() + "\n" +
+                    "       ch: weight: " + chPath.getWeight() + ", distance: " + chPath.getDistance() +
+                    ", time: " + chPath.getTime() + ", nodes: " + chPath.calcNodes());
         }
     }
 
diff --git a/core/src/test/java/com/graphhopper/routing/ch/Path4CHTest.java b/core/src/test/java/com/graphhopper/routing/ch/Path4CHTest.java
new file mode 100644
index 000000000..68b1927fe
--- /dev/null
+++ b/core/src/test/java/com/graphhopper/routing/ch/Path4CHTest.java
@@ -0,0 +1,166 @@
+package com.graphhopper.routing.ch;
+
+import com.graphhopper.routing.AbstractBidirectionEdgeCHNoSOD;
+import com.graphhopper.routing.DijkstraBidirectionEdgeCHNoSOD;
+import com.graphhopper.routing.Path;
+import com.graphhopper.routing.profiles.DecimalEncodedValue;
+import com.graphhopper.routing.util.EncodingManager;
+import com.graphhopper.routing.util.FlagEncoder;
+import com.graphhopper.routing.util.LevelEdgeFilter;
+import com.graphhopper.routing.util.MotorcycleFlagEncoder;
+import com.graphhopper.routing.weighting.FastestWeighting;
+import com.graphhopper.routing.weighting.TurnWeighting;
+import com.graphhopper.routing.weighting.Weighting;
+import com.graphhopper.storage.CHGraph;
+import com.graphhopper.storage.GraphBuilder;
+import com.graphhopper.storage.GraphHopperStorage;
+import com.graphhopper.storage.TurnCostExtension;
+import com.graphhopper.util.EdgeIteratorState;
+import com.graphhopper.util.GHUtility;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class Path4CHTest {
+    private final int maxTurnCosts = 10;
+    private GraphHopperStorage graph;
+    private CHGraph chGraph;
+    private FlagEncoder encoder;
+    private Weighting weighting;
+    private TurnCostExtension turnCostExtension;
+
+    @Before
+    public void init() {
+        encoder = new MotorcycleFlagEncoder(5, 5, maxTurnCosts);
+        EncodingManager em = EncodingManager.create(encoder);
+        weighting = new FastestWeighting(encoder);
+        graph = new GraphBuilder(em).setEdgeBasedCH(true).setCHGraph(weighting).create();
+        chGraph = graph.getGraph(CHGraph.class);
+        turnCostExtension = (TurnCostExtension) graph.getExtension();
+    }
+
+    @Test
+    public void shortcut_chain() {
+        // 0   2   4   6   8
+        //  \ / \ / \ / \ /
+        //   1   3   5   7
+        graph.edge(0, 1, 1, false);
+        graph.edge(1, 2, 1, false);
+        graph.edge(2, 3, 1, false);
+        graph.edge(3, 4, 1, false);
+        graph.edge(4, 5, 1, false);
+        graph.edge(5, 6, 1, false);
+        graph.edge(6, 7, 1, false);
+        graph.edge(7, 8, 1, false);
+        graph.freeze();
+        addTurnCost(1, 2, 3, 4);
+        addTurnCost(3, 4, 5, 2);
+        addTurnCost(5, 6, 7, 3);
+        // we 'contract' the graph such that only a few shortcuts are created and that the fwd/bwd searches for the
+        // 0-8 query meet at node 4 (make sure we include all three cases where turn cost times might come to play:
+        // fwd/bwd search and meeting point)
+        addShortcut(0, 2, 0, 1, 0, 1, 0.12, 2, 0);
+        addShortcut(2, 4, 2, 3, 2, 3, 0.12, 2, 0);
+        addShortcut(4, 6, 4, 5, 4, 5, 0.12, 2, 0);
+        addShortcut(6, 8, 6, 7, 6, 7, 0.12, 2, 0);
+        setCHOrder(1, 3, 5, 7, 0, 8, 2, 6, 4);
+
+        // going from 0 to 8 will create shortest path tree entries that follow the shortcuts.
+        // it is important that the original edge ids are used to calculate the turn costs.
+        checkPath(0, 8, 0.48, 8, 9);
+    }
+
+    private void setCHOrder(int... nodeIds) {
+        for (int i = 0; i < nodeIds.length; i++) {
+            chGraph.setLevel(nodeIds[i], i);
+        }
+    }
+
+    @Test
+    public void paths_different_fwd_bwd_speeds() {
+        //   5 3 2 1 4    turn costs ->
+        // 0-1-2-3-4-5-6
+        //   0 1 4 2 3    turn costs <-
+        DecimalEncodedValue speedEnc = encoder.getAverageSpeedEnc();
+        double fwdSpeed = 60;
+        double bwdSpeed = 30;
+        EdgeIteratorState edge0 = graph.edge(0, 1, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
+        EdgeIteratorState edge1 = graph.edge(1, 2, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
+        EdgeIteratorState edge2 = graph.edge(2, 3, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
+        EdgeIteratorState edge3 = graph.edge(3, 4, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
+        EdgeIteratorState edge4 = graph.edge(4, 5, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
+        EdgeIteratorState edge5 = graph.edge(5, 6, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
+        graph.freeze();
+
+        // turn costs ->
+        addTurnCost(edge0, edge1, 1, 5);
+        addTurnCost(edge1, edge2, 2, 3);
+        addTurnCost(edge2, edge3, 3, 2);
+        addTurnCost(edge3, edge4, 4, 1);
+        addTurnCost(edge4, edge5, 5, 4);
+        // turn costs <-
+        addTurnCost(edge5, edge4, 5, 3);
+        addTurnCost(edge4, edge3, 4, 2);
+        addTurnCost(edge3, edge2, 3, 4);
+        addTurnCost(edge2, edge1, 2, 1);
+        addTurnCost(edge1, edge0, 1, 0);
+
+        // shortcuts ->
+        addShortcut(0, 2, 0, 1, 0, 1, 0.12, 2, 5);
+        addShortcut(2, 4, 2, 3, 2, 3, 0.12, 2, 2);
+        addShortcut(4, 6, 4, 5, 4, 5, 0.12, 2, 4);
+        addShortcut(2, 6, 2, 5, 7, 8, 0.24, 4, 7);
+        addShortcut(0, 6, 0, 5, 6, 9, 0.36, 6, 12);
+
+        // shortcuts <-
+        addShortcut(6, 4, 5, 4, 5, 4, 0.24, 2, 3);
+        addShortcut(4, 2, 3, 2, 3, 2, 0.24, 2, 4);
+        addShortcut(2, 0, 1, 0, 1, 0, 0.24, 2, 0);
+        addShortcut(6, 2, 5, 2, 11, 12, 0.48, 4, 9);
+        addShortcut(6, 0, 5, 0, 14, 13, 0.60, 6, 10);
+
+        // strictly it would be cleaner to manually build the SPT and extract the path, but for convenience we
+        // use the routing algo to build it
+        checkPath(0, 6, 0.36, 6, 15);
+        checkPath(6, 0, 0.72, 6, 10);
+        checkPath(1, 3, 0.12, 2, 3);
+        checkPath(3, 1, 0.24, 2, 1);
+        checkPath(1, 5, 0.24, 4, 6);
+        checkPath(5, 1, 0.48, 4, 7);
+    }
+
+    private void addTurnCost(int from, int via, int to, int cost) {
+        addTurnCost(getEdge(from, via), getEdge(via, to), via, cost);
+    }
+
+    private void addTurnCost(EdgeIteratorState inEdge, EdgeIteratorState outEdge, int viaNode, int cost) {
+        turnCostExtension.addTurnInfo(inEdge.getEdge(), viaNode, outEdge.getEdge(), encoder.getTurnFlags(false, cost));
+    }
+
+    private EdgeIteratorState getEdge(int from, int to) {
+        return GHUtility.getEdge(graph, from, to);
+    }
+
+    private void addShortcut(int from, int to, int origFirst, int origLast, int skip1, int skip2, double edgeWeight, double distance, int turnCost) {
+        double weight = edgeWeight + turnCost * 1000;
+        chGraph.shortcutEdgeBased(from, to, PrepareEncoder.getScFwdDir(), weight, distance, skip1, skip2, origFirst, origLast);
+    }
+
+    private void checkPath(int from, int to, double edgeWeight, int distance, int turnCostTime) {
+        double expectedWeight = (edgeWeight + turnCostTime);
+        Path path = createAlgo().calcPath(from, to);
+        assertEquals("wrong weight", expectedWeight, path.getWeight(), 1.e-3);
+        assertEquals("wrong distance", distance, path.getDistance(), 1.e-3);
+        assertEquals("wrong time", expectedWeight * 1000, path.getTime(), 1.e-3);
+    }
+
+    private AbstractBidirectionEdgeCHNoSOD createAlgo() {
+        TurnWeighting chTurnWeighting = new TurnWeighting(new PreparationWeighting(weighting), turnCostExtension);
+        CHGraph lg = graph.getGraph(CHGraph.class, weighting);
+        AbstractBidirectionEdgeCHNoSOD algo = new DijkstraBidirectionEdgeCHNoSOD(lg, chTurnWeighting);
+        algo.setEdgeFilter(new LevelEdgeFilter(lg));
+        return algo;
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/test/java/com/graphhopper/storage/GraphHopperStorageCHTest.java b/core/src/test/java/com/graphhopper/storage/GraphHopperStorageCHTest.java
index 20933a742..f8c2dbf3d 100644
--- a/core/src/test/java/com/graphhopper/storage/GraphHopperStorageCHTest.java
+++ b/core/src/test/java/com/graphhopper/storage/GraphHopperStorageCHTest.java
@@ -441,6 +441,38 @@ public class GraphHopperStorageCHTest extends GraphHopperStorageTest {
         assertEquals(1, iter.getOrigEdgeLast());
     }
 
+    @Test
+    public void testGetEdgeIterator() {
+        graph = newGHStorage(false, true);
+        graph.edge(0, 1, 1, false);
+        graph.edge(1, 2, 1, false);
+        graph.freeze();
+        CHGraph lg = getGraph(graph);
+        addShortcut(lg, 0, 2, true, 0, 1, 0, 1, 2);
+
+        CHEdgeIteratorState sc02 = lg.getEdgeIteratorState(2, 2);
+        assertNotNull(sc02);
+        assertEquals(0, sc02.getBaseNode());
+        assertEquals(2, sc02.getAdjNode());
+        assertEquals(2, sc02.getEdge());
+        assertEquals(0, sc02.getSkippedEdge1());
+        assertEquals(1, sc02.getSkippedEdge2());
+        assertEquals(0, sc02.getOrigEdgeFirst());
+        assertEquals(1, sc02.getOrigEdgeLast());
+
+        CHEdgeIteratorState sc20 = lg.getEdgeIteratorState(2, 0);
+        assertNotNull(sc20);
+        assertEquals(2, sc20.getBaseNode());
+        assertEquals(0, sc20.getAdjNode());
+        assertEquals(2, sc20.getEdge());
+        // note these are not stateful! i.e. even though we are looking at the edge 2->0 the first skipped/orig edge
+        // is still edge 0 and the second skipped/last orig edge is edge 1
+        assertEquals(0, sc20.getSkippedEdge1());
+        assertEquals(1, sc20.getSkippedEdge2());
+        assertEquals(0, sc20.getOrigEdgeFirst());
+        assertEquals(1, sc20.getOrigEdgeLast());
+    }
+
     private void addShortcut(CHGraph chGraph, int from, int to, boolean fwd, int firstOrigEdge, int lastOrigEdge,
                              int skipEdge1, int skipEdge2, int distance) {
         CHEdgeIteratorState shortcut = chGraph.shortcut(from, to);
diff --git a/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java b/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java
index cf7700f1f..9ce917bde 100644
--- a/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java
+++ b/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java
@@ -7,20 +7,41 @@ import com.graphhopper.routing.profiles.DecimalEncodedValue;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.FlagEncoder;
 import com.graphhopper.routing.util.MotorcycleFlagEncoder;
+import com.graphhopper.routing.util.TraversalMode;
 import com.graphhopper.routing.weighting.FastestWeighting;
+import com.graphhopper.routing.weighting.TurnWeighting;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.util.EdgeIteratorState;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 import static org.junit.Assert.assertEquals;
 
+@RunWith(Parameterized.class)
 public class ShortcutUnpackerTest {
-
+    private final static int PREV_EDGE = 12;
+    private final static int NEXT_EDGE = 13;
+    private final boolean edgeBased;
     private FlagEncoder encoder;
     private Weighting weighting;
     private GraphHopperStorage graph;
     private CHGraph chGraph;
+    private TurnCostExtension turnCostExtension;
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Object[] params() {
+        return new Object[]{
+                TraversalMode.NODE_BASED,
+                TraversalMode.EDGE_BASED_2DIR
+        };
+    }
+
+    public ShortcutUnpackerTest(TraversalMode traversalMode) {
+        this.edgeBased = traversalMode.isEdgeBased();
+    }
 
     @Before
     public void init() {
@@ -28,8 +49,11 @@ public class ShortcutUnpackerTest {
         encoder = new MotorcycleFlagEncoder(5, 5, 10);
         EncodingManager encodingManager = EncodingManager.create(encoder);
         weighting = new FastestWeighting(encoder);
-        graph = new GraphBuilder(encodingManager).setCHGraph(weighting).setEdgeBasedCH(true).create();
+        graph = new GraphBuilder(encodingManager).setCHGraph(weighting).setEdgeBasedCH(edgeBased).create();
         chGraph = graph.getGraph(CHGraph.class, weighting);
+        if (edgeBased) {
+            turnCostExtension = (TurnCostExtension) graph.getExtension();
+        }
     }
 
     @Test
@@ -45,61 +69,78 @@ public class ShortcutUnpackerTest {
         graph.edge(4, 5, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
         graph.edge(5, 6, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
         graph.freeze();
-        chGraph.shortcut(0, 2, PrepareEncoder.getScFwdDir(), 2, 2, 0, 1);
-        chGraph.shortcut(2, 4, PrepareEncoder.getScFwdDir(), 2, 2, 2, 3);
-        chGraph.shortcut(4, 6, PrepareEncoder.getScFwdDir(), 2, 2, 4, 5);
-        chGraph.shortcut(2, 6, PrepareEncoder.getScFwdDir(), 2, 2, 7, 8);
-        chGraph.shortcut(0, 6, PrepareEncoder.getScFwdDir(), 2, 2, 6, 9);
+        shortcut(0, 2, 0, 1, 0, 1);
+        shortcut(2, 4, 2, 3, 2, 3);
+        shortcut(4, 6, 4, 5, 4, 5);
+        shortcut(2, 6, 7, 8, 2, 5);
+        shortcut(0, 6, 6, 9, 0, 5);
 
         {
-            // unpack the shortcut 0-6, traverse original edges in 'forward' order (from node 0 to 6)
+            // unpack the shortcut 0->6, traverse original edges in 'forward' order (from node 0 to 6)
             TestVisitor visitor = new TestVisitor();
-            new ShortcutUnpacker(chGraph, visitor).visitOriginalEdges(10, 6, false);
+            new ShortcutUnpacker(chGraph, visitor, edgeBased).visitOriginalEdgesFwd(10, 6, false, PREV_EDGE);
             assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5), visitor.edgeIds);
+            assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5), visitor.baseNodes);
             assertEquals(IntArrayList.from(1, 2, 3, 4, 5, 6), visitor.adjNodes);
             assertEquals(DoubleArrayList.from(0.06, 0.06, 0.06, 0.06, 0.06, 0.06), visitor.weights);
             assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances);
             assertEquals(DoubleArrayList.from(60, 60, 60, 60, 60, 60), visitor.times);
+            if (edgeBased) {
+                assertEquals(IntArrayList.from(PREV_EDGE, 0, 1, 2, 3, 4), visitor.prevOrNextEdgeIds);
+            }
         }
 
         {
-            // unpack the shortcut 0-6, traverse original edges in 'backward' order (from node 6 to 0)
+            // unpack the shortcut 0->6, traverse original edges in 'backward' order (from node 6 to 0)
             // note that traversing in backward order does not mean the original edges are read in reverse (e.g. fwd speed still applies)
             // -> only the order of the original edges is reversed
             TestVisitor visitor = new TestVisitor();
-            new ShortcutUnpacker(chGraph, visitor).visitOriginalEdges(10, 6, true);
+            new ShortcutUnpacker(chGraph, visitor, edgeBased).visitOriginalEdgesFwd(10, 6, true, PREV_EDGE);
             assertEquals(IntArrayList.from(5, 4, 3, 2, 1, 0), visitor.edgeIds);
+            assertEquals(IntArrayList.from(5, 4, 3, 2, 1, 0), visitor.baseNodes);
             assertEquals(IntArrayList.from(6, 5, 4, 3, 2, 1), visitor.adjNodes);
             assertEquals(DoubleArrayList.from(0.06, 0.06, 0.06, 0.06, 0.06, 0.06), visitor.weights);
             assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances);
             assertEquals(DoubleArrayList.from(60, 60, 60, 60, 60, 60), visitor.times);
+            if (edgeBased) {
+                assertEquals(IntArrayList.from(4, 3, 2, 1, 0, PREV_EDGE), visitor.prevOrNextEdgeIds);
+            }
         }
 
         {
-            // unpack the shortcut 6-0, traverse original edges in 'forward' order (from node 6 to 0)
+            // unpack the shortcut 6<-0 in reverse, i.e. with 6 as base node. traverse original edges in 'forward' order (from node 6 to 0)
             TestVisitor visitor = new TestVisitor();
-            new ShortcutUnpacker(chGraph, visitor).visitOriginalEdges(10, 0, false);
+            new ShortcutUnpacker(chGraph, visitor, edgeBased).visitOriginalEdgesBwd(10, 0, false, NEXT_EDGE);
             assertEquals(IntArrayList.from(5, 4, 3, 2, 1, 0), visitor.edgeIds);
+            assertEquals(IntArrayList.from(6, 5, 4, 3, 2, 1), visitor.baseNodes);
             assertEquals(IntArrayList.from(5, 4, 3, 2, 1, 0), visitor.adjNodes);
-            assertEquals(DoubleArrayList.from(0.12, 0.12, 0.12, 0.12, 0.12, 0.12), visitor.weights);
+            assertEquals(DoubleArrayList.from(0.06, 0.06, 0.06, 0.06, 0.06, 0.06), visitor.weights);
             assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances);
-            assertEquals(DoubleArrayList.from(120, 120, 120, 120, 120, 120), visitor.times);
+            assertEquals(DoubleArrayList.from(60, 60, 60, 60, 60, 60), visitor.times);
+            if (edgeBased) {
+                assertEquals(IntArrayList.from(NEXT_EDGE, 5, 4, 3, 2, 1), visitor.prevOrNextEdgeIds);
+            }
         }
 
         {
-            // unpack the shortcut 6-0, traverse original edges in 'backward' order (from node 0 to 6)
+            // unpack the shortcut 6<-0 in reverse, i.e. with 60as base node. traverse original edges in 'backward' order (from node 0 to 6)
             TestVisitor visitor = new TestVisitor();
-            new ShortcutUnpacker(chGraph, visitor).visitOriginalEdges(10, 0, true);
+            new ShortcutUnpacker(chGraph, visitor, edgeBased).visitOriginalEdgesBwd(10, 0, true, NEXT_EDGE);
             assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5), visitor.edgeIds);
+            assertEquals(IntArrayList.from(1, 2, 3, 4, 5, 6), visitor.baseNodes);
             assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5), visitor.adjNodes);
-            assertEquals(DoubleArrayList.from(0.12, 0.12, 0.12, 0.12, 0.12, 0.12), visitor.weights);
+            assertEquals(DoubleArrayList.from(0.06, 0.06, 0.06, 0.06, 0.06, 0.06), visitor.weights);
             assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances);
-            assertEquals(DoubleArrayList.from(120, 120, 120, 120, 120, 120), visitor.times);
+            assertEquals(DoubleArrayList.from(60, 60, 60, 60, 60, 60), visitor.times);
+            if (edgeBased) {
+                assertEquals(IntArrayList.from(1, 2, 3, 4, 5, NEXT_EDGE), visitor.prevOrNextEdgeIds);
+            }
         }
     }
 
     @Test
     public void loopShortcut() {
+        Assume.assumeTrue("loop shortcuts only exist for edge-based CH", edgeBased);
         //     3
         //    / \
         //   2   4
@@ -115,64 +156,158 @@ public class ShortcutUnpackerTest {
         graph.edge(4, 1, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
         graph.edge(1, 5, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
         graph.freeze();
-        shortcut(1, 3, 2, 2, 1, 2, 1, 2);
-        shortcut(3, 1, 2, 2, 3, 4, 3, 4);
-        shortcut(1, 1, 4, 4, 6, 7, 1, 4);
-        shortcut(0, 1, 5, 5, 0, 8, 0, 4);
-        shortcut(0, 5, 6, 6, 9, 5, 0, 5);
+        shortcut(1, 3, 1, 2, 1, 2);
+        shortcut(3, 1, 3, 4, 3, 4);
+        shortcut(1, 1, 6, 7, 1, 4);
+        shortcut(0, 1, 0, 8, 0, 4);
+        shortcut(0, 5, 9, 5, 0, 5);
 
         {
             // unpack the shortcut 0->5, traverse original edges in 'forward' order (from node 0 to 5)
             TestVisitor visitor = new TestVisitor();
-            new ShortcutUnpacker(chGraph, visitor).visitOriginalEdges(10, 5, false);
+            new ShortcutUnpacker(chGraph, visitor, edgeBased).visitOriginalEdgesFwd(10, 5, false, PREV_EDGE);
             assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5), visitor.edgeIds);
+            assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 1), visitor.baseNodes);
             assertEquals(IntArrayList.from(1, 2, 3, 4, 1, 5), visitor.adjNodes);
             assertEquals(DoubleArrayList.from(0.06, 0.06, 0.06, 0.06, 0.06, 0.06), visitor.weights);
             assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances);
             assertEquals(DoubleArrayList.from(60, 60, 60, 60, 60, 60), visitor.times);
+            assertEquals(IntArrayList.from(PREV_EDGE, 0, 1, 2, 3, 4), visitor.prevOrNextEdgeIds);
         }
 
         {
             // unpack the shortcut 0->5, traverse original edges in 'backward' order (from node 5 to 0)
             TestVisitor visitor = new TestVisitor();
-            new ShortcutUnpacker(chGraph, visitor).visitOriginalEdges(10, 5, true);
+            new ShortcutUnpacker(chGraph, visitor, edgeBased).visitOriginalEdgesFwd(10, 5, true, PREV_EDGE);
             assertEquals(IntArrayList.from(5, 4, 3, 2, 1, 0), visitor.edgeIds);
+            assertEquals(IntArrayList.from(1, 4, 3, 2, 1, 0), visitor.baseNodes);
             assertEquals(IntArrayList.from(5, 1, 4, 3, 2, 1), visitor.adjNodes);
             assertEquals(DoubleArrayList.from(0.06, 0.06, 0.06, 0.06, 0.06, 0.06), visitor.weights);
             assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances);
             assertEquals(DoubleArrayList.from(60, 60, 60, 60, 60, 60), visitor.times);
+            assertEquals(IntArrayList.from(4, 3, 2, 1, 0, PREV_EDGE), visitor.prevOrNextEdgeIds);
         }
 
         {
             // unpack the shortcut 5<-0, traverse original edges in 'forward' order (from node 5 to 0)
             TestVisitor visitor = new TestVisitor();
-            new ShortcutUnpacker(chGraph, visitor).visitOriginalEdges(10, 0, false);
+            new ShortcutUnpacker(chGraph, visitor, edgeBased).visitOriginalEdgesBwd(10, 0, false, NEXT_EDGE);
             assertEquals(IntArrayList.from(5, 4, 3, 2, 1, 0), visitor.edgeIds);
+            assertEquals(IntArrayList.from(5, 1, 4, 3, 2, 1), visitor.baseNodes);
             assertEquals(IntArrayList.from(1, 4, 3, 2, 1, 0), visitor.adjNodes);
-            assertEquals(DoubleArrayList.from(0.12, 0.12, 0.12, 0.12, 0.12, 0.12), visitor.weights);
+            assertEquals(DoubleArrayList.from(0.06, 0.06, 0.06, 0.06, 0.06, 0.06), visitor.weights);
             assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances);
-            assertEquals(DoubleArrayList.from(120, 120, 120, 120, 120, 120), visitor.times);
+            assertEquals(DoubleArrayList.from(60, 60, 60, 60, 60, 60), visitor.times);
+            assertEquals(IntArrayList.from(NEXT_EDGE, 5, 4, 3, 2, 1), visitor.prevOrNextEdgeIds);
         }
 
         {
             // unpack the shortcut 5<-0, traverse original edges in 'backward' order (from node 0 to 5)
             TestVisitor visitor = new TestVisitor();
-            new ShortcutUnpacker(chGraph, visitor).visitOriginalEdges(10, 0, true);
+            new ShortcutUnpacker(chGraph, visitor, edgeBased).visitOriginalEdgesBwd(10, 0, true, NEXT_EDGE);
             assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5), visitor.edgeIds);
+            assertEquals(IntArrayList.from(1, 2, 3, 4, 1, 5), visitor.baseNodes);
             assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 1), visitor.adjNodes);
-            assertEquals(DoubleArrayList.from(0.12, 0.12, 0.12, 0.12, 0.12, 0.12), visitor.weights);
+            assertEquals(DoubleArrayList.from(0.06, 0.06, 0.06, 0.06, 0.06, 0.06), visitor.weights);
             assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances);
-            assertEquals(DoubleArrayList.from(120, 120, 120, 120, 120, 120), visitor.times);
+            assertEquals(DoubleArrayList.from(60, 60, 60, 60, 60, 60), visitor.times);
+            assertEquals(IntArrayList.from(1, 2, 3, 4, 5, NEXT_EDGE), visitor.prevOrNextEdgeIds);
+        }
+    }
+
+    @Test
+    public void withTurnWeighting() {
+        Assume.assumeTrue(edgeBased);
+        //      2 5 3 2 1 4 6      turn costs ->
+        // prev 0-1-2-3-4-5-6 next
+        //      1 0 1 4 2 3 2      turn costs <-
+        DecimalEncodedValue speedEnc = encoder.getAverageSpeedEnc();
+        double fwdSpeed = 60;
+        double bwdSpeed = 30;
+        EdgeIteratorState edge0 = graph.edge(0, 1, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
+        EdgeIteratorState edge1 = graph.edge(1, 2, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
+        EdgeIteratorState edge2 = graph.edge(2, 3, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
+        EdgeIteratorState edge3 = graph.edge(3, 4, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
+        EdgeIteratorState edge4 = graph.edge(4, 5, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
+        EdgeIteratorState edge5 = graph.edge(5, 6, 1, true).set(speedEnc, fwdSpeed).setReverse(speedEnc, bwdSpeed);
+        graph.freeze();
+
+        // turn costs ->
+        turnCostExtension.addTurnInfo(PREV_EDGE, 0, edge0.getEdge(), encoder.getTurnFlags(false, 2));
+        addTurnCost(edge0, edge1, 1, 5);
+        addTurnCost(edge1, edge2, 2, 3);
+        addTurnCost(edge2, edge3, 3, 2);
+        addTurnCost(edge3, edge4, 4, 1);
+        addTurnCost(edge4, edge5, 5, 4);
+        turnCostExtension.addTurnInfo(edge5.getEdge(), 6, NEXT_EDGE, encoder.getTurnFlags(false, 6));
+        // turn costs <-
+        turnCostExtension.addTurnInfo(NEXT_EDGE, 6, edge5.getEdge(), encoder.getTurnFlags(false, 2));
+        addTurnCost(edge5, edge4, 5, 3);
+        addTurnCost(edge4, edge3, 4, 2);
+        addTurnCost(edge3, edge2, 3, 4);
+        addTurnCost(edge2, edge1, 2, 1);
+        addTurnCost(edge1, edge0, 1, 0);
+        turnCostExtension.addTurnInfo(edge0.getEdge(), 0, PREV_EDGE, encoder.getTurnFlags(false, 1));
+
+        shortcut(0, 2, 0, 1, 0, 1);
+        shortcut(2, 4, 2, 3, 2, 3);
+        shortcut(4, 6, 4, 5, 4, 5);
+        shortcut(2, 6, 7, 8, 2, 5);
+        shortcut(0, 6, 6, 9, 0, 5);
+
+        {
+            // unpack the shortcut 0->6, traverse original edges in 'forward' order (from node 0 to 6)
+            TurnWeightingVisitor visitor = new TurnWeightingVisitor();
+            new ShortcutUnpacker(chGraph, visitor, edgeBased).visitOriginalEdgesFwd(10, 6, false, PREV_EDGE);
+            assertEquals("wrong weight", 6 * 0.06, visitor.weight, 1.e-3);
+            assertEquals("wrong time", (6 * 60 + 17000), visitor.time);
+        }
+
+        {
+            // unpack the shortcut 0->6, traverse original edges in 'backward' order (from node 6 to 0)
+            TurnWeightingVisitor visitor = new TurnWeightingVisitor();
+            new ShortcutUnpacker(chGraph, visitor, edgeBased).visitOriginalEdgesFwd(10, 6, true, PREV_EDGE);
+            assertEquals("wrong weight", 6 * 0.06, visitor.weight, 1.e-3);
+            assertEquals("wrong time", (6 * 60 + 17000), visitor.time);
+        }
+
+        {
+            // unpack the shortcut 6<-0, traverse original edges in 'forward' order (from node 6 to 0)
+            TurnWeightingVisitor visitor = new TurnWeightingVisitor();
+            new ShortcutUnpacker(chGraph, visitor, edgeBased).visitOriginalEdgesBwd(10, 0, false, NEXT_EDGE);
+            assertEquals("wrong weight", 6 * 0.06, visitor.weight, 1.e-3);
+            assertEquals("wrong time", (6 * 60 + 21000), visitor.time);
+        }
+
+        {
+            // unpack the shortcut 6<-0, traverse original edges in 'backward' order (from node 0 to 6)
+            TurnWeightingVisitor visitor = new TurnWeightingVisitor();
+            new ShortcutUnpacker(chGraph, visitor, edgeBased).visitOriginalEdgesBwd(10, 0, true, NEXT_EDGE);
+            assertEquals("wrong weight", 6 * 0.06, visitor.weight, 1.e-3);
+            assertEquals("wrong time", (6 * 60 + 21000), visitor.time);
         }
     }
 
-    private void shortcut(int baseNode, int adjNode, double weight, double distance, int skip1, int skip2, int origFirst, int origLast) {
-        chGraph.shortcutEdgeBased(baseNode, adjNode, PrepareEncoder.getScFwdDir(), weight, distance, skip1, skip2, origFirst, origLast);
+    private void addTurnCost(EdgeIteratorState inEdge, EdgeIteratorState outEdge, int viaNode, double costs) {
+        turnCostExtension.addTurnInfo(inEdge.getEdge(), viaNode, outEdge.getEdge(), encoder.getTurnFlags(false, costs));
+    }
+
+    private void shortcut(int baseNode, int adjNode, int skip1, int skip2, int origFirst, int origLast) {
+        // shortcut weight/distance is not important for us here
+        double weight = 1;
+        double distance = 1;
+        if (edgeBased) {
+            chGraph.shortcutEdgeBased(baseNode, adjNode, PrepareEncoder.getScFwdDir(), weight, distance, skip1, skip2, origFirst, origLast);
+        } else {
+            chGraph.shortcut(baseNode, adjNode, PrepareEncoder.getScFwdDir(), weight, distance, skip1, skip2);
+        }
     }
 
     private class TestVisitor implements ShortcutUnpacker.Visitor {
         private final IntArrayList edgeIds = new IntArrayList();
         private final IntArrayList adjNodes = new IntArrayList();
+        private final IntArrayList baseNodes = new IntArrayList();
+        private final IntArrayList prevOrNextEdgeIds = new IntArrayList();
         private final DoubleArrayList weights = new DoubleArrayList();
         private final DoubleArrayList distances = new DoubleArrayList();
         private final DoubleArrayList times = new DoubleArrayList();
@@ -180,10 +315,24 @@ public class ShortcutUnpackerTest {
         @Override
         public void visit(EdgeIteratorState edge, boolean reverse, int prevOrNextEdgeId) {
             edgeIds.add(edge.getEdge());
-            adjNodes.add(reverse ? edge.getBaseNode() : edge.getAdjNode());
+            baseNodes.add(edge.getBaseNode());
+            adjNodes.add(edge.getAdjNode());
             weights.add(weighting.calcWeight(edge, reverse, prevOrNextEdgeId));
             distances.add(edge.getDistance());
             times.add(weighting.calcMillis(edge, reverse, prevOrNextEdgeId));
+            prevOrNextEdgeIds.add(prevOrNextEdgeId);
+        }
+    }
+
+    private class TurnWeightingVisitor implements ShortcutUnpacker.Visitor {
+        private final TurnWeighting turnWeighting = new TurnWeighting(weighting, turnCostExtension);
+        private long time = 0;
+        private double weight = 0;
+
+        @Override
+        public void visit(EdgeIteratorState edge, boolean reverse, int prevOrNextEdgeId) {
+            time += turnWeighting.calcMillis(edge, reverse, prevOrNextEdgeId);
+            weight += weighting.calcWeight(edge, reverse, prevOrNextEdgeId);
         }
     }
 }
\ No newline at end of file