Ignore:
Timestamp:
2020-02-10T23:58:31+01:00 (4 years ago)
Author:
simon04
Message:

fix #18679 - Autofilter: extract numeric value from number+unit

Location:
trunk/src/org/openstreetmap/josm/gui/autofilter
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterManager.java

    r15818 r15838  
    88import java.util.Arrays;
    99import java.util.Collection;
    10 import java.util.Comparator;
    11 import java.util.Iterator;
     10import java.util.Collections;
    1211import java.util.List;
    1312import java.util.Map;
    1413import java.util.NavigableSet;
    1514import java.util.Objects;
    16 import java.util.Set;
    1715import java.util.TreeMap;
    1816import java.util.TreeSet;
     
    2119import java.util.regex.Pattern;
    2220import java.util.stream.IntStream;
    23 import java.util.stream.Stream;
    2421
    2522import org.openstreetmap.josm.actions.mapmode.MapMode;
     
    9491     * The buttons currently displayed in map view.
    9592     */
    96     private final Map<String, AutoFilterButton> buttons = new TreeMap<>();
     93    private final Map<Integer, AutoFilterButton> buttons = new TreeMap<>();
    9794
    9895    /**
     
    146143                && enabledRule.getMinZoomLevel() <= Selector.GeneralSelector.scale2level(map.mapView.getDist100Pixel())) {
    147144            // Retrieve the values from current rule visible on screen
    148             NavigableSet<String> values = getNumericValues(enabledRule.getKey(), enabledRule.getValueComparator());
     145            NavigableSet<Integer> values = getNumericValues(enabledRule.getKey());
    149146            // Make sure current auto filter button remains visible even if no data is found, to allow user to disable it
    150147            if (currentAutoFilter != null) {
     
    158155    }
    159156
    160     static class CompiledFilter extends Filter implements MatchSupplier {
     157    class CompiledFilter extends Filter implements MatchSupplier {
    161158        final String key;
    162         final String value;
    163 
    164         CompiledFilter(String key, String value) {
     159        final int value;
     160
     161        CompiledFilter(String key, int value) {
    165162            this.key = key;
    166163            this.value = value;
     
    175172                @Override
    176173                public boolean match(OsmPrimitive osm) {
    177                     return getTagValuesForPrimitive(key, osm).anyMatch(value::equals);
     174                    return getTagValuesForPrimitive(key, osm).anyMatch(v -> v == value);
    178175                }
    179176            };
     
    181178    }
    182179
    183     private synchronized void addNewButtons(NavigableSet<String> values) {
     180    private synchronized void addNewButtons(NavigableSet<Integer> values) {
    184181        if (values.isEmpty()) {
    185182            return;
     
    188185        int maxWidth = 16;
    189186        final AutoFilterButton keyButton = AutoFilterButton.forOsmKey(enabledRule.getKey());
    190         addButton(keyButton, Integer.toString(Integer.MIN_VALUE), i++);
    191         for (final String value : values.descendingSet()) {
     187        addButton(keyButton, Integer.MIN_VALUE, i++);
     188        for (final Integer value : values.descendingSet()) {
    192189            CompiledFilter filter = new CompiledFilter(enabledRule.getKey(), value);
    193190            String label = enabledRule.getValueFormatter().apply(value);
     
    206203    }
    207204
    208     private void addButton(AutoFilterButton button, String value, int i) {
     205    private void addButton(AutoFilterButton button, int value, int i) {
    209206        MapView mapView = MainApplication.getMap().mapView;
    210207        buttons.put(value, button);
     
    213210
    214211    private void removeAllButtons() {
    215         for (Iterator<String> it = buttons.keySet().iterator(); it.hasNext();) {
    216             MainApplication.getMap().mapView.remove(buttons.get(it.next()));
    217             it.remove();
    218         }
    219     }
    220 
    221     private static NavigableSet<String> getNumericValues(String key, Comparator<String> comparator) {
    222         NavigableSet<String> values = new TreeSet<>(comparator);
    223         for (String s : getTagValues(key)) {
    224             try {
    225                 Integer.parseInt(s);
    226                 values.add(s);
    227             } catch (NumberFormatException e) {
    228                 Logging.trace(e);
    229             }
    230         }
     212        buttons.values().forEach(MainApplication.getMap().mapView::remove);
     213        buttons.clear();
     214    }
     215
     216    private NavigableSet<Integer> getNumericValues(String key) {
     217        DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
     218        if (ds == null) {
     219            return Collections.emptyNavigableSet();
     220        }
     221        BBox bbox = MainApplication.getMap().mapView.getState().getViewArea().getLatLonBoundsBox().toBBox();
     222        NavigableSet<Integer> values = new TreeSet<>();
     223        Consumer<OsmPrimitive> consumer = o -> getTagValuesForPrimitive(key, o).forEach(values::add);
     224        ds.searchNodes(bbox).forEach(consumer);
     225        ds.searchWays(bbox).forEach(consumer);
     226        ds.searchRelations(bbox).forEach(consumer);
    231227        return values;
    232228    }
    233229
    234     private static Set<String> getTagValues(String key) {
    235         DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
    236         Set<String> values = new TreeSet<>();
    237         if (ds != null) {
    238             BBox bbox = MainApplication.getMap().mapView.getState().getViewArea().getLatLonBoundsBox().toBBox();
    239             Consumer<OsmPrimitive> consumer = o -> getTagValuesForPrimitive(key, o).forEach(values::add);
    240             ds.searchNodes(bbox).forEach(consumer);
    241             ds.searchWays(bbox).forEach(consumer);
    242             ds.searchRelations(bbox).forEach(consumer);
    243         }
    244         return values;
    245     }
    246 
    247     static Stream<String> getTagValuesForPrimitive(String key, OsmPrimitive osm) {
     230    protected IntStream getTagValuesForPrimitive(String key, OsmPrimitive osm) {
     231        if (enabledRule == null) {
     232            return IntStream.empty();
     233        }
    248234        String value = osm.get(key);
    249235        if (value != null) {
    250236            Pattern p = Pattern.compile("(-?[0-9]+)-(-?[0-9]+)");
    251             return OsmUtils.splitMultipleValues(value).flatMap(v -> {
     237            return OsmUtils.splitMultipleValues(value).flatMapToInt(v -> {
    252238                Matcher m = p.matcher(v);
    253239                if (m.matches()) {
    254240                    int a = Integer.parseInt(m.group(1));
    255241                    int b = Integer.parseInt(m.group(2));
    256                     return IntStream.rangeClosed(Math.min(a, b), Math.max(a, b))
    257                             .mapToObj(Integer::toString);
     242                    return IntStream.rangeClosed(Math.min(a, b), Math.max(a, b));
    258243                } else {
    259                     return Stream.of(v);
     244                    try {
     245                        return IntStream.of(enabledRule.getValueExtractor().applyAsInt(v));
     246                    } catch (NumberFormatException e) {
     247                        Logging.trace(e);
     248                        return IntStream.empty();
     249                    }
    260250                }
    261251            });
    262         } else if (PROP_AUTO_FILTER_DEFAULTS.get() && "layer".equals(key)) {
    263             // assume sensible defaults, see #17496
    264             if (osm.hasTag("bridge") || osm.hasTag("power", "line") || osm.hasTag("location", "overhead")) {
    265                 return Stream.of("1");
    266             } else if (osm.isKeyTrue("tunnel") || osm.hasTag("tunnel", "culvert") || osm.hasTag("location", "underground")) {
    267                 return Stream.of("-1");
    268             } else if (osm.hasTag("tunnel", "building_passage") || osm.hasKey("highway", "railway", "waterway")) {
    269                 return Stream.of("0");
    270             }
    271         }
    272         return Stream.empty();
     252        }
     253        return enabledRule.getDefaultValueSupplier().apply(osm);
    273254    }
    274255
  • trunk/src/org/openstreetmap/josm/gui/autofilter/AutoFilterRule.java

    r15293 r15838  
    22package org.openstreetmap.josm.gui.autofilter;
    33
    4 import java.util.Comparator;
     4import java.util.Arrays;
    55import java.util.Objects;
     6import java.util.Optional;
    67import java.util.function.Function;
    7 import java.util.function.UnaryOperator;
     8import java.util.function.IntFunction;
     9import java.util.function.ToIntFunction;
     10import java.util.stream.IntStream;
     11
     12import org.openstreetmap.josm.data.osm.OsmPrimitive;
    813
    914/**
     
    2025    private final int minZoomLevel;
    2126
    22     private UnaryOperator<String> valueFormatter = s -> s;
     27    private Function<OsmPrimitive, IntStream> defaultValueSupplier = p -> IntStream.empty();
    2328
    24     private Comparator<String> valueComparator = Comparator.comparingInt(s -> Integer.parseInt(valueFormatter.apply(s)));
     29    private ToIntFunction<String> valueExtractor = Integer::parseInt;
     30
     31    private IntFunction<String> valueFormatter = Integer::toString;
    2532
    2633    /**
     
    5461     * @return the OSM value formatter that defines the associated button label (identity by default)
    5562     */
    56     public Function<String, String> getValueFormatter() {
     63    public IntFunction<String> getValueFormatter() {
    5764        return valueFormatter;
    5865    }
     
    6471     * @throws NullPointerException if {@code valueFormatter} is null
    6572     */
    66     public AutoFilterRule setValueFormatter(UnaryOperator<String> valueFormatter) {
     73    public AutoFilterRule setValueFormatter(IntFunction<String> valueFormatter) {
    6774        this.valueFormatter = Objects.requireNonNull(valueFormatter);
    6875        return this;
     
    7077
    7178    /**
    72      * Returns the OSM value comparator used to order the buttons.
    73      * @return the OSM value comparator
     79     * Returns a function which yields default values for the given OSM primitive
     80     * @return a function which yields default values for the given OSM primitive
    7481     */
    75     public Comparator<String> getValueComparator() {
    76         return valueComparator;
     82    public Function<OsmPrimitive, IntStream> getDefaultValueSupplier() {
     83        return defaultValueSupplier;
    7784    }
    7885
    7986    /**
    80      * Sets the OSM value comparator used to order the buttons.
    81      * @param valueComparator the OSM value comparator
     87     * Sets the function which yields default values for the given OSM primitive.
     88     * This function is invoked if the primitive does not have this {@linkplain #getKey() key}.
     89     * @param defaultValueSupplier the function which yields default values for the given OSM primitive
    8290     * @return {@code this}
    83      * @throws NullPointerException if {@code valueComparator} is null
     91     * @throws NullPointerException if {@code defaultValueSupplier} is null
    8492     */
    85     public AutoFilterRule setValueComparator(Comparator<String> valueComparator) {
    86         this.valueComparator = valueComparator;
     93    public AutoFilterRule setDefaultValueSupplier(Function<OsmPrimitive, IntStream> defaultValueSupplier) {
     94        this.defaultValueSupplier = Objects.requireNonNull(defaultValueSupplier);
     95        return this;
     96    }
     97
     98    /**
     99     * Returns a function which extracts a numeric value from an OSM value
     100     * @return a function which extracts a numeric value from an OSM value
     101     */
     102    public ToIntFunction<String> getValueExtractor() {
     103        return valueExtractor;
     104    }
     105
     106    /**
     107     * Sets the function which extracts a numeric value from an OSM value
     108     * @param valueExtractor the function which extracts a numeric value from an OSM value
     109     * @return {@code this}
     110     * @throws NullPointerException if {@code valueExtractor} is null
     111     */
     112    public AutoFilterRule setValueExtractor(ToIntFunction<String> valueExtractor) {
     113        this.valueExtractor = Objects.requireNonNull(valueExtractor);
    87114        return this;
    88115    }
     
    93120     */
    94121    public static AutoFilterRule[] defaultRules() {
    95         return new AutoFilterRule[] {
     122        return new AutoFilterRule[]{
    96123            new AutoFilterRule("level", 17),
    97             new AutoFilterRule("layer", 16),
     124            new AutoFilterRule("layer", 16)
     125                    .setDefaultValueSupplier(AutoFilterRule::defaultLayer),
    98126            new AutoFilterRule("maxspeed", 16)
    99                 .setValueFormatter(s -> s.replace(" mph", "")),
     127                    .setValueExtractor(s -> Integer.parseInt(s.replace(" mph", ""))),
    100128            new AutoFilterRule("voltage", 5)
    101                 .setValueFormatter(s -> s.replaceAll("000$", "k") + 'V')
    102                 .setValueComparator(Comparator.comparingInt(Integer::parseInt))
     129                    .setValueFormatter(s -> s % 1000 == 0 ? (s / 1000) + "kV" : s + "V"),
     130            new AutoFilterRule("building:levels", 17),
     131            new AutoFilterRule("gauge", 5),
     132            new AutoFilterRule("frequency", 5),
     133            new AutoFilterRule("incline", 13)
     134                    .setValueExtractor(s -> Integer.parseInt(s.replaceAll("%$", "")))
     135                    .setValueFormatter(v -> v + "\u2009%"),
     136            new AutoFilterRule("lanes", 13),
     137            new AutoFilterRule("admin_level", 11)
    103138        };
     139    }
     140
     141    /**
     142     * Returns the default auto filter rule for the given key
     143     * @param key the OSM key
     144     * @return default auto filter rule for the given key
     145     */
     146    static Optional<AutoFilterRule> getDefaultRule(String key) {
     147        return Arrays.stream(AutoFilterRule.defaultRules())
     148                .filter(r -> key.equals(r.getKey()))
     149                .findFirst();
     150    }
     151
     152    private static IntStream defaultLayer(OsmPrimitive osm) {
     153        // assume sensible defaults, see #17496
     154        if (osm.hasTag("bridge") || osm.hasTag("power", "line") || osm.hasTag("location", "overhead")) {
     155            return IntStream.of(1);
     156        } else if (osm.isKeyTrue("tunnel") || osm.hasTag("tunnel", "culvert") || osm.hasTag("location", "underground")) {
     157            return IntStream.of(-1);
     158        } else if (osm.hasTag("tunnel", "building_passage") || osm.hasKey("highway", "railway", "waterway")) {
     159            return IntStream.of(0);
     160        } else {
     161            return IntStream.empty();
     162        }
    104163    }
    105164
Note: See TracChangeset for help on using the changeset viewer.