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

Revision 4213, 15.7 KB checked in by stoecker, 11 months ago (diff)

fix #6523 - handle boundary like multipolygon

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