001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm.visitor; 003 004import java.util.HashMap; 005import java.util.List; 006import java.util.Map; 007import java.util.stream.Collectors; 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; 021import org.openstreetmap.josm.tools.Utils; 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 * Remembers 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 = Utils.transform(w.getNodes(), n -> mappedPrimitives.get(n).getUniqueId()); 082 clone.setNodeIds(newNodes); 083 mappedPrimitives.put(w, clone); 084 } 085 086 /** 087 * Remembers a relation in the hull 088 * 089 * @param r the relation 090 */ 091 protected void rememberRelation(Relation r) { 092 RelationData clone; 093 if (isAlreadyRemembered(r)) { 094 clone = (RelationData) mappedPrimitives.get(r); 095 } else { 096 clone = r.save(); 097 mappedPrimitives.put(r, clone); 098 } 099 100 clone.setMembers(r.getMembers().stream() 101 .map(m -> new RelationMemberData(m.getRole(), mappedPrimitives.get(m.getMember()))) 102 .collect(Collectors.toList())); 103 } 104 105 protected void rememberRelationPartial(Relation r) { 106 if (isAlreadyRemembered(r)) 107 return; 108 RelationData clone = r.save(); 109 clone.getMembers().clear(); 110 mappedPrimitives.put(r, clone); 111 } 112 113 protected void rememberIncomplete(OsmPrimitive primitive) { 114 if (isAlreadyRemembered(primitive)) 115 return; 116 PrimitiveData clone = primitive.save(); 117 clone.setIncomplete(true); 118 mappedPrimitives.put(primitive, clone); 119 } 120 121 @Override 122 public void visit(Node n) { 123 rememberNode(n); 124 } 125 126 @Override 127 public void visit(Way w) { 128 // remember all nodes this way refers to ... 129 for (Node n: w.getNodes()) { 130 n.accept(this); 131 } 132 // ... and the way itself 133 rememberWay(w); 134 } 135 136 @Override 137 public void visit(Relation r) { 138 // first, remember all primitives members refer to (only if necessary, see below) 139 rememberRelationPartial(r); 140 for (RelationMember member: r.getMembers()) { 141 if (isAlreadyRemembered(member.getMember())) { 142 // referred primitive already remembered 143 continue; 144 } 145 if (isInSelectionBase(member.getMember()) || member.getMember().isNew()) { 146 member.getMember().accept(this); 147 } else { 148 rememberIncomplete(member.getMember()); 149 } 150 } 151 rememberRelation(r); 152 } 153 154 protected void buildHull() { 155 // Create all primitives first 156 for (PrimitiveData primitive: mappedPrimitives.values()) { 157 OsmPrimitive newPrimitive = hull.getPrimitiveById(primitive); 158 boolean created = newPrimitive == null; 159 if (created) { 160 newPrimitive = primitive.getType().newInstance(primitive.getUniqueId(), true); 161 } 162 if (newPrimitive instanceof Node && !primitive.isIncomplete()) { 163 newPrimitive.load(primitive); 164 } 165 if (created) { 166 hull.addPrimitive(newPrimitive); 167 } 168 } 169 // Then ways and relations 170 for (PrimitiveData primitive : mappedPrimitives.values()) { 171 if (!(primitive instanceof NodeData) && !primitive.isIncomplete()) { 172 hull.getPrimitiveById(primitive).load(primitive); 173 } 174 } 175 } 176 177 /** 178 * Builds and returns the "hull". 179 * @return the "hull" data set 180 */ 181 public DataSet build() { 182 for (OsmPrimitive primitive: selectionBase.getAllSelected()) { 183 primitive.accept(this); 184 } 185 buildHull(); 186 return hull; 187 } 188}