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}