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

Last change on this file since 10716 was 10716, checked in by simon04, 8 years ago

see #11390, see #12890 - Deprecate predicates in OsmPrimitive class

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