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

Last change on this file since 11118 was 11038, checked in by simon04, 8 years ago

Use Relation.getMemberPrimitivesList where possible to avoid unnecessary set creation

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