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

Last change on this file since 9600 was 9552, checked in by Klumbumbus, 8 years ago

fix #10290 - remove wadi direction arrows; see #12404 - improve fence_type=chain validator warning message

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