Repository: graphhopper/graphhopper
Pull request #1704 (2019-08-28 14:54:08)

Add support for hgv toll sub-categories
This PR modifies the OSMTollParser to treat `toll:N2` and `toll:N3` as special cases of `toll:hgv`.

[Wikipedia](https://en.wikipedia.org/wiki/Large_goods_vehicle):
> A heavy goods vehicle (HGV), also large goods vehicle (LGV) or medium goods vehicle, is the European Union (EU) term for any truck with a gross combination mass (GCM) of over 3,500 kilograms (7,716 lb). Sub-category N2 is used for vehicles between 3,500 kilograms (7,716 lb) and 12,000 kilograms (26,455 lb) and N3 for all goods vehicles over 12,000 kilograms (26,455 lb) as defined in Directive 2001/116/EC.


Patch:
diff --git a/api/src/main/java/com/graphhopper/util/Helper.java b/api/src/main/java/com/graphhopper/util/Helper.java
index e7ed1df51..45cb8cb22 100644
--- a/api/src/main/java/com/graphhopper/util/Helper.java
+++ b/api/src/main/java/com/graphhopper/util/Helper.java
@@ -339,15 +339,6 @@ public class Helper {
         return integEle / ELE_FACTOR;
     }
 
-
-    /**
-     * Trying to force the release of the mapped ByteBuffer. See
-     * http://stackoverflow.com/q/2972986/194609 and use only if you know what you are doing.
-     */
-    public static void cleanHack() {
-        System.gc();
-    }
-
     public static String nf(long no) {
         // I like french localization the most: 123654 will be 123 654 instead
         // of comma vs. point confusion for English/German people.
diff --git a/config-example.yml b/config-example.yml
index 37eaa99ee..d3efc7fe2 100644
--- a/config-example.yml
+++ b/config-example.yml
@@ -15,7 +15,7 @@ graphhopper:
 
   # Add additional information to every edge. Used for path details.
   # If road_environment is added and elevation is enabled then also a tunnel and bridge interpolation is done, see #798.
-  # More options are: surface,max_width,max_height,max_weight,toll
+  # More options are: surface,max_width,max_height,max_weight,toll,track_type
   graph.encoded_values: road_class,road_class_link,road_environment,max_speed,road_access
   # If many flag_encoders or encoded_values are used you need to increase bytes_for_flags to 8 or more (multiple of 4)
   graph.bytes_for_flags: 4
diff --git a/core/files/changelog.txt b/core/files/changelog.txt
index 78c5a5d18..106f6c0d5 100644
--- a/core/files/changelog.txt
+++ b/core/files/changelog.txt
@@ -16,6 +16,8 @@
     GraphHopperStorage.getGraph(Class) was replaced by GraphHopperStorage.getBase/CHGraph(), #1669
     CHGraph.shortcut(int, int) was removed (use .shortcut(int, int, ...) and/or .shortcutEdgeBased(int, int, ...) instead, #1693
     CH graphs are now identified using CHProfile instead of Weighting, #1670
+    removed the 'traversal_mode` request parameter for /route, instead of 'traversal_mode=edge_based_2dir' use edge_based=true
+    removed GraphHopper.set/getTraversalMode() methods, #1705
 
 0.12
     renamed VirtualEdgeIteratorState.getOriginalEdgeKey to more precise getOriginalEdgeKey #1549
diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java
index 692639422..307cc9dc9 100644
--- a/core/src/main/java/com/graphhopper/GraphHopper.java
+++ b/core/src/main/java/com/graphhopper/GraphHopper.java
@@ -96,7 +96,6 @@ public class GraphHopper implements GraphHopperAPI {
     // for routing
     private int maxRoundTripRetries = 3;
     private boolean simplifyResponse = true;
-    private TraversalMode traversalMode = TraversalMode.NODE_BASED;
     private int maxVisitedNodes = Integer.MAX_VALUE;
 
     private int nonChMaxWaypointDistance = Integer.MAX_VALUE;
@@ -166,9 +165,6 @@ public class GraphHopper implements GraphHopperAPI {
     public GraphHopper setEncodingManager(EncodingManager em) {
         ensureNotLoaded();
         this.encodingManager = em;
-        if (em.needsTurnCostsSupport())
-            traversalMode = TraversalMode.EDGE_BASED;
-
         return this;
     }
 
@@ -208,18 +204,6 @@ public class GraphHopper implements GraphHopperAPI {
         return this;
     }
 
-    public TraversalMode getTraversalMode() {
-        return traversalMode;
-    }
-
-    /**
-     * Sets the default traversal mode used for the algorithms and preparation.
-     */
-    public GraphHopper setTraversalMode(TraversalMode traversalMode) {
-        this.traversalMode = traversalMode;
-        return this;
-    }
-
     public GraphHopper setPathDetailsBuilderFactory(PathDetailsBuilderFactory pathBuilderFactory) {
         this.pathBuilderFactory = pathBuilderFactory;
         return this;
@@ -972,14 +956,15 @@ public class GraphHopper implements GraphHopperAPI {
             if (!encodingManager.hasEncoder(vehicle))
                 throw new IllegalArgumentException("Vehicle not supported: " + vehicle + ". Supported are: " + encodingManager.toString());
 
+            FlagEncoder encoder = encodingManager.getEncoder(vehicle);
             HintsMap hints = request.getHints();
-            String tModeStr = hints.get("traversal_mode", traversalMode.toString());
-            TraversalMode tMode = TraversalMode.fromString(tModeStr);
+
+            // we use edge-based routing if the encoder supports turn-costs *unless* the edge_based parameter is set
+            // explicitly.
+            TraversalMode tMode = encoder.supports(TurnWeighting.class) ? TraversalMode.EDGE_BASED : TraversalMode.NODE_BASED;
             if (hints.has(Routing.EDGE_BASED))
                 tMode = hints.getBool(Routing.EDGE_BASED, false) ? TraversalMode.EDGE_BASED : TraversalMode.NODE_BASED;
 
-            FlagEncoder encoder = encodingManager.getEncoder(vehicle);
-
             if (tMode.isEdgeBased() && !encoder.supports(TurnWeighting.class)) {
                 throw new IllegalArgumentException("You need a turn cost extension to make use of edge_based=true, e.g. use car|turn_costs=true");
             }
diff --git a/core/src/main/java/com/graphhopper/routing/profiles/DefaultEncodedValueFactory.java b/core/src/main/java/com/graphhopper/routing/profiles/DefaultEncodedValueFactory.java
index a2a4dda66..88ed8a68e 100644
--- a/core/src/main/java/com/graphhopper/routing/profiles/DefaultEncodedValueFactory.java
+++ b/core/src/main/java/com/graphhopper/routing/profiles/DefaultEncodedValueFactory.java
@@ -53,6 +53,8 @@ public class DefaultEncodedValueFactory implements EncodedValueFactory {
             enc = new EnumEncodedValue<>(Surface.KEY, Surface.class);
         } else if (Toll.KEY.equals(name)) {
             enc = new EnumEncodedValue<>(Toll.KEY, Toll.class);
+        } else if (TrackType.KEY.equals(name)) {
+            enc = new EnumEncodedValue<>(TrackType.KEY, TrackType.class);
         } else {
             throw new IllegalArgumentException("DefaultEncodedValueFactory cannot find EncodedValue " + name);
         }
diff --git a/core/src/main/java/com/graphhopper/routing/profiles/TrackType.java b/core/src/main/java/com/graphhopper/routing/profiles/TrackType.java
new file mode 100644
index 000000000..a0f5fdb0a
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/profiles/TrackType.java
@@ -0,0 +1,55 @@
+/*
+ *  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.profiles;
+
+import com.graphhopper.util.Helper;
+
+/**
+ * This enum defines the track type of an edge which describes how
+ * well-maintained a certain track is. All edges that do not fit get "other" as
+ * value.
+ * 
+ * @see https://wiki.openstreetmap.org/wiki/Tracktype
+ */
+public enum TrackType {
+    OTHER("other"), GRADE1("grade1"), GRADE2("grade2"), GRADE3("grade3"), GRADE4("grade4"),
+    GRADE5("grade5");
+
+    public static final String KEY = "track_type";
+
+    private final String name;
+
+    TrackType(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+
+    public static TrackType find(String name) {
+        if (name == null)
+            return OTHER;
+        try {
+            return TrackType.valueOf(Helper.toUpperCase(name));
+        } catch (IllegalArgumentException ex) {
+            return OTHER;
+        }
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/routing/util/NameSimilarityEdgeFilter.java b/core/src/main/java/com/graphhopper/routing/util/NameSimilarityEdgeFilter.java
index 2e8d59375..94c8c2fbf 100644
--- a/core/src/main/java/com/graphhopper/routing/util/NameSimilarityEdgeFilter.java
+++ b/core/src/main/java/com/graphhopper/routing/util/NameSimilarityEdgeFilter.java
@@ -42,12 +42,13 @@ import static com.graphhopper.util.Helper.toLowerCase;
  * We aim for allowing slight typos/differences of the substrings, without having too much false positives.
  *
  * @author Robin Boldt
+ * @author Peter Karich
  */
 public class NameSimilarityEdgeFilter implements EdgeFilter {
 
-
     private static final Map<String, String> DEFAULT_REWRITE_MAP = new HashMap<String, String>() {{
-        // two char words will be ignored but ignore certain longer phrases (or rename them)
+        // Words with 2 characters like "Dr" (Drive) will be ignored, so it is not required to list them here.
+        // Words with 3 and more characters should be listed here to remove or rename them.
         for (String remove : Arrays.asList(
                 "ally", "alley",
                 "arc", "arcade",
@@ -66,6 +67,11 @@ public class NameSimilarityEdgeFilter implements EdgeFilter {
                 "via")) {
             put(remove, "");
         }
+        // expand instead of remove as significant part of the road name
+        put("n", "north");
+        put("s", "south");
+        put("w", "west");
+        put("e", "east");
     }};
     private static final Pattern NON_WORD_CHAR = Pattern.compile("[^\\p{L}]+");
     private static final JaroWinkler jaroWinkler = new JaroWinkler();
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java b/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java
index 370ba8e8f..81ba6a36f 100644
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java
+++ b/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java
@@ -52,6 +52,8 @@ public class DefaultTagParserFactory implements TagParserFactory {
             return new OSMSurfaceParser();
         else if (name.equals(Toll.KEY))
             return new OSMTollParser();
+        else if (name.equals(TrackType.KEY))
+            return new OSMTrackTypeParser();
         throw new IllegalArgumentException("entry in encoder list not supported " + name);
     }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java
index ad3167bb2..cf221007c 100644
--- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java
+++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java
@@ -46,6 +46,10 @@ public class OSMTollParser implements TagParser {
             tollEnc.setEnum(false, edgeFlags, Toll.ALL);
         else if (readerWay.hasTag("toll:hgv", "yes"))
             tollEnc.setEnum(false, edgeFlags, Toll.HGV);
+        else if (readerWay.hasTag("toll:N2", "yes"))
+            tollEnc.setEnum(false, edgeFlags, Toll.HGV);
+        else if (readerWay.hasTag("toll:N3", "yes"))
+            tollEnc.setEnum(false, edgeFlags, Toll.HGV);
         return edgeFlags;
     }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTrackTypeParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTrackTypeParser.java
new file mode 100644
index 000000000..733d212f5
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTrackTypeParser.java
@@ -0,0 +1,60 @@
+/*
+ *  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.util.parsers;
+
+import static com.graphhopper.routing.profiles.TrackType.OTHER;
+
+import java.util.List;
+
+import com.graphhopper.reader.ReaderWay;
+import com.graphhopper.routing.profiles.EncodedValue;
+import com.graphhopper.routing.profiles.EncodedValueLookup;
+import com.graphhopper.routing.profiles.EnumEncodedValue;
+import com.graphhopper.routing.profiles.TrackType;
+import com.graphhopper.routing.util.EncodingManager.Access;
+import com.graphhopper.storage.IntsRef;
+
+public class OSMTrackTypeParser implements TagParser {
+
+    private final EnumEncodedValue<TrackType> trackTypeEnc;
+
+    public OSMTrackTypeParser() {
+        this.trackTypeEnc = new EnumEncodedValue<>(TrackType.KEY, TrackType.class);
+    }
+
+    @Override
+    public void createEncodedValues(EncodedValueLookup lookup, List<EncodedValue> link) {
+        link.add(trackTypeEnc);
+    }
+
+    @Override
+    public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay readerWay, Access access,
+                    long relationFlags) {
+        if (!access.isWay())
+            return edgeFlags;
+
+        String trackTypeTag = readerWay.getTag("tracktype");
+        if (trackTypeTag == null)
+            return edgeFlags;
+        TrackType trackType = TrackType.find(trackTypeTag);
+        if (trackType != OTHER)
+            trackTypeEnc.setEnum(false, edgeFlags, trackType);
+        return edgeFlags;
+    }
+
+}
diff --git a/core/src/main/java/com/graphhopper/storage/GHDirectory.java b/core/src/main/java/com/graphhopper/storage/GHDirectory.java
index 4aeb1879f..76f9dd6a0 100644
--- a/core/src/main/java/com/graphhopper/storage/GHDirectory.java
+++ b/core/src/main/java/com/graphhopper/storage/GHDirectory.java
@@ -27,7 +27,6 @@ import static com.graphhopper.util.Helper.*;
 
 /**
  * Implements some common methods for the subclasses.
- * <p>
  *
  * @author Peter Karich
  */
@@ -109,34 +108,20 @@ public class GHDirectory implements Directory {
 
     @Override
     public void clear() {
-        // If there is at least one MMap DA then do not apply the cleanHack 
-        // for every single mmap DA as this is very slow if lots of DataAccess objects were collected 
-        // => forceClean == false
-
-        MMapDataAccess mmapDA = null;
         for (DataAccess da : map.values()) {
-            if (da instanceof MMapDataAccess)
-                mmapDA = (MMapDataAccess) da;
-
-            removeDA(da, da.getName(), false);
+            removeDA(da, da.getName());
         }
-        if (mmapDA != null)
-            cleanHack();
         map.clear();
     }
 
     @Override
     public void remove(DataAccess da) {
         removeFromMap(da.getName());
-        removeDA(da, da.getName(), true);
+        removeDA(da, da.getName());
     }
 
-    void removeDA(DataAccess da, String name, boolean forceClean) {
-        if (da instanceof MMapDataAccess)
-            ((MMapDataAccess) da).close(forceClean);
-        else
-            da.close();
-
+    void removeDA(DataAccess da, String name) {
+        da.close();
         if (da.getType().isStoring())
             removeDir(new File(location + name));
     }
diff --git a/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java b/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java
index ec52bcf00..d2bcd5368 100644
--- a/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java
+++ b/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java
@@ -52,8 +52,6 @@ import java.util.StringTokenizer;
  *
  * The exact behavior of memory-mapping is reported to be wildly platform-dependent.
  *
- * <p>
- *
  * @author Peter Karich
  * @author Michael Zilske
  */
@@ -258,7 +256,6 @@ public final class MMapDataAccess extends AbstractDataAccess {
             } catch (IOException tmpex) {
                 ioex = tmpex;
                 trial++;
-                Helper.cleanHack();
                 try {
                     // mini sleep to let JVM do unmapping
                     Thread.sleep(5);
@@ -328,19 +325,9 @@ public final class MMapDataAccess extends AbstractDataAccess {
     @Override
     public void close() {
         super.close();
-        close(true);
-    }
-
-    /**
-     * @param forceClean if true the clean hack (system.gc) will be executed and forces the system
-     *                   to cleanup the mmap resources. Set false if you need to close many MMapDataAccess objects.
-     */
-    void close(boolean forceClean) {
         clean(0, segments.size());
         segments.clear();
         Helper.close(raFile);
-        if (forceClean)
-            Helper.cleanHack();
     }
 
     @Override
@@ -476,7 +463,6 @@ public final class MMapDataAccess extends AbstractDataAccess {
         }
 
         clean(remainingSegNo, segments.size());
-        Helper.cleanHack();
         segments = new ArrayList<>(segments.subList(0, remainingSegNo));
 
         try {
diff --git a/core/src/main/java/com/graphhopper/util/details/PathDetailsBuilderFactory.java b/core/src/main/java/com/graphhopper/util/details/PathDetailsBuilderFactory.java
index 62e373256..57578f290 100644
--- a/core/src/main/java/com/graphhopper/util/details/PathDetailsBuilderFactory.java
+++ b/core/src/main/java/com/graphhopper/util/details/PathDetailsBuilderFactory.java
@@ -62,7 +62,7 @@ public class PathDetailsBuilderFactory {
         for (Map.Entry entry : Arrays.asList(new MapEntry<>(RoadClass.KEY, RoadClass.class),
                 new MapEntry<>(RoadEnvironment.KEY, RoadEnvironment.class), new MapEntry<>(Surface.KEY, Surface.class),
                 new MapEntry<>(RoadAccess.KEY, RoadAccess.class), new MapEntry<>(Toll.KEY, Toll.class),
-                new MapEntry<>(Country.KEY, Country.class))) {
+                new MapEntry<>(TrackType.KEY, TrackType.class), new MapEntry<>(Country.KEY, Country.class))) {
             String key = (String) entry.getKey();
             if (requestedPathDetails.contains(key) && encoder.hasEncodedValue(key))
                 builders.add(new EnumDetails(key, encoder.getEnumEncodedValue(key, (Class<Enum>) entry.getValue())));


Test patch:
diff --git a/core/src/test/java/com/graphhopper/routing/util/NameSimilarityEdgeFilterTest.java b/core/src/test/java/com/graphhopper/routing/util/NameSimilarityEdgeFilterTest.java
index 81dfa60b0..203f25636 100644
--- a/core/src/test/java/com/graphhopper/routing/util/NameSimilarityEdgeFilterTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/NameSimilarityEdgeFilterTest.java
@@ -19,7 +19,6 @@ package com.graphhopper.routing.util;
 
 import com.graphhopper.util.EdgeIteratorState;
 import com.graphhopper.util.GHUtility;
-import org.junit.Ignore;
 import org.junit.Test;
 
 import static junit.framework.TestCase.assertFalse;
@@ -131,13 +130,12 @@ public class NameSimilarityEdgeFilterTest {
         assertFalse(createNameSimilarityEdgeFilter("Potsdamer Str.").accept(edge));
     }
 
-    @Ignore
-    public void testThatShouldSucceed() {
-        // Google Maps vs OSM @ 32.121435,-110.857969
-        assertTrue(createNameSimilarityEdgeFilter("S Wilmot Rd").accept(createTestEdgeIterator("South Wilmot Road")));
+    @Test
+    public void testServiceMix() {
+        assertTrue(createNameSimilarityEdgeFilter("North Derby Lane").accept(createTestEdgeIterator("N Derby Ln")));
+        assertTrue(createNameSimilarityEdgeFilter("N Derby Ln").accept(createTestEdgeIterator("North Derby Lane")));
 
-        // @ 37.307774,13.581259
-        assertTrue(createNameSimilarityEdgeFilter("Via Manzoni").accept(createTestEdgeIterator("Via Alessandro Manzoni")));
+        assertTrue(createNameSimilarityEdgeFilter("George Street").accept(createTestEdgeIterator("George St")));
     }
 
     /**
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTollParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTollParserTest.java
index f6cc675a5..94249da5e 100644
--- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTollParserTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTollParserTest.java
@@ -36,6 +36,18 @@ public class OSMTollParserTest {
         readerWay.setTag("toll:hgv", "yes");
         parser.handleWayTags(intsRef, readerWay, WAY, 0);
         assertEquals(Toll.HGV, tollEnc.getEnum(false, intsRef));
+        
+        intsRef = em.createEdgeFlags();
+        readerWay.setTag("highway", "primary");
+        readerWay.setTag("toll:N2", "yes");
+        parser.handleWayTags(intsRef, readerWay, WAY, 0);
+        assertEquals(Toll.HGV, tollEnc.getEnum(false, intsRef));
+        
+        intsRef = em.createEdgeFlags();
+        readerWay.setTag("highway", "primary");
+        readerWay.setTag("toll:N3", "yes");
+        parser.handleWayTags(intsRef, readerWay, WAY, 0);
+        assertEquals(Toll.HGV, tollEnc.getEnum(false, intsRef));
 
         intsRef = em.createEdgeFlags();
         readerWay.setTag("highway", "primary");
@@ -47,6 +59,8 @@ public class OSMTollParserTest {
         readerWay.setTag("highway", "primary");
         readerWay.setTag("toll", "yes");
         readerWay.setTag("toll:hgv", "yes");
+        readerWay.setTag("toll:N2", "yes");
+        readerWay.setTag("toll:N3", "yes");
         parser.handleWayTags(intsRef, readerWay, WAY, 0);
         assertEquals(Toll.ALL, tollEnc.getEnum(false, intsRef));
     }
diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTrackTypeParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTrackTypeParserTest.java
new file mode 100644
index 000000000..eefc29332
--- /dev/null
+++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTrackTypeParserTest.java
@@ -0,0 +1,73 @@
+package com.graphhopper.routing.util.parsers;
+
+import static com.graphhopper.routing.util.EncodingManager.Access.WAY;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.graphhopper.reader.ReaderWay;
+import com.graphhopper.routing.profiles.EnumEncodedValue;
+import com.graphhopper.routing.profiles.TrackType;
+import com.graphhopper.routing.util.EncodingManager;
+import com.graphhopper.storage.IntsRef;
+
+public class OSMTrackTypeParserTest {
+
+    private EncodingManager em;
+    private EnumEncodedValue<TrackType> ttEnc;
+    private OSMTrackTypeParser parser;
+
+    @Before
+    public void setUp() {
+        parser = new OSMTrackTypeParser();
+        em = new EncodingManager.Builder(4).add(parser).build();
+        ttEnc = em.getEnumEncodedValue(TrackType.KEY, TrackType.class);
+    }
+
+    @Test
+    public void testSimpleTags() {
+        ReaderWay readerWay = new ReaderWay(1);
+        IntsRef intsRef = em.createEdgeFlags();
+        readerWay.setTag("tracktype", "grade1");
+        parser.handleWayTags(intsRef, readerWay, WAY, 0);
+        assertEquals(TrackType.GRADE1, ttEnc.getEnum(false, intsRef));
+
+        intsRef = em.createEdgeFlags();
+        readerWay.setTag("tracktype", "grade2");
+        parser.handleWayTags(intsRef, readerWay, WAY, 0);
+        assertEquals(TrackType.GRADE2, ttEnc.getEnum(false, intsRef));
+
+        intsRef = em.createEdgeFlags();
+        readerWay.setTag("tracktype", "grade3");
+        parser.handleWayTags(intsRef, readerWay, WAY, 0);
+        assertEquals(TrackType.GRADE3, ttEnc.getEnum(false, intsRef));
+
+        intsRef = em.createEdgeFlags();
+        readerWay.setTag("tracktype", "grade4");
+        parser.handleWayTags(intsRef, readerWay, WAY, 0);
+        assertEquals(TrackType.GRADE4, ttEnc.getEnum(false, intsRef));
+
+        intsRef = em.createEdgeFlags();
+        readerWay.setTag("tracktype", "grade5");
+        parser.handleWayTags(intsRef, readerWay, WAY, 0);
+        assertEquals(TrackType.GRADE5, ttEnc.getEnum(false, intsRef));
+    }
+
+    @Test
+    public void testUnkownValue() {
+        ReaderWay readerWay = new ReaderWay(1);
+        IntsRef intsRef = em.createEdgeFlags();
+        readerWay.setTag("tracktype", "unknownstuff");
+        parser.handleWayTags(intsRef, readerWay, WAY, 0);
+        assertEquals(TrackType.OTHER, ttEnc.getEnum(false, intsRef));
+    }
+
+    @Test
+    public void testNoNPE() {
+        ReaderWay readerWay = new ReaderWay(1);
+        IntsRef intsRef = em.createEdgeFlags();
+        parser.handleWayTags(intsRef, readerWay, WAY, 0);
+        assertEquals(TrackType.OTHER, ttEnc.getEnum(false, intsRef));
+    }
+}
diff --git a/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java b/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
index 8d4066630..f8bd8d5ad 100644
--- a/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
+++ b/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
@@ -1002,20 +1002,12 @@ public class GraphHopperIT {
                 setEncodingManager(EncodingManager.create("car|turn_costs=true"));
         tmpHopper.importOrLoad();
 
-        // with turn costs (default if non-CH and turn cost enabled)
-        GHRequest req = new GHRequest(55.813357, 37.5958585, 55.811042, 37.594689);
-        GHResponse rsp = tmpHopper.route(req);
-        assertEquals(1044, rsp.getBest().getDistance(), 1);
-
-        // without turn costs
-        req.getHints().put(Routing.EDGE_BASED, "false");
-        rsp = tmpHopper.route(req);
-        assertEquals(400, rsp.getBest().getDistance(), 1);
-
-        // with turn costs
-        req.getHints().put(Routing.EDGE_BASED, "true");
-        rsp = tmpHopper.route(req);
-        assertEquals(1044, rsp.getBest().getDistance(), 1);
+        // no edge_based parameter -> use edge-based (since encoder supports it and no CH)
+        assertMoscowEdgeBased(tmpHopper, "none", false);
+        // edge_based=false -> use node-based
+        assertMoscowNodeBased(tmpHopper, "false", false);
+        // edge_based=true -> use edge-based
+        assertMoscowEdgeBased(tmpHopper, "true", false);
     }
 
     @Test
@@ -1030,18 +1022,12 @@ public class GraphHopperIT {
         tmpHopper.getCHFactoryDecorator().setEdgeBasedCHMode(CHAlgoFactoryDecorator.EdgeBasedCHMode.EDGE_AND_NODE);
         tmpHopper.importOrLoad();
 
-        // without turn costs
-        GHRequest req = new GHRequest(55.813357, 37.5958585, 55.811042, 37.594689);
-        req.getHints().put(Routing.EDGE_BASED, "false");
-        GHResponse rsp = tmpHopper.route(req);
-        assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
-        assertEquals(400, rsp.getBest().getDistance(), 1);
-
-        // with turn costs                
-        req.getHints().put(Routing.EDGE_BASED, "true");
-        rsp = tmpHopper.route(req);
-        assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
-        assertEquals(1044, rsp.getBest().getDistance(), 1);
+        // no edge_based parameter -> use node-based (because its faster)
+        assertMoscowNodeBased(tmpHopper, "none", true);
+        // edge_based=false -> use node-based
+        assertMoscowNodeBased(tmpHopper, "false", true);
+        // edge_based=true -> use edge-based
+        assertMoscowEdgeBased(tmpHopper, "true", true);
     }
 
     @Test
@@ -1057,24 +1043,64 @@ public class GraphHopperIT {
                 .setDisablingAllowed(true);
         tmpHopper.importOrLoad();
 
-        // with CH
-        GHRequest req = new GHRequest(55.813357, 37.5958585, 55.811042, 37.594689);
-        req.getHints().put(Routing.EDGE_BASED, "true");
-        req.getHints().put(CH.DISABLE, "false");
-        GHResponse rsp1 = tmpHopper.route(req);
-        assertFalse(rsp1.getErrors().toString(), rsp1.hasErrors());
-        assertEquals(1044, rsp1.getBest().getDistance(), 1);
-
-        // without CH      
-        req.getHints().put(Routing.EDGE_BASED, "true");
-        req.getHints().put(CH.DISABLE, "true");
-        GHResponse rsp2 = tmpHopper.route(req);
-        assertFalse(rsp2.getErrors().toString(), rsp2.hasErrors());
-        assertEquals(1044, rsp2.getBest().getDistance(), 1);
+        // with CH -> edge-based
+        GHResponse rsp1 = assertMoscowEdgeBased(tmpHopper, "true", false);
+        // without CH -> also edge-based
+        GHResponse rsp2 = assertMoscowEdgeBased(tmpHopper, "true", true);
         // just a quick check that we did not run the same algorithm twice
         assertNotEquals(rsp1.getHints().get("visited_nodes.sum", "_"), rsp2.getHints().get("visited_nodes.sum", "_"));
     }
 
+    @Test
+    public void testNodeBasedCHOnlyButTurnCostForNonCH() {
+        // before edge-based CH was added a common case was to use edge-based without CH and CH for node-based
+        GraphHopper tmpHopper = new GraphHopperOSM().
+                setOSMFile(DIR + "/moscow.osm.gz").
+                setStoreOnFlush(true).
+                setCHEnabled(true).
+                setGraphHopperLocation(tmpGraphFile).
+                setEncodingManager(EncodingManager.create("car|turn_costs=true"));
+        tmpHopper.getCHFactoryDecorator()
+                .setEdgeBasedCHMode(CHAlgoFactoryDecorator.EdgeBasedCHMode.OFF)
+                .setDisablingAllowed(true);
+        tmpHopper.importOrLoad();
+
+        // without CH -> use edge-based unless disabled explicitly
+        assertMoscowEdgeBased(tmpHopper, "none", false);
+        assertMoscowEdgeBased(tmpHopper, "true", false);
+        assertMoscowNodeBased(tmpHopper, "false", false);
+
+        // with CH -> use node-based unless edge_based is enabled explicitly (which should give an error)
+        assertMoscowNodeBased(tmpHopper, "none", true);
+        assertMoscowNodeBased(tmpHopper, "false", true);
+        GHResponse rsp = runMoscow(tmpHopper, "true", true);
+        assertEquals(1, rsp.getErrors().size());
+        assertTrue(rsp.getErrors().toString().contains("Cannot find CH RoutingAlgorithmFactory"));
+    }
+
+    private GHResponse assertMoscowNodeBased(GraphHopper tmpHopper, String edgeBasedParam, boolean ch) {
+        GHResponse rsp = runMoscow(tmpHopper, edgeBasedParam, ch);
+        assertEquals(400, rsp.getBest().getDistance(), 1);
+        return rsp;
+    }
+
+    private GHResponse assertMoscowEdgeBased(GraphHopper tmpHopper, String edgeBasedParam, boolean ch) {
+        GHResponse rsp = runMoscow(tmpHopper, edgeBasedParam, ch);
+        assertEquals(1044, rsp.getBest().getDistance(), 1);
+        return rsp;
+    }
+
+    private GHResponse runMoscow(GraphHopper tmpHopper, String edgeBasedParam, boolean ch) {
+        GHRequest req = new GHRequest(55.813357, 37.5958585, 55.811042, 37.594689);
+        if (edgeBasedParam.equals("true") || edgeBasedParam.equals("false")) {
+            req.getHints().put(Routing.EDGE_BASED, edgeBasedParam);
+        } else {
+            req.getHints().remove(Routing.EDGE_BASED);
+        }
+        req.getHints().put(CH.DISABLE, !ch);
+        return tmpHopper.route(req);
+    }
+
     @Test
     public void testEdgeBasedRequiresTurnCostSupport() {
         GHPoint p = new GHPoint(43.727687, 7.418737);
@@ -1087,4 +1113,21 @@ public class GraphHopperIT {
                 rsp.getErrors().toString().contains("You need a turn cost extension to make use of edge_based=true, e.g. use car|turn_costs=true"));
     }
 
+    @Test
+    public void testEncoderWithTurnCostSupport_stillAllows_nodeBasedRouting() {
+        // see #1698
+        GraphHopper tmpHopper = new GraphHopperOSM().
+                setOSMFile(DIR + "/moscow.osm.gz").
+                setGraphHopperLocation(tmpGraphFile).
+                setCHEnabled(false).
+                setEncodingManager(EncodingManager.create("foot,car|turn_costs=true"));
+        tmpHopper.importOrLoad();
+        GHPoint p = new GHPoint(55.813357, 37.5958585);
+        GHPoint q = new GHPoint(55.811042, 37.594689);
+        GHRequest req = new GHRequest(p, q);
+        req.setVehicle("foot");
+        GHResponse rsp = tmpHopper.route(req);
+        assertEquals("there should not be an error, but was: " + rsp.getErrors(), 0, rsp.getErrors().size());
+    }
+
 }