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

Last change on this file since 13453 was 13434, checked in by Don-vip, 6 years ago

see #8039, see #10456 - support read-only data layers

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