diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java
index 978c3f612b..f4034e72a4 100644
--- a/src/org/openstreetmap/josm/gui/MapView.java
+++ b/src/org/openstreetmap/josm/gui/MapView.java
@@ -620,7 +620,7 @@ LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener {
         }
 
         MapFrame map = MainApplication.getMap();
-        if (AutoFilterManager.getInstance().getCurrentAutoFilter() != null) {
+        if (AutoFilterManager.getInstance().getCurrentCombinedFilter() != null) {
             AutoFilterManager.getInstance().drawOSDText(tempG);
         } else if (MainApplication.isDisplayingMapView() && map.filterDialog != null) {
             map.filterDialog.drawOSDText(tempG);
diff --git a/src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java b/src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java
index d12685529b..ec14133f2c 100644
--- a/src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java
+++ b/src/org/openstreetmap/josm/gui/autofilter/AutoFilterButton.java
@@ -35,13 +35,28 @@ public class AutoFilterButton extends JButton {
         super(new JosmAction(filter.getLabel(), null, filter.getDescription(), null, false) {
             @Override
             public synchronized void actionPerformed(ActionEvent e) {
+
                 AutoFilterManager afm = AutoFilterManager.getInstance();
-                if (filter.equals(afm.getCurrentAutoFilter())) {
-                    afm.setCurrentAutoFilter(null);
-                    MainApplication.getMap().filterDialog.getFilterModel().executeFilters(true);
-                } else {
+
+                if (afm.getCurrentAutoFilters().isEmpty()) {
                     afm.setCurrentAutoFilter(filter);
+                } else {
+                    if ((e.getModifiers() & ActionEvent.CTRL_MASK) != 0
+                            || (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0) {
+                        if (afm.getCurrentAutoFilters().contains(filter)) {
+                            afm.removeCurrentAutoFilter(filter);
+                        } else {
+                            afm.addCurrentAutoFilter(filter);
+                        }
+                    } else {
+                        if (afm.getCurrentAutoFilters().size() == 1 && afm.getCurrentAutoFilters().contains(filter)) {
+                            afm.setCurrentAutoFilter(null);
+                        } else {
+                            afm.setCurrentAutoFilter(filter);
+                        }
+                    }
                 }
+
             }
         });
         this.filter = filter;
@@ -54,7 +69,7 @@ public class AutoFilterButton extends JButton {
     protected void paintComponent(Graphics g) {
         if (getModel().isPressed()) {
             g.setColor(PROP_COLOR.get().darker().darker());
-        } else if (getModel().isRollover() || AutoFilterManager.getInstance().getCurrentAutoFilter() == filter) {
+        } else if (getModel().isRollover() || AutoFilterManager.getInstance().getCurrentAutoFilters().contains(filter)) {
             g.setColor(PROP_COLOR.get().darker());
         } else {
             g.setColor(PROP_COLOR.get());
diff --git a/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java b/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java
index 3a7ccb67a2..e59bc85f18 100644
--- a/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java
+++ b/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java
@@ -8,13 +8,15 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.NavigableSet;
 import java.util.Objects;
-import java.util.TreeMap;
+import java.util.OptionalInt;
 import java.util.TreeSet;
-import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 import org.openstreetmap.josm.actions.mapmode.MapMode;
 import org.openstreetmap.josm.data.osm.BBox;
@@ -63,10 +65,17 @@ public final class AutoFilterManager
 implements ZoomChangeListener, MapModeChangeListener, DataSetListener, PreferenceChangedListener, LayerChangeListener {
 
     /**
-     * Property to determines if the auto filter feature is enabled.
+     * Property to determine if the auto filter feature is enabled.
      */
     public static final BooleanProperty PROP_AUTO_FILTER_ENABLED = new BooleanProperty("auto.filter.enabled", true);
 
+    /**
+     * Property to determine if the auto filter feature completely hides elements instead of just disabling them.
+     * Equivalent to {@link Filter#hiding}
+     */
+    public static final BooleanProperty PROP_AUTO_FILTER_HIDING = new BooleanProperty("auto.filter.hiding", false);
+
+
     /**
      * Property to determine the current auto filter rule.
      */
@@ -80,7 +89,7 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
     /**
      * The buttons currently displayed in map view.
      */
-    private final Map<Integer, AutoFilterButton> buttons = new TreeMap<>();
+    private final Map<OptionalInt, AutoFilterButton> buttons = new HashMap<>();
 
     /**
      * The list of registered auto filter rules.
@@ -103,9 +112,10 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
     AutoFilterRule enabledRule;
 
     /**
-     * The currently selected auto filter, if any.
+     * The currently selected auto filters, if any.
+     * If more than one auto filter is active, elements will match if they match at least one of them.
      */
-    private AutoFilter currentAutoFilter;
+    private final List<AutoFilter> currentAutoFilters = new ArrayList<>();
 
     /**
      * Returns the unique instance.
@@ -133,11 +143,15 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
                 && enabledRule.getMinZoomLevel() <= Selector.GeneralSelector.scale2level(map.mapView.getDist100Pixel())) {
             // Retrieve the values from current rule visible on screen
             NavigableSet<Integer> values = getNumericValues();
-            // Make sure current auto filter button remains visible even if no data is found, to allow user to disable it
-            if (currentAutoFilter != null) {
-                values.add(currentAutoFilter.getFilter().value);
+            // Make sure current auto filter buttons remain visible even if no data is found, to allow user to disable them
+            for (var currentAutoFilter : currentAutoFilters) {
+                if (currentAutoFilter.getFilter().value != null) {
+                    values.add(currentAutoFilter.getFilter().value);
+                }
             }
-            if (!values.equals(buttons.keySet())) {
+            if (!values.equals(buttons.keySet().stream()
+                    .filter(it -> it.isPresent() && it.getAsInt() != Integer.MIN_VALUE)
+                    .map(OptionalInt::getAsInt).collect(Collectors.toSet()))) {
                 removeAllButtons();
                 addNewButtons(values);
             }
@@ -146,11 +160,12 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
 
     static class CompiledFilter extends Filter implements MatchSupplier {
         final AutoFilterRule rule;
-        final int value;
+        final Integer value;
 
-        CompiledFilter(AutoFilterRule rule, int value) {
+        CompiledFilter(AutoFilterRule rule, Integer value, boolean hiding) {
             this.rule = rule;
             this.value = value;
+            this.hiding = hiding;
             this.enable = true;
             this.inverted = true;
             this.text = rule.getKey() + "=" + rule.formatValue(value);
@@ -173,22 +188,81 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
             if (!super.equals(obj) || getClass() != obj.getClass())
                 return false;
             CompiledFilter other = (CompiledFilter) obj;
-            return Objects.equals(rule, other.rule) && value == other.value;
+            return Objects.equals(rule, other.rule) && Objects.equals(value, other.value);
+        }
+    }
+
+    /** The combination of multiple {@link CompiledFilter}s */
+    static class CombinedFilter extends Filter implements MatchSupplier {
+
+        private final List<CompiledFilter> filters;
+
+        CombinedFilter(List<CompiledFilter> filters) {
+
+            if (filters == null || filters.isEmpty()) throw new IllegalArgumentException("no filters provided");
+
+            this.filters = filters;
+
+            boolean hiding = filters.get(0).hiding;
+            String key = filters.get(0).rule.getKey();
+            List<String> values = new ArrayList<>();
+
+            for (CompiledFilter filter : filters) {
+                if (hiding != filter.hiding) throw new IllegalArgumentException("non-matching hiding properties");
+                if (!Objects.equals(key, filter.rule.getKey())) throw new IllegalArgumentException("non-matching keys");
+                values.add(filter.rule.formatValue(filter.value));
+            }
+
+            this.hiding = hiding;
+            this.enable = true;
+            this.inverted = true;
+            this.text = key + "~" + String.join("|", values);
+
+        }
+
+        @Override
+        public SearchCompiler.Match get() {
+            return new SearchCompiler.Match() {
+                @Override
+                public boolean match(OsmPrimitive osm) {
+                    return filters.stream().anyMatch(filter -> filter.get().match(osm));
+                }
+            };
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * super.hashCode() + Objects.hash(filters);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!super.equals(obj) || getClass() != obj.getClass())
+                return false;
+            CombinedFilter other = (CombinedFilter) obj;
+            return Objects.equals(filters, other.filters);
         }
     }
 
     static class Match extends SearchCompiler.Match {
         final AutoFilterRule rule;
-        final int value;
+        final Integer value;
 
-        Match(AutoFilterRule rule, int value) {
+        Match(AutoFilterRule rule, Integer value) {
             this.rule = rule;
             this.value = value;
         }
 
         @Override
         public boolean match(OsmPrimitive osm) {
-            return rule.getTagValuesForPrimitive(osm).anyMatch(v -> v == value);
+            IntStream values = rule.getTagValuesForPrimitive(osm, false);
+            if (value != null) {
+                return values.anyMatch(v -> v == value);
+            } else {
+                return values.findAny().isEmpty();
+            }
         }
 
         @Override
@@ -196,7 +270,7 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
             Match match = (Match) o;
-            return value == match.value &&
+            return Objects.equals(value, match.value) &&
                     Objects.equals(rule, match.rule);
         }
 
@@ -214,16 +288,20 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
         int maxWidth = 16;
         final AutoFilterButton keyButton = AutoFilterButton.forOsmKey(enabledRule.getKey());
         addButton(keyButton, Integer.MIN_VALUE, i++);
-        for (final Integer value : values.descendingSet()) {
-            CompiledFilter filter = new CompiledFilter(enabledRule, value);
+        var valueList = new ArrayList<>(values.descendingSet());
+        if (enabledRule.getNoValueFilter()) {
+            valueList.add(null);
+        }
+        for (final Integer value : valueList) {
+            CompiledFilter filter = new CompiledFilter(enabledRule, value, PROP_AUTO_FILTER_HIDING.get());
             String label = enabledRule.formatValue(value);
             AutoFilter autoFilter = new AutoFilter(label, filter.text, filter);
             AutoFilterButton button = new AutoFilterButton(autoFilter);
-            if (autoFilter.equals(currentAutoFilter)) {
+            if (currentAutoFilters.contains(autoFilter)) {
                 button.getModel().setPressed(true);
             }
-            maxWidth = Math.max(maxWidth, button.getPreferredSize().width);
             addButton(button, value, i++);
+            maxWidth = Math.max(maxWidth, button.getPreferredSize().width);
         }
         for (AutoFilterButton b : buttons.values()) {
             b.setSize(b == keyButton ? b.getPreferredSize().width : maxWidth, 20);
@@ -231,9 +309,9 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
         MainApplication.getMap().mapView.validate();
     }
 
-    private void addButton(AutoFilterButton button, int value, int i) {
+    private void addButton(AutoFilterButton button, Integer value, int i) {
         MapView mapView = MainApplication.getMap().mapView;
-        buttons.put(value, button);
+        buttons.put(value == null ? OptionalInt.empty() : OptionalInt.of(value), button);
         mapView.add(button).setLocation(3, 60 + 22*i);
     }
 
@@ -252,10 +330,15 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
         }
         BBox bbox = MainApplication.getMap().mapView.getState().getViewArea().getLatLonBoundsBox().toBBox();
         NavigableSet<Integer> values = new TreeSet<>();
-        Consumer<OsmPrimitive> consumer = o -> enabledRule.getTagValuesForPrimitive(o).forEach(values::add);
-        ds.searchNodes(bbox).forEach(consumer);
-        ds.searchWays(bbox).forEach(consumer);
-        ds.searchRelations(bbox).forEach(consumer);
+        for (var primitiveList : List.of(ds.searchNodes(bbox), ds.searchWays(bbox), ds.searchRelations(bbox))) {
+            // add all values that are directly mentioned
+            primitiveList.forEach(o -> enabledRule.getTagValuesForPrimitive(o, true).forEach(values::add));
+            // only add integer values from value ranges, not fractional values
+            primitiveList.forEach(o -> enabledRule.getTagValuesForPrimitive(o, false)
+                    .filter(v -> !enabledRule.formatValue(v).contains("."))
+                    .forEach(values::add));
+
+        }
         return values;
     }
 
@@ -267,6 +350,7 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
     @Override
     public void dataChanged(DataChangedEvent event) {
         updateFiltersFull();
+        updateButtons();
     }
 
     @Override
@@ -313,13 +397,13 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
     }
 
     private synchronized void updateFiltersFull() {
-        if (currentAutoFilter != null) {
+        if (!currentAutoFilters.isEmpty()) {
             model.executeFilters();
         }
     }
 
     private synchronized void updateFiltersEvent(AbstractDatasetChangedEvent event, boolean affectedOnly) {
-        if (currentAutoFilter != null) {
+        if (!currentAutoFilters.isEmpty()) {
             Collection<? extends OsmPrimitive> prims = event.getPrimitives();
             model.executeFilters(affectedOnly ? FilterModel.getAffectedPrimitives(prims) : prims);
         }
@@ -382,11 +466,25 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
     }
 
     /**
-     * Returns the currently selected auto filter, if any.
-     * @return the currently selected auto filter, or null
+     * Returns the currently selected auto filters, if any.
+     * @return the currently selected auto filters. Can be empty.
      */
-    public synchronized AutoFilter getCurrentAutoFilter() {
-        return currentAutoFilter;
+    public synchronized List<AutoFilter> getCurrentAutoFilters() {
+        return currentAutoFilters;
+    }
+
+    /**
+     * Returns a combination of all {@link #getCurrentAutoFilters()}, if any.
+     * @return a single combined filter, or null
+     */
+    public Filter getCurrentCombinedFilter() {
+        if (currentAutoFilters.isEmpty()) {
+            return null;
+        } else if (currentAutoFilters.size() == 1) {
+            return currentAutoFilters.get(0).getFilter();
+        } else {
+            return new CombinedFilter(currentAutoFilters.stream().map(AutoFilter::getFilter).collect(Collectors.toList()));
+        }
     }
 
     /**
@@ -394,16 +492,40 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
      * @param autoFilter the currently selected auto filter, or null
      */
     public synchronized void setCurrentAutoFilter(AutoFilter autoFilter) {
-        model.clearFilters();
-        currentAutoFilter = autoFilter;
+        currentAutoFilters.clear();
         if (autoFilter != null) {
-            model.addFilter(autoFilter.getFilter());
+            currentAutoFilters.add(autoFilter);
+        }
+        updateModelFilters();
+    }
+
+    public synchronized void addCurrentAutoFilter(AutoFilter autoFilter) {
+        if (!currentAutoFilters.contains(autoFilter)) {
+            currentAutoFilters.add(autoFilter);
+            updateModelFilters();
+        }
+    }
+
+    public synchronized void removeCurrentAutoFilter(AutoFilter autoFilter) {
+        if (currentAutoFilters.contains(autoFilter)) {
+            currentAutoFilters.removeIf(it -> Objects.equals(it, autoFilter));
+            updateModelFilters();
+        }
+    }
+
+    private synchronized void updateModelFilters() {
+        model.clearFilters();
+        if (currentAutoFilters.isEmpty()) {
+			if (MainApplication.getMap() != null) {
+                MainApplication.getMap().filterDialog.getFilterModel().executeFilters(true);
+            }
+        } else {
+            model.addFilter(getCurrentCombinedFilter());
             model.executeFilters();
-            if (model.isChanged()) {
-                OsmDataLayer dataLayer = MainApplication.getLayerManager().getActiveDataLayer();
-                if (dataLayer != null) {
-                    dataLayer.invalidate();
-                }
+            // update the data layer (necessary even if model.isChanged() == false to update the OSDText)
+            OsmDataLayer dataLayer = MainApplication.getLayerManager().getActiveDataLayer();
+            if (dataLayer != null) {
+                dataLayer.invalidate();
             }
         }
     }
@@ -413,8 +535,10 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
      * @param g The graphics to draw that text on.
      */
     public synchronized void drawOSDText(Graphics2D g) {
+        String filterText = Objects.requireNonNull(getCurrentCombinedFilter()).text;
+        String lengthLimitedFilterText = filterText.length() > 18 ? filterText.substring(0, 18) + "…" : filterText;
         model.drawOSDText(g, lblOSD,
-            tr("<h2>Filter active: {0}</h2>", currentAutoFilter.getFilter().text),
+            tr("<h2>Filter active: {0}</h2>", lengthLimitedFilterText),
             tr("</p><p>Click again on filter button to see all objects.</p></html>"));
     }
 
@@ -437,7 +561,8 @@ implements ZoomChangeListener, MapModeChangeListener, DataSetListener, Preferenc
                 enableAutoFilterRule((AutoFilterRule) null);
                 resetCurrentAutoFilter();
             }
-        } else if (e.getKey().equals(PROP_AUTO_FILTER_RULE.getKey())) {
+        } else if (e.getKey().equals(PROP_AUTO_FILTER_RULE.getKey())
+                || e.getKey().equals(PROP_AUTO_FILTER_HIDING.getKey())) {
             enableAutoFilterRule(PROP_AUTO_FILTER_RULE.get());
             resetCurrentAutoFilter();
             updateButtons();
diff --git a/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java b/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java
index 9c35c387e9..0710d4a03b 100644
--- a/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java
+++ b/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java
@@ -2,22 +2,22 @@
 package org.openstreetmap.josm.gui.autofilter;
 
 import java.text.DecimalFormat;
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.Optional;
+import java.util.*;
 import java.util.function.Function;
 import java.util.function.IntFunction;
 import java.util.function.ToIntFunction;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.OsmUtils;
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.tools.Logging;
 
+import static java.util.stream.Collectors.toList;
+
 /**
  * An auto filter rule determines how auto filter can be built from visible map data.
  * Several rules can be registered, but only one rule is active at the same time.
@@ -42,6 +42,11 @@ public class AutoFilterRule {
 
     private IntFunction<String> valueFormatter = Integer::toString;
 
+    private boolean noValueFilter = false;
+
+    /** The union of {@link #key} and the keys provided by {@link #setExtraKeys(List)}. */
+    private List<String> allKeys;
+
     /**
      * Constructs a new {@code AutoFilterRule}.
      * @param key the OSM key on which the rule applies
@@ -50,6 +55,7 @@ public class AutoFilterRule {
     public AutoFilterRule(String key, int minZoomLevel) {
         this.key = key;
         this.minZoomLevel = minZoomLevel;
+        this.allKeys = List.of(key);
     }
 
     /**
@@ -68,13 +74,20 @@ public class AutoFilterRule {
         return minZoomLevel;
     }
 
+    /**
+     * Returns true if there should be a filter button for OSM primitives which have no value for the key.
+     */
+    public boolean getNoValueFilter() {
+        return noValueFilter;
+    }
+
     /**
      * Formats the numeric value
      * @param value the numeric value to format
      * @return the formatted value
      */
-    public String formatValue(int value) {
-        return valueFormatter.apply(value);
+    public String formatValue(Integer value) {
+        return value == null ? "∅" : valueFormatter.apply(value);
     }
 
     /**
@@ -111,12 +124,51 @@ public class AutoFilterRule {
         return this;
     }
 
+    /**
+     * Adds a filter button for OSM primitives which have no value for the key.
+     */
+    public AutoFilterRule enableNoValueFilter() {
+        this.noValueFilter = true;
+        return this;
+    }
+
+    /**
+     * Sets extra OSM keys on which the rule applies in addition to the primary key ({@link #getKey()}).
+     * This allows a filter to look at the values of more than one key at the same time.
+     * @param extraKeys the list of extra keys, may be empty
+     * @return {@code this}
+     * @throws NullPointerException if {@code extraKeys} is null
+     */
+    public AutoFilterRule setExtraKeys(List<String> extraKeys) {
+        Objects.requireNonNull(extraKeys);
+        this.allKeys = Stream.concat(Stream.of(key), extraKeys.stream()).collect(toList());
+        return this;
+    }
+
     /**
      * Returns the numeric values for the given OSM primitive
-     * @param osm the primitive
+     *
+     * @param osm              the primitive
+     * @param directValuesOnly whether "inner" values from ranges (such as 6 and 7 for 5-8) should be omitted
      * @return a stream of numeric values
      */
-    public IntStream getTagValuesForPrimitive(OsmPrimitive osm) {
+    public IntStream getTagValuesForPrimitive(OsmPrimitive osm, boolean directValuesOnly) {
+        if (osm.isDeleted()) return IntStream.empty();
+        if (allKeys.size() == 1) {
+            IntStream values = getTagValuesForPrimitive(osm, key, directValuesOnly);
+            if (values != null) return values;
+        } else {
+            Set<Integer> allValues = new HashSet<>();
+            for (String k : allKeys) {
+                IntStream values = getTagValuesForPrimitive(osm, k, directValuesOnly);
+                if (values != null) { values.forEach(allValues::add); }
+            }
+            if (!allValues.isEmpty()) return allValues.stream().mapToInt(it -> it).sorted();
+        }
+        return Boolean.TRUE.equals(PROP_AUTO_FILTER_DEFAULTS.get()) ? defaultValueSupplier.apply(osm) : IntStream.empty();
+    }
+
+    private IntStream getTagValuesForPrimitive(OsmPrimitive osm, String key, boolean directValuesOnly) {
         String value = osm.get(key);
         if (value != null) {
             Pattern p = Pattern.compile("(-?[0-9]+)-(-?[0-9]+)");
@@ -125,7 +177,11 @@ public class AutoFilterRule {
                 if (m.matches()) {
                     int a = valueExtractor.applyAsInt(m.group(1));
                     int b = valueExtractor.applyAsInt(m.group(2));
-                    return IntStream.rangeClosed(Math.min(a, b), Math.max(a, b));
+                    if (directValuesOnly && a != b) {
+                        return IntStream.of(a, b).sorted();
+                    } else {
+                        return IntStream.rangeClosed(Math.min(a, b), Math.max(a, b));
+                    }
                 } else {
                     try {
                         return IntStream.of(valueExtractor.applyAsInt(v));
@@ -136,7 +192,7 @@ public class AutoFilterRule {
                 }
             });
         }
-        return Boolean.TRUE.equals(PROP_AUTO_FILTER_DEFAULTS.get()) ? defaultValueSupplier.apply(osm) : IntStream.empty();
+        return null;
     }
 
     /**
@@ -156,6 +212,8 @@ public class AutoFilterRule {
             new AutoFilterRule("layer", 16)
                     .setDefaultValueSupplier(AutoFilterRule::defaultLayer),
             new AutoFilterRule("level", 17)
+                .setExtraKeys(List.of("repeat_on"))
+                .enableNoValueFilter()
                 // #17109, support values like 0.5 or 1.5 - level values are multiplied by 2 when parsing, values are divided by 2 for formatting
                 .setValueExtractor(s -> (int) (Double.parseDouble(s) * 2.))
                 .setValueFormatter(v -> DecimalFormat.getInstance(Locale.ROOT).format(v / 2.)),
diff --git a/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java b/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java
index 595dbd9a9c..fc8553f65e 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java
@@ -98,7 +98,7 @@ public class FilterTableModel extends AbstractTableModel implements SortableTabl
      * @since 14206
      */
     public void executeFilters(boolean force) {
-        if (AutoFilterManager.getInstance().getCurrentAutoFilter() == null && (force || model.hasFilters())) {
+        if (AutoFilterManager.getInstance().getCurrentCombinedFilter() == null && (force || model.hasFilters())) {
             model.executeFilters();
             updateMap();
         }
@@ -111,7 +111,7 @@ public class FilterTableModel extends AbstractTableModel implements SortableTabl
      * @since 14206
      */
     public void executeFilters(Collection<? extends OsmPrimitive> primitives, boolean force) {
-        if (AutoFilterManager.getInstance().getCurrentAutoFilter() == null && (force || model.hasFilters())) {
+        if (AutoFilterManager.getInstance().getCurrentCombinedFilter() == null && (force || model.hasFilters())) {
             model.executeFilters(primitives);
             updateMap();
         }
diff --git a/src/org/openstreetmap/josm/gui/preferences/display/DrawingPreference.java b/src/org/openstreetmap/josm/gui/preferences/display/DrawingPreference.java
index c75c5fcfe9..f9dd0bf99d 100644
--- a/src/org/openstreetmap/josm/gui/preferences/display/DrawingPreference.java
+++ b/src/org/openstreetmap/josm/gui/preferences/display/DrawingPreference.java
@@ -56,6 +56,8 @@ public class DrawingPreference extends DefaultTabPreferenceSetting {
     private final JCheckBox inactive = new JCheckBox(tr("Draw inactive layers in other color"));
     private final JCheckBox discardableKeys = new JCheckBox(tr("Display discardable keys"));
     private final JCheckBox autoFilters = new JCheckBox(tr("Use auto filters"));
+    private final JCheckBox autoFiltersHiding = new JCheckBox(tr("Hide auto-filtered elements"));
+
     private final JLabel lblRule = new JLabel(tr("Rule"));
     private final JosmComboBox<AutoFilterRule> autoFilterRules = new JosmComboBox<>(
             AutoFilterManager.getInstance().getAutoFilterRules().toArray(new AutoFilterRule[] {}));
@@ -149,9 +151,12 @@ public class DrawingPreference extends DefaultTabPreferenceSetting {
         autoFilters.addActionListener(e -> {
             lblRule.setEnabled(autoFilters.isSelected());
             autoFilterRules.setEnabled(autoFilters.isSelected());
+            autoFiltersHiding.setEnabled(autoFilters.isSelected());
         });
         autoFilterRules.setToolTipText("Rule defining which tag will provide automatic filters, below a certain zoom level");
         autoFilterRules.setSelectedItem(AutoFilterManager.getInstance().getAutoFilterRule(AutoFilterManager.PROP_AUTO_FILTER_RULE.get()));
+        autoFiltersHiding.setToolTipText(tr("Completely hide elements which do not match the automatic filter instead of merely disabling them"));
+        autoFiltersHiding.setSelected(AutoFilterManager.PROP_AUTO_FILTER_HIDING.get());
 
         JLabel performanceLabel = new JLabel(tr("Options that affect drawing performance"));
 
@@ -183,6 +188,7 @@ public class DrawingPreference extends DefaultTabPreferenceSetting {
         panel.add(autoFilters, GBC.eop().insets(20, 0, 0, 0));
         panel.add(lblRule, GBC.std().insets(40, 0, 0, 0));
         panel.add(autoFilterRules, GBC.eop().fill(GBC.HORIZONTAL).insets(5, 0, 0, 0));
+        panel.add(autoFiltersHiding, GBC.eop().insets(40, 0, 0, 0));
 
         ExpertToggleAction.addVisibilitySwitcher(performanceLabel);
         ExpertToggleAction.addVisibilitySwitcher(useAntialiasing);
@@ -213,6 +219,7 @@ public class DrawingPreference extends DefaultTabPreferenceSetting {
         Config.getPref().putBoolean("draw.helper-line", drawHelperLine.isSelected());
         Config.getPref().putBoolean("display.discardable-keys", discardableKeys.isSelected());
         AutoFilterManager.PROP_AUTO_FILTER_ENABLED.put(autoFilters.isSelected());
+        AutoFilterManager.PROP_AUTO_FILTER_HIDING.put(autoFiltersHiding.isSelected());
         AutoFilterManager.PROP_AUTO_FILTER_RULE.put(((AutoFilterRule) autoFilterRules.getSelectedItem()).getKey());
         int vn = Config.getPref().getInt("mappaint.node.virtual-size", 8);
         if (virtualNodes.isSelected()) {
diff --git a/test/unit/org/openstreetmap/josm/gui/autofilter/AutoFilterRuleTest.java b/test/unit/org/openstreetmap/josm/gui/autofilter/AutoFilterRuleTest.java
index d68b4bcaec..3d1914dcb6 100644
--- a/test/unit/org/openstreetmap/josm/gui/autofilter/AutoFilterRuleTest.java
+++ b/test/unit/org/openstreetmap/josm/gui/autofilter/AutoFilterRuleTest.java
@@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test;
 @I18n
 class AutoFilterRuleTest {
     /**
-     * Unit test of {@link AutoFilterRule#getTagValuesForPrimitive}.
+     * Unit test of {@link AutoFilterRule#getTagValuesForPrimitive(OsmPrimitive, boolean)}.
      */
     @Test
     void testTagValuesForPrimitive() {
@@ -36,12 +36,14 @@ class AutoFilterRuleTest {
         assertTagValuesForPrimitive(level, "way level=6-9", 12, 13, 14, 15, 16, 17, 18);
         assertTagValuesForPrimitive(level, "way level=10;12-13", 20, 24, 25, 26);
         assertTagValuesForPrimitive(level, "way level=0;0.5;1;1.5;2;2.5;3", 0, 1, 2, 3, 4, 5, 6);
+        assertTagValuesForPrimitive(level, "node level=0 repeat_on=1;2", 0, 2, 4);
+        assertTagValuesForPrimitive(level, "way level=4 repeat_on=4;5 layer=1", 8, 10);
         assertEquals("0 0.5 1 1.5 2 2.5 3",
                 IntStream.of(0, 1, 2, 3, 4, 5, 6).mapToObj(level::formatValue).collect(Collectors.joining(" ")));
     }
 
     /**
-     * Unit test of {@link AutoFilterRule#getTagValuesForPrimitive} to deal with {@code %} of key {@code incline}.
+     * Unit test of {@link AutoFilterRule#getTagValuesForPrimitive(OsmPrimitive, boolean)} to deal with {@code %} of key {@code incline}.
      */
     @Test
     void testTagValuesForPrimitiveInclineUnit() {
@@ -52,7 +54,7 @@ class AutoFilterRuleTest {
     }
 
     /**
-     * Unit test of {@link AutoFilterRule#getTagValuesForPrimitive} provides sensible defaults, see #17496.
+     * Unit test of {@link AutoFilterRule#getTagValuesForPrimitive(OsmPrimitive, boolean)} provides sensible defaults, see #17496.
      */
     @Test
     void testTagValuesForPrimitivesDefaults() {
@@ -69,7 +71,7 @@ class AutoFilterRuleTest {
 
     private void assertTagValuesForPrimitive(AutoFilterRule rule, String assertion, int... expected) {
         final OsmPrimitive primitive = OsmUtils.createPrimitive(assertion);
-        final int[] actual = rule.getTagValuesForPrimitive(primitive).toArray();
+        final int[] actual = rule.getTagValuesForPrimitive(primitive, false).toArray();
         assertArrayEquals(expected, actual);
     }
 
