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
030 * original collection.
031 *
032 */
033public class MergeSourceBuildingVisitor extends AbstractVisitor {
034    private DataSet selectionBase;
035    private DataSet hull;
036    private Map<OsmPrimitive, PrimitiveData> mappedPrimitives;
037
038    /**
039     * Creates the visitor. The visitor starts to build the "hull" from
040     * the currently selected primitives in the dataset <code>selectionBase</code>,
041     * i.e. from {@link DataSet#getSelected()}.
042     *
043     * @param selectionBase the dataset. Must not be null.
044     * @exception IllegalArgumentException thrown if selectionBase is null
045     *
046     */
047    public MergeSourceBuildingVisitor(DataSet selectionBase) throws IllegalArgumentException {
048        CheckParameterUtil.ensureParameterNotNull(selectionBase, "selectionBase");
049        this.selectionBase = selectionBase;
050        this.hull = new DataSet();
051        this.mappedPrimitives = new HashMap<>();
052    }
053
054    protected boolean isInSelectionBase(OsmPrimitive primitive) {
055        return selectionBase.getAllSelected().contains(primitive);
056    }
057
058    protected boolean isAlreadyRemembered(OsmPrimitive primitive) {
059        return mappedPrimitives.keySet().contains(primitive);
060    }
061
062    /**
063     * Remebers a node in the "hull"
064     *
065     * @param n the node
066     */
067    protected void rememberNode(Node n) {
068        if (isAlreadyRemembered(n))
069            return;
070        mappedPrimitives.put(n, n.save());
071    }
072
073    /**
074     * remembers a way in the hull
075     *
076     * @param w the way
077     */
078    protected void rememberWay(Way w) {
079        if (isAlreadyRemembered(w))
080            return;
081        WayData clone = w.save();
082        List<Long> newNodes = new ArrayList<>(w.getNodesCount());
083        for (Node n: w.getNodes()) {
084            newNodes.add(mappedPrimitives.get(n).getUniqueId());
085        }
086        clone.setNodes(newNodes);
087        mappedPrimitives.put(w, clone);
088    }
089
090    /**
091     * Remembers a relation in the hull
092     *
093     * @param r the relation
094     */
095    protected void rememberRelation(Relation r) {
096        RelationData clone;
097        if (isAlreadyRemembered(r)) {
098            clone = (RelationData)mappedPrimitives.get(r);
099        } else {
100            clone = r.save();
101            mappedPrimitives.put(r, clone);
102        }
103
104        List<RelationMemberData> newMembers = new ArrayList<>();
105        for (RelationMember member: r.getMembers()) {
106            newMembers.add(
107                    new RelationMemberData(member.getRole(), mappedPrimitives.get(member.getMember())));
108
109        }
110        clone.setMembers(newMembers);
111    }
112
113    protected void rememberRelationPartial(Relation r) {
114        if (isAlreadyRemembered(r))
115            return;
116        RelationData clone = r.save();
117        clone.getMembers().clear();
118        mappedPrimitives.put(r, clone);
119    }
120
121    protected void rememberIncomplete(OsmPrimitive primitive) {
122        if (isAlreadyRemembered(primitive))
123            return;
124        PrimitiveData clone = primitive.save();
125        clone.setIncomplete(true);
126        mappedPrimitives.put(primitive, clone);
127    }
128
129    @Override
130    public void visit(Node n) {
131        rememberNode(n);
132    }
133
134    @Override
135    public void visit(Way w) {
136        // remember all nodes this way refers to ...
137        //
138        for (Node n: w.getNodes()) {
139            n.accept(this);
140        }
141        // ... and the way itself
142        rememberWay(w);
143    }
144
145    @Override
146    public void visit(Relation r) {
147        // first, remember all primitives members refer to (only if necessary, see
148        // below)
149        //
150        rememberRelationPartial(r);
151        for (RelationMember member: r.getMembers()) {
152            if (isAlreadyRemembered(member.getMember())) {
153                // referred primitive already remembered
154                //
155                continue;
156            }
157            if (isInSelectionBase(member.getMember()) || member.getMember().isNew()) {
158                member.getMember().accept(this);
159            } else {
160                rememberIncomplete(member.getMember());
161            }
162        }
163        rememberRelation(r);
164    }
165
166    protected void buildHull() {
167        // Create all primitives first
168        for (PrimitiveData primitive: mappedPrimitives.values()) {
169            OsmPrimitive newPrimitive = hull.getPrimitiveById(primitive);
170            boolean created = newPrimitive == null;
171            if (created) {
172                newPrimitive = primitive.getType().newInstance(primitive.getUniqueId(), true);
173            }
174            if (newPrimitive instanceof Node && !primitive.isIncomplete()) {
175                newPrimitive.load(primitive);
176            }
177            if (created) {
178                hull.addPrimitive(newPrimitive);
179            }
180        }
181        // Then ways and relations
182        for (PrimitiveData primitive : mappedPrimitives.values()) {
183            if (!(primitive instanceof NodeData) && !primitive.isIncomplete()) {
184                hull.getPrimitiveById(primitive).load(primitive);
185            }
186        }
187    }
188
189    public DataSet build() {
190        for (OsmPrimitive primitive: selectionBase.getAllSelected()) {
191            primitive.accept(this);
192        }
193        buildHull();
194        return hull;
195    }
196}