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

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

add IRelation.setMembers()

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