Repository: graphhopper/graphhopper
Pull request #2015 (2020-04-23 22:03:11)

Move max_weight into priority and more refactorings
This is for #1841.

@easbar didn't want to disturb your refactorings, so I created this branch ... please merge it when it fine for you.

One open task is to do the proper merge (or error reporting on merge), but I can do that later.


Patch:
diff --git a/api/src/main/java/com/graphhopper/routing/util/CustomModel.java b/api/src/main/java/com/graphhopper/routing/util/CustomModel.java
index 71497592c..a5f41a2df 100644
--- a/api/src/main/java/com/graphhopper/routing/util/CustomModel.java
+++ b/api/src/main/java/com/graphhopper/routing/util/CustomModel.java
@@ -34,7 +34,7 @@ public class CustomModel {
 
     static double DEFAULT_D_I = 70;
     // optional:
-    private Double maxSpeedFallback, vehicleWeight, vehicleWidth, vehicleHeight, vehicleLength;
+    private Double maxSpeedFallback;
     private Double headingPenalty = Parameters.Routing.DEFAULT_HEADING_PENALTY;
     // default value derived from the cost for time e.g. 25€/hour and for distance 0.5€/km, for trucks this is usually larger
     private double distanceInfluence = DEFAULT_D_I;
@@ -48,10 +48,6 @@ public class CustomModel {
 
     public CustomModel(CustomModel toCopy) {
         this.maxSpeedFallback = toCopy.maxSpeedFallback;
-        this.vehicleWeight = toCopy.vehicleWeight;
-        this.vehicleWidth = toCopy.vehicleWidth;
-        this.vehicleLength = toCopy.vehicleLength;
-        this.vehicleHeight = toCopy.vehicleHeight;
         this.headingPenalty = toCopy.headingPenalty;
         this.distanceInfluence = toCopy.distanceInfluence;
 
@@ -81,42 +77,6 @@ public class CustomModel {
         }
     }
 
-    public CustomModel setVehicleWeight(Double vehicleWeight) {
-        this.vehicleWeight = vehicleWeight;
-        return this;
-    }
-
-    public Double getVehicleWeight() {
-        return vehicleWeight;
-    }
-
-    public CustomModel setVehicleHeight(Double vehicleHeight) {
-        this.vehicleHeight = vehicleHeight;
-        return this;
-    }
-
-    public Double getVehicleHeight() {
-        return vehicleHeight;
-    }
-
-    public CustomModel setVehicleLength(Double vehicleLength) {
-        this.vehicleLength = vehicleLength;
-        return this;
-    }
-
-    public Double getVehicleLength() {
-        return vehicleLength;
-    }
-
-    public CustomModel setVehicleWidth(Double vehicleWidth) {
-        this.vehicleWidth = vehicleWidth;
-        return this;
-    }
-
-    public Double getVehicleWidth() {
-        return vehicleWidth;
-    }
-
     public Map<String, Object> getSpeedFactor() {
         return speedFactor;
     }
@@ -167,14 +127,10 @@ public class CustomModel {
     @Override
     public String toString() {
         return "CustomModel{" +
-                "maxSpeedFallback=" + maxSpeedFallback +
-                ", vehicleWeight=" + vehicleWeight +
-                ", vehicleWidth=" + vehicleWidth +
-                ", vehicleHeight=" + vehicleHeight +
-                ", vehicleLength=" + vehicleLength +
-                ", distanceInfluence=" + distanceInfluence +
+                "distanceInfluence=" + distanceInfluence +
                 ", speedFactor=" + speedFactor +
                 ", maxSpeed=" + maxSpeed +
+                ", maxSpeedFallback=" + maxSpeedFallback +
                 ", priorityMap=" + priorityMap +
                 ", #areas=" + areas.size() +
                 '}';
@@ -187,26 +143,6 @@ public class CustomModel {
     public static CustomModel merge(CustomModel baseModel, CustomModel queryModel) {
         // avoid changing the specified CustomModel via deep copy otherwise query-CustomModel would be modified
         CustomModel mergedCM = new CustomModel(baseModel);
-        if (queryModel.vehicleWeight != null) {
-            if (mergedCM.vehicleWeight != null && mergedCM.vehicleWeight > queryModel.vehicleWeight)
-                throw new IllegalArgumentException("CustomModel in query can only use vehicle_weight bigger or equal to " + mergedCM.vehicleWeight);
-            mergedCM.vehicleWeight = queryModel.vehicleWeight;
-        }
-        if (queryModel.vehicleHeight != null) {
-            if (mergedCM.vehicleHeight != null && mergedCM.vehicleHeight > queryModel.vehicleHeight)
-                throw new IllegalArgumentException("CustomModel in query can only use vehicle_height bigger or equal to " + mergedCM.vehicleHeight);
-            mergedCM.vehicleHeight = queryModel.vehicleHeight;
-        }
-        if (queryModel.vehicleLength != null) {
-            if (mergedCM.vehicleLength != null && mergedCM.vehicleLength > queryModel.vehicleLength)
-                throw new IllegalArgumentException("CustomModel in query can only use vehicle_length bigger or equal to " + mergedCM.vehicleLength);
-            mergedCM.vehicleLength = queryModel.vehicleLength;
-        }
-        if (queryModel.vehicleWidth != null) {
-            if (mergedCM.vehicleWidth != null && mergedCM.vehicleWidth > queryModel.vehicleWidth)
-                throw new IllegalArgumentException("CustomModel in query can only use vehicle_width bigger or equal to " + mergedCM.vehicleWidth);
-            mergedCM.vehicleWidth = queryModel.vehicleWidth;
-        }
         if (queryModel.maxSpeedFallback != null) {
             if (mergedCM.maxSpeedFallback != null && mergedCM.maxSpeedFallback > queryModel.maxSpeedFallback)
                 throw new IllegalArgumentException("CustomModel in query can only use max_speed_fallback bigger or equal to " + mergedCM.maxSpeedFallback);
@@ -219,7 +155,9 @@ public class CustomModel {
         }
 
         // example
-        // max_speed: { road_class: { secondary : 10 } }
+        // max_speed: { road_class: { secondary : 0.4 } }
+        // or
+        // priority:  { max_weight: { "<3.501": 0.7 } }
         for (Map.Entry<String, Object> queryEntry : queryModel.getMaxSpeed().entrySet()) {
             Object value = mergedCM.maxSpeed.get(queryEntry.getKey());
             applyChange(mergedCM.maxSpeed, value, queryEntry);
@@ -253,6 +191,8 @@ public class CustomModel {
         Object queryObj = querySuperEntry.getValue();
         if (!(queryObj instanceof Map))
             throw new IllegalArgumentException("query entry is not a map: " + queryObj);
+
+        // TODO NOW how to merge different ranges of DecimalEncodedValue => if range size is decreased => it is fine
         Map<Object, Object> mergedMap = (Map) mergedObj;
         Map<Object, Object> queryMap = (Map) queryObj;
         for (Map.Entry queryEntry : queryMap.entrySet()) {
diff --git a/benchmark/benchmark.sh b/benchmark/benchmark.sh
index 7f4292b6b..54c509486 100755
--- a/benchmark/benchmark.sh
+++ b/benchmark/benchmark.sh
@@ -78,7 +78,7 @@ measurement.weighting=fastest \
 measurement.ch.node=true \
 measurement.ch.edge=false \
 measurement.lm=true \
-measurement.lm.active_counts=[4,8,12,16] \
+"measurement.lm.active_counts=[4,8,12,16]" \
 measurement.lm.edge_based=true \
 "graph.flag_encoders=car|turn_costs=true" \
 graph.location=${TMP_DIR}measurement-big-gh \
@@ -108,7 +108,7 @@ graph.encoded_values=max_width,max_height,toll,hazmat \
 measurement.ch.node=true \
 measurement.ch.edge=false \
 measurement.lm=true \
-measurement.lm.active_counts=[8] \
+"measurement.lm.active_counts=[8]" \
 measurement.lm.edge_based=false \
 graph.flag_encoders=car|turn_costs=true \
 graph.location=${TMP_DIR}measurement-big-little-custom-gh \
@@ -137,7 +137,7 @@ graph.encoded_values=max_width,max_height,toll,hazmat \
 measurement.ch.node=true \
 measurement.ch.edge=false \
 measurement.lm=true \
-measurement.lm.active_counts=[8] \
+"measurement.lm.active_counts=[8]" \
 measurement.lm.edge_based=false \
 graph.flag_encoders=car \
 graph.location=${TMP_DIR}measurement-big-very-custom-gh \
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/BooleanToValueEntry.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/BooleanToValueEntry.java
index 789b3a522..6c8dd7e7e 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/custom/BooleanToValueEntry.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/BooleanToValueEntry.java
@@ -20,18 +20,61 @@ package com.graphhopper.routing.weighting.custom;
 import com.graphhopper.routing.profiles.BooleanEncodedValue;
 import com.graphhopper.util.EdgeIteratorState;
 
+import java.util.Map;
+
+import static com.graphhopper.routing.weighting.custom.CustomWeighting.CATCH_ALL;
+import static com.graphhopper.routing.weighting.custom.EnumToValueEntry.getReturnValue;
+
 final class BooleanToValueEntry implements EdgeToValueEntry {
     private final BooleanEncodedValue bev;
     private final double value, elseValue;
 
-    public BooleanToValueEntry(BooleanEncodedValue bev, double value, double elseValue) {
+    private BooleanToValueEntry(BooleanEncodedValue bev, double value, double elseValue) {
         this.bev = bev;
         this.value = value;
         this.elseValue = elseValue;
     }
 
+    /**
+     * Example map:
+     * <pre>
+     * get_off_bike:
+     *   true: 0.4
+     *   false: 0.9 # optional and default is 1, equivalent to "*": 0.9
+     * </pre>
+     */
+    static EdgeToValueEntry create(String name, BooleanEncodedValue encodedValue, Map<String, Object> map,
+                                   double defaultValue, double minValue, double maxValue) {
+        if (map.isEmpty())
+            throw new IllegalArgumentException("Empty map for " + name);
+
+        // the key can only be a String, i.e. "false" and not a boolean false -> this is properly done from jackson
+        if (map.containsKey(CATCH_ALL) && map.containsKey("false"))
+            throw new IllegalArgumentException(name + ": cannot contain false and catch-all key at the same time");
+
+        double trueValue = Double.NaN;
+        double falseValue = defaultValue;
+        for (Map.Entry<String, Object> encValEntry : map.entrySet()) {
+            if (encValEntry.getKey() == null)
+                throw new IllegalArgumentException("key for " + name + " cannot be null, value: " + encValEntry.getValue());
+            String key = encValEntry.getKey();
+
+            double returnValue = getReturnValue(name, key, encValEntry.getValue(), minValue, maxValue);
+            if ("true".equals(key)) {
+                trueValue = returnValue;
+            } else if ("false".equals(key) || CATCH_ALL.equals(key)) {
+                falseValue = returnValue;
+            } else {
+                throw new IllegalArgumentException("key for " + name + " cannot be " + key + ", value: " + encValEntry.getValue());
+            }
+        }
+
+        return new BooleanToValueEntry(encodedValue, trueValue, falseValue);
+    }
+
     @Override
     public double getValue(EdgeIteratorState iter, boolean reverse) {
+        if (Double.isNaN(value)) return elseValue; // special case if only catch-all key is present
         return iter.get(bev) ? value : elseValue;
     }
 
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomProfileConfig.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomProfileConfig.java
index 85f6c849d..5fd7d27fe 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomProfileConfig.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomProfileConfig.java
@@ -18,6 +18,7 @@ public class CustomProfileConfig extends ProfileConfig {
 
     public CustomProfileConfig setCustomModel(CustomModel customModel) {
         getHints().putObject(CustomModel.KEY, customModel);
+        getHints().putObject("custom_model_file", "empty");
         return this;
     }
 
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeighting.java
index f8d674157..4f95a2d36 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeighting.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeighting.java
@@ -41,6 +41,7 @@ import com.graphhopper.util.EdgeIteratorState;
  */
 public final class CustomWeighting extends AbstractWeighting {
     public static final String NAME = "custom";
+    public static final String CATCH_ALL = "*";
 
     /**
      * Converting to seconds is not necessary but makes adding other penalties easier (e.g. turn
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/DecimalToValueEntry.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/DecimalToValueEntry.java
new file mode 100644
index 000000000..769fc4e40
--- /dev/null
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/DecimalToValueEntry.java
@@ -0,0 +1,116 @@
+/*
+ *  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.weighting.custom;
+
+import com.graphhopper.routing.profiles.DecimalEncodedValue;
+import com.graphhopper.util.EdgeIteratorState;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static com.graphhopper.routing.weighting.custom.CustomWeighting.CATCH_ALL;
+import static com.graphhopper.routing.weighting.custom.EnumToValueEntry.getReturnValue;
+
+final class DecimalToValueEntry implements EdgeToValueEntry {
+    private final DecimalEncodedValue dev;
+    private final double minExclusive, maxExclusive;
+    private final double rangeValue;
+    private final double fallback;
+
+    private DecimalToValueEntry(DecimalEncodedValue dev, Range range, double fallback) {
+        this.dev = dev;
+        this.minExclusive = range.min;
+        this.maxExclusive = range.max;
+        this.rangeValue = range.value;
+        this.fallback = fallback;
+    }
+
+    @Override
+    public double getValue(EdgeIteratorState iter, boolean reverse) {
+        double edgeValue = iter.get(dev);
+        return edgeValue < maxExclusive && edgeValue > minExclusive ? rangeValue : fallback;
+    }
+
+    static class Range {
+        final double min, max, value;
+
+        public Range(double min, double max, double value) {
+            this.min = min;
+            this.max = max;
+            this.value = value;
+            if (max < min)
+                throw new IllegalArgumentException("Wrong range. Minimum " + min + " cannot be bigger than maximum " + max);
+        }
+
+        @Override
+        public String toString() {
+            return "min=" + min + ", max=" + max + ", value=" + value;
+        }
+    }
+
+    static EdgeToValueEntry create(String name, DecimalEncodedValue dev, Map<String, Object> map,
+                                   double defaultValue, double minValue, double maxValue) {
+        if (map.isEmpty())
+            throw new IllegalArgumentException("Empty map for " + name);
+
+        Object evEntryValue = map.get(CATCH_ALL);
+        if (evEntryValue != null)
+            defaultValue = getReturnValue(name, CATCH_ALL, evEntryValue, minValue, maxValue);
+
+        List<Range> ranges = new ArrayList<>();
+        for (Map.Entry<String, Object> encValEntry : map.entrySet()) {
+            if (encValEntry.getKey() == null)
+                throw new IllegalArgumentException("key for " + name + " cannot be null, value: " + encValEntry.getValue());
+            String key = encValEntry.getKey();
+            if (CATCH_ALL.equals(key))
+                continue;
+
+            double returnValue = getReturnValue(name, key, encValEntry.getValue(), minValue, maxValue);
+            Range range = parseRange(name, key, returnValue);
+            ranges.add(range);
+        }
+        if (ranges.size() != 1)
+            throw new IllegalArgumentException("Currently only one range can be specified but was " + ranges.size());
+        return new DecimalToValueEntry(dev, ranges.get(0), defaultValue);
+    }
+
+    static Range parseRange(String name, String rangeAsString, Object value) {
+        if (!(value instanceof Number))
+            throw new IllegalArgumentException(name + " value has to be a number but was " + value);
+
+        double num = ((Number) value).doubleValue();
+        try {
+            int gtIndex = rangeAsString.indexOf(">");
+            if (gtIndex >= 0)
+                return new Range(Double.parseDouble(rangeAsString.substring(gtIndex + 1)), Double.POSITIVE_INFINITY, num);
+
+            int ltIndex = rangeAsString.indexOf("<");
+            if (ltIndex >= 0)
+                // unsigned => 0 is currently the minimum
+                return new Range(0, Double.parseDouble(rangeAsString.substring(ltIndex + 1)), num);
+        } catch (Exception ex) {
+        }
+        throw new IllegalArgumentException("Range is invalid. It must be e.g. \">3\" or \"<5\" but was: " + rangeAsString);
+    }
+
+    @Override
+    public String toString() {
+        return dev.getName() + ", range: min:" + minExclusive + ", max:" + maxExclusive + ", value:" + rangeValue;
+    }
+}
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/EdgeToValueEntry.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/EdgeToValueEntry.java
index 20f0217e2..c943fdbf9 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/custom/EdgeToValueEntry.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/EdgeToValueEntry.java
@@ -25,6 +25,7 @@ import com.graphhopper.util.EdgeIteratorState;
  * numerical values when determining the different parameters for {@link CustomWeighting}.
  */
 interface EdgeToValueEntry {
+
     /**
      * @return the numeric value that shall be associated with the given (directed) edge
      */
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/EnumToValueEntry.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/EnumToValueEntry.java
index a3be24945..29ec6b8d0 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/custom/EnumToValueEntry.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/EnumToValueEntry.java
@@ -22,12 +22,15 @@ import com.graphhopper.routing.profiles.IntEncodedValue;
 import com.graphhopper.util.EdgeIteratorState;
 
 import java.util.Arrays;
+import java.util.Map;
+
+import static com.graphhopper.routing.weighting.custom.CustomWeighting.CATCH_ALL;
 
 final class EnumToValueEntry implements EdgeToValueEntry {
     private final IntEncodedValue eev;
     private final double[] values;
 
-    EnumToValueEntry(EnumEncodedValue eev, double[] values) {
+    private EnumToValueEntry(EnumEncodedValue eev, double[] values) {
         this.eev = eev;
         this.values = values;
     }
@@ -38,6 +41,66 @@ final class EnumToValueEntry implements EdgeToValueEntry {
         return values[enumOrdinal];
     }
 
+    /**
+     * Example map:
+     * <pre>
+     * road_class:
+     *   motorway: 0.4
+     *   "*": 0.9      # optional and default is 1
+     * </pre>
+     */
+    static EnumToValueEntry create(String name, EnumEncodedValue enumEncodedValue, Map<String, Object> map,
+                                   double defaultValue, double minValue, double maxValue) {
+        Enum[] enumValues = enumEncodedValue.getValues();
+        if (map.isEmpty())
+            throw new IllegalArgumentException("Empty map for " + name);
+
+        Object evEntryValue = map.get(CATCH_ALL);
+        if (evEntryValue != null)
+            defaultValue = getReturnValue(name, CATCH_ALL, evEntryValue, minValue, maxValue);
+
+        double[] tmp = new double[enumValues.length];
+        Arrays.fill(tmp, defaultValue);
+        for (Map.Entry<String, Object> encValEntry : map.entrySet()) {
+            if (encValEntry.getKey() == null)
+                throw new IllegalArgumentException("key for " + name + " cannot be null, value: " + encValEntry.getValue());
+            String key = encValEntry.getKey();
+            if (CATCH_ALL.equals(key))
+                continue;
+
+            Enum enumValue = getValueOf(enumValues, key);
+            double returnValue = getReturnValue(name, key, encValEntry.getValue(), minValue, maxValue);
+            tmp[enumValue.ordinal()] = returnValue;
+        }
+
+        return new EnumToValueEntry(enumEncodedValue, tmp);
+    }
+
+    static double getReturnValue(String name, String key, Object valueObject, double minValue, double maxValue) {
+        if (valueObject == null)
+            throw new IllegalArgumentException("value for " + name + " cannot be null, key: " + key);
+        if (!(valueObject instanceof Number))
+            throw new IllegalArgumentException("value for " + name + " has to be a number but was: " + valueObject.getClass().getSimpleName());
+        double value = ((Number) valueObject).doubleValue();
+        if (value < minValue)
+            throw new IllegalArgumentException(name + " cannot be smaller than " + minValue + ", was " + value);
+        if (value > maxValue)
+            throw new IllegalArgumentException(name + " cannot be bigger than " + maxValue + ", was " + value);
+        return value;
+    }
+
+    /**
+     * This method finds the enum in the enumClass via enum.toString
+     */
+    private static Enum getValueOf(Enum[] enumValues, String enumToString) {
+        for (Enum e : enumValues) {
+            if (e.toString().equals(enumToString)) {
+                return e;
+            }
+        }
+        throw new IllegalArgumentException("Cannot find enum " + enumToString + " in " + Arrays.toString(enumValues));
+    }
+
     @Override
     public String toString() {
         return eev.getName() + ": " + Arrays.toString(values);
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/GeoToValueEntry.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/GeoToValueEntry.java
index 5a8ca2f37..3214ae441 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/custom/GeoToValueEntry.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/GeoToValueEntry.java
@@ -29,11 +29,10 @@ import org.locationtech.jts.geom.prep.PreparedGeometry;
 
 final class GeoToValueEntry implements EdgeToValueEntry {
     static final String AREA_PREFIX = "area_";
-
     private final Polygon ghPolygon;
     private final double value, elseValue;
 
-    public GeoToValueEntry(PreparedGeometry geometry, double value, double elseValue) {
+    private GeoToValueEntry(PreparedGeometry geometry, double value, double elseValue) {
         this.ghPolygon = new Polygon(geometry);
         this.value = value;
         this.elseValue = elseValue;
@@ -47,6 +46,17 @@ final class GeoToValueEntry implements EdgeToValueEntry {
         return feature.getGeometry();
     }
 
+    public static EdgeToValueEntry create(String name, PreparedGeometry preparedGeometry, Number value, double defaultValue,
+                                          double minValue, double maxValue) {
+        double number = value.doubleValue();
+        if (number < minValue)
+            throw new IllegalArgumentException(name + " cannot be smaller than " + minValue + ", was " + number);
+        if (number > maxValue)
+            throw new IllegalArgumentException(name + " cannot be bigger than " + maxValue + ", was " + number);
+
+        return new GeoToValueEntry(preparedGeometry, number, defaultValue);
+    }
+
     @Override
     public double getValue(EdgeIteratorState edgeState, boolean reverse) {
         BBox bbox = GHUtility.createBBox(edgeState);
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/PriorityCalculator.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/PriorityCalculator.java
index 6c11da0f9..db1e44a88 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/custom/PriorityCalculator.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/PriorityCalculator.java
@@ -24,7 +24,6 @@ import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -32,104 +31,47 @@ final class PriorityCalculator {
     private final List<EdgeToValueEntry> priorityList = new ArrayList<>();
 
     public PriorityCalculator(CustomModel customModel, EncodedValueLookup lookup) {
-        add(lookup, customModel.getVehicleWeight(), "vehicle_weight", MaxWeight.KEY);
-        add(lookup, customModel.getVehicleWidth(), "vehicle_width", MaxWidth.KEY);
-        add(lookup, customModel.getVehicleHeight(), "vehicle_height", MaxHeight.KEY);
-        add(lookup, customModel.getVehicleLength(), "vehicle_length", MaxLength.KEY);
-
         for (Map.Entry<String, Object> entry : customModel.getPriority().entrySet()) {
             String key = entry.getKey();
+            String priorityKey = "priority." + key;
             Object value = entry.getValue();
-
-            if (value == null) {
+            if (value == null)
                 throw new IllegalArgumentException("Missing value for " + key + " in 'priority'");
-            } else if (value instanceof Number) {
-                if (key.startsWith(GeoToValueEntry.AREA_PREFIX)) {
-                    Geometry geometry = GeoToValueEntry.pickGeometry(customModel, key);
-                    priorityList.add(new GeoToValueEntry(new PreparedGeometryFactory().create(geometry), ((Number) value).doubleValue(), 1));
+
+            if (key.startsWith(GeoToValueEntry.AREA_PREFIX)) {
+                if (!(value instanceof Number))
+                    throw new IllegalArgumentException(priorityKey + ": area entry requires number value but was: " + value.getClass().getSimpleName());
+                Geometry geometry = GeoToValueEntry.pickGeometry(customModel, key);
+                priorityList.add(GeoToValueEntry.create(priorityKey, new PreparedGeometryFactory().create(geometry),
+                        (Number) value, 1, 0, 1));
+            } else {
+                if (!(value instanceof Map))
+                    throw new IllegalArgumentException(priorityKey + ": non-root entries requires a map but was: " + value.getClass().getSimpleName());
+                final double defaultPriority = 1, minPriority = 0, maxPriority = 1;
+                EncodedValue encodedValue = getEV(lookup, "priority", key);
+                if (encodedValue instanceof EnumEncodedValue) {
+                    priorityList.add(EnumToValueEntry.create(priorityKey, (EnumEncodedValue) encodedValue,
+                            (Map) value, defaultPriority, minPriority, maxPriority));
+                } else if (encodedValue instanceof DecimalEncodedValue) {
+                    priorityList.add(DecimalToValueEntry.create(priorityKey, (DecimalEncodedValue) encodedValue,
+                            (Map) value, defaultPriority, minPriority, maxPriority));
+                } else if (encodedValue instanceof BooleanEncodedValue) {
+                    priorityList.add(BooleanToValueEntry.create(priorityKey, (BooleanEncodedValue) encodedValue,
+                            (Map) value, defaultPriority, minPriority, maxPriority));
+                } else if (encodedValue instanceof IntEncodedValue) {
+                    // TODO NOW
                 } else {
-                    BooleanEncodedValue encodedValue = getEV(lookup, "priority", key, BooleanEncodedValue.class);
-                    priorityList.add(new BooleanToValueEntry(encodedValue, ((Number) value).doubleValue(), 1));
+                    throw new IllegalArgumentException("The encoded value '" + key + "' used in 'priority' is of type "
+                            + encodedValue.getClass().getSimpleName() + ", but only types enum, decimal and boolean are supported.");
                 }
-            } else if (value instanceof Map) {
-                EnumEncodedValue enumEncodedValue = getEV(lookup, "priority", key, EnumEncodedValue.class);
-                Enum[] enumValues = enumEncodedValue.getValues();
-                double[] values = createEnumToDoubleArray("priority." + key, 1, 0, 100,
-                        enumValues, (Map<String, Object>) value);
-                normalizeFactor(values, 1);
-                priorityList.add(new EnumToValueEntry(enumEncodedValue, values));
-            } else {
-                throw new IllegalArgumentException("Type " + value.getClass() + " is not supported for " + key + " in 'priority'");
             }
         }
     }
 
-    static <T extends EncodedValue> T getEV(EncodedValueLookup lookup, String name, String key, Class<T> encodedValueType) {
+    static EncodedValue getEV(EncodedValueLookup lookup, String name, String key) {
         if (!lookup.hasEncodedValue(key))
             throw new IllegalArgumentException("Cannot find encoded value '" + key + "' specified in '" + name + "'. Available: " + lookup.getAllShared());
-        T ev = lookup.getEncodedValue(key, encodedValueType);
-        if (!encodedValueType.isAssignableFrom(ev.getClass()))
-            throw new IllegalArgumentException("Expected class '" + encodedValueType.getSimpleName() + "' for '" + key
-                    + "' specified in '" + name + "'. But was " + ev.getClass().getSimpleName());
-        return ev;
-    }
-
-
-    /**
-     * This method finds the enum in the enumClass via enum.toString
-     */
-    private static Enum getValueOf(Enum[] enumValues, String enumToString) {
-        for (Enum e : enumValues) {
-            if (e.toString().equals(enumToString)) {
-                return e;
-            }
-        }
-        throw new IllegalArgumentException("Cannot find enum " + enumToString + " in " + Arrays.toString(enumValues));
-    }
-
-    static double[] createEnumToDoubleArray(String name, double defaultValue, double minValue, double maxValue,
-                                            Enum[] enumValues, Map<String, Object> map) {
-        double[] tmp = new double[enumValues.length];
-        Arrays.fill(tmp, defaultValue);
-        for (Map.Entry<String, Object> encValEntry : map.entrySet()) {
-            if (encValEntry.getKey() == null)
-                throw new IllegalArgumentException("key for " + name + " cannot be null, value: " + encValEntry.getValue());
-            if (encValEntry.getValue() == null)
-                throw new IllegalArgumentException("value for " + name + " cannot be null, key: " + encValEntry.getKey());
-
-            Enum enumValue = getValueOf(enumValues, encValEntry.getKey());
-            tmp[enumValue.ordinal()] = ((Number) encValEntry.getValue()).doubleValue();
-            if (tmp[enumValue.ordinal()] < minValue)
-                throw new IllegalArgumentException(name + " cannot be smaller than " + minValue + ", was " + tmp[enumValue.ordinal()]);
-            if (tmp[enumValue.ordinal()] > maxValue)
-                throw new IllegalArgumentException(name + " cannot be bigger than " + maxValue + ", was " + tmp[enumValue.ordinal()]);
-        }
-        return tmp;
-    }
-
-    /**
-     * Pick the maximum value and if it is greater than the specified max we divide all values with it - i.e. normalize it.
-     * <p>
-     * The purpose of this method is to avoid factors above max which makes e.g. Weighting.getMinWeight simpler (as maximum priority is 1)
-     * and also ensures that the landmark algorithm still works (weights may only be increased without affecting optimality).
-     */
-    private static void normalizeFactor(double[] values, final double max) {
-        double tmpMax = max;
-        for (int i = 0; i < values.length; i++) {
-            tmpMax = Math.max(tmpMax, values[i]);
-        }
-        if (tmpMax > max)
-            for (int i = 0; i < values.length; i++) {
-                values[i] = values[i] / tmpMax;
-            }
-    }
-
-    void add(EncodedValueLookup lookup, Double value, String name, String encValue) {
-        if (value == null)
-            return;
-        if (!lookup.hasEncodedValue(encValue))
-            throw new IllegalArgumentException("You cannot use " + name + " as encoded value '" + encValue + "' was not enabled on the server.");
-        priorityList.add(new MaxValueEntry(name, lookup.getDecimalEncodedValue(encValue), value));
+        return lookup.getEncodedValue(key, EncodedValue.class);
     }
 
     /**
@@ -145,26 +87,4 @@ final class PriorityCalculator {
         }
         return priority;
     }
-
-    private static class MaxValueEntry implements EdgeToValueEntry {
-        DecimalEncodedValue ev;
-        double vehicleValue;
-
-        public MaxValueEntry(String name, DecimalEncodedValue ev, double vehicleValue) {
-            if (vehicleValue <= 0)
-                throw new IllegalArgumentException(name + " cannot be 0 or negative");
-            this.ev = ev;
-            this.vehicleValue = vehicleValue;
-        }
-
-        @Override
-        public double getValue(EdgeIteratorState iter, boolean reverse) {
-            return (vehicleValue <= (reverse ? iter.getReverse(ev) : iter.get(ev))) ? 1 : 0.0;
-        }
-
-        @Override
-        public String toString() {
-            return ev.getName() + ": " + vehicleValue;
-        }
-    }
 }
diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/SpeedCalculator.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/SpeedCalculator.java
index 61ada9f2a..8ffe41885 100644
--- a/core/src/main/java/com/graphhopper/routing/weighting/custom/SpeedCalculator.java
+++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/SpeedCalculator.java
@@ -17,10 +17,7 @@
  */
 package com.graphhopper.routing.weighting.custom;
 
-import com.graphhopper.routing.profiles.BooleanEncodedValue;
-import com.graphhopper.routing.profiles.DecimalEncodedValue;
-import com.graphhopper.routing.profiles.EncodedValueLookup;
-import com.graphhopper.routing.profiles.EnumEncodedValue;
+import com.graphhopper.routing.profiles.*;
 import com.graphhopper.routing.util.CustomModel;
 import com.graphhopper.util.EdgeIteratorState;
 import org.locationtech.jts.geom.Geometry;
@@ -31,7 +28,6 @@ import java.util.List;
 import java.util.Map;
 
 import static com.graphhopper.routing.weighting.custom.GeoToValueEntry.AREA_PREFIX;
-import static com.graphhopper.routing.weighting.custom.PriorityCalculator.createEnumToDoubleArray;
 import static com.graphhopper.routing.weighting.custom.PriorityCalculator.getEV;
 
 final class SpeedCalculator {
@@ -52,52 +48,74 @@ final class SpeedCalculator {
         // use max_speed to lower speed for the specified conditions
         for (Map.Entry<String, Object> entry : customModel.getMaxSpeed().entrySet()) {
             String key = entry.getKey();
+            String maxSpeedKey = "max_speed." + key;
             Object value = entry.getValue();
-
-            if (value instanceof Number) {
-                double number = ((Number) value).doubleValue();
-                if (number > maxSpeed)
-                    throw new IllegalArgumentException(key + " cannot be bigger than " + maxSpeed + ", was " + number);
-
-                if (key.startsWith(AREA_PREFIX)) {
-                    Geometry geometry = GeoToValueEntry.pickGeometry(customModel, key);
-                    maxSpeedList.add(new GeoToValueEntry(new PreparedGeometryFactory().create(geometry), number, maxSpeed));
+            if (value == null)
+                throw new IllegalArgumentException("Missing value for " + key + " in 'priority'");
+
+            if (key.startsWith(AREA_PREFIX)) {
+                if (!(value instanceof Number))
+                    throw new IllegalArgumentException(maxSpeedKey + ": area entry requires number value but was: " + value.getClass().getSimpleName());
+                Geometry geometry = GeoToValueEntry.pickGeometry(customModel, key);
+                maxSpeedList.add(GeoToValueEntry.create(maxSpeedKey, new PreparedGeometryFactory().create(geometry),
+                        (Number) value, maxSpeed, 0, maxSpeed));
+            } else {
+                if (!(value instanceof Map))
+                    throw new IllegalArgumentException(maxSpeedKey + ": non-root entries requires a map but was: " + value.getClass().getSimpleName());
+                final double defaultMaxSpeed = maxSpeed, minMaxSpeed = 0, maxMaxSpeed = maxSpeed;
+                EncodedValue encodedValue = getEV(lookup, "max_speed", key);
+                if (encodedValue instanceof EnumEncodedValue) {
+                    maxSpeedList.add(EnumToValueEntry.create(maxSpeedKey, (EnumEncodedValue) encodedValue,
+                            (Map) value, defaultMaxSpeed, minMaxSpeed, maxMaxSpeed));
+                } else if (encodedValue instanceof DecimalEncodedValue) {
+                    maxSpeedList.add(DecimalToValueEntry.create(maxSpeedKey, (DecimalEncodedValue) encodedValue,
+                            (Map) value, defaultMaxSpeed, minMaxSpeed, maxMaxSpeed));
+                } else if (encodedValue instanceof BooleanEncodedValue) {
+                    maxSpeedList.add(BooleanToValueEntry.create(maxSpeedKey, (BooleanEncodedValue) encodedValue,
+                            (Map) value, defaultMaxSpeed, minMaxSpeed, maxMaxSpeed));
+                } else if (encodedValue instanceof IntEncodedValue) {
+                    // TODO
                 } else {
-                    BooleanEncodedValue encodedValue = getEV(lookup, "max_speed", key, BooleanEncodedValue.class);
-                    maxSpeedList.add(new BooleanToValueEntry(encodedValue, number, maxSpeed));
+                    throw new IllegalArgumentException("The encoded value '" + key + "' used in 'max_speed' is of type "
+                            + encodedValue.getClass().getSimpleName() + ", but only types enum, decimal and boolean are supported.");
                 }
-            } else if (value instanceof Map) {
-                EnumEncodedValue enumEncodedValue = getEV(lookup, "max_speed", key, EnumEncodedValue.class);
-                Enum[] enumValues = enumEncodedValue.getValues();
-                double[] values = createEnumToDoubleArray("max_speed." + key, maxSpeed, 0, maxSpeed,
-                        enumValues, (Map<String, Object>) value);
-                maxSpeedList.add(new EnumToValueEntry(enumEncodedValue, values));
-            } else {
-                throw new IllegalArgumentException("Type " + value.getClass() + " is not supported for 'max_speed'");
             }
         }
 
         // use speed_factor to reduce the estimated speed value under the specified conditions
         for (Map.Entry<String, Object> entry : customModel.getSpeedFactor().entrySet()) {
             String key = entry.getKey();
+            String speedFactorKey = "speed_factor." + key;
             Object value = entry.getValue();
-
-            if (value instanceof Number) {
-                if (key.startsWith(AREA_PREFIX)) {
-                    Geometry geometry = GeoToValueEntry.pickGeometry(customModel, key);
-                    speedFactorList.add(new GeoToValueEntry(new PreparedGeometryFactory().create(geometry), ((Number) value).doubleValue(), 1));
+            if (value == null)
+                throw new IllegalArgumentException("Missing value for " + key + " in 'priority'");
+
+            if (key.startsWith(AREA_PREFIX)) {
+                if (!(value instanceof Number))
+                    throw new IllegalArgumentException(speedFactorKey + ": area entry requires number value but was: " + value.getClass().getSimpleName());
+                Geometry geometry = GeoToValueEntry.pickGeometry(customModel, key);
+                speedFactorList.add(GeoToValueEntry.create(speedFactorKey, new PreparedGeometryFactory().create(geometry),
+                        (Number) value, 1, 0, 1));
+            } else {
+                if (!(value instanceof Map))
+                    throw new IllegalArgumentException(speedFactorKey + ": non-root entries requires a map but was: " + value.getClass().getSimpleName());
+                final double defaultSpeedFactor = 1, minSpeedFactor = 0, maxSpeedFactor = 1;
+                EncodedValue encodedValue = getEV(lookup, "speed_factor", key);
+                if (encodedValue instanceof EnumEncodedValue) {
+                    speedFactorList.add(EnumToValueEntry.create(speedFactorKey, (EnumEncodedValue) encodedValue,
+                            (Map) value, defaultSpeedFactor, minSpeedFactor, maxSpeedFactor));
+                } else if (encodedValue instanceof DecimalEncodedValue) {
+                    speedFactorList.add(DecimalToValueEntry.create(speedFactorKey, (DecimalEncodedValue) encodedValue,
+                            (Map) value, defaultSpeedFactor, minSpeedFactor, maxSpeedFactor));
+                } else if (encodedValue instanceof BooleanEncodedValue) {
+                    speedFactorList.add(BooleanToValueEntry.create(speedFactorKey, (BooleanEncodedValue) encodedValue,
+                            (Map) value, defaultSpeedFactor, minSpeedFactor, maxSpeedFactor));
+                } else if (encodedValue instanceof IntEncodedValue) {
+                    // TODO
                 } else {
-                    BooleanEncodedValue encodedValue = getEV(lookup, "speed_factor", key, BooleanEncodedValue.class);
-                    speedFactorList.add(new BooleanToValueEntry(encodedValue, ((Number) value).doubleValue(), 1));
+                    throw new IllegalArgumentException("The encoded value '" + key + "' used in 'speed_factor' is of type "
+                            + encodedValue.getClass().getSimpleName() + ", but only types enum, decimal and boolean are supported.");
                 }
-            } else if (value instanceof Map) {
-                EnumEncodedValue enumEncodedValue = getEV(lookup, "speed_factor", key, EnumEncodedValue.class);
-                Enum[] enumValues = enumEncodedValue.getValues();
-                double[] values = createEnumToDoubleArray("speed_factor." + key, 1, 0, 1,
-                        enumValues, (Map<String, Object>) value);
-                speedFactorList.add(new EnumToValueEntry(enumEncodedValue, values));
-            } else {
-                throw new IllegalArgumentException("Type " + value.getClass() + " is not supported for 'speed_factor'");
             }
         }
     }


Test patch:
diff --git a/api/src/test/java/com/graphhopper/routing/util/CustomModelTest.java b/api/src/test/java/com/graphhopper/routing/util/CustomModelTest.java
index 64337cb0e..5eb4da193 100644
--- a/api/src/test/java/com/graphhopper/routing/util/CustomModelTest.java
+++ b/api/src/test/java/com/graphhopper/routing/util/CustomModelTest.java
@@ -9,23 +9,24 @@ import static org.junit.jupiter.api.Assertions.*;
 
 public class CustomModelTest {
 
-    @Test
-    public void testMergeLimits() {
-        CustomModel truck = new CustomModel().setVehicleWidth(3.);
-        CustomModel car = new CustomModel().setVehicleWidth(2.);
-        CustomModel bike = new CustomModel().setVehicleWeight(0.02);
-
-        assertEquals(2, CustomModel.merge(bike, car).getVehicleWidth(), .1);
-        assertNull(bike.getVehicleWidth());
-        assertNull(car.getVehicleWeight());
-
-        assertEquals(3, CustomModel.merge(car, truck).getVehicleWidth(), .1);
-        try {
-            CustomModel.merge(truck, car);
-            fail("car is incompatible with truck as base");
-        } catch (Exception ex) {
-        }
-    }
+    // TODO NOW
+//    @Test
+//    public void testMergeLimits() {
+//        CustomModel truck = new CustomModel().setVehicleWidth(3.);
+//        CustomModel car = new CustomModel().setVehicleWidth(2.);
+//        CustomModel bike = new CustomModel().setVehicleWeight(0.02);
+//
+//        assertEquals(2, CustomModel.merge(bike, car).getVehicleWidth(), .1);
+//        assertNull(bike.getVehicleWidth());
+//        assertNull(car.getVehicleWeight());
+//
+//        assertEquals(3, CustomModel.merge(car, truck).getVehicleWidth(), .1);
+//        try {
+//            CustomModel.merge(truck, car);
+//            fail("car is incompatible with truck as base");
+//        } catch (Exception ex) {
+//        }
+//    }
 
     @Test
     public void testMergeEmptyModel() {
diff --git a/core/src/test/java/com/graphhopper/routing/weighting/CustomWeightingTest.java b/core/src/test/java/com/graphhopper/routing/weighting/CustomWeightingTest.java
index 4393c9a27..ca1655f6b 100644
--- a/core/src/test/java/com/graphhopper/routing/weighting/CustomWeightingTest.java
+++ b/core/src/test/java/com/graphhopper/routing/weighting/CustomWeightingTest.java
@@ -72,14 +72,16 @@ public class CustomWeightingTest {
 
         CustomModel vehicleModel = new CustomModel();
         Map map = new HashMap();
-        map.put(PRIMARY.toString(), 2.0);
+        map.put(PRIMARY.toString(), 1.0);
+        map.put(CustomWeighting.CATCH_ALL, 0.5);
         vehicleModel.getPriority().put(KEY, map);
 
         Weighting weighting = new CustomWeighting(carFE, encodingManager, NO_TURN_COST_PROVIDER, vehicleModel);
         assertEquals(1.73, weighting.calcEdgeWeight(edge2, false), 0.01);
         assertEquals(1.15, weighting.calcEdgeWeight(edge1, false), 0.01);
 
-        map.put(PRIMARY.toString(), 1.1);
+        map.put(PRIMARY.toString(), 1.0);
+        map.put(CustomWeighting.CATCH_ALL, 0.9);
         weighting = new CustomWeighting(carFE, encodingManager, NO_TURN_COST_PROVIDER, vehicleModel);
         assertEquals(1.15, weighting.calcEdgeWeight(edge1, false), 0.01);
 
@@ -121,7 +123,9 @@ public class CustomWeightingTest {
         CustomModel vehicleModel = new CustomModel();
 
         BooleanEncodedValue rcLinkEnc = encodingManager.getBooleanEncodedValue(RoadClassLink.KEY);
-        vehicleModel.getPriority().put(RoadClassLink.KEY, 0.5);
+        Map map = new HashMap<>();
+        map.put("true", 0.5);
+        vehicleModel.getPriority().put(RoadClassLink.KEY, map);
         CustomWeighting weighting = new CustomWeighting(carFE, encodingManager, NO_TURN_COST_PROVIDER, vehicleModel);
 
         assertEquals(3.1, weighting.calcEdgeWeight(graphHopperStorage.edge(0, 1).setDistance(10).
diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/DecimalToValueEntryTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/DecimalToValueEntryTest.java
new file mode 100644
index 000000000..a72d003e2
--- /dev/null
+++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/DecimalToValueEntryTest.java
@@ -0,0 +1,38 @@
+package com.graphhopper.routing.weighting.custom;
+
+import com.graphhopper.routing.profiles.DecimalEncodedValue;
+import com.graphhopper.routing.profiles.EncodedValue;
+import com.graphhopper.routing.profiles.UnsignedDecimalEncodedValue;
+import com.graphhopper.storage.IntsRef;
+import com.graphhopper.util.GHUtility;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class DecimalToValueEntryTest {
+
+    @Test
+    public void testMapParsing() {
+        EncodedValue.InitializerConfig config = new EncodedValue.InitializerConfig();
+        DecimalEncodedValue decimalEncodedValue = new UnsignedDecimalEncodedValue("my_speed", 7, 0.5, false);
+        decimalEncodedValue.init(config);
+
+        double defaultValue = 33;
+        Map<String, Object> map = new HashMap<>();
+        map.put(">8", 24);
+        EdgeToValueEntry entry = DecimalToValueEntry.create("priority.my_speed", decimalEncodedValue, map, defaultValue, 1, 100);
+        IntsRef flags = new IntsRef(1);
+
+        assertValue(defaultValue, 0, flags, decimalEncodedValue, entry);
+        assertValue(defaultValue, 8.0, flags, decimalEncodedValue, entry);
+        assertValue(24.0, 10, flags, decimalEncodedValue, entry);
+    }
+
+    static void assertValue(double expectedReturnValue, double returnedFromEdge, IntsRef flags, DecimalEncodedValue decimalEncodedValue, EdgeToValueEntry entry) {
+        decimalEncodedValue.setDecimal(false, flags, returnedFromEdge);
+        assertEquals(expectedReturnValue, entry.getValue(GHUtility.createMockedEdgeIteratorState(100, flags), false));
+    }
+}
\ No newline at end of file
diff --git a/web/src/test/java/com/graphhopper/http/resources/CustomWeightingRouteResourceLMTest.java b/web/src/test/java/com/graphhopper/http/resources/CustomWeightingRouteResourceLMTest.java
index e56ecdd8d..8dcf85f4c 100644
--- a/web/src/test/java/com/graphhopper/http/resources/CustomWeightingRouteResourceLMTest.java
+++ b/web/src/test/java/com/graphhopper/http/resources/CustomWeightingRouteResourceLMTest.java
@@ -97,7 +97,8 @@ public class CustomWeightingRouteResourceLMTest {
                 "profile: car_custom\n" +
                 "priority:\n" +
                 "  road_class:\n" +
-                "    secondary: 2\n";
+                "    secondary: 1\n" +
+                "    '*': 0.5\n";
         JsonNode yamlNode = queryYaml(yamlQuery, 200).readEntity(JsonNode.class);
         JsonNode path = yamlNode.get("paths").get(0);
         assertBetween("distance wasn't correct", path.get("distance").asDouble(), 1300, 1400);
@@ -106,7 +107,7 @@ public class CustomWeightingRouteResourceLMTest {
         yamlQuery = "points: [[1.5274,42.506211], [1.54006,42.511178]]\n" +
                 "profile: car_custom\n" +
                 "priority:\n" +
-                "  road_class: { residential: 1.2, primary: 1.5 }";
+                "  road_class: { residential: 0.8, primary: 1, '*': 0.66 }";
         yamlNode = queryYaml(yamlQuery, 200).readEntity(JsonNode.class);
         path = yamlNode.get("paths").get(0);
         assertBetween("distance wasn't correct", path.get("distance").asDouble(), 1650, 1750);
diff --git a/web/src/test/resources/com/graphhopper/http/resources/cargo_bike.yml b/web/src/test/resources/com/graphhopper/http/resources/cargo_bike.yml
index 5f803f35c..8b086f30d 100644
--- a/web/src/test/resources/com/graphhopper/http/resources/cargo_bike.yml
+++ b/web/src/test/resources/com/graphhopper/http/resources/cargo_bike.yml
@@ -1,9 +1,6 @@
 # For a custom request you need to specify a profile (with weighting=custom) but on import we don't need this
 # profile: bike
 
-vehicle_height: 2.3
-vehicle_width: 1.2
-
 # let's assume cargo bikes are e-bikes and we should lower speed to prefer shorter and not faster routes automatically
 max_speed:
   road_class:
@@ -19,8 +16,13 @@ priority:
   surface:
     sand: 0.5
   # prefer better tracks
-  track_type: { other: 1.1, grade1: 1.1 }
+  track_type: { other: 1.0, grade1: 1.0, "*": 0.9 }
   # prefer official bike routes
   bike_network: { other: 0.5 }
   # avoid all situations where we have to get off the bike
-  get_off_bike: 0.5
\ No newline at end of file
+  get_off_bike:
+    "true": 0.5
+  max_height:
+    "<2.3": 0
+  max_width:
+    "<1.2": 0
diff --git a/web/src/test/resources/com/graphhopper/http/resources/truck.yml b/web/src/test/resources/com/graphhopper/http/resources/truck.yml
index 3324f9346..c96744c6c 100644
--- a/web/src/test/resources/com/graphhopper/http/resources/truck.yml
+++ b/web/src/test/resources/com/graphhopper/http/resources/truck.yml
@@ -1,14 +1,6 @@
 # For a custom request you need to specify a profile (with weighting=custom) but on import we don't need this
 # profile: car
 
-# in meter
-vehicle_height: 3.8
-vehicle_width: 2.5
-# vehicle_length: 5
-
-# in tons
-vehicle_weight: 4.5
-
 # The formula is defined in CustomWeighting, where the speed_factor can be used to decrease speed.
 # The minimum value is 0 and the maximum is 1. If two conditions are satisfied the values are
 # multiplied e.g. if road_class==motorway and road_environment==tunnel, then the resulting speed is
@@ -28,6 +20,7 @@ max_speed:
     residential: 30
   road_environment:
     bridge: 85
+
 # Trucks are slower so limit all speed to this value. In km/h
 # If none of the conditions in the map above apply use the fallback, if specified:
 max_speed_fallback: 100
@@ -39,13 +32,15 @@ max_speed_fallback: 100
 distance_influence: 90
 
 # Now we want to set a preference without changing the taken time. The default priority is 1 and does not change something.
-# Higher than 1 will prefer the road under the specified condition and lower than 1 will avoid it.
-# If two conditions are met the values will be multiplied. The minimum value is 0 (block access) the maximum value is 100.
+# The minimum value is 0 (block access). A value lower than 1 will avoid it. Values higher than 1 are not allowed.
+# To prefer a certain case you lower the priority of all other entries via the catch-all key "*",
+# see the road_class entry where motorways are preferred. If two conditions are met the values will be multiplied.
 priority:
   road_class:
-    motorway: 1.1
-    residential: 0.5
-    # cars allow tracks so we should block them here, but in the example in CustomWeightingRouteResourceTest the bridge is only accessible through a track
+    motorway: 1.0
+    residential: 0.4
+    "*": 0.9
+    # and if you do not want that tracks go over tracks just exclude them via
     # track: 0
   # let's assume we transport gas: so NEVER go on restricted roads with hazmat==no
   hazmat: { no: 0 }
@@ -53,11 +48,15 @@ priority:
   # TODO include private and delivery only access for base car profile so that we can avoid it here, but not exclude it
   road_access: { destination: 0.1 }
   # avoid turns if possible and links are one simple indication for that
-  road_class_link: 0.5
+  road_class_link:
+    true: 0.5
   # avoid toll roads
-  toll: { no: 2 }
+  toll: { no: 1, "*": 0.5 }
   # avoid a certain area
   area_custom1: 0.5
+  max_weight: { "<4.5": 0 }
+  max_height: { "<3.8": 0 }
+  max_width: { "<2.5": 0 }
 
 areas:
   custom1: