Repository: graphhopper/graphhopper
Pull request #2256 (2021-03-16 16:40:50)

Make LM preparation working with custom profiles
The LM preparation will get an extreme slow down for custom profiles that exclude e.g. motorways (occurs only for world wide instances). Related to #1835.

Usually we just need to pick one node of the returned subnetwork in LM preparation and use that to calculate the coordinates of the landmarks. But likely because of some old code that tried to make this more robust, we loop over all nodes of this subnetwork, to try again with nodes that couldn't be explored.

Interestingly even with this loop it is fast as we exit the loop as soon as a largish subnetwork was found. But now with the CustomWeighting the Tarjan algorithm incorrectly returns a large subnetwork that is splitted by a oneway motorway (Tarjan only sees access not the weight) and then the initial search takes 30 sec (in the large subnetwork) and then due to the next landmarks searches it gets stuck in the small subnetwork (due to the oneway motorway and the filter just uses access flags) and so it returns too few nodes for the last explorer (explorer.getFromCount) so that the next node of the large subnetwork is tried where the same happens again and again likely until all nodes of the large subnetwork were tried. Somewhere [here](https://graphhopper.com/maps/?point=22.365107%2C114.180932&point=22.3804%2C114.203421).

This PR should fix this problem (not yet tried for world wide instances).

Performance seems to be fine.

easbar: Very nice you figured this out! Thanks :)
karussell: It still does not work in rare cases. The reason is that the custom profile can create islands. E.g. "exclude motorway" can disconnect otherwise accessible roads from the main network. To make Tarjan algo aware of this and handle islands that are partially connect we need to replace FiniteWeighting with a directed EdgeFilter:

```
EdgeFilter accessFilter = edgeState -> edgeState.get(encoder.getAccessEnc()) && Double.isFinite(weighting.calcEdgeWeight(edgeState, false));
```

Otherwise the algo won't split e.g. the graph 1-2-3->4-5 into two networks 1-2-3 and 4-5. Which can lead to a problem in the next step where LandmarkExplorer selects landmarks and picks a random start node from a network like 1-2-3 but ends with e.g. 4 as next node and because 4-5 is a network from which 1-2-3 is not reachable and which is also too small it will be skipped and a new start node e.g. 2 is tried and and could fail again and again. Which is the problem here as for the planet-wide import the main network "1-2-3" consists of millions of nodes and so exploration takes half a minute for every node leading to a too long waiting time.

Unfortunately this is not enough to fix the slow down for world wide LM preparation.

The second problem is that LandmarkExplorer is not aware of the created islands (from the customization) and a graph exploration there can cross "island boundaries" leading to the same problem. To fix that we mark the main road network and force the exploration (in LandmarkExplorer) to stay there.

Unfortunately this causes the routing requests to return incorrect approximations. I think there is a bug which was mostly covered until now. Will need to investigate this further.

Likely these problems won't occur when #2273 is done.
easbar: > To make Tarjan algo aware of this and handle islands that are partially connect we need to replace FiniteWeighting with a directed EdgeFilter:

Yes, currently in master we use : `DefaultEdgeFilter.outEdges(accessEnc)`. Using `FiniteWeightFilter` would indeed me wrong because it accepts an edge even when only the reverse direction has finite weight.
karussell: This finally goes through also for a planet-wide import and I was able to remove a bit more code :)

The minor question is now if we should require the subnetwork EncodedValue and adapt the tests. Or if we should keep it like it currently is (only include subnetwork EV if it exists).

For that let me explain why the subnetwork EncodedValue is used. Because it is not required as we do a Tarjan in LandmarkStorage again. And we could do this TarjanSCC also edge-based and then use the created subnetworks instead of relying on the work of PrepareRoutingSubnetwork (which could even use a different minimum network size). 
I tried this but as everything regarding landmarks is still node-based this opened up a rabbit hole (required to change the stored LM weights to edge-based and maybe even the LM query to be clean). IMO this is a separate issue and it is not clear if this would improve something as memory usage could increase (higher edge key than nodes?). So I decided to keep using the node-based Tarjan but use the subnetwork EncodedValue from PrepareRoutingSubnetwork that is also correct regarding turn restrictions. So basically what we did before but now cleaner and based on the Profile not FlagEncoder only.

Patch:
diff --git a/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java b/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java
index f5418f4e4..5829e33ad 100644
--- a/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java
+++ b/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java
@@ -19,7 +19,6 @@ package com.graphhopper.routing;
 
 import com.carrotsearch.hppc.IntObjectMap;
 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;
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 4e2fbf244..d21d55578 100644
--- a/core/src/main/java/com/graphhopper/routing/lm/LandmarkStorage.java
+++ b/core/src/main/java/com/graphhopper/routing/lm/LandmarkStorage.java
@@ -20,24 +20,30 @@ package com.graphhopper.routing.lm;
 import com.carrotsearch.hppc.IntArrayList;
 import com.carrotsearch.hppc.IntHashSet;
 import com.carrotsearch.hppc.IntObjectMap;
-import com.carrotsearch.hppc.IntSet;
 import com.carrotsearch.hppc.predicates.IntObjectPredicate;
 import com.carrotsearch.hppc.procedures.IntObjectProcedure;
 import com.graphhopper.coll.MapEntry;
 import com.graphhopper.routing.DijkstraBidirectionRef;
 import com.graphhopper.routing.SPTEntry;
 import com.graphhopper.routing.ev.BooleanEncodedValue;
+import com.graphhopper.routing.ev.Subnetwork;
 import com.graphhopper.routing.subnetwork.SubnetworkStorage;
 import com.graphhopper.routing.subnetwork.TarjanSCC;
 import com.graphhopper.routing.subnetwork.TarjanSCC.ConnectedComponents;
-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.TraversalMode;
 import com.graphhopper.routing.util.spatialrules.SpatialRule;
 import com.graphhopper.routing.util.spatialrules.SpatialRuleLookup;
 import com.graphhopper.routing.util.spatialrules.SpatialRuleSet;
 import com.graphhopper.routing.weighting.ShortestWeighting;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.*;
-import com.graphhopper.util.*;
+import com.graphhopper.util.EdgeIteratorState;
+import com.graphhopper.util.GHUtility;
+import com.graphhopper.util.Helper;
+import com.graphhopper.util.StopWatch;
 import com.graphhopper.util.exceptions.ConnectionNotFoundException;
 import com.graphhopper.util.shapes.GHPoint;
 import org.slf4j.Logger;
@@ -237,29 +243,37 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
 
         byte[] subnetworks = new byte[graph.getNodes()];
         Arrays.fill(subnetworks, (byte) UNSET_SUBNETWORK);
-        EdgeFilter tarjanFilter = EdgeFilter.ALL_EDGES;
-        IntHashSet blockedEdges = new IntHashSet();
 
-        // the ruleLookup splits certain areas from each other but avoids making this a permanent change so that other algorithms still can route through these regions.
+        String snKey = Subnetwork.key(lmConfig.getName());
+        // TODO We could use EdgeBasedTarjanSCC instead of node-based TarjanSCC here to get the small networks directly,
+        //  instead of using the subnetworkEnc from PrepareRoutingSubnetworks.
+        if (!graph.getEncodingManager().hasEncodedValue(snKey))
+            throw new IllegalArgumentException("EncodedValue '" + snKey + "' does not exist. For Landmarks this is " +
+                    "currently required (also used in PrepareRoutingSubnetworks). See #2256");
+
+        // Exclude edges that we previously marked in PrepareRoutingSubnetworks to avoid problems like "connection not found".
+        final BooleanEncodedValue edgeInSubnetworkEnc = graph.getEncodingManager().getBooleanEncodedValue(snKey);
+        final IntHashSet blockedEdges;
+        // the ruleLookup splits certain areas from each other but avoids making this a permanent change so that other
+        // algorithms still can route through these regions. This is done to increase the density of landmarks for an
+        // area like Europe+Asia, which improves the query speed.
         if (ruleLookup != null && !ruleLookup.getRules().isEmpty()) {
             StopWatch sw = new StopWatch().start();
             blockedEdges = findBorderEdgeIds(ruleLookup);
-            tarjanFilter = new SimpleBlockedEdgesFilter(blockedEdges);
             if (logDetails)
                 LOGGER.info("Made " + blockedEdges.size() + " edges inaccessible. Calculated country cut in " + sw.stop().getSeconds() + "s, " + Helper.getMemInfo());
+        } else {
+            blockedEdges = new IntHashSet();
         }
 
-        StopWatch sw = new StopWatch().start();
+        EdgeFilter accessFilter = edge -> !edge.get(edgeInSubnetworkEnc) && !blockedEdges.contains(edge.getEdge());
+        EdgeFilter tarjanFilter = edge -> accessFilter.accept(edge) && Double.isFinite(weighting.calcEdgeWeightWithAccess(edge, false));
 
-        // we cannot reuse the components calculated in PrepareRoutingSubnetworks as the edgeIds changed in between (called graph.optimize)
-        // also calculating subnetworks from scratch makes bigger problems when working with many oneways
-        final EdgeFilter tmpFilter = tarjanFilter;
-        EdgeFilter edgeFilter = edge -> AccessFilter.outEdges(encoder.getAccessEnc()).accept(edge) && tmpFilter.accept(edge);
-        ConnectedComponents graphComponents = TarjanSCC.findComponents(graph, edgeFilter, true);
+        StopWatch sw = new StopWatch().start();
+        ConnectedComponents graphComponents = TarjanSCC.findComponents(graph, tarjanFilter, true);
         if (logDetails)
             LOGGER.info("Calculated " + graphComponents.getComponents().size() + " subnetworks via tarjan in " + sw.stop().getSeconds() + "s, " + Helper.getMemInfo());
 
-        EdgeExplorer tmpExplorer = graph.createEdgeExplorer(new RequireBothDirectionsEdgeFilter(encoder));
         String additionalInfo = "";
         // guess the factor
         if (factor <= 0) {
@@ -268,7 +282,7 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
             // see estimateMaxWeight. If we pick the distance too big for small areas this could lead to (slightly)
             // suboptimal routes as there will be too big rounding errors. But picking it too small is bad for performance
             // e.g. for Germany at least 1500km is very important otherwise speed is at least twice as slow e.g. for 1000km
-            double maxWeight = estimateMaxWeight(tmpExplorer, graphComponents.getComponents(), blockedEdges);
+            double maxWeight = estimateMaxWeight(graphComponents.getComponents(), accessFilter);
             setMaximumWeight(maxWeight);
             additionalInfo = ", maxWeight:" + maxWeight + " from quick estimation";
         }
@@ -289,16 +303,14 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
             // ensure start node is reachable from both sides and no subnetwork is associated
             for (; index >= 0; index--) {
                 int nextStartNode = subnetworkIds.get(index);
-                if (subnetworks[nextStartNode] == UNSET_SUBNETWORK
-                        && GHUtility.count(tmpExplorer.setBaseNode(nextStartNode)) > 0) {
-
+                if (subnetworks[nextStartNode] == UNSET_SUBNETWORK) {
                     if (logDetails) {
                         GHPoint p = createPoint(graph, nextStartNode);
                         LOGGER.info("start node: " + nextStartNode + " (" + p + ") subnetwork " + index + ", subnetwork size: " + subnetworkIds.size()
                                 + ", " + Helper.getMemInfo() + ((ruleLookup == null) ? "" : " area:" + ruleLookup.lookupRules(p.lat, p.lon).getRules()));
                     }
 
-                    if (createLandmarksForSubnetwork(nextStartNode, subnetworks, blockedEdges))
+                    if (createLandmarksForSubnetwork(nextStartNode, subnetworks, accessFilter))
                         break;
                 }
             }
@@ -340,9 +352,10 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
     /**
      * This method returns the maximum weight for the graph starting from the landmarks
      */
-    private double estimateMaxWeight(EdgeExplorer requireBothDirExplorer, List<IntArrayList> graphComponents, IntHashSet blockedEdges) {
+    private double estimateMaxWeight(List<IntArrayList> graphComponents, EdgeFilter accessFilter) {
         double maxWeight = 0;
         int searchedSubnetworks = 0;
+        Random random = new Random(0);
         // the maximum weight can only be an approximation so there is only a tiny improvement when we would do this for
         // all landmarks. See #2027 (1st commit) where only 1 landmark was sufficient when multiplied with 1.01 at the end
         // TODO instead of calculating the landmarks again here we could store them in landmarkIDs and do this for all here
@@ -352,25 +365,26 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
                 continue;
 
             searchedSubnetworks++;
-            int index = subnetworkIds.size() - 1;
-            for (; index >= 0; index--) {
+            int maxRetries = Math.max(subnetworkIds.size(), 100);
+            for (int retry = 0; retry < maxRetries; retry++) {
+                int index = random.nextInt(subnetworkIds.size());
                 int nextStartNode = subnetworkIds.get(index);
-                if (GHUtility.count(requireBothDirExplorer.setBaseNode(nextStartNode)) > 0) {
-                    LandmarkExplorer explorer = findLandmarks(tmpLandmarkNodeIds, nextStartNode, blockedEdges, "estimate " + index);
-                    if (explorer.getFromCount() < minimumNodes)
-                        continue;
-
-                    // starting
-                    for (int lmIdx = 0; lmIdx < tmpLandmarkNodeIds.length; lmIdx++) {
-                        int lmNodeId = tmpLandmarkNodeIds[lmIdx];
-                        explorer = new LandmarkExplorer(graph, this, weighting, traversalMode, true);
-                        explorer.setStartNode(lmNodeId);
-                        explorer.setFilter(blockedEdges, encoder.getAccessEnc(), true, true);
-                        explorer.runAlgo();
-                        maxWeight = Math.max(maxWeight, explorer.getLastEntry().weight);
-                    }
-                    break;
+                LandmarkExplorer explorer = findLandmarks(tmpLandmarkNodeIds, nextStartNode, accessFilter, "estimate " + index);
+                if (explorer.getFromCount() < minimumNodes) {
+                    LOGGER.error("method findLandmarks for " + createPoint(graph, nextStartNode) + " (" + nextStartNode + ")"
+                            + " resulted in too few visited nodes: " + explorer.getFromCount() + " vs expected minimum " + minimumNodes + ", see #2256");
+                    continue;
+                }
+
+                // starting
+                for (int lmIdx = 0; lmIdx < tmpLandmarkNodeIds.length; lmIdx++) {
+                    int lmNodeId = tmpLandmarkNodeIds[lmIdx];
+                    explorer = new LandmarkExplorer(graph, this, weighting, traversalMode, accessFilter, false);
+                    explorer.setStartNode(lmNodeId);
+                    explorer.runAlgo();
+                    maxWeight = Math.max(maxWeight, explorer.getLastEntry().weight);
                 }
+                break;
             }
         }
 
@@ -387,7 +401,7 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
      *
      * @return landmark mapping
      */
-    private boolean createLandmarksForSubnetwork(final int startNode, final byte[] subnetworks, IntHashSet blockedEdges) {
+    private boolean createLandmarksForSubnetwork(final int startNode, final byte[] subnetworks, EdgeFilter accessFilter) {
         final int subnetworkId = landmarkIDs.size();
         int[] tmpLandmarkNodeIds = new int[landmarks];
         int logOffset = Math.max(1, landmarks / 2);
@@ -418,7 +432,7 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
         if (pickedPrecalculatedLandmarks) {
             LOGGER.info("Picked " + tmpLandmarkNodeIds.length + " landmark suggestions, skip finding landmarks");
         } else {
-            LandmarkExplorer explorer = findLandmarks(tmpLandmarkNodeIds, startNode, blockedEdges, "create");
+            LandmarkExplorer explorer = findLandmarks(tmpLandmarkNodeIds, startNode, accessFilter, "create");
             if (explorer.getFromCount() < minimumNodes) {
                 // too small subnetworks are initialized with special id==0
                 explorer.setSubnetworks(subnetworks, UNCLEAR_SUBNETWORK);
@@ -434,9 +448,8 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
                 throw new RuntimeException("Thread was interrupted for landmark " + lmIdx);
             }
             int lmNodeId = tmpLandmarkNodeIds[lmIdx];
-            LandmarkExplorer explorer = new LandmarkExplorer(graph, this, weighting, traversalMode, true);
+            LandmarkExplorer explorer = new LandmarkExplorer(graph, this, weighting, traversalMode, accessFilter, false);
             explorer.setStartNode(lmNodeId);
-            explorer.setFilter(blockedEdges, encoder.getAccessEnc(), false, true);
             explorer.runAlgo();
             explorer.initLandmarkWeights(lmIdx, lmNodeId, LM_ROW_LENGTH, FROM_OFFSET);
 
@@ -446,9 +459,8 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
                     return false;
             }
 
-            explorer = new LandmarkExplorer(graph, this, weighting, traversalMode, false);
+            explorer = new LandmarkExplorer(graph, this, weighting, traversalMode, accessFilter, true);
             explorer.setStartNode(lmNodeId);
-            explorer.setFilter(blockedEdges, encoder.getAccessEnc(), true, false);
             explorer.runAlgo();
             explorer.initLandmarkWeights(lmIdx, lmNodeId, LM_ROW_LENGTH, TO_OFFSET);
 
@@ -731,21 +743,19 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
         return graph.getNodes();
     }
 
-    private LandmarkExplorer findLandmarks(int[] landmarkNodeIdsToReturn, int startNode, IntHashSet blockedEdges, String info) {
+    private LandmarkExplorer findLandmarks(int[] landmarkNodeIdsToReturn, int startNode, EdgeFilter accessFilter, String info) {
         int logOffset = Math.max(1, landmarkNodeIdsToReturn.length / 2);
         // 1a) pick landmarks via special weighting for a better geographical spreading
         Weighting initWeighting = lmSelectionWeighting;
-        LandmarkExplorer explorer = new LandmarkExplorer(graph, this, initWeighting, traversalMode, true);
+        LandmarkExplorer explorer = new LandmarkExplorer(graph, this, initWeighting, traversalMode, accessFilter, false);
         explorer.setStartNode(startNode);
-        explorer.setFilter(blockedEdges, encoder.getAccessEnc(), true, true);
         explorer.runAlgo();
 
         if (explorer.getFromCount() >= minimumNodes) {
             // 1b) we have one landmark, now determine the other landmarks
             landmarkNodeIdsToReturn[0] = explorer.getLastEntry().adjNode;
             for (int lmIdx = 0; lmIdx < landmarkNodeIdsToReturn.length - 1; lmIdx++) {
-                explorer = new LandmarkExplorer(graph, this, initWeighting, traversalMode, true);
-                explorer.setFilter(blockedEdges, encoder.getAccessEnc(), true, true);
+                explorer = new LandmarkExplorer(graph, this, initWeighting, traversalMode, accessFilter, false);
                 // set all current landmarks as start so that the next getLastNode is hopefully a "far away" node
                 for (int j = 0; j < lmIdx + 1; j++) {
                     explorer.setStartNode(landmarkNodeIdsToReturn[j]);
@@ -766,42 +776,38 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
      * It derives from DijkstraBidirectionRef, but is only used as forward or backward search.
      */
     private static class LandmarkExplorer extends DijkstraBidirectionRef {
-        // edgeFilter is null initially and must be set via setEdgeFilter
-        private EdgeFilter edgeFilter;
-        // todo: rename 'from' to 'reverse' (and flip it) ? 'from' is used in many places for node ids and 'reverse' is mostly used for the direction
-        private boolean from;
+        private EdgeFilter accessFilter;
+        private final boolean reverse;
         private final LandmarkStorage lms;
         private SPTEntry lastEntry;
 
-        public LandmarkExplorer(Graph g, LandmarkStorage lms, Weighting weighting, TraversalMode tMode, boolean from) {
+        public LandmarkExplorer(Graph g, LandmarkStorage lms, Weighting weighting, TraversalMode tMode, EdgeFilter accessFilter, boolean reverse) {
             super(g, weighting, tMode);
+            this.accessFilter = accessFilter;
             this.lms = lms;
-            this.from = from;
+            this.reverse = reverse;
             // set one of the bi directions as already finished
-            if (from)
-                finishedTo = true;
-            else
+            if (reverse)
                 finishedFrom = true;
+            else
+                finishedTo = true;
+
             // no path should be calculated
             setUpdateBestPath(false);
         }
 
-        public void setFilter(IntHashSet set, BooleanEncodedValue accessEnc, boolean bwd, boolean fwd) {
-            edgeFilter = new BlockedEdgesFilter(accessEnc, bwd, fwd, set);
-        }
-
         public void setStartNode(int startNode) {
-            if (from)
-                initFrom(startNode, 0);
-            else
+            if (reverse)
                 initTo(startNode, 0);
+            else
+                initFrom(startNode, 0);
         }
 
         @Override
         protected double calcWeight(EdgeIteratorState iter, SPTEntry currEdge, boolean reverse) {
-            if (!edgeFilter.accept(iter))
+            if (!accessFilter.accept(iter))
                 return Double.POSITIVE_INFINITY;
-            return GHUtility.calcWeightWithTurnWeight(weighting, iter, reverse, currEdge.edge) + currEdge.getWeightOfVisitedPath();
+            return GHUtility.calcWeightWithTurnWeightWithAccess(weighting, iter, reverse, currEdge.edge) + currEdge.getWeightOfVisitedPath();
         }
 
         int getFromCount() {
@@ -820,12 +826,12 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
 
         @Override
         public boolean finished() {
-            if (from) {
-                lastEntry = currFrom;
-                return finishedFrom;
-            } else {
+            if (reverse) {
                 lastEntry = currTo;
                 return finishedTo;
+            } else {
+                lastEntry = currFrom;
+                return finishedFrom;
             }
         }
 
@@ -834,7 +840,7 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
                 throw new IllegalStateException("Too many subnetworks " + subnetworkId);
 
             final AtomicBoolean failed = new AtomicBoolean(false);
-            IntObjectMap<SPTEntry> map = from ? bestWeightMapFrom : bestWeightMapTo;
+            IntObjectMap<SPTEntry> map = reverse ? bestWeightMapTo : bestWeightMapFrom;
             map.forEach(new IntObjectPredicate<SPTEntry>() {
                 @Override
                 public boolean apply(int nodeId, SPTEntry value) {
@@ -858,7 +864,7 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
         }
 
         public void initLandmarkWeights(final int lmIdx, int lmNodeId, final long rowSize, final int offset) {
-            IntObjectMap<SPTEntry> map = from ? bestWeightMapFrom : bestWeightMapTo;
+            IntObjectMap<SPTEntry> map = reverse ? bestWeightMapTo : bestWeightMapFrom;
             final AtomicInteger maxedout = new AtomicInteger(0);
             final Map.Entry<Double, Double> finalMaxWeight = new MapEntry<>(0d, 0d);
 
@@ -893,58 +899,4 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
     static GHPoint createPoint(Graph graph, int nodeId) {
         return new GHPoint(graph.getNodeAccess().getLat(nodeId), graph.getNodeAccess().getLon(nodeId));
     }
-
-    final static class RequireBothDirectionsEdgeFilter implements EdgeFilter {
-
-        private BooleanEncodedValue accessEnc;
-
-        public RequireBothDirectionsEdgeFilter(FlagEncoder flagEncoder) {
-            this.accessEnc = flagEncoder.getAccessEnc();
-        }
-
-        @Override
-        public boolean accept(EdgeIteratorState edgeState) {
-            return edgeState.get(accessEnc) && edgeState.getReverse(accessEnc);
-        }
-    }
-
-    private static class SimpleBlockedEdgesFilter implements EdgeFilter {
-        private final IntSet blockedEdges;
-
-        SimpleBlockedEdgesFilter(IntSet blockedEdges) {
-            this.blockedEdges = blockedEdges;
-        }
-
-        @Override
-        public boolean accept(EdgeIteratorState edgeState) {
-            return !blockedEdges.contains(edgeState.getEdge());
-        }
-    }
-
-    private static class BlockedEdgesFilter implements EdgeFilter {
-        private final IntHashSet blockedEdges;
-        private final BooleanEncodedValue accessEnc;
-        private final boolean fwd;
-        private final boolean bwd;
-
-        public BlockedEdgesFilter(BooleanEncodedValue accessEnc, boolean bwd, boolean fwd, IntHashSet blockedEdges) {
-            this.accessEnc = accessEnc;
-            this.fwd = fwd;
-            this.bwd = bwd;
-            this.blockedEdges = blockedEdges;
-        }
-
-        @Override
-        public final boolean accept(EdgeIteratorState iter) {
-            boolean blocked = blockedEdges.contains(iter.getEdge());
-            if (blocked)
-                return false;
-            return fwd && iter.get(accessEnc) || bwd && iter.getReverse(accessEnc);
-        }
-
-        @Override
-        public String toString() {
-            return accessEnc + ", bwd:" + bwd + ", fwd:" + fwd;
-        }
-    }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/util/FiniteWeightFilter.java b/core/src/main/java/com/graphhopper/routing/util/FiniteWeightFilter.java
index 3d915dace..e5ce630fa 100644
--- a/core/src/main/java/com/graphhopper/routing/util/FiniteWeightFilter.java
+++ b/core/src/main/java/com/graphhopper/routing/util/FiniteWeightFilter.java
@@ -32,7 +32,7 @@ public class FiniteWeightFilter implements EdgeFilter {
     }
 
     @Override
-    public boolean accept(EdgeIteratorState edgeState) {
+    public final boolean accept(EdgeIteratorState edgeState) {
         return Double.isFinite(weighting.calcEdgeWeightWithAccess(edgeState, false)) ||
                 Double.isFinite(weighting.calcEdgeWeightWithAccess(edgeState, true));
     }
diff --git a/core/src/main/java/com/graphhopper/util/GHUtility.java b/core/src/main/java/com/graphhopper/util/GHUtility.java
index f96c08c42..e5ad9a15f 100644
--- a/core/src/main/java/com/graphhopper/util/GHUtility.java
+++ b/core/src/main/java/com/graphhopper/util/GHUtility.java
@@ -679,7 +679,7 @@ public class GHUtility {
 
         BooleanEncodedValue accessEnc = encoder.getAccessEnc();
         DecimalEncodedValue avSpeedEnc = encoder.getAverageSpeedEnc();
-        edge.set(accessEnc, fwd).setReverse(accessEnc, bwd);
+        edge.set(accessEnc, fwd, bwd);
         if (fwd)
             edge.set(avSpeedEnc, averageSpeed);
         if (bwd && avSpeedEnc.isStoreTwoDirections())
diff --git a/web-bundle/src/main/resources/com/graphhopper/maps/js/main-template.js b/web-bundle/src/main/resources/com/graphhopper/maps/js/main-template.js
index 8f63c6d16..95f542a8c 100644
--- a/web-bundle/src/main/resources/com/graphhopper/maps/js/main-template.js
+++ b/web-bundle/src/main/resources/com/graphhopper/maps/js/main-template.js
@@ -81,14 +81,18 @@ $(document).ready(function (e) {
     cmEditor.validListener = function(valid) {
         $("#custom-model-search-button").prop('disabled', !valid);
     };
+    ghRequest.cmEditor = cmEditor;
+    ghRequest.cmEditorActive = false;
     $("#custom-model-button").click(function() {
         $("#custom-model-box").toggle();
+        ghRequest.cmEditorActive = !ghRequest.cmEditorActive;
         // avoid default action, so use a different search button
         $("#searchButton").toggle();
         mapLayer.adjustMapSize();
         cmEditor.cm.refresh();
         cmEditor.cm.focus();
-        cmEditor.cm.setCursor(cmEditor.cm.lineCount())
+        cmEditor.cm.setCursor(cmEditor.cm.lineCount());
+        sendCustomData();
     });
     function showCustomModelExample() {
         cmEditor.value =
@@ -115,72 +119,22 @@ $(document).ready(function (e) {
         cmEditor.cm.execCommand('selectAll');
         cmEditor.cm.refresh();
     }
-    $("#custom-model-example").click(function() {
+    $("#custom-model-example").click(function () {
         showCustomModelExample();
         return false;
     });
 
-    var sendCustomData = function() {
-       mapLayer.clearElevation();
-       mapLayer.clearLayers();
-       flagAll();
-
-       var infoDiv = $("#info");
-       infoDiv.empty();
-       infoDiv.show();
-       var routeResultsDiv = $("<div class='route_results'/>");
-       infoDiv.append(routeResultsDiv);
-       routeResultsDiv.html('<img src="img/indicator.gif"/> Search Route ...');
-
-       var points = [];
-       for(var idx = 0; idx < ghRequest.route.size(); idx++) {
-           var point = ghRequest.route.getIndex(idx);
-           if (point.isResolved()) {
-               points.push([point.lng, point.lat]);
-           } else {
-               routeResultsDiv.html("Unresolved points");
-               return;
-           }
-       }
-
-       var customModel = cmEditor.jsonObj;
-       if (customModel === null) {
-           routeResultsDiv.html("Invalid custom model");
-           return;
-       }
-
-       const details = cmEditor.getUsedCategories();
-       details.push('average_speed');
-       details.push('distance');
-       details.push('time');
-       var request = {
-           points: points,
-           points_encoded: false,
-           elevation: ghRequest.api_params.elevation,
-           profile: ghRequest.api_params.profile,
-           custom_model: customModel,
-           "ch.disable": true,
-           details: details
-       }
-
-       $.ajax({
-           url: host + "/route",
-           type: "POST",
-           contentType: 'application/json; charset=utf-8',
-           dataType: "json",
-           data: JSON.stringify(request),
-           success: createRouteCallback(ghRequest, routeResultsDiv, "", true),
-           error: function(err) {
-               routeResultsDiv.html("Error response: cannot process input");
-               var json = JSON.parse(err.responseText);
-               createRouteCallback(ghRequest, routeResultsDiv, "", true)(json);
-           }
-        });
+    var sendCustomData = function () {
+        ghRequest.ignoreCustomErrors = false;
+        mySubmit();
     };
 
-    // todo: prevent this as long as custom model is invalid?
-    // so far it is useful so we can send the request regardless of the invalidation
-    cmEditor.setExtraKey('Ctrl-Enter', sendCustomData);
+    var sendCustomDataIgnoreErrors = function () {
+        ghRequest.ignoreCustomErrors = true;
+        mySubmit();
+    }
+
+    cmEditor.setExtraKey('Ctrl-Enter', sendCustomDataIgnoreErrors);
     $("#custom-model-search-button").click(sendCustomData);
 
     if (isProduction())
@@ -903,9 +857,61 @@ function routeLatLng(request, doQuery) {
     if (buttonToSelectId)
     $("button#" + buttonToSelectId.toLowerCase()).addClass("selectprofile");
 
-    var urlForAPI = request.createURL();
     routeResultsDiv.html('<img src="img/indicator.gif"/> Search Route ...');
-    request.doRequest(urlForAPI, createRouteCallback(request, routeResultsDiv, urlForHistory, doZoom));
+    if (request.cmEditorActive) {
+        doCustomRequest(request, routeResultsDiv);
+    } else {
+        var urlForAPI = request.createURL();
+        request.doRequest(urlForAPI, createRouteCallback(request, routeResultsDiv, urlForHistory, doZoom));
+    }
+}
+
+function doCustomRequest(request, routeResultsDiv) {
+    var customModelErrors = request.cmEditor.getCurrentErrors(request.cmEditor.cm.getValue(), request.cmEditor.cm);
+    if (customModelErrors.errors.length !== 0) {
+        if (request.ignoreCustomErrors)
+            console.warn('sending custom model that is likely invalid');
+        else {
+            routeResultsDiv.html('');
+            routeResultsDiv.append("<div class='error'>Invalid custom model</div>");
+            return;
+        }
+    }
+    var customModel = request.cmEditor.jsonObj;
+    var details = request.cmEditor.getUsedCategories();
+    details.push('average_speed');
+    details.push('distance');
+    details.push('time');
+
+    var points = [];
+    for (var idx = 0; idx < ghRequest.route.size(); idx++) {
+        var point = ghRequest.route.getIndex(idx);
+        points.push([point.lng, point.lat]);
+    }
+
+    var reqBody = {
+        points: points,
+        points_encoded: false,
+        elevation: ghRequest.api_params.elevation,
+        profile: ghRequest.api_params.profile,
+        custom_model: customModel,
+        "ch.disable": true,
+        details: details
+    }
+
+    $.ajax({
+        url: host + "/route",
+        type: "POST",
+        contentType: 'application/json; charset=utf-8',
+        dataType: "json",
+        data: JSON.stringify(reqBody),
+        success: createRouteCallback(ghRequest, routeResultsDiv, "", true),
+        error: function (err) {
+            routeResultsDiv.html("Error response: cannot process input");
+            var json = JSON.parse(err.responseText);
+            createRouteCallback(ghRequest, routeResultsDiv, "", true)(json);
+        }
+    });
 }
 
 function mySubmit() {


Test patch:
diff --git a/core/src/test/java/com/graphhopper/GraphHopperTest.java b/core/src/test/java/com/graphhopper/GraphHopperTest.java
index a3e9e3f8e..399503511 100644
--- a/core/src/test/java/com/graphhopper/GraphHopperTest.java
+++ b/core/src/test/java/com/graphhopper/GraphHopperTest.java
@@ -1551,16 +1551,16 @@ public class GraphHopperTest {
 
         // LM (should be the same as flex, but with less visited nodes!)
         testCrossQueryAssert(profile1, hopper, 528.3, 74, false);
-        testCrossQueryAssert(profile2, hopper, 635.8, 84, false);
+        testCrossQueryAssert(profile2, hopper, 635.8, 82, false);
         // this is actually interesting: the number of visited nodes *increases* once again (while it strictly decreases
         // with rising distance factor for flex): cross-querying 'works', but performs *worse*, because the landmarks
         // were not customized for the weighting in use. Creating a separate LM preparation for profile3 yields 74
-        // instead of 124 visited nodes (not shown here)
-        testCrossQueryAssert(profile3, hopper, 815.2, 128, false);
+        testCrossQueryAssert(profile3, hopper, 815.2, 138, false);
     }
 
     private void testCrossQueryAssert(String profile, GraphHopper hopper, double expectedWeight, int expectedVisitedNodes, boolean disableLM) {
-        GHResponse response = hopper.route(new GHRequest(43.727687, 7.418737, 43.74958, 7.436566).setProfile(profile).putHint("lm.disable", disableLM));
+        GHResponse response = hopper.route(new GHRequest(43.727687, 7.418737, 43.74958, 7.436566).
+                setProfile(profile).putHint("lm.disable", disableLM));
         assertEquals(expectedWeight, response.getBest().getRouteWeight(), 0.1);
         int visitedNodes = response.getHints().getInt("visited_nodes.sum", 0);
         assertEquals(expectedVisitedNodes, visitedNodes);
diff --git a/core/src/test/java/com/graphhopper/routing/RandomizedRoutingTest.java b/core/src/test/java/com/graphhopper/routing/RandomizedRoutingTest.java
index 4283278c6..2213358f4 100644
--- a/core/src/test/java/com/graphhopper/routing/RandomizedRoutingTest.java
+++ b/core/src/test/java/com/graphhopper/routing/RandomizedRoutingTest.java
@@ -24,6 +24,7 @@ import com.graphhopper.Repeat;
 import com.graphhopper.RepeatRule;
 import com.graphhopper.routing.ch.CHRoutingAlgorithmFactory;
 import com.graphhopper.routing.ch.PrepareContractionHierarchies;
+import com.graphhopper.routing.ev.Subnetwork;
 import com.graphhopper.routing.lm.LMConfig;
 import com.graphhopper.routing.lm.PerfectApproximator;
 import com.graphhopper.routing.lm.PrepareLandmarks;
@@ -132,7 +133,7 @@ public class RandomizedRoutingTest {
         // fail sometimes for edge-based algorithms, #1631, but maybe we can should disable different fwd/bwd speeds
         // only for loops instead?
         encoder = new CarFlagEncoder(5, 5, maxTurnCosts);
-        encodingManager = EncodingManager.create(encoder);
+        encodingManager = new EncodingManager.Builder().add(encoder).add(Subnetwork.create("car")).build();
         graph = new GraphBuilder(encodingManager)
                 .setCHConfigStrings("p1|car|fastest|node", "p2|car|fastest|edge")
                 .setDir(dir)
@@ -140,7 +141,7 @@ public class RandomizedRoutingTest {
         turnCostStorage = graph.getTurnCostStorage();
         chConfigs = graph.getCHConfigs();
         // important: for LM preparation we need to use a weighting without turn costs #1960
-        lmConfig = new LMConfig("config", chConfigs.get(0).getWeighting());
+        lmConfig = new LMConfig("car", chConfigs.get(0).getWeighting());
         weighting = traversalMode.isEdgeBased() ? chConfigs.get(1).getWeighting() : chConfigs.get(0).getWeighting();
     }
 
diff --git a/core/src/test/java/com/graphhopper/routing/lm/LMApproximatorTest.java b/core/src/test/java/com/graphhopper/routing/lm/LMApproximatorTest.java
index d78828bf3..9162d8a56 100644
--- a/core/src/test/java/com/graphhopper/routing/lm/LMApproximatorTest.java
+++ b/core/src/test/java/com/graphhopper/routing/lm/LMApproximatorTest.java
@@ -22,6 +22,7 @@ import com.graphhopper.Repeat;
 import com.graphhopper.RepeatRule;
 import com.graphhopper.routing.Dijkstra;
 import com.graphhopper.routing.Path;
+import com.graphhopper.routing.ev.Subnetwork;
 import com.graphhopper.routing.util.CarFlagEncoder;
 import com.graphhopper.routing.util.AccessFilter;
 import com.graphhopper.routing.util.EncodingManager;
@@ -50,7 +51,7 @@ public class LMApproximatorTest {
     private void run(long seed) {
         Directory dir = new RAMDirectory();
         CarFlagEncoder encoder = new CarFlagEncoder(5, 5, 1);
-        EncodingManager encodingManager = EncodingManager.create(encoder);
+        EncodingManager encodingManager = new EncodingManager.Builder().add(encoder).add(Subnetwork.create("car")).build();
         GraphHopperStorage graph = new GraphBuilder(encodingManager).setDir(dir).withTurnCosts(true).create();
 
         Random rnd = new Random(seed);
@@ -59,7 +60,7 @@ public class LMApproximatorTest {
 
         Weighting weighting = new FastestWeighting(encoder);
 
-        PrepareLandmarks lm = new PrepareLandmarks(dir, graph, new LMConfig("c", weighting), 16);
+        PrepareLandmarks lm = new PrepareLandmarks(dir, graph, new LMConfig("car", weighting), 16);
         lm.setMaximumWeight(10000);
         lm.doWork();
         LandmarkStorage landmarkStorage = lm.getLandmarkStorage();
diff --git a/core/src/test/java/com/graphhopper/routing/lm/LMIssueTest.java b/core/src/test/java/com/graphhopper/routing/lm/LMIssueTest.java
index acb4671cb..6cf4f6beb 100644
--- a/core/src/test/java/com/graphhopper/routing/lm/LMIssueTest.java
+++ b/core/src/test/java/com/graphhopper/routing/lm/LMIssueTest.java
@@ -20,6 +20,7 @@ package com.graphhopper.routing.lm;
 
 import com.graphhopper.routing.*;
 import com.graphhopper.routing.ev.DecimalEncodedValue;
+import com.graphhopper.routing.ev.Subnetwork;
 import com.graphhopper.routing.util.CarFlagEncoder;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.FlagEncoder;
@@ -56,7 +57,7 @@ public class LMIssueTest {
     public void init() {
         dir = new RAMDirectory();
         encoder = new CarFlagEncoder(5, 5, 1);
-        EncodingManager encodingManager = EncodingManager.create(encoder);
+        EncodingManager encodingManager = new EncodingManager.Builder().add(encoder).add(Subnetwork.create("car")).build();
         graph = new GraphBuilder(encodingManager)
                 .setDir(dir)
                 .create();
@@ -65,7 +66,7 @@ public class LMIssueTest {
 
     private void preProcessGraph() {
         graph.freeze();
-        lm = new PrepareLandmarks(dir, graph, new LMConfig("c", weighting), 16);
+        lm = new PrepareLandmarks(dir, graph, new LMConfig("car", weighting), 16);
         lm.setMaximumWeight(10000);
         lm.doWork();
     }
diff --git a/core/src/test/java/com/graphhopper/routing/lm/LandmarkStorageTest.java b/core/src/test/java/com/graphhopper/routing/lm/LandmarkStorageTest.java
index ef2c8286e..d5029a89e 100644
--- a/core/src/test/java/com/graphhopper/routing/lm/LandmarkStorageTest.java
+++ b/core/src/test/java/com/graphhopper/routing/lm/LandmarkStorageTest.java
@@ -18,6 +18,9 @@
 package com.graphhopper.routing.lm;
 
 import com.graphhopper.routing.RoutingAlgorithmTest;
+import com.graphhopper.routing.ev.BooleanEncodedValue;
+import com.graphhopper.routing.ev.Subnetwork;
+import com.graphhopper.routing.subnetwork.PrepareRoutingSubnetworks;
 import com.graphhopper.routing.util.CarFlagEncoder;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.FlagEncoder;
@@ -26,6 +29,7 @@ import com.graphhopper.routing.util.spatialrules.SpatialRule;
 import com.graphhopper.routing.util.spatialrules.SpatialRuleLookup;
 import com.graphhopper.routing.util.spatialrules.SpatialRuleSet;
 import com.graphhopper.routing.weighting.FastestWeighting;
+import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.DataAccess;
 import com.graphhopper.storage.Directory;
 import com.graphhopper.storage.GraphHopperStorage;
@@ -51,11 +55,13 @@ public class LandmarkStorageTest {
     private GraphHopperStorage graph;
     private FlagEncoder encoder;
     private EncodingManager encodingManager;
+    private BooleanEncodedValue subnetworkEnc;
 
     @Before
     public void setUp() {
         encoder = new CarFlagEncoder();
-        encodingManager = EncodingManager.create(encoder);
+        subnetworkEnc = Subnetwork.create("car");
+        encodingManager = new EncodingManager.Builder().add(encoder).add(subnetworkEnc).build();
         graph = new GraphHopperStorage(new RAMDirectory(),
                 encodingManager, false);
         graph.create(1000);
@@ -126,17 +132,21 @@ public class LandmarkStorageTest {
         GHUtility.setSpeed(60, true, true, encoder, graph.edge(4, 5).setDistance(10.5));
         GHUtility.setSpeed(60, true, false, encoder, graph.edge(5, 6).setDistance(10.6));
 
-        LandmarkStorage storage = new LandmarkStorage(graph, new RAMDirectory(), new LMConfig("c1", new FastestWeighting(encoder)), 2);
+        Weighting weighting = new FastestWeighting(encoder);
+        // 1 means => 2 allowed edge keys => excludes the node 6
+        subnetworkRemoval(weighting, 1);
+
+        LandmarkStorage storage = new LandmarkStorage(graph, new RAMDirectory(), new LMConfig("car", weighting), 2);
         storage.setMinimumNodes(2);
         storage.createLandmarks();
         assertEquals(3, storage.getSubnetworksWithLandmarks());
         assertEquals("[2, 0]", Arrays.toString(storage.getLandmarks(1)));
-        assertEquals("[6, 4]", Arrays.toString(storage.getLandmarks(2)));
+        // do not include 6 as landmark!
+        assertEquals("[5, 4]", Arrays.toString(storage.getLandmarks(2)));
     }
 
     @Test
-    public void testWithSubnetworks2() {
-        // should not happen with subnetwork preparation
+    public void testWithStronglyConnectedComponent() {
         // 0 - 1 - 2 = 3 - 4
         GHUtility.setSpeed(60, true, true, encoder, graph.edge(0, 1).setDistance(10.1));
         GHUtility.setSpeed(60, true, true, encoder, graph.edge(1, 2).setDistance(10.2));
@@ -144,17 +154,30 @@ public class LandmarkStorageTest {
         GHUtility.setSpeed(60, true, false, encoder, graph.edge(3, 2).setDistance(10.2));
         GHUtility.setSpeed(60, true, true, encoder, graph.edge(3, 4).setDistance(10.4));
 
-        LandmarkStorage storage = new LandmarkStorage(graph, new RAMDirectory(), new LMConfig("c", new FastestWeighting(encoder)), 2);
+        Weighting weighting = new FastestWeighting(encoder);
+
+        // 3 nodes => 6 allowed edge keys but still do not exclude 3 & 4 as strongly connected and not a too small subnetwork!
+        subnetworkRemoval(weighting, 4);
+
+        LandmarkStorage storage = new LandmarkStorage(graph, new RAMDirectory(), new LMConfig("car", weighting), 2);
         storage.setMinimumNodes(3);
         storage.createLandmarks();
         assertEquals(2, storage.getSubnetworksWithLandmarks());
         assertEquals("[4, 0]", Arrays.toString(storage.getLandmarks(1)));
     }
 
+    private void subnetworkRemoval(Weighting weighting, int minNodeSize) {
+        // currently we rely on subnetwork removal in Landmark preparation, see #2256
+        // PrepareRoutingSubnetworks removes OSM bugs regarding turn restriction mapping which the node-based Tarjan in Landmark preparation can't
+        new PrepareRoutingSubnetworks(graph, Collections.singletonList(new PrepareRoutingSubnetworks.PrepareJob(subnetworkEnc, weighting))).
+                setMinNetworkSize(minNodeSize).
+                doWork();
+    }
+
     @Test
     public void testWithOnewaySubnetworks() {
-        // should not happen with subnetwork preparation
-        // create an indifferent problem: node 2 and 3 are part of two 'disconnected' subnetworks
+        // 0 -- 1 -> 2 -> 3
+        // 4 -- 5 ->/
         GHUtility.setSpeed(60, true, true, encoder, graph.edge(0, 1).setDistance(10.1));
         GHUtility.setSpeed(60, true, false, encoder, graph.edge(1, 2).setDistance(10.2));
         GHUtility.setSpeed(60, true, false, encoder, graph.edge(2, 3).setDistance(10.3));
@@ -162,34 +185,54 @@ public class LandmarkStorageTest {
         GHUtility.setSpeed(60, true, true, encoder, graph.edge(4, 5).setDistance(10.5));
         GHUtility.setSpeed(60, true, false, encoder, graph.edge(5, 2).setDistance(10.2));
 
-        LandmarkStorage storage = new LandmarkStorage(graph, new RAMDirectory(), new LMConfig("c", new FastestWeighting(encoder)), 2);
+        Weighting weighting = new FastestWeighting(encoder);
+        // 1 allowed node => 2 allowed edge keys (exclude 2 and 3 because they are separate too small oneway subnetworks)
+        subnetworkRemoval(weighting, 1);
+
+        LandmarkStorage storage = new LandmarkStorage(graph, new RAMDirectory(), new LMConfig("car", weighting), 2);
         storage.setMinimumNodes(2);
         storage.createLandmarks();
 
-        assertEquals(2, storage.getSubnetworksWithLandmarks());
-        assertEquals("[4, 0]", Arrays.toString(storage.getLandmarks(1)));
+        assertEquals(3, storage.getSubnetworksWithLandmarks());
+        assertEquals("[1, 0]", Arrays.toString(storage.getLandmarks(1)));
+        assertEquals("[5, 4]", Arrays.toString(storage.getLandmarks(2)));
     }
 
     @Test
-    public void testWeightingConsistence() {
+    public void testWeightingConsistence1() {
         // create an indifferent problem: shortest weighting can pass the speed==0 edge but fastest cannot (?)
-        GHUtility.setSpeed(60, true, true, encoder, graph.edge(0, 1).setDistance(10.1));
-        GHUtility.setSpeed(0.9, true, true, encoder, graph.edge(1, 2).setDistance(10));
-        GHUtility.setSpeed(60, true, true, encoder, graph.edge(2, 3).setDistance(10.3));
+        graph.edge(0, 1).setDistance(10.1).set(encoder.getAccessEnc(), true, true);
+        GHUtility.setSpeed(30, true, true, encoder, graph.edge(1, 2).setDistance(10));
+        graph.edge(2, 3).setDistance(10.1).set(encoder.getAccessEnc(), true, true);
 
-        LandmarkStorage storage = new LandmarkStorage(graph, new RAMDirectory(), new LMConfig("c", new FastestWeighting(encoder)), 2);
+        LandmarkStorage storage = new LandmarkStorage(graph, new RAMDirectory(), new LMConfig("car", new FastestWeighting(encoder)), 2);
         storage.setMinimumNodes(2);
         storage.createLandmarks();
 
         assertEquals(2, storage.getSubnetworksWithLandmarks());
+        assertEquals("[2, 1]", Arrays.toString(storage.getLandmarks(1)));
+    }
+
+    @Test
+    public void testWeightingConsistence2() {
+        GHUtility.setSpeed(30, true, true, encoder, graph.edge(0, 1).setDistance(10));
+        graph.edge(2, 3).setDistance(10.1).set(encoder.getAccessEnc(), true, true);
+        GHUtility.setSpeed(30, true, true, encoder, graph.edge(2, 3).setDistance(10));
+
+        LandmarkStorage storage = new LandmarkStorage(graph, new RAMDirectory(), new LMConfig("car", new FastestWeighting(encoder)), 2);
+        storage.setMinimumNodes(2);
+        storage.createLandmarks();
+
+        assertEquals(3, storage.getSubnetworksWithLandmarks());
         assertEquals("[1, 0]", Arrays.toString(storage.getLandmarks(1)));
+        assertEquals("[3, 2]", Arrays.toString(storage.getLandmarks(2)));
     }
 
     @Test
     public void testWithBorderBlocking() {
         RoutingAlgorithmTest.initBiGraph(graph, encoder);
 
-        LandmarkStorage storage = new LandmarkStorage(graph, new RAMDirectory(), new LMConfig("c", new FastestWeighting(encoder)), 2);
+        LandmarkStorage storage = new LandmarkStorage(graph, new RAMDirectory(), new LMConfig("car", new FastestWeighting(encoder)), 2);
         final SpatialRule ruleRight = new AbstractSpatialRule(Collections.<Polygon>emptyList()) {
             @Override
             public String getId() {
diff --git a/core/src/test/java/com/graphhopper/routing/lm/PrepareLandmarksTest.java b/core/src/test/java/com/graphhopper/routing/lm/PrepareLandmarksTest.java
index e9a1e18c9..2da3f447b 100644
--- a/core/src/test/java/com/graphhopper/routing/lm/PrepareLandmarksTest.java
+++ b/core/src/test/java/com/graphhopper/routing/lm/PrepareLandmarksTest.java
@@ -23,6 +23,7 @@ import com.graphhopper.routing.Path;
 import com.graphhopper.routing.RoutingAlgorithm;
 import com.graphhopper.routing.ev.BooleanEncodedValue;
 import com.graphhopper.routing.ev.DecimalEncodedValue;
+import com.graphhopper.routing.ev.Subnetwork;
 import com.graphhopper.routing.querygraph.QueryGraph;
 import com.graphhopper.routing.util.*;
 import com.graphhopper.routing.weighting.FastestWeighting;
@@ -64,7 +65,7 @@ public class PrepareLandmarksTest {
     public void setUp() {
         encoder = new CarFlagEncoder();
         tm = TraversalMode.NODE_BASED;
-        encodingManager = EncodingManager.create(encoder);
+        encodingManager = new EncodingManager.Builder().add(encoder).add(Subnetwork.create("car")).build();
         GraphHopperStorage tmp = new GraphHopperStorage(new RAMDirectory(),
                 encodingManager, false);
         tmp.create(1000);
@@ -103,7 +104,7 @@ public class PrepareLandmarksTest {
 
         int lm = 5, activeLM = 2;
         Weighting weighting = new FastestWeighting(encoder);
-        LMConfig lmConfig = new LMConfig("c", weighting);
+        LMConfig lmConfig = new LMConfig("car", weighting);
         LandmarkStorage store = new LandmarkStorage(graph, dir, lmConfig, lm);
         store.setMinimumNodes(2);
         store.createLandmarks();
@@ -192,7 +193,7 @@ public class PrepareLandmarksTest {
 
         Directory dir = new RAMDirectory(fileStr, true).create();
         Weighting weighting = new FastestWeighting(encoder);
-        LMConfig lmConfig = new LMConfig("c", weighting);
+        LMConfig lmConfig = new LMConfig("car", weighting);
         PrepareLandmarks plm = new PrepareLandmarks(dir, graph, lmConfig, 2);
         plm.setMinimumNodes(2);
         plm.doWork();
diff --git a/core/src/test/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCCTest.java b/core/src/test/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCCTest.java
index 09b424b05..21cb35388 100644
--- a/core/src/test/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCCTest.java
+++ b/core/src/test/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCCTest.java
@@ -22,6 +22,9 @@ import com.carrotsearch.hppc.IntArrayList;
 import com.carrotsearch.hppc.cursors.IntCursor;
 import com.graphhopper.routing.subnetwork.EdgeBasedTarjanSCC.ConnectedComponents;
 import com.graphhopper.routing.subnetwork.TarjanSCCTest.IntWithArray;
+import com.graphhopper.routing.util.CarFlagEncoder;
+import com.graphhopper.routing.util.EncodingManager;
+import com.graphhopper.routing.util.FlagEncoder;
 import com.graphhopper.routing.util.*;
 import com.graphhopper.storage.GraphBuilder;
 import com.graphhopper.storage.GraphHopperStorage;
diff --git a/core/src/test/java/com/graphhopper/storage/GraphHopperStorageLMTest.java b/core/src/test/java/com/graphhopper/storage/GraphHopperStorageLMTest.java
index a277f900a..3afd85e39 100644
--- a/core/src/test/java/com/graphhopper/storage/GraphHopperStorageLMTest.java
+++ b/core/src/test/java/com/graphhopper/storage/GraphHopperStorageLMTest.java
@@ -4,6 +4,7 @@ import com.graphhopper.GraphHopper;
 import com.graphhopper.config.LMProfile;
 import com.graphhopper.config.Profile;
 import com.graphhopper.reader.ReaderWay;
+import com.graphhopper.routing.ev.Subnetwork;
 import com.graphhopper.routing.util.CarFlagEncoder;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.EncodingManager.Access;
@@ -23,7 +24,7 @@ public class GraphHopperStorageLMTest {
         String defaultGraphLoc = "./target/ghstorage_lm";
         Helper.removeDir(new File(defaultGraphLoc));
         CarFlagEncoder carFlagEncoder = new CarFlagEncoder();
-        EncodingManager encodingManager = EncodingManager.create(carFlagEncoder);
+        EncodingManager encodingManager = new EncodingManager.Builder().add(carFlagEncoder).add(Subnetwork.create("my_profile")).build();
         GraphHopperStorage graph = GraphBuilder.start(encodingManager).setRAM(defaultGraphLoc, true).create();
 
         // 0-1