001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm.visitor;
003
004import java.util.ArrayList;
005import java.util.HashMap;
006import java.util.List;
007import java.util.Map;
008
009import org.openstreetmap.josm.data.osm.DataSet;
010import org.openstreetmap.josm.data.osm.Node;
011import org.openstreetmap.josm.data.osm.NodeData;
012import org.openstreetmap.josm.data.osm.OsmPrimitive;
013import org.openstreetmap.josm.data.osm.PrimitiveData;
014import org.openstreetmap.josm.data.osm.Relation;
015import org.openstreetmap.josm.data.osm.RelationData;
016import org.openstreetmap.josm.data.osm.RelationMember;
017import org.openstreetmap.josm.data.osm.RelationMemberData;
018import org.openstreetmap.josm.data.osm.Way;
019import org.openstreetmap.josm.data.osm.WayData;
020import org.openstreetmap.josm.tools.CheckParameterUtil;
021
022/**
023 * MergeSourceBuildingVisitor helps to build the "hull" of a collection of {@link OsmPrimitive}s
024 * which shall be merged into another layer. The "hull" is slightly bigger than the original
025 * collection. It includes, for instance the nodes of a way in the original collection even though
026 * these nodes might not be present explicitly in the original collection. The "hull" also includes
027 * incomplete {@link OsmPrimitive}s which are referred to by relations in the original collection. And
028 * it turns {@link OsmPrimitive} referred to by {@link Relation}s in the original collection into
029 * incomplete {@link OsmPrimitive}s in the "hull", if they are not themselves present in the original collection.
030 * @since 1891
031 */
032public class MergeSourceBuildingVisitor implements OsmPrimitiveVisitor {
033    private final DataSet selectionBase;
034    private final DataSet hull;
035    private final Map<OsmPrimitive, PrimitiveData> mappedPrimitives;
036
037    /**
038     * Creates the visitor. The visitor starts to build the "hull" from
039     * the currently selected primitives in the dataset <code>selectionBase</code>,
040     * i.e. from {@link DataSet#getSelected()}.
041     *
042     * @param selectionBase the dataset. Must not be null.
043     * @throws IllegalArgumentException if selectionBase is null
044     */
045    public MergeSourceBuildingVisitor(DataSet selectionBase) {
046        CheckParameterUtil.ensureParameterNotNull(selectionBase, "selectionBase");
047        this.selectionBase = selectionBase;
048        this.hull = new DataSet();
049        this.mappedPrimitives = new HashMap<>();
050    }
051
052    protected boolean isInSelectionBase(OsmPrimitive primitive) {
053        return selectionBase.getAllSelected().contains(primitive);
054    }
055
056    protected boolean isAlreadyRemembered(OsmPrimitive primitive) {
057        return mappedPrimitives.containsKey(primitive);
058    }
059
060    /**
061     * Remebers a node in the "hull"
062     *
063     * @param n the node
064     */
065    protected void rememberNode(Node n) {
066        if (isAlreadyRemembered(n))
067            return;
068        mappedPrimitives.put(n, n.save());
069    }
070
071    /**
072     * remembers a way in the hull
073     *
074     * @param w the way
075     */
076    protected void rememberWay(Way w) {
077        if (isAlreadyRemembered(w))
078            return;
079        WayData clone = w.save();
080        List<Long> newNodes = new ArrayList<>(w.getNodesCount());
081        for (Node n: w.getNodes()) {
082            newNodes.add(mappedPrimitives.get(n).getUniqueId());
083        }
084        clone.setNodeIds(newNodes);
085        mappedPrimitives.put(w, clone);
086    }
087
088    /**
089     * Remembers a relation in the hull
090     *
091     * @param r the relation
092     */
093    protected void rememberRelation(Relation r) {
094        RelationData clone;
095        if (isAlreadyRemembered(r)) {
096            clone = (RelationData) mappedPrimitives.get(r);
097        } else {
098            clone = r.save();
099            mappedPrimitives.put(r, clone);
100        }
101
102        List<RelationMemberData> newMembers = new ArrayList<>();
103        for (RelationMember member: r.getMembers()) {
104            newMembers.add(new RelationMemberData(member.getRole(), mappedPrimitives.get(member.getMember())));
105
106        }
107        clone.setMembers(newMembers);
108    }
109
110    protected void rememberRelationPartial(Relation r) {
111        if (isAlreadyRemembered(r))
112            return;
113        RelationData clone = r.save();
114        clone.getMembers().clear();
115        mappedPrimitives.put(r, clone);
116    }
117
118    protected void rememberIncomplete(OsmPrimitive primitive) {
119        if (isAlreadyRemembered(primitive))
120            return;
121        PrimitiveData clone = primitive.save();
122        clone.setIncomplete(true);
123        mappedPrimitives.put(primitive, clone);
124    }
125
126    @Override
127    public void visit(Node n) {
128        rememberNode(n);
129    }
130
131    @Override
132    public void visit(Way w) {
133        // remember all nodes this way refers to ...
134        for (Node n: w.getNodes()) {
135            n.accept(this);
136        }
137        // ... and the way itself
138        rememberWay(w);
139    }
140
141    @Override
142    public void visit(Relation r) {
143        // first, remember all primitives members refer to (only if necessary, see below)
144        rememberRelationPartial(r);
145        for (RelationMember member: r.getMembers()) {
146            if (isAlreadyRemembered(member.getMember())) {
147                // referred primitive already remembered
148                continue;
149            }
150            if (isInSelectionBase(member.getMember()) || member.getMember().isNew()) {
151                member.getMember().accept(this);
152            } else {
153                rememberIncomplete(member.getMember());
154            }
155        }
156        rememberRelation(r);
157    }
158
159    protected void buildHull() {
160        // Create all primitives first
161        for (PrimitiveData primitive: mappedPrimitives.values()) {
162            OsmPrimitive newPrimitive = hull.getPrimitiveById(primitive);
163            boolean created = newPrimitive == null;
164            if (created) {
165                newPrimitive = primitive.getType().newInstance(primitive.getUniqueId(), true);
166            }
167            if (newPrimitive instanceof Node && !primitive.isIncomplete()) {
168                newPrimitive.load(primitive);
169            }
170            if (created) {
171                hull.addPrimitive(newPrimitive);
172            }
173        }
174        // Then ways and relations
175        for (PrimitiveData primitive : mappedPrimitives.values()) {
176            if (!(primitive instanceof NodeData) && !primitive.isIncomplete()) {
177                hull.getPrimitiveById(primitive).load(primitive);
178            }
179        }
180    }
181
182    /**
183     * Builds and returns the "hull".
184     * @return the "hull" data set
185     */
186    public DataSet build() {
187        for (OsmPrimitive primitive: selectionBase.getAllSelected()) {
188            primitive.accept(this);
189        }
190        buildHull();
191        return hull;
192    }
193}