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

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

fix #12467, see #12412 - presets no longer fit correct for boundary relations (regression from r9574)

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