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

Last change on this file since 8591 was 8591, checked in by Klumbumbus, 9 years ago

fix #11600 see #11393 - (re)add default direction arrows for highway=motorway(_link); lower missing oneway tag to info level

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