source: josm/trunk/src/org/openstreetmap/josm/data/osm/FilterMatcher.java@ 12867

Last change on this file since 12867 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

  • Property svn:eol-style set to native
File size: 12.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import java.util.ArrayList;
5import java.util.Collection;
6import java.util.List;
7
8import org.openstreetmap.josm.data.osm.search.SearchCompiler;
9import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
10import org.openstreetmap.josm.data.osm.search.SearchCompiler.Not;
11import org.openstreetmap.josm.data.osm.search.SearchMode;
12import org.openstreetmap.josm.data.osm.search.SearchParseError;
13import org.openstreetmap.josm.gui.MainApplication;
14import org.openstreetmap.josm.gui.MapFrame;
15import org.openstreetmap.josm.tools.SubclassFilteredCollection;
16
17/**
18 * Class that encapsulates the filter logic, i.e. applies a list of
19 * filters to a primitive.
20 *
21 * Uses {@link Match#match} to see if the filter expression matches,
22 * cares for "inverted-flag" of the filters and combines the results of all active
23 * filters.
24 *
25 * There are two major use cases:
26 *
27 * (1) Hide features that you don't like to edit but get in the way, e.g.
28 * <code>landuse</code> or power lines. It is expected, that the inverted flag
29 * if false for these kind of filters.
30 *
31 * (2) Highlight certain features, that are currently interesting and hide everything
32 * else. This can be thought of as an improved search (Ctrl-F), where you can
33 * continue editing and don't loose the current selection. It is expected that
34 * the inverted flag of the filter is true in this case.
35 *
36 * In addition to the formal application of filter rules, some magic is applied
37 * to (hopefully) match the expectations of the user:
38 *
39 * (1) non-inverted: When hiding a way, all its untagged nodes are hidden as well.
40 * This avoids a "cloud of nodes", that normally isn't useful without the
41 * corresponding way.
42 *
43 * (2) inverted: When displaying a way, we show all its nodes, although the
44 * individual nodes do not match the filter expression. The reason is, that a
45 * way without its nodes cannot be edited properly.
46 *
47 * Multipolygons and (untagged) member ways are handled in a similar way.
48 */
49public class FilterMatcher {
50
51 /**
52 * Describes quality of the filtering.
53 *
54 * Depending on the context, this can either refer to disabled or
55 * to hidden primitives.
56 *
57 * The distinction is necessary, because untagged nodes should only
58 * "inherit" their filter property from the parent way, when the
59 * parent way is hidden (or disabled) "explicitly" (i.e. by a non-inverted
60 * filter). This way, filters like
61 * <code>["child type:way", inverted, Add]</code> show the
62 * untagged way nodes, as intended.
63 *
64 * This information is only needed for ways and relations, so nodes are
65 * either <code>NOT_FILTERED</code> or <code>PASSIV</code>.
66 */
67 public enum FilterType {
68 /** no filter applies */
69 NOT_FILTERED,
70 /** at least one non-inverted filter applies */
71 EXPLICIT,
72 /** at least one filter applies, but they are all inverted filters */
73 PASSIV
74 }
75
76 private static class FilterInfo {
77 private final Match match;
78 private final boolean isDelete;
79 private final boolean isInverted;
80
81 FilterInfo(Filter filter) throws SearchParseError {
82 if (filter.mode == SearchMode.remove || filter.mode == SearchMode.in_selection) {
83 isDelete = true;
84 } else {
85 isDelete = false;
86 }
87
88 Match compiled = SearchCompiler.compile(filter);
89 this.match = filter.inverted ? new Not(compiled) : compiled;
90 this.isInverted = filter.inverted;
91 }
92 }
93
94 private final List<FilterInfo> hiddenFilters = new ArrayList<>();
95 private final List<FilterInfo> disabledFilters = new ArrayList<>();
96
97 /**
98 * Clears the current filters, and adds the given filters
99 * @param filters the filters to add
100 * @throws SearchParseError if the search expression in one of the filters cannot be parsed
101 */
102 public void update(Collection<Filter> filters) throws SearchParseError {
103 reset();
104 for (Filter filter : filters) {
105 add(filter);
106 }
107 }
108
109 /**
110 * Clears the filters in use.
111 */
112 public void reset() {
113 hiddenFilters.clear();
114 disabledFilters.clear();
115 }
116
117 /**
118 * Adds a filter to the currently used filters
119 * @param filter the filter to add
120 * @throws SearchParseError if the search expression in the filter cannot be parsed
121 */
122 public void add(final Filter filter) throws SearchParseError {
123 if (!filter.enable) {
124 return;
125 }
126
127 FilterInfo fi = new FilterInfo(filter);
128 if (fi.isDelete) {
129 if (filter.hiding) {
130 // Remove only hide flag
131 hiddenFilters.add(fi);
132 } else {
133 // Remove both flags
134 disabledFilters.add(fi);
135 hiddenFilters.add(fi);
136 }
137 } else {
138 if (filter.mode == SearchMode.replace && filter.hiding) {
139 hiddenFilters.clear();
140 disabledFilters.clear();
141 }
142
143 disabledFilters.add(fi);
144 if (filter.hiding) {
145 hiddenFilters.add(fi);
146 }
147 }
148 }
149
150 /**
151 * Check if primitive is filtered.
152 * @param primitive the primitive to check
153 * @param hidden the minimum level required for the primitive to count as filtered
154 * @return when hidden is true, returns whether the primitive is hidden
155 * when hidden is false, returns whether the primitive is disabled or hidden
156 */
157 private static boolean isFiltered(OsmPrimitive primitive, boolean hidden) {
158 return hidden ? primitive.isDisabledAndHidden() : primitive.isDisabled();
159 }
160
161 /**
162 * Check if primitive is hidden explicitly.
163 * Only used for ways and relations.
164 * @param primitive the primitive to check
165 * @param hidden the level where the check is performed
166 * @return true, if at least one non-inverted filter applies to the primitive
167 */
168 private static boolean isFilterExplicit(OsmPrimitive primitive, boolean hidden) {
169 return hidden ? primitive.getHiddenType() : primitive.getDisabledType();
170 }
171
172 /**
173 * Check if all parent ways are filtered.
174 * @param primitive the primitive to check
175 * @param hidden parameter that indicates the minimum level of filtering:
176 * true when objects need to be hidden to count as filtered and
177 * false when it suffices to be disabled to count as filtered
178 * @return true if (a) there is at least one parent way
179 * (b) all parent ways are filtered at least at the level indicated by the
180 * parameter <code>hidden</code> and
181 * (c) at least one of the parent ways is explicitly filtered
182 */
183 private static boolean allParentWaysFiltered(OsmPrimitive primitive, boolean hidden) {
184 List<OsmPrimitive> refs = primitive.getReferrers();
185 boolean isExplicit = false;
186 for (OsmPrimitive p: refs) {
187 if (p instanceof Way) {
188 if (!isFiltered(p, hidden))
189 return false;
190 isExplicit |= isFilterExplicit(p, hidden);
191 }
192 }
193 return isExplicit;
194 }
195
196 private static boolean oneParentWayNotFiltered(OsmPrimitive primitive, boolean hidden) {
197 List<OsmPrimitive> refs = primitive.getReferrers();
198 for (OsmPrimitive p: refs) {
199 if (p instanceof Way && !isFiltered(p, hidden))
200 return true;
201 }
202
203 return false;
204 }
205
206 private static boolean allParentMultipolygonsFiltered(OsmPrimitive primitive, boolean hidden) {
207 boolean isExplicit = false;
208 for (Relation r : new SubclassFilteredCollection<OsmPrimitive, Relation>(
209 primitive.getReferrers(), OsmPrimitive::isMultipolygon)) {
210 if (!isFiltered(r, hidden))
211 return false;
212 isExplicit |= isFilterExplicit(r, hidden);
213 }
214 return isExplicit;
215 }
216
217 private static boolean oneParentMultipolygonNotFiltered(OsmPrimitive primitive, boolean hidden) {
218 for (Relation r : new SubclassFilteredCollection<OsmPrimitive, Relation>(
219 primitive.getReferrers(), OsmPrimitive::isMultipolygon)) {
220 if (!isFiltered(r, hidden))
221 return true;
222 }
223 return false;
224 }
225
226 private static FilterType test(List<FilterInfo> filters, OsmPrimitive primitive, boolean hidden) {
227 MapFrame map = MainApplication.getMap();
228 if (primitive.isIncomplete() ||
229 (map != null && map.mapMode != null && map.mapMode.getPreservedPrimitives().contains(primitive)))
230 return FilterType.NOT_FILTERED;
231
232 boolean filtered = false;
233 // If the primitive is "explicitly" hidden by a non-inverted filter.
234 // Only interesting for nodes.
235 boolean explicitlyFiltered = false;
236
237 for (FilterInfo fi: filters) {
238 if (fi.isDelete) {
239 if (filtered && fi.match.match(primitive)) {
240 filtered = false;
241 }
242 } else {
243 if ((!filtered || (!explicitlyFiltered && !fi.isInverted)) && fi.match.match(primitive)) {
244 filtered = true;
245 if (!fi.isInverted) {
246 explicitlyFiltered = true;
247 }
248 }
249 }
250 }
251
252 if (primitive instanceof Node) {
253 if (filtered) {
254 // If there is a parent way, that is not hidden, we show the
255 // node anyway, unless there is no non-inverted filter that
256 // applies to the node directly.
257 if (explicitlyFiltered)
258 return FilterType.PASSIV;
259 else {
260 if (oneParentWayNotFiltered(primitive, hidden))
261 return FilterType.NOT_FILTERED;
262 else
263 return FilterType.PASSIV;
264 }
265 } else {
266 if (!primitive.isTagged() && allParentWaysFiltered(primitive, hidden))
267 // Technically not hidden by any filter, but we hide it anyway, if
268 // it is untagged and all parent ways are hidden.
269 return FilterType.PASSIV;
270 else
271 return FilterType.NOT_FILTERED;
272 }
273 } else if (primitive instanceof Way) {
274 if (filtered) {
275 if (explicitlyFiltered)
276 return FilterType.EXPLICIT;
277 else {
278 if (oneParentMultipolygonNotFiltered(primitive, hidden))
279 return FilterType.NOT_FILTERED;
280 else
281 return FilterType.PASSIV;
282 }
283 } else {
284 if (!primitive.isTagged() && allParentMultipolygonsFiltered(primitive, hidden))
285 return FilterType.EXPLICIT;
286 else
287 return FilterType.NOT_FILTERED;
288 }
289 } else {
290 if (filtered)
291 return explicitlyFiltered ? FilterType.EXPLICIT : FilterType.PASSIV;
292 else
293 return FilterType.NOT_FILTERED;
294 }
295
296 }
297
298 /**
299 * Check if primitive is hidden.
300 * The filter flags for all parent objects must be set correctly, when
301 * calling this method.
302 * @param primitive the primitive
303 * @return FilterType.NOT_FILTERED when primitive is not hidden;
304 * FilterType.EXPLICIT when primitive is hidden and there is a non-inverted
305 * filter that applies;
306 * FilterType.PASSIV when primitive is hidden and all filters that apply
307 * are inverted
308 */
309 public FilterType isHidden(OsmPrimitive primitive) {
310 return test(hiddenFilters, primitive, true);
311 }
312
313 /**
314 * Check if primitive is disabled.
315 * The filter flags for all parent objects must be set correctly, when
316 * calling this method.
317 * @param primitive the primitive
318 * @return FilterType.NOT_FILTERED when primitive is not disabled;
319 * FilterType.EXPLICIT when primitive is disabled and there is a non-inverted
320 * filter that applies;
321 * FilterType.PASSIV when primitive is disabled and all filters that apply
322 * are inverted
323 */
324 public FilterType isDisabled(OsmPrimitive primitive) {
325 return test(disabledFilters, primitive, false);
326 }
327
328 /**
329 * Returns a new {@code FilterMatcher} containing the given filters.
330 * @param filters filters to add to the resulting filter matcher
331 * @return a new {@code FilterMatcher} containing the given filters
332 * @throws SearchParseError if the search expression in a filter cannot be parsed
333 * @since 12383
334 */
335 public static FilterMatcher of(Filter... filters) throws SearchParseError {
336 FilterMatcher result = new FilterMatcher();
337 for (Filter filter : filters) {
338 result.add(filter);
339 }
340 return result;
341 }
342}
Note: See TracBrowser for help on using the repository browser.