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

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

see #11390 - fix checkstyle violations

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