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

Last change on this file since 8370 was 8370, checked in by Don-vip, 9 years ago

fix #11393 - treat oneway=reversible on highway=motorway_link

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