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

Last change on this file since 12851 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
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import java.util.ArrayList;
5import java.util.Arrays;
6import java.util.Collection;
7import java.util.Collections;
8import java.util.HashSet;
9import java.util.List;
10import java.util.Map;
11import java.util.Optional;
12import java.util.Set;
13import java.util.stream.Collectors;
14
15import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
16import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
17import org.openstreetmap.josm.spi.preferences.Config;
18import org.openstreetmap.josm.tools.CopyList;
19import org.openstreetmap.josm.tools.SubclassFilteredCollection;
20import org.openstreetmap.josm.tools.Utils;
21
22/**
23 * A relation, having a set of tags and any number (0...n) of members.
24 *
25 * @author Frederik Ramm
26 */
27public final class Relation extends OsmPrimitive implements IRelation {
28
29 private RelationMember[] members = new RelationMember[0];
30
31 private BBox bbox;
32
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() {
39 return new CopyList<>(members);
40 }
41
42 /**
43 *
44 * @param members Can be null, in that case all members are removed
45 * @since 1925
46 */
47 public void setMembers(List<RelationMember> members) {
48 boolean locked = writeLock();
49 try {
50 for (RelationMember rm : this.members) {
51 rm.getMember().removeReferrer(this);
52 rm.getMember().clearCachedStyle();
53 }
54
55 if (members != null) {
56 this.members = members.toArray(new RelationMember[members.size()]);
57 } else {
58 this.members = new RelationMember[0];
59 }
60 for (RelationMember rm : this.members) {
61 rm.getMember().addReferrer(this);
62 rm.getMember().clearCachedStyle();
63 }
64
65 fireMembersChanged();
66 } finally {
67 writeUnlock(locked);
68 }
69 }
70
71 @Override
72 public int getMembersCount() {
73 return members.length;
74 }
75
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 */
81 public RelationMember getMember(int index) {
82 return members[index];
83 }
84
85 /**
86 * Adds the specified relation member at the last position.
87 * @param member the member to add
88 */
89 public void addMember(RelationMember member) {
90 boolean locked = writeLock();
91 try {
92 members = Utils.addInArrayCopy(members, member);
93 member.getMember().addReferrer(this);
94 member.getMember().clearCachedStyle();
95 fireMembersChanged();
96 } finally {
97 writeUnlock(locked);
98 }
99 }
100
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 */
106 public void addMember(int index, RelationMember member) {
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);
115 member.getMember().clearCachedStyle();
116 fireMembersChanged();
117 } finally {
118 writeUnlock(locked);
119 }
120 }
121
122 /**
123 * Replace member at position specified by index.
124 * @param index index (positive integer)
125 * @param member relation member to set
126 * @return Member that was at the position
127 */
128 public RelationMember setMember(int index, RelationMember member) {
129 boolean locked = writeLock();
130 try {
131 RelationMember originalMember = members[index];
132 members[index] = member;
133 if (originalMember.getMember() != member.getMember()) {
134 member.getMember().addReferrer(this);
135 member.getMember().clearCachedStyle();
136 originalMember.getMember().removeReferrer(this);
137 originalMember.getMember().clearCachedStyle();
138 fireMembersChanged();
139 }
140 return originalMember;
141 } finally {
142 writeUnlock(locked);
143 }
144 }
145
146 /**
147 * Removes member at specified position.
148 * @param index index (positive integer)
149 * @return Member that was at the position
150 */
151 public RelationMember removeMember(int index) {
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 }
161 }
162
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
178 /**
179 * @deprecated no longer supported
180 */
181 @Override
182 @Deprecated
183 public void accept(org.openstreetmap.josm.data.osm.visitor.Visitor visitor) {
184 visitor.visit(this);
185 }
186
187 @Override
188 public void accept(OsmPrimitiveVisitor visitor) {
189 visitor.visit(this);
190 }
191
192 @Override
193 public void accept(PrimitiveVisitor visitor) {
194 visitor.visit(this);
195 }
196
197 protected Relation(long id, boolean allowNegative) {
198 super(id, allowNegative);
199 }
200
201 /**
202 * Create a new relation with id 0
203 */
204 public Relation() {
205 super(0, false);
206 }
207
208 /**
209 * Constructs an identical clone of the argument.
210 * @param clone The relation to clone
211 * @param clearMetadata If {@code true}, clears the OSM id and other metadata as defined by {@link #clearOsmMetadata}.
212 * If {@code false}, does nothing
213 */
214 public Relation(Relation clone, boolean clearMetadata) {
215 super(clone.getUniqueId(), true);
216 cloneFrom(clone);
217 if (clearMetadata) {
218 clearOsmMetadata();
219 }
220 }
221
222 /**
223 * Create an identical clone of the argument (including the id)
224 * @param clone The relation to clone, including its id
225 */
226 public Relation(Relation clone) {
227 this(clone, false);
228 }
229
230 /**
231 * Creates a new relation for the given id. If the id &gt; 0, the way is marked
232 * as incomplete.
233 *
234 * @param id the id. &gt; 0 required
235 * @throws IllegalArgumentException if id &lt; 0
236 */
237 public Relation(long id) {
238 super(id, false);
239 }
240
241 /**
242 * Creates new relation
243 * @param id the id
244 * @param version version number (positive integer)
245 */
246 public Relation(long id, int version) {
247 super(id, version, false);
248 }
249
250 @Override
251 public void cloneFrom(OsmPrimitive osm) {
252 if (!(osm instanceof Relation))
253 throw new IllegalArgumentException("Not a relation: " + osm);
254 boolean locked = writeLock();
255 try {
256 super.cloneFrom(osm);
257 // It's not necessary to clone members as RelationMember class is immutable
258 setMembers(((Relation) osm).getMembers());
259 } finally {
260 writeUnlock(locked);
261 }
262 }
263
264 @Override
265 public void load(PrimitiveData data) {
266 if (!(data instanceof RelationData))
267 throw new IllegalArgumentException("Not a relation data: " + data);
268 boolean locked = writeLock();
269 try {
270 super.load(data);
271
272 RelationData relationData = (RelationData) data;
273
274 List<RelationMember> newMembers = new ArrayList<>();
275 for (RelationMemberData member : relationData.getMembers()) {
276 newMembers.add(new RelationMember(member.getRole(), Optional.ofNullable(getDataSet().getPrimitiveById(member))
277 .orElseThrow(() -> new AssertionError("Data consistency problem - relation with missing member detected"))));
278 }
279 setMembers(newMembers);
280 } finally {
281 writeUnlock(locked);
282 }
283 }
284
285 @Override
286 public RelationData save() {
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
295 @Override
296 public String toString() {
297 StringBuilder result = new StringBuilder(32);
298 result.append("{Relation id=")
299 .append(getUniqueId())
300 .append(" version=")
301 .append(getVersion())
302 .append(' ')
303 .append(getFlagsAsString())
304 .append(" [");
305 for (RelationMember rm:getMembers()) {
306 result.append(OsmPrimitiveType.from(rm.getMember()))
307 .append(' ')
308 .append(rm.getMember().getUniqueId())
309 .append(", ");
310 }
311 result.delete(result.length()-2, result.length())
312 .append("]}");
313 return result.toString();
314 }
315
316 @Override
317 public boolean hasEqualSemanticAttributes(OsmPrimitive other, boolean testInterestingTagsOnly) {
318 return (other instanceof Relation)
319 && hasEqualSemanticFlags(other)
320 && Arrays.equals(members, ((Relation) other).members)
321 && super.hasEqualSemanticAttributes(other, testInterestingTagsOnly);
322 }
323
324 @Override
325 public int compareTo(OsmPrimitive o) {
326 return o instanceof Relation ? Long.compare(getUniqueId(), o.getUniqueId()) : -1;
327 }
328
329 /**
330 * Returns the first member.
331 * @return first member, or {@code null}
332 */
333 public RelationMember firstMember() {
334 return (isIncomplete() || members.length == 0) ? null : members[0];
335 }
336
337 /**
338 * Returns the last member.
339 * @return last member, or {@code null}
340 */
341 public RelationMember lastMember() {
342 return (isIncomplete() || members.length == 0) ? null : members[members.length - 1];
343 }
344
345 /**
346 * removes all members with member.member == primitive
347 *
348 * @param primitive the primitive to check for
349 */
350 public void removeMembersFor(OsmPrimitive primitive) {
351 removeMembersFor(Collections.singleton(primitive));
352 }
353
354 @Override
355 public void setDeleted(boolean deleted) {
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 }
364 }
365 super.setDeleted(deleted);
366 } finally {
367 writeUnlock(locked);
368 }
369 }
370
371 /**
372 * Obtains all members with member.member == primitive
373 * @param primitives the primitives to check for
374 * @return all relation members for the given primitives
375 */
376 public Collection<RelationMember> getMembersFor(final Collection<? extends OsmPrimitive> primitives) {
377 return SubclassFilteredCollection.filter(getMembers(), member -> primitives.contains(member.getMember()));
378 }
379
380 /**
381 * removes all members with member.member == primitive
382 *
383 * @param primitives the primitives to check for
384 * @since 5613
385 */
386 public void removeMembersFor(Collection<? extends OsmPrimitive> primitives) {
387 if (primitives == null || primitives.isEmpty())
388 return;
389
390 boolean locked = writeLock();
391 try {
392 List<RelationMember> members = getMembers();
393 members.removeAll(getMembersFor(primitives));
394 setMembers(members);
395 } finally {
396 writeUnlock(locked);
397 }
398 }
399
400 @Override
401 public String getDisplayName(NameFormatter formatter) {
402 return formatter.format(this);
403 }
404
405 /**
406 * Replies the set of {@link OsmPrimitive}s referred to by at least one
407 * member of this relation
408 *
409 * @return the set of {@link OsmPrimitive}s referred to by at least one
410 * member of this relation
411 * @see #getMemberPrimitivesList()
412 */
413 public Set<OsmPrimitive> getMemberPrimitives() {
414 return getMembers().stream().map(RelationMember::getMember).collect(Collectors.toSet());
415 }
416
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 */
423 public <T extends OsmPrimitive> Collection<T> getMemberPrimitives(Class<T> tClass) {
424 return Utils.filteredCollection(getMemberPrimitivesList(), tClass);
425 }
426
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 */
431 public List<OsmPrimitive> getMemberPrimitivesList() {
432 return Utils.transform(getMembers(), RelationMember::getMember);
433 }
434
435 @Override
436 public OsmPrimitiveType getType() {
437 return OsmPrimitiveType.RELATION;
438 }
439
440 @Override
441 public OsmPrimitiveType getDisplayType() {
442 return isMultipolygon() && !isBoundary() ? OsmPrimitiveType.MULTIPOLYGON : OsmPrimitiveType.RELATION;
443 }
444
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
453 @Override
454 public boolean isMultipolygon() {
455 return "multipolygon".equals(get("type")) || isBoundary();
456 }
457
458 @Override
459 public BBox getBBox() {
460 if (getDataSet() != null && bbox != null)
461 return new BBox(bbox); // use cached value
462
463 BBox box = new BBox();
464 addToBBox(box, new HashSet<PrimitiveId>());
465 if (getDataSet() != null)
466 setBBox(box); // set cache
467 return new BBox(box);
468 }
469
470 private void setBBox(BBox bbox) {
471 this.bbox = bbox;
472 }
473
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);
479 }
480 }
481
482 @Override
483 public void updatePosition() {
484 setBBox(null); // make sure that it is recalculated
485 setBBox(getBBox());
486 }
487
488 @Override
489 void setDataset(DataSet dataSet) {
490 super.setDataset(dataSet);
491 checkMembers();
492 setBBox(null); // bbox might have changed if relation was in ds, was removed, modified, added back to dataset
493 }
494
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() {
500 DataSet dataSet = getDataSet();
501 if (dataSet != null) {
502 RelationMember[] members = this.members;
503 for (RelationMember rm: members) {
504 if (rm.getMember().getDataSet() != dataSet)
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()));
508 }
509 if (Config.getPref().getBoolean("debug.checkDeleteReferenced", true)) {
510 for (RelationMember rm: members) {
511 if (rm.getMember().isDeleted())
512 throw new DataIntegrityProblemException("Deleted member referenced: " + toString());
513 }
514 }
515 }
516 }
517
518 /**
519 * Fires the {@code RelationMembersChangedEvent} to listeners.
520 * @throws DataIntegrityProblemException if members are not valid
521 * @see #checkMembers
522 */
523 private void fireMembersChanged() {
524 checkMembers();
525 if (getDataSet() != null) {
526 getDataSet().fireRelationMembersChanged(this);
527 }
528 }
529
530 /**
531 * Determines if at least one child primitive is incomplete.
532 *
533 * @return true if at least one child primitive is incomplete
534 */
535 public boolean hasIncompleteMembers() {
536 RelationMember[] members = this.members;
537 for (RelationMember rm: members) {
538 if (rm.getMember().isIncomplete()) return true;
539 }
540 return false;
541 }
542
543 /**
544 * Replies a collection with the incomplete children this relation refers to.
545 *
546 * @return the incomplete children. Empty collection if no children are incomplete.
547 */
548 public Collection<OsmPrimitive> getIncompleteMembers() {
549 Set<OsmPrimitive> ret = new HashSet<>();
550 RelationMember[] members = this.members;
551 for (RelationMember rm: members) {
552 if (!rm.getMember().isIncomplete()) {
553 continue;
554 }
555 ret.add(rm.getMember());
556 }
557 return ret;
558 }
559
560 @Override
561 protected void keysChangedImpl(Map<String, String> originalKeys) {
562 super.keysChangedImpl(originalKeys);
563 for (OsmPrimitive member : getMemberPrimitivesList()) {
564 member.clearCachedStyle();
565 }
566 }
567
568 @Override
569 public boolean concernsArea() {
570 return isMultipolygon() && hasAreaTags();
571 }
572
573 @Override
574 public boolean isOutsideDownloadArea() {
575 return false;
576 }
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 }
593}
Note: See TracBrowser for help on using the repository browser.