source: josm/trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java@ 10308

Last change on this file since 10308 was 10040, checked in by Don-vip, 8 years ago

fix #12680 - Documentation and cleanup on predicates (patch by michael2402, modified)

  • Property svn:eol-style set to native
File size: 50.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.text.MessageFormat;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.Date;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.LinkedHashSet;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Locale;
18import java.util.Map;
19import java.util.Objects;
20import java.util.Set;
21
22import org.openstreetmap.josm.Main;
23import org.openstreetmap.josm.actions.search.SearchCompiler;
24import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
25import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
26import org.openstreetmap.josm.data.osm.visitor.Visitor;
27import org.openstreetmap.josm.gui.mappaint.StyleCache;
28import org.openstreetmap.josm.tools.CheckParameterUtil;
29import org.openstreetmap.josm.tools.Predicate;
30import org.openstreetmap.josm.tools.Predicates;
31import org.openstreetmap.josm.tools.Utils;
32import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
33
34/**
35 * The base class for OSM objects ({@link Node}, {@link Way}, {@link Relation}).
36 *
37 * It can be created, deleted and uploaded to the OSM-Server.
38 *
39 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
40 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
41 * by the server environment and not an extendible data stuff.
42 *
43 * @author imi
44 */
45public abstract class OsmPrimitive extends AbstractPrimitive implements Comparable<OsmPrimitive>, TemplateEngineDataProvider {
46 private static final String SPECIAL_VALUE_ID = "id";
47 private static final String SPECIAL_VALUE_LOCAL_NAME = "localname";
48
49 /**
50 * An object can be disabled by the filter mechanism.
51 * Then it will show in a shade of gray on the map or it is completely
52 * hidden from the view.
53 * Disabled objects usually cannot be selected or modified
54 * while the filter is active.
55 */
56 protected static final int FLAG_DISABLED = 1 << 4;
57
58 /**
59 * This flag is only relevant if an object is disabled by the
60 * filter mechanism (i.e.&nbsp;FLAG_DISABLED is set).
61 * Then it indicates, whether it is completely hidden or
62 * just shown in gray color.
63 *
64 * When the primitive is not disabled, this flag should be
65 * unset as well (for efficient access).
66 */
67 protected static final int FLAG_HIDE_IF_DISABLED = 1 << 5;
68
69 /**
70 * Flag used internally by the filter mechanism.
71 */
72 protected static final int FLAG_DISABLED_TYPE = 1 << 6;
73
74 /**
75 * Flag used internally by the filter mechanism.
76 */
77 protected static final int FLAG_HIDDEN_TYPE = 1 << 7;
78
79 /**
80 * This flag is set if the primitive is a way and
81 * according to the tags, the direction of the way is important.
82 * (e.g. one way street.)
83 */
84 protected static final int FLAG_HAS_DIRECTIONS = 1 << 8;
85
86 /**
87 * If the primitive is tagged.
88 * Some trivial tags like source=* are ignored here.
89 */
90 protected static final int FLAG_TAGGED = 1 << 9;
91
92 /**
93 * This flag is only relevant if FLAG_HAS_DIRECTIONS is set.
94 * It shows, that direction of the arrows should be reversed.
95 * (E.g. oneway=-1.)
96 */
97 protected static final int FLAG_DIRECTION_REVERSED = 1 << 10;
98
99 /**
100 * When hovering over ways and nodes in add mode, the
101 * "target" objects are visually highlighted. This flag indicates
102 * that the primitive is currently highlighted.
103 */
104 protected static final int FLAG_HIGHLIGHTED = 1 << 11;
105
106 /**
107 * If the primitive is annotated with a tag such as note, fixme, etc.
108 * Match the "work in progress" tags in default map style.
109 */
110 protected static final int FLAG_ANNOTATED = 1 << 12;
111
112 /**
113 * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in
114 * another collection of {@link OsmPrimitive}s. The result collection is a list.
115 *
116 * If <code>list</code> is null, replies an empty list.
117 *
118 * @param <T> type of data (must be one of the {@link OsmPrimitive} types
119 * @param list the original list
120 * @param type the type to filter for
121 * @return the sub-list of OSM primitives of type <code>type</code>
122 */
123 public static <T extends OsmPrimitive> List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) {
124 if (list == null) return Collections.emptyList();
125 List<T> ret = new LinkedList<>();
126 for (OsmPrimitive p: list) {
127 if (type.isInstance(p)) {
128 ret.add(type.cast(p));
129 }
130 }
131 return ret;
132 }
133
134 /**
135 * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in
136 * another collection of {@link OsmPrimitive}s. The result collection is a set.
137 *
138 * If <code>list</code> is null, replies an empty set.
139 *
140 * @param <T> type of data (must be one of the {@link OsmPrimitive} types
141 * @param set the original collection
142 * @param type the type to filter for
143 * @return the sub-set of OSM primitives of type <code>type</code>
144 */
145 public static <T extends OsmPrimitive> Set<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) {
146 Set<T> ret = new LinkedHashSet<>();
147 if (set != null) {
148 for (OsmPrimitive p: set) {
149 if (type.isInstance(p)) {
150 ret.add(type.cast(p));
151 }
152 }
153 }
154 return ret;
155 }
156
157 /**
158 * Replies the collection of referring primitives for the primitives in <code>primitives</code>.
159 *
160 * @param primitives the collection of primitives.
161 * @return the collection of referring primitives for the primitives in <code>primitives</code>;
162 * empty set if primitives is null or if there are no referring primitives
163 */
164 public static Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) {
165 Set<OsmPrimitive> ret = new HashSet<>();
166 if (primitives == null || primitives.isEmpty()) return ret;
167 for (OsmPrimitive p: primitives) {
168 ret.addAll(p.getReferrers());
169 }
170 return ret;
171 }
172
173 /**
174 * A predicate that filters primitives that are usable.
175 * @see OsmPrimitive#isUsable()
176 */
177 public static final Predicate<OsmPrimitive> isUsablePredicate = new Predicate<OsmPrimitive>() {
178 @Override
179 public boolean evaluate(OsmPrimitive primitive) {
180 return primitive.isUsable();
181 }
182 };
183
184 /**
185 * A predicate filtering primitives that are selectable.
186 */
187 public static final Predicate<OsmPrimitive> isSelectablePredicate = new Predicate<OsmPrimitive>() {
188 @Override
189 public boolean evaluate(OsmPrimitive primitive) {
190 return primitive.isSelectable();
191 }
192 };
193
194 /**
195 * A predicate filtering primitives that are not deleted.
196 */
197 public static final Predicate<OsmPrimitive> nonDeletedPredicate = new Predicate<OsmPrimitive>() {
198 @Override public boolean evaluate(OsmPrimitive primitive) {
199 return !primitive.isDeleted();
200 }
201 };
202
203 /**
204 * A predicate filtering primitives that are not deleted and not incomplete.
205 */
206 public static final Predicate<OsmPrimitive> nonDeletedCompletePredicate = new Predicate<OsmPrimitive>() {
207 @Override public boolean evaluate(OsmPrimitive primitive) {
208 return !primitive.isDeleted() && !primitive.isIncomplete();
209 }
210 };
211
212 /**
213 * A predicate filtering primitives that are not deleted and not incomplete and that are not a relation.
214 */
215 public static final Predicate<OsmPrimitive> nonDeletedPhysicalPredicate = new Predicate<OsmPrimitive>() {
216 @Override public boolean evaluate(OsmPrimitive primitive) {
217 return !primitive.isDeleted() && !primitive.isIncomplete() && !(primitive instanceof Relation);
218 }
219 };
220
221 /**
222 * A predicate filtering primitives that are modified
223 */
224 public static final Predicate<OsmPrimitive> modifiedPredicate = new Predicate<OsmPrimitive>() {
225 @Override public boolean evaluate(OsmPrimitive primitive) {
226 return primitive.isModified();
227 }
228 };
229
230 /**
231 * A predicate filtering nodes.
232 */
233 public static final Predicate<OsmPrimitive> nodePredicate = Predicates.<OsmPrimitive>isOfClass(Node.class);
234
235 /**
236 * A predicate filtering ways.
237 */
238 public static final Predicate<OsmPrimitive> wayPredicate = Predicates.<OsmPrimitive>isOfClass(Way.class);
239
240 /**
241 * A predicate filtering relations.
242 */
243 public static final Predicate<OsmPrimitive> relationPredicate = Predicates.<OsmPrimitive>isOfClass(Relation.class);
244
245 /**
246 * A predicate filtering multipolygon relations.
247 */
248 public static final Predicate<OsmPrimitive> multipolygonPredicate = new Predicate<OsmPrimitive>() {
249 @Override public boolean evaluate(OsmPrimitive primitive) {
250 return primitive.getClass() == Relation.class && ((Relation) primitive).isMultipolygon();
251 }
252 };
253
254 /**
255 * This matches all ways that have a direction
256 *
257 * @see #FLAG_HAS_DIRECTIONS
258 */
259 public static final Predicate<Tag> directionalKeyPredicate = new Predicate<Tag>() {
260 @Override
261 public boolean evaluate(Tag tag) {
262 return directionKeys.match(tag);
263 }
264 };
265
266 /**
267 * Creates a new primitive for the given id.
268 *
269 * If allowNegativeId is set, provided id can be &lt; 0 and will be set to primitive without any processing.
270 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
271 * positive number.
272 *
273 * @param id the id
274 * @param allowNegativeId {@code true} to allow negative id
275 * @throws IllegalArgumentException if id &lt; 0 and allowNegativeId is false
276 */
277 protected OsmPrimitive(long id, boolean allowNegativeId) {
278 if (allowNegativeId) {
279 this.id = id;
280 } else {
281 if (id < 0)
282 throw new IllegalArgumentException(MessageFormat.format("Expected ID >= 0. Got {0}.", id));
283 else if (id == 0) {
284 this.id = generateUniqueId();
285 } else {
286 this.id = id;
287 }
288
289 }
290 this.version = 0;
291 this.setIncomplete(id > 0);
292 }
293
294 /**
295 * Creates a new primitive for the given id and version.
296 *
297 * If allowNegativeId is set, provided id can be &lt; 0 and will be set to primitive without any processing.
298 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
299 * positive number.
300 *
301 * If id is not &gt; 0 version is ignored and set to 0.
302 *
303 * @param id the id
304 * @param version the version (positive integer)
305 * @param allowNegativeId {@code true} to allow negative id
306 * @throws IllegalArgumentException if id &lt; 0 and allowNegativeId is false
307 */
308 protected OsmPrimitive(long id, int version, boolean allowNegativeId) {
309 this(id, allowNegativeId);
310 this.version = id > 0 ? version : 0;
311 setIncomplete(id > 0 && version == 0);
312 }
313
314 /*----------
315 * MAPPAINT
316 *--------*/
317 public StyleCache mappaintStyle;
318 public int mappaintCacheIdx;
319
320 /* This should not be called from outside. Fixing the UI to add relevant
321 get/set functions calling this implicitely is preferred, so we can have
322 transparent cache handling in the future. */
323 public void clearCachedStyle() {
324 mappaintStyle = null;
325 }
326 /* end of mappaint data */
327
328 /*---------
329 * DATASET
330 *---------*/
331
332 /** the parent dataset */
333 private DataSet dataSet;
334
335 /**
336 * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
337 * @param dataSet the parent dataset
338 */
339 void setDataset(DataSet dataSet) {
340 if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
341 throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
342 this.dataSet = dataSet;
343 }
344
345 /**
346 *
347 * @return DataSet this primitive is part of.
348 */
349 public DataSet getDataSet() {
350 return dataSet;
351 }
352
353 /**
354 * Throws exception if primitive is not part of the dataset
355 */
356 public void checkDataset() {
357 if (dataSet == null)
358 throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
359 }
360
361 protected boolean writeLock() {
362 if (dataSet != null) {
363 dataSet.beginUpdate();
364 return true;
365 } else
366 return false;
367 }
368
369 protected void writeUnlock(boolean locked) {
370 if (locked) {
371 // It shouldn't be possible for dataset to become null because
372 // method calling setDataset would need write lock which is owned by this thread
373 dataSet.endUpdate();
374 }
375 }
376
377 /**
378 * Sets the id and the version of this primitive if it is known to the OSM API.
379 *
380 * Since we know the id and its version it can't be incomplete anymore. incomplete
381 * is set to false.
382 *
383 * @param id the id. &gt; 0 required
384 * @param version the version &gt; 0 required
385 * @throws IllegalArgumentException if id &lt;= 0
386 * @throws IllegalArgumentException if version &lt;= 0
387 * @throws DataIntegrityProblemException if id is changed and primitive was already added to the dataset
388 */
389 @Override
390 public void setOsmId(long id, int version) {
391 boolean locked = writeLock();
392 try {
393 if (id <= 0)
394 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
395 if (version <= 0)
396 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
397 if (dataSet != null && id != this.id) {
398 DataSet datasetCopy = dataSet;
399 // Reindex primitive
400 datasetCopy.removePrimitive(this);
401 this.id = id;
402 datasetCopy.addPrimitive(this);
403 }
404 super.setOsmId(id, version);
405 } finally {
406 writeUnlock(locked);
407 }
408 }
409
410 /**
411 * Clears the metadata, including id and version known to the OSM API.
412 * The id is a new unique id. The version, changeset and timestamp are set to 0.
413 * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
414 *
415 * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@link DataSet}.
416 *
417 * @throws DataIntegrityProblemException If primitive was already added to the dataset
418 * @since 6140
419 */
420 @Override
421 public void clearOsmMetadata() {
422 if (dataSet != null)
423 throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
424 super.clearOsmMetadata();
425 }
426
427 @Override
428 public void setUser(User user) {
429 boolean locked = writeLock();
430 try {
431 super.setUser(user);
432 } finally {
433 writeUnlock(locked);
434 }
435 }
436
437 @Override
438 public void setChangesetId(int changesetId) {
439 boolean locked = writeLock();
440 try {
441 int old = this.changesetId;
442 super.setChangesetId(changesetId);
443 if (dataSet != null) {
444 dataSet.fireChangesetIdChanged(this, old, changesetId);
445 }
446 } finally {
447 writeUnlock(locked);
448 }
449 }
450
451 @Override
452 public void setTimestamp(Date timestamp) {
453 boolean locked = writeLock();
454 try {
455 super.setTimestamp(timestamp);
456 } finally {
457 writeUnlock(locked);
458 }
459 }
460
461
462 /* -------
463 /* FLAGS
464 /* ------*/
465
466 private void updateFlagsNoLock(int flag, boolean value) {
467 super.updateFlags(flag, value);
468 }
469
470 @Override
471 protected final void updateFlags(int flag, boolean value) {
472 boolean locked = writeLock();
473 try {
474 updateFlagsNoLock(flag, value);
475 } finally {
476 writeUnlock(locked);
477 }
478 }
479
480 /**
481 * Make the primitive disabled (e.g.&nbsp;if a filter applies).
482 *
483 * To enable the primitive again, use unsetDisabledState.
484 * @param hidden if the primitive should be completely hidden from view or
485 * just shown in gray color.
486 * @return true, any flag has changed; false if you try to set the disabled
487 * state to the value that is already preset
488 */
489 public boolean setDisabledState(boolean hidden) {
490 boolean locked = writeLock();
491 try {
492 int oldFlags = flags;
493 updateFlagsNoLock(FLAG_DISABLED, true);
494 updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, hidden);
495 return oldFlags != flags;
496 } finally {
497 writeUnlock(locked);
498 }
499 }
500
501 /**
502 * Remove the disabled flag from the primitive.
503 * Afterwards, the primitive is displayed normally and can be selected again.
504 * @return {@code true} if a change occurred
505 */
506 public boolean unsetDisabledState() {
507 boolean locked = writeLock();
508 try {
509 int oldFlags = flags;
510 updateFlagsNoLock(FLAG_DISABLED + FLAG_HIDE_IF_DISABLED, false);
511 return oldFlags != flags;
512 } finally {
513 writeUnlock(locked);
514 }
515 }
516
517 /**
518 * Set binary property used internally by the filter mechanism.
519 * @param isExplicit new "disabled type" flag value
520 */
521 public void setDisabledType(boolean isExplicit) {
522 updateFlags(FLAG_DISABLED_TYPE, isExplicit);
523 }
524
525 /**
526 * Set binary property used internally by the filter mechanism.
527 * @param isExplicit new "hidden type" flag value
528 */
529 public void setHiddenType(boolean isExplicit) {
530 updateFlags(FLAG_HIDDEN_TYPE, isExplicit);
531 }
532
533 /**
534 * Replies true, if this primitive is disabled. (E.g. a filter applies)
535 * @return {@code true} if this object has the "disabled" flag enabled
536 */
537 public boolean isDisabled() {
538 return (flags & FLAG_DISABLED) != 0;
539 }
540
541 /**
542 * Replies true, if this primitive is disabled and marked as completely hidden on the map.
543 * @return {@code true} if this object has both the "disabled" and "hide if disabled" flags enabled
544 */
545 public boolean isDisabledAndHidden() {
546 return ((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0);
547 }
548
549 /**
550 * Get binary property used internally by the filter mechanism.
551 * @return {@code true} if this object has the "hidden type" flag enabled
552 */
553 public boolean getHiddenType() {
554 return (flags & FLAG_HIDDEN_TYPE) != 0;
555 }
556
557 /**
558 * Get binary property used internally by the filter mechanism.
559 * @return {@code true} if this object has the "disabled type" flag enabled
560 */
561 public boolean getDisabledType() {
562 return (flags & FLAG_DISABLED_TYPE) != 0;
563 }
564
565 /**
566 * Determines if this object is selectable.
567 * @return {@code true} if this object is selectable
568 */
569 public boolean isSelectable() {
570 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_DISABLED + FLAG_HIDE_IF_DISABLED)) == 0;
571 }
572
573 /**
574 * Determines if this object is drawable.
575 * @return {@code true} if this object is drawable
576 */
577 public boolean isDrawable() {
578 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 0;
579 }
580
581 @Override
582 public void setModified(boolean modified) {
583 boolean locked = writeLock();
584 try {
585 super.setModified(modified);
586 if (dataSet != null) {
587 dataSet.firePrimitiveFlagsChanged(this);
588 }
589 clearCachedStyle();
590 } finally {
591 writeUnlock(locked);
592 }
593 }
594
595 @Override
596 public void setVisible(boolean visible) {
597 boolean locked = writeLock();
598 try {
599 super.setVisible(visible);
600 clearCachedStyle();
601 } finally {
602 writeUnlock(locked);
603 }
604 }
605
606 @Override
607 public void setDeleted(boolean deleted) {
608 boolean locked = writeLock();
609 try {
610 super.setDeleted(deleted);
611 if (dataSet != null) {
612 if (deleted) {
613 dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
614 } else {
615 dataSet.firePrimitivesAdded(Collections.singleton(this), false);
616 }
617 }
618 clearCachedStyle();
619 } finally {
620 writeUnlock(locked);
621 }
622 }
623
624 @Override
625 protected final void setIncomplete(boolean incomplete) {
626 boolean locked = writeLock();
627 try {
628 if (dataSet != null && incomplete != this.isIncomplete()) {
629 if (incomplete) {
630 dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
631 } else {
632 dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
633 }
634 }
635 super.setIncomplete(incomplete);
636 } finally {
637 writeUnlock(locked);
638 }
639 }
640
641 /**
642 * Determines whether the primitive is selected
643 * @return whether the primitive is selected
644 * @see DataSet#isSelected(OsmPrimitive)
645 */
646 public boolean isSelected() {
647 return dataSet != null && dataSet.isSelected(this);
648 }
649
650 /**
651 * Determines if this primitive is a member of a selected relation.
652 * @return {@code true} if this primitive is a member of a selected relation, {@code false} otherwise
653 */
654 public boolean isMemberOfSelected() {
655 if (referrers == null)
656 return false;
657 if (referrers instanceof OsmPrimitive)
658 return referrers instanceof Relation && ((OsmPrimitive) referrers).isSelected();
659 for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
660 if (ref instanceof Relation && ref.isSelected())
661 return true;
662 }
663 return false;
664 }
665
666 /**
667 * Determines if this primitive is an outer member of a selected multipolygon relation.
668 * @return {@code true} if this primitive is an outer member of a selected multipolygon relation, {@code false} otherwise
669 * @since 7621
670 */
671 public boolean isOuterMemberOfSelected() {
672 if (referrers == null)
673 return false;
674 if (referrers instanceof OsmPrimitive) {
675 return isOuterMemberOfMultipolygon((OsmPrimitive) referrers);
676 }
677 for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
678 if (isOuterMemberOfMultipolygon(ref))
679 return true;
680 }
681 return false;
682 }
683
684 private boolean isOuterMemberOfMultipolygon(OsmPrimitive ref) {
685 if (ref instanceof Relation && ref.isSelected() && ((Relation) ref).isMultipolygon()) {
686 for (RelationMember rm : ((Relation) ref).getMembersFor(Collections.singleton(this))) {
687 if ("outer".equals(rm.getRole())) {
688 return true;
689 }
690 }
691 }
692 return false;
693 }
694
695 /**
696 * Updates the highlight flag for this primitive.
697 * @param highlighted The new highlight flag.
698 */
699 public void setHighlighted(boolean highlighted) {
700 if (isHighlighted() != highlighted) {
701 updateFlags(FLAG_HIGHLIGHTED, highlighted);
702 if (dataSet != null) {
703 dataSet.fireHighlightingChanged();
704 }
705 }
706 }
707
708 /**
709 * Checks if the highlight flag for this primitive was set
710 * @return The highlight flag.
711 */
712 public boolean isHighlighted() {
713 return (flags & FLAG_HIGHLIGHTED) != 0;
714 }
715
716 /*---------------------------------------------------
717 * WORK IN PROGRESS, UNINTERESTING AND DIRECTION KEYS
718 *--------------------------------------------------*/
719
720 private static volatile Collection<String> workinprogress;
721 private static volatile Collection<String> uninteresting;
722 private static volatile Collection<String> discardable;
723
724 /**
725 * Returns a list of "uninteresting" keys that do not make an object
726 * "tagged". Entries that end with ':' are causing a whole namespace to be considered
727 * "uninteresting". Only the first level namespace is considered.
728 * Initialized by isUninterestingKey()
729 * @return The list of uninteresting keys.
730 */
731 public static Collection<String> getUninterestingKeys() {
732 if (uninteresting == null) {
733 List<String> l = new LinkedList<>(Arrays.asList(
734 "source", "source_ref", "source:", "comment",
735 "converted_by", "watch", "watch:",
736 "description", "attribution"));
737 l.addAll(getDiscardableKeys());
738 l.addAll(getWorkInProgressKeys());
739 uninteresting = Main.pref.getCollection("tags.uninteresting", l);
740 }
741 return uninteresting;
742 }
743
744 /**
745 * Returns a list of keys which have been deemed uninteresting to the point
746 * that they can be silently removed from data which is being edited.
747 * @return The list of discardable keys.
748 */
749 public static Collection<String> getDiscardableKeys() {
750 if (discardable == null) {
751 discardable = Main.pref.getCollection("tags.discardable",
752 Arrays.asList(
753 "created_by",
754 "geobase:datasetName",
755 "geobase:uuid",
756 "KSJ2:ADS",
757 "KSJ2:ARE",
758 "KSJ2:AdminArea",
759 "KSJ2:COP_label",
760 "KSJ2:DFD",
761 "KSJ2:INT",
762 "KSJ2:INT_label",
763 "KSJ2:LOC",
764 "KSJ2:LPN",
765 "KSJ2:OPC",
766 "KSJ2:PubFacAdmin",
767 "KSJ2:RAC",
768 "KSJ2:RAC_label",
769 "KSJ2:RIC",
770 "KSJ2:RIN",
771 "KSJ2:WSC",
772 "KSJ2:coordinate",
773 "KSJ2:curve_id",
774 "KSJ2:curve_type",
775 "KSJ2:filename",
776 "KSJ2:lake_id",
777 "KSJ2:lat",
778 "KSJ2:long",
779 "KSJ2:river_id",
780 "odbl",
781 "odbl:note",
782 "SK53_bulk:load",
783 "sub_sea:type",
784 "tiger:source",
785 "tiger:separated",
786 "tiger:tlid",
787 "tiger:upload_uuid",
788 "yh:LINE_NAME",
789 "yh:LINE_NUM",
790 "yh:STRUCTURE",
791 "yh:TOTYUMONO",
792 "yh:TYPE",
793 "yh:WIDTH",
794 "yh:WIDTH_RANK"
795 ));
796 }
797 return discardable;
798 }
799
800 /**
801 * Returns a list of "work in progress" keys that do not make an object
802 * "tagged" but "annotated".
803 * @return The list of work in progress keys.
804 * @since 5754
805 */
806 public static Collection<String> getWorkInProgressKeys() {
807 if (workinprogress == null) {
808 workinprogress = Main.pref.getCollection("tags.workinprogress",
809 Arrays.asList("note", "fixme", "FIXME"));
810 }
811 return workinprogress;
812 }
813
814 /**
815 * Determines if key is considered "uninteresting".
816 * @param key The key to check
817 * @return true if key is considered "uninteresting".
818 */
819 public static boolean isUninterestingKey(String key) {
820 getUninterestingKeys();
821 if (uninteresting.contains(key))
822 return true;
823 int pos = key.indexOf(':');
824 if (pos > 0)
825 return uninteresting.contains(key.substring(0, pos + 1));
826 return false;
827 }
828
829 /**
830 * Returns {@link #getKeys()} for which {@code key} does not fulfill {@link #isUninterestingKey}.
831 * @return A map of interesting tags
832 */
833 public Map<String, String> getInterestingTags() {
834 Map<String, String> result = new HashMap<>();
835 String[] keys = this.keys;
836 if (keys != null) {
837 for (int i = 0; i < keys.length; i += 2) {
838 if (!isUninterestingKey(keys[i])) {
839 result.put(keys[i], keys[i + 1]);
840 }
841 }
842 }
843 return result;
844 }
845
846 /**
847 * A tagged way that matches this pattern has a direction.
848 * @see #FLAG_HAS_DIRECTIONS
849 */
850 private static volatile Match directionKeys;
851
852 /**
853 * A tagged way that matches this pattern has a direction that is reversed.
854 * <p>
855 * This pattern should be a subset of {@link #directionKeys}
856 * @see #FLAG_DIRECTION_REVERSED
857 */
858 private static volatile Match reversedDirectionKeys;
859
860 static {
861 String reversedDirectionDefault = "oneway=\"-1\"";
862
863 String directionDefault = "oneway? | (aerialway=* -aerialway=station) | "+
864 "waterway=stream | waterway=river | waterway=ditch | waterway=drain | "+
865 "\"piste:type\"=downhill | \"piste:type\"=sled | man_made=\"piste:halfpipe\" | "+
866 "junction=roundabout | (highway=motorway & -oneway=no & -oneway=reversible) | "+
867 "(highway=motorway_link & -oneway=no & -oneway=reversible)";
868
869 reversedDirectionKeys = compileDirectionKeys("tags.reversed_direction", reversedDirectionDefault);
870 directionKeys = compileDirectionKeys("tags.direction", directionDefault);
871 }
872
873 private static Match compileDirectionKeys(String prefName, String defaultValue) throws AssertionError {
874 try {
875 return SearchCompiler.compile(Main.pref.get(prefName, defaultValue));
876 } catch (ParseError e) {
877 Main.error("Unable to compile pattern for " + prefName + ", trying default pattern: " + e.getMessage());
878 }
879
880 try {
881 return SearchCompiler.compile(defaultValue);
882 } catch (ParseError e2) {
883 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage(), e2);
884 }
885 }
886
887 private void updateTagged() {
888 for (String key: keySet()) {
889 // 'area' is not really uninteresting (putting it in that list may have unpredictable side effects)
890 // but it's clearly not enough to consider an object as tagged (see #9261)
891 if (!isUninterestingKey(key) && !"area".equals(key)) {
892 updateFlagsNoLock(FLAG_TAGGED, true);
893 return;
894 }
895 }
896 updateFlagsNoLock(FLAG_TAGGED, false);
897 }
898
899 private void updateAnnotated() {
900 for (String key: keySet()) {
901 if (getWorkInProgressKeys().contains(key)) {
902 updateFlagsNoLock(FLAG_ANNOTATED, true);
903 return;
904 }
905 }
906 updateFlagsNoLock(FLAG_ANNOTATED, false);
907 }
908
909 /**
910 * Determines if this object is considered "tagged". To be "tagged", an object
911 * must have one or more "interesting" tags. "created_by" and "source"
912 * are typically considered "uninteresting" and do not make an object
913 * "tagged".
914 * @return true if this object is considered "tagged"
915 */
916 public boolean isTagged() {
917 return (flags & FLAG_TAGGED) != 0;
918 }
919
920 /**
921 * Determines if this object is considered "annotated". To be "annotated", an object
922 * must have one or more "work in progress" tags, such as "note" or "fixme".
923 * @return true if this object is considered "annotated"
924 * @since 5754
925 */
926 public boolean isAnnotated() {
927 return (flags & FLAG_ANNOTATED) != 0;
928 }
929
930 private void updateDirectionFlags() {
931 boolean hasDirections = false;
932 boolean directionReversed = false;
933 if (reversedDirectionKeys.match(this)) {
934 hasDirections = true;
935 directionReversed = true;
936 }
937 if (directionKeys.match(this)) {
938 hasDirections = true;
939 }
940
941 updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed);
942 updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections);
943 }
944
945 /**
946 * true if this object has direction dependent tags (e.g. oneway)
947 * @return {@code true} if this object has direction dependent tags
948 */
949 public boolean hasDirectionKeys() {
950 return (flags & FLAG_HAS_DIRECTIONS) != 0;
951 }
952
953 /**
954 * true if this object has the "reversed diretion" flag enabled
955 * @return {@code true} if this object has the "reversed diretion" flag enabled
956 */
957 public boolean reversedDirection() {
958 return (flags & FLAG_DIRECTION_REVERSED) != 0;
959 }
960
961 /*------------
962 * Keys handling
963 ------------*/
964
965 @Override
966 public final void setKeys(TagMap keys) {
967 boolean locked = writeLock();
968 try {
969 super.setKeys(keys);
970 } finally {
971 writeUnlock(locked);
972 }
973 }
974
975 @Override
976 public final void setKeys(Map<String, String> keys) {
977 boolean locked = writeLock();
978 try {
979 super.setKeys(keys);
980 } finally {
981 writeUnlock(locked);
982 }
983 }
984
985 @Override
986 public final void put(String key, String value) {
987 boolean locked = writeLock();
988 try {
989 super.put(key, value);
990 } finally {
991 writeUnlock(locked);
992 }
993 }
994
995 @Override
996 public final void remove(String key) {
997 boolean locked = writeLock();
998 try {
999 super.remove(key);
1000 } finally {
1001 writeUnlock(locked);
1002 }
1003 }
1004
1005 @Override
1006 public final void removeAll() {
1007 boolean locked = writeLock();
1008 try {
1009 super.removeAll();
1010 } finally {
1011 writeUnlock(locked);
1012 }
1013 }
1014
1015 @Override
1016 protected void keysChangedImpl(Map<String, String> originalKeys) {
1017 clearCachedStyle();
1018 if (dataSet != null) {
1019 for (OsmPrimitive ref : getReferrers()) {
1020 ref.clearCachedStyle();
1021 }
1022 }
1023 updateDirectionFlags();
1024 updateTagged();
1025 updateAnnotated();
1026 if (dataSet != null) {
1027 dataSet.fireTagsChanged(this, originalKeys);
1028 }
1029 }
1030
1031 /*------------
1032 * Referrers
1033 ------------*/
1034
1035 private Object referrers;
1036
1037 /**
1038 * Add new referrer. If referrer is already included then no action is taken
1039 * @param referrer The referrer to add
1040 */
1041 protected void addReferrer(OsmPrimitive referrer) {
1042 if (referrers == null) {
1043 referrers = referrer;
1044 } else if (referrers instanceof OsmPrimitive) {
1045 if (referrers != referrer) {
1046 referrers = new OsmPrimitive[] {(OsmPrimitive) referrers, referrer};
1047 }
1048 } else {
1049 for (OsmPrimitive primitive:(OsmPrimitive[]) referrers) {
1050 if (primitive == referrer)
1051 return;
1052 }
1053 referrers = Utils.addInArrayCopy((OsmPrimitive[]) referrers, referrer);
1054 }
1055 }
1056
1057 /**
1058 * Remove referrer. No action is taken if referrer is not registered
1059 * @param referrer The referrer to remove
1060 */
1061 protected void removeReferrer(OsmPrimitive referrer) {
1062 if (referrers instanceof OsmPrimitive) {
1063 if (referrers == referrer) {
1064 referrers = null;
1065 }
1066 } else if (referrers instanceof OsmPrimitive[]) {
1067 OsmPrimitive[] orig = (OsmPrimitive[]) referrers;
1068 int idx = -1;
1069 for (int i = 0; i < orig.length; i++) {
1070 if (orig[i] == referrer) {
1071 idx = i;
1072 break;
1073 }
1074 }
1075 if (idx == -1)
1076 return;
1077
1078 if (orig.length == 2) {
1079 referrers = orig[1-idx]; // idx is either 0 or 1, take the other
1080 } else { // downsize the array
1081 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
1082 System.arraycopy(orig, 0, smaller, 0, idx);
1083 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
1084 referrers = smaller;
1085 }
1086 }
1087 }
1088
1089 /**
1090 * Find primitives that reference this primitive. Returns only primitives that are included in the same
1091 * dataset as this primitive. <br>
1092 *
1093 * For example following code will add wnew as referer to all nodes of existingWay, but this method will
1094 * not return wnew because it's not part of the dataset <br>
1095 *
1096 * <code>Way wnew = new Way(existingWay)</code>
1097 *
1098 * @param allowWithoutDataset If true, method will return empty list if primitive is not part of the dataset. If false,
1099 * exception will be thrown in this case
1100 *
1101 * @return a collection of all primitives that reference this primitive.
1102 */
1103 public final List<OsmPrimitive> getReferrers(boolean allowWithoutDataset) {
1104 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
1105 // when way is cloned
1106
1107 if (dataSet == null && allowWithoutDataset)
1108 return Collections.emptyList();
1109
1110 checkDataset();
1111 Object referrers = this.referrers;
1112 List<OsmPrimitive> result = new ArrayList<>();
1113 if (referrers != null) {
1114 if (referrers instanceof OsmPrimitive) {
1115 OsmPrimitive ref = (OsmPrimitive) referrers;
1116 if (ref.dataSet == dataSet) {
1117 result.add(ref);
1118 }
1119 } else {
1120 for (OsmPrimitive o:(OsmPrimitive[]) referrers) {
1121 if (dataSet == o.dataSet) {
1122 result.add(o);
1123 }
1124 }
1125 }
1126 }
1127 return result;
1128 }
1129
1130 public final List<OsmPrimitive> getReferrers() {
1131 return getReferrers(false);
1132 }
1133
1134 /**
1135 * <p>Visits {@code visitor} for all referrers.</p>
1136 *
1137 * @param visitor the visitor. Ignored, if null.
1138 */
1139 public void visitReferrers(Visitor visitor) {
1140 if (visitor == null) return;
1141 if (this.referrers == null)
1142 return;
1143 else if (this.referrers instanceof OsmPrimitive) {
1144 OsmPrimitive ref = (OsmPrimitive) this.referrers;
1145 if (ref.dataSet == dataSet) {
1146 ref.accept(visitor);
1147 }
1148 } else if (this.referrers instanceof OsmPrimitive[]) {
1149 OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers;
1150 for (OsmPrimitive ref: refs) {
1151 if (ref.dataSet == dataSet) {
1152 ref.accept(visitor);
1153 }
1154 }
1155 }
1156 }
1157
1158 /**
1159 Return true, if this primitive is referred by at least n ways
1160 @param n Minimal number of ways to return true. Must be positive
1161 * @return {@code true} if this primitive is referred by at least n ways
1162 */
1163 public final boolean isReferredByWays(int n) {
1164 // Count only referrers that are members of the same dataset (primitive can have some fake references, for example
1165 // when way is cloned
1166 Object referrers = this.referrers;
1167 if (referrers == null) return false;
1168 checkDataset();
1169 if (referrers instanceof OsmPrimitive)
1170 return n <= 1 && referrers instanceof Way && ((OsmPrimitive) referrers).dataSet == dataSet;
1171 else {
1172 int counter = 0;
1173 for (OsmPrimitive o : (OsmPrimitive[]) referrers) {
1174 if (dataSet == o.dataSet && o instanceof Way) {
1175 if (++counter >= n)
1176 return true;
1177 }
1178 }
1179 return false;
1180 }
1181 }
1182
1183 /*-----------------
1184 * OTHER METHODS
1185 *----------------*/
1186
1187 /**
1188 * Implementation of the visitor scheme. Subclasses have to call the correct
1189 * visitor function.
1190 * @param visitor The visitor from which the visit() function must be called.
1191 */
1192 public abstract void accept(Visitor visitor);
1193
1194 /**
1195 * Get and write all attributes from the parameter. Does not fire any listener, so
1196 * use this only in the data initializing phase
1197 * @param other other primitive
1198 */
1199 public void cloneFrom(OsmPrimitive other) {
1200 // write lock is provided by subclasses
1201 if (id != other.id && dataSet != null)
1202 throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
1203
1204 super.cloneFrom(other);
1205 clearCachedStyle();
1206 }
1207
1208 /**
1209 * Merges the technical and semantical attributes from <code>other</code> onto this.
1210 *
1211 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
1212 * have an assigend OSM id, the IDs have to be the same.
1213 *
1214 * @param other the other primitive. Must not be null.
1215 * @throws IllegalArgumentException if other is null.
1216 * @throws DataIntegrityProblemException if either this is new and other is not, or other is new and this is not
1217 * @throws DataIntegrityProblemException if other isn't new and other.getId() != this.getId()
1218 */
1219 public void mergeFrom(OsmPrimitive other) {
1220 boolean locked = writeLock();
1221 try {
1222 CheckParameterUtil.ensureParameterNotNull(other, "other");
1223 if (other.isNew() ^ isNew())
1224 throw new DataIntegrityProblemException(
1225 tr("Cannot merge because either of the participating primitives is new and the other is not"));
1226 if (!other.isNew() && other.getId() != id)
1227 throw new DataIntegrityProblemException(
1228 tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
1229
1230 setKeys(other.hasKeys() ? other.getKeys() : null);
1231 timestamp = other.timestamp;
1232 version = other.version;
1233 setIncomplete(other.isIncomplete());
1234 flags = other.flags;
1235 user = other.user;
1236 changesetId = other.changesetId;
1237 } finally {
1238 writeUnlock(locked);
1239 }
1240 }
1241
1242 /**
1243 * Replies true if other isn't null and has the same interesting tags (key/value-pairs) as this.
1244 *
1245 * @param other the other object primitive
1246 * @return true if other isn't null and has the same interesting tags (key/value-pairs) as this.
1247 */
1248 public boolean hasSameInterestingTags(OsmPrimitive other) {
1249 return (keys == null && other.keys == null)
1250 || getInterestingTags().equals(other.getInterestingTags());
1251 }
1252
1253 /**
1254 * Replies true if this primitive and other are equal with respect to their semantic attributes.
1255 * <ol>
1256 * <li>equal id</li>
1257 * <li>both are complete or both are incomplete</li>
1258 * <li>both have the same tags</li>
1259 * </ol>
1260 * @param other other primitive to compare
1261 * @return true if this primitive and other are equal with respect to their semantic attributes.
1262 */
1263 public final boolean hasEqualSemanticAttributes(OsmPrimitive other) {
1264 return hasEqualSemanticAttributes(other, true);
1265 }
1266
1267 boolean hasEqualSemanticAttributes(final OsmPrimitive other, final boolean testInterestingTagsOnly) {
1268 if (!isNew() && id != other.id)
1269 return false;
1270 if (isIncomplete() ^ other.isIncomplete()) // exclusive or operator for performance (see #7159)
1271 return false;
1272 return testInterestingTagsOnly ? hasSameInterestingTags(other) : getKeys().equals(other.getKeys());
1273 }
1274
1275 /**
1276 * Replies true if this primitive and other are equal with respect to their technical attributes.
1277 * The attributes:
1278 * <ol>
1279 * <li>deleted</li>
1280 * <li>modified</li>
1281 * <li>timestamp</li>
1282 * <li>version</li>
1283 * <li>visible</li>
1284 * <li>user</li>
1285 * </ol>
1286 * have to be equal
1287 * @param other the other primitive
1288 * @return true if this primitive and other are equal with respect to their technical attributes
1289 */
1290 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
1291 if (other == null) return false;
1292
1293 return isDeleted() == other.isDeleted()
1294 && isModified() == other.isModified()
1295 && timestamp == other.timestamp
1296 && version == other.version
1297 && isVisible() == other.isVisible()
1298 && (user == null ? other.user == null : user == other.user)
1299 && changesetId == other.changesetId;
1300 }
1301
1302 /**
1303 * Loads (clone) this primitive from provided PrimitiveData
1304 * @param data The object which should be cloned
1305 */
1306 public void load(PrimitiveData data) {
1307 // Write lock is provided by subclasses
1308 setKeys(data.hasKeys() ? data.getKeys() : null);
1309 setRawTimestamp(data.getRawTimestamp());
1310 user = data.getUser();
1311 setChangesetId(data.getChangesetId());
1312 setDeleted(data.isDeleted());
1313 setModified(data.isModified());
1314 setIncomplete(data.isIncomplete());
1315 version = data.getVersion();
1316 }
1317
1318 /**
1319 * Save parameters of this primitive to the transport object
1320 * @return The saved object data
1321 */
1322 public abstract PrimitiveData save();
1323
1324 /**
1325 * Save common parameters of primitives to the transport object
1326 * @param data The object to save the data into
1327 */
1328 protected void saveCommonAttributes(PrimitiveData data) {
1329 data.setId(id);
1330 data.setKeys(hasKeys() ? getKeys() : null);
1331 data.setRawTimestamp(getRawTimestamp());
1332 data.setUser(user);
1333 data.setDeleted(isDeleted());
1334 data.setModified(isModified());
1335 data.setVisible(isVisible());
1336 data.setIncomplete(isIncomplete());
1337 data.setChangesetId(changesetId);
1338 data.setVersion(version);
1339 }
1340
1341 /**
1342 * Fetch the bounding box of the primitive
1343 * @return Bounding box of the object
1344 */
1345 public abstract BBox getBBox();
1346
1347 /**
1348 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1349 */
1350 public abstract void updatePosition();
1351
1352 /*----------------
1353 * OBJECT METHODS
1354 *---------------*/
1355
1356 @Override
1357 protected String getFlagsAsString() {
1358 StringBuilder builder = new StringBuilder(super.getFlagsAsString());
1359
1360 if (isDisabled()) {
1361 if (isDisabledAndHidden()) {
1362 builder.append('h');
1363 } else {
1364 builder.append('d');
1365 }
1366 }
1367 if (isTagged()) {
1368 builder.append('T');
1369 }
1370 if (hasDirectionKeys()) {
1371 if (reversedDirection()) {
1372 builder.append('<');
1373 } else {
1374 builder.append('>');
1375 }
1376 }
1377 return builder.toString();
1378 }
1379
1380 /**
1381 * Equal, if the id (and class) is equal.
1382 *
1383 * An primitive is equal to its incomplete counter part.
1384 */
1385 @Override
1386 public boolean equals(Object obj) {
1387 if (this == obj) return true;
1388 if (obj == null || getClass() != obj.getClass()) return false;
1389 OsmPrimitive that = (OsmPrimitive) obj;
1390 return Objects.equals(id, that.id);
1391 }
1392
1393 /**
1394 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
1395 *
1396 * An primitive has the same hashcode as its incomplete counterpart.
1397 */
1398 @Override
1399 public int hashCode() {
1400 return Objects.hash(id);
1401 }
1402
1403 /**
1404 * Replies the display name of a primitive formatted by <code>formatter</code>
1405 * @param formatter formatter to use
1406 *
1407 * @return the display name
1408 */
1409 public abstract String getDisplayName(NameFormatter formatter);
1410
1411 @Override
1412 public Collection<String> getTemplateKeys() {
1413 Collection<String> keySet = keySet();
1414 List<String> result = new ArrayList<>(keySet.size() + 2);
1415 result.add(SPECIAL_VALUE_ID);
1416 result.add(SPECIAL_VALUE_LOCAL_NAME);
1417 result.addAll(keySet);
1418 return result;
1419 }
1420
1421 @Override
1422 public Object getTemplateValue(String name, boolean special) {
1423 if (special) {
1424 String lc = name.toLowerCase(Locale.ENGLISH);
1425 if (SPECIAL_VALUE_ID.equals(lc))
1426 return getId();
1427 else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc))
1428 return getLocalName();
1429 else
1430 return null;
1431
1432 } else
1433 return getIgnoreCase(name);
1434 }
1435
1436 @Override
1437 public boolean evaluateCondition(Match condition) {
1438 return condition.match(this);
1439 }
1440
1441 /**
1442 * Replies the set of referring relations
1443 * @param primitives primitives to fetch relations from
1444 *
1445 * @return the set of referring relations
1446 */
1447 public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) {
1448 Set<Relation> ret = new HashSet<>();
1449 for (OsmPrimitive w : primitives) {
1450 ret.addAll(OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class));
1451 }
1452 return ret;
1453 }
1454
1455 /**
1456 * Determines if this primitive has tags denoting an area.
1457 * @return {@code true} if this primitive has tags denoting an area, {@code false} otherwise.
1458 * @since 6491
1459 */
1460 public final boolean hasAreaTags() {
1461 return hasKey("landuse")
1462 || "yes".equals(get("area"))
1463 || "riverbank".equals(get("waterway"))
1464 || hasKey("natural")
1465 || hasKey("amenity")
1466 || hasKey("leisure")
1467 || hasKey("building")
1468 || hasKey("building:part");
1469 }
1470
1471 /**
1472 * Determines if this primitive semantically concerns an area.
1473 * @return {@code true} if this primitive semantically concerns an area, according to its type, geometry and tags, {@code false} otherwise.
1474 * @since 6491
1475 */
1476 public abstract boolean concernsArea();
1477
1478 /**
1479 * Tests if this primitive lies outside of the downloaded area of its {@link DataSet}.
1480 * @return {@code true} if this primitive lies outside of the downloaded area
1481 */
1482 public abstract boolean isOutsideDownloadArea();
1483}
Note: See TracBrowser for help on using the repository browser.