Repository: graphhopper/graphhopper
Pull request #1928 (2020-02-25 10:46:42)

GraphHopperServerConfiguration auto-detects available ports
Removed fixation to port 8080. Each test class detects an available port itself now.

karussell: What is the use case here? Is it to have parallel tests?
easbar: I sometimes ran into this issue when something else on my system is running on port 8080 (this port is quite commonly used by all kinds of software)
taulinger: > I sometimes run into this issue when something else on my system is running on port 8080 (this port is quite commonly used by all kinds of software)

That's the point. In our case, we build GraphHopper on our internal CI-System, where port 8080 is also already in use.
karussell: Ok. Makes sense. Can we make creating this URL somehow easier? E.g. with a helper method?

```java
Builder clientRequest(App app, String path) {
   if(!path.startsWith("/")) path = "/" + path;
   return app.client().target("http://localhost:"+ app.getLocalPort() + path).request();
}
```

And instead of changing the production GraphHopperServerConfiguration can we do this only in the tests? E.g. extend it for a test config?

Patch:
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 19620aac9..27a685e82 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -67,6 +67,7 @@ Here is an overview:
  * stefanholder, Stefan Holder, BMW AG, refactored unfavoring of virtual edges #885
  * stevensnoeijen, fixes like #1568
  * Svantulden, improved documentation and nearest API
+ * taulinger, hopefully more to come 
  * thehereward, code cleanups like #620
  * vvikas, ideas for many to many improvements and #616
  * zstadler, multiple fixes and car4wd
diff --git a/api/src/main/java/com/graphhopper/util/DistanceCalc.java b/api/src/main/java/com/graphhopper/util/DistanceCalc.java
index 13418e866..03f753bd0 100644
--- a/api/src/main/java/com/graphhopper/util/DistanceCalc.java
+++ b/api/src/main/java/com/graphhopper/util/DistanceCalc.java
@@ -38,6 +38,11 @@ public interface DistanceCalc {
      */
     double calcDist(double fromLat, double fromLon, double toLat, double toLon);
 
+    /**
+     * Calculates 3d distance of (from, to) in meter.
+     */
+    double calcDist3D(double fromLat, double fromLon, double fromEle, double toLat, double toLon, double toEle);
+
     /**
      * Returns the specified length in normalized meter.
      */
@@ -85,6 +90,15 @@ public interface DistanceCalc {
                                       double a_lat_deg, double a_lon_deg,
                                       double b_lat_deg, double b_lon_deg);
 
+    /**
+     * This method calculates the distance from r to edge (a, b) where the crossing point is c including elevation
+     *
+     * @return the distance in normalized meter
+     */
+    double calcNormalizedEdgeDistance3D(double r_lat_deg, double r_lon_deg, double r_ele_m,
+                                        double a_lat_deg, double a_lon_deg, double a_ele_m,
+                                        double b_lat_deg, double b_lon_deg, double b_ele_m);
+
     /**
      * @return the crossing point c of the vertical line from r to line (a, b)
      */
diff --git a/api/src/main/java/com/graphhopper/util/DistanceCalcEarth.java b/api/src/main/java/com/graphhopper/util/DistanceCalcEarth.java
index 4c7bdbac5..5262a5dc7 100644
--- a/api/src/main/java/com/graphhopper/util/DistanceCalcEarth.java
+++ b/api/src/main/java/com/graphhopper/util/DistanceCalcEarth.java
@@ -39,6 +39,7 @@ public class DistanceCalcEarth implements DistanceCalc {
      */
     public final static double C = 2 * PI * R;
     public final static double KM_MILE = 1.609344;
+    public final static double METERS_PER_DEGREE = C / 360.0;
 
     /**
      * Calculates distance of (from, to) in meter.
@@ -52,6 +53,20 @@ public class DistanceCalcEarth implements DistanceCalc {
         return R * 2 * asin(sqrt(normedDist));
     }
 
+    /**
+     * This implements a rather quick solution to calculate 3D distances on earth using euclidean
+     * geometry mixed with Haversine formula used for the on earth distance. The haversine formula makes
+     * not so much sense as it is only important for large distances where then the rather smallish
+     * heights would becomes negligible.
+     */
+    @Override
+    public double calcDist3D(double fromLat, double fromLon, double fromHeight,
+                             double toLat, double toLon, double toHeight) {
+        double eleDelta = hasElevationDiff(fromHeight, toHeight) ? (toHeight - fromHeight) : 0;
+        double len = calcDist(fromLat, fromLon, toLat, toLon);
+        return Math.sqrt(eleDelta * eleDelta + len * len);
+    }
+
     @Override
     public double calcDenormalizedDist(double normedDist) {
         return R * 2 * asin(sqrt(normedDist));
@@ -107,6 +122,41 @@ public class DistanceCalcEarth implements DistanceCalc {
         return calcNormalizedEdgeDistanceNew(r_lat_deg, r_lon_deg, a_lat_deg, a_lon_deg, b_lat_deg, b_lon_deg, false);
     }
 
+    @Override
+    public double calcNormalizedEdgeDistance3D(double r_lat_deg, double r_lon_deg, double r_ele_m,
+                                               double a_lat_deg, double a_lon_deg, double a_ele_m,
+                                               double b_lat_deg, double b_lon_deg, double b_ele_m) {
+        if (Double.isNaN(r_ele_m) || Double.isNaN(a_ele_m) || Double.isNaN(b_ele_m))
+            return calcNormalizedEdgeDistance(r_lat_deg, r_lon_deg, a_lat_deg, a_lon_deg, b_lat_deg, b_lon_deg);
+
+        double shrinkFactor = calcShrinkFactor(a_lat_deg, b_lat_deg);
+
+        double a_lat = a_lat_deg;
+        double a_lon = a_lon_deg * shrinkFactor;
+        double a_ele = a_ele_m / METERS_PER_DEGREE;
+
+        double b_lat = b_lat_deg;
+        double b_lon = b_lon_deg * shrinkFactor;
+        double b_ele = b_ele_m / METERS_PER_DEGREE;
+
+        double r_lat = r_lat_deg;
+        double r_lon = r_lon_deg * shrinkFactor;
+        double r_ele = r_ele_m / METERS_PER_DEGREE;
+
+        double delta_lon = b_lon - a_lon;
+        double delta_lat = b_lat - a_lat;
+        double delta_ele = b_ele - a_ele;
+
+        double norm = delta_lon * delta_lon + delta_lat * delta_lat + delta_ele * delta_ele;
+        double factor = ((r_lon - a_lon) * delta_lon + (r_lat - a_lat) * delta_lat + (r_ele - a_ele) * delta_ele) / norm;
+
+        // x,y,z is projection of r onto segment a-b
+        double c_lon = a_lon + factor * delta_lon;
+        double c_lat = a_lat + factor * delta_lat;
+        double c_ele_m = (a_ele + factor * delta_ele) * METERS_PER_DEGREE;
+        return calcNormalizedDist(c_lat, c_lon / shrinkFactor, r_lat_deg, r_lon_deg) + calcNormalizedDist(r_ele_m - c_ele_m);
+    }
+
     /**
      * New edge distance calculation where no validEdgeDistance check would be necessary
      * <p>
@@ -116,7 +166,8 @@ public class DistanceCalcEarth implements DistanceCalc {
      */
     public double calcNormalizedEdgeDistanceNew(double r_lat_deg, double r_lon_deg,
                                                 double a_lat_deg, double a_lon_deg,
-                                                double b_lat_deg, double b_lon_deg, boolean reduceToSegment) {
+                                                double b_lat_deg, double b_lon_deg,
+                                                boolean reduceToSegment) {
         double shrinkFactor = calcShrinkFactor(a_lat_deg, b_lat_deg);
 
         double a_lat = a_lat_deg;
@@ -136,7 +187,7 @@ public class DistanceCalcEarth implements DistanceCalc {
             return calcNormalizedDist(a_lat_deg, r_lon_deg, r_lat_deg, r_lon_deg);
 
         if (delta_lon == 0)
-            // special case: vertical edge        
+            // special case: vertical edge
             return calcNormalizedDist(r_lat_deg, a_lon_deg, r_lat_deg, r_lon_deg);
 
         double norm = delta_lon * delta_lon + delta_lat * delta_lat;
@@ -254,6 +305,10 @@ public class DistanceCalcEarth implements DistanceCalc {
         return abs(lon1 - lon2) > 300;
     }
 
+    protected boolean hasElevationDiff(double a, double b) {
+        return a != b && !Double.isNaN(a) && !Double.isNaN(b);
+    }
+
     @Override
     public String toString() {
         return "EXACT";
diff --git a/api/src/main/java/com/graphhopper/util/DistancePlaneProjection.java b/api/src/main/java/com/graphhopper/util/DistancePlaneProjection.java
index 74088b39d..fd040d296 100644
--- a/api/src/main/java/com/graphhopper/util/DistancePlaneProjection.java
+++ b/api/src/main/java/com/graphhopper/util/DistancePlaneProjection.java
@@ -36,14 +36,18 @@ import static java.lang.Math.*;
 public class DistancePlaneProjection extends DistanceCalcEarth {
     @Override
     public double calcDist(double fromLat, double fromLon, double toLat, double toLon) {
-        double dLat = toRadians(toLat - fromLat);
-        double dLon = toRadians(toLon - fromLon);
-        // use mean latitude as reference point for delta_lon
-        double tmp = cos(toRadians((fromLat + toLat) / 2)) * dLon;
-        double normedDist = dLat * dLat + tmp * tmp;
+        double normedDist = calcNormalizedDist(fromLat, fromLon, toLat, toLon);
         return R * sqrt(normedDist);
     }
 
+    @Override
+    public double calcDist3D(double fromLat, double fromLon, double fromHeight,
+                             double toLat, double toLon, double toHeight) {
+        double dEleNorm = hasElevationDiff(fromHeight, toHeight) ? calcNormalizedDist(toHeight - fromHeight) : 0;
+        double normedDist = calcNormalizedDist(fromLat, fromLon, toLat, toLon);
+        return R * sqrt(normedDist + dEleNorm);
+    }
+
     @Override
     public double calcDenormalizedDist(double normedDist) {
         return R * sqrt(normedDist);
diff --git a/api/src/main/java/com/graphhopper/util/Helper.java b/api/src/main/java/com/graphhopper/util/Helper.java
index 60b3b32c6..8695f3ec4 100644
--- a/api/src/main/java/com/graphhopper/util/Helper.java
+++ b/api/src/main/java/com/graphhopper/util/Helper.java
@@ -36,7 +36,6 @@ import java.util.Map.Entry;
  */
 public class Helper {
     public static final DistanceCalc DIST_EARTH = new DistanceCalcEarth();
-    public static final DistanceCalc3D DIST_3D = new DistanceCalc3D();
     public static final DistancePlaneProjection DIST_PLANE = new DistancePlaneProjection();
     public static final AngleCalc ANGLE_CALC = new AngleCalc();
     public static final Charset UTF_CS = Charset.forName("UTF-8");
diff --git a/api/src/main/java/com/graphhopper/util/PointList.java b/api/src/main/java/com/graphhopper/util/PointList.java
index 69aec762e..77410bf04 100644
--- a/api/src/main/java/com/graphhopper/util/PointList.java
+++ b/api/src/main/java/com/graphhopper/util/PointList.java
@@ -152,7 +152,6 @@ public class PointList implements Iterable<GHPoint3D>, PointAccess {
         }
     };
 
-    private final static DistanceCalc3D distCalc3D = new DistanceCalc3D();
     final static String ERR_MSG = "Tried to access PointList with too big index!";
     protected int size = 0;
     protected boolean is3D;
@@ -549,7 +548,7 @@ public class PointList implements Iterable<GHPoint3D>, PointAccess {
         for (int i = 0; i < size(); i++) {
             if (i > 0) {
                 if (is3D())
-                    dist += distCalc3D.calcDist(prevLat, prevLon, prevEle, getLat(i), getLon(i), getEle(i));
+                    dist += calc.calcDist3D(prevLat, prevLon, prevEle, getLat(i), getLon(i), getEle(i));
                 else
                     dist += calc.calcDist(prevLat, prevLon, getLat(i), getLon(i));
             }
diff --git a/benchmark/benchmark.sh b/benchmark/benchmark.sh
index 0548f7cf7..90f5098dc 100755
--- a/benchmark/benchmark.sh
+++ b/benchmark/benchmark.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 # usage:
-# benchmark/benchmark.sh <data_dir> <results_dir> <small_osm_map_path> <big_osm_map_path> <use_measurement_time_as_ref_time>
+# benchmark/benchmark.sh <data_dir> <results_dir> <small_osm_map_path> <big_osm_map_path> <use_measurement_time_as_ref_time> <spatial_rule_borders_dir>
 #
 # where:
 # <data_dir> = base directory used to store results
@@ -8,6 +8,7 @@
 # <small_osm_map_path> = path to osm map the measurement is run on for slow measurements
 # <big_osm_map_path> = path to osm map the measurement is run on for fast measurements
 # <use_measurement_time_as_ref_time> = true/false, false by default, meaning the git commit time will be used as reference
+# <spatial_rule_borders_dir> = base directory to load borders of spatial rules from
 
 # make this script exit if a command fails, a variable is missing etc.
 set -euo pipefail
@@ -17,6 +18,7 @@ defaultSingleResultsDir=measurements/results/$(date '+%d-%m-%Y-%s%N')/
 defaultSmallMap=core/files/andorra.osm.pbf
 defaultBigMap=core/files/andorra.osm.pbf
 defaultUseMeasurementTimeAsRefTime=false
+defaultBordersDirectory=core/files/spatialrules
 
 # this is the directory where we read/write data from/to
 DATA_DIR=${1:-$defaultDataDir}
@@ -26,6 +28,7 @@ SINGLE_RESULTS_DIR=${2:-$defaultSingleResultsDir}
 SMALL_OSM_MAP=${3:-$defaultSmallMap}
 BIG_OSM_MAP=${4:-$defaultBigMap}
 USE_MEASUREMENT_TIME_AS_REF_TIME=${5:-$defaultUseMeasurementTimeAsRefTime}
+BORDERS_DIRECTORY=${6:-$defaultBordersDirectory}
 
 # create directories
 mkdir -p ${DATA_DIR}
@@ -51,6 +54,7 @@ measurement.lm=false \
 graph.location=${TMP_DIR}measurement-small-gh \
 prepare.min_network_size=10000 \
 prepare.min_oneway_network_size=10000 \
+spatial_rules.borders_directory=${BORDERS_DIRECTORY} \
 measurement.json=true \
 measurement.count=5000 \
 measurement.use_measurement_time_as_ref_time=${USE_MEASUREMENT_TIME_AS_REF_TIME}
@@ -72,6 +76,7 @@ measurement.lm=true \
 graph.location=${TMP_DIR}measurement-big-gh \
 prepare.min_network_size=10000 \
 prepare.min_oneway_network_size=10000 \
+spatial_rules.borders_directory=${BORDERS_DIRECTORY} \
 measurement.json=true \
 measurement.count=5000 \
 measurement.use_measurement_time_as_ref_time=${USE_MEASUREMENT_TIME_AS_REF_TIME}
diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java
index 02dc21905..f2f76a827 100644
--- a/core/src/main/java/com/graphhopper/GraphHopper.java
+++ b/core/src/main/java/com/graphhopper/GraphHopper.java
@@ -24,10 +24,7 @@ import com.graphhopper.json.geo.JsonFeature;
 import com.graphhopper.reader.DataReader;
 import com.graphhopper.reader.dem.*;
 import com.graphhopper.reader.osm.conditional.DateRangeParser;
-import com.graphhopper.routing.AlgorithmOptions;
-import com.graphhopper.routing.Path;
-import com.graphhopper.routing.RoutingAlgorithmFactory;
-import com.graphhopper.routing.RoutingAlgorithmFactorySimple;
+import com.graphhopper.routing.*;
 import com.graphhopper.routing.ch.CHPreparationHandler;
 import com.graphhopper.routing.ch.CHRoutingAlgorithmFactory;
 import com.graphhopper.routing.lm.LMPreparationHandler;
@@ -105,6 +102,7 @@ public class GraphHopper implements GraphHopperAPI {
     private boolean smoothElevation = false;
     // for routing
     private final RoutingConfig routingConfig = new RoutingConfig();
+    private ProfileResolver profileResolver = new ProfileResolver();
 
     // for index
     private LocationIndex locationIndex;
@@ -142,14 +140,11 @@ public class GraphHopper implements GraphHopperAPI {
         return this;
     }
 
-    /**
-     * @return the first flag encoder of the encoding manager
-     */
     FlagEncoder getDefaultVehicle() {
         if (encodingManager == null)
             throw new IllegalStateException("No encoding manager specified or loaded");
 
-        return encodingManager.fetchEdgeEncoders().get(0);
+        return profileResolver.getDefaultVehicle(encodingManager);
     }
 
     public EncodingManager getEncodingManager() {
@@ -333,6 +328,10 @@ public class GraphHopper implements GraphHopperAPI {
         return this;
     }
 
+    public List<ProfileConfig> getProfiles() {
+        return new ArrayList<>(profilesByName.values());
+    }
+
     public int getMaxVisitedNodes() {
         return routingConfig.getMaxVisitedNodes();
     }
@@ -492,6 +491,15 @@ public class GraphHopper implements GraphHopperAPI {
         return this;
     }
 
+    public ProfileResolver getProfileResolver() {
+        return this.profileResolver;
+    }
+
+    public GraphHopper setProfileResolver(ProfileResolver profileResolver) {
+        this.profileResolver = profileResolver;
+        return this;
+    }
+
     /**
      * Reads the configuration from a {@link GraphHopperConfig} object which can be manually filled, or more typically
      * is read from `config.yml`.
@@ -587,9 +595,11 @@ public class GraphHopper implements GraphHopperAPI {
     private static ElevationProvider createElevationProvider(GraphHopperConfig ghConfig) {
         String eleProviderStr = toLowerCase(ghConfig.get("graph.elevation.provider", "noop"));
 
-        // keep fallback until 0.8
-        boolean eleCalcMean = ghConfig.has("graph.elevation.calcmean")
-                ? ghConfig.getBool("graph.elevation.calcmean", false)
+        if (ghConfig.has("graph.elevation.calcmean"))
+            throw new IllegalArgumentException("graph.elevation.calcmean is deprecated, use graph.elevation.interpolate");
+
+        boolean interpolate = ghConfig.has("graph.elevation.interpolate")
+                ? "bilinear".equals(ghConfig.get("graph.elevation.interpolate", "none"))
                 : ghConfig.getBool("graph.elevation.calc_mean", false);
 
         String cacheDirStr = ghConfig.get("graph.elevation.cache_dir", "");
@@ -618,7 +628,7 @@ public class GraphHopper implements GraphHopperAPI {
         }
 
         elevationProvider.setAutoRemoveTemporaryFiles(removeTempElevationFiles);
-        elevationProvider.setCalcMean(eleCalcMean);
+        elevationProvider.setInterpolate(interpolate);
         if (!baseURL.isEmpty())
             elevationProvider.setBaseURL(baseURL);
         elevationProvider.setDAType(elevationDAType);
@@ -785,7 +795,6 @@ public class GraphHopper implements GraphHopperAPI {
 
         ghStorage.addCHGraphs(chProfiles);
 
-
         if (!new File(graphHopperFolder).exists())
             return false;
 
@@ -824,9 +833,11 @@ public class GraphHopper implements GraphHopperAPI {
                         "\nYou need to add `|turn_costs=true` to the vehicle in `graph.flag_encoders`");
             }
             try {
-                createWeighting(new HintsMap(profile.getWeighting()), encoder, NO_TURN_COST_PROVIDER);
+                createWeighting(profile, new PMap());
             } catch (IllegalArgumentException e) {
-                throw new IllegalArgumentException("The profile '" + profile.getName() + "' was configured with an unknown weighting '" + profile.getWeighting() + "'");
+                throw new IllegalArgumentException("Could not create weighting for profile: '" + profile.getName() + "'.\n" +
+                        "Profile: " + profile + "\n" +
+                        "Error: " + e.getMessage());
             }
         }
         Set<String> chProfileSet = new LinkedHashSet<>(chPreparationHandler.getCHProfileConfigs().size());
@@ -851,9 +862,14 @@ public class GraphHopper implements GraphHopperAPI {
         }
     }
 
-    public RoutingAlgorithmFactory getAlgorithmFactory(HintsMap map) {
-        boolean disableCH = map.getBool(Parameters.CH.DISABLE, false);
-        boolean disableLM = map.getBool(Parameters.Landmark.DISABLE, false);
+    public ProfileConfig resolveProfile(HintsMap hints) {
+        if (encodingManager == null)
+            throw new IllegalStateException("No encoding manager specified or loaded");
+
+        return profileResolver.resolveProfile(encodingManager, chPreparationHandler.getCHProfiles(), lmPreparationHandler.getLMProfiles(), hints);
+    }
+
+    public RoutingAlgorithmFactory getAlgorithmFactory(String profile, boolean disableCH, boolean disableLM) {
         if (chPreparationHandler.isEnabled() && disableCH && !chPreparationHandler.isDisablingAllowed()) {
             throw new IllegalArgumentException("Disabling CH is not allowed on the server side");
         }
@@ -863,9 +879,9 @@ public class GraphHopper implements GraphHopperAPI {
 
         // for now do not allow mixing CH&LM #1082,#1889
         if (chPreparationHandler.isEnabled() && !disableCH) {
-            return chPreparationHandler.getAlgorithmFactory(map);
+            return chPreparationHandler.getAlgorithmFactory(profile);
         } else if (lmPreparationHandler.isEnabled() && !disableLM) {
-            return lmPreparationHandler.getAlgorithmFactory(map);
+            return lmPreparationHandler.getAlgorithmFactory(profile);
         } else {
             return new RoutingAlgorithmFactorySimple();
         }
@@ -882,14 +898,10 @@ public class GraphHopper implements GraphHopperAPI {
 
         for (CHProfileConfig chConfig : chPreparationHandler.getCHProfileConfigs()) {
             ProfileConfig profile = profilesByName.get(chConfig.getProfile());
-            FlagEncoder encoder = encodingManager.getEncoder(profile.getVehicle());
             if (profile.isTurnCosts()) {
-                assert encoder.supportsTurnCosts() : "encoder " + encoder + " should support turn costs";
-                int uTurnCosts = profile.getHints().getInt(Routing.U_TURN_COSTS, INFINITE_U_TURN_COSTS);
-                TurnCostProvider turnCostProvider = new DefaultTurnCostProvider(encoder, ghStorage.getTurnCostStorage(), uTurnCosts);
-                chPreparationHandler.addCHProfile(CHProfile.edgeBased(profile.getName(), createWeighting(new HintsMap(profile.getWeighting()), encoder, turnCostProvider)));
+                chPreparationHandler.addCHProfile(CHProfile.edgeBased(profile.getName(), createWeighting(profile, new PMap())));
             } else {
-                chPreparationHandler.addCHProfile(CHProfile.nodeBased(profile.getName(), createWeighting(new HintsMap(profile.getWeighting()), encoder, NO_TURN_COST_PROVIDER)));
+                chPreparationHandler.addCHProfile(CHProfile.nodeBased(profile.getName(), createWeighting(profile, new PMap())));
             }
         }
     }
@@ -904,9 +916,11 @@ public class GraphHopper implements GraphHopperAPI {
 
         for (LMProfileConfig lmConfig : lmPreparationHandler.getLMProfileConfigs()) {
             ProfileConfig profile = profilesByName.get(lmConfig.getProfile());
-            FlagEncoder encoder = encodingManager.getEncoder(profile.getVehicle());
-            // note that we do not consider turn costs during LM preparation?
-            Weighting weighting = createWeighting(new HintsMap(profile.getWeighting()), encoder, NO_TURN_COST_PROVIDER);
+            // Note that turn costs will be ignored during LM preparation even when the created weighting includes
+            // turn costs, because the preparation is running node-based. This is important if we want to allow e.g.
+            // changing the u_turn_costs per request (we have to use the minimum weight settings (= no turn costs) for
+            // the preparation)
+            Weighting weighting = createWeighting(profile, new PMap());
             lmPreparationHandler.addLMProfile(new LMProfile(profile.getName(), weighting));
         }
     }
@@ -983,18 +997,11 @@ public class GraphHopper implements GraphHopperAPI {
     }
 
     /**
-     * Based on the hintsMap and the specified encoder a Weighting instance can be
-     * created. Note that all URL parameters are available in the hintsMap as String if
-     * you use the web module.
-     *
-     * @param hintsMap all parameters influencing the weighting. E.g. parameters coming via
-     *                 GHRequest.getHints or directly via "&amp;api.xy=" from the URL of the web UI
-     * @param encoder  the required vehicle
-     * @return the weighting to be used for route calculation
-     * @see HintsMap
+     * @param profileConfig The profile for which the weighting shall be created
+     * @param hints         Additional hints that can be used to further specify the weighting that shall be created
      */
-    public Weighting createWeighting(HintsMap hintsMap, FlagEncoder encoder, TurnCostProvider turnCostProvider) {
-        return new DefaultWeightingFactory().createWeighting(hintsMap, encoder, turnCostProvider);
+    public Weighting createWeighting(ProfileConfig profileConfig, PMap hints) {
+        return new DefaultWeightingFactory(encodingManager, ghStorage).createWeighting(profileConfig, hints);
     }
 
     @Override
@@ -1017,36 +1024,10 @@ public class GraphHopper implements GraphHopperAPI {
         if (locationIndex == null)
             throw new IllegalStateException("Location index not initialized");
 
-        // default handling
-        String vehicle = request.getVehicle();
-        if (vehicle.isEmpty()) {
-            vehicle = getDefaultVehicle().toString();
-            request.setVehicle(vehicle);
-        }
-
         Lock readLock = readWriteLock.readLock();
         readLock.lock();
         try {
-            if (!encodingManager.hasEncoder(vehicle))
-                throw new IllegalArgumentException("Vehicle not supported: " + vehicle + ". Supported are: " + encodingManager.toString());
-
-            FlagEncoder encoder = encodingManager.getEncoder(vehicle);
             HintsMap hints = request.getHints();
-
-            // we use edge-based routing if the encoder supports turn-costs *unless* the edge_based parameter is set
-            // explicitly.
-            TraversalMode tMode = encoder.supportsTurnCosts() ? 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;
-
-            if (tMode.isEdgeBased() && !encoder.supportsTurnCosts()) {
-                throw new IllegalArgumentException("You need to set up a turn cost storage to make use of edge_based=true, e.g. use car|turn_costs=true");
-            }
-
-            if (!tMode.isEdgeBased() && !request.getCurbsides().isEmpty()) {
-                throw new IllegalArgumentException("To make use of the " + CURBSIDE + " parameter you need to set " + Routing.EDGE_BASED + " to true");
-            }
-
             boolean disableCH = hints.getBool(CH.DISABLE, false);
             if (chPreparationHandler.isEnabled() && !chPreparationHandler.isDisablingAllowed() && disableCH)
                 throw new IllegalArgumentException("Disabling CH not allowed on the server-side");
@@ -1059,7 +1040,7 @@ public class GraphHopper implements GraphHopperAPI {
                 if (request.hasFavoredHeading(0))
                     throw new IllegalArgumentException("The 'heading' parameter is currently not supported for speed mode, you need to disable speed mode with `ch.disable=true`. See issue #483");
 
-                if (request.getHints().getBool(Routing.PASS_THROUGH, false))
+                if (hints.getBool(Routing.PASS_THROUGH, false))
                     throw new IllegalArgumentException("The '" + Parameters.Routing.PASS_THROUGH + "' parameter is currently not supported for speed mode, you need to disable speed mode with `ch.disable=true`. See issue #1765");
             }
 
@@ -1072,7 +1053,13 @@ public class GraphHopper implements GraphHopperAPI {
             // For example see #734
             checkIfPointsAreInBounds(points);
 
-            RoutingAlgorithmFactory algorithmFactory = getAlgorithmFactory(hints);
+            ProfileConfig profile = resolveProfile(hints);
+            if (!profile.isTurnCosts() && !request.getCurbsides().isEmpty())
+                throw new IllegalArgumentException("To make use of the " + CURBSIDE + " parameter you need to use a profile that supports turn costs");
+
+            TraversalMode tMode = profile.isTurnCosts() ? TraversalMode.EDGE_BASED : TraversalMode.NODE_BASED;
+
+            RoutingAlgorithmFactory algorithmFactory = getAlgorithmFactory(profile.getName(), disableCH, disableLM);
             Weighting weighting;
             Graph graph = ghStorage;
             if (chPreparationHandler.isEnabled() && !disableCH) {
@@ -1088,14 +1075,12 @@ public class GraphHopper implements GraphHopperAPI {
                 }
             } else {
                 checkNonChMaxWaypointDistance(points);
-                final int uTurnCostsInt = request.getHints().getInt(Routing.U_TURN_COSTS, INFINITE_U_TURN_COSTS);
+                final int uTurnCostsInt = hints.getInt(Routing.U_TURN_COSTS, INFINITE_U_TURN_COSTS);
                 if (uTurnCostsInt != INFINITE_U_TURN_COSTS && !tMode.isEdgeBased()) {
                     throw new IllegalArgumentException("Finite u-turn costs can only be used for edge-based routing, use `" + Routing.EDGE_BASED + "=true'");
                 }
-                TurnCostProvider turnCostProvider = (encoder.supportsTurnCosts() && tMode.isEdgeBased())
-                        ? new DefaultTurnCostProvider(encoder, ghStorage.getTurnCostStorage(), uTurnCostsInt)
-                        : NO_TURN_COST_PROVIDER;
-                weighting = createWeighting(hints, encoder, turnCostProvider);
+                FlagEncoder encoder = encodingManager.getEncoder(profile.getVehicle());
+                weighting = createWeighting(profile, hints);
                 if (hints.has(Routing.BLOCK_AREA))
                     weighting = new BlockAreaWeighting(weighting, GraphEdgeIdFinder.createBlockArea(ghStorage, locationIndex,
                             points, hints, DefaultEdgeFilter.allEdges(encoder)));
@@ -1117,7 +1102,9 @@ public class GraphHopper implements GraphHopperAPI {
                 throw new IllegalArgumentException("The max_visited_nodes parameter has to be below or equal to:" + routingConfig.getMaxVisitedNodes());
 
             AlgorithmOptions algoOpts = AlgorithmOptions.start().
-                    algorithm(algoStr).traversalMode(tMode).weighting(weighting).
+                    algorithm(algoStr).
+                    traversalMode(tMode).
+                    weighting(weighting).
                     maxVisitedNodes(maxVisitedNodesForRequest).
                     hints(hints).
                     build();
@@ -1203,7 +1190,7 @@ public class GraphHopper implements GraphHopperAPI {
         GHPoint lastPoint = points.get(0);
         GHPoint point;
         double dist;
-        DistanceCalc calc = DIST_3D;
+        DistanceCalc calc = DIST_EARTH;
         for (int i = 1; i < points.size(); i++) {
             point = points.get(i);
             dist = calc.calcDist(lastPoint.getLat(), lastPoint.getLon(), point.getLat(), point.getLon());
@@ -1352,27 +1339,49 @@ public class GraphHopper implements GraphHopperAPI {
     }
 
     private static class DefaultWeightingFactory {
-        public Weighting createWeighting(HintsMap hintsMap, FlagEncoder encoder, TurnCostProvider turnCostProvider) {
-            String weightingStr = toLowerCase(hintsMap.getWeighting());
+        private final EncodingManager encodingManager;
+        private final GraphHopperStorage ghStorage;
+
+        public DefaultWeightingFactory(EncodingManager encodingManager, GraphHopperStorage ghStorage) {
+            this.encodingManager = encodingManager;
+            this.ghStorage = ghStorage;
+        }
+
+        public Weighting createWeighting(ProfileConfig profile, PMap hints) {
+            FlagEncoder encoder = encodingManager.getEncoder(profile.getVehicle());
+            TurnCostProvider turnCostProvider;
+            if (profile.isTurnCosts()) {
+                if (!encoder.supportsTurnCosts())
+                    throw new IllegalArgumentException("Encoder " + encoder + " does not support turn costs");
+                int uTurnCosts = profile.getHints().getInt(Routing.U_TURN_COSTS, INFINITE_U_TURN_COSTS);
+                turnCostProvider = new DefaultTurnCostProvider(encoder, ghStorage.getTurnCostStorage(), uTurnCosts);
+            } else {
+                turnCostProvider = NO_TURN_COST_PROVIDER;
+            }
+
+            String weightingStr = toLowerCase(profile.getWeighting());
+            if (weightingStr.isEmpty())
+                throw new IllegalArgumentException("You have to specify a weighting");
+
             Weighting weighting = null;
 
             if ("shortest".equalsIgnoreCase(weightingStr)) {
                 weighting = new ShortestWeighting(encoder, turnCostProvider);
-            } else if ("fastest".equalsIgnoreCase(weightingStr) || weightingStr.isEmpty()) {
+            } else if ("fastest".equalsIgnoreCase(weightingStr)) {
                 if (encoder.supports(PriorityWeighting.class))
-                    weighting = new PriorityWeighting(encoder, hintsMap, turnCostProvider);
+                    weighting = new PriorityWeighting(encoder, hints, turnCostProvider);
                 else
-                    weighting = new FastestWeighting(encoder, hintsMap, turnCostProvider);
+                    weighting = new FastestWeighting(encoder, hints, turnCostProvider);
             } else if ("curvature".equalsIgnoreCase(weightingStr)) {
                 if (encoder.supports(CurvatureWeighting.class))
-                    weighting = new CurvatureWeighting(encoder, hintsMap, turnCostProvider);
+                    weighting = new CurvatureWeighting(encoder, hints, turnCostProvider);
 
             } else if ("short_fastest".equalsIgnoreCase(weightingStr)) {
-                weighting = new ShortFastestWeighting(encoder, hintsMap, turnCostProvider);
+                weighting = new ShortFastestWeighting(encoder, hints, turnCostProvider);
             }
 
             if (weighting == null)
-                throw new IllegalArgumentException("weighting " + weightingStr + " not supported");
+                throw new IllegalArgumentException("Weighting '" + weightingStr + "' not supported");
 
             return weighting;
         }
diff --git a/core/src/main/java/com/graphhopper/reader/dem/AbstractElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/AbstractElevationProvider.java
index 733a2e1c2..676eab263 100644
--- a/core/src/main/java/com/graphhopper/reader/dem/AbstractElevationProvider.java
+++ b/core/src/main/java/com/graphhopper/reader/dem/AbstractElevationProvider.java
@@ -39,7 +39,7 @@ public abstract class AbstractElevationProvider implements ElevationProvider {
     String baseUrl;
     Directory dir;
     DAType daType = DAType.MMAP;
-    boolean calcMean = false;
+    boolean interpolate = false;
     boolean autoRemoveTemporary = true;
     long sleep = 2000;
 
@@ -55,8 +55,8 @@ public abstract class AbstractElevationProvider implements ElevationProvider {
     }
 
     @Override
-    public void setCalcMean(boolean eleCalcMean) {
-        calcMean = eleCalcMean;
+    public void setInterpolate(boolean interpolate) {
+        this.interpolate = interpolate;
     }
 
     void setSleep(long sleep) {
@@ -97,7 +97,7 @@ public abstract class AbstractElevationProvider implements ElevationProvider {
             return dir;
 
         logger.info(this.toString() + " Elevation Provider, from: " + baseUrl + ", to: " + cacheDir + ", as: " + daType +
-                " using calcmean: " + calcMean);
+                " using interpolate: " + interpolate);
         return dir = new GHDirectory(cacheDir.getAbsolutePath(), daType);
     }
 
diff --git a/core/src/main/java/com/graphhopper/reader/dem/AbstractSRTMElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/AbstractSRTMElevationProvider.java
index 81cbdbb8b..d9176ab35 100644
--- a/core/src/main/java/com/graphhopper/reader/dem/AbstractSRTMElevationProvider.java
+++ b/core/src/main/java/com/graphhopper/reader/dem/AbstractSRTMElevationProvider.java
@@ -127,7 +127,7 @@ public abstract class AbstractSRTMElevationProvider extends AbstractElevationPro
 
             demProvider = new HeightTile(minLat, minLon, width, width, precision, DEGREE, DEGREE);
             cacheData.put(intKey, demProvider);
-            demProvider.setCalcMean(calcMean);
+            demProvider.setInterpolate(interpolate);
             demProvider.setHeights(heights);
         }
 
diff --git a/core/src/main/java/com/graphhopper/reader/dem/AbstractTiffElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/AbstractTiffElevationProvider.java
index 7c32b6ad2..ebf1800a5 100644
--- a/core/src/main/java/com/graphhopper/reader/dem/AbstractTiffElevationProvider.java
+++ b/core/src/main/java/com/graphhopper/reader/dem/AbstractTiffElevationProvider.java
@@ -104,7 +104,7 @@ public abstract class AbstractTiffElevationProvider extends AbstractElevationPro
             int minLon = getMinLonForTile(lon);
             // less restrictive against boundary checking
             demProvider = new HeightTile(minLat, minLon, WIDTH, HEIGHT, LON_DEGREE * precision, LON_DEGREE, LAT_DEGREE);
-            demProvider.setCalcMean(calcMean);
+            demProvider.setInterpolate(interpolate);
 
             cacheData.put(name, demProvider);
             DataAccess heights = getDirectory().find(name + ".gh");
diff --git a/core/src/main/java/com/graphhopper/reader/dem/EdgeElevationInterpolator.java b/core/src/main/java/com/graphhopper/reader/dem/EdgeElevationInterpolator.java
index feb760886..3bcb5881f 100644
--- a/core/src/main/java/com/graphhopper/reader/dem/EdgeElevationInterpolator.java
+++ b/core/src/main/java/com/graphhopper/reader/dem/EdgeElevationInterpolator.java
@@ -157,7 +157,7 @@ public class EdgeElevationInterpolator {
                 }
                 if (count > 2)
                     edge.setWayGeometry(pointList.shallowCopy(1, count - 1, false));
-                edge.setDistance(pointList.calcDistance(Helper.DIST_3D));
+                edge.setDistance(pointList.calcDistance(Helper.DIST_EARTH));
             }
         }
     }
diff --git a/core/src/main/java/com/graphhopper/reader/dem/ElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/ElevationProvider.java
index 1e1cafaf0..7fc3fc7f0 100644
--- a/core/src/main/java/com/graphhopper/reader/dem/ElevationProvider.java
+++ b/core/src/main/java/com/graphhopper/reader/dem/ElevationProvider.java
@@ -48,7 +48,7 @@ public interface ElevationProvider {
         }
 
         @Override
-        public void setCalcMean(boolean eleCalcMean) {
+        public void setInterpolate(boolean interpolate) {
         }
     };
 
@@ -69,10 +69,11 @@ public interface ElevationProvider {
     ElevationProvider setDAType(DAType daType);
 
     /**
-     * Configuration option to include surrounding elevation points when fetching the elevation. Has
-     * only an effect if called before the first getEle call. Turned off by default.
+     * Configuration option to use bilinear interpolation to find the elevation at a point from the
+     * surrounding elevation points. Has only an effect if called before the first getEle call.
+     * Turned off by default.
      */
-    void setCalcMean(boolean calcMean);
+    void setInterpolate(boolean interpolate);
 
     /**
      * Release resources.
diff --git a/core/src/main/java/com/graphhopper/reader/dem/GMTEDProvider.java b/core/src/main/java/com/graphhopper/reader/dem/GMTEDProvider.java
index 1faab6054..37cc11031 100644
--- a/core/src/main/java/com/graphhopper/reader/dem/GMTEDProvider.java
+++ b/core/src/main/java/com/graphhopper/reader/dem/GMTEDProvider.java
@@ -132,7 +132,7 @@ public class GMTEDProvider extends AbstractTiffElevationProvider {
         System.out.println(provider.getEle(48.469123, 9.576393));
 
         // 1113 vs new: (1115)
-        provider.setCalcMean(true);
+        provider.setInterpolate(true);
         System.out.println(provider.getEle(47.467753, 14.573911));
 
         // 0
diff --git a/core/src/main/java/com/graphhopper/reader/dem/HeightTile.java b/core/src/main/java/com/graphhopper/reader/dem/HeightTile.java
index b8f902eab..114469d93 100644
--- a/core/src/main/java/com/graphhopper/reader/dem/HeightTile.java
+++ b/core/src/main/java/com/graphhopper/reader/dem/HeightTile.java
@@ -43,7 +43,9 @@ public class HeightTile {
     private final double lonHigherBound;
     private final double latHigherBound;
     private DataAccess heights;
-    private boolean calcMean;
+    private boolean interpolate;
+    private final double MIN_ELEVATION_METERS = -12_000;
+    private final double MAX_ELEVATION_METERS = 9_000;
 
     public HeightTile(int minLat, int minLon, int width, int height, double precision, int horizontalDegree, int verticalDegree) {
         this.minLat = minLat;
@@ -59,8 +61,8 @@ public class HeightTile {
         this.verticalDegree = verticalDegree;
     }
 
-    public HeightTile setCalcMean(boolean b) {
-        this.calcMean = b;
+    public HeightTile setInterpolate(boolean interpolate) {
+        this.interpolate = interpolate;
         return this;
     }
 
@@ -77,6 +79,20 @@ public class HeightTile {
         this.heights = da;
     }
 
+    private short getHeightSample(int x, int y) {
+        // always keep in mind factor 2 because of short value
+        return heights.getShort(2 * (y * width + x));
+    }
+
+    private boolean isValidElevation(double elevation) {
+        return elevation > MIN_ELEVATION_METERS && elevation < MAX_ELEVATION_METERS;
+    }
+
+    private double linearInterpolate(double a, double b, double f) {
+        // interpolate between a and b but if either are invalid, return the other
+        return !isValidElevation(a) ? b : !isValidElevation(b) ? a : (a + (b - a) * f);
+    }
+
     public double getHeight(double lat, double lon) {
         double deltaLat = Math.abs(lat - minLat);
         double deltaLon = Math.abs(lon - minLon);
@@ -85,47 +101,37 @@ public class HeightTile {
         if (deltaLon > lonHigherBound || deltaLon < lowerBound)
             throw new IllegalStateException("longitude not in boundary of this file:" + lat + "," + lon + ", this:" + this.toString());
 
-        // first row in the file is the northernmost one
-        // http://gis.stackexchange.com/a/43756/9006
-        int lonSimilar = (int) (width / horizontalDegree * deltaLon);
-        // different fallback methods for lat and lon as we have different rounding (lon -> positive, lat -> negative)
-        if (lonSimilar >= width)
-            lonSimilar = width - 1;
-        int latSimilar = height - 1 - (int) (height / verticalDegree * deltaLat);
-        if (latSimilar < 0)
-            latSimilar = 0;
-
-        // always keep in mind factor 2 because of short value
-        int daPointer = 2 * (latSimilar * width + lonSimilar);
-        int value = heights.getShort(daPointer);
-        AtomicInteger counter = new AtomicInteger(1);
-        if (value == Short.MIN_VALUE)
-            return Double.NaN;
-
-        if (calcMean) {
-            if (lonSimilar > 0)
-                value += includePoint(daPointer - 2, counter);
-
-            if (lonSimilar < width - 1)
-                value += includePoint(daPointer + 2, counter);
-
-            if (latSimilar > 0)
-                value += includePoint(daPointer - 2 * width, counter);
-
-            if (latSimilar < height - 1)
-                value += includePoint(daPointer + 2 * width, counter);
+        double elevation;
+        if (interpolate) {
+            double x = (width - 1) * deltaLon / horizontalDegree;
+            double y = (height - 1) * (1 - deltaLat / verticalDegree);
+            int left = (int) x;
+            int top = (int) y;
+            int right = left + 1;
+            int bottom = top + 1;
+
+            double w00 = getHeightSample(left, top);
+            double w01 = getHeightSample(left, bottom);
+            double w10 = getHeightSample(right, top);
+            double w11 = getHeightSample(right, bottom);
+
+            double topEle = linearInterpolate(w00, w10, x - left);
+            double bottomEle = linearInterpolate(w01, w11, x - left);
+            elevation = linearInterpolate(topEle, bottomEle, y - top);
+        } else {
+            // first row in the file is the northernmost one
+            // http://gis.stackexchange.com/a/43756/9006
+            int x = (int) (width / horizontalDegree * deltaLon);
+            // different fallback methods for lat and lon as we have different rounding (lon -> positive, lat -> negative)
+            if (x >= width)
+                x = width - 1;
+            int y = height - 1 - (int) (height / verticalDegree * deltaLat);
+            if (y < 0)
+                y = 0;
+
+            elevation = getHeightSample(x, y);
         }
-
-        return (double) value / counter.get();
-    }
-
-    private double includePoint(int pointer, AtomicInteger counter) {
-        short value = heights.getShort(pointer);
-        if (value == Short.MIN_VALUE)
-            return 0;
-
-        counter.incrementAndGet();
-        return value;
+        return isValidElevation(elevation) ? elevation : Double.NaN;
     }
 
     public void toImage(String imageFile) throws IOException {
diff --git a/core/src/main/java/com/graphhopper/reader/dem/MultiSourceElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/MultiSourceElevationProvider.java
index 570c10105..451a7d4ff 100644
--- a/core/src/main/java/com/graphhopper/reader/dem/MultiSourceElevationProvider.java
+++ b/core/src/main/java/com/graphhopper/reader/dem/MultiSourceElevationProvider.java
@@ -77,9 +77,9 @@ public class MultiSourceElevationProvider implements ElevationProvider {
     }
 
     @Override
-    public void setCalcMean(boolean calcMean) {
-        srtmProvider.setCalcMean(calcMean);
-        globalProvider.setCalcMean(calcMean);
+    public void setInterpolate(boolean interpolate) {
+        srtmProvider.setInterpolate(interpolate);
+        globalProvider.setInterpolate(interpolate);
     }
 
     @Override
diff --git a/core/src/main/java/com/graphhopper/routing/ProfileResolver.java b/core/src/main/java/com/graphhopper/routing/ProfileResolver.java
new file mode 100644
index 000000000..b503b774f
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/ProfileResolver.java
@@ -0,0 +1,209 @@
+/*
+ *  Licensed to GraphHopper GmbH under one or more contributor
+ *  license agreements. See the NOTICE file distributed with this work for
+ *  additional information regarding copyright ownership.
+ *
+ *  GraphHopper GmbH licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except in
+ *  compliance with the License. You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.graphhopper.routing;
+
+import com.graphhopper.config.ProfileConfig;
+import com.graphhopper.routing.lm.LMProfile;
+import com.graphhopper.routing.util.EncodingManager;
+import com.graphhopper.routing.util.FlagEncoder;
+import com.graphhopper.routing.util.HintsMap;
+import com.graphhopper.storage.CHProfile;
+import com.graphhopper.util.Parameters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.graphhopper.routing.weighting.Weighting.INFINITE_U_TURN_COSTS;
+
+public class ProfileResolver {
+
+    public ProfileConfig resolveProfile(EncodingManager encodingManager, List<CHProfile> chProfiles, List<LMProfile> lmProfiles, HintsMap hints) {
+        // default handling
+        String vehicle = hints.getVehicle();
+        if (vehicle.isEmpty()) {
+            vehicle = getDefaultVehicle(encodingManager).toString();
+        }
+        String weighting = hints.getWeighting();
+        if (weighting.isEmpty()) {
+            weighting = "fastest";
+        }
+        if (!encodingManager.hasEncoder(vehicle))
+            throw new IllegalArgumentException("Vehicle not supported: " + vehicle + ". Supported are: " + encodingManager.toString());
+
+        FlagEncoder encoder = encodingManager.getEncoder(vehicle);
+        // we use turn costs if the encoder supports it *unless* the edge_based parameter is set explicitly
+        boolean turnCosts = encoder.supportsTurnCosts();
+        if (hints.has(Parameters.Routing.EDGE_BASED))
+            turnCosts = hints.getBool(Parameters.Routing.EDGE_BASED, false);
+        if (turnCosts && !encoder.supportsTurnCosts())
+            throw new IllegalArgumentException("You need to set up a turn cost storage to make use of edge_based=true, e.g. use car|turn_costs=true");
+
+        String profileName = resolveProfileName(chProfiles, lmProfiles, hints);
+
+        return new ProfileConfig(profileName)
+                .setVehicle(vehicle)
+                .setWeighting(weighting)
+                .setTurnCosts(turnCosts);
+    }
+
+    private String resolveProfileName(List<CHProfile> chProfiles, List<LMProfile> lmProfiles, HintsMap hints) {
+        boolean disableCH = hints.getBool(Parameters.CH.DISABLE, false);
+        boolean disableLM = hints.getBool(Parameters.Landmark.DISABLE, false);
+
+        String profileName;
+        if (!chProfiles.isEmpty() && !disableCH) {
+            profileName = selectCHProfile(chProfiles, hints).getName();
+        } else if (!lmProfiles.isEmpty() && !disableLM) {
+            profileName = selectLMProfile(lmProfiles, hints).getName();
+        } else {
+            // todonow: here we will instead select one of the existing profiles
+            profileName = "unprepared_profile";
+        }
+        return profileName;
+    }
+
+    /**
+     * @param chProfiles the CH profiles to choose from
+     * @param hintsMap   a map used to describe the CH profile that shall be selected
+     * @throws IllegalArgumentException if no CH profile could be selected for the given parameters
+     */
+    public CHProfile selectCHProfile(List<CHProfile> chProfiles, HintsMap hintsMap) {
+        int numMatchingEdgeBased = 0;
+        List<CHProfile> matchingProfiles = new ArrayList<>();
+        for (CHProfile p : chProfiles) {
+            if (!chProfileMatchesHints(p, hintsMap))
+                continue;
+            matchingProfiles.add(p);
+            if (p.isEdgeBased()) {
+                numMatchingEdgeBased++;
+            }
+        }
+
+        Boolean edgeBased = getEdgeBased(hintsMap);
+        Integer uTurnCosts = getUTurnCosts(hintsMap);
+        if (matchingProfiles.isEmpty()) {
+            throw new IllegalArgumentException("Cannot find matching CH profile for your request. Please check your parameters." +
+                    "\nYou can try disabling CH using " + Parameters.CH.DISABLE + "=true" +
+                    "\nrequested:  " + getCHRequestAsString(hintsMap, edgeBased, uTurnCosts) + "\navailable: " + chProfiles);
+        } else if (matchingProfiles.size() == 1) {
+            return matchingProfiles.get(0);
+        } else {
+            // special case: prefer edge-based over node-based if these are the only two options
+            CHProfile match1 = matchingProfiles.get(0);
+            CHProfile match2 = matchingProfiles.get(1);
+            if (edgeBased == null && matchingProfiles.size() == 2 &&
+                    match1.getWeighting().getName().equals(match2.getWeighting().getName()) &&
+                    match1.getWeighting().getFlagEncoder().toString().equals(match2.getWeighting().getFlagEncoder().toString()) &&
+                    match1.isEdgeBased() != match2.isEdgeBased()) {
+                return match1.isEdgeBased() ? match1 : match2;
+            }
+            // special case: error if multiple edge-based matches. to differentiate between these it will be required
+            // to explicitly set the profile parameter.
+            if (numMatchingEdgeBased > 1 && numMatchingEdgeBased == matchingProfiles.size()) {
+                throw new IllegalArgumentException("There are multiple edge-based CH profiles matching your request. You need to" +
+                        " specify the profile you want to use explicitly, see here: https://github.com/graphhopper/graphhopper/pull/1934.");
+            } else {
+                throw new IllegalArgumentException("There are multiple CH profiles matching your request. Use the `weighting`,`vehicle`,`edge_based` and/or `u_turn_costs` parameters to be more specific." +
+                        "\nYou can also try disabling CH altogether using " + Parameters.CH.DISABLE + "=true" +
+                        "\nrequested:  " + getCHRequestAsString(hintsMap, edgeBased, uTurnCosts) + "\nmatched:   " + matchingProfiles + "\navailable: " + chProfiles);
+
+            }
+        }
+    }
+
+    public LMProfile selectLMProfile(List<LMProfile> lmProfiles, HintsMap hintsMap) {
+        List<LMProfile> matchingProfiles = new ArrayList<>();
+        for (LMProfile p : lmProfiles) {
+            if (!lmProfileMatchesHints(p, hintsMap))
+                continue;
+            matchingProfiles.add(p);
+        }
+        // Note:
+        // There are situations where we can use the requested encoder/weighting with an existing LM preparation, even
+        // though the preparation was done with a different weighting. For example this works when the new weighting
+        // only yields higher (but never lower) weights than the one that was used for the preparation. However, its not
+        // trivial to check whether or not this is the case so we do not allow this for now.
+        if (matchingProfiles.isEmpty()) {
+            throw new IllegalArgumentException("Cannot find matching LM profile for your request. Please check your parameters." +
+                    "\nYou can try disabling LM by setting " + Parameters.Landmark.DISABLE + "=true" +
+                    "\nrequested: " + getLMRequestAsString(hintsMap) + "\navailable: " + lmProfilesAsStrings(lmProfiles));
+        } else if (matchingProfiles.size() == 1) {
+            return matchingProfiles.get(0);
+        } else {
+            throw new IllegalArgumentException("There are multiple LM profiles matching your request. Use the `weighting` and `vehicle` parameters to be more specific." +
+                    "\nYou can also try disabling LM altogether using " + Parameters.CH.DISABLE + "=true" +
+                    "\nrequested:  " + getLMRequestAsString(hintsMap) + "\nmatched:   " + lmProfilesAsStrings(matchingProfiles) + "\navailable: " + lmProfilesAsStrings(lmProfiles));
+        }
+    }
+
+    protected boolean chProfileMatchesHints(CHProfile p, HintsMap hintsMap) {
+        Boolean edgeBased = getEdgeBased(hintsMap);
+        Integer uTurnCosts = getUTurnCosts(hintsMap);
+        return (edgeBased == null || p.isEdgeBased() == edgeBased) &&
+                // u-turn costs cannot be used to select one of multiple edge-based CH profiles,
+                // but when they are set only edge-based profiles can match
+                (uTurnCosts == null || p.isEdgeBased()) &&
+                (hintsMap.getWeighting().isEmpty() || p.getWeighting().getName().equals(hintsMap.getWeighting())) &&
+                (hintsMap.getVehicle().isEmpty() || p.getWeighting().getFlagEncoder().toString().equals(hintsMap.getVehicle()));
+    }
+
+    protected boolean lmProfileMatchesHints(LMProfile p, HintsMap hintsMap) {
+        return (hintsMap.getWeighting().isEmpty() || p.getWeighting().getName().equals(hintsMap.getWeighting())) &&
+                (hintsMap.getVehicle().isEmpty() || p.getWeighting().getFlagEncoder().toString().equals(hintsMap.getVehicle()));
+    }
+
+    /**
+     * @return the first flag encoder of the encoding manager
+     */
+    public FlagEncoder getDefaultVehicle(EncodingManager encodingManager) {
+        return encodingManager.fetchEdgeEncoders().get(0);
+    }
+
+    private String getLMRequestAsString(HintsMap map) {
+        return (map.getWeighting().isEmpty() ? "*" : map.getWeighting()) +
+                "|" +
+                (map.getVehicle().isEmpty() ? "*" : map.getVehicle());
+    }
+
+    private String getCHRequestAsString(HintsMap hintsMap, Boolean edgeBased, Integer uTurnCosts) {
+        return (hintsMap.getWeighting().isEmpty() ? "*" : hintsMap.getWeighting()) +
+                "|" +
+                (hintsMap.getVehicle().isEmpty() ? "*" : hintsMap.getVehicle()) +
+                "|" +
+                "edge_based=" + (edgeBased != null ? edgeBased : "*") +
+                "|" +
+                "u_turn_costs=" + (uTurnCosts != null ? uTurnCosts : "*");
+    }
+
+    private List<String> lmProfilesAsStrings(List<LMProfile> profiles) {
+        List<String> result = new ArrayList<>(profiles.size());
+        for (LMProfile p : profiles) {
+            result.add(p.getWeighting().getName() + "|" + p.getWeighting().getFlagEncoder().toString());
+        }
+        return result;
+    }
+
+    private Boolean getEdgeBased(HintsMap hintsMap) {
+        return hintsMap.has(Parameters.Routing.EDGE_BASED) ? hintsMap.getBool(Parameters.Routing.EDGE_BASED, false) : null;
+    }
+
+    private Integer getUTurnCosts(HintsMap hintsMap) {
+        return hintsMap.has(Parameters.Routing.U_TURN_COSTS) ? hintsMap.getInt(Parameters.Routing.U_TURN_COSTS, INFINITE_U_TURN_COSTS) : null;
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/routing/ch/CHPreparationHandler.java b/core/src/main/java/com/graphhopper/routing/ch/CHPreparationHandler.java
index fc13f0300..03d3da7c4 100644
--- a/core/src/main/java/com/graphhopper/routing/ch/CHPreparationHandler.java
+++ b/core/src/main/java/com/graphhopper/routing/ch/CHPreparationHandler.java
@@ -20,7 +20,6 @@ package com.graphhopper.routing.ch;
 import com.graphhopper.GraphHopperConfig;
 import com.graphhopper.config.CHProfileConfig;
 import com.graphhopper.routing.RoutingAlgorithmFactory;
-import com.graphhopper.routing.util.HintsMap;
 import com.graphhopper.storage.CHProfile;
 import com.graphhopper.storage.GraphHopperStorage;
 import com.graphhopper.storage.StorableProperties;
@@ -164,34 +163,29 @@ public class CHPreparationHandler {
 
     /**
      * @return a {@link RoutingAlgorithmFactory} for CH or throw an error if no preparation is available for the given
-     * hints
+     * profile name
      */
-    public RoutingAlgorithmFactory getAlgorithmFactory(HintsMap map) {
-        if (preparations.isEmpty())
-            throw new IllegalStateException("No CH preparations added yet");
-        return getPreparation(map).getRoutingAlgorithmFactory();
+    public RoutingAlgorithmFactory getAlgorithmFactory(String profile) {
+        return getPreparation(profile).getRoutingAlgorithmFactory();
     }
 
-    public PrepareContractionHierarchies getPreparation(HintsMap map) {
-        CHProfile selectedProfile = selectProfile(map);
-        return getPreparation(selectedProfile);
-    }
-
-    public PrepareContractionHierarchies getPreparation(CHProfile chProfile) {
-        for (PrepareContractionHierarchies p : getPreparations()) {
-            if (p.getCHProfile().equals(chProfile)) {
-                return p;
+    public PrepareContractionHierarchies getPreparation(String profile) {
+        if (preparations.isEmpty())
+            throw new IllegalStateException("No CH preparations added yet");
+        List<String> profileNames = new ArrayList<>(preparations.size());
+        for (PrepareContractionHierarchies preparation : preparations) {
+            profileNames.add(preparation.getCHProfile().getName());
+            if (preparation.getCHProfile().getName().equalsIgnoreCase(profile)) {
+                return preparation;
             }
         }
-        throw new IllegalStateException("Could not find CH preparation for profile: " + chProfile);
+        throw new IllegalArgumentException("Cannot find CH preparation for the requested profile: '" + profile + "'" +
+                "\nYou can try disabling CH using " + CH.DISABLE + "=true" +
+                "\navailable CH profiles: " + profileNames);
     }
 
-    private CHProfile selectProfile(HintsMap map) {
-        try {
-            return CHProfileSelector.select(chProfiles, map);
-        } catch (CHProfileSelectionException e) {
-            throw new IllegalArgumentException(e.getMessage());
-        }
+    public PrepareContractionHierarchies getPreparation(CHProfile chProfile) {
+        return getPreparation(chProfile.getName());
     }
 
     public int getPreparationThreads() {
diff --git a/core/src/main/java/com/graphhopper/routing/ch/CHProfileSelector.java b/core/src/main/java/com/graphhopper/routing/ch/CHProfileSelector.java
deleted file mode 100644
index 24ad6809c..000000000
--- a/core/src/main/java/com/graphhopper/routing/ch/CHProfileSelector.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- *  Licensed to GraphHopper GmbH under one or more contributor
- *  license agreements. See the NOTICE file distributed with this work for
- *  additional information regarding copyright ownership.
- *
- *  GraphHopper GmbH licenses this file to you under the Apache License,
- *  Version 2.0 (the "License"); you may not use this file except in
- *  compliance with the License. You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package com.graphhopper.routing.ch;
-
-import com.graphhopper.routing.util.HintsMap;
-import com.graphhopper.storage.CHProfile;
-import com.graphhopper.util.Parameters;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.graphhopper.routing.weighting.Weighting.INFINITE_U_TURN_COSTS;
-
-/**
- * This class is used to determine the appropriate CH profile given (or not given) some (request) parameters
- */
-public class CHProfileSelector {
-    private final List<CHProfile> chProfiles;
-    private final HintsMap hintsMap;
-    private final Boolean edgeBased;
-    private final Integer uTurnCosts;
-
-    private CHProfileSelector(List<CHProfile> chProfiles, HintsMap hintsMap) {
-        this.chProfiles = chProfiles;
-        this.hintsMap = hintsMap;
-        edgeBased = hintsMap.has(Parameters.Routing.EDGE_BASED) ? hintsMap.getBool(Parameters.Routing.EDGE_BASED, false) : null;
-        uTurnCosts = hintsMap.has(Parameters.Routing.U_TURN_COSTS) ? hintsMap.getInt(Parameters.Routing.U_TURN_COSTS, INFINITE_U_TURN_COSTS) : null;
-    }
-
-    /**
-     * @param chProfiles the CH profiles to choose from
-     * @param hintsMap   a map used to describe the CH profile that shall be selected
-     * @throws CHProfileSelectionException if no CH profile could be selected for the given parameters
-     */
-    public static CHProfile select(List<CHProfile> chProfiles, HintsMap hintsMap) {
-        return new CHProfileSelector(chProfiles, hintsMap).select();
-    }
-
-    private CHProfile select() {
-
-        List<CHProfile> matchingProfiles = new ArrayList<>();
-        for (CHProfile p : chProfiles) {
-            if (edgeBased != null && p.isEdgeBased() != edgeBased) {
-                continue;
-            }
-            if (!p.getWeighting().matches(hintsMap)) {
-                continue;
-            }
-            matchingProfiles.add(p);
-        }
-
-        if (matchingProfiles.isEmpty()) {
-            throw new CHProfileSelectionException("Cannot find matching CH profile for your request. Please check your parameters." +
-                    "\nYou can try disabling CH using " + Parameters.CH.DISABLE + "=true" +
-                    "\nrequested:  " + getRequestAsString() + "\navailable: " + chProfiles);
-        } else if (matchingProfiles.size() == 1) {
-            return matchingProfiles.get(0);
-        } else {
-            // special case: prefer edge-based over node-based if these are the only two options
-            CHProfile match1 = matchingProfiles.get(0);
-            CHProfile match2 = matchingProfiles.get(1);
-            if (edgeBased == null && matchingProfiles.size() == 2 &&
-                    getWeighting(match1).equals(getWeighting(match2)) &&
-                    getVehicle(match1).equals(getVehicle(match2)) &&
-                    match1.isEdgeBased() != match2.isEdgeBased()) {
-                return match1.isEdgeBased() ? match1 : match2;
-            }
-            throw new CHProfileSelectionException("There are multiple CH profiles matching your request. Use the `weighting`,`vehicle`,`edge_based` and/or `u_turn_costs` parameters to be more specific." +
-                    "\nYou can also try disabling CH altogether using " + Parameters.CH.DISABLE + "=true" +
-                    "\nrequested:  " + getRequestAsString() + "\nmatched:   " + matchingProfiles + "\navailable: " + chProfiles);
-        }
-    }
-
-    private String getVehicle(CHProfile match1) {
-        return match1.getWeighting().getFlagEncoder().toString();
-    }
-
-    private String getWeighting(CHProfile match1) {
-        return match1.getWeighting().getName();
-    }
-
-    private String getRequestAsString() {
-        String sb = (hintsMap.getWeighting().isEmpty() ? "*" : hintsMap.getWeighting()) +
-                "|" +
-                (hintsMap.getVehicle().isEmpty() ? "*" : hintsMap.getVehicle()) +
-                "|" +
-                "edge_based=" + (edgeBased != null ? edgeBased : "*") +
-                "|" +
-                "u_turn_costs=" + (uTurnCosts != null ? uTurnCosts : "*");
-        return sb;
-    }
-}
diff --git a/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java b/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java
index 5669f8a3c..b838c0d31 100644
--- a/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java
+++ b/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java
@@ -21,7 +21,6 @@ import com.graphhopper.GraphHopperConfig;
 import com.graphhopper.config.LMProfileConfig;
 import com.graphhopper.routing.RoutingAlgorithmFactory;
 import com.graphhopper.routing.ch.CHPreparationHandler;
-import com.graphhopper.routing.util.HintsMap;
 import com.graphhopper.storage.GraphHopperStorage;
 import com.graphhopper.storage.StorableProperties;
 import com.graphhopper.storage.index.LocationIndex;
@@ -181,33 +180,27 @@ public class LMPreparationHandler {
 
     /**
      * @return a {@link RoutingAlgorithmFactory} for LM or throw an error if no preparation is available for the given
-     * hints
+     * profile name
      */
-    public RoutingAlgorithmFactory getAlgorithmFactory(HintsMap map) {
-        PrepareLandmarks preparation = getPreparation(map);
+    public RoutingAlgorithmFactory getAlgorithmFactory(String profile) {
+        PrepareLandmarks preparation = getPreparation(profile);
         return preparation.getRoutingAlgorithmFactory().setDefaultActiveLandmarks(activeLandmarkCount);
     }
 
-    private PrepareLandmarks getPreparation(HintsMap map) {
+    private PrepareLandmarks getPreparation(String profile) {
         if (preparations.isEmpty())
             throw new IllegalStateException("No LM preparations added yet");
 
-        List<String> lmProfiles = new ArrayList<>(preparations.size());
-        for (final PrepareLandmarks p : preparations) {
-            lmProfiles.add(p.getLMProfile().getName());
-            if (p.getLMProfile().getWeighting().matches(map))
-                return p;
+        List<String> profileNames = new ArrayList<>(preparations.size());
+        for (PrepareLandmarks preparation : preparations) {
+            profileNames.add(preparation.getLMProfile().getName());
+            if (preparation.getLMProfile().getName().equals(profile)) {
+                return preparation;
+            }
         }
-
-        // There are situations where we can use the requested encoder/weighting with an existing LM preparation, even
-        // though the preparation was done with a different weighting. For example this works when the new weighting
-        // only yields higher (but never lower) weights than the one that was used for the preparation. However, its not
-        // trivial to check whether or not this is the case so we do not allow this for now.
-        String requestedString = (map.getWeighting().isEmpty() ? "*" : map.getWeighting()) + "|" +
-                (map.getVehicle().isEmpty() ? "*" : map.getVehicle());
-        throw new IllegalArgumentException("Cannot find matching LM profile for your request. Please check your parameters." +
-                "\nYou can try disabling LM by setting " + Parameters.Landmark.DISABLE + "=true" +
-                "\nrequested: " + requestedString + "\navailable: " + lmProfiles);
+        throw new IllegalArgumentException("Cannot find LM preparation for the requested profile: '" + profile + "'" +
+                "\nYou can try disabling LM using " + Parameters.Landmark.DISABLE + "=true" +
+                "\navailable LM profiles: " + profileNames);
     }
 
     /**
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 f3a822559..76c77ddb1 100644
--- a/core/src/main/java/com/graphhopper/routing/lm/LandmarkStorage.java
+++ b/core/src/main/java/com/graphhopper/routing/lm/LandmarkStorage.java
@@ -118,6 +118,7 @@ public class LandmarkStorage implements Storable<LandmarkStorage> {
 
         // Edge based is not really necessary because when adding turn costs while routing we can still
         // use the node based traversal as this is a smaller weight approximation and will still produce correct results
+        // In this sense its even 'better' to use node-based.
         this.traversalMode = TraversalMode.NODE_BASED;
         this.landmarkWeightDA = dir.find("landmarks_" + lmProfile.getName());
 
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/AbstractAdjustedWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/AbstractAdjustedWeighting.java
index 7b794cee6..00f9893d5 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/AbstractAdjustedWeighting.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/AbstractAdjustedWeighting.java
@@ -18,7 +18,6 @@
 package com.graphhopper.routing.weighting;
 
 import com.graphhopper.routing.util.FlagEncoder;
-import com.graphhopper.routing.util.HintsMap;
 import com.graphhopper.util.EdgeIteratorState;
 
 /**
@@ -68,12 +67,6 @@ public abstract class AbstractAdjustedWeighting implements Weighting {
         return superWeighting.getFlagEncoder();
     }
 
-    @Override
-    public boolean matches(HintsMap reqMap) {
-        return getName().equals(reqMap.getWeighting())
-                && superWeighting.getFlagEncoder().toString().equals(reqMap.getVehicle());
-    }
-
     @Override
     public String toString() {
         return getName() + "|" + superWeighting.toString();
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/AbstractWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/AbstractWeighting.java
index 573176d5b..363b40f26 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/AbstractWeighting.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/AbstractWeighting.java
@@ -20,9 +20,7 @@ package com.graphhopper.routing.weighting;
 import com.graphhopper.routing.profiles.BooleanEncodedValue;
 import com.graphhopper.routing.profiles.DecimalEncodedValue;
 import com.graphhopper.routing.util.FlagEncoder;
-import com.graphhopper.routing.util.HintsMap;
 import com.graphhopper.util.EdgeIteratorState;
-import com.graphhopper.util.Parameters;
 
 import static com.graphhopper.routing.weighting.TurnCostProvider.NO_TURN_COST_PROVIDER;
 import static com.graphhopper.util.Helper.isEmpty;
@@ -92,14 +90,6 @@ public abstract class AbstractWeighting implements Weighting {
         return turnCostProvider.calcTurnMillis(inEdge, viaNode, outEdge);
     }
 
-    @Override
-    public boolean matches(HintsMap reqMap) {
-        String requestedUTurnCosts = reqMap.get(Parameters.Routing.U_TURN_COSTS, "");
-        return (reqMap.getWeighting().isEmpty() || getName().equals(reqMap.getWeighting())) &&
-                (requestedUTurnCosts.isEmpty() || turnCostProvider.getName().equals(requestedUTurnCosts)) &&
-                (reqMap.getVehicle().isEmpty() || flagEncoder.toString().equals(reqMap.getVehicle()));
-    }
-
     @Override
     public FlagEncoder getFlagEncoder() {
         return flagEncoder;
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/QueryGraphWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/QueryGraphWeighting.java
index aaf161d4b..134620961 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/QueryGraphWeighting.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/QueryGraphWeighting.java
@@ -21,7 +21,6 @@ package com.graphhopper.routing.weighting;
 import com.carrotsearch.hppc.IntArrayList;
 import com.graphhopper.routing.querygraph.QueryGraph;
 import com.graphhopper.routing.util.FlagEncoder;
-import com.graphhopper.routing.util.HintsMap;
 import com.graphhopper.util.EdgeIterator;
 import com.graphhopper.util.EdgeIteratorState;
 
@@ -105,11 +104,6 @@ public class QueryGraphWeighting implements Weighting {
         return weighting.getName();
     }
 
-    @Override
-    public boolean matches(HintsMap map) {
-        return weighting.matches(map);
-    }
-
     private int getOriginalEdge(int edge) {
         return closestEdges.get((edge - firstVirtualEdgeId) / 4);
     }
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/Weighting.java b/core/src/main/java/com/graphhopper/routing/weighting/Weighting.java
index cd84eb8fc..07e452fc1 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/Weighting.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/Weighting.java
@@ -18,7 +18,6 @@
 package com.graphhopper.routing.weighting;
 
 import com.graphhopper.routing.util.FlagEncoder;
-import com.graphhopper.routing.util.HintsMap;
 import com.graphhopper.util.EdgeIteratorState;
 
 /**
@@ -65,8 +64,4 @@ public interface Weighting {
 
     String getName();
 
-    /**
-     * Returns true if the specified weighting and encoder matches to this Weighting.
-     */
-    boolean matches(HintsMap map);
 }
diff --git a/core/src/main/java/com/graphhopper/util/DistanceCalc2D.java b/core/src/main/java/com/graphhopper/util/DistanceCalcEuclidean.java
similarity index 66%
rename from core/src/main/java/com/graphhopper/util/DistanceCalc2D.java
rename to core/src/main/java/com/graphhopper/util/DistanceCalcEuclidean.java
index c34596ee5..276ab1f02 100644
--- a/core/src/main/java/com/graphhopper/util/DistanceCalc2D.java
+++ b/core/src/main/java/com/graphhopper/util/DistanceCalcEuclidean.java
@@ -28,12 +28,17 @@ import com.graphhopper.util.shapes.GHPoint;
  *
  * @author Peter Karich
  */
-public class DistanceCalc2D extends DistanceCalcEarth {
+public class DistanceCalcEuclidean extends DistanceCalcEarth {
     @Override
     public double calcDist(double fromY, double fromX, double toY, double toX) {
         return sqrt(calcNormalizedDist(fromY, fromX, toY, toX));
     }
 
+    @Override
+    public double calcDist3D(double fromY, double fromX, double fromHeight, double toY, double toX, double toHeight) {
+        return sqrt(calcNormalizedDist(fromY, fromX, toY, toX) + calcNormalizedDist(toHeight - fromHeight));
+    }
+
     @Override
     public double calcDenormalizedDist(double normedDist) {
         return sqrt(normedDist);
@@ -91,4 +96,38 @@ public class DistanceCalc2D extends DistanceCalcEarth {
     public boolean isCrossBoundary(double lon1, double lon2) {
         throw new UnsupportedOperationException("Not supported for the 2D Euclidean space");
     }
+
+    @Override
+    public double calcNormalizedEdgeDistance(double ry, double rx,
+                                             double ay, double ax,
+                                             double by, double bx) {
+        return calcNormalizedEdgeDistance3D(
+            ry, rx, 0,
+            ay, ax, 0,
+            by, bx, 0
+        );
+    }
+
+    @Override
+    public double calcNormalizedEdgeDistance3D(double ry, double rx, double rz,
+                                               double ay, double ax, double az,
+                                               double by, double bx, double bz) {
+        double dx = bx - ax;
+        double dy = by - ay;
+        double dz = bz - az;
+
+        double norm = dx * dx + dy * dy + dz * dz;
+        double factor = ((rx - ax) * dx + (ry - ay) * dy + (rz - az) * dz) / norm;
+
+        // x,y,z is projection of r onto segment a-b
+        double cx = ax + factor * dx;
+        double cy = ay + factor * dy;
+        double cz = az + factor * dz;
+
+        double rdx = cx - ax;
+        double rdy = cy - ay;
+        double rdz = cz - az;
+
+        return rdx * rdx + rdy * rdy + rdz * rdz;
+    }
 }
diff --git a/reader-osm/src/main/java/com/graphhopper/reader/osm/OSMReader.java b/reader-osm/src/main/java/com/graphhopper/reader/osm/OSMReader.java
index 8035d89a6..dee4bdff3 100644
--- a/reader-osm/src/main/java/com/graphhopper/reader/osm/OSMReader.java
+++ b/reader-osm/src/main/java/com/graphhopper/reader/osm/OSMReader.java
@@ -75,7 +75,6 @@ public class OSMReader implements DataReader, TurnCostParser.ExternalInternalMap
     private final NodeAccess nodeAccess;
     private final LongIndexedContainer barrierNodeIds = new LongArrayList();
     private final DistanceCalc distCalc = Helper.DIST_EARTH;
-    private final DistanceCalc3D distCalc3D = Helper.DIST_3D;
     private final DouglasPeucker simplifyAlgo = new DouglasPeucker();
     private boolean smoothElevation = false;
     private final boolean exitOnlyPillarNodeException = true;
diff --git a/tools/pom.xml b/tools/pom.xml
index 3e69cc52f..aa73df5be 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -3,9 +3,7 @@
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
-    <groupId>com.graphhopper</groupId>
     <artifactId>graphhopper-tools</artifactId>
-    <version>1.0-SNAPSHOT</version>
     <packaging>jar</packaging>
     <name>GraphHopper Tools</name>
 
@@ -38,6 +36,10 @@
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.graphhopper.external</groupId>
+            <artifactId>jackson-datatype-jts</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
diff --git a/tools/src/main/java/com/graphhopper/tools/Measurement.java b/tools/src/main/java/com/graphhopper/tools/Measurement.java
index 45761be32..e41e90806 100644
--- a/tools/src/main/java/com/graphhopper/tools/Measurement.java
+++ b/tools/src/main/java/com/graphhopper/tools/Measurement.java
@@ -17,6 +17,8 @@
  */
 package com.graphhopper.tools;
 
+import com.bedatadriven.jackson.datatype.jts.JtsModule;
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.graphhopper.*;
 import com.graphhopper.coll.GHBitSet;
@@ -24,9 +26,12 @@ import com.graphhopper.coll.GHBitSetImpl;
 import com.graphhopper.config.CHProfileConfig;
 import com.graphhopper.config.LMProfileConfig;
 import com.graphhopper.config.ProfileConfig;
+import com.graphhopper.json.geo.JsonFeatureCollection;
 import com.graphhopper.reader.DataReader;
 import com.graphhopper.reader.osm.GraphHopperOSM;
 import com.graphhopper.routing.util.*;
+import com.graphhopper.routing.util.spatialrules.*;
+import com.graphhopper.routing.util.spatialrules.SpatialRuleLookupBuilder.SpatialRuleFactory;
 import com.graphhopper.storage.*;
 import com.graphhopper.storage.index.LocationIndex;
 import com.graphhopper.util.*;
@@ -34,13 +39,20 @@ import com.graphhopper.util.Parameters.Algorithms;
 import com.graphhopper.util.Parameters.CH;
 import com.graphhopper.util.Parameters.Landmark;
 import com.graphhopper.util.shapes.BBox;
+import com.graphhopper.util.shapes.GHPoint;
+
+import org.locationtech.jts.geom.Polygon;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.text.SimpleDateFormat;
 import java.util.*;
@@ -74,6 +86,7 @@ public class Measurement {
     // Every value is one y-value in a separate diagram with an identical x-value for every Measurement.start call
     void start(PMap args) throws IOException {
         final String graphLocation = args.get("graph.location", "");
+        final String countryBordersDirectory = args.get("spatial_rules.borders_directory", "");
         final boolean useJson = args.getBool("measurement.json", false);
         boolean cleanGraph = args.getBool("measurement.clean", false);
         String summaryLocation = args.get("measurement.summaryfile", "");
@@ -232,6 +245,10 @@ public class Measurement {
                             edgeBased().withInstructions().withPointHints().simplify());
                 }
             }
+            if (!isEmpty(countryBordersDirectory)) {
+                printSpatialRuleLookupTest(countryBordersDirectory, count * 100);
+            }
+            
         } catch (Exception ex) {
             logger.error("Problem while measuring " + graphLocation, ex);
             put("error", ex.toString());
@@ -456,6 +473,57 @@ public class Measurement {
         }.setIterations(count).start();
         print("unit_tests" + description + ".get_edge_state", miniPerf);
     }
+    
+    private void printSpatialRuleLookupTest(String countryBordersDirectory, int count) {
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.registerModule(new JtsModule());
+        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+        
+        List<JsonFeatureCollection> jsonFeatureCollections = new ArrayList<>();
+        try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(countryBordersDirectory), "*.{geojson,json}")) {
+            for (Path borderFile : stream) {
+                try (BufferedReader reader = Files.newBufferedReader(borderFile, StandardCharsets.UTF_8)) {
+                    JsonFeatureCollection jsonFeatureCollection = objectMapper.readValue(reader, JsonFeatureCollection.class);
+                    jsonFeatureCollections.add(jsonFeatureCollection);
+                }
+            }
+        } catch (IOException e) {
+            logger.error("Failed to load borders.", e);
+            return;
+        }
+        
+        SpatialRuleFactory rulePerCountryFactory = new SpatialRuleFactory() {
+            
+            @Override
+            public SpatialRule createSpatialRule(final String id, final List<Polygon> borders) {
+                return new AbstractSpatialRule(borders) {
+                    @Override
+                    public String getId() {
+                        return id;
+                    }
+                };
+            }
+        };
+        
+        final SpatialRuleLookup spatialRuleLookup = SpatialRuleLookupBuilder.buildIndex(jsonFeatureCollections, "ISO_A3", rulePerCountryFactory);
+    
+        // generate random points in central Europe
+        final List<GHPoint> randomPoints = new ArrayList<>(count);
+        for (int i = 0; i < count; i++) {
+            double lat = 46d + Math.random() * 7d;
+            double lon = 6d + Math.random() * 21d;
+            randomPoints.add(new GHPoint(lat, lon));
+        }
+        
+        MiniPerfTest lookupPerfTest = new MiniPerfTest() {
+            @Override
+            public int doCalc(boolean warmup, int run) {
+                return spatialRuleLookup.lookupRule(randomPoints.get(run)).hashCode();
+            }
+        }.setIterations(count).start();
+        
+        print("spatialrulelookup", lookupPerfTest);
+    }
 
     private void compareRouting(final GraphHopper hopper, String vehicle, int count) {
         logger.info("Comparing " + count + " routes. Differences will be printed to stderr.");
diff --git a/tools/src/main/java/com/graphhopper/ui/MiniGraphUI.java b/tools/src/main/java/com/graphhopper/ui/MiniGraphUI.java
index a8cb0d119..d3bc6e076 100644
--- a/tools/src/main/java/com/graphhopper/ui/MiniGraphUI.java
+++ b/tools/src/main/java/com/graphhopper/ui/MiniGraphUI.java
@@ -22,6 +22,9 @@ import com.graphhopper.GraphHopper;
 import com.graphhopper.GraphHopperConfig;
 import com.graphhopper.coll.GHBitSet;
 import com.graphhopper.coll.GHTBitSet;
+import com.graphhopper.config.CHProfileConfig;
+import com.graphhopper.config.LMProfileConfig;
+import com.graphhopper.config.ProfileConfig;
 import com.graphhopper.reader.osm.GraphHopperOSM;
 import com.graphhopper.routing.*;
 import com.graphhopper.routing.ch.CHRoutingAlgorithmFactory;
@@ -31,13 +34,11 @@ import com.graphhopper.routing.querygraph.QueryGraph;
 import com.graphhopper.routing.util.AllEdgesIterator;
 import com.graphhopper.routing.util.EdgeFilter;
 import com.graphhopper.routing.util.FlagEncoder;
-import com.graphhopper.routing.util.HintsMap;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.*;
 import com.graphhopper.storage.index.LocationIndexTree;
 import com.graphhopper.storage.index.QueryResult;
 import com.graphhopper.util.PMap;
-import com.graphhopper.util.Parameters;
 import com.graphhopper.util.Parameters.Algorithms;
 import com.graphhopper.util.PointList;
 import com.graphhopper.util.StopWatch;
@@ -51,10 +52,9 @@ import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseWheelEvent;
 import java.awt.event.MouseWheelListener;
+import java.util.Arrays;
 import java.util.Random;
 
-import static com.graphhopper.routing.weighting.TurnCostProvider.NO_TURN_COST_PROVIDER;
-
 /**
  * A rough graphical user interface for visualizing the OSM graph. Mainly for debugging algorithms
  * and spatial data structures. See e.g. this blog post:
@@ -97,17 +97,16 @@ public class MiniGraphUI {
         encoder = hopper.getEncodingManager().getEncoder("car");
         avSpeedEnc = encoder.getAverageSpeedEnc();
         accessEnc = encoder.getAccessEnc();
-        HintsMap map = new HintsMap("fastest").
-                setVehicle("car");
-
+        ProfileConfig profile = hopper.getProfiles().iterator().next();
         boolean ch = true;
         if (ch) {
-            map.put(Parameters.Landmark.DISABLE, true);
             CHProfile chProfile = hopper.getCHPreparationHandler().getNodeBasedCHProfiles().get(0);
             weighting = chProfile.getWeighting();
             routingGraph = hopper.getGraphHopperStorage().getCHGraph(chProfile);
 
-            final RoutingAlgorithmFactory tmpFactory = hopper.getAlgorithmFactory(map);
+            boolean disableCH = false;
+            boolean disableLM = true;
+            final RoutingAlgorithmFactory tmpFactory = hopper.getAlgorithmFactory(profile.getName(), disableCH, disableLM);
             algoFactory = new RoutingAlgorithmFactory() {
 
                 class TmpAlgo extends DijkstraBidirectionCH implements DebugAlgo {
@@ -143,11 +142,11 @@ public class MiniGraphUI {
             algoOpts = new AlgorithmOptions(Algorithms.DIJKSTRA_BI, weighting);
 
         } else {
-            map.put(Parameters.CH.DISABLE, true);
-//            map.put(Parameters.Landmark.DISABLE, true);
             routingGraph = graph;
-            weighting = hopper.createWeighting(map, encoder, NO_TURN_COST_PROVIDER);
-            final RoutingAlgorithmFactory tmpFactory = hopper.getAlgorithmFactory(map);
+            weighting = hopper.createWeighting(profile, new PMap());
+            boolean disableCH = true;
+            boolean disableLM = false;
+            final RoutingAlgorithmFactory tmpFactory = hopper.getAlgorithmFactory(profile.getName(), disableCH, disableLM);
             algoFactory = new RoutingAlgorithmFactory() {
 
                 @Override
@@ -385,9 +384,20 @@ public class MiniGraphUI {
         }
     }
 
-    public static void main(String[] strs) throws Exception {
+    public static void main(String[] strs) {
         PMap args = PMap.read(strs);
         GraphHopperConfig ghConfig = new GraphHopperConfig(args);
+        ghConfig.setProfiles(Arrays.asList(
+                new ProfileConfig("profile")
+                        .setVehicle("car")
+                        .setWeighting("fastest")
+        ));
+        ghConfig.setCHProfiles(Arrays.asList(
+                new CHProfileConfig("profile")
+        ));
+        ghConfig.setLMProfiles(Arrays.asList(
+                new LMProfileConfig("profile")
+        ));
         GraphHopper hopper = new GraphHopperOSM().init(ghConfig).importOrLoad();
         boolean debug = args.getBool("minigraphui.debug", false);
         new MiniGraphUI(hopper, debug).visualize();
diff --git a/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java b/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java
index 78dced27b..278117e59 100644
--- a/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java
+++ b/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java
@@ -3,6 +3,7 @@ package com.graphhopper.resources;
 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.graphhopper.GraphHopper;
+import com.graphhopper.config.ProfileConfig;
 import com.graphhopper.http.WebHelper;
 import com.graphhopper.isochrone.algorithm.ContourBuilder;
 import com.graphhopper.isochrone.algorithm.Isochrone;
@@ -34,8 +35,6 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import java.util.*;
 
-import static com.graphhopper.routing.weighting.TurnCostProvider.NO_TURN_COST_PROVIDER;
-
 @Path("isochrone")
 public class IsochroneResource {
 
@@ -55,7 +54,6 @@ public class IsochroneResource {
     @Produces({MediaType.APPLICATION_JSON})
     public Response doGet(
             @Context UriInfo uriInfo,
-            @QueryParam("vehicle") @DefaultValue("car") String vehicle,
             @QueryParam("buckets") @DefaultValue("1") int nBuckets,
             @QueryParam("reverse_flow") @DefaultValue("false") boolean reverseFlow,
             @QueryParam("point") GHPoint point,
@@ -71,13 +69,18 @@ public class IsochroneResource {
 
         StopWatch sw = new StopWatch().start();
 
-        if (!encodingManager.hasEncoder(vehicle))
-            throw new IllegalArgumentException("vehicle not supported:" + vehicle);
-
         if (respType != null && !respType.equalsIgnoreCase("json") && !respType.equalsIgnoreCase("geojson"))
             throw new IllegalArgumentException("Format not supported:" + respType);
 
-        FlagEncoder encoder = encodingManager.getEncoder(vehicle);
+        HintsMap hintsMap = new HintsMap();
+        RouteResource.initHints(hintsMap, uriInfo.getQueryParameters());
+        hintsMap.put(Parameters.CH.DISABLE, true);
+        hintsMap.put(Parameters.Landmark.DISABLE, true);
+        ProfileConfig profile = graphHopper.resolveProfile(hintsMap);
+        if (profile.isTurnCosts()) {
+            throw new IllegalArgumentException("Isochrone calculation does not support turn costs yet");
+        }
+        FlagEncoder encoder = encodingManager.getEncoder(profile.getVehicle());
         EdgeFilter edgeFilter = DefaultEdgeFilter.allEdges(encoder);
         LocationIndex locationIndex = graphHopper.getLocationIndex();
         QueryResult qr = locationIndex.findClosest(point.lat, point.lon, edgeFilter);
@@ -87,11 +90,7 @@ public class IsochroneResource {
         Graph graph = graphHopper.getGraphHopperStorage();
         QueryGraph queryGraph = QueryGraph.lookup(graph, qr);
 
-        HintsMap hintsMap = new HintsMap();
-        RouteResource.initHints(hintsMap, uriInfo.getQueryParameters());
-
-        // todo: isochrones with turn costs ?
-        Weighting weighting = graphHopper.createWeighting(hintsMap, encoder, NO_TURN_COST_PROVIDER);
+        Weighting weighting = graphHopper.createWeighting(profile, hintsMap);
         if (hintsMap.has(Parameters.Routing.BLOCK_AREA))
             weighting = new BlockAreaWeighting(weighting, GraphEdgeIdFinder.createBlockArea(graph, locationIndex,
                     Collections.singletonList(point), hintsMap, DefaultEdgeFilter.allEdges(encoder)));
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 6a669d9e0..aa5306886 100644
--- a/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java
+++ b/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java
@@ -1,6 +1,7 @@
 package com.graphhopper.resources;
 
 import com.graphhopper.GraphHopper;
+import com.graphhopper.config.ProfileConfig;
 import com.graphhopper.isochrone.algorithm.Isochrone;
 import com.graphhopper.routing.profiles.*;
 import com.graphhopper.routing.querygraph.QueryGraph;
@@ -28,8 +29,6 @@ import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.util.*;
 
-import static com.graphhopper.routing.weighting.TurnCostProvider.NO_TURN_COST_PROVIDER;
-
 /**
  * This resource provides the entire shortest path tree as response. In a simple CSV format discussed at #1577.
  */
@@ -51,7 +50,6 @@ public class SPTResource {
     @Produces("text/csv")
     public Response doGet(
             @Context UriInfo uriInfo,
-            @QueryParam("vehicle") @DefaultValue("car") String vehicle,
             @QueryParam("reverse_flow") @DefaultValue("false") boolean reverseFlow,
             @QueryParam("point") GHPoint point,
             @QueryParam("columns") String columnsParam,
@@ -62,11 +60,15 @@ public class SPTResource {
             throw new IllegalArgumentException("point parameter cannot be null");
 
         StopWatch sw = new StopWatch().start();
-
-        if (!encodingManager.hasEncoder(vehicle))
-            throw new IllegalArgumentException("vehicle not supported:" + vehicle);
-
-        FlagEncoder encoder = encodingManager.getEncoder(vehicle);
+        HintsMap hintsMap = new HintsMap();
+        RouteResource.initHints(hintsMap, uriInfo.getQueryParameters());
+        hintsMap.put(Parameters.CH.DISABLE, true);
+        hintsMap.put(Parameters.Landmark.DISABLE, true);
+        ProfileConfig profile = graphHopper.resolveProfile(hintsMap);
+        if (profile.isTurnCosts()) {
+            throw new IllegalArgumentException("SPT calculation does not support turn costs yet");
+        }
+        FlagEncoder encoder = encodingManager.getEncoder(profile.getVehicle());
         EdgeFilter edgeFilter = DefaultEdgeFilter.allEdges(encoder);
         LocationIndex locationIndex = graphHopper.getLocationIndex();
         QueryResult qr = locationIndex.findClosest(point.lat, point.lon, edgeFilter);
@@ -75,11 +77,8 @@ public class SPTResource {
 
         Graph graph = graphHopper.getGraphHopperStorage();
         QueryGraph queryGraph = QueryGraph.lookup(graph, qr);
-        HintsMap hintsMap = new HintsMap();
-        RouteResource.initHints(hintsMap, uriInfo.getQueryParameters());
 
-        // todo: /spt with turn costs ?
-        Weighting weighting = graphHopper.createWeighting(hintsMap, encoder, NO_TURN_COST_PROVIDER);
+        Weighting weighting = graphHopper.createWeighting(profile, hintsMap);
         if (hintsMap.has(Parameters.Routing.BLOCK_AREA))
             weighting = new BlockAreaWeighting(weighting, GraphEdgeIdFinder.createBlockArea(graph, locationIndex,
                     Collections.singletonList(point), hintsMap, DefaultEdgeFilter.allEdges(encoder)));
diff --git a/web/src/main/java/com/graphhopper/http/GraphHopperServerConfiguration.java b/web/src/main/java/com/graphhopper/http/GraphHopperServerConfiguration.java
index ad0121c47..bbd380f38 100644
--- a/web/src/main/java/com/graphhopper/http/GraphHopperServerConfiguration.java
+++ b/web/src/main/java/com/graphhopper/http/GraphHopperServerConfiguration.java
@@ -15,7 +15,6 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-
 package com.graphhopper.http;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
diff --git a/core/src/main/java/com/graphhopper/routing/ch/CHProfileSelectionException.java b/web/src/test/java/com/graphhopper/http/util/TestUtils.java
similarity index 52%
rename from core/src/main/java/com/graphhopper/routing/ch/CHProfileSelectionException.java
rename to web/src/test/java/com/graphhopper/http/util/TestUtils.java
index 531b5bdb5..36a4cfb23 100644
--- a/core/src/main/java/com/graphhopper/routing/ch/CHProfileSelectionException.java
+++ b/web/src/test/java/com/graphhopper/http/util/TestUtils.java
@@ -15,17 +15,33 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
+package com.graphhopper.http.util;
 
-package com.graphhopper.routing.ch;
+import io.dropwizard.Configuration;
+import io.dropwizard.testing.junit.DropwizardAppRule;
+import javax.ws.rs.client.WebTarget;
 
-import com.graphhopper.util.exceptions.DetailedIllegalArgumentException;
+/**
+ *
+ * @author thomas aulinger
+ */
+public class TestUtils {
+    private TestUtils() {
+
+    }
 
-import java.util.HashMap;
+    public static WebTarget clientTarget(DropwizardAppRule<? extends Configuration> app, String path) {
+        if (!path.startsWith("/")) {
+            path = "/" + path;
+        }
+        return app.client().target(clientUrl(app, path));
+    }
 
-public class CHProfileSelectionException extends DetailedIllegalArgumentException {
-    CHProfileSelectionException(final String message) {
-        super(message, new HashMap<String, Object>() {{
-            put("error", message);
-        }});
+    public static String clientUrl(DropwizardAppRule<? extends Configuration> app, String path) {
+        if (!path.startsWith("/")) {
+            path = "/" + path;
+        }
+        return "http://localhost:" + app.getLocalPort() + path;
     }
+
 }


Test patch:
diff --git a/api/src/test/java/com/graphhopper/util/DistanceCalcEarthTest.java b/api/src/test/java/com/graphhopper/util/DistanceCalcEarthTest.java
index 5fec97fa8..921ad97d1 100644
--- a/api/src/test/java/com/graphhopper/util/DistanceCalcEarthTest.java
+++ b/api/src/test/java/com/graphhopper/util/DistanceCalcEarthTest.java
@@ -99,6 +99,49 @@ public class DistanceCalcEarthTest {
         assertEquals(expectedDist, dist, 1e-4);
     }
 
+    @Test
+    public void testEdgeDistance3d() {
+        double dist = dc.calcNormalizedEdgeDistance3D(49.94241, 11.544356, 0,
+                49.937964, 11.541824, 0,
+                49.942272, 11.555643, 0);
+        double expectedDist = dc.calcNormalizedDist(49.94241, 11.544356,
+                49.9394, 11.54681);
+        assertEquals(expectedDist, dist, 1e-4);
+
+        // test identical lats
+        dist = dc.calcNormalizedEdgeDistance3D(49.936299, 11.543992, 0,
+                49.9357, 11.543047, 0,
+                49.9357, 11.549227, 0);
+        expectedDist = dc.calcNormalizedDist(49.936299, 11.543992,
+                49.9357, 11.543992);
+        assertEquals(expectedDist, dist, 1e-4);
+    }
+
+    @Test
+    public void testEdgeDistance3dEarth() {
+        double dist = dc.calcNormalizedEdgeDistance3D(0, 0.5, 10,
+                0, 0, 0,
+                0, 1, 0);
+        assertEquals(10, dc.calcDenormalizedDist(dist), 1e-4);
+    }
+
+    @Test
+    public void testEdgeDistance3dEarthNaN() {
+        double dist = dc.calcNormalizedEdgeDistance3D(0, 0.5, Double.NaN,
+                0, 0, 0,
+                0, 1, 0);
+        assertEquals(0, dc.calcDenormalizedDist(dist), 1e-4);
+    }
+
+    @Test
+    public void testEdgeDistance3dPlane() {
+        DistanceCalc calc = new DistancePlaneProjection();
+        double dist = calc.calcNormalizedEdgeDistance3D(0, 0.5, 10,
+                0, 0, 0,
+                0, 1, 0);
+        assertEquals(10, calc.calcDenormalizedDist(dist), 1e-4);
+    }
+
     @Test
     public void testValidEdgeDistance() {
         assertTrue(dc.validEdgeDistance(49.94241, 11.544356, 49.937964, 11.541824, 49.942272, 11.555643));
@@ -158,4 +201,60 @@ public class DistanceCalcEarthTest {
         assertEquals(new GHPoint(55.81863, 37.594626), distCalc.calcCrossingPointToEdge(queryLat, queryLon,
                 tmpLat, tmpLon, wayLat, wayLon));
     }
+
+    @Test
+    public void testDistance3dEarth() {
+        DistanceCalc distCalc = new DistanceCalcEarth();
+        assertEquals(1, distCalc.calcDist3D(
+                0, 0, 0,
+                0, 0, 1
+        ), 1e-6);
+    }
+
+    @Test
+    public void testDistance3dEarthNaN() {
+        DistanceCalc distCalc = new DistanceCalcEarth();
+        assertEquals(0, distCalc.calcDist3D(
+                0, 0, 0,
+                0, 0, Double.NaN
+        ), 1e-6);
+        assertEquals(0, distCalc.calcDist3D(
+                0, 0, Double.NaN,
+                0, 0, 10
+        ), 1e-6);
+        assertEquals(0, distCalc.calcDist3D(
+                0, 0, Double.NaN,
+                0, 0, Double.NaN
+        ), 1e-6);
+    }
+
+    @Test
+    public void testDistance3dPlane() {
+        DistancePlaneProjection distCalc = new DistancePlaneProjection();
+        assertEquals(1, distCalc.calcDist3D(
+                0, 0, 0,
+                0, 0, 1
+        ), 1e-6);
+        assertEquals(10, distCalc.calcDist3D(
+                0, 0, 0,
+                0, 0, 10
+        ), 1e-6);
+    }
+
+    @Test
+    public void testDistance3dPlaneNaN() {
+        DistancePlaneProjection distCalc = new DistancePlaneProjection();
+        assertEquals(0, distCalc.calcDist3D(
+                0, 0, 0,
+                0, 0, Double.NaN
+        ), 1e-6);
+        assertEquals(0, distCalc.calcDist3D(
+                0, 0, Double.NaN,
+                0, 0, 10
+        ), 1e-6);
+        assertEquals(0, distCalc.calcDist3D(
+                0, 0, Double.NaN,
+                0, 0, Double.NaN
+        ), 1e-6);
+    }
 }
diff --git a/core/src/main/java/com/graphhopper/util/MiniPerfTest.java b/core/src/main/java/com/graphhopper/util/MiniPerfTest.java
index ffe3dcfc4..2cda3fec5 100644
--- a/core/src/main/java/com/graphhopper/util/MiniPerfTest.java
+++ b/core/src/main/java/com/graphhopper/util/MiniPerfTest.java
@@ -28,11 +28,16 @@ import java.util.Locale;
  * @author Peter Karich
  */
 public abstract class MiniPerfTest {
+
+    private static final double NS_PER_S  = 1e9;
+    private static final double NS_PER_MS = 1e6;
+    private static final double NS_PER_US = 1e3;
+    
     protected Logger logger = LoggerFactory.getLogger(getClass());
     private int counts = 100;
-    private double fullTime = 0;
-    private double max;
-    private double min = Double.MAX_VALUE;
+    private long fullTime = 0;
+    private long max;
+    private long min = Long.MAX_VALUE;
     private int dummySum;
 
     public MiniPerfTest start() {
@@ -65,21 +70,21 @@ public abstract class MiniPerfTest {
      * @return minimum time of every call, in ms
      */
     public double getMin() {
-        return min / 1e6;
+        return min / NS_PER_MS;
     }
 
     /**
      * @return maximum time of every calls, in ms
      */
     public double getMax() {
-        return max / 1e6;
+        return max / NS_PER_MS;
     }
 
     /**
      * @return time for all calls accumulated, in ms
      */
     public double getSum() {
-        return fullTime / 1e6;
+        return fullTime / NS_PER_MS;
     }
 
     /**
@@ -88,16 +93,33 @@ public abstract class MiniPerfTest {
     public double getMean() {
         return getSum() / counts;
     }
+    
+    private String formatDuration(double durationNs) {
+        double divisor;
+        String unit;
+        if (durationNs > 1e7d) {
+            divisor = NS_PER_S;
+            unit = "s";
+        } else if (durationNs > 1e4d) {
+            divisor = NS_PER_MS;
+            unit = "ms";
+        } else {
+            divisor = NS_PER_US;
+            unit = "us";
+        }
+        return nf(durationNs / divisor) + unit;
+    }
 
     public String getReport() {
-        return "sum:" + nf(getSum() / 1000f) + "s, time/call:" + nf(getMean() / 1000f) + "s";
+        double meanNs = ((double) fullTime) / counts;
+        return "sum:" + formatDuration(fullTime) + ", time/call:" + formatDuration(meanNs);
     }
 
     public int getDummySum() {
         return dummySum;
     }
 
-    public String nf(Number num) {
+    private String nf(Number num) {
         return new DecimalFormat("#.###", DecimalFormatSymbols.getInstance(Locale.ROOT)).format(num);
     }
 
diff --git a/core/src/test/java/com/graphhopper/GraphHopperProfileConfigTest.java b/core/src/test/java/com/graphhopper/GraphHopperProfileConfigTest.java
index d985814e6..31af3c41a 100644
--- a/core/src/test/java/com/graphhopper/GraphHopperProfileConfigTest.java
+++ b/core/src/test/java/com/graphhopper/GraphHopperProfileConfigTest.java
@@ -28,6 +28,7 @@ import com.graphhopper.routing.util.EncodingManager;
 import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
+import java.util.Arrays;
 
 import static org.junit.jupiter.api.Assertions.*;
 
@@ -53,7 +54,7 @@ public class GraphHopperProfileConfigTest {
     @Test
     public void duplicateProfileName_error() {
         final GraphHopper hopper = createHopper(EncodingManager.create("car"));
-        assertIllegalArgument("Profile names must be unique. Duplicate name: 'my_profile'", new Runnable() {
+        assertIllegalArgument(new Runnable() {
             @Override
             public void run() {
                 hopper.setProfiles(
@@ -62,43 +63,46 @@ public class GraphHopperProfileConfigTest {
                         new ProfileConfig("my_profile").setVehicle("car").setWeighting("shortest")
                 );
             }
-        });
+        }, "Profile names must be unique. Duplicate name: 'my_profile'");
     }
 
     @Test
     public void vehicleDoesNotExist_error() {
         final GraphHopper hopper = createHopper(EncodingManager.create("car"));
         hopper.setProfiles(new ProfileConfig("profile").setVehicle("your_car"));
-        assertIllegalArgument("Unknown vehicle 'your_car' in profile: name=profile", new Runnable() {
+        assertIllegalArgument(new Runnable() {
             @Override
             public void run() {
                 hopper.load(GH_LOCATION);
             }
-        });
+        }, "Unknown vehicle 'your_car' in profile: name=profile");
     }
 
     @Test
     public void vehicleWithoutTurnCostSupport_error() {
         final GraphHopper hopper = createHopper(EncodingManager.create("car"));
         hopper.setProfiles(new ProfileConfig("profile").setVehicle("car").setTurnCosts(true));
-        assertIllegalArgument("The profile 'profile' was configured with 'turn_costs=true', but the corresponding vehicle 'car' does not support turn costs", new Runnable() {
+        assertIllegalArgument(new Runnable() {
             @Override
             public void run() {
                 hopper.load(GH_LOCATION);
             }
-        });
+        }, "The profile 'profile' was configured with 'turn_costs=true', but the corresponding vehicle 'car' does not support turn costs");
     }
 
     @Test
     public void profileWithUnknownWeighting_error() {
         final GraphHopper hopper = createHopper(EncodingManager.create("car"));
         hopper.setProfiles(new ProfileConfig("profile").setVehicle("car").setWeighting("your_weighting"));
-        assertIllegalArgument("The profile 'profile' was configured with an unknown weighting 'your_weighting'", new Runnable() {
-            @Override
-            public void run() {
-                hopper.load(GH_LOCATION);
-            }
-        });
+        assertIllegalArgument(new Runnable() {
+                                  @Override
+                                  public void run() {
+                                      hopper.load(GH_LOCATION);
+                                  }
+                              },
+                "Could not create weighting for profile: 'profile'",
+                "Weighting 'your_weighting' not supported"
+        );
     }
 
     @Test
@@ -106,12 +110,12 @@ public class GraphHopperProfileConfigTest {
         final GraphHopper hopper = createHopper(EncodingManager.create("car"));
         hopper.setProfiles(new ProfileConfig("profile1").setVehicle("car"));
         hopper.getCHPreparationHandler().setCHProfileConfigs(new CHProfileConfig("other_profile"));
-        assertIllegalArgument("CH profile references unknown profile 'other_profile'", new Runnable() {
+        assertIllegalArgument(new Runnable() {
             @Override
             public void run() {
                 hopper.load(GH_LOCATION);
             }
-        });
+        }, "CH profile references unknown profile 'other_profile'");
     }
 
     @Test
@@ -122,12 +126,12 @@ public class GraphHopperProfileConfigTest {
                 new CHProfileConfig("profile"),
                 new CHProfileConfig("profile")
         );
-        assertIllegalArgument("Duplicate CH reference to profile 'profile'", new Runnable() {
+        assertIllegalArgument(new Runnable() {
             @Override
             public void run() {
                 hopper.load(GH_LOCATION);
             }
-        });
+        }, "Duplicate CH reference to profile 'profile'");
     }
 
     @Test
@@ -135,12 +139,12 @@ public class GraphHopperProfileConfigTest {
         final GraphHopper hopper = createHopper(EncodingManager.create("car"));
         hopper.setProfiles(new ProfileConfig("profile1").setVehicle("car"));
         hopper.getLMPreparationHandler().setLMProfileConfigs(new LMProfileConfig("other_profile"));
-        assertIllegalArgument("LM profile references unknown profile 'other_profile'", new Runnable() {
+        assertIllegalArgument(new Runnable() {
             @Override
             public void run() {
                 hopper.load(GH_LOCATION);
             }
-        });
+        }, "LM profile references unknown profile 'other_profile'");
     }
 
     @Test
@@ -151,12 +155,12 @@ public class GraphHopperProfileConfigTest {
                 new LMProfileConfig("profile"),
                 new LMProfileConfig("profile")
         );
-        assertIllegalArgument("Duplicate LM reference to profile 'profile'", new Runnable() {
+        assertIllegalArgument(new Runnable() {
             @Override
             public void run() {
                 hopper.load(GH_LOCATION);
             }
-        });
+        }, "Duplicate LM reference to profile 'profile'");
     }
 
 
@@ -168,12 +172,14 @@ public class GraphHopperProfileConfigTest {
         return hopper;
     }
 
-    private static void assertIllegalArgument(String messagePart, Runnable runnable) {
+    private static void assertIllegalArgument(Runnable runnable, String... messageParts) {
         try {
             runnable.run();
-            fail("There should have been an error containing:\n\t" + messagePart);
+            fail("There should have been an error containing:\n\t" + Arrays.asList(messageParts));
         } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains(messagePart), "Unexpected error message:\n\t" + e.getMessage() + "\nExpected the message to contain:\n\t" + messagePart);
+            for (String messagePart : messageParts) {
+                assertTrue(e.getMessage().contains(messagePart), "Unexpected error message:\n\t" + e.getMessage() + "\nExpected the message to contain:\n\t" + messagePart);
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/core/src/test/java/com/graphhopper/reader/dem/HeightTileTest.java b/core/src/test/java/com/graphhopper/reader/dem/HeightTileTest.java
index 9d624115c..1722c92d6 100644
--- a/core/src/test/java/com/graphhopper/reader/dem/HeightTileTest.java
+++ b/core/src/test/java/com/graphhopper/reader/dem/HeightTileTest.java
@@ -96,37 +96,63 @@ public class HeightTileTest {
     }
 
     @Test
-    public void testCalcMean() {
-        int width = 10;
-        HeightTile instance = new HeightTile(0, 0, width, width, 1e-6, 10, 10).setCalcMean(true);
+    public void testInterpolate() {
+        HeightTile instance = new HeightTile(0, 0, 2, 2, 1e-6, 10, 10).setInterpolate(true);
         DataAccess heights = new RAMDirectory().find("tmp");
-        heights.create(2 * 10 * 10);
+        heights.create(2 * 2 * 2);
         instance.setHeights(heights);
-        init(heights, width, width, 1);
-
-        // x,y=0,9
-        heights.setShort(2 * (9 * width + 0), (short) 10);
-
-        // x,y=1,7
-        heights.setShort(2 * (7 * width + 1), (short) 70);
-
-        // x,y=2,8
-        heights.setShort(2 * (8 * width + 2), (short) 90);
-
-        assertEquals((70 + 4) / 5d, instance.getHeight(2, 1), 1e-3);
-
-        assertEquals((70 + 90 + 3) / 5d, instance.getHeight(2.5, 2.5), 1e-3);
-
-        assertEquals((90 + 3) / 4d, instance.getHeight(-0.5, 2.5), 1e-3);
-
-        assertEquals((10 + 2) / 3d, instance.getHeight(-0.5, -0.5), 1e-3);
+        double topLeft = 0;
+        double topRight = 1;
+        double bottomLeft = 2;
+        double bottomRight = 3;
+        set(heights, 2, 0, 0, (short) topLeft);
+        set(heights, 2, 1, 0, (short) topRight);
+        set(heights, 2, 0, 1, (short) bottomLeft);
+        set(heights, 2, 1, 1, (short) bottomRight);
+
+        // corners
+        assertEquals(bottomLeft, instance.getHeight(0, 0), 1e-3);
+        assertEquals(topLeft, instance.getHeight(10, 0), 1e-3);
+        assertEquals(bottomRight, instance.getHeight(0, 10), 1e-3);
+        assertEquals(topRight, instance.getHeight(10, 10), 1e-3);
+
+        // midpoints
+        assertEquals(avg(topLeft, topRight), instance.getHeight(10, 5), 1e-3);
+        assertEquals(avg(bottomLeft, bottomRight), instance.getHeight(0, 5), 1e-3);
+        assertEquals(avg(topLeft, bottomLeft), instance.getHeight(5, 0), 1e-3);
+        assertEquals(avg(topRight, bottomRight, topLeft, bottomLeft), instance.getHeight(5, 5), 1e-3);
+
+        // missing data uses whatever remains
+        set(heights, 2, 1, 0, Short.MIN_VALUE);
+        set(heights, 2, 0, 1, Short.MIN_VALUE);
+        set(heights, 2, 1, 1, Short.MIN_VALUE);
+        assertEquals(topLeft, instance.getHeight(0, 0), 1e-3);
+        assertEquals(topLeft, instance.getHeight(10, 0), 1e-3);
+        assertEquals(topLeft, instance.getHeight(0, 10), 1e-3);
+        assertEquals(topLeft, instance.getHeight(10, 10), 1e-3);
+
+        // when all data missing, returns NaN
+        set(heights, 2, 0, 0, Short.MIN_VALUE);
+        assertEquals(Double.NaN, instance.getHeight(5, 5), 1e-3);
     }
 
     private void init(DataAccess da, int width, int height, int i) {
         for (int x = 0; x < width; x++) {
             for (int y = 0; y < height; y++) {
-                da.setShort(2 * (y * width + x), (short) 1);
+                set(da, width, x, y, (short) 1);
             }
         }
     }
+
+    private void set(DataAccess da, int width, int x, int y, short height) {
+        da.setShort(2 * (y * width + x), height);
+    }
+
+    private double avg(double... ns) {
+        double sum = 0;
+        for (double n : ns) {
+            sum += n;
+        }
+        return sum / ns.length;
+    }
 }
\ No newline at end of file
diff --git a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmIT.java b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmIT.java
index bd78dbda8..b4ea4eec7 100644
--- a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmIT.java
+++ b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmIT.java
@@ -18,15 +18,12 @@
 package com.graphhopper.routing;
 
 import com.graphhopper.GraphHopper;
+import com.graphhopper.config.ProfileConfig;
 import com.graphhopper.reader.PrincetonReader;
-import com.graphhopper.routing.ch.CHProfileSelector;
 import com.graphhopper.routing.util.EncodingManager;
-import com.graphhopper.routing.util.FlagEncoder;
 import com.graphhopper.routing.util.HintsMap;
 import com.graphhopper.routing.util.TestAlgoCollector.AlgoHelperEntry;
 import com.graphhopper.routing.util.TraversalMode;
-import com.graphhopper.routing.weighting.DefaultTurnCostProvider;
-import com.graphhopper.routing.weighting.TurnCostProvider;
 import com.graphhopper.routing.weighting.Weighting;
 import com.graphhopper.storage.CHProfile;
 import com.graphhopper.storage.Directory;
@@ -45,7 +42,6 @@ import java.util.List;
 import java.util.Random;
 import java.util.zip.GZIPInputStream;
 
-import static com.graphhopper.routing.weighting.TurnCostProvider.NO_TURN_COST_PROVIDER;
 import static com.graphhopper.util.Parameters.Algorithms.*;
 import static org.junit.Assert.assertTrue;
 
@@ -63,9 +59,8 @@ public class RoutingAlgorithmIT {
         if (tMode.isEdgeBased())
             addStr = "turn|";
 
-        FlagEncoder encoder = hopper.getEncodingManager().getEncoder(hints.getVehicle());
-        TurnCostProvider turnCostProvider = tMode.isEdgeBased() ? new DefaultTurnCostProvider(encoder, ghStorage.getTurnCostStorage()) : NO_TURN_COST_PROVIDER;
-        Weighting weighting = hopper.createWeighting(hints, encoder, turnCostProvider);
+        ProfileConfig profile = new ProfileConfig("profile").setVehicle(hints.getVehicle()).setWeighting(hints.getWeighting()).setTurnCosts(tMode.isEdgeBased());
+        Weighting weighting = hopper.createWeighting(profile, hints);
 
         HintsMap defaultHints = new HintsMap().put(Parameters.CH.DISABLE, true).put(Parameters.Landmark.DISABLE, true)
                 .setVehicle(hints.getVehicle()).setWeighting(hints.getWeighting());
@@ -88,7 +83,7 @@ public class RoutingAlgorithmIT {
             algos.add(new AlgoHelperEntry(ghStorage, AlgorithmOptions.start(astarbiOpts).hints(lmHints).build(), idx, "astarbi|landmarks|" + weighting) {
                 @Override
                 public RoutingAlgorithmFactory createRoutingFactory() {
-                    return hopper.getAlgorithmFactory(lmHints);
+                    return hopper.getAlgorithmFactory(lmHints.getVehicle() + "_profile", true, false);
                 }
             });
         }
@@ -97,12 +92,12 @@ public class RoutingAlgorithmIT {
             final HintsMap chHints = new HintsMap(defaultHints);
             chHints.put(Parameters.CH.DISABLE, false);
             chHints.put(Parameters.Routing.EDGE_BASED, tMode.isEdgeBased());
-            CHProfile pickedProfile = CHProfileSelector.select(hopper.getCHPreparationHandler().getCHProfiles(), chHints);
+            CHProfile pickedProfile = new ProfileResolver().selectCHProfile(hopper.getCHPreparationHandler().getCHProfiles(), chHints);
             algos.add(new AlgoHelperEntry(ghStorage.getCHGraph(pickedProfile),
                     AlgorithmOptions.start(dijkstrabiOpts).hints(chHints).build(), idx, "dijkstrabi|ch|algos|" + hints.getWeighting()) {
                 @Override
                 public RoutingAlgorithmFactory createRoutingFactory() {
-                    return hopper.getAlgorithmFactory(chHints);
+                    return hopper.getAlgorithmFactory(chHints.getVehicle() + "_profile", false, true);
                 }
             });
 
@@ -110,7 +105,7 @@ public class RoutingAlgorithmIT {
                     AlgorithmOptions.start(astarbiOpts).hints(chHints).build(), idx, "astarbi|ch|algos|" + hints.getWeighting()) {
                 @Override
                 public RoutingAlgorithmFactory createRoutingFactory() {
-                    return hopper.getAlgorithmFactory(chHints);
+                    return hopper.getAlgorithmFactory(chHints.getVehicle() + "_profile", false, true);
                 }
             });
         }
diff --git a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmTest.java b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmTest.java
index 9f1f0a926..3ae6adde3 100644
--- a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmTest.java
+++ b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmTest.java
@@ -23,7 +23,10 @@ import com.graphhopper.routing.ch.PrepareContractionHierarchies;
 import com.graphhopper.routing.profiles.BooleanEncodedValue;
 import com.graphhopper.routing.profiles.DecimalEncodedValue;
 import com.graphhopper.routing.querygraph.QueryGraph;
-import com.graphhopper.routing.util.*;
+import com.graphhopper.routing.util.EdgeFilter;
+import com.graphhopper.routing.util.EncodingManager;
+import com.graphhopper.routing.util.FlagEncoder;
+import com.graphhopper.routing.util.TraversalMode;
 import com.graphhopper.routing.weighting.FastestWeighting;
 import com.graphhopper.routing.weighting.ShortestWeighting;
 import com.graphhopper.routing.weighting.TurnCostProvider;
@@ -853,11 +856,6 @@ public class RoutingAlgorithmTest {
                 return tmpW.calcTurnMillis(inEdge, viaNode, outEdge);
             }
 
-            @Override
-            public boolean matches(HintsMap map) {
-                throw new UnsupportedOperationException("Not supported");
-            }
-
             @Override
             public String getName() {
                 return "custom";
diff --git a/core/src/test/java/com/graphhopper/routing/ch/CHProfileSelectorTest.java b/core/src/test/java/com/graphhopper/routing/ch/CHProfileSelectorTest.java
index 484ed028e..ed970cf1c 100644
--- a/core/src/test/java/com/graphhopper/routing/ch/CHProfileSelectorTest.java
+++ b/core/src/test/java/com/graphhopper/routing/ch/CHProfileSelectorTest.java
@@ -18,23 +18,25 @@
 
 package com.graphhopper.routing.ch;
 
+import com.graphhopper.routing.ProfileResolver;
 import com.graphhopper.routing.util.*;
 import com.graphhopper.routing.weighting.*;
 import com.graphhopper.storage.CHProfile;
 import com.graphhopper.storage.TurnCostStorage;
 import com.graphhopper.util.Parameters;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 
 public class CHProfileSelectorTest {
 
+    private static final String MULTIPLE_EDGE_BASED_ERROR = "There are multiple edge-based CH profiles matching your request. You need to specify the profile you want to use explicitly";
     private static final String MULTIPLE_MATCHES_ERROR = "There are multiple CH profiles matching your request. Use the `weighting`,`vehicle`,`edge_based` and/or `u_turn_costs` parameters to be more specific";
     private static final String NO_MATCH_ERROR = "Cannot find matching CH profile for your request";
 
@@ -48,7 +50,7 @@ public class CHProfileSelectorTest {
     private Weighting weightingShortestCar;
     private Weighting weightingShortestBike;
 
-    @Before
+    @BeforeEach
     public void setup() {
         FlagEncoder carEncoder = new CarFlagEncoder();
         FlagEncoder bikeEncoder = new BikeFlagEncoder();
@@ -76,7 +78,9 @@ public class CHProfileSelectorTest {
         assertCHProfileSelectionError(NO_MATCH_ERROR, chProfiles, false, 20);
         assertProfileFound(chProfiles.get(0), chProfiles, null, null);
         assertCHProfileSelectionError(NO_MATCH_ERROR, chProfiles, null, 30);
-        assertCHProfileSelectionError(NO_MATCH_ERROR, chProfiles, "foot", "fastest", false, null);
+        String error = assertCHProfileSelectionError(NO_MATCH_ERROR, chProfiles, "foot", "fastest", false, null);
+        assertTrue(error.contains("requested:  fastest|foot|edge_based=false|u_turn_costs=*"), error);
+        assertTrue(error.contains("available: [fastest|car|edge_based=false]"), error);
     }
 
     @Test
@@ -109,17 +113,17 @@ public class CHProfileSelectorTest {
                 CHProfile.edgeBased(weightingFastestCarEdge30),
                 CHProfile.edgeBased(weightingFastestCarEdge50)
         );
-        // when no u-turns are specified we throw
-        assertCHProfileSelectionError(MULTIPLE_MATCHES_ERROR, chProfiles, true, null);
-        // when we request one that does not exist we throw
-        assertCHProfileSelectionError(NO_MATCH_ERROR, chProfiles, true, 40);
-        // when we request one that exists it works
-        assertProfileFound(chProfiles.get(1), chProfiles, true, 30);
-
-        // without specifying edge-based
-        assertProfileFound(chProfiles.get(1), chProfiles, null, 30);
-        assertCHProfileSelectionError(NO_MATCH_ERROR, chProfiles, null, 40);
+        // since there are multiple edge-based profiles we will get an error no matter if we do not specify the u-turn
+        // costs, specify some that do not exist or even specify the right u-turn costs, because we no longer allow
+        // choosing between multiple edge-based profiles without giving the profile name explicitly
+        assertCHProfileSelectionError(MULTIPLE_EDGE_BASED_ERROR, chProfiles, true, null);
+        assertCHProfileSelectionError(MULTIPLE_EDGE_BASED_ERROR, chProfiles, true, 40);
+        assertCHProfileSelectionError(MULTIPLE_EDGE_BASED_ERROR, chProfiles, true, 30);
+
+        // without specifying edge-based we also get an error
         assertCHProfileSelectionError(MULTIPLE_MATCHES_ERROR, chProfiles, null, null);
+        assertCHProfileSelectionError(MULTIPLE_EDGE_BASED_ERROR, chProfiles, null, 40);
+        assertCHProfileSelectionError(MULTIPLE_EDGE_BASED_ERROR, chProfiles, null, 30);
     }
 
     @Test
@@ -173,9 +177,9 @@ public class CHProfileSelectorTest {
         // if we do not specify the vehicle but edge_based=false its clear what to return because for node-based there is only one weighting
         assertProfileFound(chProfiles.get(0), chProfiles, "", "", false, null);
         // ... for edge_based=true this is an error, because there are two edge_based profiles
-        assertCHProfileSelectionError(MULTIPLE_MATCHES_ERROR, chProfiles, "", "", true, null);
-        // .. we can however get a clear match if we specify the u-turn costs
-        assertProfileFound(chProfiles.get(1), chProfiles, "", "", true, 10);
+        assertCHProfileSelectionError(MULTIPLE_EDGE_BASED_ERROR, chProfiles, "", "", true, null);
+        // ... and its even an error if we specify the (correct) u-turn costs, because we no longer allow distinguishing the profile this way
+        assertCHProfileSelectionError(MULTIPLE_EDGE_BASED_ERROR, chProfiles, "", "", true, 10);
     }
 
     @Test
@@ -219,25 +223,27 @@ public class CHProfileSelectorTest {
     private void assertProfileFound(CHProfile expectedProfile, List<CHProfile> profiles, String vehicle, String weighting, Boolean edgeBased, Integer uTurnCosts) {
         HintsMap hintsMap = createHintsMap(vehicle, weighting, edgeBased, uTurnCosts);
         try {
-            CHProfile selectedProfile = CHProfileSelector.select(profiles, hintsMap);
+            CHProfile selectedProfile = new ProfileResolver().selectCHProfile(profiles, hintsMap);
             assertEquals(expectedProfile, selectedProfile);
-        } catch (CHProfileSelectionException e) {
+        } catch (IllegalArgumentException e) {
             fail("no profile found\nexpected: " + expectedProfile + "\nerror: " + e.getMessage());
         }
     }
 
-    private void assertCHProfileSelectionError(String expectedError, List<CHProfile> profiles, Boolean edgeBased, Integer uTurnCosts) {
-        assertCHProfileSelectionError(expectedError, profiles, "car", "fastest", edgeBased, uTurnCosts);
+    private String assertCHProfileSelectionError(String expectedError, List<CHProfile> profiles, Boolean edgeBased, Integer uTurnCosts) {
+        return assertCHProfileSelectionError(expectedError, profiles, "car", "fastest", edgeBased, uTurnCosts);
     }
 
-    private void assertCHProfileSelectionError(String expectedError, List<CHProfile> profiles, String vehicle, String weighting, Boolean edgeBased, Integer uTurnCosts) {
+    private String assertCHProfileSelectionError(String expectedError, List<CHProfile> profiles, String vehicle, String weighting, Boolean edgeBased, Integer uTurnCosts) {
         HintsMap hintsMap = createHintsMap(vehicle, weighting, edgeBased, uTurnCosts);
         try {
-            CHProfileSelector.select(profiles, hintsMap);
+            new ProfileResolver().selectCHProfile(profiles, hintsMap);
             fail("There should have been an error");
-        } catch (CHProfileSelectionException e) {
-            assertTrue("There should have been an error message containing:\n'" + expectedError + "'\nbut was:\n'" + e.getMessage() + "'",
-                    e.getMessage().contains(expectedError));
+            return "";
+        } catch (IllegalArgumentException e) {
+            assertTrue(e.getMessage().contains(expectedError),
+                    "There should have been an error message containing:\n'" + expectedError + "'\nbut was:\n'" + e.getMessage() + "'");
+            return e.getMessage();
         }
     }
 
diff --git a/core/src/test/java/com/graphhopper/routing/ch/PrepareContractionHierarchiesTest.java b/core/src/test/java/com/graphhopper/routing/ch/PrepareContractionHierarchiesTest.java
index 9a66d0978..62d07bb6d 100644
--- a/core/src/test/java/com/graphhopper/routing/ch/PrepareContractionHierarchiesTest.java
+++ b/core/src/test/java/com/graphhopper/routing/ch/PrepareContractionHierarchiesTest.java
@@ -468,7 +468,7 @@ public class PrepareContractionHierarchiesTest {
         qr.setSnappedPosition(QueryResult.Position.EDGE);
         qr.setClosestNode(8);
         qr.setWayIndex(0);
-        qr.calcSnappedPoint(new DistanceCalc2D());
+        qr.calcSnappedPoint(new DistanceCalcEuclidean());
         QueryGraph queryGraph = QueryGraph.lookup(lg, qr);
 
         // we make sure our weight fine tunings do what they are supposed to
diff --git a/core/src/test/java/com/graphhopper/routing/lm/LMProfileSelectorTest.java b/core/src/test/java/com/graphhopper/routing/lm/LMProfileSelectorTest.java
new file mode 100644
index 000000000..a97d47dd8
--- /dev/null
+++ b/core/src/test/java/com/graphhopper/routing/lm/LMProfileSelectorTest.java
@@ -0,0 +1,141 @@
+/*
+ *  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.lm;
+
+import com.graphhopper.routing.ProfileResolver;
+import com.graphhopper.routing.util.*;
+import com.graphhopper.routing.weighting.FastestWeighting;
+import com.graphhopper.routing.weighting.Weighting;
+import com.graphhopper.util.Parameters;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+
+public class LMProfileSelectorTest {
+
+    private static final String MULTIPLE_MATCHES_ERROR = "There are multiple LM profiles matching your request. Use the `weighting` and `vehicle` parameters to be more specific";
+    private static final String NO_MATCH_ERROR = "Cannot find matching LM profile for your request";
+
+    private Weighting weightingFastestCar;
+    private Weighting weightingFastestBike;
+
+    @BeforeEach
+    public void setup() {
+        FlagEncoder carEncoder = new CarFlagEncoder();
+        FlagEncoder bikeEncoder = new BikeFlagEncoder();
+        EncodingManager.create(carEncoder, bikeEncoder);
+        weightingFastestCar = new FastestWeighting(carEncoder);
+        weightingFastestBike = new FastestWeighting(bikeEncoder);
+    }
+
+    @Test
+    public void singleProfile() {
+        List<LMProfile> lmProfiles = Arrays.asList(
+                new LMProfile("profile1", weightingFastestCar)
+        );
+        // as long as we do not request something that does not fit the existing profile we have a match
+        assertProfileFound(lmProfiles.get(0), lmProfiles, null, null, null, null);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, "car", null, null, null);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, null, "fastest", null, null);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, "car", "fastest", null, null);
+
+        // requesting edge_based or u_turn_costs should not lead to a non-match
+        assertProfileFound(lmProfiles.get(0), lmProfiles, null, null, true, null);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, null, null, false, null);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, null, null, null, 54);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, null, null, true, 54);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, "car", null, true, 54);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, null, "fastest", true, 54);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, "car", "fastest", true, null);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, "car", "fastest", true, 54);
+
+        // if we request something that does not fit we do not get a match
+        String error = assertLMProfileSelectionError(NO_MATCH_ERROR, lmProfiles, "bike", null, null, null);
+        assertTrue(error.contains("available: [fastest|car]"), error);
+        assertLMProfileSelectionError(NO_MATCH_ERROR, lmProfiles, "car", "shortest", null, null);
+        assertLMProfileSelectionError(NO_MATCH_ERROR, lmProfiles, null, "shortest", null, null);
+        assertLMProfileSelectionError(NO_MATCH_ERROR, lmProfiles, "truck", "short_fastest", null, null);
+    }
+
+    @Test
+    public void multipleProfiles() {
+        List<LMProfile> lmProfiles = Arrays.asList(
+                new LMProfile("profile1", weightingFastestCar),
+                new LMProfile("profile2", weightingFastestBike)
+        );
+
+        assertProfileFound(lmProfiles.get(0), lmProfiles, "car", null, null, null);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, "car", "fastest", null, null);
+        assertProfileFound(lmProfiles.get(1), lmProfiles, "bike", null, null, null);
+        assertProfileFound(lmProfiles.get(1), lmProfiles, "bike", "fastest", null, null);
+
+        // not specific enough
+        String error = assertLMProfileSelectionError(MULTIPLE_MATCHES_ERROR, lmProfiles, null, "fastest", null, null);
+        assertTrue(error.contains("requested:  fastest|*"), error);
+        assertTrue(error.contains("matched:   [fastest|car, fastest|bike]"), error);
+        assertTrue(error.contains("available: [fastest|car, fastest|bike]"), error);
+        assertLMProfileSelectionError(NO_MATCH_ERROR, lmProfiles, null, "shortest", null, null);
+
+        // edge_based/u_turn_costs is set, but lm should not really care
+        assertProfileFound(lmProfiles.get(0), lmProfiles, "car", null, null, null);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, "car", null, true, null);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, "car", null, null, 54);
+        assertProfileFound(lmProfiles.get(0), lmProfiles, "car", null, false, 64);
+    }
+
+
+    private void assertProfileFound(LMProfile expectedProfile, List<LMProfile> profiles, String vehicle, String weighting, Boolean edgeBased, Integer uTurnCosts) {
+        HintsMap hintsMap = createHintsMap(vehicle, weighting, edgeBased, uTurnCosts);
+        try {
+            LMProfile selectedProfile = new ProfileResolver().selectLMProfile(profiles, hintsMap);
+            assertEquals(expectedProfile, selectedProfile);
+        } catch (IllegalArgumentException e) {
+            fail("no profile found\nexpected: " + expectedProfile + "\nerror: " + e.getMessage());
+        }
+    }
+
+    private String assertLMProfileSelectionError(String expectedError, List<LMProfile> profiles, String vehicle, String weighting, Boolean edgeBased, Integer uTurnCosts) {
+        HintsMap hintsMap = createHintsMap(vehicle, weighting, edgeBased, uTurnCosts);
+        try {
+            new ProfileResolver().selectLMProfile(profiles, hintsMap);
+            fail("There should have been an error");
+            return "";
+        } catch (IllegalArgumentException e) {
+            assertTrue(e.getMessage().contains(expectedError),
+                    "There should have been an error message containing:\n'" + expectedError + "'\nbut was:\n'" + e.getMessage() + "'");
+            return e.getMessage();
+        }
+    }
+
+    private HintsMap createHintsMap(String vehicle, String weighting, Boolean edgeBased, Integer uTurnCosts) {
+        HintsMap hintsMap = new HintsMap().setWeighting(weighting).setVehicle(vehicle);
+        if (edgeBased != null) {
+            hintsMap.put(Parameters.Routing.EDGE_BASED, edgeBased);
+        }
+        if (uTurnCosts != null) {
+            hintsMap.put(Parameters.Routing.U_TURN_COSTS, uTurnCosts);
+        }
+        return hintsMap;
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/com/graphhopper/routing/querygraph/QueryGraphTest.java b/core/src/test/java/com/graphhopper/routing/querygraph/QueryGraphTest.java
index 5ca674bbb..d1ffee626 100644
--- a/core/src/test/java/com/graphhopper/routing/querygraph/QueryGraphTest.java
+++ b/core/src/test/java/com/graphhopper/routing/querygraph/QueryGraphTest.java
@@ -274,7 +274,7 @@ public class QueryGraphTest {
         EdgeIteratorState edge = g.edge(0, 1);
         EdgeIteratorState edgeReverse = edge.detach(true);
 
-        DistanceCalc2D distCalc = new DistanceCalc2D();
+        DistanceCalcEuclidean distCalc = new DistanceCalcEuclidean();
         QueryResult qr = new QueryResult(0, 0.00005);
         qr.setClosestEdge(edge);
         qr.setWayIndex(0);
@@ -310,7 +310,7 @@ public class QueryGraphTest {
         QueryResult qr = new QueryResult(-0.0005, 0.001);
         qr.setClosestEdge(edge);
         qr.setWayIndex(1);
-        qr.calcSnappedPoint(new DistanceCalc2D());
+        qr.calcSnappedPoint(new DistanceCalcEuclidean());
 
         QueryGraph qg = lookup(qr);
         EdgeExplorer ee = qg.createEdgeExplorer();
@@ -342,7 +342,7 @@ public class QueryGraphTest {
         QueryResult qr = new QueryResult(0.0011, 0.0009);
         qr.setClosestEdge(edge);
         qr.setWayIndex(1);
-        qr.calcSnappedPoint(new DistanceCalc2D());
+        qr.calcSnappedPoint(new DistanceCalcEuclidean());
 
         QueryGraph qg = lookup(qr);
         EdgeExplorer ee = qg.createEdgeExplorer();
@@ -545,7 +545,7 @@ public class QueryGraphTest {
         qr.setClosestEdge(edge);
         qr.setWayIndex(wayIndex);
         qr.setSnappedPosition(EDGE);
-        qr.calcSnappedPoint(new DistanceCalc2D());
+        qr.calcSnappedPoint(new DistanceCalcEuclidean());
         return qr;
     }
 
diff --git a/core/src/test/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilderTest.java b/core/src/test/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilderTest.java
index 38e17493a..28a81b809 100644
--- a/core/src/test/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilderTest.java
+++ b/core/src/test/java/com/graphhopper/routing/util/spatialrules/SpatialRuleLookupBuilderTest.java
@@ -24,7 +24,6 @@ import com.graphhopper.routing.profiles.*;
 import com.graphhopper.routing.util.CarFlagEncoder;
 import com.graphhopper.routing.util.EncodingManager;
 import com.graphhopper.routing.util.parsers.SpatialRuleParser;
-import com.graphhopper.routing.util.spatialrules.SpatialRuleLookupBuilder.SpatialRuleFactory;
 import com.graphhopper.routing.util.spatialrules.countries.GermanySpatialRule;
 import com.graphhopper.storage.Graph;
 import com.graphhopper.storage.GraphBuilder;
@@ -41,8 +40,6 @@ import org.locationtech.jts.geom.Polygon;
 import java.io.FileReader;
 import java.io.IOException;
 import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
 
 import static com.graphhopper.util.GHUtility.updateDistancesFor;
 import static junit.framework.TestCase.assertFalse;
@@ -210,50 +207,4 @@ public class SpatialRuleLookupBuilderTest {
         e4.setFlags(em.handleWayTags(livingStreet2, map, relFlags));
         assertEquals(MaxSpeed.UNSET_SPEED, e4.get(tmpCarMaxSpeedEnc), .1);
     }
-
-    @Test
-    public void testSpeed() throws IOException {
-        final FileReader reader = new FileReader(COUNTRIES_FILE);
-        SpatialRuleFactory rulePerCountryFactory = new SpatialRuleFactory() {
-
-            @Override
-            public SpatialRule createSpatialRule(final String id, final List<Polygon> borders) {
-                return new AbstractSpatialRule(borders) {
-                    
-                    @Override
-                    public double getMaxSpeed(RoadClass roadClass, TransportationMode transport, double currentMaxSpeed) {
-                        return 100;
-                    }
-
-                    @Override
-                    public RoadAccess getAccess(RoadClass roadClass, TransportationMode transport, RoadAccess currentRoadAccess) {
-                        return RoadAccess.YES;
-                    }
-
-                    @Override
-                    public String getId() {
-                        return id;
-                    }
-                };
-            }
-        };
-        SpatialRuleLookup spatialRuleLookup = SpatialRuleLookupBuilder.buildIndex(Collections.singletonList(
-                Jackson.newObjectMapper().readValue(reader, JsonFeatureCollection.class)), "ISO_A3", rulePerCountryFactory);
-
-        // generate random points in central Europe
-        int randomPoints = 250_000;
-        long start = System.nanoTime();
-        for (int i = 0; i < randomPoints; i++) {
-            double lat = 46d + Math.random() * 7d;
-            double lon = 6d + Math.random() * 21d;
-            spatialRuleLookup.lookupRule(new GHPoint(lat, lon));
-        }
-
-        long duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
-        // System.out.println("Lookup of " + randomPoints + " points took " + duration + "ms");
-        // System.out.println("Average lookup duration: " + ((double) duration) / randomPoints + "ms");
-        long maxBenchmarkRuntimeMs = 5_000L;
-        assertTrue("Benchmark must be finished in less than " + maxBenchmarkRuntimeMs + "ms",
-                duration < maxBenchmarkRuntimeMs);
-    }
 }
diff --git a/core/src/test/java/com/graphhopper/util/DistanceCalc2DTest.java b/core/src/test/java/com/graphhopper/util/DistanceCalcEuclideanTest.java
similarity index 73%
rename from core/src/test/java/com/graphhopper/util/DistanceCalc2DTest.java
rename to core/src/test/java/com/graphhopper/util/DistanceCalcEuclideanTest.java
index 6ce67951c..fca9d7cbe 100644
--- a/core/src/test/java/com/graphhopper/util/DistanceCalc2DTest.java
+++ b/core/src/test/java/com/graphhopper/util/DistanceCalcEuclideanTest.java
@@ -23,11 +23,11 @@ import org.junit.Test;
 
 import com.graphhopper.util.shapes.GHPoint;
 
-public class DistanceCalc2DTest {
+public class DistanceCalcEuclideanTest {
 
     @Test
     public void testCrossingPointToEdge() {
-        DistanceCalc2D distanceCalc = new DistanceCalc2D();
+        DistanceCalcEuclidean distanceCalc = new DistanceCalcEuclidean();
         GHPoint point = distanceCalc.calcCrossingPointToEdge(0, 10, 0, 0, 10, 10);
         Assert.assertEquals(5, point.getLat(), 0);
         Assert.assertEquals(5, point.getLon(), 0);
@@ -35,17 +35,30 @@ public class DistanceCalc2DTest {
 
     @Test
     public void testCalcNormalizedEdgeDistance() {
-        DistanceCalc2D distanceCalc = new DistanceCalc2D();
+        DistanceCalcEuclidean distanceCalc = new DistanceCalcEuclidean();
         double distance = distanceCalc.calcNormalizedEdgeDistance(0, 10, 0, 0, 10, 10);
         Assert.assertEquals(50, distance, 0);
     }
 
     @Test
     public void testValidEdgeDistance() {
-        DistanceCalc2D distanceCalc = new DistanceCalc2D();
+        DistanceCalcEuclidean distanceCalc = new DistanceCalcEuclidean();
         boolean validEdgeDistance = distanceCalc.validEdgeDistance(5, 15, 0, 0, 10, 10);
         Assert.assertEquals(false, validEdgeDistance);
         validEdgeDistance = distanceCalc.validEdgeDistance(15, 5, 0, 0, 10, 10);
         Assert.assertEquals(false, validEdgeDistance);
     }
+
+    @Test
+    public void testDistance3dEuclidean() {
+        DistanceCalcEuclidean distCalc = new DistanceCalcEuclidean();
+        Assert.assertEquals(1, distCalc.calcDist3D(
+                0, 0, 0,
+                0, 0, 1
+        ), 1e-6);
+        Assert.assertEquals(10, distCalc.calcDist3D(
+                0, 0, 0,
+                0, 0, 10
+        ), 1e-6);
+    }
 }
diff --git a/core/src/test/java/com/graphhopper/util/GHUtilityTest.java b/core/src/test/java/com/graphhopper/util/GHUtilityTest.java
index 77e5b7a89..971658c03 100644
--- a/core/src/test/java/com/graphhopper/util/GHUtilityTest.java
+++ b/core/src/test/java/com/graphhopper/util/GHUtilityTest.java
@@ -70,7 +70,7 @@ public class GHUtilityTest {
 
     double getLengthOfAllEdges(Graph graph) {
         double distance = 0;
-        DistanceCalc calc = new DistanceCalc2D();
+        DistanceCalc calc = new DistanceCalcEuclidean();
         AllEdgesIterator iter = graph.getAllEdges();
         while (iter.next()) {
             // This is meant to verify that all of the same edges (including tower nodes)
diff --git a/reader-gtfs/src/test/java/com/graphhopper/reader/gtfs/GraphExplorerTest.java b/reader-gtfs/src/test/java/com/graphhopper/reader/gtfs/GraphExplorerTest.java
index 440701ae4..9a8ae4bb6 100644
--- a/reader-gtfs/src/test/java/com/graphhopper/reader/gtfs/GraphExplorerTest.java
+++ b/reader-gtfs/src/test/java/com/graphhopper/reader/gtfs/GraphExplorerTest.java
@@ -26,7 +26,7 @@ import com.graphhopper.routing.weighting.FastestWeighting;
 import com.graphhopper.storage.GraphHopperStorage;
 import com.graphhopper.storage.RAMDirectory;
 import com.graphhopper.storage.index.QueryResult;
-import com.graphhopper.util.DistanceCalc2D;
+import com.graphhopper.util.DistanceCalcEuclidean;
 import com.graphhopper.util.EdgeIteratorState;
 import com.graphhopper.util.PointList;
 import org.junit.Test;
@@ -184,12 +184,12 @@ public class GraphExplorerTest {
         point1.setQueryDistance(0.0);
         point1.setWayIndex(0);
         point1.setSnappedPosition(QueryResult.Position.EDGE);
-        point1.calcSnappedPoint(new DistanceCalc2D());
+        point1.calcSnappedPoint(new DistanceCalcEuclidean());
         point2.setClosestEdge(d);
         point2.setQueryDistance(0.0);
         point2.setWayIndex(0);
         point2.setSnappedPosition(QueryResult.Position.EDGE);
-        point2.calcSnappedPoint(new DistanceCalc2D());
+        point2.calcSnappedPoint(new DistanceCalcEuclidean());
         QueryGraph queryGraph = QueryGraph.lookup(wrapperGraph, point1, point2);
 
         GraphExplorer testee = new GraphExplorer(queryGraph, new FastestWeighting(foot), pt, gtfsStorage, realtimeFeed, false, false, 5.0, false);
diff --git a/reader-osm/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java b/reader-osm/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java
index 475178b5e..31e525fa2 100644
--- a/reader-osm/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java
+++ b/reader-osm/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java
@@ -134,7 +134,7 @@ public class GraphHopperOSMTest {
                 setDataReaderFile(testOsm);
         gh.importOrLoad();
 
-        assertFalse(gh.getAlgorithmFactory(new HintsMap(weighting)) instanceof CHRoutingAlgorithmFactory);
+        assertFalse(gh.getAlgorithmFactory("profile", false, false) instanceof CHRoutingAlgorithmFactory);
 
         GHResponse rsp = gh.route(new GHRequest(51.2492152, 9.4317166, 51.2, 9.4)
                 .setVehicle(vehicle).setWeighting(weighting));
@@ -156,7 +156,7 @@ public class GraphHopperOSMTest {
                 setGraphHopperLocation(ghLoc).
                 setDataReaderFile(testOsm);
 
-        assertFalse(gh.getAlgorithmFactory(new HintsMap(weighting)) instanceof CHRoutingAlgorithmFactory);
+        assertFalse(gh.getAlgorithmFactory("profile", false, false) instanceof CHRoutingAlgorithmFactory);
         gh.close();
     }
 
@@ -988,23 +988,22 @@ public class GraphHopperOSMTest {
         CHPreparationHandler chHandler = new CHPreparationHandler();
         Weighting fwSimpleTruck = new FastestWeighting(simpleTruck);
         Weighting fwTruck = new FastestWeighting(truck);
-        GraphHopperStorage storage = new GraphBuilder(em).setCHProfiles(Arrays.asList(CHProfile.nodeBased(fwSimpleTruck), CHProfile.nodeBased(fwTruck))).build();
-        chHandler.addCHProfile(CHProfile.nodeBased(fwSimpleTruck));
-        chHandler.addCHProfile(CHProfile.nodeBased(fwTruck));
-        chHandler.addPreparation(PrepareContractionHierarchies.fromGraphHopperStorage(storage, CHProfile.nodeBased(fwSimpleTruck)));
-        chHandler.addPreparation(PrepareContractionHierarchies.fromGraphHopperStorage(storage, CHProfile.nodeBased(fwTruck)));
-
-        HintsMap wMap = new HintsMap("fastest");
-        wMap.put("vehicle", "truck");
-        assertEquals("fastest|truck", ((CHRoutingAlgorithmFactory) chHandler.getAlgorithmFactory(wMap)).getWeighting().toString());
-        wMap.put("vehicle", "simple_truck");
-        assertEquals("fastest|simple_truck", ((CHRoutingAlgorithmFactory) chHandler.getAlgorithmFactory(wMap)).getWeighting().toString());
+        CHProfile simpleTruckProfile = CHProfile.nodeBased("simple_truck", fwSimpleTruck);
+        CHProfile truckProfile = CHProfile.nodeBased("truck", fwTruck);
+        GraphHopperStorage storage = new GraphBuilder(em).setCHProfiles(Arrays.asList(simpleTruckProfile, truckProfile)).build();
+        chHandler.addCHProfile(simpleTruckProfile);
+        chHandler.addCHProfile(truckProfile);
+        chHandler.addPreparation(PrepareContractionHierarchies.fromGraphHopperStorage(storage, simpleTruckProfile));
+        chHandler.addPreparation(PrepareContractionHierarchies.fromGraphHopperStorage(storage, truckProfile));
+
+        assertEquals("fastest|truck", ((CHRoutingAlgorithmFactory) chHandler.getAlgorithmFactory("truck")).getWeighting().toString());
+        assertEquals("fastest|simple_truck", ((CHRoutingAlgorithmFactory) chHandler.getAlgorithmFactory("simple_truck")).getWeighting().toString());
 
         // make sure weighting cannot be mixed
-        chHandler.addCHProfile(CHProfile.nodeBased(fwTruck));
-        chHandler.addCHProfile(CHProfile.nodeBased(fwSimpleTruck));
+        chHandler.addCHProfile(truckProfile);
+        chHandler.addCHProfile(simpleTruckProfile);
         try {
-            chHandler.addPreparation(PrepareContractionHierarchies.fromGraphHopperStorage(storage, CHProfile.nodeBased(fwSimpleTruck)));
+            chHandler.addPreparation(PrepareContractionHierarchies.fromGraphHopperStorage(storage, simpleTruckProfile));
             fail();
         } catch (Exception ex) {
         }
diff --git a/web/src/test/java/com/graphhopper/http/GraphHopperLandmarksTest.java b/web/src/test/java/com/graphhopper/http/GraphHopperLandmarksTest.java
index e29f87103..327027edc 100644
--- a/web/src/test/java/com/graphhopper/http/GraphHopperLandmarksTest.java
+++ b/web/src/test/java/com/graphhopper/http/GraphHopperLandmarksTest.java
@@ -21,18 +21,19 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.graphhopper.config.CHProfileConfig;
 import com.graphhopper.config.LMProfileConfig;
 import com.graphhopper.config.ProfileConfig;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
 import com.graphhopper.util.Helper;
 import io.dropwizard.testing.junit.DropwizardAppRule;
-import org.junit.AfterClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-
-import javax.ws.rs.core.Response;
 import java.io.File;
 import java.util.Collections;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import org.junit.ClassRule;
+import org.junit.Test;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
+import javax.ws.rs.core.Response;
+import org.junit.AfterClass;
 
 /**
  * Tests the creation of Landmarks and parsing the map.geo.json file
@@ -42,7 +43,7 @@ import static org.junit.Assert.assertTrue;
 public class GraphHopperLandmarksTest {
     private static final String DIR = "./target/landmark-test-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         config.getGraphHopperConfiguration().
@@ -67,7 +68,7 @@ public class GraphHopperLandmarksTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule(
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(
             GraphHopperApplication.class, config);
 
     @AfterClass
@@ -77,14 +78,14 @@ public class GraphHopperLandmarksTest {
 
     @Test
     public void testQueries() {
-        Response response = app.client().target("http://localhost:8080/route?point=55.99022,29.129734&point=56.001069,29.150848").request().buildGet().invoke();
+        Response response = clientTarget(app, "/route?point=55.99022,29.129734&point=56.001069,29.150848").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         JsonNode path = json.get("paths").get(0);
         double distance = path.get("distance").asDouble();
         assertEquals("distance wasn't correct:" + distance, 1870, distance, 100);
 
-        response = app.client().target("http://localhost:8080/route?point=55.99022,29.129734&point=56.001069,29.150848&ch.disable=true").request().buildGet().invoke();
+        response = clientTarget(app, "route?point=55.99022,29.129734&point=56.001069,29.150848&ch.disable=true").request().buildGet().invoke();
         json = response.readEntity(JsonNode.class);
         distance = json.get("paths").get(0).get("distance").asDouble();
         assertEquals("distance wasn't correct:" + distance, 1870, distance, 100);
@@ -94,16 +95,16 @@ public class GraphHopperLandmarksTest {
     public void testLandmarkDisconnect() {
         // if one algorithm is disabled then the following chain is executed: CH -> LM -> flexible
         // disconnected for landmarks
-        Response response = app.client().target("http://localhost:8080/route?" + "point=55.99022,29.129734&point=56.007787,29.208355&ch.disable=true").request().buildGet().invoke();
+        Response response = clientTarget(app, "route?point=55.99022,29.129734&point=56.007787,29.208355&ch.disable=true").request().buildGet().invoke();
         assertEquals(400, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         assertTrue(json.get("message").toString().contains("Different subnetworks"));
 
         // without landmarks it should work
-        response = app.client().target("http://localhost:8080/route?" + "point=55.99022,29.129734&point=56.007787,29.208355&ch.disable=true&lm.disable=true").request().buildGet().invoke();
+        response = clientTarget(app, "route?point=55.99022,29.129734&point=56.007787,29.208355&ch.disable=true&lm.disable=true").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         json = response.readEntity(JsonNode.class);
         double distance = json.get("paths").get(0).get("distance").asDouble();
         assertEquals("distance wasn't correct:" + distance, 5790, distance, 100);
     }
-}
\ No newline at end of file
+}
diff --git a/web/src/test/java/com/graphhopper/http/SpatialRulesTest.java b/web/src/test/java/com/graphhopper/http/SpatialRulesTest.java
index 664ba7cfe..efcf870f2 100644
--- a/web/src/test/java/com/graphhopper/http/SpatialRulesTest.java
+++ b/web/src/test/java/com/graphhopper/http/SpatialRulesTest.java
@@ -18,16 +18,16 @@
 package com.graphhopper.http;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
 import com.graphhopper.util.Helper;
 import io.dropwizard.testing.junit.DropwizardAppRule;
+import java.io.File;
+import javax.ws.rs.core.Response;
 import org.junit.AfterClass;
+import static org.junit.Assert.*;
 import org.junit.ClassRule;
 import org.junit.Test;
-
-import javax.ws.rs.core.Response;
-import java.io.File;
-
-import static org.junit.Assert.*;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
 
 /**
  * Tests the DataFlagEncoder with the SpatialRuleLookup enabled
@@ -37,10 +37,10 @@ import static org.junit.Assert.*;
 public class SpatialRulesTest {
     private static final String DIR = "./target/north-bayreuth-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
-     // The EncodedValue "country" requires the setting "spatial_rules.borders_directory" as "country" does not load via DefaultTagParserFactory
+        // The EncodedValue "country" requires the setting "spatial_rules.borders_directory" as "country" does not load via DefaultTagParserFactory
         // TODO should we automatically detect this somehow and include a default country file?
         config.getGraphHopperConfiguration().
                 put("graph.flag_encoders", "car").
@@ -52,7 +52,7 @@ public class SpatialRulesTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule<>(
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(
             GraphHopperApplication.class, config);
 
     @AfterClass
@@ -62,7 +62,7 @@ public class SpatialRulesTest {
 
     @Test
     public void testDetourToComplyWithSpatialRule() {
-        final Response response = app.client().target("http://localhost:8080/route?" + "point=49.995933,11.54809&point=50.004871,11.517191").request().buildGet().invoke();
+        final Response response = clientTarget(app, "route?" + "point=49.995933,11.54809&point=50.004871,11.517191").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         assertFalse(json.get("info").has("errors"));
diff --git a/web/src/test/java/com/graphhopper/http/resources/ChangeGraphResourceTest.java b/web/src/test/java/com/graphhopper/http/resources/ChangeGraphResourceTest.java
index f7470044d..48141244e 100644
--- a/web/src/test/java/com/graphhopper/http/resources/ChangeGraphResourceTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/ChangeGraphResourceTest.java
@@ -19,18 +19,17 @@ package com.graphhopper.http.resources;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.graphhopper.http.GraphHopperApplication;
-import com.graphhopper.http.GraphHopperServerConfiguration;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
 import com.graphhopper.util.Helper;
 import io.dropwizard.testing.junit.DropwizardAppRule;
-import org.junit.AfterClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-
+import java.io.File;
 import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.Response;
-import java.io.File;
-
+import org.junit.AfterClass;
 import static org.junit.Assert.*;
+import org.junit.ClassRule;
+import org.junit.Test;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
 
 /**
  * @author Peter Karich
@@ -38,7 +37,7 @@ import static org.junit.Assert.*;
 public class ChangeGraphResourceTest {
     private static final String DIR = "./target/andorra-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         config.getGraphHopperConfiguration().
@@ -49,10 +48,9 @@ public class ChangeGraphResourceTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule(
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(
             GraphHopperApplication.class, config);
 
-
     @AfterClass
     public static void cleanUp() {
         Helper.removeDir(new File(DIR));
@@ -60,7 +58,7 @@ public class ChangeGraphResourceTest {
 
     @Test
     public void testBlockAccessViaPoint() throws Exception {
-        Response response = app.client().target("http://localhost:8080/route?point=42.531453,1.518946&point=42.511178,1.54006").request().buildGet().invoke();
+        Response response = clientTarget(app, "route?point=42.531453,1.518946&point=42.511178,1.54006").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         assertFalse(json.get("info").has("errors"));
@@ -82,13 +80,13 @@ public class ChangeGraphResourceTest {
                 + "    \"access\": false"
                 + "  }}]}";
 
-        response = app.client().target("http://localhost:8080/change").request().post(Entity.json(geoJson));
+        response = clientTarget(app, "/change").request().post(Entity.json(geoJson));
         assertEquals(200, response.getStatus());
         json = response.readEntity(JsonNode.class);
         assertEquals(1, json.get("updates").asInt());
 
         // route around blocked road => longer
-        response = app.client().target("http://localhost:8080/route?point=42.531453,1.518946&point=42.511178,1.54006").request().buildGet().invoke();
+        response = clientTarget(app, "/route?point=42.531453,1.518946&point=42.511178,1.54006").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         json = response.readEntity(JsonNode.class);
         assertFalse(json.get("info").has("errors"));
diff --git a/web/src/test/java/com/graphhopper/http/resources/GtfsTest.java b/web/src/test/java/com/graphhopper/http/resources/GtfsTest.java
index 900b7ad81..6777bb45c 100644
--- a/web/src/test/java/com/graphhopper/http/resources/GtfsTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/GtfsTest.java
@@ -19,19 +19,18 @@ package com.graphhopper.http.resources;
 
 import com.graphhopper.GHResponse;
 import com.graphhopper.http.GraphHopperApplication;
-import com.graphhopper.http.GraphHopperServerConfiguration;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
 import com.graphhopper.resources.InfoResource;
 import com.graphhopper.util.Helper;
 import io.dropwizard.testing.junit.DropwizardAppRule;
+import java.io.File;
+import javax.ws.rs.core.Response;
 import org.junit.AfterClass;
+import static org.junit.Assert.*;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
-
-import javax.ws.rs.core.Response;
-import java.io.File;
-
-import static org.junit.Assert.*;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
 
 /**
  * Similar to PtRouteResourceTest, but tests the entire app, not the resource, so that the plugging-together
@@ -40,7 +39,7 @@ import static org.junit.Assert.*;
 public class GtfsTest {
     private static final String DIR = "./target/gtfs-app-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         config.getGraphHopperConfiguration().
@@ -51,7 +50,7 @@ public class GtfsTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule<>(GraphHopperApplication.class, config);
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(GraphHopperApplication.class, config);
 
     @BeforeClass
     @AfterClass
@@ -61,7 +60,7 @@ public class GtfsTest {
 
     @Test
     public void testStationStationQuery() {
-        final Response response = app.client().target("http://localhost:8080/route")
+        final Response response = clientTarget(app, "/route")
                 .queryParam("point", "Stop(NADAV)")
                 .queryParam("point", "Stop(NANAA)")
                 .queryParam("vehicle", "pt")
@@ -74,9 +73,9 @@ public class GtfsTest {
 
     @Test
     public void testPointPointQuery() {
-        final Response response = app.client().target("http://localhost:8080/route")
-                .queryParam("point","36.914893,-116.76821") // NADAV stop
-                .queryParam("point","36.914944,-116.761472") //NANAA stop
+        final Response response = clientTarget(app, "/route")
+                .queryParam("point", "36.914893,-116.76821") // NADAV stop
+                .queryParam("point", "36.914944,-116.761472") //NANAA stop
                 .queryParam("vehicle", "pt")
                 .queryParam("pt.earliest_departure_time", "2007-01-01T08:00:00Z")
                 .request().buildGet().invoke();
@@ -87,7 +86,7 @@ public class GtfsTest {
 
     @Test
     public void testWalkQuery() {
-        final Response response = app.client().target("http://localhost:8080/route")
+        final Response response = clientTarget(app, "/route")
                 .queryParam("point", "36.914893,-116.76821")
                 .queryParam("point", "36.914944,-116.761472")
                 .queryParam("vehicle", "foot")
@@ -99,7 +98,7 @@ public class GtfsTest {
 
     @Test
     public void testInfo() {
-        final Response response = app.client().target("http://localhost:8080/info")
+        final Response response = clientTarget(app, "/info")
                 .request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         InfoResource.Info info = response.readEntity(InfoResource.Info.class);
diff --git a/web/src/test/java/com/graphhopper/http/resources/I18nResourceTest.java b/web/src/test/java/com/graphhopper/http/resources/I18nResourceTest.java
index 4eb5d8745..b7795d62f 100644
--- a/web/src/test/java/com/graphhopper/http/resources/I18nResourceTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/I18nResourceTest.java
@@ -1,7 +1,7 @@
 package com.graphhopper.http.resources;
 
 import com.graphhopper.http.GraphHopperApplication;
-import com.graphhopper.http.GraphHopperServerConfiguration;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
 import com.graphhopper.util.Helper;
 import io.dropwizard.testing.junit.DropwizardAppRule;
 import org.junit.AfterClass;
@@ -13,11 +13,12 @@ import java.io.File;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
 
 public class I18nResourceTest {
     private static final String DIR = "./target/andorra-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         config.getGraphHopperConfiguration().
@@ -27,7 +28,7 @@ public class I18nResourceTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule(
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(
             GraphHopperApplication.class, config);
 
     @AfterClass
@@ -37,12 +38,12 @@ public class I18nResourceTest {
 
     @Test
     public void requestI18n() {
-        Response response = app.client().target("http://localhost:8080/i18n").request().buildGet().invoke();
+        Response response = clientTarget(app, "/i18n").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         String str = response.readEntity(String.class);
         assertTrue(str, str.contains("\"en\":") && str.contains("\"locale\":\"\""));
 
-        response = app.client().target("http://localhost:8080/i18n/de").request().buildGet().invoke();
+        response = clientTarget(app, "/i18n/de").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         str = response.readEntity(String.class);
         assertTrue(str, str.contains("\"default\":") && str.contains("\"locale\":\"de\""));
diff --git a/web/src/test/java/com/graphhopper/http/resources/IsochroneResourceTest.java b/web/src/test/java/com/graphhopper/http/resources/IsochroneResourceTest.java
index 72a8c3b9e..736fda1de 100644
--- a/web/src/test/java/com/graphhopper/http/resources/IsochroneResourceTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/IsochroneResourceTest.java
@@ -20,7 +20,8 @@ package com.graphhopper.http.resources;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.graphhopper.http.GraphHopperApplication;
-import com.graphhopper.http.GraphHopperServerConfiguration;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
 import com.graphhopper.json.geo.JsonFeatureCollection;
 import com.graphhopper.util.Helper;
 import io.dropwizard.testing.junit.DropwizardAppRule;
@@ -42,7 +43,7 @@ import static org.junit.Assert.assertEquals;
 public class IsochroneResourceTest {
     private static final String DIR = "./target/andorra-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         config.getGraphHopperConfiguration().
@@ -52,7 +53,7 @@ public class IsochroneResourceTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule<>(
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(
             GraphHopperApplication.class, config);
 
     @AfterClass
@@ -64,7 +65,7 @@ public class IsochroneResourceTest {
 
     @Test
     public void requestByTimeLimit() {
-        Response rsp = app.client().target("http://localhost:8080/isochrone")
+        Response rsp = clientTarget(app, "/isochrone")
                 .queryParam("point", "42.531073,1.573792")
                 .queryParam("time_limit", 5 * 60)
                 .queryParam("buckets", 2)
@@ -85,7 +86,7 @@ public class IsochroneResourceTest {
 
     @Test
     public void requestByDistanceLimit() {
-        Response rsp = app.client().target("http://localhost:8080/isochrone")
+        Response rsp = clientTarget(app, "/isochrone")
                 .queryParam("point", "42.531073,1.573792")
                 .queryParam("distance_limit", 3_000)
                 .queryParam("buckets", 2)
@@ -106,7 +107,7 @@ public class IsochroneResourceTest {
 
     @Test
     public void requestReverseFlow() {
-        Response rsp = app.client().target("http://localhost:8080/isochrone")
+        Response rsp = clientTarget(app, "/isochrone")
                 .queryParam("point", "42.531073,1.573792")
                 .queryParam("reverse_flow", true)
                 .queryParam("time_limit", 5 * 60)
@@ -131,13 +132,13 @@ public class IsochroneResourceTest {
 
     @Test
     public void requestBadRequest() {
-        Response response = app.client().target("http://localhost:8080/route?point=-1.816719,51.557148").request().buildGet().invoke();
+        Response response = clientTarget(app, "/route?point=-1.816719,51.557148").request().buildGet().invoke();
         assertEquals(400, response.getStatus());
     }
 
     @Test
     public void requestWithShortest() {
-        Response rsp = app.client().target("http://localhost:8080/isochrone")
+        Response rsp = clientTarget(app, "/isochrone")
                 .queryParam("point", "42.509644,1.540554")
                 .queryParam("time_limit", 130)
                 .queryParam("buckets", 1)
@@ -155,7 +156,7 @@ public class IsochroneResourceTest {
         // more like a circle => shorter is expected
         assertTrue(polygon0.getCoordinates().length < 185);
 
-        rsp = app.client().target("http://localhost:8080/isochrone")
+        rsp = clientTarget(app, "/isochrone")
                 .queryParam("point", "42.509644,1.540554")
                 .queryParam("time_limit", 130)
                 .queryParam("buckets", 1)
@@ -169,7 +170,7 @@ public class IsochroneResourceTest {
 
     @Test
     public void requestJsonBadType() {
-        Response response = app.client().target("http://localhost:8080/isochrone?point=42.531073,1.573792&time_limit=130&type=xml")
+        Response response = clientTarget(app, "/isochrone?point=42.531073,1.573792&time_limit=130&type=xml")
                 .request().buildGet().invoke();
 
         JsonNode json = response.readEntity(JsonNode.class);
@@ -180,7 +181,7 @@ public class IsochroneResourceTest {
 
     @Test
     public void requestWithBlockArea() {
-        Response rsp = app.client().target("http://localhost:8080/isochrone")
+        Response rsp = clientTarget(app, "/isochrone")
                 .queryParam("point", "42.531073,1.573792")
                 .queryParam("time_limit", 5 * 60)
                 .queryParam("buckets", 2)
@@ -204,7 +205,7 @@ public class IsochroneResourceTest {
 
     @Test
     public void requestJsonWithType() {
-        Response response = app.client().target("http://localhost:8080/isochrone?point=42.531073,1.573792&time_limit=130&type=json")
+        Response response = clientTarget(app, "/isochrone?point=42.531073,1.573792&time_limit=130&type=json")
                 .request().buildGet().invoke();
         JsonNode json = response.readEntity(JsonNode.class);
         assertTrue(json.has("polygons"));
@@ -213,7 +214,7 @@ public class IsochroneResourceTest {
 
     @Test
     public void requestJsonNoType() {
-        Response response = app.client().target("http://localhost:8080/isochrone?point=42.531073,1.573792&time_limit=130")
+        Response response = clientTarget(app, "/isochrone?point=42.531073,1.573792&time_limit=130")
                 .request().buildGet().invoke();
         JsonNode json = response.readEntity(JsonNode.class);
         assertTrue(json.has("polygons"));
@@ -222,7 +223,7 @@ public class IsochroneResourceTest {
 
     @Test
     public void requestGeoJsonPolygons() {
-        Response response = app.client().target("http://localhost:8080/isochrone?point=42.531073,1.573792&time_limit=130&type=geojson")
+        Response response = clientTarget(app, "/isochrone?point=42.531073,1.573792&time_limit=130&type=geojson")
                 .request().buildGet().invoke();
         JsonNode json = response.readEntity(JsonNode.class);
 
@@ -246,7 +247,7 @@ public class IsochroneResourceTest {
 
     @Test
     public void requestGeoJsonPolygonsBuckets() {
-        Response response = app.client().target("http://localhost:8080/isochrone?point=42.531073,1.573792&time_limit=130&type=geojson&buckets=3")
+        Response response = clientTarget(app, "/isochrone?point=42.531073,1.573792&time_limit=130&type=geojson&buckets=3")
                 .request().buildGet().invoke();
         JsonNode json = response.readEntity(JsonNode.class);
 
@@ -261,4 +262,4 @@ public class IsochroneResourceTest {
         assertEquals(lastFeature.path("geometry").path("type").asText(), "Polygon");
     }
 
-}
\ No newline at end of file
+}
diff --git a/web/src/test/java/com/graphhopper/http/resources/MvtResourceTest.java b/web/src/test/java/com/graphhopper/http/resources/MvtResourceTest.java
index a6eac007e..43ffc193d 100644
--- a/web/src/test/java/com/graphhopper/http/resources/MvtResourceTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/MvtResourceTest.java
@@ -18,7 +18,7 @@
 package com.graphhopper.http.resources;
 
 import com.graphhopper.http.GraphHopperApplication;
-import com.graphhopper.http.GraphHopperServerConfiguration;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
 import com.graphhopper.util.Helper;
 import com.wdtinc.mapbox_vector_tile.adapt.jts.MvtReader;
 import com.wdtinc.mapbox_vector_tile.adapt.jts.TagKeyValueMapConverter;
@@ -42,6 +42,7 @@ import java.util.Map;
 
 import static junit.framework.TestCase.assertTrue;
 import static org.junit.Assert.assertEquals;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
 
 /**
  * @author Peter Karich
@@ -49,7 +50,7 @@ import static org.junit.Assert.assertEquals;
 public class MvtResourceTest {
     private static final String DIR = "./target/andorra-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         config.getGraphHopperConfiguration().
@@ -62,7 +63,7 @@ public class MvtResourceTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule<>(GraphHopperApplication.class, config);
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(GraphHopperApplication.class, config);
 
     @BeforeClass
     @AfterClass
@@ -72,7 +73,7 @@ public class MvtResourceTest {
 
     @Test
     public void testBasicMvtQuery() throws IOException {
-        final Response response = app.client().target("http://localhost:8080/mvt/15/16528/12099.mvt").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/mvt/15/16528/12099.mvt").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         InputStream is = response.readEntity(InputStream.class);
         JtsMvt result = MvtReader.loadMvt(is, new GeometryFactory(), new TagKeyValueMapConverter());
@@ -88,7 +89,7 @@ public class MvtResourceTest {
 
     @Test
     public void testWithDetailsInResponse() throws IOException {
-        final Response response = app.client().target("http://localhost:8080/mvt/15/16522/12102.mvt?details=max_speed&details=road_class&details=road_environment").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/mvt/15/16522/12102.mvt?details=max_speed&details=road_class&details=road_environment").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         InputStream is = response.readEntity(InputStream.class);
         JtsMvt result = MvtReader.loadMvt(is, new GeometryFactory(), new TagKeyValueMapConverter());
diff --git a/web/src/test/java/com/graphhopper/http/resources/NearestResourceTest.java b/web/src/test/java/com/graphhopper/http/resources/NearestResourceTest.java
index fa63aa957..4dc2376e8 100644
--- a/web/src/test/java/com/graphhopper/http/resources/NearestResourceTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/NearestResourceTest.java
@@ -18,7 +18,7 @@
 package com.graphhopper.http.resources;
 
 import com.graphhopper.http.GraphHopperApplication;
-import com.graphhopper.http.GraphHopperServerConfiguration;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
 import com.graphhopper.resources.NearestResource;
 import com.graphhopper.util.Helper;
 import io.dropwizard.testing.junit.DropwizardAppRule;
@@ -31,6 +31,7 @@ import java.io.File;
 
 import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertThat;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
 
 /**
  * @author svantulden
@@ -38,7 +39,7 @@ import static org.junit.Assert.assertThat;
 public class NearestResourceTest {
     private static final String dir = "./target/andorra-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         config.getGraphHopperConfiguration().
@@ -48,7 +49,7 @@ public class NearestResourceTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule(
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(
             GraphHopperApplication.class, config);
 
 
@@ -59,7 +60,7 @@ public class NearestResourceTest {
 
     @Test
     public void testBasicNearestQuery() throws Exception {
-        final Response response = app.client().target("http://localhost:8080/nearest?point=42.554851,1.536198").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/nearest?point=42.554851,1.536198").request().buildGet().invoke();
         assertThat("HTTP status", response.getStatus(), is(200));
         NearestResource.Response json = response.readEntity(NearestResource.Response.class);
         assertThat("nearest point", json.coordinates, is(new double[]{1.5363742288086868, 42.55483907636756}));
diff --git a/web/src/test/java/com/graphhopper/http/resources/NearestResourceWithEleTest.java b/web/src/test/java/com/graphhopper/http/resources/NearestResourceWithEleTest.java
index a022cca0c..e81f3022e 100644
--- a/web/src/test/java/com/graphhopper/http/resources/NearestResourceWithEleTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/NearestResourceWithEleTest.java
@@ -20,7 +20,7 @@ package com.graphhopper.http.resources;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.graphhopper.http.GraphHopperApplication;
-import com.graphhopper.http.GraphHopperServerConfiguration;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
 import com.graphhopper.util.Helper;
 import io.dropwizard.testing.junit.DropwizardAppRule;
 import org.junit.AfterClass;
@@ -31,6 +31,7 @@ import java.io.File;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
 
 /**
  * @author svantulden
@@ -38,7 +39,7 @@ import static org.junit.Assert.assertTrue;
 public class NearestResourceWithEleTest {
     private static final String dir = "./target/monaco-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         config.getGraphHopperConfiguration().
@@ -51,7 +52,7 @@ public class NearestResourceWithEleTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule(
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(
             GraphHopperApplication.class, config);
 
     @AfterClass
@@ -61,7 +62,7 @@ public class NearestResourceWithEleTest {
 
     @Test
     public void testWithEleQuery() throws Exception {
-        JsonNode json = app.client().target("http://localhost:8080/nearest?point=43.730864,7.420771&elevation=true").request().buildGet().invoke().readEntity(JsonNode.class);
+        JsonNode json = clientTarget(app, "/nearest?point=43.730864,7.420771&elevation=true").request().buildGet().invoke().readEntity(JsonNode.class);
         assertFalse(json.has("error"));
         ArrayNode point = (ArrayNode) json.get("coordinates");
         assertTrue("returned point is not 3D: " + point, point.size() == 3);
@@ -73,7 +74,7 @@ public class NearestResourceWithEleTest {
 
     @Test
     public void testWithoutEleQuery() throws Exception {
-        JsonNode json = app.client().target("http://localhost:8080/nearest?point=43.730864,7.420771&elevation=false").request().buildGet().invoke().readEntity(JsonNode.class);
+        JsonNode json = clientTarget(app, "/nearest?point=43.730864,7.420771&elevation=false").request().buildGet().invoke().readEntity(JsonNode.class);
         assertFalse(json.has("error"));
         ArrayNode point = (ArrayNode) json.get("coordinates");
         assertTrue("returned point is not 2D: " + point, point.size() == 2);
@@ -82,7 +83,7 @@ public class NearestResourceWithEleTest {
         assertTrue("nearest point wasn't correct: lat=" + lat + ", lon=" + lon, lat == 43.73070006215647 && lon == 7.421392181993846);
 
         // Default elevation is false        
-        json = app.client().target("http://localhost:8080/nearest?point=43.730864,7.420771").request().buildGet().invoke().readEntity(JsonNode.class);
+        json = clientTarget(app, "/nearest?point=43.730864,7.420771").request().buildGet().invoke().readEntity(JsonNode.class);
         assertFalse(json.has("error"));
         point = (ArrayNode) json.get("coordinates");
         assertTrue("returned point is not 2D: " + point, point.size() == 2);
diff --git a/web/src/test/java/com/graphhopper/http/resources/PtIsochroneTest.java b/web/src/test/java/com/graphhopper/http/resources/PtIsochroneTest.java
index 5a094db23..ab74aa124 100644
--- a/web/src/test/java/com/graphhopper/http/resources/PtIsochroneTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/PtIsochroneTest.java
@@ -19,7 +19,7 @@
 package com.graphhopper.http.resources;
 
 import com.graphhopper.http.GraphHopperApplication;
-import com.graphhopper.http.GraphHopperServerConfiguration;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
 import com.graphhopper.resources.PtIsochroneResource;
 import com.graphhopper.util.Helper;
 import io.dropwizard.testing.junit.DropwizardAppRule;
@@ -39,6 +39,7 @@ import java.time.ZoneId;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
 
 public class PtIsochroneTest {
 
@@ -46,7 +47,7 @@ public class PtIsochroneTest {
     private static final ZoneId zoneId = ZoneId.of("America/Los_Angeles");
     private GeometryFactory geometryFactory = new GeometryFactory();
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         config.getGraphHopperConfiguration()
@@ -57,7 +58,7 @@ public class PtIsochroneTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule<>(GraphHopperApplication.class, config);
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(GraphHopperApplication.class, config);
 
     @BeforeClass
     @AfterClass
@@ -67,8 +68,7 @@ public class PtIsochroneTest {
 
     @Test
     public void testIsoline() {
-        WebTarget webTarget = app.client()
-                .target("http://localhost:8080/isochrone")
+        WebTarget webTarget = clientTarget(app, "/isochrone")
                 .queryParam("vehicle", "pt")
                 .queryParam("point", "36.914893,-116.76821") // NADAV
                 .queryParam("pt.earliest_departure_time", LocalDateTime.of(2007, 1, 1, 0, 0, 0).atZone(zoneId).toInstant())
diff --git a/web/src/test/java/com/graphhopper/http/resources/RouteResourceIssue1574Test.java b/web/src/test/java/com/graphhopper/http/resources/RouteResourceIssue1574Test.java
index 8531c94d4..cc1c81663 100644
--- a/web/src/test/java/com/graphhopper/http/resources/RouteResourceIssue1574Test.java
+++ b/web/src/test/java/com/graphhopper/http/resources/RouteResourceIssue1574Test.java
@@ -21,7 +21,8 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.graphhopper.config.CHProfileConfig;
 import com.graphhopper.config.ProfileConfig;
 import com.graphhopper.http.GraphHopperApplication;
-import com.graphhopper.http.GraphHopperServerConfiguration;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
 import com.graphhopper.util.Helper;
 import io.dropwizard.testing.junit.DropwizardAppRule;
 import org.junit.AfterClass;
@@ -42,7 +43,7 @@ import static org.junit.Assert.assertFalse;
 public class RouteResourceIssue1574Test {
     private static final String DIR = "./target/andorra-1574-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         // this is the reason we put this test into an extra file: we can only reproduce the bug of issue 1574 by increasing the one-way-network size
@@ -61,7 +62,7 @@ public class RouteResourceIssue1574Test {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule<>(GraphHopperApplication.class, config);
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(GraphHopperApplication.class, config);
 
     @BeforeClass
     @AfterClass
@@ -71,7 +72,7 @@ public class RouteResourceIssue1574Test {
 
     @Test
     public void testStallOnDemandBug_issue1574() {
-        final Response response = app.client().target("http://localhost:8080/route?point=42.486984,1.493152&point=42.481863,1.491297&point=42.49697,1.501265&&vehicle=car&weighting=fastest&stall_on_demand=true").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/route?point=42.486984,1.493152&point=42.481863,1.491297&point=42.49697,1.501265&&vehicle=car&weighting=fastest&stall_on_demand=true").request().buildGet().invoke();
         JsonNode json = response.readEntity(JsonNode.class);
         assertFalse("there should be no error, but: " + json.get("message"), json.has("message"));
         System.out.println(json);
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 4789e9f9d..b98c65a11 100644
--- a/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java
@@ -26,7 +26,7 @@ import com.graphhopper.api.GraphHopperWeb;
 import com.graphhopper.config.CHProfileConfig;
 import com.graphhopper.config.ProfileConfig;
 import com.graphhopper.http.GraphHopperApplication;
-import com.graphhopper.http.GraphHopperServerConfiguration;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
 import com.graphhopper.routing.profiles.RoadClass;
 import com.graphhopper.routing.profiles.RoadEnvironment;
 import com.graphhopper.routing.profiles.Surface;
@@ -51,6 +51,8 @@ import java.util.List;
 import java.util.Map;
 
 import static org.junit.Assert.*;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
+import static com.graphhopper.http.util.TestUtils.clientUrl;
 
 /**
  * @author Peter Karich
@@ -58,7 +60,7 @@ import static org.junit.Assert.*;
 public class RouteResourceTest {
     private static final String DIR = "./target/andorra-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         config.getGraphHopperConfiguration().
@@ -75,7 +77,7 @@ public class RouteResourceTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule<>(GraphHopperApplication.class, config);
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(GraphHopperApplication.class, config);
 
     @BeforeClass
     @AfterClass
@@ -85,7 +87,7 @@ public class RouteResourceTest {
 
     @Test
     public void testBasicQuery() {
-        final Response response = app.client().target("http://localhost:8080/route?point=42.554851,1.536198&point=42.510071,1.548128").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/route?point=42.554851,1.536198&point=42.510071,1.548128").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         JsonNode infoJson = json.get("info");
@@ -99,7 +101,7 @@ public class RouteResourceTest {
     @Test
     public void testBasicPostQuery() {
         String jsonStr = "{ \"points\": [[1.536198,42.554851], [1.548128, 42.510071]] }";
-        final Response response = app.client().target("http://localhost:8080/route").request().post(Entity.json(jsonStr));
+        final Response response = clientTarget(app, "/route").request().post(Entity.json(jsonStr));
         assertEquals(200, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         JsonNode infoJson = json.get("info");
@@ -112,7 +114,7 @@ public class RouteResourceTest {
 
     @Test
     public void testWrongPointFormat() {
-        final Response response = app.client().target("http://localhost:8080/route?point=1234&point=42.510071,1.548128").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/route?point=1234&point=42.510071,1.548128").request().buildGet().invoke();
         assertEquals(400, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         assertTrue("There should be an error " + json.get("message"), json.get("message").asText().contains("Cannot parse point '1234'"));
@@ -120,7 +122,7 @@ public class RouteResourceTest {
 
     @Test
     public void testQueryWithoutInstructions() {
-        final Response response = app.client().target("http://localhost:8080/route?point=42.554851,1.536198&point=42.510071,1.548128&instructions=false").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/route?point=42.554851,1.536198&point=42.510071,1.548128&instructions=false").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         JsonNode infoJson = json.get("info");
@@ -134,7 +136,7 @@ public class RouteResourceTest {
     @Test
     public void testCHWithHeading_error() {
         // There are special cases where heading works with node-based CH, but generally it leads to wrong results -> we expect an error
-        final Response response = app.client().target("http://localhost:8080/route?" + "point=42.496696,1.499323&point=42.497257,1.501501&heading=240&heading=240").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/route?" + "point=42.496696,1.499323&point=42.497257,1.501501&heading=240&heading=240").request().buildGet().invoke();
         assertEquals(400, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         assertTrue("There should have been an error response", json.has("message"));
@@ -145,7 +147,7 @@ public class RouteResourceTest {
     @Test
     public void testCHWithPassThrough_error() {
         // There are special cases where pass_through works with node-based CH, but generally it leads to wrong results -> we expect an error
-        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();
+        final Response response = clientTarget(app, "/route?point=42.534133,1.581473&point=42.534781,1.582149&point=42.535042,1.582514&pass_through=true").request().buildGet().invoke();
         assertEquals(400, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         assertTrue("There should have been an error response", json.has("message"));
@@ -155,7 +157,7 @@ public class RouteResourceTest {
 
     @Test
     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();
+        final Response response = clientTarget(app, "/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);
         JsonNode cson = json.get("paths").get(0).get("points");
@@ -164,7 +166,7 @@ public class RouteResourceTest {
 
     @Test
     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();
+        final Response response = clientTarget(app, "/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);
         assertTrue(json.has("message"));
@@ -174,7 +176,7 @@ public class RouteResourceTest {
     @Test
     public void testGraphHopperWeb() {
         GraphHopperWeb hopper = new GraphHopperWeb();
-        assertTrue(hopper.load("http://localhost:8080/route"));
+        assertTrue(hopper.load(clientUrl(app, "/route")));
         GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128));
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
         assertTrue(rsp.getErrors().toString(), rsp.getErrors().isEmpty());
@@ -203,7 +205,7 @@ public class RouteResourceTest {
     @Test
     public void testPathDetailsRoadClass() {
         GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
-        assertTrue(hopper.load("http://localhost:8080/route"));
+        assertTrue(hopper.load(clientUrl(app, "/route")));
         GHRequest request = new GHRequest(42.546757, 1.528645, 42.520573, 1.557999);
         request.setPathDetails(Arrays.asList(RoadClass.KEY, Surface.KEY, RoadEnvironment.KEY, "average_speed"));
         GHResponse rsp = hopper.route(request);
@@ -220,7 +222,7 @@ public class RouteResourceTest {
     @Test
     public void testPathDetails() {
         GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
-        assertTrue(hopper.load("http://localhost:8080/route"));
+        assertTrue(hopper.load(clientUrl(app, "/route")));
         GHRequest request = new GHRequest(42.554851, 1.536198, 42.510071, 1.548128);
         request.setPathDetails(Arrays.asList("average_speed", "edge_id", "time"));
         GHResponse rsp = hopper.route(request);
@@ -258,7 +260,7 @@ public class RouteResourceTest {
     @Test
     public void testPathDetailsSamePoint() {
         GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
-        assertTrue(hopper.load("http://localhost:8080/route"));
+        assertTrue(hopper.load(clientUrl(app, "/route")));
         GHRequest request = new GHRequest(42.554851, 1.536198, 42.554851, 1.536198);
         request.setPathDetails(Arrays.asList("average_speed", "edge_id", "time"));
         GHResponse rsp = hopper.route(request);
@@ -269,7 +271,7 @@ public class RouteResourceTest {
     @Test
     public void testPathDetailsNoConnection() {
         GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
-        assertTrue(hopper.load("http://localhost:8080/route"));
+        assertTrue(hopper.load(clientUrl(app, "/route")));
         GHRequest request = new GHRequest(42.542078, 1.45586, 42.537841, 1.439981);
         request.setPathDetails(Collections.singletonList("average_speed"));
         GHResponse rsp = hopper.route(request);
@@ -278,7 +280,7 @@ public class RouteResourceTest {
 
     @Test
     public void testPathDetailsWithoutGraphHopperWeb() {
-        final Response response = app.client().target("http://localhost:8080/route?point=42.554851,1.536198&point=42.510071,1.548128&details=average_speed&details=edge_id&details=max_speed").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/route?point=42.554851,1.536198&point=42.510071,1.548128&details=average_speed&details=edge_id&details=max_speed").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         JsonNode infoJson = json.get("info");
@@ -307,7 +309,7 @@ public class RouteResourceTest {
     @Test
     public void testInitInstructionsWithTurnDescription() {
         GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
-        assertTrue(hopper.load("http://localhost:8080/route"));
+        assertTrue(hopper.load(clientUrl(app, "/route")));
         GHRequest request = new GHRequest(42.554851, 1.536198, 42.510071, 1.548128);
         GHResponse rsp = hopper.route(request);
         assertEquals("Continue onto Carrer Antoni Fiter i Rossell", rsp.getBest().getInstructions().get(3).getName());
@@ -321,7 +323,7 @@ public class RouteResourceTest {
     @Test
     public void testSnapPreventions() {
         GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
-        assertTrue(hopper.load("http://localhost:8080/route"));
+        assertTrue(hopper.load(clientUrl(app, "route")));
         GHRequest request = new GHRequest(42.511139, 1.53285, 42.508165, 1.532271);
         GHResponse rsp = hopper.route(request);
         assertFalse(rsp.getErrors().toString(), rsp.hasErrors());
@@ -335,7 +337,7 @@ public class RouteResourceTest {
     @Test
     public void testSnapPreventionsAndPointHints() {
         GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
-        assertTrue(hopper.load("http://localhost:8080/route"));
+        assertTrue(hopper.load(clientUrl(app, "/route")));
         GHRequest request = new GHRequest(42.511139, 1.53285, 42.508165, 1.532271);
         request.setSnapPreventions(Collections.singletonList("tunnel"));
         request.setPointHints(Arrays.asList("Avinguda Fiter i Rossell", ""));
@@ -353,7 +355,7 @@ public class RouteResourceTest {
     public void testPostWithPointHintsAndSnapPrevention() {
         String jsonStr = "{ \"points\": [[1.53285,42.511139], [1.532271,42.508165]], " +
                 "\"point_hints\":[\"Avinguda Fiter i Rossell\",\"\"] }";
-        Response response = app.client().target("http://localhost:8080/route").request().post(Entity.json(jsonStr));
+        Response response = clientTarget(app, "/route").request().post(Entity.json(jsonStr));
         assertEquals(200, response.getStatus());
         JsonNode path = response.readEntity(JsonNode.class).get("paths").get(0);
         assertEquals(1590, path.get("distance").asDouble(), 2);
@@ -361,7 +363,7 @@ public class RouteResourceTest {
         jsonStr = "{ \"points\": [[1.53285,42.511139], [1.532271,42.508165]], " +
                 "\"point_hints\":[\"Tunèl del Pont Pla\",\"\"], " +
                 "\"snap_preventions\": [\"tunnel\"] }";
-        response = app.client().target("http://localhost:8080/route").request().post(Entity.json(jsonStr));
+        response = clientTarget(app, "/route").request().post(Entity.json(jsonStr));
         assertEquals(200, response.getStatus());
         path = response.readEntity(JsonNode.class).get("paths").get(0);
         assertEquals(490, path.get("distance").asDouble(), 2);
@@ -370,7 +372,7 @@ public class RouteResourceTest {
     @Test
     public void testGraphHopperWebRealExceptions() {
         GraphHopperAPI hopper = new com.graphhopper.api.GraphHopperWeb();
-        assertTrue(hopper.load("http://localhost:8080/route"));
+        assertTrue(hopper.load(clientUrl(app, "/route")));
 
         // IllegalArgumentException (Wrong Request)
         GHResponse rsp = hopper.route(new GHRequest());
@@ -398,7 +400,7 @@ public class RouteResourceTest {
                 + ", IllegalArgumentException expected.", ex instanceof IllegalArgumentException);
 
         // an IllegalArgumentException from inside the core is written as JSON
-        final Response response = app.client().target("http://localhost:8080/route?vehicle=SPACE-SHUTTLE&point=42.554851,1.536198&point=42.510071,1.548128").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/route?vehicle=SPACE-SHUTTLE&point=42.554851,1.536198&point=42.510071,1.548128").request().buildGet().invoke();
         assertEquals(400, response.getStatus());
         String msg = (String) response.readEntity(Map.class).get("message");
         assertTrue(msg, msg.contains("Vehicle not supported:"));
@@ -406,7 +408,7 @@ public class RouteResourceTest {
 
     @Test
     public void testGPX() {
-        final Response response = app.client().target("http://localhost:8080/route?point=42.554851,1.536198&point=42.510071,1.548128&type=gpx").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/route?point=42.554851,1.536198&point=42.510071,1.548128&type=gpx").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         String str = response.readEntity(String.class);
         // For backward compatibility we currently export route and track.
@@ -417,7 +419,7 @@ public class RouteResourceTest {
 
     @Test
     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();
+        final Response response = clientTarget(app, "/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);
         assertFalse(str.contains("<gh:distance>115.1</gh:distance>"));
@@ -427,7 +429,7 @@ public class RouteResourceTest {
 
     @Test
     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();
+        final Response response = clientTarget(app, "/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);
         assertFalse(str.contains("<gh:distance>115.1</gh:distance>"));
@@ -437,7 +439,7 @@ public class RouteResourceTest {
 
     @Test
     public void testGPXWithError() {
-        final Response response = app.client().target("http://localhost:8080/route?point=42.554851,1.536198&type=gpx").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/route?point=42.554851,1.536198&type=gpx").request().buildGet().invoke();
         assertEquals(400, response.getStatus());
         String str = response.readEntity(String.class);
         assertFalse(str, str.contains("<html>"));
@@ -448,19 +450,19 @@ public class RouteResourceTest {
 
     @Test
     public void testWithError() {
-        final Response response = app.client().target("http://localhost:8080/route?point=42.554851,1.536198").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/route?point=42.554851,1.536198").request().buildGet().invoke();
         assertEquals(400, response.getStatus());
     }
 
     @Test
     public void testNoPoint() {
-        JsonNode json = app.client().target("http://localhost:8080/route?heading=0").request().buildGet().invoke().readEntity(JsonNode.class);
+        JsonNode json = clientTarget(app, "/route?heading=0").request().buildGet().invoke().readEntity(JsonNode.class);
         assertEquals("You have to pass at least one point", json.get("message").asText());
     }
 
     @Test
     public void testTooManyHeadings() {
-        final Response response = app.client().target("http://localhost:8080/route?point=42.554851,1.536198&heading=0&heading=0").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/route?point=42.554851,1.536198&heading=0&heading=0").request().buildGet().invoke();
         assertEquals(400, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         assertEquals("The number of 'heading' parameters must be <= 1 or equal to the number of points (1)", json.get("message").asText());
diff --git a/web/src/test/java/com/graphhopper/http/resources/RouteResourceWithEleTest.java b/web/src/test/java/com/graphhopper/http/resources/RouteResourceWithEleTest.java
index 0ff13cfb1..ee7333224 100644
--- a/web/src/test/java/com/graphhopper/http/resources/RouteResourceWithEleTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/RouteResourceWithEleTest.java
@@ -19,7 +19,8 @@ package com.graphhopper.http.resources;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.graphhopper.http.GraphHopperApplication;
-import com.graphhopper.http.GraphHopperServerConfiguration;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
 import com.graphhopper.util.Helper;
 import io.dropwizard.testing.junit.DropwizardAppRule;
 import org.junit.AfterClass;
@@ -37,7 +38,7 @@ import static org.junit.Assert.*;
 public class RouteResourceWithEleTest {
     private static final String dir = "./target/monaco-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         config.getGraphHopperConfiguration().
@@ -50,7 +51,7 @@ public class RouteResourceWithEleTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule(
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(
             GraphHopperApplication.class, config);
 
 
@@ -61,7 +62,7 @@ public class RouteResourceWithEleTest {
 
     @Test
     public void testElevation() throws Exception {
-        final Response response = app.client().target("http://localhost:8080/route?" + "point=43.730864,7.420771&point=43.727687,7.418737&points_encoded=false&elevation=true").request().buildGet().invoke();
+        final Response response = clientTarget(app, "/route?" + "point=43.730864,7.420771&point=43.727687,7.418737&points_encoded=false&elevation=true").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         JsonNode infoJson = json.get("info");
@@ -82,7 +83,7 @@ public class RouteResourceWithEleTest {
     @Test
     public void testNoElevation() throws Exception {
         // default is elevation=false
-        Response response = app.client().target("http://localhost:8080/route?point=43.730864,7.420771&point=43.727687,7.418737&points_encoded=false").request().buildGet().invoke();
+        Response response = clientTarget(app, "/route?point=43.730864,7.420771&point=43.727687,7.418737&points_encoded=false").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         JsonNode json = response.readEntity(JsonNode.class);
         JsonNode infoJson = json.get("info");
@@ -95,7 +96,7 @@ public class RouteResourceWithEleTest {
         assertTrue("Elevation should not be included!", cson.toString().contains("[7.421392,43.7307]"));
 
         // disable elevation
-        response = app.client().target("http://localhost:8080/route?point=43.730864,7.420771&point=43.727687,7.418737&points_encoded=false&elevation=false").request().buildGet().invoke();
+        response = clientTarget(app, "/route?point=43.730864,7.420771&point=43.727687,7.418737&points_encoded=false&elevation=false").request().buildGet().invoke();
         assertEquals(200, response.getStatus());
         json = response.readEntity(JsonNode.class);
         infoJson = json.get("info");
diff --git a/web/src/test/java/com/graphhopper/http/resources/SPTResourceTest.java b/web/src/test/java/com/graphhopper/http/resources/SPTResourceTest.java
index 5609262c3..f55acfeac 100644
--- a/web/src/test/java/com/graphhopper/http/resources/SPTResourceTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/SPTResourceTest.java
@@ -19,7 +19,7 @@
 package com.graphhopper.http.resources;
 
 import com.graphhopper.http.GraphHopperApplication;
-import com.graphhopper.http.GraphHopperServerConfiguration;
+import com.graphhopper.http.util.GraphHopperServerTestConfiguration;
 import com.graphhopper.util.Helper;
 import io.dropwizard.testing.junit.DropwizardAppRule;
 import org.junit.AfterClass;
@@ -34,11 +34,13 @@ import java.util.List;
 import static junit.framework.TestCase.assertTrue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static com.graphhopper.http.util.TestUtils.clientTarget;
+
 
 public class SPTResourceTest {
     private static final String DIR = "./target/spt-gh/";
 
-    private static final GraphHopperServerConfiguration config = new GraphHopperServerConfiguration();
+    private static final GraphHopperServerTestConfiguration config = new GraphHopperServerTestConfiguration();
 
     static {
         config.getGraphHopperConfiguration().
@@ -49,7 +51,7 @@ public class SPTResourceTest {
     }
 
     @ClassRule
-    public static final DropwizardAppRule<GraphHopperServerConfiguration> app = new DropwizardAppRule<>(
+    public static final DropwizardAppRule<GraphHopperServerTestConfiguration> app = new DropwizardAppRule(
             GraphHopperApplication.class, config);
 
     @AfterClass
@@ -59,7 +61,7 @@ public class SPTResourceTest {
 
     @Test
     public void requestSPT() {
-        Response rsp = app.client().target("http://localhost:8080/spt?point=42.531073,1.573792&time_limit=300").request().buildGet().invoke();
+        Response rsp = clientTarget(app, "/spt?point=42.531073,1.573792&time_limit=300").request().buildGet().invoke();
         String rspCsvString = rsp.readEntity(String.class);
         String[] lines = rspCsvString.split("\n");
         assertTrue(lines.length > 500);
@@ -71,7 +73,7 @@ public class SPTResourceTest {
         assertEquals(118, Integer.parseInt(row[2]) / 1000, 1);
         assertEquals(2263, Integer.parseInt(row[3]), 1);
 
-        rsp = app.client().target("http://localhost:8080/spt?point=42.531073,1.573792&columns=prev_time").request().buildGet().invoke();
+        rsp = clientTarget(app, "/spt?point=42.531073,1.573792&columns=prev_time").request().buildGet().invoke();
         rspCsvString = rsp.readEntity(String.class);
         lines = rspCsvString.split("\n");
         assertTrue(lines.length > 500);
@@ -85,7 +87,7 @@ public class SPTResourceTest {
 
     @Test
     public void requestDetails() {
-        Response rsp = app.client().target("http://localhost:8080/spt?point=42.531073,1.573792&time_limit=300&columns=street_name,road_class,max_speed").request().buildGet().invoke();
+        Response rsp = clientTarget(app, "/spt?point=42.531073,1.573792&time_limit=300&columns=street_name,road_class,max_speed").request().buildGet().invoke();
         String rspCsvString = rsp.readEntity(String.class);
         String[] lines = rspCsvString.split("\n");
         assertTrue(lines.length > 500);
diff --git a/web/src/test/java/com/graphhopper/http/util/GraphHopperServerTestConfiguration.java b/web/src/test/java/com/graphhopper/http/util/GraphHopperServerTestConfiguration.java
new file mode 100644
index 000000000..13d7a05e3
--- /dev/null
+++ b/web/src/test/java/com/graphhopper/http/util/GraphHopperServerTestConfiguration.java
@@ -0,0 +1,42 @@
+/*
+ *  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.http.util;
+
+import com.graphhopper.http.GraphHopperServerConfiguration;
+import io.dropwizard.jetty.HttpConnectorFactory;
+import io.dropwizard.server.DefaultServerFactory;
+
+/**
+ *
+ * @author thomas aulinger
+ */
+public class GraphHopperServerTestConfiguration extends GraphHopperServerConfiguration {
+
+    public GraphHopperServerTestConfiguration() {
+        init();
+    }
+
+    private void init() {
+
+        // The following is to make sure it runs with a random port
+        ((HttpConnectorFactory) ((DefaultServerFactory) getServerFactory()).getApplicationConnectors().get(0)).setPort(0);
+        // this is for admin port
+        ((HttpConnectorFactory) ((DefaultServerFactory) getServerFactory()).getAdminConnectors().get(0)).setPort(0);
+    }
+
+}