Repository: graphhopper/graphhopper
Pull request #1701 (2019-08-28 11:12:53)

Tracktype parser
This pull request adds support for OSMs [tracktype](https://wiki.openstreetmap.org/wiki/Key:tracktype) key.

karussell: Thanks, this looks good! Can you adjust the code formatting? (Like 4 spaces, see [here](https://github.com/graphhopper/graphhopper/blob/master/.github/CONTRIBUTING.md#code-formatting))
otbutz: > Thanks, this looks good! Can you adjust the code formatting? (Like 4 spaces, see [here](https://github.com/graphhopper/graphhopper/blob/master/.github/CONTRIBUTING.md#code-formatting))

Sure
otbutz: Done :+1: 

Patch:
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/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/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/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/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));
+    }
+}