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

Last change on this file since 3155 was 3155, checked in by jttt, 14 years ago

Check for relations without physical members in Relation.getBBox()

  • Property svn:eol-style set to native
File size: 12.7 KB
Line 
1package org.openstreetmap.josm.data.osm;
2
3import java.util.ArrayList;
4import java.util.Collection;
5import java.util.HashSet;
6import java.util.List;
7import java.util.Set;
8
9import org.openstreetmap.josm.Main;
10import org.openstreetmap.josm.data.osm.visitor.Visitor;
11import org.openstreetmap.josm.tools.CopyList;
12
13/**
14 * An relation, having a set of tags and any number (0...n) of members.
15 *
16 * @author Frederik Ramm <frederik@remote.org>
17 */
18public final class Relation extends OsmPrimitive {
19
20 private final List<RelationMember> members = new ArrayList<RelationMember>();
21
22 private BBox bbox;
23
24 /**
25 * @return Members of the relation. Changes made in returned list are not mapped
26 * back to the primitive, use setMembers() to modify the members
27 * @since 1925
28 */
29 public List<RelationMember> getMembers() {
30 return new CopyList<RelationMember>(members.toArray(new RelationMember[members.size()]));
31 }
32
33 /**
34 *
35 * @param members Can be null, in that case all members are removed
36 * @since 1925
37 */
38 public void setMembers(List<RelationMember> members) {
39 for (RelationMember rm:this.members) {
40 rm.getMember().removeReferrer(this);
41 }
42
43 this.members.clear();
44 if (members != null) {
45 this.members.addAll(members);
46 }
47 for (RelationMember rm:this.members) {
48 rm.getMember().addReferrer(this);
49 }
50
51 fireMembersChanged();
52 }
53
54 /**
55 *
56 * @since 1926
57 */
58 public int getMembersCount() {
59 return members.size();
60 }
61
62 /**
63 *
64 * @param index
65 * @return
66 * @since 1926
67 */
68 public RelationMember getMember(int index) {
69 return members.get(index);
70 }
71
72 /**
73 *
74 * @param member
75 * @since 1951
76 */
77 public void addMember(RelationMember member) {
78 members.add(member);
79 member.getMember().addReferrer(this);
80 fireMembersChanged();
81 }
82
83 /**
84 *
85 * @param index
86 * @param member
87 * @since 1951
88 */
89 public void addMember(int index, RelationMember member) {
90 members.add(index, member);
91 member.getMember().addReferrer(this);
92 fireMembersChanged();
93 }
94
95 /**
96 * Replace member at position specified by index.
97 * @param index
98 * @param member
99 * @return Member that was at the position
100 * @since 1951
101 */
102 public RelationMember setMember(int index, RelationMember member) {
103 RelationMember result = members.set(index, member);
104 if (result.getMember() != member.getMember()) {
105 member.getMember().addReferrer(this);
106 result.getMember().removeReferrer(this);
107 fireMembersChanged();
108 }
109 return result;
110 }
111
112 /**
113 * Removes member at specified position.
114 * @param index
115 * @return Member that was at the position
116 * @since 1951
117 */
118 public RelationMember removeMember(int index) {
119 RelationMember result = members.remove(index);
120 for (RelationMember rm:members) {
121 // Do not remove referrer if this primitive is used in relation twice
122 if (rm.getMember() == result.getMember())
123 return result;
124 }
125 result.getMember().removeReferrer(this);
126 fireMembersChanged();
127 return result;
128 }
129
130 @Override public void visit(Visitor visitor) {
131 visitor.visit(this);
132 }
133
134 protected Relation(long id, boolean allowNegative) {
135 super(id, allowNegative);
136 }
137
138 /**
139 * Create a new relation with id 0
140 */
141 public Relation() {
142 super(0, false);
143 }
144
145 public Relation(Relation clone, boolean clearId) {
146 super(clone.getUniqueId(), true);
147 cloneFrom(clone);
148 if (clearId) {
149 clearOsmId();
150 }
151 }
152
153 /**
154 * Create an identical clone of the argument (including the id)
155 */
156 public Relation(Relation clone) {
157 this(clone, false);
158 }
159
160 /**
161 * Creates a new relation for the given id. If the id > 0, the way is marked
162 * as incomplete.
163 *
164 * @param id the id. > 0 required
165 * @throws IllegalArgumentException thrown if id < 0
166 */
167 public Relation(long id) throws IllegalArgumentException {
168 super(id, false);
169 }
170
171 /**
172 * Creates new relation
173 * @param id
174 * @param version
175 */
176 public Relation(long id, int version) {
177 super(id, version, false);
178 }
179
180 @Override public void cloneFrom(OsmPrimitive osm) {
181 super.cloneFrom(osm);
182 // It's not necessary to clone members as RelationMember class is immutable
183 setMembers(((Relation)osm).getMembers());
184 }
185
186 @Override public void load(PrimitiveData data) {
187 super.load(data);
188
189 RelationData relationData = (RelationData) data;
190
191 List<RelationMember> newMembers = new ArrayList<RelationMember>();
192 for (RelationMemberData member : relationData.getMembers()) {
193 OsmPrimitive primitive = getDataSet().getPrimitiveById(member);
194 if (primitive == null)
195 throw new AssertionError("Data consistency problem - relation with missing member detected");
196 newMembers.add(new RelationMember(member.getRole(), primitive));
197 }
198 setMembers(newMembers);
199 }
200
201 @Override public RelationData save() {
202 RelationData data = new RelationData();
203 saveCommonAttributes(data);
204 for (RelationMember member:getMembers()) {
205 data.getMembers().add(new RelationMemberData(member.getRole(), member.getMember()));
206 }
207 return data;
208 }
209
210 @Override public String toString() {
211 StringBuilder result = new StringBuilder();
212 result.append("{Relation id=");
213 result.append(getUniqueId());
214 result.append(" version=");
215 result.append(getVersion());
216 result.append(" ");
217 result.append(getFlagsAsString());
218 result.append(" [");
219 for (RelationMember rm:getMembers()) {
220 result.append(OsmPrimitiveType.from(rm.getMember()));
221 result.append(" ");
222 result.append(rm.getMember().getUniqueId());
223 result.append(", ");
224 }
225 result.delete(result.length()-2, result.length());
226 result.append("]");
227 result.append("}");
228 return result.toString();
229 }
230
231 @Override
232 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
233 if (other == null || ! (other instanceof Relation) )
234 return false;
235 if (! super.hasEqualSemanticAttributes(other))
236 return false;
237 Relation r = (Relation)other;
238 return members.equals(r.members);
239 }
240
241 public int compareTo(OsmPrimitive o) {
242 return o instanceof Relation ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : -1;
243 }
244
245 public RelationMember firstMember() {
246 if (isIncomplete()) return null;
247 return (members.size() == 0) ? null : members.get(0);
248 }
249 public RelationMember lastMember() {
250 if (isIncomplete()) return null;
251 return (members.size() == 0) ? null : members.get(members.size() -1);
252 }
253
254 /**
255 * removes all members with member.member == primitive
256 *
257 * @param primitive the primitive to check for
258 */
259 public void removeMembersFor(OsmPrimitive primitive) {
260 if (primitive == null)
261 return;
262
263 ArrayList<RelationMember> todelete = new ArrayList<RelationMember>();
264 for (RelationMember member: members) {
265 if (member.getMember() == primitive) {
266 todelete.add(member);
267 }
268 }
269 primitive.removeReferrer(this);
270 members.removeAll(todelete);
271 fireMembersChanged();
272 }
273
274 @Override
275 public void setDeleted(boolean deleted) {
276 for (RelationMember rm:members) {
277 if (deleted) {
278 rm.getMember().removeReferrer(this);
279 } else {
280 rm.getMember().addReferrer(this);
281 }
282 }
283 super.setDeleted(deleted);
284 }
285
286 /**
287 * removes all members with member.member == primitive
288 *
289 * @param primitives the primitives to check for
290 */
291 public void removeMembersFor(Collection<OsmPrimitive> primitives) {
292 if (primitives == null || primitives.isEmpty())
293 return;
294
295 ArrayList<RelationMember> todelete = new ArrayList<RelationMember>();
296 for (RelationMember member: members) {
297 if (primitives.contains(member.getMember())) {
298 todelete.add(member);
299 }
300 }
301 members.removeAll(todelete);
302 for (OsmPrimitive primitive:primitives) {
303 primitive.removeReferrer(this);
304 }
305 fireMembersChanged();
306 }
307
308 @Override
309 public String getDisplayName(NameFormatter formatter) {
310 return formatter.format(this);
311 }
312
313 /**
314 * Replies the set of {@see OsmPrimitive}s referred to by at least one
315 * member of this relation
316 *
317 * @return the set of {@see OsmPrimitive}s referred to by at least one
318 * member of this relation
319 */
320 public Set<OsmPrimitive> getMemberPrimitives() {
321 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
322 for (RelationMember m: members) {
323 if (m.getMember() != null) {
324 ret.add(m.getMember());
325 }
326 }
327 return ret;
328 }
329
330 public OsmPrimitiveType getType() {
331 return OsmPrimitiveType.RELATION;
332 }
333
334 @Override
335 public BBox getBBox() {
336 if (members.isEmpty())
337 return new BBox(0, 0, 0, 0);
338 if (getDataSet() == null)
339 return calculateBBox(new HashSet<PrimitiveId>());
340 else {
341 if (bbox == null) {
342 bbox = calculateBBox(new HashSet<PrimitiveId>());
343 }
344 if (bbox == null)
345 return new BBox(0, 0, 0, 0); // No real members
346 else
347 return new BBox(bbox);
348 }
349 }
350
351 private BBox calculateBBox(Set<PrimitiveId> visitedRelations) {
352 if (visitedRelations.contains(this))
353 return null;
354 visitedRelations.add(this);
355 if (members.isEmpty())
356 return null;
357 else {
358 BBox result = null;
359 for (RelationMember rm:members) {
360 BBox box = rm.isRelation()?rm.getRelation().calculateBBox(visitedRelations):rm.getMember().getBBox();
361 if (box != null) {
362 if (result == null) {
363 result = box;
364 } else {
365 result.add(box);
366 }
367 }
368 }
369 return result;
370 }
371 }
372
373 @Override
374 public void updatePosition() {
375 bbox = calculateBBox(new HashSet<PrimitiveId>());
376 }
377
378 @Override
379 public void setDataset(DataSet dataSet) {
380 super.setDataset(dataSet);
381 checkMembers();
382 bbox = null; // bbox might have changed if relation was in ds, was removed, modified, added back to dataset
383 }
384
385 private void checkMembers() {
386 DataSet dataSet = getDataSet();
387 if (dataSet != null) {
388 for (RelationMember rm: members) {
389 if (rm.getMember().getDataSet() != dataSet)
390 throw new DataIntegrityProblemException(String.format("Relation member must be part of the same dataset as relation(%s, %s)", getPrimitiveId(), rm.getMember().getPrimitiveId()));
391 }
392 if (Main.pref.getBoolean("debug.checkDeleteReferenced", true)) {
393 for (RelationMember rm: members) {
394 if (rm.getMember().isDeleted())
395 throw new DataIntegrityProblemException("Deleted member referenced: " + toString());
396 }
397 }
398 }
399 }
400
401 private void fireMembersChanged() {
402 checkMembers();
403 if (getDataSet() != null) {
404 getDataSet().fireRelationMembersChanged(this);
405 }
406 }
407
408 /**
409 * Replies true if at least one child primitive is incomplete
410 *
411 * @return true if at least one child primitive is incomplete
412 */
413 public boolean hasIncompleteMembers() {
414 for (RelationMember rm: members) {
415 if (rm.getMember().isIncomplete()) return true;
416 }
417 return false;
418 }
419
420 /**
421 * Replies a collection with the incomplete children this relation
422 * refers to
423 *
424 * @return the incomplete children. Empty collection if no children are incomplete.
425 */
426 public Collection<OsmPrimitive> getIncompleteMembers() {
427 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
428 for (RelationMember rm: members) {
429 if (!rm.getMember().isIncomplete()) {
430 continue;
431 }
432 ret.add(rm.getMember());
433 }
434 return ret;
435 }
436}
Note: See TracBrowser for help on using the repository browser.