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

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

see #11390, see #12890 - Lambda can be replaced with method reference

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