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 * @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.keySet().contains(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.setNodes(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 List<RelationMemberData> newMembers = new ArrayList<>(); 104 for (RelationMember member: r.getMembers()) { 105 newMembers.add( 106 new RelationMemberData(member.getRole(), mappedPrimitives.get(member.getMember()))); 107 108 } 109 clone.setMembers(newMembers); 110 } 111 112 protected void rememberRelationPartial(Relation r) { 113 if (isAlreadyRemembered(r)) 114 return; 115 RelationData clone = r.save(); 116 clone.getMembers().clear(); 117 mappedPrimitives.put(r, clone); 118 } 119 120 protected void rememberIncomplete(OsmPrimitive primitive) { 121 if (isAlreadyRemembered(primitive)) 122 return; 123 PrimitiveData clone = primitive.save(); 124 clone.setIncomplete(true); 125 mappedPrimitives.put(primitive, clone); 126 } 127 128 @Override 129 public void visit(Node n) { 130 rememberNode(n); 131 } 132 133 @Override 134 public void visit(Way w) { 135 // remember all nodes this way refers to ... 136 // 137 for (Node n: w.getNodes()) { 138 n.accept(this); 139 } 140 // ... and the way itself 141 rememberWay(w); 142 } 143 144 @Override 145 public void visit(Relation r) { 146 // first, remember all primitives members refer to (only if necessary, see 147 // below) 148 // 149 rememberRelationPartial(r); 150 for (RelationMember member: r.getMembers()) { 151 if (isAlreadyRemembered(member.getMember())) { 152 // referred primitive already remembered 153 // 154 continue; 155 } 156 if (isInSelectionBase(member.getMember()) || member.getMember().isNew()) { 157 member.getMember().accept(this); 158 } else { 159 rememberIncomplete(member.getMember()); 160 } 161 } 162 rememberRelation(r); 163 } 164 165 protected void buildHull() { 166 // Create all primitives first 167 for (PrimitiveData primitive: mappedPrimitives.values()) { 168 OsmPrimitive newPrimitive = hull.getPrimitiveById(primitive); 169 boolean created = newPrimitive == null; 170 if (created) { 171 newPrimitive = primitive.getType().newInstance(primitive.getUniqueId(), true); 172 } 173 if (newPrimitive instanceof Node && !primitive.isIncomplete()) { 174 newPrimitive.load(primitive); 175 } 176 if (created) { 177 hull.addPrimitive(newPrimitive); 178 } 179 } 180 // Then ways and relations 181 for (PrimitiveData primitive : mappedPrimitives.values()) { 182 if (!(primitive instanceof NodeData) && !primitive.isIncomplete()) { 183 hull.getPrimitiveById(primitive).load(primitive); 184 } 185 } 186 } 187 188 public DataSet build() { 189 for (OsmPrimitive primitive: selectionBase.getAllSelected()) { 190 primitive.accept(this); 191 } 192 buildHull(); 193 return hull; 194 } 195}