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

Last change on this file since 10657 was 10657, checked in by Don-vip, 8 years ago

see #11390, see #12890 - use Java 8 Predicates

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