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

Last change on this file since 3943 was 3943, checked in by bastiK, 13 years ago

fixed #6040 - JOSM doesn't display mappaint colours and style for newly created multipolygons (any more)

  • Property svn:eol-style set to native
File size: 15.1 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.HashSet;
8import java.util.List;
9import java.util.Set;
10
11import org.openstreetmap.josm.Main;
12import org.openstreetmap.josm.data.osm.visitor.Visitor;
13import org.openstreetmap.josm.tools.CopyList;
14
15/**
16 * An relation, having a set of tags and any number (0...n) of members.
17 *
18 * @author Frederik Ramm <frederik@remote.org>
19 */
20public final class Relation extends OsmPrimitive {
21
22 private RelationMember[] members = new RelationMember[0];
23
24 private BBox bbox;
25
26 /**
27 * @return Members of the relation. Changes made in returned list are not mapped
28 * back to the primitive, use setMembers() to modify the members
29 * @since 1925
30 */
31 public List<RelationMember> getMembers() {
32 return new CopyList<RelationMember>(members);
33 }
34
35 /**
36 *
37 * @param members Can be null, in that case all members are removed
38 * @since 1925
39 */
40 public void setMembers(List<RelationMember> members) {
41 boolean locked = writeLock();
42 try {
43 for (RelationMember rm : this.members) {
44 rm.getMember().removeReferrer(this);
45 rm.getMember().clearCachedStyle();
46 }
47
48 if (members != null) {
49 this.members = members.toArray(new RelationMember[members.size()]);
50 } else {
51 this.members = new RelationMember[0];
52 }
53 for (RelationMember rm : this.members) {
54 rm.getMember().addReferrer(this);
55 rm.getMember().clearCachedStyle();
56 }
57
58 fireMembersChanged();
59 } finally {
60 writeUnlock(locked);
61 }
62 }
63
64 /**
65 * @return number of members
66 */
67 public int getMembersCount() {
68 return members.length;
69 }
70
71 public RelationMember getMember(int index) {
72 return members[index];
73 }
74
75 public void addMember(RelationMember member) {
76 boolean locked = writeLock();
77 try {
78 RelationMember[] newMembers = new RelationMember[members.length + 1];
79 System.arraycopy(members, 0, newMembers, 0, members.length);
80 newMembers[members.length] = member;
81 members = newMembers;
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
109 * @param member
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
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 public void visit(Visitor visitor) {
148 visitor.visit(this);
149 }
150
151 protected Relation(long id, boolean allowNegative) {
152 super(id, allowNegative);
153 }
154
155 /**
156 * Create a new relation with id 0
157 */
158 public Relation() {
159 super(0, false);
160 }
161
162 public Relation(Relation clone, boolean clearId) {
163 super(clone.getUniqueId(), true);
164 cloneFrom(clone);
165 if (clearId) {
166 clearOsmId();
167 }
168 }
169
170 /**
171 * Create an identical clone of the argument (including the id)
172 */
173 public Relation(Relation clone) {
174 this(clone, false);
175 }
176
177 /**
178 * Creates a new relation for the given id. If the id > 0, the way is marked
179 * as incomplete.
180 *
181 * @param id the id. > 0 required
182 * @throws IllegalArgumentException thrown if id < 0
183 */
184 public Relation(long id) throws IllegalArgumentException {
185 super(id, false);
186 }
187
188 /**
189 * Creates new relation
190 * @param id
191 * @param version
192 */
193 public Relation(long id, int version) {
194 super(id, version, false);
195 }
196
197 @Override public void cloneFrom(OsmPrimitive osm) {
198 boolean locked = writeLock();
199 try {
200 super.cloneFrom(osm);
201 // It's not necessary to clone members as RelationMember class is immutable
202 setMembers(((Relation)osm).getMembers());
203 } finally {
204 writeUnlock(locked);
205 }
206 }
207
208 @Override public void load(PrimitiveData data) {
209 boolean locked = writeLock();
210 try {
211 super.load(data);
212
213 RelationData relationData = (RelationData) data;
214
215 List<RelationMember> newMembers = new ArrayList<RelationMember>();
216 for (RelationMemberData member : relationData.getMembers()) {
217 OsmPrimitive primitive = getDataSet().getPrimitiveById(member);
218 if (primitive == null)
219 throw new AssertionError("Data consistency problem - relation with missing member detected");
220 newMembers.add(new RelationMember(member.getRole(), primitive));
221 }
222 setMembers(newMembers);
223 } finally {
224 writeUnlock(locked);
225 }
226 }
227
228 @Override public RelationData save() {
229 RelationData data = new RelationData();
230 saveCommonAttributes(data);
231 for (RelationMember member:getMembers()) {
232 data.getMembers().add(new RelationMemberData(member.getRole(), member.getMember()));
233 }
234 return data;
235 }
236
237 @Override public String toString() {
238 StringBuilder result = new StringBuilder();
239 result.append("{Relation id=");
240 result.append(getUniqueId());
241 result.append(" version=");
242 result.append(getVersion());
243 result.append(" ");
244 result.append(getFlagsAsString());
245 result.append(" [");
246 for (RelationMember rm:getMembers()) {
247 result.append(OsmPrimitiveType.from(rm.getMember()));
248 result.append(" ");
249 result.append(rm.getMember().getUniqueId());
250 result.append(", ");
251 }
252 result.delete(result.length()-2, result.length());
253 result.append("]");
254 result.append("}");
255 return result.toString();
256 }
257
258 @Override
259 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
260 if (other == null || ! (other instanceof Relation) )
261 return false;
262 if (! super.hasEqualSemanticAttributes(other))
263 return false;
264 Relation r = (Relation)other;
265 return Arrays.equals(members, r.members);
266 }
267
268 public int compareTo(OsmPrimitive o) {
269 return o instanceof Relation ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : -1;
270 }
271
272 public RelationMember firstMember() {
273 if (isIncomplete()) return null;
274
275 RelationMember[] members = this.members;
276 return (members.length == 0) ? null : members[0];
277 }
278 public RelationMember lastMember() {
279 if (isIncomplete()) return null;
280
281 RelationMember[] members = this.members;
282 return (members.length == 0) ? null : members[members.length - 1];
283 }
284
285 /**
286 * removes all members with member.member == primitive
287 *
288 * @param primitive the primitive to check for
289 */
290 public void removeMembersFor(OsmPrimitive primitive) {
291 if (primitive == null)
292 return;
293
294 boolean locked = writeLock();
295 try {
296 List<RelationMember> todelete = new ArrayList<RelationMember>();
297 for (RelationMember member: members) {
298 if (member.getMember() == primitive) {
299 todelete.add(member);
300 }
301 }
302 List<RelationMember> members = getMembers();
303 members.removeAll(todelete);
304 setMembers(members);
305 } finally {
306 writeUnlock(locked);
307 }
308 }
309
310 @Override
311 public void setDeleted(boolean deleted) {
312 boolean locked = writeLock();
313 try {
314 for (RelationMember rm:members) {
315 if (deleted) {
316 rm.getMember().removeReferrer(this);
317 } else {
318 rm.getMember().addReferrer(this);
319 }
320 }
321 super.setDeleted(deleted);
322 } finally {
323 writeUnlock(locked);
324 }
325 }
326
327 /**
328 * removes all members with member.member == primitive
329 *
330 * @param primitives the primitives to check for
331 */
332 public void removeMembersFor(Collection<OsmPrimitive> primitives) {
333 if (primitives == null || primitives.isEmpty())
334 return;
335
336 boolean locked = writeLock();
337 try {
338 ArrayList<RelationMember> todelete = new ArrayList<RelationMember>();
339 for (RelationMember member: members) {
340 if (primitives.contains(member.getMember())) {
341 todelete.add(member);
342 }
343 }
344 List<RelationMember> members = getMembers();
345 members.removeAll(todelete);
346 setMembers(members);
347 } finally {
348 writeUnlock(locked);
349 }
350 }
351
352 @Override
353 public String getDisplayName(NameFormatter formatter) {
354 return formatter.format(this);
355 }
356
357 /**
358 * Replies the set of {@see OsmPrimitive}s referred to by at least one
359 * member of this relation
360 *
361 * @return the set of {@see OsmPrimitive}s referred to by at least one
362 * member of this relation
363 */
364 public Set<OsmPrimitive> getMemberPrimitives() {
365 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
366 RelationMember[] members = this.members;
367 for (RelationMember m: members) {
368 if (m.getMember() != null) {
369 ret.add(m.getMember());
370 }
371 }
372 return ret;
373 }
374
375 public OsmPrimitiveType getType() {
376 return OsmPrimitiveType.RELATION;
377 }
378
379 public OsmPrimitiveType getDisplayType() {
380 return isMultipolygon() ? OsmPrimitiveType.MULTIPOLYGON
381 : OsmPrimitiveType.RELATION;
382 }
383
384 public boolean isMultipolygon() {
385 return "multipolygon".equals(get("type"));
386 }
387
388 @Override
389 public BBox getBBox() {
390 RelationMember[] members = this.members;
391
392 if (members.length == 0)
393 return new BBox(0, 0, 0, 0);
394 if (getDataSet() == null)
395 return calculateBBox(new HashSet<PrimitiveId>());
396 else {
397 if (bbox == null) {
398 bbox = calculateBBox(new HashSet<PrimitiveId>());
399 }
400 if (bbox == null)
401 return new BBox(0, 0, 0, 0); // No real members
402 else
403 return new BBox(bbox);
404 }
405 }
406
407 private BBox calculateBBox(Set<PrimitiveId> visitedRelations) {
408 if (visitedRelations.contains(this))
409 return null;
410 visitedRelations.add(this);
411
412 RelationMember[] members = this.members;
413 if (members.length == 0)
414 return null;
415 else {
416 BBox result = null;
417 for (RelationMember rm:members) {
418 BBox box = rm.isRelation()?rm.getRelation().calculateBBox(visitedRelations):rm.getMember().getBBox();
419 if (box != null) {
420 if (result == null) {
421 result = box;
422 } else {
423 result.add(box);
424 }
425 }
426 }
427 return result;
428 }
429 }
430
431 @Override
432 public void updatePosition() {
433 bbox = calculateBBox(new HashSet<PrimitiveId>());
434 }
435
436 @Override
437 public void setDataset(DataSet dataSet) {
438 super.setDataset(dataSet);
439 checkMembers();
440 bbox = null; // bbox might have changed if relation was in ds, was removed, modified, added back to dataset
441 }
442
443 private void checkMembers() {
444 DataSet dataSet = getDataSet();
445 if (dataSet != null) {
446 RelationMember[] members = this.members;
447 for (RelationMember rm: members) {
448 if (rm.getMember().getDataSet() != dataSet)
449 throw new DataIntegrityProblemException(String.format("Relation member must be part of the same dataset as relation(%s, %s)", getPrimitiveId(), rm.getMember().getPrimitiveId()));
450 }
451 if (Main.pref.getBoolean("debug.checkDeleteReferenced", true)) {
452 for (RelationMember rm: members) {
453 if (rm.getMember().isDeleted())
454 throw new DataIntegrityProblemException("Deleted member referenced: " + toString());
455 }
456 }
457 }
458 }
459
460 private void fireMembersChanged() {
461 checkMembers();
462 if (getDataSet() != null) {
463 getDataSet().fireRelationMembersChanged(this);
464 }
465 }
466
467 /**
468 * Replies true if at least one child primitive is incomplete
469 *
470 * @return true if at least one child primitive is incomplete
471 */
472 public boolean hasIncompleteMembers() {
473 RelationMember[] members = this.members;
474 for (RelationMember rm: members) {
475 if (rm.getMember().isIncomplete()) return true;
476 }
477 return false;
478 }
479
480 /**
481 * Replies a collection with the incomplete children this relation
482 * refers to
483 *
484 * @return the incomplete children. Empty collection if no children are incomplete.
485 */
486 public Collection<OsmPrimitive> getIncompleteMembers() {
487 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
488 RelationMember[] members = this.members;
489 for (RelationMember rm: members) {
490 if (!rm.getMember().isIncomplete()) {
491 continue;
492 }
493 ret.add(rm.getMember());
494 }
495 return ret;
496 }
497}
Note: See TracBrowser for help on using the repository browser.