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