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

Last change on this file since 13497 was 13453, checked in by Don-vip, 6 years ago

fix #8039, fix #10456: final fixes for the read-only/locked layers:

  • rename "read-only" to "locked" (in XML and Java classes/interfaces)
  • add a new download policy (true/never) to allow private layers forbidding only to download data, but allowing everything else

This leads to:

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