source: josm/trunk/src/org/openstreetmap/josm/data/osm/Relation.java@ 13138

Last change on this file since 13138 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

  • Property svn:eol-style set to native
File size: 18.2 KB
RevLine 
[3719]1// License: GPL. For details, see LICENSE file.
[343]2package org.openstreetmap.josm.data.osm;
3
[1925]4import java.util.ArrayList;
[3347]5import java.util.Arrays;
[2308]6import java.util.Collection;
[6564]7import java.util.Collections;
[2070]8import java.util.HashSet;
[343]9import java.util.List;
[5676]10import java.util.Map;
[11553]11import java.util.Optional;
[2070]12import java.util.Set;
[11038]13import java.util.stream.Collectors;
[343]14
[12809]15import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
[4100]16import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
[12846]17import org.openstreetmap.josm.spi.preferences.Config;
[1925]18import org.openstreetmap.josm.tools.CopyList;
[10657]19import org.openstreetmap.josm.tools.SubclassFilteredCollection;
[5578]20import org.openstreetmap.josm.tools.Utils;
[343]21
22/**
[7432]23 * A relation, having a set of tags and any number (0...n) of members.
[1169]24 *
[6830]25 * @author Frederik Ramm
[343]26 */
[4098]27public final class Relation extends OsmPrimitive implements IRelation {
[343]28
[3347]29 private RelationMember[] members = new RelationMember[0];
[343]30
[2982]31 private BBox bbox;
32
[1925]33 /**
34 * @return Members of the relation. Changes made in returned list are not mapped
35 * back to the primitive, use setMembers() to modify the members
36 * @since 1925
37 */
38 public List<RelationMember> getMembers() {
[7005]39 return new CopyList<>(members);
[1925]40 }
41
42 /**
43 *
[1926]44 * @param members Can be null, in that case all members are removed
[1925]45 * @since 1925
46 */
47 public void setMembers(List<RelationMember> members) {
[3348]48 boolean locked = writeLock();
49 try {
[3943]50 for (RelationMember rm : this.members) {
[3348]51 rm.getMember().removeReferrer(this);
[3943]52 rm.getMember().clearCachedStyle();
[3348]53 }
[2407]54
[3348]55 if (members != null) {
56 this.members = members.toArray(new RelationMember[members.size()]);
[3359]57 } else {
58 this.members = new RelationMember[0];
[3348]59 }
[3943]60 for (RelationMember rm : this.members) {
[3348]61 rm.getMember().addReferrer(this);
[3943]62 rm.getMember().clearCachedStyle();
[3348]63 }
64
65 fireMembersChanged();
66 } finally {
67 writeUnlock(locked);
[1926]68 }
[1925]69 }
70
[4098]71 @Override
[1926]72 public int getMembersCount() {
[3347]73 return members.length;
[1926]74 }
75
[11038]76 /**
77 * Returns the relation member at the specified index.
78 * @param index the index of the relation member
79 * @return relation member at the specified index
80 */
[1926]81 public RelationMember getMember(int index) {
[3347]82 return members[index];
[1926]83 }
84
[11038]85 /**
86 * Adds the specified relation member at the last position.
87 * @param member the member to add
88 */
[1951]89 public void addMember(RelationMember member) {
[3348]90 boolean locked = writeLock();
91 try {
[6717]92 members = Utils.addInArrayCopy(members, member);
[3348]93 member.getMember().addReferrer(this);
[3943]94 member.getMember().clearCachedStyle();
[3348]95 fireMembersChanged();
96 } finally {
97 writeUnlock(locked);
98 }
[1951]99 }
100
[11038]101 /**
102 * Adds the specified relation member at the specified index.
103 * @param member the member to add
104 * @param index the index at which the specified element is to be inserted
105 */
[1951]106 public void addMember(int index, RelationMember member) {
[3348]107 boolean locked = writeLock();
108 try {
109 RelationMember[] newMembers = new RelationMember[members.length + 1];
110 System.arraycopy(members, 0, newMembers, 0, index);
111 System.arraycopy(members, index, newMembers, index + 1, members.length - index);
112 newMembers[index] = member;
113 members = newMembers;
114 member.getMember().addReferrer(this);
[3943]115 member.getMember().clearCachedStyle();
[3348]116 fireMembersChanged();
117 } finally {
118 writeUnlock(locked);
119 }
[1951]120 }
121
122 /**
123 * Replace member at position specified by index.
[8470]124 * @param index index (positive integer)
125 * @param member relation member to set
[1951]126 * @return Member that was at the position
127 */
128 public RelationMember setMember(int index, RelationMember member) {
[3348]129 boolean locked = writeLock();
130 try {
131 RelationMember originalMember = members[index];
132 members[index] = member;
[10662]133 if (originalMember.getMember() != member.getMember()) {
[3348]134 member.getMember().addReferrer(this);
[3943]135 member.getMember().clearCachedStyle();
[3348]136 originalMember.getMember().removeReferrer(this);
[3943]137 originalMember.getMember().clearCachedStyle();
[3348]138 fireMembersChanged();
139 }
140 return originalMember;
141 } finally {
142 writeUnlock(locked);
[2407]143 }
[1951]144 }
145
146 /**
147 * Removes member at specified position.
[8470]148 * @param index index (positive integer)
[1951]149 * @return Member that was at the position
150 */
151 public RelationMember removeMember(int index) {
[3348]152 boolean locked = writeLock();
153 try {
154 List<RelationMember> members = getMembers();
155 RelationMember result = members.remove(index);
156 setMembers(members);
157 return result;
158 } finally {
159 writeUnlock(locked);
160 }
[1951]161 }
162
[4098]163 @Override
164 public long getMemberId(int idx) {
165 return members[idx].getUniqueId();
166 }
167
168 @Override
169 public String getRole(int idx) {
170 return members[idx].getRole();
171 }
172
173 @Override
174 public OsmPrimitiveType getMemberType(int idx) {
175 return members[idx].getType();
176 }
177
[12813]178 /**
179 * @deprecated no longer supported
180 */
[7556]181 @Override
[12809]182 @Deprecated
183 public void accept(org.openstreetmap.josm.data.osm.visitor.Visitor visitor) {
[1169]184 visitor.visit(this);
185 }
[343]186
[7556]187 @Override
[12809]188 public void accept(OsmPrimitiveVisitor visitor) {
189 visitor.visit(this);
190 }
191
192 @Override
[7556]193 public void accept(PrimitiveVisitor visitor) {
[4100]194 visitor.visit(this);
195 }
196
[2284]197 protected Relation(long id, boolean allowNegative) {
198 super(id, allowNegative);
199 }
200
[1169]201 /**
[2070]202 * Create a new relation with id 0
[1169]203 */
[2070]204 public Relation() {
[2284]205 super(0, false);
[1169]206 }
[343]207
[6140]208 /**
209 * Constructs an identical clone of the argument.
210 * @param clone The relation to clone
[8509]211 * @param clearMetadata If {@code true}, clears the OSM id and other metadata as defined by {@link #clearOsmMetadata}.
212 * If {@code false}, does nothing
[6140]213 */
214 public Relation(Relation clone, boolean clearMetadata) {
[2410]215 super(clone.getUniqueId(), true);
216 cloneFrom(clone);
[6140]217 if (clearMetadata) {
218 clearOsmMetadata();
[2410]219 }
220 }
221
[1169]222 /**
[2070]223 * Create an identical clone of the argument (including the id)
[6140]224 * @param clone The relation to clone, including its id
[1169]225 */
[2070]226 public Relation(Relation clone) {
[2410]227 this(clone, false);
[1169]228 }
[343]229
[1169]230 /**
[6830]231 * Creates a new relation for the given id. If the id &gt; 0, the way is marked
[2070]232 * as incomplete.
[2284]233 *
[6830]234 * @param id the id. &gt; 0 required
[8291]235 * @throws IllegalArgumentException if id &lt; 0
[1169]236 */
[8291]237 public Relation(long id) {
[2284]238 super(id, false);
[1169]239 }
[755]240
[2620]241 /**
[2932]242 * Creates new relation
[8470]243 * @param id the id
244 * @param version version number (positive integer)
[2620]245 */
246 public Relation(long id, int version) {
[2932]247 super(id, version, false);
[2620]248 }
249
[7556]250 @Override
251 public void cloneFrom(OsmPrimitive osm) {
[11383]252 if (!(osm instanceof Relation))
253 throw new IllegalArgumentException("Not a relation: " + osm);
[3348]254 boolean locked = writeLock();
255 try {
256 super.cloneFrom(osm);
257 // It's not necessary to clone members as RelationMember class is immutable
[8510]258 setMembers(((Relation) osm).getMembers());
[3348]259 } finally {
260 writeUnlock(locked);
261 }
[1169]262 }
[755]263
[7556]264 @Override
265 public void load(PrimitiveData data) {
[11383]266 if (!(data instanceof RelationData))
267 throw new IllegalArgumentException("Not a relation data: " + data);
[3348]268 boolean locked = writeLock();
269 try {
270 super.load(data);
[2284]271
[3348]272 RelationData relationData = (RelationData) data;
[2284]273
[7005]274 List<RelationMember> newMembers = new ArrayList<>();
[3348]275 for (RelationMemberData member : relationData.getMembers()) {
[11553]276 newMembers.add(new RelationMember(member.getRole(), Optional.ofNullable(getDataSet().getPrimitiveById(member))
277 .orElseThrow(() -> new AssertionError("Data consistency problem - relation with missing member detected"))));
[3348]278 }
279 setMembers(newMembers);
280 } finally {
281 writeUnlock(locked);
[2284]282 }
283 }
284
[11383]285 @Override
286 public RelationData save() {
[2284]287 RelationData data = new RelationData();
288 saveCommonAttributes(data);
289 for (RelationMember member:getMembers()) {
290 data.getMembers().add(new RelationMemberData(member.getRole(), member.getMember()));
291 }
292 return data;
293 }
294
[8379]295 @Override
296 public String toString() {
[10242]297 StringBuilder result = new StringBuilder(32);
[8379]298 result.append("{Relation id=")
299 .append(getUniqueId())
300 .append(" version=")
301 .append(getVersion())
[8390]302 .append(' ')
[8379]303 .append(getFlagsAsString())
304 .append(" [");
[2363]305 for (RelationMember rm:getMembers()) {
[8379]306 result.append(OsmPrimitiveType.from(rm.getMember()))
[8390]307 .append(' ')
[8379]308 .append(rm.getMember().getUniqueId())
309 .append(", ");
[2363]310 }
[8379]311 result.delete(result.length()-2, result.length())
[8390]312 .append("]}");
[2363]313 return result.toString();
[1169]314 }
315
[1690]316 @Override
[9979]317 public boolean hasEqualSemanticAttributes(OsmPrimitive other, boolean testInterestingTagsOnly) {
[11292]318 return (other instanceof Relation)
319 && hasEqualSemanticFlags(other)
320 && Arrays.equals(members, ((Relation) other).members)
321 && super.hasEqualSemanticAttributes(other, testInterestingTagsOnly);
[1690]322 }
323
[4098]324 @Override
[1169]325 public int compareTo(OsmPrimitive o) {
[8365]326 return o instanceof Relation ? Long.compare(getUniqueId(), o.getUniqueId()) : -1;
[1169]327 }
328
[8365]329 /**
330 * Returns the first member.
331 * @return first member, or {@code null}
332 */
[1596]333 public RelationMember firstMember() {
[8365]334 return (isIncomplete() || members.length == 0) ? null : members[0];
335 }
[3348]336
[8365]337 /**
338 * Returns the last member.
339 * @return last member, or {@code null}
340 */
[1596]341 public RelationMember lastMember() {
[8365]342 return (isIncomplete() || members.length == 0) ? null : members[members.length - 1];
[1596]343 }
[1670]344
345 /**
346 * removes all members with member.member == primitive
[1677]347 *
[1670]348 * @param primitive the primitive to check for
349 */
350 public void removeMembersFor(OsmPrimitive primitive) {
[6564]351 removeMembersFor(Collections.singleton(primitive));
[1670]352 }
[1990]353
[2407]354 @Override
355 public void setDeleted(boolean deleted) {
[3348]356 boolean locked = writeLock();
357 try {
358 for (RelationMember rm:members) {
359 if (deleted) {
360 rm.getMember().removeReferrer(this);
361 } else {
362 rm.getMember().addReferrer(this);
363 }
[2407]364 }
[3348]365 super.setDeleted(deleted);
366 } finally {
367 writeUnlock(locked);
[2407]368 }
369 }
370
[2308]371 /**
[6564]372 * Obtains all members with member.member == primitive
373 * @param primitives the primitives to check for
[9243]374 * @return all relation members for the given primitives
[6564]375 */
376 public Collection<RelationMember> getMembersFor(final Collection<? extends OsmPrimitive> primitives) {
[10657]377 return SubclassFilteredCollection.filter(getMembers(), member -> primitives.contains(member.getMember()));
[6564]378 }
379
380 /**
[2308]381 * removes all members with member.member == primitive
382 *
383 * @param primitives the primitives to check for
[5613]384 * @since 5613
[2308]385 */
[5613]386 public void removeMembersFor(Collection<? extends OsmPrimitive> primitives) {
[2308]387 if (primitives == null || primitives.isEmpty())
388 return;
389
[3348]390 boolean locked = writeLock();
391 try {
392 List<RelationMember> members = getMembers();
[6564]393 members.removeAll(getMembersFor(primitives));
[3348]394 setMembers(members);
395 } finally {
396 writeUnlock(locked);
[2308]397 }
398 }
399
[1990]400 @Override
401 public String getDisplayName(NameFormatter formatter) {
402 return formatter.format(this);
403 }
[2070]404
405 /**
[5266]406 * Replies the set of {@link OsmPrimitive}s referred to by at least one
[2070]407 * member of this relation
[2284]408 *
[5266]409 * @return the set of {@link OsmPrimitive}s referred to by at least one
[2070]410 * member of this relation
[11038]411 * @see #getMemberPrimitivesList()
[2070]412 */
413 public Set<OsmPrimitive> getMemberPrimitives() {
[11038]414 return getMembers().stream().map(RelationMember::getMember).collect(Collectors.toSet());
[2070]415 }
[2399]416
[11038]417 /**
418 * Returns the {@link OsmPrimitive}s of the specified type referred to by at least one member of this relation.
419 * @param tClass the type of the primitive
420 * @param <T> the type of the primitive
421 * @return the primitives
422 */
[6575]423 public <T extends OsmPrimitive> Collection<T> getMemberPrimitives(Class<T> tClass) {
[11038]424 return Utils.filteredCollection(getMemberPrimitivesList(), tClass);
[6575]425 }
426
[11038]427 /**
428 * Returns an unmodifiable list of the {@link OsmPrimitive}s referred to by at least one member of this relation.
429 * @return an unmodifiable list of the primitives
430 */
[5578]431 public List<OsmPrimitive> getMemberPrimitivesList() {
[10717]432 return Utils.transform(getMembers(), RelationMember::getMember);
[5578]433 }
434
[4098]435 @Override
[2399]436 public OsmPrimitiveType getType() {
437 return OsmPrimitiveType.RELATION;
438 }
[2427]439
[4098]440 @Override
[3844]441 public OsmPrimitiveType getDisplayType() {
[9716]442 return isMultipolygon() && !isBoundary() ? OsmPrimitiveType.MULTIPOLYGON : OsmPrimitiveType.RELATION;
[3844]443 }
444
[9716]445 /**
446 * Determines if this relation is a boundary.
447 * @return {@code true} if a boundary relation
448 */
449 public boolean isBoundary() {
450 return "boundary".equals(get("type"));
451 }
452
[10716]453 @Override
[3844]454 public boolean isMultipolygon() {
[9716]455 return "multipolygon".equals(get("type")) || isBoundary();
[3844]456 }
457
[2427]458 @Override
459 public BBox getBBox() {
[11269]460 if (getDataSet() != null && bbox != null)
461 return new BBox(bbox); // use cached value
[3348]462
[11269]463 BBox box = new BBox();
464 addToBBox(box, new HashSet<PrimitiveId>());
465 if (getDataSet() != null)
[11316]466 setBBox(box); // set cache
[11269]467 return new BBox(box);
[2427]468 }
[2437]469
[11316]470 private void setBBox(BBox bbox) {
471 this.bbox = bbox;
472 }
473
[11269]474 @Override
475 protected void addToBBox(BBox box, Set<PrimitiveId> visited) {
476 for (RelationMember rm : members) {
477 if (visited.add(rm.getMember()))
478 rm.getMember().addToBBox(box, visited);
[2982]479 }
480 }
481
[2437]482 @Override
483 public void updatePosition() {
[11316]484 setBBox(null); // make sure that it is recalculated
485 setBBox(getBBox());
[2437]486 }
[2439]487
[2970]488 @Override
[7796]489 void setDataset(DataSet dataSet) {
[2970]490 super.setDataset(dataSet);
491 checkMembers();
[11316]492 setBBox(null); // bbox might have changed if relation was in ds, was removed, modified, added back to dataset
[2970]493 }
494
[10632]495 /**
496 * Checks that members are part of the same dataset, and that they're not deleted.
497 * @throws DataIntegrityProblemException if one the above conditions is not met
498 */
499 private void checkMembers() {
[2963]500 DataSet dataSet = getDataSet();
501 if (dataSet != null) {
[3348]502 RelationMember[] members = this.members;
[2963]503 for (RelationMember rm: members) {
504 if (rm.getMember().getDataSet() != dataSet)
[8509]505 throw new DataIntegrityProblemException(
506 String.format("Relation member must be part of the same dataset as relation(%s, %s)",
507 getPrimitiveId(), rm.getMember().getPrimitiveId()));
[2963]508 }
[12846]509 if (Config.getPref().getBoolean("debug.checkDeleteReferenced", true)) {
[3032]510 for (RelationMember rm: members) {
511 if (rm.getMember().isDeleted())
512 throw new DataIntegrityProblemException("Deleted member referenced: " + toString());
513 }
514 }
[2439]515 }
516 }
[2970]517
[10632]518 /**
519 * Fires the {@code RelationMembersChangedEvent} to listeners.
520 * @throws DataIntegrityProblemException if members are not valid
521 * @see #checkMembers
522 */
523 private void fireMembersChanged() {
[2970]524 checkMembers();
525 if (getDataSet() != null) {
526 getDataSet().fireRelationMembersChanged(this);
527 }
528 }
[3102]529
530 /**
[7432]531 * Determines if at least one child primitive is incomplete.
[3107]532 *
[3102]533 * @return true if at least one child primitive is incomplete
534 */
535 public boolean hasIncompleteMembers() {
[3348]536 RelationMember[] members = this.members;
[3102]537 for (RelationMember rm: members) {
538 if (rm.getMember().isIncomplete()) return true;
539 }
540 return false;
541 }
542
543 /**
[7432]544 * Replies a collection with the incomplete children this relation refers to.
[3107]545 *
[3102]546 * @return the incomplete children. Empty collection if no children are incomplete.
547 */
548 public Collection<OsmPrimitive> getIncompleteMembers() {
[7005]549 Set<OsmPrimitive> ret = new HashSet<>();
[3348]550 RelationMember[] members = this.members;
[3102]551 for (RelationMember rm: members) {
552 if (!rm.getMember().isIncomplete()) {
553 continue;
554 }
555 ret.add(rm.getMember());
556 }
557 return ret;
558 }
[5676]559
560 @Override
561 protected void keysChangedImpl(Map<String, String> originalKeys) {
562 super.keysChangedImpl(originalKeys);
[11038]563 for (OsmPrimitive member : getMemberPrimitivesList()) {
[7091]564 member.clearCachedStyle();
[5676]565 }
566 }
[6491]567
568 @Override
569 public boolean concernsArea() {
570 return isMultipolygon() && hasAreaTags();
571 }
[6639]572
573 @Override
574 public boolean isOutsideDownloadArea() {
575 return false;
576 }
[7556]577
578 /**
579 * Returns the set of roles used in this relation.
580 * @return the set of roles used in this relation. Can be empty but never null
581 * @since 7556
582 */
583 public Set<String> getMemberRoles() {
584 Set<String> result = new HashSet<>();
585 for (RelationMember rm : members) {
586 String role = rm.getRole();
587 if (!role.isEmpty()) {
588 result.add(role);
589 }
590 }
591 return result;
592 }
[343]593}
Note: See TracBrowser for help on using the repository browser.