Repository: graphhopper/graphhopper
Pull request #1850 (2020-01-10 10:37:40)

Use RoutingCHGraph adapter for CH query algorithms
This is more or less a follow up of #1818 and part of #1780. There is a new interface `RoutingCHGraph` (with corresponding implementation) that is used by all CH algorithms. Internally it still uses `BaseGraph/CHGraphImpl`, but this is no longer (as) exposed to the algorithms, which can now retrieve the edge weights via `edge.getWeight()`. It also helps for #1835/#1776, because all access flag checks are run immediately before `calcWeight` already and only need to be moved into `Weighting` in #1835. 

So far the query speed seems to decrease by about 10%, I could not find out why exactly this is, but I am assuming its because we are wrapping the routing ch graph iterators around the base/ch graph iterators. Not sure, but since this will later be removed anyway I'd say this is ok for now ?


Patch:
diff --git a/core/src/main/java/com/graphhopper/routing/AStar.java b/core/src/main/java/com/graphhopper/routing/AStar.java
index 28d936a49..275f31e1d 100644
--- a/core/src/main/java/com/graphhopper/routing/AStar.java
+++ b/core/src/main/java/com/graphhopper/routing/AStar.java
@@ -24,7 +24,10 @@ import com.graphhopper.routing.weighting.WeightApproximator;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.SPTEntry;
-import com.graphhopper.util.*;
+import com.graphhopper.util.EdgeIterator;
+import com.graphhopper.util.EdgeIteratorState;
+import com.graphhopper.util.Helper;
+import com.graphhopper.util.Parameters;
 
 import java.util.PriorityQueue;
 
@@ -83,19 +86,19 @@ public class AStar extends AbstractRoutingAlgorithm {
 
     private void runAlgo() {
         double currWeightToGoal, estimationFullWeight;
-        EdgeExplorer explorer = outEdgeExplorer;
         while (true) {
             visitedNodes++;
             if (isMaxVisitedNodesExceeded() || finished())
                 break;
 
             int currNode = currEdge.adjNode;
-            EdgeIterator iter = explorer.setBaseNode(currNode);
+            EdgeIterator iter = edgeExplorer.setBaseNode(currNode);
             while (iter.next()) {
                 if (!accept(iter, currEdge.edge))
                     continue;
 
-                double tmpWeight = weighting.calcWeight(iter, false, currEdge.edge) + currEdge.weightOfVisitedPath;
+                // todo: for #1776/#1835 move the access check into weighting
+                double tmpWeight = !outEdgeFilter.accept(iter) ? Double.POSITIVE_INFINITY : (weighting.calcWeight(iter, false, currEdge.edge) + currEdge.weightOfVisitedPath);
                 if (Double.isInfinite(tmpWeight)) {
                     continue;
                 }
diff --git a/core/src/main/java/com/graphhopper/routing/AStarBidirection.java b/core/src/main/java/com/graphhopper/routing/AStarBidirection.java
index ec5fd1359..218428309 100644
--- a/core/src/main/java/com/graphhopper/routing/AStarBidirection.java
+++ b/core/src/main/java/com/graphhopper/routing/AStarBidirection.java
@@ -30,6 +30,8 @@ import com.graphhopper.util.EdgeIteratorState;
 import com.graphhopper.util.Helper;
 import com.graphhopper.util.Parameters;
 
+import java.util.PriorityQueue;
+
 /**
  * This class implements a bidirectional A* algorithm. It is interesting to note that a
  * bidirectional dijkstra is far more efficient than a single direction one. The same does not hold
@@ -56,7 +58,7 @@ import com.graphhopper.util.Parameters;
  * @author Peter Karich
  * @author jansoe
  */
-public class AStarBidirection extends AbstractBidirAlgo implements RecalculationHook {
+public class AStarBidirection extends AbstractNonCHBidirAlgo implements RecalculationHook {
     private ConsistentWeightApproximator weightApprox;
 
     public AStarBidirection(Graph graph, Weighting weighting, TraversalMode tMode) {
@@ -127,8 +129,16 @@ public class AStarBidirection extends AbstractBidirAlgo implements Recalculation
 
     @Override
     public void afterHeuristicChange(boolean forward, boolean backward) {
-        if (forward) {
+        updatePriorityQueues(pqOpenSetFrom, pqOpenSetTo, weightApprox, forward, backward);
+    }
+
+    @Override
+    public String getName() {
+        return Parameters.Algorithms.ASTAR_BI + "|" + weightApprox;
+    }
 
+    public static void updatePriorityQueues(PriorityQueue<SPTEntry> pqOpenSetFrom, PriorityQueue<SPTEntry> pqOpenSetTo, ConsistentWeightApproximator weightApprox, boolean forward, boolean backward) {
+        if (forward) {
             // update PQ due to heuristic change (i.e. weight changed)
             if (!pqOpenSetFrom.isEmpty()) {
                 // copy into temporary array to avoid pointer change of PQ
@@ -157,9 +167,4 @@ public class AStarBidirection extends AbstractBidirAlgo implements Recalculation
             }
         }
     }
-
-    @Override
-    public String getName() {
-        return Parameters.Algorithms.ASTAR_BI + "|" + weightApprox;
-    }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/AStarBidirectionCH.java b/core/src/main/java/com/graphhopper/routing/AStarBidirectionCH.java
index 11261afe7..ecf6f01b4 100644
--- a/core/src/main/java/com/graphhopper/routing/AStarBidirectionCH.java
+++ b/core/src/main/java/com/graphhopper/routing/AStarBidirectionCH.java
@@ -17,43 +17,96 @@
  */
 package com.graphhopper.routing;
 
-import com.graphhopper.routing.ch.NodeBasedCHBidirPathExtractor;
 import com.graphhopper.routing.util.TraversalMode;
-import com.graphhopper.routing.weighting.Weighting;
-import com.graphhopper.storage.Graph;
+import com.graphhopper.routing.weighting.BeelineWeightApproximator;
+import com.graphhopper.routing.weighting.ConsistentWeightApproximator;
+import com.graphhopper.routing.weighting.WeightApproximator;
+import com.graphhopper.storage.RoutingCHEdgeIteratorState;
+import com.graphhopper.storage.RoutingCHGraph;
+import com.graphhopper.storage.SPTEntry;
+import com.graphhopper.util.EdgeIterator;
+import com.graphhopper.util.Helper;
 
-public class AStarBidirectionCH extends AStarBidirection {
-    public AStarBidirectionCH(Graph graph, Weighting weighting) {
-        super(graph, weighting, TraversalMode.NODE_BASED);
+/**
+ * @see AStarBidirection
+ */
+public class AStarBidirectionCH extends AbstractBidirCHAlgo implements RecalculationHook {
+    private ConsistentWeightApproximator weightApprox;
+
+    public AStarBidirectionCH(RoutingCHGraph graph) {
+        super(graph, TraversalMode.NODE_BASED);
+        BeelineWeightApproximator defaultApprox = new BeelineWeightApproximator(nodeAccess, graph.getWeighting());
+        defaultApprox.setDistanceCalc(Helper.DIST_PLANE);
+        setApproximation(defaultApprox);
     }
 
     @Override
-    protected void initCollections(int size) {
-        super.initCollections(Math.min(size, 2000));
+    void init(int from, double fromWeight, int to, double toWeight) {
+        weightApprox.setFrom(from);
+        weightApprox.setTo(to);
+        super.init(from, fromWeight, to, toWeight);
     }
 
     @Override
-    protected boolean finished() {
-        // we need to finish BOTH searches for CH!
-        if (finishedFrom && finishedTo)
-            return true;
+    protected SPTEntry createStartEntry(int node, double weight, boolean reverse) {
+        double heapWeight = weight + weightApprox.approximate(node, reverse);
+        return new AStar.AStarEntry(EdgeIterator.NO_EDGE, node, heapWeight, weight);
+    }
 
-        // changed finish condition for CH
-        return currFrom.weight >= bestWeight && currTo.weight >= bestWeight;
+    @Override
+    protected SPTEntry createEntry(RoutingCHEdgeIteratorState edge, int incEdge, double weight, SPTEntry parent, boolean reverse) {
+        int neighborNode = edge.getAdjNode();
+        double heapWeight = weight + weightApprox.approximate(neighborNode, reverse);
+        AStar.AStarEntry entry = new AStar.AStarEntry(edge.getEdge(), neighborNode, heapWeight, weight);
+        entry.parent = parent;
+        return entry;
     }
 
     @Override
-    protected BidirPathExtractor createPathExtractor(Graph graph, Weighting weighting) {
-        return new NodeBasedCHBidirPathExtractor(graph, graph.getBaseGraph(), weighting);
+    protected void updateEntry(SPTEntry entry, RoutingCHEdgeIteratorState edge, int edgeId, double weight, SPTEntry parent, boolean reverse) {
+        entry.edge = edge.getEdge();
+        entry.weight = weight + weightApprox.approximate(edge.getAdjNode(), reverse);
+        ((AStar.AStarEntry) entry).weightOfVisitedPath = weight;
+        entry.parent = parent;
     }
 
     @Override
-    public String getName() {
-        return "astarbi|ch";
+    protected double calcWeight(RoutingCHEdgeIteratorState iter, SPTEntry currEdge, boolean reverse) {
+        // TODO performance: check if the node is already existent in the opposite direction
+        // then we could avoid the approximation as we already know the exact complete path!
+        return super.calcWeight(iter, currEdge, reverse);
+    }
+
+    public WeightApproximator getApproximation() {
+        return weightApprox.getApproximation();
+    }
+
+    /**
+     * @param approx if true it enables approximate distance calculation from lat,lon values
+     */
+    public AStarBidirectionCH setApproximation(WeightApproximator approx) {
+        weightApprox = new ConsistentWeightApproximator(approx);
+        return this;
+    }
+
+    void setFromDataStructures(AStarBidirectionCH astar) {
+        super.setFromDataStructures(astar);
+        weightApprox.setFrom(astar.currFrom.adjNode);
+    }
+
+    void setToDataStructures(AStarBidirectionCH astar) {
+        super.setToDataStructures(astar);
+        weightApprox.setTo(astar.currTo.adjNode);
+    }
+
+    @Override
+    public void afterHeuristicChange(boolean forward, boolean backward) {
+        AStarBidirection.updatePriorityQueues(pqOpenSetFrom, pqOpenSetTo, weightApprox, forward, backward);
     }
 
     @Override
-    public String toString() {
-        return getName() + "|" + weighting;
+    public String getName() {
+        return "astarbi|ch";
     }
+
 }
diff --git a/core/src/main/java/com/graphhopper/routing/AStarBidirectionEdgeCHNoSOD.java b/core/src/main/java/com/graphhopper/routing/AStarBidirectionEdgeCHNoSOD.java
index c5d3915f8..384778cc5 100644
--- a/core/src/main/java/com/graphhopper/routing/AStarBidirectionEdgeCHNoSOD.java
+++ b/core/src/main/java/com/graphhopper/routing/AStarBidirectionEdgeCHNoSOD.java
@@ -18,10 +18,12 @@
 package com.graphhopper.routing;
 
 import com.graphhopper.routing.ch.AStarCHEntry;
-import com.graphhopper.routing.weighting.*;
-import com.graphhopper.storage.Graph;
+import com.graphhopper.routing.weighting.BeelineWeightApproximator;
+import com.graphhopper.routing.weighting.ConsistentWeightApproximator;
+import com.graphhopper.routing.weighting.WeightApproximator;
+import com.graphhopper.storage.RoutingCHEdgeIteratorState;
+import com.graphhopper.storage.RoutingCHGraph;
 import com.graphhopper.storage.SPTEntry;
-import com.graphhopper.util.EdgeIteratorState;
 import com.graphhopper.util.Helper;
 
 /**
@@ -31,9 +33,9 @@ public class AStarBidirectionEdgeCHNoSOD extends AbstractBidirectionEdgeCHNoSOD
     private final boolean useHeuristicForNodeOrder = false;
     private ConsistentWeightApproximator weightApprox;
 
-    public AStarBidirectionEdgeCHNoSOD(Graph graph, TurnWeighting weighting) {
-        super(graph, weighting);
-        setApproximation(new BeelineWeightApproximator(nodeAccess, weighting).setDistanceCalc(Helper.DIST_PLANE));
+    public AStarBidirectionEdgeCHNoSOD(RoutingCHGraph graph) {
+        super(graph);
+        setApproximation(new BeelineWeightApproximator(nodeAccess, graph.getWeighting()).setDistanceCalc(Helper.DIST_PLANE));
     }
 
     @Override
@@ -60,7 +62,7 @@ public class AStarBidirectionEdgeCHNoSOD extends AbstractBidirectionEdgeCHNoSOD
     }
 
     @Override
-    protected SPTEntry createEntry(EdgeIteratorState edge, int incEdge, double weight, SPTEntry parent, boolean reverse) {
+    protected SPTEntry createEntry(RoutingCHEdgeIteratorState edge, int incEdge, double weight, SPTEntry parent, boolean reverse) {
         int neighborNode = edge.getAdjNode();
         double heapWeight = getHeapWeight(neighborNode, reverse, weight);
         AStarCHEntry entry = new AStarCHEntry(edge.getEdge(), incEdge, neighborNode, heapWeight, weight);
@@ -69,7 +71,7 @@ public class AStarBidirectionEdgeCHNoSOD extends AbstractBidirectionEdgeCHNoSOD
     }
 
     @Override
-    protected void updateEntry(SPTEntry entry, EdgeIteratorState edge, int edgeId, double weight, SPTEntry parent, boolean reverse) {
+    protected void updateEntry(SPTEntry entry, RoutingCHEdgeIteratorState edge, int edgeId, double weight, SPTEntry parent, boolean reverse) {
         entry.edge = edge.getEdge();
         ((AStarCHEntry) entry).incEdge = edgeId;
         entry.weight = getHeapWeight(edge.getAdjNode(), reverse, weight);
diff --git a/core/src/main/java/com/graphhopper/routing/AbstractBidirAlgo.java b/core/src/main/java/com/graphhopper/routing/AbstractBidirAlgo.java
index 0b07c72a9..84af50442 100644
--- a/core/src/main/java/com/graphhopper/routing/AbstractBidirAlgo.java
+++ b/core/src/main/java/com/graphhopper/routing/AbstractBidirAlgo.java
@@ -19,28 +19,18 @@ package com.graphhopper.routing;
 
 import com.carrotsearch.hppc.IntObjectMap;
 import com.graphhopper.coll.GHIntObjectHashMap;
-import com.graphhopper.routing.util.EdgeFilter;
 import com.graphhopper.routing.util.TraversalMode;
-import com.graphhopper.routing.weighting.Weighting;
-import com.graphhopper.storage.Graph;
+import com.graphhopper.storage.NodeAccess;
 import com.graphhopper.storage.SPTEntry;
-import com.graphhopper.util.EdgeExplorer;
-import com.graphhopper.util.EdgeIterator;
-import com.graphhopper.util.EdgeIteratorState;
-import com.graphhopper.util.GHUtility;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.PriorityQueue;
 
 import static com.graphhopper.util.EdgeIterator.ANY_EDGE;
 
-/**
- * Common subclass for bidirectional algorithms.
- * <p>
- *
- * @author Peter Karich
- * @author easbar
- */
-public abstract class AbstractBidirAlgo extends AbstractRoutingAlgorithm implements BidirRoutingAlgorithm {
+public abstract class AbstractBidirAlgo implements BidirRoutingAlgorithm {
+    protected final TraversalMode traversalMode;
     protected int from;
     protected int to;
     protected int fromOutEdge;
@@ -52,22 +42,22 @@ public abstract class AbstractBidirAlgo extends AbstractRoutingAlgorithm impleme
     protected SPTEntry currTo;
     protected SPTEntry bestFwdEntry;
     protected SPTEntry bestBwdEntry;
-    protected double bestWeight;
+    protected double bestWeight = Double.MAX_VALUE;
+    protected NodeAccess nodeAccess;
+    protected int maxVisitedNodes = Integer.MAX_VALUE;
     PriorityQueue<SPTEntry> pqOpenSetFrom;
     PriorityQueue<SPTEntry> pqOpenSetTo;
-    private boolean updateBestPath = true;
+    protected boolean updateBestPath = true;
     protected boolean finishedFrom;
     protected boolean finishedTo;
     int visitedCountFrom;
     int visitedCountTo;
+    private boolean alreadyRun;
 
-    public AbstractBidirAlgo(Graph graph, Weighting weighting, TraversalMode tMode) {
-        super(graph, weighting, tMode);
+    public AbstractBidirAlgo(TraversalMode traversalMode) {
+        this.traversalMode = traversalMode;
         fromOutEdge = ANY_EDGE;
         toInEdge = ANY_EDGE;
-        bestWeight = Double.MAX_VALUE;
-        int size = Math.min(Math.max(200, graph.getNodes() / 10), 150_000);
-        initCollections(size);
     }
 
     protected void initCollections(int size) {
@@ -83,20 +73,6 @@ public abstract class AbstractBidirAlgo extends AbstractRoutingAlgorithm impleme
      */
     protected abstract SPTEntry createStartEntry(int node, double weight, boolean reverse);
 
-    /**
-     * Creates a new entry of the shortest path tree (a {@link SPTEntry} or one of its subclasses) during a dijkstra
-     * expansion.
-     *
-     * @param edge    the edge that is currently processed for the expansion
-     * @param incEdge the id of the edge that is incoming to the node the edge is pointed at. usually this is the same as
-     *                edge.getEdge(), but for edge-based CH and in case edge is a shortcut incEdge is the original edge
-     *                that is incoming to the node
-     * @param weight  the weight the shortest path three entry should carry
-     * @param parent  the parent entry of in the shortest path tree
-     * @param reverse true if we are currently looking at the backward search, false otherwise
-     */
-    protected abstract SPTEntry createEntry(EdgeIteratorState edge, int incEdge, double weight, SPTEntry parent, boolean reverse);
-
     @Override
     public Path calcPath(int from, int to) {
         return calcPath(from, to, ANY_EDGE, ANY_EDGE);
@@ -115,10 +91,6 @@ public abstract class AbstractBidirAlgo extends AbstractRoutingAlgorithm impleme
         return extractPath();
     }
 
-    protected BidirPathExtractor createPathExtractor(Graph graph, Weighting weighting) {
-        return new BidirPathExtractor(graph, weighting);
-    }
-
     void init(int from, double fromWeight, int to, double toWeight) {
         initFrom(from, fromWeight);
         initTo(to, toWeight);
@@ -147,7 +119,7 @@ public abstract class AbstractBidirAlgo extends AbstractRoutingAlgorithm impleme
         if (!traversalMode.isEdgeBased()) {
             if (updateBestPath) {
                 bestWeightMapOther = bestWeightMapFrom;
-                updateBestPath(GHUtility.getEdge(graph, currFrom.adjNode, to), currFrom, to, true);
+                updateBestPath(Double.POSITIVE_INFINITY, currFrom, -1, to, true);
             }
         } else if (from == to && fromOutEdge == ANY_EDGE && toInEdge == ANY_EDGE) {
             // special handling if start and end are the same and no directions are restricted
@@ -166,57 +138,9 @@ public abstract class AbstractBidirAlgo extends AbstractRoutingAlgorithm impleme
         postInitTo();
     }
 
-    protected void postInitFrom() {
-        if (fromOutEdge == ANY_EDGE) {
-            fillEdgesFromUsingFilter(additionalEdgeFilter);
-        } else {
-            // need to use a local reference here, because additionalEdgeFilter is modified when calling fillEdgesFromUsingFilter
-            final EdgeFilter tmpFilter = additionalEdgeFilter;
-            fillEdgesFromUsingFilter(new EdgeFilter() {
-                @Override
-                public boolean accept(EdgeIteratorState edgeState) {
-                    return (tmpFilter == null || tmpFilter.accept(edgeState)) && edgeState.getOrigEdgeFirst() == fromOutEdge;
-                }
-            });
-        }
-    }
-
-    protected void postInitTo() {
-        if (toInEdge == ANY_EDGE) {
-            fillEdgesToUsingFilter(additionalEdgeFilter);
-        } else {
-            final EdgeFilter tmpFilter = additionalEdgeFilter;
-            fillEdgesToUsingFilter(new EdgeFilter() {
-                @Override
-                public boolean accept(EdgeIteratorState edgeState) {
-                    return (tmpFilter == null || tmpFilter.accept(edgeState)) && edgeState.getOrigEdgeLast() == toInEdge;
-                }
-            });
-        }
-    }
+    protected abstract void postInitFrom();
 
-    /**
-     * @param edgeFilter edge filter used to fill edges. the {@link #additionalEdgeFilter} reference will be set to
-     *                   edgeFilter by this method, so make sure edgeFilter does not use it directly.
-     */
-    protected void fillEdgesFromUsingFilter(EdgeFilter edgeFilter) {
-        // we temporarily ignore the additionalEdgeFilter
-        EdgeFilter tmpFilter = additionalEdgeFilter;
-        additionalEdgeFilter = edgeFilter;
-        finishedFrom = !fillEdgesFrom();
-        additionalEdgeFilter = tmpFilter;
-    }
-
-    /**
-     * @see #fillEdgesFromUsingFilter(EdgeFilter)
-     */
-    protected void fillEdgesToUsingFilter(EdgeFilter edgeFilter) {
-        // we temporarily ignore the additionalEdgeFilter
-        EdgeFilter tmpFilter = additionalEdgeFilter;
-        additionalEdgeFilter = edgeFilter;
-        finishedTo = !fillEdgesTo();
-        additionalEdgeFilter = tmpFilter;
-    }
+    protected abstract void postInitTo();
 
     protected void runAlgo() {
         while (!finished() && !isMaxVisitedNodesExceeded()) {
@@ -232,7 +156,6 @@ public abstract class AbstractBidirAlgo extends AbstractRoutingAlgorithm impleme
     // a node from overlap may not be on the best path!
     // => when scanning an arc (v, w) in the forward search and w is scanned in the reverseOrder
     //    search, update extractPath = μ if df (v) + (v, w) + dr (w) < μ
-    @Override
     protected boolean finished() {
         if (finishedFrom || finishedTo)
             return true;
@@ -240,71 +163,12 @@ public abstract class AbstractBidirAlgo extends AbstractRoutingAlgorithm impleme
         return currFrom.weight + currTo.weight >= bestWeight;
     }
 
-    boolean fillEdgesFrom() {
-        if (pqOpenSetFrom.isEmpty()) {
-            return false;
-        }
-        currFrom = pqOpenSetFrom.poll();
-        visitedCountFrom++;
-        if (fromEntryCanBeSkipped()) {
-            return true;
-        }
-        if (fwdSearchCanBeStopped()) {
-            return false;
-        }
-        bestWeightMapOther = bestWeightMapTo;
-        fillEdges(currFrom, pqOpenSetFrom, bestWeightMapFrom, outEdgeExplorer, false);
-        return true;
-    }
-
-    boolean fillEdgesTo() {
-        if (pqOpenSetTo.isEmpty()) {
-            return false;
-        }
-        currTo = pqOpenSetTo.poll();
-        visitedCountTo++;
-        if (toEntryCanBeSkipped()) {
-            return true;
-        }
-        if (bwdSearchCanBeStopped()) {
-            return false;
-        }
-        bestWeightMapOther = bestWeightMapFrom;
-        fillEdges(currTo, pqOpenSetTo, bestWeightMapTo, inEdgeExplorer, true);
-        return true;
-    }
+    abstract boolean fillEdgesFrom();
 
-    private void fillEdges(SPTEntry currEdge, PriorityQueue<SPTEntry> prioQueue,
-                           IntObjectMap<SPTEntry> bestWeightMap, EdgeExplorer explorer, boolean reverse) {
-        EdgeIterator iter = explorer.setBaseNode(currEdge.adjNode);
-        while (iter.next()) {
-            if (!accept(iter, currEdge, reverse))
-                continue;
+    abstract boolean fillEdgesTo();
 
-            final double weight = calcWeight(iter, currEdge, reverse);
-            if (Double.isInfinite(weight)) {
-                continue;
-            }
-            final int origEdgeId = getOrigEdgeId(iter, reverse);
-            final int traversalId = getTraversalId(iter, origEdgeId, reverse);
-            SPTEntry entry = bestWeightMap.get(traversalId);
-            if (entry == null) {
-                entry = createEntry(iter, origEdgeId, weight, currEdge, reverse);
-                bestWeightMap.put(traversalId, entry);
-                prioQueue.add(entry);
-            } else if (entry.getWeightOfVisitedPath() > weight) {
-                prioQueue.remove(entry);
-                updateEntry(entry, iter, origEdgeId, weight, currEdge, reverse);
-                prioQueue.add(entry);
-            } else
-                continue;
-
-            if (updateBestPath)
-                updateBestPath(iter, entry, traversalId, reverse);
-        }
-    }
-
-    protected void updateBestPath(EdgeIteratorState edgeState, SPTEntry entry, int traversalId, boolean reverse) {
+    protected void updateBestPath(double edgeWeight, SPTEntry entry, int origEdgeId, int traversalId, boolean reverse) {
+        assert traversalMode.isEdgeBased() != Double.isInfinite(edgeWeight);
         SPTEntry entryOther = bestWeightMapOther.get(traversalId);
         if (entryOther == null)
             return;
@@ -317,7 +181,7 @@ public abstract class AbstractBidirAlgo extends AbstractRoutingAlgorithm impleme
 
             // prevents the path to contain the edge at the meeting point twice and subtracts the weight (excluding turn weight => no previous edge)
             entry = entry.getParent();
-            weight -= weighting.calcEdgeWeight(edgeState, reverse);
+            weight -= edgeWeight;
         }
 
         if (weight < bestWeight) {
@@ -327,39 +191,15 @@ public abstract class AbstractBidirAlgo extends AbstractRoutingAlgorithm impleme
         }
     }
 
-    protected void updateEntry(SPTEntry entry, EdgeIteratorState edge, int edgeId, double weight, SPTEntry parent, boolean reverse) {
-        entry.edge = edge.getEdge();
-        entry.weight = weight;
-        entry.parent = parent;
-    }
-
-    protected boolean accept(EdgeIteratorState edge, SPTEntry currEdge, boolean reverse) {
-        return accept(edge, getIncomingEdge(currEdge));
-    }
+    protected abstract double getInEdgeWeight(SPTEntry entry);
 
-    protected int getOrigEdgeId(EdgeIteratorState edge, boolean reverse) {
-        return edge.getEdge();
-    }
+    protected abstract int getOtherNode(int edge, int node);
 
     protected int getIncomingEdge(SPTEntry entry) {
         return entry.edge;
     }
 
-    protected int getTraversalId(EdgeIteratorState edge, int origEdgeId, boolean reverse) {
-        return traversalMode.createTraversalId(edge, reverse);
-    }
-
-    protected double calcWeight(EdgeIteratorState iter, SPTEntry currEdge, boolean reverse) {
-        return weighting.calcWeight(iter, reverse, getIncomingEdge(currEdge)) + currEdge.getWeightOfVisitedPath();
-    }
-
-    @Override
-    protected Path extractPath() {
-        if (finished())
-            return createPathExtractor(graph, weighting).extract(bestFwdEntry, bestBwdEntry, bestWeight);
-
-        return createEmptyPath();
-    }
+    abstract protected Path extractPath();
 
     protected boolean fromEntryCanBeSkipped() {
         return false;
@@ -427,4 +267,31 @@ public abstract class AbstractBidirAlgo extends AbstractRoutingAlgorithm impleme
         visitedCountTo = other.visitedCountTo;
         // inEdgeExplorer
     }
+
+    @Override
+    public void setMaxVisitedNodes(int numberOfNodes) {
+        this.maxVisitedNodes = numberOfNodes;
+    }
+
+    protected void checkAlreadyRun() {
+        if (alreadyRun)
+            throw new IllegalStateException("Create a new instance per call");
+
+        alreadyRun = true;
+    }
+
+    @Override
+    public List<Path> calcPaths(int from, int to) {
+        return Collections.singletonList(calcPath(from, to));
+    }
+
+    @Override
+    public String getName() {
+        return getClass().getSimpleName();
+    }
+
+    protected boolean isMaxVisitedNodesExceeded() {
+        return maxVisitedNodes < getVisitedNodes();
+    }
+
 }
diff --git a/core/src/main/java/com/graphhopper/routing/AbstractBidirCHAlgo.java b/core/src/main/java/com/graphhopper/routing/AbstractBidirCHAlgo.java
new file mode 100644
index 000000000..084e3fd62
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/AbstractBidirCHAlgo.java
@@ -0,0 +1,306 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except in
+ *  compliance with the License. You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package com.graphhopper.routing;
+
+import com.carrotsearch.hppc.IntObjectMap;
+import com.graphhopper.routing.ch.NodeBasedCHBidirPathExtractor;
+import com.graphhopper.routing.util.TraversalMode;
+import com.graphhopper.storage.*;
+
+import java.util.PriorityQueue;
+
+import static com.graphhopper.util.EdgeIterator.ANY_EDGE;
+
+/**
+ * Common subclass for bidirectional CH algorithms.
+ * <p>
+ *
+ * @author Peter Karich
+ * @author easbar
+ * @see AbstractNonCHBidirAlgo for non-CH bidirectional algorithms
+ */
+public abstract class AbstractBidirCHAlgo extends AbstractBidirAlgo implements BidirRoutingAlgorithm {
+    protected final RoutingCHGraph graph;
+    protected RoutingCHEdgeExplorer allEdgeExplorer;
+    protected RoutingCHEdgeExplorer inEdgeExplorer;
+    protected RoutingCHEdgeExplorer outEdgeExplorer;
+    protected CHEdgeFilter levelEdgeFilter;
+
+    public AbstractBidirCHAlgo(RoutingCHGraph graph, TraversalMode tMode) {
+        super(tMode);
+        this.graph = graph;
+        this.nodeAccess = graph.getGraph().getNodeAccess();
+        allEdgeExplorer = graph.createAllEdgeExplorer();
+        outEdgeExplorer = graph.createOutEdgeExplorer();
+        inEdgeExplorer = graph.createInEdgeExplorer();
+        levelEdgeFilter = new CHLevelEdgeFilter(graph);
+        int size = Math.min(Math.max(200, graph.getNodes() / 10), 150_000);
+        initCollections(size);
+    }
+
+    @Override
+    protected void initCollections(int size) {
+        super.initCollections(Math.min(size, 2000));
+    }
+
+    /**
+     * Creates a new entry of the shortest path tree (a {@link SPTEntry} or one of its subclasses) during a dijkstra
+     * expansion.
+     *
+     * @param edge    the edge that is currently processed for the expansion
+     * @param incEdge the id of the edge that is incoming to the node the edge is pointed at. usually this is the same as
+     *                edge.getEdge(), but for edge-based CH and in case edge is a shortcut incEdge is the original edge
+     *                that is incoming to the node
+     * @param weight  the weight the shortest path three entry should carry
+     * @param parent  the parent entry of in the shortest path tree
+     * @param reverse true if we are currently looking at the backward search, false otherwise
+     */
+    protected abstract SPTEntry createEntry(RoutingCHEdgeIteratorState edge, int incEdge, double weight, SPTEntry parent, boolean reverse);
+
+    protected BidirPathExtractor createPathExtractor(RoutingCHGraph graph) {
+        return new NodeBasedCHBidirPathExtractor(graph);
+    }
+
+    @Override
+    protected void postInitFrom() {
+        if (fromOutEdge == ANY_EDGE) {
+            fillEdgesFromUsingFilter(levelEdgeFilter);
+        } else {
+            // need to use a local reference here, because levelEdgeFilter is modified when calling fillEdgesFromUsingFilter
+            final CHEdgeFilter tmpFilter = levelEdgeFilter;
+            fillEdgesFromUsingFilter(new CHEdgeFilter() {
+                @Override
+                public boolean accept(RoutingCHEdgeIteratorState edgeState) {
+                    return (tmpFilter == null || tmpFilter.accept(edgeState)) && edgeState.getOrigEdgeFirst() == fromOutEdge;
+                }
+            });
+        }
+    }
+
+    @Override
+    protected void postInitTo() {
+        if (toInEdge == ANY_EDGE) {
+            fillEdgesToUsingFilter(levelEdgeFilter);
+        } else {
+            final CHEdgeFilter tmpFilter = levelEdgeFilter;
+            fillEdgesToUsingFilter(new CHEdgeFilter() {
+                @Override
+                public boolean accept(RoutingCHEdgeIteratorState edgeState) {
+                    return (tmpFilter == null || tmpFilter.accept(edgeState)) && edgeState.getOrigEdgeLast() == toInEdge;
+                }
+            });
+        }
+    }
+
+    /**
+     * @param edgeFilter edge filter used to fill edges. the {@link #levelEdgeFilter} reference will be set to
+     *                   edgeFilter by this method, so make sure edgeFilter does not use it directly.
+     */
+    protected void fillEdgesFromUsingFilter(CHEdgeFilter edgeFilter) {
+        // we temporarily ignore the additionalEdgeFilter
+        CHEdgeFilter tmpFilter = levelEdgeFilter;
+        levelEdgeFilter = edgeFilter;
+        finishedFrom = !fillEdgesFrom();
+        levelEdgeFilter = tmpFilter;
+    }
+
+    /**
+     * @see #fillEdgesFromUsingFilter(CHEdgeFilter)
+     */
+    protected void fillEdgesToUsingFilter(CHEdgeFilter edgeFilter) {
+        // we temporarily ignore the additionalEdgeFilter
+        CHEdgeFilter tmpFilter = levelEdgeFilter;
+        levelEdgeFilter = edgeFilter;
+        finishedTo = !fillEdgesTo();
+        levelEdgeFilter = tmpFilter;
+    }
+
+    @Override
+    public boolean finished() {
+        // we need to finish BOTH searches for CH!
+        if (finishedFrom && finishedTo)
+            return true;
+
+        // changed also the final finish condition for CH
+        return currFrom.weight >= bestWeight && currTo.weight >= bestWeight;
+    }
+
+    @Override
+    boolean fillEdgesFrom() {
+        if (pqOpenSetFrom.isEmpty()) {
+            return false;
+        }
+        currFrom = pqOpenSetFrom.poll();
+        visitedCountFrom++;
+        if (fromEntryCanBeSkipped()) {
+            return true;
+        }
+        if (fwdSearchCanBeStopped()) {
+            return false;
+        }
+        bestWeightMapOther = bestWeightMapTo;
+        fillEdges(currFrom, pqOpenSetFrom, bestWeightMapFrom, outEdgeExplorer, false);
+        return true;
+    }
+
+    @Override
+    boolean fillEdgesTo() {
+        if (pqOpenSetTo.isEmpty()) {
+            return false;
+        }
+        currTo = pqOpenSetTo.poll();
+        visitedCountTo++;
+        if (toEntryCanBeSkipped()) {
+            return true;
+        }
+        if (bwdSearchCanBeStopped()) {
+            return false;
+        }
+        bestWeightMapOther = bestWeightMapFrom;
+        fillEdges(currTo, pqOpenSetTo, bestWeightMapTo, inEdgeExplorer, true);
+        return true;
+    }
+
+    private void fillEdges(SPTEntry currEdge, PriorityQueue<SPTEntry> prioQueue,
+                           IntObjectMap<SPTEntry> bestWeightMap, RoutingCHEdgeExplorer explorer, boolean reverse) {
+        RoutingCHEdgeIterator iter = explorer.setBaseNode(currEdge.adjNode);
+        while (iter.next()) {
+            if (!accept(iter, currEdge, reverse))
+                continue;
+
+            final double weight = calcWeight(iter, currEdge, reverse);
+            if (Double.isInfinite(weight)) {
+                continue;
+            }
+            final int origEdgeId = getOrigEdgeId(iter, reverse);
+            final int traversalId = getTraversalId(iter, origEdgeId, reverse);
+            SPTEntry entry = bestWeightMap.get(traversalId);
+            if (entry == null) {
+                entry = createEntry(iter, origEdgeId, weight, currEdge, reverse);
+                bestWeightMap.put(traversalId, entry);
+                prioQueue.add(entry);
+            } else if (entry.getWeightOfVisitedPath() > weight) {
+                prioQueue.remove(entry);
+                updateEntry(entry, iter, origEdgeId, weight, currEdge, reverse);
+                prioQueue.add(entry);
+            } else
+                continue;
+
+            if (updateBestPath) {
+                // only needed for edge-based -> skip the calculation and use dummy value otherwise
+                updateBestPath(Double.POSITIVE_INFINITY, entry, origEdgeId, traversalId, reverse);
+            }
+        }
+    }
+
+    protected double calcWeight(RoutingCHEdgeIteratorState edgeState, boolean reverse, int prevOrNextEdgeId) {
+        double edgeWeight = edgeState.getWeight(reverse);
+        final int origEdgeId = reverse ? edgeState.getOrigEdgeLast() : edgeState.getOrigEdgeFirst();
+        double turnCosts = reverse
+                ? graph.getTurnWeight(origEdgeId, edgeState.getBaseNode(), prevOrNextEdgeId)
+                : graph.getTurnWeight(prevOrNextEdgeId, edgeState.getBaseNode(), origEdgeId);
+        return edgeWeight + turnCosts;
+    }
+
+    protected void updateEntry(SPTEntry entry, RoutingCHEdgeIteratorState edge, int edgeId, double weight, SPTEntry parent, boolean reverse) {
+        entry.edge = edge.getEdge();
+        entry.weight = weight;
+        entry.parent = parent;
+    }
+
+    protected boolean accept(RoutingCHEdgeIteratorState edge, SPTEntry currEdge, boolean reverse) {
+        return accept(edge, getIncomingEdge(currEdge));
+    }
+
+    protected int getOrigEdgeId(RoutingCHEdgeIteratorState edge, boolean reverse) {
+        return edge.getEdge();
+    }
+
+    protected int getTraversalId(RoutingCHEdgeIteratorState edge, int origEdgeId, boolean reverse) {
+        return getTraversalId(edge, reverse);
+    }
+
+    protected int getTraversalId(RoutingCHEdgeIteratorState edge, boolean reverse) {
+        return traversalMode.createTraversalId(edge.getBaseNode(), edge.getAdjNode(), edge.getEdge(), reverse);
+    }
+
+    @Override
+    protected int getOtherNode(int edge, int node) {
+        return graph.getOtherNode(edge, node);
+    }
+
+    protected double calcWeight(RoutingCHEdgeIteratorState iter, SPTEntry currEdge, boolean reverse) {
+        return calcWeight(iter, reverse, getIncomingEdge(currEdge)) + currEdge.getWeightOfVisitedPath();
+    }
+
+    @Override
+    protected double getInEdgeWeight(SPTEntry entry) {
+        return graph.getEdgeIteratorState(getIncomingEdge(entry), entry.adjNode).getWeight(false);
+    }
+
+    @Override
+    protected Path extractPath() {
+        if (finished())
+            return createPathExtractor(graph).extract(bestFwdEntry, bestBwdEntry, bestWeight);
+
+        return createEmptyPath();
+    }
+
+    protected boolean accept(RoutingCHEdgeIteratorState iter, int prevOrNextEdgeId) {
+        // for edge-based traversal we leave it for TurnWeighting to decide whether or not a u-turn is acceptable,
+        // but for node-based traversal we exclude such a turn for performance reasons already here
+        if (!traversalMode.isEdgeBased() && iter.getEdge() == prevOrNextEdgeId)
+            return false;
+
+        return levelEdgeFilter == null || levelEdgeFilter.accept(iter);
+    }
+
+    protected Path createEmptyPath() {
+        return new Path(graph.getGraph());
+    }
+
+    @Override
+    public String toString() {
+        return getName() + "|" + graph.getWeighting();
+    }
+
+    private static class CHLevelEdgeFilter implements CHEdgeFilter {
+        private final RoutingCHGraph graph;
+        private final int maxNodes;
+
+        public CHLevelEdgeFilter(RoutingCHGraph graph) {
+            this.graph = graph;
+            maxNodes = graph.getBaseGraph().getNodes();
+        }
+
+        @Override
+        public boolean accept(RoutingCHEdgeIteratorState edgeState) {
+            int base = edgeState.getBaseNode();
+            int adj = edgeState.getAdjNode();
+            // always accept virtual edges, see #288
+            if (base >= maxNodes || adj >= maxNodes)
+                return true;
+
+            // minor performance improvement: shortcuts in wrong direction are disconnected, so no need to exclude them
+            if (edgeState.isShortcut())
+                return true;
+
+            return graph.getLevel(base) <= graph.getLevel(adj);
+        }
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/routing/AbstractBidirectionEdgeCHNoSOD.java b/core/src/main/java/com/graphhopper/routing/AbstractBidirectionEdgeCHNoSOD.java
index 74361b9df..11f85cc40 100644
--- a/core/src/main/java/com/graphhopper/routing/AbstractBidirectionEdgeCHNoSOD.java
+++ b/core/src/main/java/com/graphhopper/routing/AbstractBidirectionEdgeCHNoSOD.java
@@ -19,16 +19,8 @@ package com.graphhopper.routing;
 
 import com.graphhopper.routing.ch.CHEntry;
 import com.graphhopper.routing.ch.EdgeBasedCHBidirPathExtractor;
-import com.graphhopper.routing.util.DefaultEdgeFilter;
-import com.graphhopper.routing.util.EdgeFilter;
 import com.graphhopper.routing.util.TraversalMode;
-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.EdgeExplorer;
-import com.graphhopper.util.EdgeIterator;
-import com.graphhopper.util.EdgeIteratorState;
+import com.graphhopper.storage.*;
 import com.graphhopper.util.GHUtility;
 
 import static com.graphhopper.util.EdgeIterator.ANY_EDGE;
@@ -36,19 +28,16 @@ import static com.graphhopper.util.EdgeIterator.ANY_EDGE;
 /**
  * @author easbar
  */
-public abstract class AbstractBidirectionEdgeCHNoSOD extends AbstractBidirAlgo {
-    private final EdgeExplorer innerInExplorer;
-    private final EdgeExplorer innerOutExplorer;
-    private final TurnWeighting turnWeighting;
-
-    public AbstractBidirectionEdgeCHNoSOD(Graph graph, TurnWeighting weighting) {
-        super(graph, weighting, TraversalMode.EDGE_BASED);
-        this.turnWeighting = weighting;
-        // the inner explorers will run on the base- (or query/base-)graph edges only
-        Graph baseGraph = graph.getBaseGraph();
+public abstract class AbstractBidirectionEdgeCHNoSOD extends AbstractBidirCHAlgo {
+    private final RoutingCHEdgeExplorer innerInExplorer;
+    private final RoutingCHEdgeExplorer innerOutExplorer;
+
+    public AbstractBidirectionEdgeCHNoSOD(RoutingCHGraph graph) {
+        super(graph, TraversalMode.EDGE_BASED);
+        // the inner explorers will run on the base-(or base-query-)graph edges only
         // we need extra edge explorers, because they get called inside a loop that already iterates over edges
-        innerInExplorer = baseGraph.createEdgeExplorer(DefaultEdgeFilter.inEdges(flagEncoder));
-        innerOutExplorer = baseGraph.createEdgeExplorer(DefaultEdgeFilter.outEdges(flagEncoder));
+        innerInExplorer = graph.createOriginalInEdgeExplorer();
+        innerOutExplorer = graph.createOriginalOutEdgeExplorer();
     }
 
     @Override
@@ -57,11 +46,11 @@ public abstract class AbstractBidirectionEdgeCHNoSOD extends AbstractBidirAlgo {
         // i.e. LevelEdgeFilter, For the first step though we need all edges, so we need to ignore this filter.
         // Using an arbitrary filter is not supported for CH anyway.
         if (fromOutEdge == ANY_EDGE) {
-            fillEdgesFromUsingFilter(EdgeFilter.ALL_EDGES);
+            fillEdgesFromUsingFilter(CHEdgeFilter.ALL_EDGES);
         } else {
-            fillEdgesFromUsingFilter(new EdgeFilter() {
+            fillEdgesFromUsingFilter(new CHEdgeFilter() {
                 @Override
-                public boolean accept(EdgeIteratorState edgeState) {
+                public boolean accept(RoutingCHEdgeIteratorState edgeState) {
                     return edgeState.getOrigEdgeFirst() == fromOutEdge;
                 }
             });
@@ -71,11 +60,11 @@ public abstract class AbstractBidirectionEdgeCHNoSOD extends AbstractBidirAlgo {
     @Override
     protected void postInitTo() {
         if (toInEdge == ANY_EDGE) {
-            fillEdgesToUsingFilter(EdgeFilter.ALL_EDGES);
+            fillEdgesToUsingFilter(CHEdgeFilter.ALL_EDGES);
         } else {
-            fillEdgesToUsingFilter(new EdgeFilter() {
+            fillEdgesToUsingFilter(new CHEdgeFilter() {
                 @Override
-                public boolean accept(EdgeIteratorState edgeState) {
+                public boolean accept(RoutingCHEdgeIteratorState edgeState) {
                     return edgeState.getOrigEdgeLast() == toInEdge;
                 }
             });
@@ -83,29 +72,15 @@ public abstract class AbstractBidirectionEdgeCHNoSOD extends AbstractBidirAlgo {
     }
 
     @Override
-    protected void initCollections(int size) {
-        super.initCollections(Math.min(size, 2000));
-    }
-
-    @Override
-    public boolean finished() {
-        // we need to finish BOTH searches for CH!
-        if (finishedFrom && finishedTo)
-            return true;
-
-        // changed also the final finish condition for CH
-        return currFrom.weight >= bestWeight && currTo.weight >= bestWeight;
-    }
-
-    @Override
-    protected void updateBestPath(EdgeIteratorState edgeState, SPTEntry entry, int traversalId, boolean reverse) {
+    protected void updateBestPath(double edgeWeight, SPTEntry entry, int origEdgeId, int traversalId, boolean reverse) {
+        assert Double.isInfinite(edgeWeight) : "edge-based CH does not use pre-calculated edge weight";
         // special case where the fwd/bwd search runs directly into the opposite node, for example if the highest level
         // node of the shortest path matches the source or target. in this case one of the searches does not contribute
         // anything to the shortest path.
         int oppositeNode = reverse ? from : to;
         int oppositeEdge = reverse ? fromOutEdge : toInEdge;
         boolean oppositeEdgeRestricted = reverse ? (fromOutEdge != ANY_EDGE) : (toInEdge != ANY_EDGE);
-        if (edgeState.getAdjNode() == oppositeNode && (!oppositeEdgeRestricted || getOrigEdgeId(edgeState, reverse) == oppositeEdge)) {
+        if (entry.adjNode == oppositeNode && (!oppositeEdgeRestricted || origEdgeId == oppositeEdge)) {
             if (entry.getWeightOfVisitedPath() < bestWeight) {
                 bestFwdEntry = reverse ? new CHEntry(oppositeNode, 0) : entry;
                 bestBwdEntry = reverse ? entry : new CHEntry(oppositeNode, 0);
@@ -114,24 +89,23 @@ public abstract class AbstractBidirectionEdgeCHNoSOD extends AbstractBidirAlgo {
             }
         }
 
-        EdgeIterator iter = reverse ?
-                innerInExplorer.setBaseNode(edgeState.getAdjNode()) :
-                innerOutExplorer.setBaseNode(edgeState.getAdjNode());
+        RoutingCHEdgeIterator iter = reverse ?
+                innerInExplorer.setBaseNode(entry.adjNode) :
+                innerOutExplorer.setBaseNode(entry.adjNode);
 
         // todo: for a-star it should be possible to skip bridge node check at the beginning of the search as long as
         // minimum source-target distance lies above total sum of fwd+bwd path candidates.
         while (iter.next()) {
             final int edgeId = getOrigEdgeId(iter, !reverse);
-            final int prevOrNextOrigEdgeId = getOrigEdgeId(edgeState, reverse);
-            int key = GHUtility.createEdgeKey(graph.getOtherNode(edgeId, iter.getBaseNode()), iter.getBaseNode(), edgeId, !reverse);
+            int key = GHUtility.createEdgeKey(getOtherNode(edgeId, iter.getBaseNode()), iter.getBaseNode(), edgeId, !reverse);
             SPTEntry entryOther = bestWeightMapOther.get(key);
             if (entryOther == null) {
                 continue;
             }
 
             double turnCostsAtBridgeNode = reverse ?
-                    turnWeighting.calcTurnWeight(edgeId, iter.getBaseNode(), prevOrNextOrigEdgeId) :
-                    turnWeighting.calcTurnWeight(prevOrNextOrigEdgeId, iter.getBaseNode(), edgeId);
+                    graph.getTurnWeight(edgeId, iter.getBaseNode(), origEdgeId) :
+                    graph.getTurnWeight(origEdgeId, iter.getBaseNode(), edgeId);
 
             double newWeight = entry.getWeightOfVisitedPath() + entryOther.getWeightOfVisitedPath() + turnCostsAtBridgeNode;
             if (newWeight < bestWeight) {
@@ -143,12 +117,12 @@ public abstract class AbstractBidirectionEdgeCHNoSOD extends AbstractBidirAlgo {
     }
 
     @Override
-    protected BidirPathExtractor createPathExtractor(Graph graph, Weighting weighting) {
-        return new EdgeBasedCHBidirPathExtractor(graph, graph.getBaseGraph(), weighting);
+    protected BidirPathExtractor createPathExtractor(RoutingCHGraph graph) {
+        return new EdgeBasedCHBidirPathExtractor(graph);
     }
 
     @Override
-    protected int getOrigEdgeId(EdgeIteratorState edge, boolean reverse) {
+    protected int getOrigEdgeId(RoutingCHEdgeIteratorState edge, boolean reverse) {
         return reverse ? edge.getOrigEdgeFirst() : edge.getOrigEdgeLast();
     }
 
@@ -158,27 +132,22 @@ public abstract class AbstractBidirectionEdgeCHNoSOD extends AbstractBidirAlgo {
     }
 
     @Override
-    protected int getTraversalId(EdgeIteratorState edge, int origEdgeId, boolean reverse) {
-        int baseNode = graph.getOtherNode(origEdgeId, edge.getAdjNode());
+    protected int getTraversalId(RoutingCHEdgeIteratorState edge, int origEdgeId, boolean reverse) {
+        int baseNode = getOtherNode(origEdgeId, edge.getAdjNode());
         return GHUtility.createEdgeKey(baseNode, edge.getAdjNode(), origEdgeId, reverse);
     }
 
     @Override
-    protected boolean accept(EdgeIteratorState edge, SPTEntry currEdge, boolean reverse) {
+    protected boolean accept(RoutingCHEdgeIteratorState edge, SPTEntry currEdge, boolean reverse) {
         final int incEdge = getIncomingEdge(currEdge);
         final int prevOrNextEdgeId = getOrigEdgeId(edge, !reverse);
         double turnWeight = reverse
-                ? turnWeighting.calcTurnWeight(prevOrNextEdgeId, edge.getBaseNode(), incEdge)
-                : turnWeighting.calcTurnWeight(incEdge, edge.getBaseNode(), prevOrNextEdgeId);
+                ? graph.getTurnWeight(prevOrNextEdgeId, edge.getBaseNode(), incEdge)
+                : graph.getTurnWeight(incEdge, edge.getBaseNode(), prevOrNextEdgeId);
         if (Double.isInfinite(turnWeight)) {
             return false;
         }
-        return additionalEdgeFilter == null || additionalEdgeFilter.accept(edge);
-    }
-
-    @Override
-    public String toString() {
-        return getName() + "|" + weighting;
+        return levelEdgeFilter == null || levelEdgeFilter.accept(edge);
     }
 
 }
diff --git a/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java b/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java
new file mode 100644
index 000000000..9d364400d
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java
@@ -0,0 +1,260 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except in
+ *  compliance with the License. You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package com.graphhopper.routing;
+
+import com.carrotsearch.hppc.IntObjectMap;
+import com.graphhopper.routing.util.DefaultEdgeFilter;
+import com.graphhopper.routing.util.EdgeFilter;
+import com.graphhopper.routing.util.FlagEncoder;
+import com.graphhopper.routing.util.TraversalMode;
+import com.graphhopper.routing.weighting.Weighting;
+import com.graphhopper.storage.Graph;
+import com.graphhopper.storage.SPTEntry;
+import com.graphhopper.util.EdgeExplorer;
+import com.graphhopper.util.EdgeIterator;
+import com.graphhopper.util.EdgeIteratorState;
+
+import java.util.PriorityQueue;
+
+import static com.graphhopper.util.EdgeIterator.ANY_EDGE;
+
+/**
+ * Common subclass for bidirectional algorithms.
+ *
+ * @author Peter Karich
+ * @author easbar
+ * @see AbstractBidirCHAlgo for bidirectional CH algorithms
+ */
+public abstract class AbstractNonCHBidirAlgo extends AbstractBidirAlgo implements BidirRoutingAlgorithm {
+    protected final Graph graph;
+    protected final Weighting weighting;
+    protected final FlagEncoder flagEncoder;
+    protected EdgeExplorer edgeExplorer;
+    protected EdgeFilter inEdgeFilter;
+    protected EdgeFilter outEdgeFilter;
+    protected EdgeFilter additionalEdgeFilter;
+
+    public AbstractNonCHBidirAlgo(Graph graph, Weighting weighting, TraversalMode tMode) {
+        super(tMode);
+        this.weighting = weighting;
+        this.flagEncoder = weighting.getFlagEncoder();
+        this.graph = graph;
+        this.nodeAccess = graph.getNodeAccess();
+        edgeExplorer = graph.createEdgeExplorer();
+        outEdgeFilter = DefaultEdgeFilter.outEdges(flagEncoder.getAccessEnc());
+        inEdgeFilter = DefaultEdgeFilter.inEdges(flagEncoder.getAccessEnc());
+        int size = Math.min(Math.max(200, graph.getNodes() / 10), 150_000);
+        initCollections(size);
+    }
+
+    /**
+     * Creates a new entry of the shortest path tree (a {@link SPTEntry} or one of its subclasses) during a dijkstra
+     * expansion.
+     *
+     * @param edge    the edge that is currently processed for the expansion
+     * @param incEdge the id of the edge that is incoming to the node the edge is pointed at. usually this is the same as
+     *                edge.getEdge(), but for edge-based CH and in case edge is a shortcut incEdge is the original edge
+     *                that is incoming to the node
+     * @param weight  the weight the shortest path three entry should carry
+     * @param parent  the parent entry of in the shortest path tree
+     * @param reverse true if we are currently looking at the backward search, false otherwise
+     */
+    protected abstract SPTEntry createEntry(EdgeIteratorState edge, int incEdge, double weight, SPTEntry parent, boolean reverse);
+
+    protected BidirPathExtractor createPathExtractor(Graph graph, Weighting weighting) {
+        return new BidirPathExtractor(graph, weighting);
+    }
+
+    protected void postInitFrom() {
+        if (fromOutEdge == ANY_EDGE) {
+            fillEdgesFrom();
+        } else {
+            fillEdgesFromUsingFilter(new EdgeFilter() {
+                @Override
+                public boolean accept(EdgeIteratorState edgeState) {
+                    return edgeState.getOrigEdgeFirst() == fromOutEdge;
+                }
+            });
+        }
+    }
+
+    protected void postInitTo() {
+        if (toInEdge == ANY_EDGE) {
+            fillEdgesTo();
+        } else {
+            fillEdgesToUsingFilter(new EdgeFilter() {
+                @Override
+                public boolean accept(EdgeIteratorState edgeState) {
+                    return edgeState.getOrigEdgeLast() == toInEdge;
+                }
+            });
+        }
+    }
+
+    /**
+     * @param edgeFilter edge filter used to filter edges during {@link #fillEdgesFrom()}
+     */
+    protected void fillEdgesFromUsingFilter(EdgeFilter edgeFilter) {
+        additionalEdgeFilter = edgeFilter;
+        finishedFrom = !fillEdgesFrom();
+        additionalEdgeFilter = null;
+    }
+
+    /**
+     * @see #fillEdgesFromUsingFilter(EdgeFilter)
+     */
+    protected void fillEdgesToUsingFilter(EdgeFilter edgeFilter) {
+        additionalEdgeFilter = edgeFilter;
+        finishedTo = !fillEdgesTo();
+        additionalEdgeFilter = null;
+    }
+
+    @Override
+    boolean fillEdgesFrom() {
+        if (pqOpenSetFrom.isEmpty()) {
+            return false;
+        }
+        currFrom = pqOpenSetFrom.poll();
+        visitedCountFrom++;
+        if (fromEntryCanBeSkipped()) {
+            return true;
+        }
+        if (fwdSearchCanBeStopped()) {
+            return false;
+        }
+        bestWeightMapOther = bestWeightMapTo;
+        fillEdges(currFrom, pqOpenSetFrom, bestWeightMapFrom, false);
+        return true;
+    }
+
+    @Override
+    boolean fillEdgesTo() {
+        if (pqOpenSetTo.isEmpty()) {
+            return false;
+        }
+        currTo = pqOpenSetTo.poll();
+        visitedCountTo++;
+        if (toEntryCanBeSkipped()) {
+            return true;
+        }
+        if (bwdSearchCanBeStopped()) {
+            return false;
+        }
+        bestWeightMapOther = bestWeightMapFrom;
+        fillEdges(currTo, pqOpenSetTo, bestWeightMapTo, true);
+        return true;
+    }
+
+    private void fillEdges(SPTEntry currEdge, PriorityQueue<SPTEntry> prioQueue, IntObjectMap<SPTEntry> bestWeightMap, boolean reverse) {
+        EdgeIterator iter = edgeExplorer.setBaseNode(currEdge.adjNode);
+        while (iter.next()) {
+            if (!accept(iter, currEdge, reverse))
+                continue;
+
+            final double weight = calcWeight(iter, currEdge, reverse);
+            if (Double.isInfinite(weight)) {
+                continue;
+            }
+            final int origEdgeId = getOrigEdgeId(iter, reverse);
+            final int traversalId = getTraversalId(iter, origEdgeId, reverse);
+            SPTEntry entry = bestWeightMap.get(traversalId);
+            if (entry == null) {
+                entry = createEntry(iter, origEdgeId, weight, currEdge, reverse);
+                bestWeightMap.put(traversalId, entry);
+                prioQueue.add(entry);
+            } else if (entry.getWeightOfVisitedPath() > weight) {
+                prioQueue.remove(entry);
+                updateEntry(entry, iter, origEdgeId, weight, currEdge, reverse);
+                prioQueue.add(entry);
+            } else
+                continue;
+
+            if (updateBestPath) {
+                // only needed for edge-based -> skip the calculation and use dummy value otherwise
+                double edgeWeight = traversalMode.isEdgeBased() ? weighting.calcEdgeWeight(iter, reverse) : Double.POSITIVE_INFINITY;
+                // todo: performance - if bestWeightMapOther.get(traversalId) == null, updateBestPath will exit early and we might
+                // have calculated the edgeWeight unnecessarily
+                updateBestPath(edgeWeight, entry, origEdgeId, traversalId, reverse);
+            }
+        }
+    }
+
+    protected void updateEntry(SPTEntry entry, EdgeIteratorState edge, int edgeId, double weight, SPTEntry parent, boolean reverse) {
+        entry.edge = edge.getEdge();
+        entry.weight = weight;
+        entry.parent = parent;
+    }
+
+    protected boolean accept(EdgeIteratorState edge, SPTEntry currEdge, boolean reverse) {
+        return accept(edge, getIncomingEdge(currEdge));
+    }
+
+    protected int getOrigEdgeId(EdgeIteratorState edge, boolean reverse) {
+        return edge.getEdge();
+    }
+
+    protected int getTraversalId(EdgeIteratorState edge, int origEdgeId, boolean reverse) {
+        return traversalMode.createTraversalId(edge, reverse);
+    }
+
+    protected double calcWeight(EdgeIteratorState iter, SPTEntry currEdge, boolean reverse) {
+        // todo: for #1776/#1835 move access flag checks into weighting
+        final boolean access = reverse ? inEdgeFilter.accept(iter) : outEdgeFilter.accept(iter);
+        if (!access) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return weighting.calcWeight(iter, reverse, getIncomingEdge(currEdge)) + currEdge.getWeightOfVisitedPath();
+    }
+
+    @Override
+    protected double getInEdgeWeight(SPTEntry entry) {
+        return weighting.calcEdgeWeight(graph.getEdgeIteratorState(getIncomingEdge(entry), entry.adjNode), false);
+    }
+
+    @Override
+    protected int getOtherNode(int edge, int node) {
+        return graph.getOtherNode(edge, node);
+    }
+
+    @Override
+    protected Path extractPath() {
+        if (finished())
+            return createPathExtractor(graph, weighting).extract(bestFwdEntry, bestBwdEntry, bestWeight);
+
+        return createEmptyPath();
+    }
+
+    protected boolean accept(EdgeIteratorState iter, int prevOrNextEdgeId) {
+        // for edge-based traversal we leave it for TurnWeighting to decide whether or not a u-turn is acceptable,
+        // but for node-based traversal we exclude such a turn for performance reasons already here
+        if (!traversalMode.isEdgeBased() && iter.getEdge() == prevOrNextEdgeId)
+            return false;
+
+        return additionalEdgeFilter == null || additionalEdgeFilter.accept(iter);
+    }
+
+    protected Path createEmptyPath() {
+        return new Path(graph);
+    }
+
+    @Override
+    public String toString() {
+        return getName() + "|" + weighting;
+    }
+
+}
diff --git a/core/src/main/java/com/graphhopper/routing/AbstractRoutingAlgorithm.java b/core/src/main/java/com/graphhopper/routing/AbstractRoutingAlgorithm.java
index 7e68d62ef..1080b1b5a 100644
--- a/core/src/main/java/com/graphhopper/routing/AbstractRoutingAlgorithm.java
+++ b/core/src/main/java/com/graphhopper/routing/AbstractRoutingAlgorithm.java
@@ -39,10 +39,10 @@ public abstract class AbstractRoutingAlgorithm implements RoutingAlgorithm {
     protected final FlagEncoder flagEncoder;
     protected final TraversalMode traversalMode;
     protected NodeAccess nodeAccess;
-    protected EdgeExplorer inEdgeExplorer;
-    protected EdgeExplorer outEdgeExplorer;
+    protected EdgeExplorer edgeExplorer;
+    protected EdgeFilter inEdgeFilter;
+    protected EdgeFilter outEdgeFilter;
     protected int maxVisitedNodes = Integer.MAX_VALUE;
-    protected EdgeFilter additionalEdgeFilter;
     private boolean alreadyRun;
 
     /**
@@ -56,8 +56,9 @@ public abstract class AbstractRoutingAlgorithm implements RoutingAlgorithm {
         this.traversalMode = traversalMode;
         this.graph = graph;
         this.nodeAccess = graph.getNodeAccess();
-        outEdgeExplorer = graph.createEdgeExplorer(DefaultEdgeFilter.outEdges(flagEncoder));
-        inEdgeExplorer = graph.createEdgeExplorer(DefaultEdgeFilter.inEdges(flagEncoder));
+        inEdgeFilter = DefaultEdgeFilter.inEdges(flagEncoder.getAccessEnc());
+        outEdgeFilter = DefaultEdgeFilter.outEdges(flagEncoder.getAccessEnc());
+        edgeExplorer = graph.createEdgeExplorer();
     }
 
     @Override
@@ -65,18 +66,10 @@ public abstract class AbstractRoutingAlgorithm implements RoutingAlgorithm {
         this.maxVisitedNodes = numberOfNodes;
     }
 
-    public RoutingAlgorithm setEdgeFilter(EdgeFilter additionalEdgeFilter) {
-        this.additionalEdgeFilter = additionalEdgeFilter;
-        return this;
-    }
-
     protected boolean accept(EdgeIteratorState iter, int prevOrNextEdgeId) {
         // for edge-based traversal we leave it for TurnWeighting to decide whether or not a u-turn is acceptable,
         // but for node-based traversal we exclude such a turn for performance reasons already here
-        if (!traversalMode.isEdgeBased() && iter.getEdge() == prevOrNextEdgeId)
-            return false;
-
-        return additionalEdgeFilter == null || additionalEdgeFilter.accept(iter);
+        return traversalMode.isEdgeBased() || iter.getEdge() != prevOrNextEdgeId;
     }
 
     protected void checkAlreadyRun() {
diff --git a/core/src/main/java/com/graphhopper/routing/AlternativeRouteCH.java b/core/src/main/java/com/graphhopper/routing/AlternativeRouteCH.java
index 2f29146d5..e6bcef933 100644
--- a/core/src/main/java/com/graphhopper/routing/AlternativeRouteCH.java
+++ b/core/src/main/java/com/graphhopper/routing/AlternativeRouteCH.java
@@ -20,9 +20,8 @@ package com.graphhopper.routing;
 
 import com.carrotsearch.hppc.IntIndexedContainer;
 import com.carrotsearch.hppc.predicates.IntObjectPredicate;
-import com.graphhopper.routing.ch.CHWeighting;
-import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.Graph;
+import com.graphhopper.storage.RoutingCHGraph;
 import com.graphhopper.storage.SPTEntry;
 import com.graphhopper.util.EdgeIteratorState;
 
@@ -45,8 +44,8 @@ public class AlternativeRouteCH extends DijkstraBidirectionCHNoSOD {
     private double maxShareFactor = 0.6;
     public static final double T = 10000.0;
 
-    public AlternativeRouteCH(Graph graph, Weighting weighting) {
-        super(graph, weighting);
+    public AlternativeRouteCH(RoutingCHGraph graph) {
+        super(graph);
     }
 
     List<AlternativeInfo> calcAlternatives(final int s, final int t) {
@@ -78,7 +77,7 @@ public class AlternativeRouteCH extends DijkstraBidirectionCHNoSOD {
                 // s -> v and v -> t need not be shortest paths. In fact, they can sometimes be pretty strange.
                 // We still use this preliminary path to filter for shared path length with other alternatives,
                 // so we don't have to work so much.
-                Path preliminaryRoute = createPathExtractor(graph, weighting).extract(fromSPTEntry, toSPTEntry, fromSPTEntry.getWeightOfVisitedPath() + toSPTEntry.getWeightOfVisitedPath());
+                Path preliminaryRoute = createPathExtractor(graph).extract(fromSPTEntry, toSPTEntry, fromSPTEntry.getWeightOfVisitedPath() + toSPTEntry.getWeightOfVisitedPath());
                 double preliminaryShare = calculateShare(preliminaryRoute);
                 if (preliminaryShare > maxShareFactor) {
                     return true;
@@ -86,13 +85,11 @@ public class AlternativeRouteCH extends DijkstraBidirectionCHNoSOD {
 
                 // Okay, now we want the s -> v -> t shortest via-path, so we route s -> v and v -> t
                 // and glue them together.
-                DijkstraBidirectionCHNoSOD svRouter = new DijkstraBidirectionCHNoSOD(graph, weighting);
-                svRouter.setEdgeFilter(additionalEdgeFilter);
+                DijkstraBidirectionCHNoSOD svRouter = new DijkstraBidirectionCHNoSOD(graph);
                 final Path svPath = svRouter.calcPath(s, v);
-                DijkstraBidirectionCHNoSOD vtRouter = new DijkstraBidirectionCHNoSOD(graph, weighting);
-                vtRouter.setEdgeFilter(additionalEdgeFilter);
+                DijkstraBidirectionCHNoSOD vtRouter = new DijkstraBidirectionCHNoSOD(graph);
                 final Path vtPath = vtRouter.calcPath(v, t);
-                Path path = concat(graph.getBaseGraph(), svPath, vtPath);
+                Path path = concat(graph.getGraph().getBaseGraph(), svPath, vtPath);
 
                 // And calculate the share again, because this can be totally different.
                 // The first filter is a good heuristic, but we still need this one.
@@ -137,8 +134,7 @@ public class AlternativeRouteCH extends DijkstraBidirectionCHNoSOD {
                 if (path.getEdgeCount() == 0) return true;
                 int fromNode = getPreviousNodeTMetersAway(path, vIndex);
                 int toNode = getNextNodeTMetersAway(path, vIndex);
-                DijkstraBidirectionCHNoSOD tRouter = new DijkstraBidirectionCHNoSOD(graph, new CHWeighting(weighting));
-                tRouter.setEdgeFilter(additionalEdgeFilter);
+                DijkstraBidirectionCHNoSOD tRouter = new DijkstraBidirectionCHNoSOD(graph);
                 Path tPath = tRouter.calcPath(fromNode, toNode);
                 IntIndexedContainer tNodes = tPath.calcNodes();
                 return tNodes.contains(path.calcNodes().get(vIndex));
diff --git a/core/src/main/java/com/graphhopper/routing/Dijkstra.java b/core/src/main/java/com/graphhopper/routing/Dijkstra.java
index 6ad277fed..2587d5170 100644
--- a/core/src/main/java/com/graphhopper/routing/Dijkstra.java
+++ b/core/src/main/java/com/graphhopper/routing/Dijkstra.java
@@ -23,7 +23,6 @@ import com.graphhopper.routing.util.TraversalMode;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.SPTEntry;
-import com.graphhopper.util.EdgeExplorer;
 import com.graphhopper.util.EdgeIterator;
 import com.graphhopper.util.EdgeIteratorState;
 import com.graphhopper.util.Parameters;
@@ -68,19 +67,19 @@ public class Dijkstra extends AbstractRoutingAlgorithm {
     }
 
     protected void runAlgo() {
-        EdgeExplorer explorer = outEdgeExplorer;
         while (true) {
             visitedNodes++;
             if (isMaxVisitedNodesExceeded() || finished())
                 break;
 
             int currNode = currEdge.adjNode;
-            EdgeIterator iter = explorer.setBaseNode(currNode);
+            EdgeIterator iter = edgeExplorer.setBaseNode(currNode);
             while (iter.next()) {
                 if (!accept(iter, currEdge.edge))
                     continue;
 
-                double tmpWeight = weighting.calcWeight(iter, false, currEdge.edge) + currEdge.weight;
+                // todo: for #1776/#1835 move the access check into weighting
+                double tmpWeight = !outEdgeFilter.accept(iter) ? Double.POSITIVE_INFINITY : (weighting.calcWeight(iter, false, currEdge.edge) + currEdge.weight);
                 if (Double.isInfinite(tmpWeight)) {
                     continue;
                 }
diff --git a/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionCH.java b/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionCH.java
index 317b68eee..7cd14e9d8 100644
--- a/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionCH.java
+++ b/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionCH.java
@@ -18,11 +18,10 @@
 package com.graphhopper.routing;
 
 import com.carrotsearch.hppc.IntObjectMap;
-import com.graphhopper.routing.weighting.Weighting;
-import com.graphhopper.storage.Graph;
+import com.graphhopper.storage.RoutingCHEdgeExplorer;
+import com.graphhopper.storage.RoutingCHEdgeIterator;
+import com.graphhopper.storage.RoutingCHGraph;
 import com.graphhopper.storage.SPTEntry;
-import com.graphhopper.util.EdgeExplorer;
-import com.graphhopper.util.EdgeIterator;
 
 /**
  * Uses a very simple version of stall-on-demand (SOD) for CH queries to prevent exploring nodes that can not be part
@@ -34,8 +33,8 @@ import com.graphhopper.util.EdgeIterator;
  */
 public class DijkstraBidirectionCH extends DijkstraBidirectionCHNoSOD {
 
-    public DijkstraBidirectionCH(Graph graph, Weighting weighting) {
-        super(graph, weighting);
+    public DijkstraBidirectionCH(RoutingCHGraph graph) {
+        super(graph);
     }
 
     @Override
@@ -48,35 +47,31 @@ public class DijkstraBidirectionCH extends DijkstraBidirectionCHNoSOD {
         return entryIsStallable(currTo, bestWeightMapTo, outEdgeExplorer, true);
     }
 
-    @Override
-    public String getName() {
-        return "dijkstrabi|ch";
-    }
-
-    @Override
-    public String toString() {
-        return getName() + "|" + weighting;
-    }
-
-    private boolean entryIsStallable(SPTEntry entry, IntObjectMap<SPTEntry> bestWeightMap, EdgeExplorer edgeExplorer,
+    private boolean entryIsStallable(SPTEntry entry, IntObjectMap<SPTEntry> bestWeightMap, RoutingCHEdgeExplorer edgeExplorer,
                                      boolean reverse) {
         // We check for all 'incoming' edges if we can prove that the current node (that is about to be settled) is 
         // reached via a suboptimal path. We do this regardless of the CH level of the adjacent nodes.
-        EdgeIterator iter = edgeExplorer.setBaseNode(entry.adjNode);
+        RoutingCHEdgeIterator iter = edgeExplorer.setBaseNode(entry.adjNode);
         while (iter.next()) {
             // no need to inspect the edge we are coming from
             if (iter.getEdge() == entry.edge) {
                 continue;
             }
-            int traversalId = traversalMode.createTraversalId(iter, reverse);
+            int traversalId = getTraversalId(iter, reverse);
             SPTEntry adjNode = bestWeightMap.get(traversalId);
             // we have to be careful because of rounded shortcut weights in combination with virtual via nodes, see #1574
             final double precision = 0.001;
             if (adjNode != null &&
-                    adjNode.weight + weighting.calcEdgeWeight(iter, !reverse) - entry.weight < -precision) {
+                    adjNode.weight + calcWeight(iter, !reverse, getIncomingEdge(entry)) - entry.weight < -precision) {
                 return true;
             }
         }
         return false;
     }
+
+    @Override
+    public String getName() {
+        return "dijkstrabi|ch";
+    }
+
 }
diff --git a/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionCHNoSOD.java b/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionCHNoSOD.java
index 60684553e..ec52b2901 100644
--- a/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionCHNoSOD.java
+++ b/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionCHNoSOD.java
@@ -17,34 +17,30 @@
  */
 package com.graphhopper.routing;
 
-import com.graphhopper.routing.ch.NodeBasedCHBidirPathExtractor;
 import com.graphhopper.routing.util.TraversalMode;
-import com.graphhopper.routing.weighting.Weighting;
-import com.graphhopper.storage.Graph;
+import com.graphhopper.storage.RoutingCHEdgeIteratorState;
+import com.graphhopper.storage.RoutingCHGraph;
+import com.graphhopper.storage.SPTEntry;
 
-public class DijkstraBidirectionCHNoSOD extends DijkstraBidirectionRef {
-    public DijkstraBidirectionCHNoSOD(Graph graph, Weighting weighting) {
-        super(graph, weighting, TraversalMode.NODE_BASED);
+public class DijkstraBidirectionCHNoSOD extends AbstractBidirCHAlgo {
+    public DijkstraBidirectionCHNoSOD(RoutingCHGraph graph) {
+        super(graph, TraversalMode.NODE_BASED);
     }
 
     @Override
-    protected void initCollections(int size) {
-        super.initCollections(Math.min(size, 2000));
+    protected SPTEntry createStartEntry(int node, double weight, boolean reverse) {
+        return new SPTEntry(node, weight);
     }
 
     @Override
-    public boolean finished() {
-        // we need to finish BOTH searches for CH!
-        if (finishedFrom && finishedTo)
-            return true;
-
-        // changed also the final finish condition for CH
-        return currFrom.weight >= bestWeight && currTo.weight >= bestWeight;
+    protected SPTEntry createEntry(RoutingCHEdgeIteratorState edge, int incEdge, double weight, SPTEntry parent, boolean reverse) {
+        SPTEntry entry = new SPTEntry(edge.getEdge(), edge.getAdjNode(), weight);
+        entry.parent = parent;
+        return entry;
     }
 
-    @Override
-    protected BidirPathExtractor createPathExtractor(Graph graph, Weighting weighting) {
-        return new NodeBasedCHBidirPathExtractor(graph, graph.getBaseGraph(), weighting);
+    protected SPTEntry getParent(SPTEntry entry) {
+        return entry.getParent();
     }
 
     @Override
@@ -52,8 +48,4 @@ public class DijkstraBidirectionCHNoSOD extends DijkstraBidirectionRef {
         return "dijkstrabi|ch|no_sod";
     }
 
-    @Override
-    public String toString() {
-        return getName() + "|" + weighting;
-    }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionEdgeCHNoSOD.java b/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionEdgeCHNoSOD.java
index 48c705571..a9bd26c0f 100644
--- a/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionEdgeCHNoSOD.java
+++ b/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionEdgeCHNoSOD.java
@@ -18,17 +18,16 @@
 package com.graphhopper.routing;
 
 import com.graphhopper.routing.ch.CHEntry;
-import com.graphhopper.routing.weighting.TurnWeighting;
-import com.graphhopper.storage.Graph;
+import com.graphhopper.storage.RoutingCHEdgeIteratorState;
+import com.graphhopper.storage.RoutingCHGraph;
 import com.graphhopper.storage.SPTEntry;
-import com.graphhopper.util.EdgeIteratorState;
 
 /**
  * @author easbar
  */
 public class DijkstraBidirectionEdgeCHNoSOD extends AbstractBidirectionEdgeCHNoSOD {
-    public DijkstraBidirectionEdgeCHNoSOD(Graph graph, TurnWeighting weighting) {
-        super(graph, weighting);
+    public DijkstraBidirectionEdgeCHNoSOD(RoutingCHGraph graph) {
+        super(graph);
     }
 
     @Override
@@ -37,14 +36,14 @@ public class DijkstraBidirectionEdgeCHNoSOD extends AbstractBidirectionEdgeCHNoS
     }
 
     @Override
-    protected CHEntry createEntry(EdgeIteratorState edge, int incEdge, double weight, SPTEntry parent, boolean reverse) {
+    protected CHEntry createEntry(RoutingCHEdgeIteratorState edge, int incEdge, double weight, SPTEntry parent, boolean reverse) {
         CHEntry entry = new CHEntry(edge.getEdge(), incEdge, edge.getAdjNode(), weight);
         entry.parent = parent;
         return entry;
     }
 
     @Override
-    protected void updateEntry(SPTEntry entry, EdgeIteratorState edge, int edgeId, double weight, SPTEntry parent, boolean reverse) {
+    protected void updateEntry(SPTEntry entry, RoutingCHEdgeIteratorState edge, int edgeId, double weight, SPTEntry parent, boolean reverse) {
         entry.edge = edge.getEdge();
         ((CHEntry) entry).incEdge = edgeId;
         entry.weight = weight;
diff --git a/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionRef.java b/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionRef.java
index 11af29cab..44a238eef 100644
--- a/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionRef.java
+++ b/core/src/main/java/com/graphhopper/routing/DijkstraBidirectionRef.java
@@ -21,7 +21,8 @@ import com.graphhopper.routing.util.TraversalMode;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.SPTEntry;
-import com.graphhopper.util.*;
+import com.graphhopper.util.EdgeIteratorState;
+import com.graphhopper.util.Parameters;
 
 /**
  * Calculates best path in bidirectional way.
@@ -31,7 +32,7 @@ import com.graphhopper.util.*;
  *
  * @author Peter Karich
  */
-public class DijkstraBidirectionRef extends AbstractBidirAlgo {
+public class DijkstraBidirectionRef extends AbstractNonCHBidirAlgo {
     public DijkstraBidirectionRef(Graph graph, Weighting weighting, TraversalMode tMode) {
         super(graph, weighting, tMode);
     }
diff --git a/core/src/main/java/com/graphhopper/routing/DijkstraOneToMany.java b/core/src/main/java/com/graphhopper/routing/DijkstraOneToMany.java
index a3297803d..7e3bb2342 100644
--- a/core/src/main/java/com/graphhopper/routing/DijkstraOneToMany.java
+++ b/core/src/main/java/com/graphhopper/routing/DijkstraOneToMany.java
@@ -167,14 +167,14 @@ public class DijkstraOneToMany extends AbstractRoutingAlgorithm {
 
         while (true) {
             visitedNodes++;
-            EdgeIterator iter = outEdgeExplorer.setBaseNode(currNode);
+            EdgeIterator iter = edgeExplorer.setBaseNode(currNode);
             while (iter.next()) {
                 int adjNode = iter.getAdjNode();
                 int prevEdgeId = edgeIds[adjNode];
                 if (!accept(iter, prevEdgeId))
                     continue;
 
-                double tmpWeight = weighting.calcWeight(iter, false, prevEdgeId) + weights[currNode];
+                double tmpWeight = !outEdgeFilter.accept(iter) ? Double.POSITIVE_INFINITY : (weighting.calcWeight(iter, false, prevEdgeId) + weights[currNode]);
                 if (Double.isInfinite(tmpWeight))
                     continue;
 
diff --git a/core/src/main/java/com/graphhopper/routing/ch/CHRoutingAlgorithmFactory.java b/core/src/main/java/com/graphhopper/routing/ch/CHRoutingAlgorithmFactory.java
index 23372e451..b10b3899f 100644
--- a/core/src/main/java/com/graphhopper/routing/ch/CHRoutingAlgorithmFactory.java
+++ b/core/src/main/java/com/graphhopper/routing/ch/CHRoutingAlgorithmFactory.java
@@ -19,71 +19,72 @@
 package com.graphhopper.routing.ch;
 
 import com.graphhopper.routing.*;
-import com.graphhopper.routing.util.LevelEdgeFilter;
 import com.graphhopper.routing.weighting.TurnWeighting;
 import com.graphhopper.routing.weighting.Weighting;
-import com.graphhopper.storage.CHGraph;
-import com.graphhopper.storage.CHProfile;
-import com.graphhopper.storage.Graph;
-import com.graphhopper.storage.TurnCostStorage;
+import com.graphhopper.storage.*;
 
 import static com.graphhopper.util.Parameters.Algorithms.*;
 import static com.graphhopper.util.Parameters.Algorithms.AltRoute.MAX_SHARE;
 import static com.graphhopper.util.Parameters.Algorithms.AltRoute.MAX_WEIGHT;
 
 public class CHRoutingAlgorithmFactory implements RoutingAlgorithmFactory {
-    private final CHGraph chGraph;
     private final CHProfile chProfile;
 
     public CHRoutingAlgorithmFactory(CHGraph chGraph) {
-        this.chGraph = chGraph;
         this.chProfile = chGraph.getCHProfile();
     }
 
     @Override
     public RoutingAlgorithm createAlgo(Graph graph, AlgorithmOptions opts) {
-        // todo: This method does not really fit for CH: We are passed a graph, but really we already know which
+        // todo: This method does not really fit for CH: We get a graph, but really we already know which
         // graph we have to use: the CH graph. Same with  opts.weighting: The CHProfile already contains a weighting
         // and we cannot really use it here. The real reason we do this the way its done atm is that graph might be
         // a QueryGraph that wraps (our) CHGraph.
-        AbstractBidirAlgo algo = doCreateAlgo(graph, opts);
-        algo.setEdgeFilter(new LevelEdgeFilter(chGraph));
+        RoutingAlgorithm algo = doCreateAlgo(graph, opts);
         algo.setMaxVisitedNodes(opts.getMaxVisitedNodes());
         return algo;
     }
 
-    private AbstractBidirAlgo doCreateAlgo(Graph graph, AlgorithmOptions opts) {
+    private RoutingAlgorithm doCreateAlgo(Graph graph, AlgorithmOptions opts) {
         if (chProfile.isEdgeBased()) {
-            return createAlgoEdgeBased(graph, opts);
+            // important: do not simply take the turn cost storage from ghStorage, because we need the wrapped storage from
+            // query graph!
+            TurnCostStorage turnCostStorage = graph.getTurnCostStorage();
+            if (turnCostStorage == null) {
+                throw new IllegalArgumentException("For edge-based CH you need a turn cost extension");
+            }
+            TurnWeighting turnWeighting = new TurnWeighting(getWeighting(), turnCostStorage, chProfile.getUTurnCosts());
+            RoutingCHGraph g = new RoutingCHGraphImpl(graph, getWeighting(), turnWeighting);
+            return createAlgoEdgeBased(g, opts);
         } else {
-            return createAlgoNodeBased(graph, opts);
+            RoutingCHGraph g = new RoutingCHGraphImpl(graph, chProfile.getWeighting());
+            return createAlgoNodeBased(g, opts);
         }
     }
 
-    private AbstractBidirAlgo createAlgoEdgeBased(Graph graph, AlgorithmOptions opts) {
+    private RoutingAlgorithm createAlgoEdgeBased(RoutingCHGraph g, AlgorithmOptions opts) {
         if (ASTAR_BI.equals(opts.getAlgorithm())) {
-            return new AStarBidirectionEdgeCHNoSOD(graph, createTurnWeightingForEdgeBased(graph))
-                    .setApproximation(RoutingAlgorithmFactorySimple.getApproximation(ASTAR_BI, opts, graph.getNodeAccess()));
+            return new AStarBidirectionEdgeCHNoSOD(g)
+                    .setApproximation(RoutingAlgorithmFactorySimple.getApproximation(ASTAR_BI, opts, g.getGraph().getNodeAccess()));
         } else if (DIJKSTRA_BI.equals(opts.getAlgorithm())) {
-            return new DijkstraBidirectionEdgeCHNoSOD(graph, createTurnWeightingForEdgeBased(graph));
+            return new DijkstraBidirectionEdgeCHNoSOD(g);
         } else {
             throw new IllegalArgumentException("Algorithm " + opts.getAlgorithm() + " not supported for edge-based Contraction Hierarchies. Try with ch.disable=true");
         }
     }
 
-    private AbstractBidirAlgo createAlgoNodeBased(Graph graph, AlgorithmOptions opts) {
-        CHWeighting chWeighting = new CHWeighting(chProfile.getWeighting());
+    private RoutingAlgorithm createAlgoNodeBased(RoutingCHGraph g, AlgorithmOptions opts) {
         if (ASTAR_BI.equals(opts.getAlgorithm())) {
-            return new AStarBidirectionCH(graph, chWeighting)
-                    .setApproximation(RoutingAlgorithmFactorySimple.getApproximation(ASTAR_BI, opts, graph.getNodeAccess()));
+            return new AStarBidirectionCH(g)
+                    .setApproximation(RoutingAlgorithmFactorySimple.getApproximation(ASTAR_BI, opts, g.getGraph().getNodeAccess()));
         } else if (DIJKSTRA_BI.equals(opts.getAlgorithm())) {
             if (opts.getHints().getBool("stall_on_demand", true)) {
-                return new DijkstraBidirectionCH(graph, chWeighting);
+                return new DijkstraBidirectionCH(g);
             } else {
-                return new DijkstraBidirectionCHNoSOD(graph, chWeighting);
+                return new DijkstraBidirectionCHNoSOD(g);
             }
         } else if (ALT_ROUTE.equalsIgnoreCase(opts.getAlgorithm())) {
-            AlternativeRouteCH altRouteAlgo = new AlternativeRouteCH(graph, chWeighting);
+            AlternativeRouteCH altRouteAlgo = new AlternativeRouteCH(g);
             altRouteAlgo.setMaxWeightFactor(opts.getHints().getDouble(MAX_WEIGHT, 1.4));
             altRouteAlgo.setMaxShareFactor(opts.getHints().getDouble(MAX_SHARE, 0.6));
             return altRouteAlgo;
@@ -92,17 +93,6 @@ public class CHRoutingAlgorithmFactory implements RoutingAlgorithmFactory {
         }
     }
 
-    private TurnWeighting createTurnWeightingForEdgeBased(Graph graph) {
-        // important: do not simply take the turn cost storage from ghStorage, because we need the wrapped storage from
-        // query graph!
-        TurnCostStorage turnCostStorage = graph.getTurnCostStorage();
-        if (turnCostStorage == null) {
-            throw new IllegalArgumentException("For edge-based CH you need a turn cost storage");
-        }
-        CHWeighting chWeighting = new CHWeighting(chProfile.getWeighting());
-        return new TurnWeighting(chWeighting, turnCostStorage, chProfile.getUTurnCosts());
-    }
-
     public Weighting getWeighting() {
         return chProfile.getWeighting();
     }
diff --git a/core/src/main/java/com/graphhopper/routing/ch/CHWeighting.java b/core/src/main/java/com/graphhopper/routing/ch/CHWeighting.java
deleted file mode 100644
index 3aa69b212..000000000
--- a/core/src/main/java/com/graphhopper/routing/ch/CHWeighting.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- *  Licensed to GraphHopper GmbH under one or more contributor
- *  license agreements. See the NOTICE file distributed with this work for
- *  additional information regarding copyright ownership.
- *
- *  GraphHopper GmbH licenses this file to you under the Apache License,
- *  Version 2.0 (the "License"); you may not use this file except in
- *  compliance with the License. You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-package com.graphhopper.routing.ch;
-
-import com.graphhopper.routing.util.FlagEncoder;
-import com.graphhopper.routing.util.HintsMap;
-import com.graphhopper.routing.weighting.Weighting;
-import com.graphhopper.util.CHEdgeIteratorState;
-import com.graphhopper.util.EdgeIteratorState;
-
-import static com.graphhopper.util.EdgeIterator.NO_EDGE;
-
-/**
- * Used by CH algorithms and therefore assumed that all edges are of type CHEdgeIteratorState
- * <p>
- *
- * @author Peter Karich
- * @see CHRoutingAlgorithmFactory
- */
-public class CHWeighting implements Weighting {
-    private final Weighting userWeighting;
-
-    public CHWeighting(Weighting userWeighting) {
-        this.userWeighting = userWeighting;
-    }
-
-    @Override
-    public final double getMinWeight(double distance) {
-        return userWeighting.getMinWeight(distance);
-    }
-
-    @Override
-    public final double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse) {
-        return calcWeight(edgeState, reverse, NO_EDGE);
-    }
-
-    @Override
-    public double calcWeight(EdgeIteratorState edgeState, boolean reverse, int prevOrNextEdgeId) {
-        CHEdgeIteratorState tmp = (CHEdgeIteratorState) edgeState;
-        if (tmp.isShortcut())
-            // if a shortcut is in both directions the weight is identical => no need for 'reverse'
-            return tmp.getWeight();
-
-        return userWeighting.calcWeight(edgeState, reverse, prevOrNextEdgeId);
-    }
-
-    @Override
-    public final long calcEdgeMillis(EdgeIteratorState edgeState, boolean reverse) {
-        return calcMillis(edgeState, reverse, NO_EDGE);
-    }
-
-    @Override
-    public long calcMillis(EdgeIteratorState edgeState, boolean reverse, int prevOrNextEdgeId) {
-        if (edgeState instanceof CHEdgeIteratorState && ((CHEdgeIteratorState) edgeState).isShortcut()) {
-            throw new IllegalStateException("calcMillis should only be called on original edges");
-        }
-        return userWeighting.calcMillis(edgeState, reverse, prevOrNextEdgeId);
-    }
-
-    @Override
-    public FlagEncoder getFlagEncoder() {
-        return userWeighting.getFlagEncoder();
-    }
-
-    @Override
-    public boolean matches(HintsMap map) {
-        return getName().equals(map.getWeighting()) && userWeighting.getFlagEncoder().toString().equals(map.getVehicle());
-    }
-
-    @Override
-    public String getName() {
-        return "prepare|" + userWeighting.getName();
-    }
-
-    @Override
-    public String toString() {
-        return getName();
-    }
-}
diff --git a/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedCHBidirPathExtractor.java b/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedCHBidirPathExtractor.java
index 2711b3d2f..b3dc09116 100644
--- a/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedCHBidirPathExtractor.java
+++ b/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedCHBidirPathExtractor.java
@@ -20,8 +20,7 @@ package com.graphhopper.routing.ch;
 
 import com.graphhopper.routing.BidirPathExtractor;
 import com.graphhopper.routing.weighting.TurnWeighting;
-import com.graphhopper.routing.weighting.Weighting;
-import com.graphhopper.storage.Graph;
+import com.graphhopper.storage.RoutingCHGraph;
 import com.graphhopper.storage.SPTEntry;
 import com.graphhopper.storage.ShortcutUnpacker;
 import com.graphhopper.util.EdgeIteratorState;
@@ -30,12 +29,14 @@ import com.graphhopper.util.EdgeIteratorState;
  * @author easbar
  */
 public class EdgeBasedCHBidirPathExtractor extends BidirPathExtractor {
+    private final RoutingCHGraph routingGraph;
     private final ShortcutUnpacker shortcutUnpacker;
 
-    public EdgeBasedCHBidirPathExtractor(Graph routingGraph, Graph baseGraph, Weighting weighting) {
-        super(baseGraph, weighting);
-        shortcutUnpacker = createShortcutUnpacker(routingGraph, weighting);
-        if (!(weighting instanceof TurnWeighting)) {
+    public EdgeBasedCHBidirPathExtractor(RoutingCHGraph routingGraph) {
+        super(routingGraph.getGraph(), routingGraph.getWeighting());
+        this.routingGraph = routingGraph;
+        shortcutUnpacker = createShortcutUnpacker();
+        if (!(routingGraph.getWeighting() instanceof TurnWeighting)) {
             throw new IllegalArgumentException("Need a TurnWeighting for edge-based CH");
         }
     }
@@ -49,12 +50,12 @@ public class EdgeBasedCHBidirPathExtractor extends BidirPathExtractor {
         }
     }
 
-    private ShortcutUnpacker createShortcutUnpacker(Graph routingGraph, final Weighting weighting) {
+    private ShortcutUnpacker createShortcutUnpacker() {
         return new ShortcutUnpacker(routingGraph, new ShortcutUnpacker.Visitor() {
             @Override
             public void visit(EdgeIteratorState edge, boolean reverse, int prevOrNextEdgeId) {
                 path.addDistance(edge.getDistance());
-                path.addTime(weighting.calcMillis(edge, reverse, prevOrNextEdgeId));
+                path.addTime(routingGraph.getWeighting().calcMillis(edge, reverse, prevOrNextEdgeId));
                 path.addEdge(edge.getEdge());
             }
         }, true);
diff --git a/core/src/main/java/com/graphhopper/routing/ch/NodeBasedCHBidirPathExtractor.java b/core/src/main/java/com/graphhopper/routing/ch/NodeBasedCHBidirPathExtractor.java
index ddf2f9669..c01fdda57 100644
--- a/core/src/main/java/com/graphhopper/routing/ch/NodeBasedCHBidirPathExtractor.java
+++ b/core/src/main/java/com/graphhopper/routing/ch/NodeBasedCHBidirPathExtractor.java
@@ -19,17 +19,18 @@
 package com.graphhopper.routing.ch;
 
 import com.graphhopper.routing.BidirPathExtractor;
-import com.graphhopper.routing.weighting.Weighting;
-import com.graphhopper.storage.Graph;
+import com.graphhopper.storage.RoutingCHGraph;
 import com.graphhopper.storage.ShortcutUnpacker;
 import com.graphhopper.util.EdgeIteratorState;
 
 public class NodeBasedCHBidirPathExtractor extends BidirPathExtractor {
     private final ShortcutUnpacker shortcutUnpacker;
+    private final RoutingCHGraph routingGraph;
 
-    public NodeBasedCHBidirPathExtractor(Graph routingGraph, Graph baseGraph, Weighting weighting) {
-        super(baseGraph, weighting);
-        shortcutUnpacker = createShortcutUnpacker(routingGraph, weighting);
+    public NodeBasedCHBidirPathExtractor(RoutingCHGraph routingGraph) {
+        super(routingGraph.getGraph(), routingGraph.getWeighting());
+        this.routingGraph = routingGraph;
+        shortcutUnpacker = createShortcutUnpacker();
     }
 
     @Override
@@ -41,12 +42,12 @@ public class NodeBasedCHBidirPathExtractor extends BidirPathExtractor {
         }
     }
 
-    private ShortcutUnpacker createShortcutUnpacker(Graph routingGraph, final Weighting weighting) {
+    private ShortcutUnpacker createShortcutUnpacker() {
         return new ShortcutUnpacker(routingGraph, new ShortcutUnpacker.Visitor() {
             @Override
             public void visit(EdgeIteratorState edge, boolean reverse, int prevOrNextEdgeId) {
                 path.addDistance(edge.getDistance());
-                path.addTime(weighting.calcEdgeMillis(edge, reverse));
+                path.addTime(routingGraph.getWeighting().calcEdgeMillis(edge, reverse));
                 path.addEdge(edge.getEdge());
             }
         }, false);
diff --git a/core/src/main/java/com/graphhopper/routing/ch/PrepareCHEdgeIteratorImpl.java b/core/src/main/java/com/graphhopper/routing/ch/PrepareCHEdgeIteratorImpl.java
index d693f6147..781ef6813 100644
--- a/core/src/main/java/com/graphhopper/routing/ch/PrepareCHEdgeIteratorImpl.java
+++ b/core/src/main/java/com/graphhopper/routing/ch/PrepareCHEdgeIteratorImpl.java
@@ -21,7 +21,6 @@ package com.graphhopper.routing.ch;
 import com.graphhopper.routing.profiles.BooleanEncodedValue;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.util.CHEdgeIterator;
-import com.graphhopper.util.CHEdgeIteratorState;
 import com.graphhopper.util.EdgeExplorer;
 import com.graphhopper.util.EdgeIterator;
 
@@ -188,33 +187,4 @@ public class PrepareCHEdgeIteratorImpl implements PrepareCHEdgeExplorer, Prepare
         assert chIterator != null : "You need to call setBaseNode() before using the iterator";
     }
 
-    public static class ShortcutFilter {
-        private final boolean fwd;
-        private final boolean bwd;
-
-        private ShortcutFilter(boolean fwd, boolean bwd) {
-            this.fwd = fwd;
-            this.bwd = bwd;
-        }
-
-        public static ShortcutFilter outEdges() {
-            return new ShortcutFilter(true, false);
-        }
-
-        public static ShortcutFilter inEdges() {
-            return new ShortcutFilter(false, true);
-        }
-
-        public static ShortcutFilter allEdges() {
-            return new ShortcutFilter(true, true);
-        }
-
-        public boolean accept(CHEdgeIteratorState edgeState) {
-            // c.f. comment in DefaultEdgeFilter
-            if (edgeState.getBaseNode() == edgeState.getAdjNode()) {
-                return edgeState.getFwdAccess() || edgeState.getBwdAccess();
-            }
-            return fwd && edgeState.getFwdAccess() || bwd && edgeState.getBwdAccess();
-        }
-    }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/ch/ShortcutFilter.java b/core/src/main/java/com/graphhopper/routing/ch/ShortcutFilter.java
new file mode 100644
index 000000000..65906f10f
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/ch/ShortcutFilter.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except in
+ *  compliance with the License. You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.graphhopper.routing.ch;
+
+import com.graphhopper.util.CHEdgeIteratorState;
+
+public class ShortcutFilter {
+    public final boolean fwd;
+    public final boolean bwd;
+
+    private ShortcutFilter(boolean fwd, boolean bwd) {
+        this.fwd = fwd;
+        this.bwd = bwd;
+    }
+
+    public static ShortcutFilter outEdges() {
+        return new ShortcutFilter(true, false);
+    }
+
+    public static ShortcutFilter inEdges() {
+        return new ShortcutFilter(false, true);
+    }
+
+    public static ShortcutFilter allEdges() {
+        return new ShortcutFilter(true, true);
+    }
+
+    public boolean accept(CHEdgeIteratorState edgeState) {
+        // c.f. comment in DefaultEdgeFilter
+        if (edgeState.getBaseNode() == edgeState.getAdjNode()) {
+            return edgeState.getFwdAccess() || edgeState.getBwdAccess();
+        }
+        return fwd && edgeState.getFwdAccess() || bwd && edgeState.getBwdAccess();
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/routing/lm/LandmarkStorage.java b/core/src/main/java/com/graphhopper/routing/lm/LandmarkStorage.java
index a0edec5d6..8a1f6843f 100644
--- a/core/src/main/java/com/graphhopper/routing/lm/LandmarkStorage.java
+++ b/core/src/main/java/com/graphhopper/routing/lm/LandmarkStorage.java
@@ -792,8 +792,8 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
 
         public void setFilter(IntHashSet set, boolean fwd, boolean bwd) {
             EdgeFilter ef = new BlockedEdgesFilter(flagEncoder.getAccessEnc(), fwd, bwd, set);
-            outEdgeExplorer = graph.createEdgeExplorer(ef);
-            inEdgeExplorer = graph.createEdgeExplorer(ef);
+            inEdgeFilter = ef;
+            outEdgeFilter = ef;
         }
 
         public void setStartNode(int startNode) {
diff --git a/core/src/main/java/com/graphhopper/routing/querygraph/QueryGraph.java b/core/src/main/java/com/graphhopper/routing/querygraph/QueryGraph.java
index a4bafe8a9..06cafc200 100644
--- a/core/src/main/java/com/graphhopper/routing/querygraph/QueryGraph.java
+++ b/core/src/main/java/com/graphhopper/routing/querygraph/QueryGraph.java
@@ -389,4 +389,8 @@ public class QueryGraph implements Graph {
     private UnsupportedOperationException exc() {
         return new UnsupportedOperationException("QueryGraph cannot be modified.");
     }
+
+    public Graph getMainGraph() {
+        return mainGraph;
+    }
 }
\ No newline at end of file
diff --git a/core/src/main/java/com/graphhopper/routing/template/ViaRoutingTemplate.java b/core/src/main/java/com/graphhopper/routing/template/ViaRoutingTemplate.java
index ff323cba6..d95a09590 100644
--- a/core/src/main/java/com/graphhopper/routing/template/ViaRoutingTemplate.java
+++ b/core/src/main/java/com/graphhopper/routing/template/ViaRoutingTemplate.java
@@ -136,7 +136,7 @@ public class ViaRoutingTemplate extends AbstractRoutingTemplate implements Routi
             List<Path> tmpPathList;
             if (!directions.isEmpty()) {
                 assert ghRequest.getCurbsides().size() == directions.size();
-                if (!(algo instanceof AbstractBidirAlgo)) {
+                if (!(algo instanceof BidirRoutingAlgorithm)) {
                     throw new IllegalArgumentException("To make use of the " + Routing.CURBSIDE + " parameter you need a bidirectional algorithm, got: " + algo.getName());
                 } else {
                     final String fromCurbside = ghRequest.getCurbsides().get(placeIndex - 1);
@@ -161,7 +161,7 @@ public class ViaRoutingTemplate extends AbstractRoutingTemplate implements Routi
                         }
                     }
                     // todo: enable curbside feature for alternative routes as well ?
-                    tmpPathList = Collections.singletonList(((AbstractBidirAlgo) algo)
+                    tmpPathList = Collections.singletonList(((BidirRoutingAlgorithm) algo)
                             .calcPath(fromQResult.getClosestNode(), toQResult.getClosestNode(), sourceOutEdge, targetInEdge));
                 }
             } else {
diff --git a/core/src/main/java/com/graphhopper/storage/CHEdgeFilter.java b/core/src/main/java/com/graphhopper/storage/CHEdgeFilter.java
new file mode 100644
index 000000000..7d6363273
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/storage/CHEdgeFilter.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except in
+ *  compliance with the License. You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.graphhopper.storage;
+
+public interface CHEdgeFilter {
+
+    CHEdgeFilter ALL_EDGES = new CHEdgeFilter() {
+        @Override
+        public final boolean accept(RoutingCHEdgeIteratorState edgeState) {
+            return true;
+        }
+    };
+
+    boolean accept(RoutingCHEdgeIteratorState edgeState);
+}
diff --git a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeExplorer.java b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeExplorer.java
new file mode 100644
index 000000000..fd2ad5ff8
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeExplorer.java
@@ -0,0 +1,23 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except in
+ *  compliance with the License. You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.graphhopper.storage;
+
+public interface RoutingCHEdgeExplorer {
+    RoutingCHEdgeIterator setBaseNode(int baseNode);
+}
diff --git a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIterator.java b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIterator.java
new file mode 100644
index 000000000..3b1f31e41
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIterator.java
@@ -0,0 +1,23 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except in
+ *  compliance with the License. You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.graphhopper.storage;
+
+public interface RoutingCHEdgeIterator extends RoutingCHEdgeIteratorState {
+    boolean next();
+}
diff --git a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorImpl.java b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorImpl.java
new file mode 100644
index 000000000..208cc113c
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorImpl.java
@@ -0,0 +1,90 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except in
+ *  compliance with the License. You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.graphhopper.storage;
+
+import com.graphhopper.routing.ch.ShortcutFilter;
+import com.graphhopper.routing.weighting.Weighting;
+import com.graphhopper.util.CHEdgeIteratorState;
+import com.graphhopper.util.EdgeExplorer;
+import com.graphhopper.util.EdgeIterator;
+import com.graphhopper.util.EdgeIteratorState;
+
+public class RoutingCHEdgeIteratorImpl extends RoutingCHEdgeIteratorStateImpl implements RoutingCHEdgeExplorer, RoutingCHEdgeIterator {
+    private final EdgeExplorer edgeExplorer;
+    private final ShortcutFilter shortcutFilter;
+    private EdgeIterator edgeIterator;
+
+    public static RoutingCHEdgeIteratorImpl inEdges(EdgeExplorer edgeExplorer, Weighting weighting) {
+        return new RoutingCHEdgeIteratorImpl(edgeExplorer, weighting, ShortcutFilter.inEdges());
+    }
+
+    public static RoutingCHEdgeIteratorImpl outEdges(EdgeExplorer edgeExplorer, Weighting weighting) {
+        return new RoutingCHEdgeIteratorImpl(edgeExplorer, weighting, ShortcutFilter.outEdges());
+    }
+
+    public static RoutingCHEdgeIteratorImpl allEdges(EdgeExplorer edgeExplorer, Weighting weighting) {
+        return new RoutingCHEdgeIteratorImpl(edgeExplorer, weighting, ShortcutFilter.allEdges());
+    }
+
+    public RoutingCHEdgeIteratorImpl(EdgeExplorer edgeExplorer, Weighting weighting, ShortcutFilter shortcutFilter) {
+        super(null, weighting);
+        this.edgeExplorer = edgeExplorer;
+        this.shortcutFilter = shortcutFilter;
+    }
+
+    @Override
+    EdgeIteratorState edgeState() {
+        return edgeIterator;
+    }
+
+    @Override
+    public RoutingCHEdgeIterator setBaseNode(int baseNode) {
+        edgeIterator = edgeExplorer.setBaseNode(baseNode);
+        return this;
+    }
+
+    @Override
+    public boolean next() {
+        while (true) {
+            boolean hasNext = edgeIterator.next();
+            if (!hasNext) {
+                return false;
+            } else if (hasAccess()) {
+                return true;
+            }
+        }
+    }
+
+    private boolean hasAccess() {
+        if (isShortcut()) {
+            return shortcutFilter.accept((CHEdgeIteratorState) edgeIterator);
+        } else {
+            // c.f. comment in DefaultEdgeFilter
+            if (edgeIterator.getBaseNode() == edgeIterator.getAdjNode()) {
+                return finiteWeight(false) || finiteWeight(true);
+            }
+            return shortcutFilter.fwd && finiteWeight(false) || shortcutFilter.bwd && finiteWeight(true);
+        }
+    }
+
+    private boolean finiteWeight(boolean reverse) {
+        return !Double.isInfinite(getOrigEdgeWeight(reverse, false));
+    }
+
+}
diff --git a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorState.java b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorState.java
new file mode 100644
index 000000000..5ab3929a0
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorState.java
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except in
+ *  compliance with the License. You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.graphhopper.storage;
+
+import com.graphhopper.util.EdgeIteratorState;
+
+public interface RoutingCHEdgeIteratorState {
+    EdgeIteratorState getBaseGraphEdgeState();
+
+    int getEdge();
+
+    int getOrigEdgeFirst();
+
+    int getOrigEdgeLast();
+
+    int getBaseNode();
+
+    int getAdjNode();
+
+    boolean isShortcut();
+
+    int getSkippedEdge1();
+
+    int getSkippedEdge2();
+
+    double getWeight(boolean reverse);
+}
diff --git a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorStateImpl.java b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorStateImpl.java
new file mode 100644
index 000000000..314ed926a
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorStateImpl.java
@@ -0,0 +1,117 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except in
+ *  compliance with the License. You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.graphhopper.storage;
+
+import com.graphhopper.routing.profiles.BooleanEncodedValue;
+import com.graphhopper.routing.weighting.Weighting;
+import com.graphhopper.util.CHEdgeIteratorState;
+import com.graphhopper.util.EdgeIteratorState;
+
+public class RoutingCHEdgeIteratorStateImpl implements RoutingCHEdgeIteratorState {
+    private final EdgeIteratorState edgeState;
+    private final Weighting weighting;
+    private final BooleanEncodedValue accessEnc;
+
+    public RoutingCHEdgeIteratorStateImpl(EdgeIteratorState edgeState, Weighting weighting) {
+        this.edgeState = edgeState;
+        this.weighting = weighting;
+        this.accessEnc = weighting.getFlagEncoder().getAccessEnc();
+    }
+
+    @Override
+    public EdgeIteratorState getBaseGraphEdgeState() {
+        if (isShortcut()) {
+            throw new IllegalStateException("Base edge can only be obtained for original edges, was: " + edgeState());
+        }
+        return edgeState();
+    }
+
+    @Override
+    public int getEdge() {
+        return edgeState().getEdge();
+    }
+
+    @Override
+    public int getOrigEdgeFirst() {
+        return edgeState().getOrigEdgeFirst();
+    }
+
+    @Override
+    public int getOrigEdgeLast() {
+        return edgeState().getOrigEdgeLast();
+    }
+
+    @Override
+    public int getBaseNode() {
+        return edgeState().getBaseNode();
+    }
+
+    @Override
+    public int getAdjNode() {
+        return edgeState().getAdjNode();
+    }
+
+    @Override
+    public boolean isShortcut() {
+        return (edgeState() instanceof CHEdgeIteratorState) && ((CHEdgeIteratorState) edgeState()).isShortcut();
+    }
+
+    @Override
+    public int getSkippedEdge1() {
+        return ((CHEdgeIteratorState) edgeState()).getSkippedEdge1();
+    }
+
+    @Override
+    public int getSkippedEdge2() {
+        return ((CHEdgeIteratorState) edgeState()).getSkippedEdge2();
+    }
+
+    @Override
+    public double getWeight(boolean reverse) {
+        if (isShortcut()) {
+            return ((CHEdgeIteratorState) edgeState()).getWeight();
+        } else {
+            return getOrigEdgeWeight(reverse, true);
+        }
+    }
+
+    /**
+     * @param needWeight if true this method will return as soon as its clear that the weight is finite (no need to
+     *                   do the full computation)
+     */
+    double getOrigEdgeWeight(boolean reverse, boolean needWeight) {
+        // todo: for #1776 move the access check into the weighting
+        final EdgeIteratorState baseEdge = getBaseGraphEdgeState();
+        final boolean access = reverse
+                ? baseEdge.getReverse(accessEnc)
+                : baseEdge.get(accessEnc);
+        if (baseEdge.getBaseNode() != baseEdge.getAdjNode() && !access) {
+            return Double.POSITIVE_INFINITY;
+        }
+        if (!needWeight) {
+            return 0;
+        }
+        return weighting.calcEdgeWeight(baseEdge, reverse);
+    }
+
+    EdgeIteratorState edgeState() {
+        // use this only via this getter method as it might have been overwritten
+        return edgeState;
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/storage/RoutingCHGraph.java b/core/src/main/java/com/graphhopper/storage/RoutingCHGraph.java
new file mode 100644
index 000000000..0c88f613e
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/storage/RoutingCHGraph.java
@@ -0,0 +1,62 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except in
+ *  compliance with the License. You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.graphhopper.storage;
+
+import com.graphhopper.routing.querygraph.QueryGraph;
+import com.graphhopper.routing.weighting.Weighting;
+
+public interface RoutingCHGraph {
+    int getNodes();
+
+    int getEdges();
+
+    int getOriginalEdges();
+
+    int getOtherNode(int edge, int node);
+
+    boolean isAdjacentToNode(int edge, int node);
+
+    RoutingCHEdgeExplorer createInEdgeExplorer();
+
+    RoutingCHEdgeExplorer createOutEdgeExplorer();
+
+    RoutingCHEdgeExplorer createAllEdgeExplorer();
+
+    RoutingCHEdgeExplorer createOriginalInEdgeExplorer();
+
+    RoutingCHEdgeExplorer createOriginalOutEdgeExplorer();
+
+    RoutingCHEdgeIteratorState getEdgeIteratorState(int edgeId, int adjNode);
+
+    int getLevel(int node);
+
+    double getTurnWeight(int inEdge, int viaNode, int outEdge);
+
+    /**
+     * @return the underlying graph, i.e. a {@link CHGraph} or a {@link QueryGraph} wrapping a {@link CHGraph}
+     */
+    Graph getGraph();
+
+    /**
+     * @return the base graph (no virtual edges or shortcuts)
+     */
+    Graph getBaseGraph();
+
+    Weighting getWeighting();
+}
diff --git a/core/src/main/java/com/graphhopper/storage/RoutingCHGraphImpl.java b/core/src/main/java/com/graphhopper/storage/RoutingCHGraphImpl.java
new file mode 100644
index 000000000..bb7a3be09
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/storage/RoutingCHGraphImpl.java
@@ -0,0 +1,142 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except in
+ *  compliance with the License. You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.graphhopper.storage;
+
+import com.graphhopper.routing.querygraph.QueryGraph;
+import com.graphhopper.routing.weighting.TurnWeighting;
+import com.graphhopper.routing.weighting.Weighting;
+import com.graphhopper.util.EdgeIteratorState;
+
+public class RoutingCHGraphImpl implements RoutingCHGraph {
+    /**
+     * can be a CHGraph or a QueryGraph wrapping a CHGraph
+     */
+    private final Graph graph;
+    /**
+     * the CHGraph, might be the same as graph
+     */
+    private final CHGraph chGraph;
+    /**
+     * the base graph
+     */
+    private final Graph baseGraph;
+    private final Weighting weighting;
+    private final TurnWeighting turnWeighting;
+
+    public RoutingCHGraphImpl(Graph graph, Weighting weighting) {
+        this(graph, weighting, null);
+    }
+
+    public RoutingCHGraphImpl(Graph graph, Weighting weighting, TurnWeighting turnWeighting) {
+        this.graph = graph;
+        if (graph instanceof QueryGraph) {
+            chGraph = (CHGraph) ((QueryGraph) graph).getMainGraph();
+        } else {
+            chGraph = (CHGraph) graph;
+        }
+        baseGraph = chGraph.getBaseGraph();
+        this.weighting = weighting;
+        this.turnWeighting = turnWeighting;
+    }
+
+    @Override
+    public int getNodes() {
+        return graph.getNodes();
+    }
+
+    @Override
+    public int getEdges() {
+        return graph.getEdges();
+    }
+
+    @Override
+    public int getOriginalEdges() {
+        return baseGraph.getEdges();
+    }
+
+    @Override
+    public int getOtherNode(int edge, int node) {
+        return graph.getOtherNode(edge, node);
+    }
+
+    @Override
+    public boolean isAdjacentToNode(int edge, int node) {
+        return graph.isAdjacentToNode(edge, node);
+    }
+
+    @Override
+    public RoutingCHEdgeExplorer createInEdgeExplorer() {
+        return RoutingCHEdgeIteratorImpl.inEdges(graph.createEdgeExplorer(), weighting);
+    }
+
+    @Override
+    public RoutingCHEdgeExplorer createOutEdgeExplorer() {
+        return RoutingCHEdgeIteratorImpl.outEdges(graph.createEdgeExplorer(), weighting);
+    }
+
+    @Override
+    public RoutingCHEdgeExplorer createAllEdgeExplorer() {
+        return RoutingCHEdgeIteratorImpl.allEdges(graph.createEdgeExplorer(), weighting);
+    }
+
+    @Override
+    public RoutingCHEdgeExplorer createOriginalInEdgeExplorer() {
+        return RoutingCHEdgeIteratorImpl.inEdges(graph.getBaseGraph().createEdgeExplorer(), weighting);
+    }
+
+    @Override
+    public RoutingCHEdgeExplorer createOriginalOutEdgeExplorer() {
+        return RoutingCHEdgeIteratorImpl.outEdges(graph.getBaseGraph().createEdgeExplorer(), weighting);
+    }
+
+    @Override
+    public RoutingCHEdgeIteratorState getEdgeIteratorState(int edgeId, int adjNode) {
+        EdgeIteratorState edgeState = graph.getEdgeIteratorState(edgeId, adjNode);
+        return edgeState == null ? null : new RoutingCHEdgeIteratorStateImpl(edgeState, weighting);
+    }
+
+    @Override
+    public int getLevel(int node) {
+        return chGraph.getLevel(node);
+    }
+
+    @Override
+    public Graph getGraph() {
+        return graph;
+    }
+
+    @Override
+    public Graph getBaseGraph() {
+        return chGraph.getBaseGraph();
+    }
+
+    @Override
+    public Weighting getWeighting() {
+        return turnWeighting != null ? turnWeighting : weighting;
+    }
+
+    @Override
+    public double getTurnWeight(int edgeFrom, int nodeVia, int edgeTo) {
+        if (turnWeighting == null) {
+            return 0;
+        }
+        return turnWeighting.calcTurnWeight(edgeFrom, nodeVia, edgeTo);
+    }
+
+}
diff --git a/core/src/main/java/com/graphhopper/storage/ShortcutUnpacker.java b/core/src/main/java/com/graphhopper/storage/ShortcutUnpacker.java
index 5effacd34..c4cdef846 100644
--- a/core/src/main/java/com/graphhopper/storage/ShortcutUnpacker.java
+++ b/core/src/main/java/com/graphhopper/storage/ShortcutUnpacker.java
@@ -1,7 +1,6 @@
 package com.graphhopper.storage;
 
 import com.graphhopper.routing.ch.PrepareContractionHierarchies;
-import com.graphhopper.util.CHEdgeIteratorState;
 import com.graphhopper.util.EdgeIteratorState;
 
 import java.util.Locale;
@@ -17,12 +16,12 @@ import static com.graphhopper.util.EdgeIterator.NO_EDGE;
  * @see PrepareContractionHierarchies
  */
 public class ShortcutUnpacker {
-    private final Graph graph;
+    private final RoutingCHGraph graph;
     private final Visitor visitor;
     private final boolean edgeBased;
     private boolean reverseOrder;
 
-    public ShortcutUnpacker(Graph graph, Visitor visitor, boolean edgeBased) {
+    public ShortcutUnpacker(RoutingCHGraph graph, Visitor visitor, boolean edgeBased) {
         this.graph = graph;
         this.visitor = visitor;
         this.edgeBased = edgeBased;
@@ -44,16 +43,16 @@ public class ShortcutUnpacker {
 
     private void doVisitOriginalEdges(int edgeId, int adjNode, boolean reverseOrder, boolean reverse, int prevOrNextEdgeId) {
         this.reverseOrder = reverseOrder;
-        CHEdgeIteratorState edge = getEdge(edgeId, adjNode);
+        RoutingCHEdgeIteratorState 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, reverse, prevOrNextEdgeId);
     }
 
-    private void expandEdge(CHEdgeIteratorState edge, boolean reverse, int prevOrNextEdgeId) {
+    private void expandEdge(RoutingCHEdgeIteratorState edge, boolean reverse, int prevOrNextEdgeId) {
         if (!edge.isShortcut()) {
-            visitor.visit(edge, reverse, prevOrNextEdgeId);
+            visitor.visit(edge.getBaseGraphEdgeState(), reverse, prevOrNextEdgeId);
             return;
         }
         if (edgeBased) {
@@ -69,10 +68,10 @@ public class ShortcutUnpacker {
             skippedEdge1 = skippedEdge2;
             skippedEdge2 = tmp;
         }
-        CHEdgeIteratorState sk2 = getEdge(skippedEdge2, adj);
+        RoutingCHEdgeIteratorState 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());
+        RoutingCHEdgeIteratorState 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, " +
@@ -89,8 +88,8 @@ public class ShortcutUnpacker {
     }
 
     private void expandSkippedEdgesNodeBased(int skippedEdge1, int skippedEdge2, int base, int adj, boolean reverse) {
-        CHEdgeIteratorState sk2 = getEdge(skippedEdge2, adj);
-        CHEdgeIteratorState sk1;
+        RoutingCHEdgeIteratorState sk2 = getEdge(skippedEdge2, adj);
+        RoutingCHEdgeIteratorState sk1;
         if (sk2 == null) {
             sk2 = getEdge(skippedEdge1, adj);
             sk1 = getEdge(skippedEdge2, sk2.getBaseNode());
@@ -106,7 +105,7 @@ public class ShortcutUnpacker {
         }
     }
 
-    private int getOppositeEdge(CHEdgeIteratorState edgeState, int adjNode) {
+    private int getOppositeEdge(RoutingCHEdgeIteratorState 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.
@@ -115,8 +114,8 @@ public class ShortcutUnpacker {
                 : edgeState.getOrigEdgeLast();
     }
 
-    private CHEdgeIteratorState getEdge(int edgeId, int adjNode) {
-        return (CHEdgeIteratorState) graph.getEdgeIteratorState(edgeId, adjNode);
+    private RoutingCHEdgeIteratorState getEdge(int edgeId, int adjNode) {
+        return graph.getEdgeIteratorState(edgeId, adjNode);
     }
 
     public interface Visitor {
diff --git a/isochrone/src/main/java/com/graphhopper/isochrone/algorithm/Isochrone.java b/isochrone/src/main/java/com/graphhopper/isochrone/algorithm/Isochrone.java
index 83dbc732f..3cbba9b6f 100644
--- a/isochrone/src/main/java/com/graphhopper/isochrone/algorithm/Isochrone.java
+++ b/isochrone/src/main/java/com/graphhopper/isochrone/algorithm/Isochrone.java
@@ -23,12 +23,12 @@ import com.graphhopper.coll.GHIntObjectHashMap;
 import com.graphhopper.routing.AbstractRoutingAlgorithm;
 import com.graphhopper.routing.Path;
 import com.graphhopper.routing.PathExtractor;
+import com.graphhopper.routing.util.EdgeFilter;
 import com.graphhopper.routing.util.TraversalMode;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.NodeAccess;
 import com.graphhopper.storage.SPTEntry;
-import com.graphhopper.util.EdgeExplorer;
 import com.graphhopper.util.EdgeIterator;
 import com.graphhopper.util.shapes.GHPoint;
 import org.locationtech.jts.geom.Coordinate;
@@ -226,7 +226,7 @@ public class Isochrone extends AbstractRoutingAlgorithm {
         checkAlreadyRun();
         currEdge = new IsoLabel(-1, from, 0, 0, 0);
         fromMap.put(from, currEdge);
-        EdgeExplorer explorer = reverseFlow ? inEdgeExplorer : outEdgeExplorer;
+        EdgeFilter filter = reverseFlow ? inEdgeFilter : outEdgeFilter;
         while (true) {
             visitedNodes++;
             if (finished()) {
@@ -234,13 +234,14 @@ public class Isochrone extends AbstractRoutingAlgorithm {
             }
 
             int neighborNode = currEdge.adjNode;
-            EdgeIterator iter = explorer.setBaseNode(neighborNode);
+            EdgeIterator iter = edgeExplorer.setBaseNode(neighborNode);
             while (iter.next()) {
                 if (!accept(iter, currEdge.edge)) {
                     continue;
                 }
 
-                double tmpWeight = weighting.calcWeight(iter, reverseFlow, currEdge.edge) + currEdge.weight;
+                // todo: for #1776/#1835 move the access check into weighting
+                double tmpWeight = !filter.accept(iter) ? Double.POSITIVE_INFINITY : (weighting.calcWeight(iter, reverseFlow, currEdge.edge) + currEdge.weight);
                 if (Double.isInfinite(tmpWeight))
                     continue;
 
diff --git a/tools/src/main/java/com/graphhopper/ui/DebugAStarBi.java b/tools/src/main/java/com/graphhopper/ui/DebugAStarBi.java
index 30db0529c..2e31e006a 100644
--- a/tools/src/main/java/com/graphhopper/ui/DebugAStarBi.java
+++ b/tools/src/main/java/com/graphhopper/ui/DebugAStarBi.java
@@ -22,7 +22,6 @@ import com.graphhopper.routing.util.TraversalMode;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.SPTEntry;
-import com.graphhopper.util.EdgeIteratorState;
 
 import java.awt.*;
 
@@ -44,11 +43,11 @@ public class DebugAStarBi extends AStarBidirection implements DebugAlgo {
     }
 
     @Override
-    public void updateBestPath(EdgeIteratorState edgeState, SPTEntry entry, int traversalId, boolean reverse) {
+    public void updateBestPath(double edgeWeight, SPTEntry entry, int origEdgeId, int traversalId, boolean reverse) {
         if (g2 != null) {
             mg.plotNode(g2, traversalId, Color.YELLOW);
         }
-        super.updateBestPath(edgeState, entry, traversalId, reverse);
+        super.updateBestPath(edgeWeight, entry, origEdgeId, traversalId, reverse);
     }
 
     @Override
diff --git a/tools/src/main/java/com/graphhopper/ui/DebugDijkstraBidirection.java b/tools/src/main/java/com/graphhopper/ui/DebugDijkstraBidirection.java
index ef41465c6..c203307ec 100644
--- a/tools/src/main/java/com/graphhopper/ui/DebugDijkstraBidirection.java
+++ b/tools/src/main/java/com/graphhopper/ui/DebugDijkstraBidirection.java
@@ -23,7 +23,6 @@ import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.NodeAccess;
 import com.graphhopper.storage.SPTEntry;
-import com.graphhopper.util.EdgeIteratorState;
 
 import java.awt.*;
 
@@ -47,11 +46,11 @@ public class DebugDijkstraBidirection extends DijkstraBidirectionRef implements
     }
 
     @Override
-    public void updateBestPath(EdgeIteratorState es, SPTEntry entry, int traversalId, boolean reverse) {
+    public void updateBestPath(double edgeWeight, SPTEntry entry, int origEdgeId, int traversalId, boolean reverse) {
         if (g2 != null) {
             mg.plotEdge(g2, na.getLat(entry.parent.adjNode), na.getLon(entry.parent.adjNode), na.getLat(entry.adjNode), na.getLon(entry.adjNode), .8f);
         }
         // System.out.println("new node:" + currLoc);
-        super.updateBestPath(es, entry, traversalId, reverse);
+        super.updateBestPath(edgeWeight, entry, origEdgeId, traversalId, reverse);
     }
 }
diff --git a/tools/src/main/java/com/graphhopper/ui/MiniGraphUI.java b/tools/src/main/java/com/graphhopper/ui/MiniGraphUI.java
index 22481ffb4..db0655574 100644
--- a/tools/src/main/java/com/graphhopper/ui/MiniGraphUI.java
+++ b/tools/src/main/java/com/graphhopper/ui/MiniGraphUI.java
@@ -23,18 +23,23 @@ import com.graphhopper.coll.GHBitSet;
 import com.graphhopper.coll.GHTBitSet;
 import com.graphhopper.reader.osm.GraphHopperOSM;
 import com.graphhopper.routing.*;
-import com.graphhopper.routing.ch.CHWeighting;
 import com.graphhopper.routing.ch.PrepareContractionHierarchies;
 import com.graphhopper.routing.profiles.BooleanEncodedValue;
 import com.graphhopper.routing.profiles.DecimalEncodedValue;
 import com.graphhopper.routing.querygraph.QueryGraph;
-import com.graphhopper.routing.util.*;
+import com.graphhopper.routing.util.AllEdgesIterator;
+import com.graphhopper.routing.util.EdgeFilter;
+import com.graphhopper.routing.util.FlagEncoder;
+import com.graphhopper.routing.util.HintsMap;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.*;
 import com.graphhopper.storage.index.LocationIndexTree;
 import com.graphhopper.storage.index.QueryResult;
-import com.graphhopper.util.*;
+import com.graphhopper.util.CmdArgs;
+import com.graphhopper.util.Parameters;
 import com.graphhopper.util.Parameters.Algorithms;
+import com.graphhopper.util.PointList;
+import com.graphhopper.util.StopWatch;
 import com.graphhopper.util.shapes.BBox;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -106,8 +111,8 @@ public class MiniGraphUI {
                     private final GraphicsWrapper mg;
                     private Graphics2D g2;
 
-                    public TmpAlgo(Graph graph, Weighting type, GraphicsWrapper mg) {
-                        super(graph, type);
+                    public TmpAlgo(RoutingCHGraph graph, GraphicsWrapper mg) {
+                        super(graph);
                         this.mg = mg;
                     }
 
@@ -117,11 +122,11 @@ public class MiniGraphUI {
                     }
 
                     @Override
-                    public void updateBestPath(EdgeIteratorState es, SPTEntry entry, int traversalId, boolean reverse) {
+                    public void updateBestPath(double edgeWeight, SPTEntry entry, int origEdgeId, int traversalId, boolean reverse) {
                         if (g2 != null)
                             mg.plotNode(g2, traversalId, Color.YELLOW, 6);
 
-                        super.updateBestPath(es, entry, traversalId, reverse);
+                        super.updateBestPath(edgeWeight, entry, origEdgeId, traversalId, reverse);
                     }
                 }
 
@@ -129,8 +134,7 @@ public class MiniGraphUI {
                 public RoutingAlgorithm createAlgo(Graph g, AlgorithmOptions opts) {
                     // doable but ugly
                     Weighting w = ((PrepareContractionHierarchies) tmpFactory).getWeighting();
-                    return new TmpAlgo(g, new CHWeighting(w), mg).
-                            setEdgeFilter(new LevelEdgeFilter((CHGraph) routingGraph));
+                    return new TmpAlgo(new RoutingCHGraphImpl(routingGraph, w), mg);
                 }
             };
             algoOpts = new AlgorithmOptions(Algorithms.DIJKSTRA_BI, weighting);


Test patch:
diff --git a/core/src/test/java/com/graphhopper/routing/AlternativeRouteCHTest.java b/core/src/test/java/com/graphhopper/routing/AlternativeRouteCHTest.java
index 6c5ad681a..23d55407b 100644
--- a/core/src/test/java/com/graphhopper/routing/AlternativeRouteCHTest.java
+++ b/core/src/test/java/com/graphhopper/routing/AlternativeRouteCHTest.java
@@ -17,19 +17,23 @@
  */
 package com.graphhopper.routing;
 
-import com.graphhopper.routing.ch.CHWeighting;
 import com.graphhopper.routing.ch.NodeOrderingProvider;
 import com.graphhopper.routing.ch.PrepareContractionHierarchies;
-import com.graphhopper.routing.util.*;
+import com.graphhopper.routing.util.CarFlagEncoder;
+import com.graphhopper.routing.util.EncodingManager;
+import com.graphhopper.routing.util.FlagEncoder;
 import com.graphhopper.routing.weighting.FastestWeighting;
 import com.graphhopper.routing.weighting.Weighting;
-import com.graphhopper.storage.*;
+import com.graphhopper.storage.CHProfile;
+import com.graphhopper.storage.GraphHopperStorage;
+import com.graphhopper.storage.RAMDirectory;
+import com.graphhopper.storage.RoutingCHGraphImpl;
 import org.junit.Test;
 
 import java.util.Arrays;
 import java.util.List;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
 public class AlternativeRouteCHTest {
     private final FlagEncoder carFE = new CarFlagEncoder();
@@ -94,8 +98,7 @@ public class AlternativeRouteCHTest {
     @Test
     public void testCalcAlternatives() {
         GraphHopperStorage g = createTestGraph(em);
-        AlternativeRouteCH altDijkstra = new AlternativeRouteCH(g.getCHGraph(), new CHWeighting(weighting));
-        altDijkstra.setEdgeFilter(new LevelEdgeFilter(g.getCHGraph()));
+        AlternativeRouteCH altDijkstra = new AlternativeRouteCH(new RoutingCHGraphImpl(g.getCHGraph(), weighting));
         altDijkstra.setMaxShareFactor(0.9);
         altDijkstra.setMaxWeightFactor(10);
         List<AlternativeRouteCH.AlternativeInfo> pathInfos = altDijkstra.calcAlternatives(5, 10);
diff --git a/core/src/test/java/com/graphhopper/routing/CHQueryWithTurnCostsTest.java b/core/src/test/java/com/graphhopper/routing/CHQueryWithTurnCostsTest.java
index 2d0a67fe9..9d34149b4 100644
--- a/core/src/test/java/com/graphhopper/routing/CHQueryWithTurnCostsTest.java
+++ b/core/src/test/java/com/graphhopper/routing/CHQueryWithTurnCostsTest.java
@@ -19,19 +19,14 @@
 package com.graphhopper.routing;
 
 import com.carrotsearch.hppc.IntArrayList;
-import com.graphhopper.routing.ch.CHWeighting;
 import com.graphhopper.routing.ch.PrepareEncoder;
 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.CHGraph;
-import com.graphhopper.storage.CHProfile;
-import com.graphhopper.storage.GraphBuilder;
-import com.graphhopper.storage.GraphHopperStorage;
+import com.graphhopper.storage.*;
 import com.graphhopper.util.EdgeIteratorState;
 import com.graphhopper.util.GHUtility;
 import org.junit.Test;
@@ -711,12 +706,10 @@ public class CHQueryWithTurnCostsTest {
     }
 
     private AbstractBidirectionEdgeCHNoSOD createAlgo() {
-        TurnWeighting chTurnWeighting = new TurnWeighting(new CHWeighting(weighting), graph.getTurnCostStorage());
-        AbstractBidirectionEdgeCHNoSOD algo = "astar".equals(algoString) ?
-                new AStarBidirectionEdgeCHNoSOD(chGraph, chTurnWeighting) :
-                new DijkstraBidirectionEdgeCHNoSOD(chGraph, chTurnWeighting);
-        algo.setEdgeFilter(new LevelEdgeFilter(chGraph));
-        return algo;
+        TurnWeighting turnWeighting = new TurnWeighting(weighting, graph.getTurnCostStorage());
+        return "astar".equals(algoString) ?
+                new AStarBidirectionEdgeCHNoSOD(new RoutingCHGraphImpl(chGraph, weighting, turnWeighting)) :
+                new DijkstraBidirectionEdgeCHNoSOD(new RoutingCHGraphImpl(chGraph, weighting, turnWeighting));
     }
 
     private void addShortcut(int from, int to, int firstOrigEdge, int lastOrigEdge, int skipped1, int skipped2, double weight) {
diff --git a/core/src/test/java/com/graphhopper/routing/DijkstraOneToManyTest.java b/core/src/test/java/com/graphhopper/routing/DijkstraOneToManyTest.java
index 9ada34f9e..3d9498be3 100644
--- a/core/src/test/java/com/graphhopper/routing/DijkstraOneToManyTest.java
+++ b/core/src/test/java/com/graphhopper/routing/DijkstraOneToManyTest.java
@@ -18,18 +18,14 @@
 package com.graphhopper.routing;
 
 import com.carrotsearch.hppc.IntArrayList;
-import com.graphhopper.routing.util.EdgeFilter;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.FlagEncoder;
 import com.graphhopper.routing.util.TraversalMode;
-import com.graphhopper.routing.weighting.FastestWeighting;
 import com.graphhopper.routing.weighting.ShortestWeighting;
 import com.graphhopper.routing.weighting.Weighting;
-import com.graphhopper.storage.CHProfile;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.GraphBuilder;
 import com.graphhopper.storage.GraphHopperStorage;
-import com.graphhopper.util.EdgeIteratorState;
 import org.junit.Test;
 
 import static com.graphhopper.routing.RoutingAlgorithmTest.initTestStorage;
@@ -124,37 +120,6 @@ public class DijkstraOneToManyTest {
         assertEquals(IntArrayList.from(0, 1, 2), p.calcNodes());
     }
 
-    @Test
-    public void testDifferentEdgeFilter() {
-        GraphHopperStorage g = new GraphBuilder(encodingManager).setCHProfiles(CHProfile.nodeBased(new FastestWeighting(carEncoder))).create();
-        g.edge(4, 3, 10, true);
-        g.edge(3, 6, 10, true);
-
-        g.edge(4, 5, 10, true);
-        g.edge(5, 6, 10, true);
-
-        DijkstraOneToMany algo = createAlgo(g);
-        algo.setEdgeFilter(new EdgeFilter() {
-            @Override
-            public boolean accept(EdgeIteratorState iter) {
-                return iter.getAdjNode() != 5;
-            }
-        });
-        Path p = algo.calcPath(4, 6);
-        assertEquals(IntArrayList.from(4, 3, 6), p.calcNodes());
-
-        // important call!
-        algo.clear();
-        algo.setEdgeFilter(new EdgeFilter() {
-            @Override
-            public boolean accept(EdgeIteratorState iter) {
-                return iter.getAdjNode() != 3;
-            }
-        });
-        p = algo.calcPath(4, 6);
-        assertEquals(IntArrayList.from(4, 5, 6), p.calcNodes());
-    }
-
     private void initGraph(Graph g) {
         // 0-1-2-3-4
         // |       /
diff --git a/core/src/test/java/com/graphhopper/routing/DirectedBidirectionalDijkstraTest.java b/core/src/test/java/com/graphhopper/routing/DirectedBidirectionalDijkstraTest.java
index ce668f7ed..548cc9c1e 100644
--- a/core/src/test/java/com/graphhopper/routing/DirectedBidirectionalDijkstraTest.java
+++ b/core/src/test/java/com/graphhopper/routing/DirectedBidirectionalDijkstraTest.java
@@ -498,7 +498,7 @@ public class DirectedBidirectionalDijkstraTest {
 
         EdgeIteratorState virtualEdge = GHUtility.getEdge(queryGraph, 6, 1);
         int outEdge = virtualEdge.getEdge();
-        AbstractBidirAlgo algo = createAlgo(queryGraph, weighting);
+        BidirRoutingAlgorithm algo = createAlgo(queryGraph, weighting);
         Path path = algo.calcPath(6, 0, outEdge, ANY_EDGE);
         assertEquals(nodes(6, 1, 2, 3, 4, 5, 0), path.calcNodes());
         assertEquals(5 + virtualEdge.getDistance(), path.getDistance(), 1.e-3);
@@ -509,11 +509,11 @@ public class DirectedBidirectionalDijkstraTest {
     }
 
     private Path calcPath(int source, int target, int sourceOutEdge, int targetInEdge, Weighting w) {
-        AbstractBidirAlgo algo = createAlgo(graph, w);
+        BidirRoutingAlgorithm algo = createAlgo(graph, w);
         return algo.calcPath(source, target, sourceOutEdge, targetInEdge);
     }
 
-    private AbstractBidirAlgo createAlgo(Graph graph, Weighting weighting) {
+    private BidirRoutingAlgorithm createAlgo(Graph graph, Weighting weighting) {
         return new DijkstraBidirectionRef(graph, weighting, TraversalMode.EDGE_BASED);
     }
 
diff --git a/core/src/test/java/com/graphhopper/routing/ch/NodeBasedNodeContractorTest.java b/core/src/test/java/com/graphhopper/routing/ch/NodeBasedNodeContractorTest.java
index 1344c828b..9f2a11bf2 100644
--- a/core/src/test/java/com/graphhopper/routing/ch/NodeBasedNodeContractorTest.java
+++ b/core/src/test/java/com/graphhopper/routing/ch/NodeBasedNodeContractorTest.java
@@ -17,20 +17,20 @@
  */
 package com.graphhopper.routing.ch;
 
-import com.carrotsearch.hppc.IntArrayList;
 import com.graphhopper.routing.Dijkstra;
 import com.graphhopper.routing.DijkstraBidirectionCH;
 import com.graphhopper.routing.Path;
 import com.graphhopper.routing.profiles.BooleanEncodedValue;
 import com.graphhopper.routing.profiles.EncodedValue;
 import com.graphhopper.routing.profiles.SimpleBooleanEncodedValue;
-import com.graphhopper.routing.querygraph.QueryGraph;
-import com.graphhopper.routing.util.*;
+import com.graphhopper.routing.util.AllCHEdgesIterator;
+import com.graphhopper.routing.util.CarFlagEncoder;
+import com.graphhopper.routing.util.EncodingManager;
+import com.graphhopper.routing.util.TraversalMode;
 import com.graphhopper.routing.weighting.FastestWeighting;
 import com.graphhopper.routing.weighting.ShortestWeighting;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.*;
-import com.graphhopper.storage.index.LocationIndexTree;
 import com.graphhopper.util.CHEdgeIteratorState;
 import com.graphhopper.util.EdgeIteratorState;
 import com.graphhopper.util.PMap;
@@ -41,8 +41,6 @@ import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
 
-import static com.graphhopper.util.GHUtility.updateDistancesFor;
-import static com.graphhopper.util.Parameters.Routing.HEADING_PENALTY;
 import static org.junit.Assert.*;
 
 public class NodeBasedNodeContractorTest {
@@ -318,7 +316,7 @@ public class NodeBasedNodeContractorTest {
         Dijkstra dikstra = new Dijkstra(graph, weighting, TraversalMode.NODE_BASED);
         Path dijkstraPath = dikstra.calcPath(from, to);
 
-        DijkstraBidirectionCH ch = new DijkstraBidirectionCH(lg, new CHWeighting(weighting));
+        DijkstraBidirectionCH ch = new DijkstraBidirectionCH(new RoutingCHGraphImpl(lg, weighting));
         Path chPath = ch.calcPath(from, to);
         assertEquals(dijkstraPath.calcNodes(), chPath.calcNodes());
         assertEquals(dijkstraPath.getDistance(), chPath.getDistance(), 1.e-6);
@@ -367,7 +365,7 @@ public class NodeBasedNodeContractorTest {
         Dijkstra dikstra = new Dijkstra(graph, weighting, TraversalMode.NODE_BASED);
         Path dijkstraPath = dikstra.calcPath(from, to);
 
-        DijkstraBidirectionCH ch = new DijkstraBidirectionCH(lg, new CHWeighting(weighting));
+        DijkstraBidirectionCH ch = new DijkstraBidirectionCH(new RoutingCHGraphImpl(lg, weighting));
         Path chPath = ch.calcPath(from, to);
         assertEquals(dijkstraPath.calcNodes(), chPath.calcNodes());
         assertEquals(dijkstraPath.getDistance(), chPath.getDistance(), 1.e-6);
@@ -400,65 +398,6 @@ public class NodeBasedNodeContractorTest {
         checkNoShortcuts(pg);
     }
 
-    @Test
-    public void routingWithHeading_fails() {
-        // heading does not work properly with CH!
-        // 3 - 2 - x - 0 - 1 - 4
-        //     |           |
-        //     5 - - - - - 6
-        graph.edge(3, 2, 10, true);
-        graph.edge(2, 0, 10, true);
-        graph.edge(0, 1, 10, true);
-        graph.edge(1, 4, 10, true);
-        graph.edge(2, 5, 10, true);
-        graph.edge(5, 6, 10, true);
-        graph.edge(6, 1, 10, true);
-        updateDistancesFor(graph, 3, 0.02, 0.00);
-        updateDistancesFor(graph, 2, 0.02, 0.01);
-        updateDistancesFor(graph, 0, 0.02, 0.03);
-        updateDistancesFor(graph, 1, 0.02, 0.04);
-        updateDistancesFor(graph, 4, 0.02, 0.05);
-        updateDistancesFor(graph, 5, 0.01, 0.01);
-        updateDistancesFor(graph, 6, 0.01, 0.01);
-
-        graph.freeze();
-        setMaxLevelOnAllNodes(pg);
-
-        // perform CH contraction
-        Weighting weighting = new FastestWeighting(encoder, new PMap().put(HEADING_PENALTY, Double.POSITIVE_INFINITY));
-        PrepareCHGraph pg = PrepareCHGraph.nodeBased(lg, weighting);
-        contractInOrder(pg, 0, 1, 2, 3, 4, 5, 6);
-
-        // build query graph
-        LocationIndexTree locationIndex = new LocationIndexTree(graph, new RAMDirectory());
-        locationIndex.prepareIndex();
-
-        QueryGraph chQueryGraph = QueryGraph.lookup(lg, locationIndex.findClosest(0.021, 0.02, EdgeFilter.ALL_EDGES));
-
-        QueryGraph queryGraph = QueryGraph.lookup(graph, locationIndex.findClosest(0.021, 0.02, EdgeFilter.ALL_EDGES));
-
-        // without heading
-        {
-            DijkstraBidirectionCH ch = new DijkstraBidirectionCH(chQueryGraph, new CHWeighting(weighting));
-            Path chPath = ch.calcPath(7, 3);
-            assertEquals(IntArrayList.from(7, 2, 3), chPath.calcNodes());
-        }
-        // with heading
-        {
-            queryGraph.enforceHeading(7, 90, false);
-            Dijkstra dijkstra = new Dijkstra(queryGraph, weighting, TraversalMode.NODE_BASED);
-            Path path = dijkstra.calcPath(7, 3);
-            assertEquals(IntArrayList.from(7, 0, 1, 6, 5, 2, 3), path.calcNodes());
-
-            chQueryGraph.enforceHeading(7, 90, false);
-            DijkstraBidirectionCH ch = new DijkstraBidirectionCH(chQueryGraph, new CHWeighting(weighting));
-            Path chPath = ch.calcPath(7, 3);
-            // the route takes a u-turn at node 1 and 'skips' the virtual node when going from 0 to 3 (because it takes
-            // the shortcut from 1 to 2), which both should not be
-            assertEquals(IntArrayList.from(7, 0, 1, 0, 2, 3), chPath.calcNodes());
-        }
-    }
-
     private void contractInOrder(int... nodeIds) {
         contractInOrder(pg, nodeIds);
     }
diff --git a/core/src/test/java/com/graphhopper/routing/ch/Path4CHTest.java b/core/src/test/java/com/graphhopper/routing/ch/Path4CHTest.java
index 3d057c96c..8f2a8cb15 100644
--- a/core/src/test/java/com/graphhopper/routing/ch/Path4CHTest.java
+++ b/core/src/test/java/com/graphhopper/routing/ch/Path4CHTest.java
@@ -1,15 +1,13 @@
 package com.graphhopper.routing.ch;
 
-import com.graphhopper.routing.AbstractBidirectionEdgeCHNoSOD;
-import com.graphhopper.routing.DijkstraBidirectionEdgeCHNoSOD;
+import com.graphhopper.routing.AlgorithmOptions;
 import com.graphhopper.routing.Path;
+import com.graphhopper.routing.RoutingAlgorithm;
 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.CHProfile;
@@ -28,13 +26,12 @@ public class Path4CHTest {
     private GraphHopperStorage graph;
     private CHGraph chGraph;
     private FlagEncoder encoder;
-    private Weighting weighting;
 
     @Before
     public void init() {
         encoder = new MotorcycleFlagEncoder(5, 5, maxTurnCosts);
         EncodingManager em = EncodingManager.create(encoder);
-        weighting = new FastestWeighting(encoder);
+        Weighting weighting = new FastestWeighting(encoder);
         graph = new GraphBuilder(em).setCHProfiles(CHProfile.edgeBased(weighting, INFINITE_U_TURN_COSTS)).create();
         chGraph = graph.getCHGraph();
     }
@@ -151,12 +148,8 @@ public class Path4CHTest {
         assertEquals("wrong time", expectedWeight * 1000, path.getTime(), 1.e-3);
     }
 
-    private AbstractBidirectionEdgeCHNoSOD createAlgo() {
-        TurnWeighting chTurnWeighting = new TurnWeighting(new CHWeighting(weighting), graph.getTurnCostStorage());
-        CHGraph lg = graph.getCHGraph();
-        AbstractBidirectionEdgeCHNoSOD algo = new DijkstraBidirectionEdgeCHNoSOD(lg, chTurnWeighting);
-        algo.setEdgeFilter(new LevelEdgeFilter(lg));
-        return algo;
+    private RoutingAlgorithm createAlgo() {
+        return new CHRoutingAlgorithmFactory(chGraph).createAlgo(chGraph, AlgorithmOptions.start().build());
     }
 
 }
\ No newline at end of file
diff --git a/core/src/test/java/com/graphhopper/routing/weighting/BidirectionalRoutingTest.java b/core/src/test/java/com/graphhopper/routing/weighting/BidirectionalRoutingTest.java
index bc39ad16a..e987c9166 100644
--- a/core/src/test/java/com/graphhopper/routing/weighting/BidirectionalRoutingTest.java
+++ b/core/src/test/java/com/graphhopper/routing/weighting/BidirectionalRoutingTest.java
@@ -113,7 +113,7 @@ public class BidirectionalRoutingTest {
     }
 
     private BidirRoutingAlgorithm createAlgo() {
-        return createAlgo(prepareCH ? chGraph : graph);
+        return createAlgo(graph);
     }
 
     private BidirRoutingAlgorithm createAlgo(Graph graph) {
@@ -121,9 +121,9 @@ public class BidirectionalRoutingTest {
             case ASTAR:
                 return new AStarBidirection(graph, weighting, traversalMode);
             case CH_DIJKSTRA:
-                return (BidirRoutingAlgorithm) pch.getRoutingAlgorithmFactory().createAlgo(graph, AlgorithmOptions.start().weighting(weighting).algorithm(DIJKSTRA_BI).build());
+                return (BidirRoutingAlgorithm) pch.getRoutingAlgorithmFactory().createAlgo(graph instanceof QueryGraph ? graph : chGraph, AlgorithmOptions.start().weighting(weighting).algorithm(DIJKSTRA_BI).build());
             case CH_ASTAR:
-                return (BidirRoutingAlgorithm) pch.getRoutingAlgorithmFactory().createAlgo(graph, AlgorithmOptions.start().weighting(weighting).algorithm(ASTAR_BI).build());
+                return (BidirRoutingAlgorithm) pch.getRoutingAlgorithmFactory().createAlgo(graph instanceof QueryGraph ? graph : chGraph, AlgorithmOptions.start().weighting(weighting).algorithm(ASTAR_BI).build());
             case LM:
                 AStarBidirection astarbi = new AStarBidirection(graph, weighting, traversalMode);
                 return (BidirRoutingAlgorithm) lm.getDecoratedAlgorithm(graph, astarbi, AlgorithmOptions.start().build());
diff --git a/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java b/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java
index 0da64dc79..01fa15fb7 100644
--- a/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java
+++ b/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java
@@ -295,7 +295,7 @@ public class ShortcutUnpackerTest {
     }
 
     private ShortcutUnpacker createShortcutUnpacker(ShortcutUnpacker.Visitor visitor) {
-        return new ShortcutUnpacker(chGraph, visitor, edgeBased);
+        return new ShortcutUnpacker(new RoutingCHGraphImpl(chGraph, weighting), visitor, edgeBased);
     }
 
     private void setTurnCost(int fromEdge, int viaNode, int toEdge, double cost) {