Repository: graphhopper/graphhopper (java)

Issue:
Avoid certain road type snapping
With the point_hint the snapping was already improved.

Now with #1548 we can further filter out edges when doing the location snapping. So very often the route does not start on a motorway or on a ferry but sometimes like for navigation this can happen.

So we need to make it configurable like a parameter `avoid_snap=motorway,ferry`. Is the name ok, or `point_avoid_snap` or `avoid_point_snap` or something? 

And should we have a different avoid parameter for road_class and one for road_environment? I find this too complex and would just allow a few values like motorway, ferry, ford, tunnel, bridge, but mixing different "units" also doesn't feel 100% correct.

What happens if there is no edge left - try again?

Pull request description:
avoid snapping to a certain road class or environment
Fixes #1643

Parameter is `avoid_snap=motorway&avoid_snap=ferry`

Alternatives `snap_avoidance`, `snap_ban` or `snap_prevention` might be a better fit (suggestions from @easbar)

TODO:

 - [x] rename to noun `snap_prevention`

Patch:
diff --git a/api/src/main/java/com/graphhopper/GHRequest.java b/api/src/main/java/com/graphhopper/GHRequest.java
index 38b40c252..cff736a29 100644
--- a/api/src/main/java/com/graphhopper/GHRequest.java
+++ b/api/src/main/java/com/graphhopper/GHRequest.java
@@ -39,6 +39,7 @@ public class GHRequest {
     // Headings are north based azimuth (clockwise) in (0, 360) or NaN for equal preference
     private final List<Double> favoredHeadings;
     private List<String> pointHints = new ArrayList<>();
+    private List<String> snapPreventions = new ArrayList<>();
     private List<String> pathDetails = new ArrayList<>();
     private String algo = "";
     private boolean possibleToAdd = false;
@@ -254,6 +255,19 @@ public class GHRequest {
         return pointHints.size() == points.size() && !points.isEmpty();
     }
 
+    public GHRequest setSnapPreventions(List<String> snapPreventions) {
+        this.snapPreventions = snapPreventions;
+        return this;
+    }
+
+    public boolean hasSnapPreventions() {
+        return !snapPreventions.isEmpty();
+    }
+
+    public List<String> getSnapPreventions() {
+        return snapPreventions;
+    }
+
     public GHRequest setPathDetails(List<String> pathDetails) {
         this.pathDetails = pathDetails;
         return this;
diff --git a/api/src/main/java/com/graphhopper/util/Parameters.java b/api/src/main/java/com/graphhopper/util/Parameters.java
index 0ec9de5fc..5692c988d 100644
--- a/api/src/main/java/com/graphhopper/util/Parameters.java
+++ b/api/src/main/java/com/graphhopper/util/Parameters.java
@@ -114,6 +114,7 @@ public class Parameters {
          */
         public static final String PASS_THROUGH = "pass_through";
         public static final String POINT_HINT = "point_hint";
+        public static final String SNAP_PREVENTION = "snap_prevention";
         /**
          * default heading penalty in seconds
          */
@@ -189,7 +190,7 @@ public class Parameters {
     /**
      * Properties for the details response
      */
-    public static final class DETAILS {
+    public static final class Details {
 
         public static final String PATH_DETAILS = "details";
 
diff --git a/client-hc/src/main/java/com/graphhopper/api/GraphHopperWeb.java b/client-hc/src/main/java/com/graphhopper/api/GraphHopperWeb.java
index 844fe1dc8..fea4af35b 100644
--- a/client-hc/src/main/java/com/graphhopper/api/GraphHopperWeb.java
+++ b/client-hc/src/main/java/com/graphhopper/api/GraphHopperWeb.java
@@ -38,7 +38,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
-import static com.graphhopper.util.Helper.*;
+import static com.graphhopper.util.Helper.round6;
+import static com.graphhopper.util.Helper.toLowerCase;
 
 /**
  * Main wrapper of the GraphHopper Directions API for a simple and efficient
@@ -239,11 +240,15 @@ public class GraphHopperWeb implements GraphHopperAPI {
         }
 
         for (String details : request.getPathDetails()) {
-            url += "&" + Parameters.DETAILS.PATH_DETAILS + "=" + details;
+            url += "&" + Parameters.Details.PATH_DETAILS + "=" + details;
         }
 
         for (String hint : request.getPointHints()) {
-            url += "&point_hint=" + WebHelper.encodeURL(hint);
+            url += "&" + Parameters.Routing.POINT_HINT + "=" + WebHelper.encodeURL(hint);
+        }
+
+        for (String snapPrevention : request.getSnapPreventions()) {
+            url += "&" + Parameters.Routing.SNAP_PREVENTION + "=" + WebHelper.encodeURL(snapPrevention);
         }
 
         if (!key.isEmpty()) {
diff --git a/core/src/main/java/com/graphhopper/routing/template/ViaRoutingTemplate.java b/core/src/main/java/com/graphhopper/routing/template/ViaRoutingTemplate.java
index 5ad1d7f2f..bf6c7cdaa 100644
--- a/core/src/main/java/com/graphhopper/routing/template/ViaRoutingTemplate.java
+++ b/core/src/main/java/com/graphhopper/routing/template/ViaRoutingTemplate.java
@@ -21,6 +21,8 @@ import com.graphhopper.GHRequest;
 import com.graphhopper.GHResponse;
 import com.graphhopper.PathWrapper;
 import com.graphhopper.routing.*;
+import com.graphhopper.routing.profiles.RoadClass;
+import com.graphhopper.routing.profiles.RoadEnvironment;
 import com.graphhopper.routing.util.*;
 import com.graphhopper.storage.index.LocationIndex;
 import com.graphhopper.storage.index.QueryResult;
@@ -62,12 +64,17 @@ public class ViaRoutingTemplate extends AbstractRoutingTemplate implements Routi
             throw new IllegalArgumentException("At least 2 points have to be specified, but was:" + points.size());
 
         EdgeFilter edgeFilter = DefaultEdgeFilter.allEdges(encoder);
+        EdgeFilter strictEdgeFilter = !ghRequest.hasSnapPreventions() ? edgeFilter : new SnapPreventionEdgeFilter(edgeFilter,
+                encoder.getEnumEncodedValue(RoadClass.KEY, RoadClass.class),
+                encoder.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class), ghRequest.getSnapPreventions());
         queryResults = new ArrayList<>(points.size());
         for (int placeIndex = 0; placeIndex < points.size(); placeIndex++) {
             GHPoint point = points.get(placeIndex);
             QueryResult qr = null;
             if (ghRequest.hasPointHints())
-                qr = locationIndex.findClosest(point.lat, point.lon, new NameSimilarityEdgeFilter(edgeFilter, ghRequest.getPointHints().get(placeIndex)));
+                qr = locationIndex.findClosest(point.lat, point.lon, new NameSimilarityEdgeFilter(strictEdgeFilter, ghRequest.getPointHints().get(placeIndex)));
+            else if (ghRequest.hasSnapPreventions())
+                qr = locationIndex.findClosest(point.lat, point.lon, strictEdgeFilter);
             if (qr == null || !qr.isValid())
                 qr = locationIndex.findClosest(point.lat, point.lon, edgeFilter);
             if (!qr.isValid())
diff --git a/core/src/main/java/com/graphhopper/routing/util/SnapPreventionEdgeFilter.java b/core/src/main/java/com/graphhopper/routing/util/SnapPreventionEdgeFilter.java
new file mode 100644
index 000000000..7499dcd5c
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/util/SnapPreventionEdgeFilter.java
@@ -0,0 +1,62 @@
+package com.graphhopper.routing.util;
+
+import com.graphhopper.routing.profiles.EnumEncodedValue;
+import com.graphhopper.routing.profiles.RoadClass;
+import com.graphhopper.routing.profiles.RoadEnvironment;
+import com.graphhopper.util.EdgeIteratorState;
+import com.graphhopper.util.Parameters;
+
+import java.util.List;
+
+import static com.graphhopper.routing.profiles.RoadClass.MOTORWAY;
+import static com.graphhopper.routing.profiles.RoadClass.TRUNK;
+import static com.graphhopper.routing.profiles.RoadEnvironment.*;
+
+public class SnapPreventionEdgeFilter implements EdgeFilter {
+
+    private final EnumEncodedValue<RoadEnvironment> reEnc;
+    private final EnumEncodedValue<RoadClass> rcEnc;
+    private final EdgeFilter filter;
+    private boolean avoidMotorway = false, avoidTrunk;
+    private boolean avoidTunnel, avoidBridge, avoidFerry, avoidFord;
+
+    public SnapPreventionEdgeFilter(EdgeFilter filter, EnumEncodedValue<RoadClass> rcEnc,
+                                    EnumEncodedValue<RoadEnvironment> reEnc, List<String> snapPreventions) {
+        this.filter = filter;
+        this.reEnc = reEnc;
+        this.rcEnc = rcEnc;
+
+        for (String roadClassOrRoadEnv : snapPreventions) {
+            if ("motorway".equals(roadClassOrRoadEnv)) {
+                avoidMotorway = true;
+                continue;
+            } else if ("trunk".equals(roadClassOrRoadEnv)) {
+                avoidTrunk = true;
+                continue;
+            }
+
+            RoadEnvironment rc = RoadEnvironment.find(roadClassOrRoadEnv);
+            if (rc == TUNNEL)
+                avoidTunnel = true;
+            else if (rc == BRIDGE)
+                avoidBridge = true;
+            else if (rc == FERRY)
+                avoidFerry = true;
+            else if (rc == FORD)
+                avoidFord = true;
+            else
+                throw new IllegalArgumentException("Cannot find " + Parameters.Routing.SNAP_PREVENTION + ": " + roadClassOrRoadEnv);
+        }
+    }
+
+    @Override
+    public boolean accept(EdgeIteratorState edgeState) {
+        return filter.accept(edgeState)
+                && !(avoidMotorway && edgeState.get(rcEnc) == MOTORWAY)
+                && !(avoidTrunk && edgeState.get(rcEnc) == TRUNK)
+                && !(avoidTunnel && edgeState.get(reEnc) == TUNNEL)
+                && !(avoidBridge && edgeState.get(reEnc) == BRIDGE)
+                && !(avoidFord && edgeState.get(reEnc) == FORD)
+                && !(avoidFerry && edgeState.get(reEnc) == FERRY);
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/util/details/DistanceDetails.java b/core/src/main/java/com/graphhopper/util/details/DistanceDetails.java
index dd35f4062..c36e1cf45 100644
--- a/core/src/main/java/com/graphhopper/util/details/DistanceDetails.java
+++ b/core/src/main/java/com/graphhopper/util/details/DistanceDetails.java
@@ -19,7 +19,7 @@ package com.graphhopper.util.details;
 
 import com.graphhopper.util.EdgeIteratorState;
 
-import static com.graphhopper.util.Parameters.DETAILS.DISTANCE;
+import static com.graphhopper.util.Parameters.Details.DISTANCE;
 
 public class DistanceDetails extends AbstractPathDetailsBuilder {
 
diff --git a/core/src/main/java/com/graphhopper/util/details/EdgeIdDetails.java b/core/src/main/java/com/graphhopper/util/details/EdgeIdDetails.java
index f4b6bc2f6..3ee89686a 100644
--- a/core/src/main/java/com/graphhopper/util/details/EdgeIdDetails.java
+++ b/core/src/main/java/com/graphhopper/util/details/EdgeIdDetails.java
@@ -21,7 +21,7 @@ import com.graphhopper.routing.VirtualEdgeIteratorState;
 import com.graphhopper.util.EdgeIteratorState;
 import com.graphhopper.util.GHUtility;
 
-import static com.graphhopper.util.Parameters.DETAILS.EDGE_ID;
+import static com.graphhopper.util.Parameters.Details.EDGE_ID;
 
 /**
  * Calculate the Edge Id segments of a Path
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 03eefec7c..62e373256 100644
--- a/core/src/main/java/com/graphhopper/util/details/PathDetailsBuilderFactory.java
+++ b/core/src/main/java/com/graphhopper/util/details/PathDetailsBuilderFactory.java
@@ -27,7 +27,7 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
-import static com.graphhopper.util.Parameters.DETAILS.*;
+import static com.graphhopper.util.Parameters.Details.*;
 
 /**
  * Generates a list of PathDetailsBuilder from a List of PathDetail names
diff --git a/core/src/main/java/com/graphhopper/util/details/StreetNameDetails.java b/core/src/main/java/com/graphhopper/util/details/StreetNameDetails.java
index 8ae0a05e4..ca80323f1 100644
--- a/core/src/main/java/com/graphhopper/util/details/StreetNameDetails.java
+++ b/core/src/main/java/com/graphhopper/util/details/StreetNameDetails.java
@@ -19,7 +19,7 @@ package com.graphhopper.util.details;
 
 import com.graphhopper.util.EdgeIteratorState;
 
-import static com.graphhopper.util.Parameters.DETAILS.STREET_NAME;
+import static com.graphhopper.util.Parameters.Details.STREET_NAME;
 
 /**
  * Calculate the speed name segments of a Path
diff --git a/core/src/main/java/com/graphhopper/util/details/TimeDetails.java b/core/src/main/java/com/graphhopper/util/details/TimeDetails.java
index d65d314d3..cb89cd082 100644
--- a/core/src/main/java/com/graphhopper/util/details/TimeDetails.java
+++ b/core/src/main/java/com/graphhopper/util/details/TimeDetails.java
@@ -20,7 +20,7 @@ package com.graphhopper.util.details;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.util.EdgeIteratorState;
 
-import static com.graphhopper.util.Parameters.DETAILS.TIME;
+import static com.graphhopper.util.Parameters.Details.TIME;
 
 /**
  * Calculate the time segments for a Path
diff --git a/docs/web/api-doc.md b/docs/web/api-doc.md
index 8b06a1cbf..2cd4c7959 100644
--- a/docs/web/api-doc.md
+++ b/docs/web/api-doc.md
@@ -26,7 +26,8 @@ debug            | false   | If true, the output will be formated.
 calc_points      | true    | If the points for the route should be calculated at all printing out only distance and time.
 type             | json    | Specifies the resulting format of the route, for `json` the content type will be application/json. Other possible format options: <br> `gpx`, the content type will be application/gpx+xml, see below for more parameters.
 point_hint       | -       | Optional parameter. Specifies a hint for each `point` parameter to prefer a certain street for the closest location lookup. E.g. if there is an address or house with two or more neighboring streets you can control for which street the closest location is looked up.
-details          | -       | Optional parameter. You can request additional details for the route: `average_speed`, `street_name`, `edge_id`, and `time`. The returned format for one details is `[fromRef, toRef, value]`. The `ref` references the points of the response.
+snap_prevention  | -       | Optional parameter to avoid snapping to a certain road class or road environment. Current supported values: `motorway`, `trunk`, `ferry`, `tunnel`, `bridge` and `ford`. Multiple values are specified like `snap_prevention=ferry&snap_prevention=motorway`
+details          | -       | Optional parameter. You can request additional details for the route: `average_speed`, `street_name`, `edge_id`, `road_class`, `road_environment`, `max_speed` and `time` (and see which other values are configured in `graph.encoded_values`).  Multiple values are specified like `details=average_speed&details=time`. The returned format for one detail segment is `[fromRef, toRef, value]`. The `ref` references the points of the response. Value can also be `null` if the property does not exist for one detail segment.
 
 ### GPX
 
diff --git a/tools/src/main/java/com/graphhopper/tools/Measurement.java b/tools/src/main/java/com/graphhopper/tools/Measurement.java
index 3acafc99d..9fb0af5d8 100644
--- a/tools/src/main/java/com/graphhopper/tools/Measurement.java
+++ b/tools/src/main/java/com/graphhopper/tools/Measurement.java
@@ -449,7 +449,7 @@ public class Measurement {
                         put("instructions", withInstructions);
 
                 if (withInstructions)
-                    req.setPathDetails(Arrays.asList(Parameters.DETAILS.AVERAGE_SPEED));
+                    req.setPathDetails(Arrays.asList(Parameters.Details.AVERAGE_SPEED));
 
                 // put(algo + ".approximation", "BeelineSimplification").
                 // put(algo + ".epsilon", 2);
diff --git a/web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java b/web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java
index f1b3a3b9b..a060ef5b5 100644
--- a/web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java
+++ b/web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java
@@ -58,7 +58,7 @@ public class MVTResource {
             @PathParam("z") int zInfo,
             @PathParam("x") int xInfo,
             @PathParam("y") int yInfo,
-            @QueryParam(Parameters.DETAILS.PATH_DETAILS) List<String> pathDetails) {
+            @QueryParam(Parameters.Details.PATH_DETAILS) List<String> pathDetails) {
 
         if (zInfo <= 9) {
             VectorTile.Tile.Builder mvtBuilder = VectorTile.Tile.newBuilder();
diff --git a/web-bundle/src/main/java/com/graphhopper/resources/RouteResource.java b/web-bundle/src/main/java/com/graphhopper/resources/RouteResource.java
index 9521fa1e6..9ae528bac 100644
--- a/web-bundle/src/main/java/com/graphhopper/resources/RouteResource.java
+++ b/web-bundle/src/main/java/com/graphhopper/resources/RouteResource.java
@@ -23,7 +23,9 @@ import com.graphhopper.GraphHopperAPI;
 import com.graphhopper.MultiException;
 import com.graphhopper.http.WebHelper;
 import com.graphhopper.routing.util.HintsMap;
-import com.graphhopper.util.*;
+import com.graphhopper.util.Constants;
+import com.graphhopper.util.InstructionList;
+import com.graphhopper.util.StopWatch;
 import com.graphhopper.util.gpx.GpxFromInstructions;
 import com.graphhopper.util.shapes.GHPoint;
 import org.slf4j.Logger;
@@ -40,6 +42,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
+import static com.graphhopper.util.Parameters.Details.PATH_DETAILS;
 import static com.graphhopper.util.Parameters.Routing.*;
 
 /**
@@ -80,8 +83,9 @@ public class RouteResource {
             @QueryParam("weighting") @DefaultValue("fastest") String weighting,
             @QueryParam("algorithm") @DefaultValue("") String algoStr,
             @QueryParam("locale") @DefaultValue("en") String localeStr,
-            @QueryParam(Parameters.Routing.POINT_HINT) List<String> pointHints,
-            @QueryParam(Parameters.DETAILS.PATH_DETAILS) List<String> pathDetails,
+            @QueryParam(POINT_HINT) List<String> pointHints,
+            @QueryParam(SNAP_PREVENTION) List<String> snapPreventions,
+            @QueryParam(PATH_DETAILS) List<String> pathDetails,
             @QueryParam("heading") List<Double> favoredHeadings,
             @QueryParam("gpx.route") @DefaultValue("true") boolean withRoute /* default to false for the route part in next API version, see #437 */,
             @QueryParam("gpx.track") @DefaultValue("true") boolean withTrack,
@@ -123,6 +127,7 @@ public class RouteResource {
                 setAlgorithm(algoStr).
                 setLocale(localeStr).
                 setPointHints(pointHints).
+                setSnapPreventions(snapPreventions).
                 setPathDetails(pathDetails).
                 getHints().
                 put(CALC_POINTS, calcPoints).
diff --git a/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java b/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java
index 995636bd6..507d8da0f 100644
--- a/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java
+++ b/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java
@@ -166,7 +166,7 @@ public class SPTResource {
                         if (edge == null)
                             continue;
 
-                        if (col.equals(Parameters.DETAILS.STREET_NAME)) {
+                        if (col.equals(Parameters.Details.STREET_NAME)) {
                             sb.append(edge.getName().replaceAll(",", ""));
                             continue;
                         }


Test patch:
diff --git a/core/src/test/java/com/graphhopper/routing/PathTest.java b/core/src/test/java/com/graphhopper/routing/PathTest.java
index ef040594d..f816aa4de 100644
--- a/core/src/test/java/com/graphhopper/routing/PathTest.java
+++ b/core/src/test/java/com/graphhopper/routing/PathTest.java
@@ -34,7 +34,7 @@ import org.junit.Test;
 import java.util.*;
 
 import static com.graphhopper.storage.AbstractGraphStorageTester.assertPList;
-import static com.graphhopper.util.Parameters.DETAILS.*;
+import static com.graphhopper.util.Parameters.Details.*;
 import static org.junit.Assert.*;
 
 /**
diff --git a/core/src/test/java/com/graphhopper/routing/util/SnapPreventionEdgeFilterTest.java b/core/src/test/java/com/graphhopper/routing/util/SnapPreventionEdgeFilterTest.java
new file mode 100644
index 000000000..e5fa5cfea
--- /dev/null
+++ b/core/src/test/java/com/graphhopper/routing/util/SnapPreventionEdgeFilterTest.java
@@ -0,0 +1,43 @@
+package com.graphhopper.routing.util;
+
+import com.graphhopper.routing.profiles.EnumEncodedValue;
+import com.graphhopper.routing.profiles.RoadClass;
+import com.graphhopper.routing.profiles.RoadEnvironment;
+import com.graphhopper.storage.IntsRef;
+import com.graphhopper.util.EdgeIteratorState;
+import com.graphhopper.util.GHUtility;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+public class SnapPreventionEdgeFilterTest {
+
+    @Test
+    public void accept() {
+        EdgeFilter trueFilter = new EdgeFilter() {
+            @Override
+            public boolean accept(EdgeIteratorState edgeState) {
+                return true;
+            }
+        };
+        EncodingManager em = GHUtility.addDefaultEncodedValues(new EncodingManager.Builder(4)).build();
+        EnumEncodedValue<RoadClass> rcEnc = em.getEnumEncodedValue(RoadClass.KEY, RoadClass.class);
+        EnumEncodedValue<RoadEnvironment> reEnc = em.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class);
+        SnapPreventionEdgeFilter filter = new SnapPreventionEdgeFilter(trueFilter, rcEnc, reEnc, Arrays.asList("motorway", "ferry"));
+
+        IntsRef intsRef = em.createEdgeFlags();
+        assertTrue(filter.accept(GHUtility.createMockedEdgeIteratorState(1, intsRef)));
+        reEnc.setEnum(false, intsRef, RoadEnvironment.FERRY);
+        assertFalse(filter.accept(GHUtility.createMockedEdgeIteratorState(1, intsRef)));
+        reEnc.setEnum(false, intsRef, RoadEnvironment.FORD);
+        assertTrue(filter.accept(GHUtility.createMockedEdgeIteratorState(1, intsRef)));
+
+        rcEnc.setEnum(false, intsRef, RoadClass.RESIDENTIAL);
+        assertTrue(filter.accept(GHUtility.createMockedEdgeIteratorState(1, intsRef)));
+        rcEnc.setEnum(false, intsRef, RoadClass.MOTORWAY);
+        assertFalse(filter.accept(GHUtility.createMockedEdgeIteratorState(1, intsRef)));
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java b/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java
index 047a594f8..89a6df952 100644
--- a/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java
+++ b/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java
@@ -30,7 +30,7 @@ import com.graphhopper.routing.weighting.ShortestWeighting;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.GraphBuilder;
 import com.graphhopper.storage.NodeAccess;
-import com.graphhopper.util.Parameters.DETAILS;
+import com.graphhopper.util.Parameters.Details;
 import com.graphhopper.util.details.PathDetail;
 import com.graphhopper.util.details.PathDetailsBuilderFactory;
 import org.junit.Before;
@@ -139,7 +139,7 @@ public class PathSimplificationTest {
         // Path is: [0 0-1, 3 1-4, 6 4-7, 9 7-8, 11 8-9, 10 9-10]
         Path p = new Dijkstra(g, new ShortestWeighting(carEncoder), tMode).calcPath(0, 10);
         InstructionList wayList = p.calcInstructions(carManager.getBooleanEncodedValue(Roundabout.KEY), usTR);
-        Map<String, List<PathDetail>> details = p.calcDetails(Arrays.asList(DETAILS.AVERAGE_SPEED), new PathDetailsBuilderFactory(), 0);
+        Map<String, List<PathDetail>> details = p.calcDetails(Arrays.asList(Details.AVERAGE_SPEED), new PathDetailsBuilderFactory(), 0);
 
         PathWrapper pathWrapper = new PathWrapper();
         pathWrapper.setInstructions(wayList);
diff --git a/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java b/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
index 18411a825..336dea3e5 100644
--- a/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
+++ b/reader-osm/src/test/java/com/graphhopper/GraphHopperIT.java
@@ -23,7 +23,6 @@ import com.graphhopper.routing.ch.CHAlgoFactoryDecorator;
 import com.graphhopper.routing.util.CarFlagEncoder;
 import com.graphhopper.routing.util.DefaultFlagEncoderFactory;
 import com.graphhopper.routing.util.EncodingManager;
-import com.graphhopper.routing.util.FlagEncoderFactory;
 import com.graphhopper.util.*;
 import com.graphhopper.util.Parameters.CH;
 import com.graphhopper.util.Parameters.Landmark;
@@ -440,14 +439,14 @@ public class GraphHopperIT {
         request.addPoint(new GHPoint(43.74958, 7.436566));
         request.addPoint(new GHPoint(43.727687, 7.418737));
         request.setAlgorithm(ASTAR).setVehicle(vehicle).setWeighting(weightCalcStr);
-        request.setPathDetails(Arrays.asList(Parameters.DETAILS.AVERAGE_SPEED));
+        request.setPathDetails(Arrays.asList(Parameters.Details.AVERAGE_SPEED));
 
         GHResponse rsp = hopper.route(request);
 
         PathWrapper arsp = rsp.getBest();
         Map<String, List<PathDetail>> details = arsp.getPathDetails();
         assertTrue(details.size() == 1);
-        List<PathDetail> detailList = details.get(Parameters.DETAILS.AVERAGE_SPEED);
+        List<PathDetail> detailList = details.get(Parameters.Details.AVERAGE_SPEED);
         assertEquals(1, detailList.size());
         assertEquals(5.0, detailList.get(0).getValue());
         assertEquals(0, detailList.get(0).getFirst());
@@ -905,7 +904,7 @@ public class GraphHopperIT {
                         addPoint(new GHPoint(49.984565, 11.499188)).
                         addPoint(new GHPoint(49.9847, 11.499612)).
                         setVehicle("car").setWeighting("fastest").
-                        setPathDetails(Arrays.asList(Parameters.DETAILS.AVERAGE_SPEED));
+                        setPathDetails(Arrays.asList(Parameters.Details.AVERAGE_SPEED));
 
         GHResponse rsp = tmpHopper.route(req);
 
@@ -925,7 +924,7 @@ public class GraphHopperIT {
                 addPoint(new GHPoint(49.984352, 11.498802)).
                 addPoint(new GHPoint(49.984352, 11.498802)).
                 setVehicle("car").setWeighting("fastest").
-                setPathDetails(Arrays.asList(Parameters.DETAILS.AVERAGE_SPEED));
+                setPathDetails(Arrays.asList(Parameters.Details.AVERAGE_SPEED));
 
         GHResponse rsp = tmpHopper.route(req);
 
diff --git a/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java b/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java
index b67113769..f87f41538 100644
--- a/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java
@@ -99,7 +99,7 @@ public class RouteResourceTest {
     }
 
     @Test
-    public void testQueryWithDirections() throws Exception {
+    public void testQueryWithDirections() {
         // Note, in general specifying directions does not work with CH, but this is an example where it works
         final Response response = app.client().target("http://localhost:8080/route?" + "point=42.496696,1.499323&point=42.497257,1.501501&heading=240&heading=240&ch.force_heading=true").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
@@ -113,7 +113,7 @@ public class RouteResourceTest {
     }
 
     @Test
-    public void testQueryWithStraightVia() throws Exception {
+    public void testQueryWithStraightVia() {
         // Note, in general specifying pass_through does not work with CH, but this is an example where it works
         final Response response = app.client().target("http://localhost:8080/route?point=42.534133,1.581473&point=42.534781,1.582149&point=42.535042,1.582514&pass_through=true").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
@@ -127,7 +127,7 @@ public class RouteResourceTest {
     }
 
     @Test
-    public void testJsonRounding() throws Exception {
+    public void testJsonRounding() {
         final Response response = app.client().target("http://localhost:8080/route?point=42.554851234,1.536198&point=42.510071,1.548128&points_encoded=false").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
@@ -136,7 +136,7 @@ public class RouteResourceTest {
     }
 
     @Test
-    public void testFailIfElevationRequestedButNotIncluded() throws Exception {
+    public void testFailIfElevationRequestedButNotIncluded() {
         final Response response = app.client().target("http://localhost:8080/route?point=42.554851234,1.536198&point=42.510071,1.548128&points_encoded=false&elevation=true").request().buildGet().invoke();
         assertEquals(400, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
@@ -229,7 +229,7 @@ public class RouteResourceTest {
     }
 
     @Test
-    public void testPathDetailsSamePoint() throws Exception {
+    public void testPathDetailsSamePoint() {
         GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
         assertTrue(hopper.load("http://localhost:8080/route"));
         GHRequest request = new GHRequest(42.554851, 1.536198, 42.554851, 1.536198);
@@ -240,7 +240,7 @@ public class RouteResourceTest {
     }
 
     @Test
-    public void testPathDetailsNoConnection() throws Exception {
+    public void testPathDetailsNoConnection() {
         GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
         assertTrue(hopper.load("http://localhost:8080/route"));
         GHRequest request = new GHRequest(42.542078, 1.45586, 42.537841, 1.439981);
@@ -290,6 +290,37 @@ public class RouteResourceTest {
         assertEquals("Carrer Antoni Fiter i Rossell", rsp.getBest().getInstructions().get(3).getName());
     }
 
+    @Test
+    public void testSnapPreventions() {
+        GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
+        assertTrue(hopper.load("http://localhost:8080/route"));
+        GHRequest request = new GHRequest(42.511139, 1.53285, 42.508165, 1.532271);
+        GHResponse rsp = hopper.route(request);
+        assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
+        assertEquals(490, rsp.getBest().getDistance(), 2);
+
+        request.setSnapPreventions(Arrays.asList("tunnel"));
+        rsp = hopper.route(request);
+        assertEquals(1081, rsp.getBest().getDistance(), 2);
+    }
+
+    @Test
+    public void testSnapPreventionsAndPointHints() {
+        GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
+        assertTrue(hopper.load("http://localhost:8080/route"));
+        GHRequest request = new GHRequest(42.511139, 1.53285, 42.508165, 1.532271);
+        request.setSnapPreventions(Arrays.asList("tunnel"));
+        request.setPointHints(Arrays.asList("Avinguda Fiter i Rossell", ""));
+        GHResponse rsp = hopper.route(request);
+        assertEquals(1590, rsp.getBest().getDistance(), 2);
+
+        // contradicting hints should still allow routing
+        request.setSnapPreventions(Arrays.asList("tunnel"));
+        request.setPointHints(Arrays.asList("Tunèl del Pont Pla", ""));
+        rsp = hopper.route(request);
+        assertEquals(490, rsp.getBest().getDistance(), 2);
+    }
+
     @Test
     public void testGraphHopperWebRealExceptions() {
         GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
@@ -339,7 +370,7 @@ public class RouteResourceTest {
     }
 
     @Test
-    public void testGPXWithExcludedRouteSelection() throws Exception {
+    public void testGPXWithExcludedRouteSelection() {
         final Response response = app.client().target("http://localhost:8080/route?point=42.554851,1.536198&point=42.510071,1.548128&type=gpx&gpx.route=false&gpx.waypoints=false").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         String str = response.readEntity(String.class);
@@ -349,7 +380,7 @@ public class RouteResourceTest {
     }
 
     @Test
-    public void testGPXWithTrackAndWaypointsSelection() throws Exception {
+    public void testGPXWithTrackAndWaypointsSelection() {
         final Response response = app.client().target("http://localhost:8080/route?point=42.554851,1.536198&point=42.510071,1.548128&type=gpx&gpx.track=true&gpx.route=false&gpx.waypoints=true").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         String str = response.readEntity(String.class);
@@ -370,7 +401,7 @@ public class RouteResourceTest {
     }
 
     @Test
-    public void testWithError() throws Exception {
+    public void testWithError() {
         final Response response = app.client().target("http://localhost:8080/route?point=42.554851,1.536198").request().buildGet().invoke();
         assertEquals(400, response.getStatus());
     }