Index: trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java	(revision 15837)
+++ trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java	(revision 15838)
@@ -8,11 +8,9 @@
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Comparator;
-import java.util.Iterator;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.NavigableSet;
 import java.util.Objects;
-import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
@@ -21,5 +19,4 @@
 import java.util.regex.Pattern;
 import java.util.stream.IntStream;
-import java.util.stream.Stream;
 
 import org.openstreetmap.josm.actions.mapmode.MapMode;
@@ -94,5 +91,5 @@
      * The buttons currently displayed in map view.
      */
-    private final Map<String, AutoFilterButton> buttons = new TreeMap<>();
+    private final Map<Integer, AutoFilterButton> buttons = new TreeMap<>();
 
     /**
@@ -146,5 +143,5 @@
                 && enabledRule.getMinZoomLevel() <= Selector.GeneralSelector.scale2level(map.mapView.getDist100Pixel())) {
             // Retrieve the values from current rule visible on screen
-            NavigableSet<String> values = getNumericValues(enabledRule.getKey(), enabledRule.getValueComparator());
+            NavigableSet<Integer> values = getNumericValues(enabledRule.getKey());
             // Make sure current auto filter button remains visible even if no data is found, to allow user to disable it
             if (currentAutoFilter != null) {
@@ -158,9 +155,9 @@
     }
 
-    static class CompiledFilter extends Filter implements MatchSupplier {
+    class CompiledFilter extends Filter implements MatchSupplier {
         final String key;
-        final String value;
-
-        CompiledFilter(String key, String value) {
+        final int value;
+
+        CompiledFilter(String key, int value) {
             this.key = key;
             this.value = value;
@@ -175,5 +172,5 @@
                 @Override
                 public boolean match(OsmPrimitive osm) {
-                    return getTagValuesForPrimitive(key, osm).anyMatch(value::equals);
+                    return getTagValuesForPrimitive(key, osm).anyMatch(v -> v == value);
                 }
             };
@@ -181,5 +178,5 @@
     }
 
-    private synchronized void addNewButtons(NavigableSet<String> values) {
+    private synchronized void addNewButtons(NavigableSet<Integer> values) {
         if (values.isEmpty()) {
             return;
@@ -188,6 +185,6 @@
         int maxWidth = 16;
         final AutoFilterButton keyButton = AutoFilterButton.forOsmKey(enabledRule.getKey());
-        addButton(keyButton, Integer.toString(Integer.MIN_VALUE), i++);
-        for (final String value : values.descendingSet()) {
+        addButton(keyButton, Integer.MIN_VALUE, i++);
+        for (final Integer value : values.descendingSet()) {
             CompiledFilter filter = new CompiledFilter(enabledRule.getKey(), value);
             String label = enabledRule.getValueFormatter().apply(value);
@@ -206,5 +203,5 @@
     }
 
-    private void addButton(AutoFilterButton button, String value, int i) {
+    private void addButton(AutoFilterButton button, int value, int i) {
         MapView mapView = MainApplication.getMap().mapView;
         buttons.put(value, button);
@@ -213,62 +210,46 @@
 
     private void removeAllButtons() {
-        for (Iterator<String> it = buttons.keySet().iterator(); it.hasNext();) {
-            MainApplication.getMap().mapView.remove(buttons.get(it.next()));
-            it.remove();
-        }
-    }
-
-    private static NavigableSet<String> getNumericValues(String key, Comparator<String> comparator) {
-        NavigableSet<String> values = new TreeSet<>(comparator);
-        for (String s : getTagValues(key)) {
-            try {
-                Integer.parseInt(s);
-                values.add(s);
-            } catch (NumberFormatException e) {
-                Logging.trace(e);
-            }
-        }
+        buttons.values().forEach(MainApplication.getMap().mapView::remove);
+        buttons.clear();
+    }
+
+    private NavigableSet<Integer> getNumericValues(String key) {
+        DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
+        if (ds == null) {
+            return Collections.emptyNavigableSet();
+        }
+        BBox bbox = MainApplication.getMap().mapView.getState().getViewArea().getLatLonBoundsBox().toBBox();
+        NavigableSet<Integer> values = new TreeSet<>();
+        Consumer<OsmPrimitive> consumer = o -> getTagValuesForPrimitive(key, o).forEach(values::add);
+        ds.searchNodes(bbox).forEach(consumer);
+        ds.searchWays(bbox).forEach(consumer);
+        ds.searchRelations(bbox).forEach(consumer);
         return values;
     }
 
-    private static Set<String> getTagValues(String key) {
-        DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
-        Set<String> values = new TreeSet<>();
-        if (ds != null) {
-            BBox bbox = MainApplication.getMap().mapView.getState().getViewArea().getLatLonBoundsBox().toBBox();
-            Consumer<OsmPrimitive> consumer = o -> getTagValuesForPrimitive(key, o).forEach(values::add);
-            ds.searchNodes(bbox).forEach(consumer);
-            ds.searchWays(bbox).forEach(consumer);
-            ds.searchRelations(bbox).forEach(consumer);
-        }
-        return values;
-    }
-
-    static Stream<String> getTagValuesForPrimitive(String key, OsmPrimitive osm) {
+    protected IntStream getTagValuesForPrimitive(String key, OsmPrimitive osm) {
+        if (enabledRule == null) {
+            return IntStream.empty();
+        }
         String value = osm.get(key);
         if (value != null) {
             Pattern p = Pattern.compile("(-?[0-9]+)-(-?[0-9]+)");
-            return OsmUtils.splitMultipleValues(value).flatMap(v -> {
+            return OsmUtils.splitMultipleValues(value).flatMapToInt(v -> {
                 Matcher m = p.matcher(v);
                 if (m.matches()) {
                     int a = Integer.parseInt(m.group(1));
                     int b = Integer.parseInt(m.group(2));
-                    return IntStream.rangeClosed(Math.min(a, b), Math.max(a, b))
-                            .mapToObj(Integer::toString);
+                    return IntStream.rangeClosed(Math.min(a, b), Math.max(a, b));
                 } else {
-                    return Stream.of(v);
+                    try {
+                        return IntStream.of(enabledRule.getValueExtractor().applyAsInt(v));
+                    } catch (NumberFormatException e) {
+                        Logging.trace(e);
+                        return IntStream.empty();
+                    }
                 }
             });
-        } else if (PROP_AUTO_FILTER_DEFAULTS.get() && "layer".equals(key)) {
-            // assume sensible defaults, see #17496
-            if (osm.hasTag("bridge") || osm.hasTag("power", "line") || osm.hasTag("location", "overhead")) {
-                return Stream.of("1");
-            } else if (osm.isKeyTrue("tunnel") || osm.hasTag("tunnel", "culvert") || osm.hasTag("location", "underground")) {
-                return Stream.of("-1");
-            } else if (osm.hasTag("tunnel", "building_passage") || osm.hasKey("highway", "railway", "waterway")) {
-                return Stream.of("0");
-            }
-        }
-        return Stream.empty();
+        }
+        return enabledRule.getDefaultValueSupplier().apply(osm);
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java	(revision 15837)
+++ trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java	(revision 15838)
@@ -2,8 +2,13 @@
 package org.openstreetmap.josm.gui.autofilter;
 
-import java.util.Comparator;
+import java.util.Arrays;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.function.Function;
-import java.util.function.UnaryOperator;
+import java.util.function.IntFunction;
+import java.util.function.ToIntFunction;
+import java.util.stream.IntStream;
+
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
 
 /**
@@ -20,7 +25,9 @@
     private final int minZoomLevel;
 
-    private UnaryOperator<String> valueFormatter = s -> s;
+    private Function<OsmPrimitive, IntStream> defaultValueSupplier = p -> IntStream.empty();
 
-    private Comparator<String> valueComparator = Comparator.comparingInt(s -> Integer.parseInt(valueFormatter.apply(s)));
+    private ToIntFunction<String> valueExtractor = Integer::parseInt;
+
+    private IntFunction<String> valueFormatter = Integer::toString;
 
     /**
@@ -54,5 +61,5 @@
      * @return the OSM value formatter that defines the associated button label (identity by default)
      */
-    public Function<String, String> getValueFormatter() {
+    public IntFunction<String> getValueFormatter() {
         return valueFormatter;
     }
@@ -64,5 +71,5 @@
      * @throws NullPointerException if {@code valueFormatter} is null
      */
-    public AutoFilterRule setValueFormatter(UnaryOperator<String> valueFormatter) {
+    public AutoFilterRule setValueFormatter(IntFunction<String> valueFormatter) {
         this.valueFormatter = Objects.requireNonNull(valueFormatter);
         return this;
@@ -70,19 +77,39 @@
 
     /**
-     * Returns the OSM value comparator used to order the buttons.
-     * @return the OSM value comparator
+     * Returns a function which yields default values for the given OSM primitive
+     * @return a function which yields default values for the given OSM primitive
      */
-    public Comparator<String> getValueComparator() {
-        return valueComparator;
+    public Function<OsmPrimitive, IntStream> getDefaultValueSupplier() {
+        return defaultValueSupplier;
     }
 
     /**
-     * Sets the OSM value comparator used to order the buttons.
-     * @param valueComparator the OSM value comparator
+     * Sets the function which yields default values for the given OSM primitive.
+     * This function is invoked if the primitive does not have this {@linkplain #getKey() key}.
+     * @param defaultValueSupplier the function which yields default values for the given OSM primitive
      * @return {@code this}
-     * @throws NullPointerException if {@code valueComparator} is null
+     * @throws NullPointerException if {@code defaultValueSupplier} is null
      */
-    public AutoFilterRule setValueComparator(Comparator<String> valueComparator) {
-        this.valueComparator = valueComparator;
+    public AutoFilterRule setDefaultValueSupplier(Function<OsmPrimitive, IntStream> defaultValueSupplier) {
+        this.defaultValueSupplier = Objects.requireNonNull(defaultValueSupplier);
+        return this;
+    }
+
+    /**
+     * Returns a function which extracts a numeric value from an OSM value
+     * @return a function which extracts a numeric value from an OSM value
+     */
+    public ToIntFunction<String> getValueExtractor() {
+        return valueExtractor;
+    }
+
+    /**
+     * Sets the function which extracts a numeric value from an OSM value
+     * @param valueExtractor the function which extracts a numeric value from an OSM value
+     * @return {@code this}
+     * @throws NullPointerException if {@code valueExtractor} is null
+     */
+    public AutoFilterRule setValueExtractor(ToIntFunction<String> valueExtractor) {
+        this.valueExtractor = Objects.requireNonNull(valueExtractor);
         return this;
     }
@@ -93,13 +120,45 @@
      */
     public static AutoFilterRule[] defaultRules() {
-        return new AutoFilterRule[] {
+        return new AutoFilterRule[]{
             new AutoFilterRule("level", 17),
-            new AutoFilterRule("layer", 16),
+            new AutoFilterRule("layer", 16)
+                    .setDefaultValueSupplier(AutoFilterRule::defaultLayer),
             new AutoFilterRule("maxspeed", 16)
-                .setValueFormatter(s -> s.replace(" mph", "")),
+                    .setValueExtractor(s -> Integer.parseInt(s.replace(" mph", ""))),
             new AutoFilterRule("voltage", 5)
-                .setValueFormatter(s -> s.replaceAll("000$", "k") + 'V')
-                .setValueComparator(Comparator.comparingInt(Integer::parseInt))
+                    .setValueFormatter(s -> s % 1000 == 0 ? (s / 1000) + "kV" : s + "V"),
+            new AutoFilterRule("building:levels", 17),
+            new AutoFilterRule("gauge", 5),
+            new AutoFilterRule("frequency", 5),
+            new AutoFilterRule("incline", 13)
+                    .setValueExtractor(s -> Integer.parseInt(s.replaceAll("%$", "")))
+                    .setValueFormatter(v -> v + "\u2009%"),
+            new AutoFilterRule("lanes", 13),
+            new AutoFilterRule("admin_level", 11)
         };
+    }
+
+    /**
+     * Returns the default auto filter rule for the given key
+     * @param key the OSM key
+     * @return default auto filter rule for the given key
+     */
+    static Optional<AutoFilterRule> getDefaultRule(String key) {
+        return Arrays.stream(AutoFilterRule.defaultRules())
+                .filter(r -> key.equals(r.getKey()))
+                .findFirst();
+    }
+
+    private static IntStream defaultLayer(OsmPrimitive osm) {
+        // assume sensible defaults, see #17496
+        if (osm.hasTag("bridge") || osm.hasTag("power", "line") || osm.hasTag("location", "overhead")) {
+            return IntStream.of(1);
+        } else if (osm.isKeyTrue("tunnel") || osm.hasTag("tunnel", "culvert") || osm.hasTag("location", "underground")) {
+            return IntStream.of(-1);
+        } else if (osm.hasTag("tunnel", "building_passage") || osm.hasKey("highway", "railway", "waterway")) {
+            return IntStream.of(0);
+        } else {
+            return IntStream.empty();
+        }
     }
 
