source: josm/trunk/src/org/openstreetmap/josm/data/osm/visitor/MergeVisitor.java@ 804

Last change on this file since 804 was 627, checked in by framm, 16 years ago
  • Property svn:eol-style set to native
File size: 8.0 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.data.osm.visitor;
3
4import java.util.Collection;
5import java.util.Date;
6import java.util.HashMap;
7import java.util.Iterator;
8import java.util.LinkedList;
9import java.util.Map;
10
11import org.openstreetmap.josm.data.osm.DataSet;
12import org.openstreetmap.josm.data.osm.Relation;
13import org.openstreetmap.josm.data.osm.RelationMember;
14import org.openstreetmap.josm.data.osm.Node;
15import org.openstreetmap.josm.data.osm.OsmPrimitive;
16import org.openstreetmap.josm.data.osm.Way;
17
18/**
19 * A visitor that get a data set at construction time and merge every visited object
20 * into it.
21 *
22 * @author imi
23 */
24public class MergeVisitor implements Visitor {
25
26 /**
27 * Map from primitives in the database to visited primitives. (Attention: The other way
28 * round than merged)
29 */
30 public Map<OsmPrimitive, OsmPrimitive> conflicts = new HashMap<OsmPrimitive, OsmPrimitive>();
31
32 private final DataSet ds;
33 private final DataSet mergeds;
34
35 private final HashMap<Long, Node> nodeshash = new HashMap<Long, Node>();
36 private final HashMap<Long, Way> wayshash = new HashMap<Long, Way>();
37 private final HashMap<Long, Relation> relshash = new HashMap<Long, Relation>();
38
39 /**
40 * A list of all primitives that got replaced with other primitives.
41 * Key is the primitives in the other's dataset and the value is the one that is now
42 * in ds.nodes instead.
43 */
44 private final Map<OsmPrimitive, OsmPrimitive> merged
45 = new HashMap<OsmPrimitive, OsmPrimitive>();
46
47 public MergeVisitor(DataSet ds, DataSet mergeds) {
48 this.ds = ds;
49 this.mergeds = mergeds;
50
51 for (Node n : ds.nodes) if (n.id != 0) nodeshash.put(n.id, n);
52 for (Way w : ds.ways) if (w.id != 0) wayshash.put(w.id, w);
53 for (Relation r : ds.relations) if (r.id != 0) relshash.put(r.id, r);
54 }
55
56 private <P extends OsmPrimitive> void genMerge(P other,
57 Collection<P> myprims, Collection<P> mergeprims,
58 HashMap<Long, P> primhash) {
59 // 1. Try to find an identical prim with the same id.
60 if (mergeAfterId(myprims, primhash, other))
61 return;
62
63 // 2. Try to find a prim we can merge with the prim from the other ds.
64 for (P my : myprims) {
65 // LinkedList.contains calls equal, and OsmPrimitive.equal
66 // compares just the id.
67 if (match(my, other) && !mergeprims.contains(my)) {
68 merged.put(other, my);
69 mergeCommon(my, other);
70 return;
71 }
72 }
73
74 // 3. No idea how to merge that. Simply add it unchanged.
75 myprims.add(other);
76 }
77
78 public void visit(Node other) {
79 genMerge(other, ds.nodes, mergeds.nodes, nodeshash);
80 }
81
82 public void visit(Way other) {
83 fixWay(other);
84 genMerge(other, ds.ways, mergeds.ways, wayshash);
85 }
86
87 public void visit(Relation other) {
88 fixRelation(other);
89 genMerge(other, ds.relations, mergeds.relations, relshash);
90 }
91
92 /**
93 * Postprocess the dataset and fix all merged references to point to the actual
94 * data.
95 */
96 public void fixReferences() {
97 for (Way w : ds.ways) fixWay(w);
98 for (Relation r : ds.relations) fixRelation(r);
99 for (OsmPrimitive osm : conflicts.values())
100 if (osm instanceof Way)
101 fixWay((Way)osm);
102 else if (osm instanceof Relation)
103 fixRelation((Relation) osm);
104 }
105
106 private void fixWay(Way w) {
107 boolean replacedSomething = false;
108 LinkedList<Node> newNodes = new LinkedList<Node>();
109 for (Node n : w.nodes) {
110 Node otherN = (Node) merged.get(n);
111 newNodes.add(otherN == null ? n : otherN);
112 if (otherN != null)
113 replacedSomething = true;
114 }
115 if (replacedSomething) {
116 w.nodes.clear();
117 w.nodes.addAll(newNodes);
118 }
119 }
120
121 private void fixRelation(Relation r) {
122 boolean replacedSomething = false;
123 LinkedList<RelationMember> newMembers = new LinkedList<RelationMember>();
124 for (RelationMember m : r.members) {
125 OsmPrimitive otherP = merged.get(m.member);
126 if (otherP == null) {
127 newMembers.add(m);
128 } else {
129 RelationMember mnew = new RelationMember(m);
130 mnew.member = otherP;
131 newMembers.add(mnew);
132 replacedSomething = true;
133 }
134 }
135 if (replacedSomething) {
136 r.members.clear();
137 r.members.addAll(newMembers);
138 }
139 }
140
141 private static <P extends OsmPrimitive> boolean match(P p1, P p2) {
142 if ((p1.id == 0 || p2.id == 0) && !p1.incomplete && !p2.incomplete) {
143 return realMatch(p1, p2);
144 }
145 return p1.id == p2.id;
146 }
147
148 /** @return true if the prims have pretty much the same data, i.e. the
149 * same position, the same members, ...
150 */
151 // Java cannot dispatch on generics...
152 private static boolean realMatch(OsmPrimitive p1, OsmPrimitive p2) {
153 if (p1 instanceof Node && p2 instanceof Node) {
154 return realMatch((Node) p1, (Node) p2);
155 } else if (p1 instanceof Way && p2 instanceof Way) {
156 return realMatch((Way) p1, (Way) p2);
157 } else if (p1 instanceof Relation && p2 instanceof Relation) {
158 return realMatch((Relation) p1, (Relation) p2);
159 } else {
160 throw new RuntimeException("arguments have unknown type");
161 }
162 }
163
164 private static boolean realMatch(Node n1, Node n2) {
165 return n1.coor.equalsEpsilon(n2.coor);
166 }
167
168 private static boolean realMatch(Way w1, Way w2) {
169 if (w1.nodes.size() != w2.nodes.size())
170 return false;
171 Iterator<Node> it = w1.nodes.iterator();
172 for (Node n : w2.nodes)
173 if (!match(n, it.next()))
174 return false;
175 return true;
176 }
177
178 private static boolean realMatch(Relation w1, Relation w2) {
179 // FIXME this is not perfect yet...
180 if (w1.members.size() != w2.members.size())
181 return false;
182 for (RelationMember em : w1.members) {
183 if (!w2.members.contains(em)) {
184 return false;
185 }
186 }
187 return true;
188 }
189
190 /**
191 * Merge the common parts of an osm primitive.
192 * @param my The object, the information gets merged into
193 * @param other The object, the information gets merged from
194 */
195 private void mergeCommon(OsmPrimitive my, OsmPrimitive other) {
196 if (other.deleted)
197 my.delete(true);
198 if (my.id == 0 || !my.modified || other.modified) {
199 if (my.id == 0 && other.id != 0) {
200 my.id = other.id;
201 my.modified = other.modified; // match a new node
202 } else if (my.id != 0 && other.id != 0 && other.modified)
203 my.modified = true;
204 }
205 if (other.keys == null)
206 return;
207 if (my.keySet().containsAll(other.keys.keySet()))
208 return;
209 if (my.keys == null)
210 my.keys = other.keys;
211 else
212 my.keys.putAll(other.keys);
213
214 my.modified = true;
215 }
216
217 /**
218 * @return <code>true</code>, if no merge is needed or merge is performed already.
219 */
220 private <P extends OsmPrimitive> boolean mergeAfterId(
221 Collection<P> primitives, HashMap<Long, P> hash, P other) {
222 // Fast-path merging of identical objects
223 if (hash.containsKey(other.id)) {
224 P my = hash.get(other.id);
225 if (my.realEqual(other, true)) {
226 merged.put(other, my);
227 return true;
228 }
229 }
230
231 for (P my : primitives) {
232 if (my.realEqual(other, false)) {
233 merged.put(other, my);
234 return true; // no merge needed.
235 }
236 if (my.realEqual(other, true)) {
237 Date myd = my.timestamp == null ? new Date(0) : my.getTimestamp();
238 Date otherd = other.timestamp == null ? new Date(0) : other.getTimestamp();
239
240 // they differ in modified/timestamp combination only. Auto-resolve it.
241 merged.put(other, my);
242 if (myd.before(otherd)) {
243 my.modified = other.modified;
244 my.timestamp = other.timestamp;
245 }
246 return true; // merge done.
247 }
248 if (my.id == other.id && my.id != 0) {
249 Date myd = my.timestamp == null ? new Date(0) : my.getTimestamp();
250 Date otherd = other.timestamp == null ? new Date(0) : other.getTimestamp();
251
252 if (my.incomplete || other.incomplete) {
253 if (my.incomplete) {
254 my.cloneFrom(other);
255 }
256 } else if (my.modified && other.modified) {
257 conflicts.put(my, other);
258 } else if (!my.modified && !other.modified) {
259 if (myd.before(otherd)) {
260 my.cloneFrom(other);
261 }
262 } else if (other.modified) {
263 if (myd.after(otherd)) {
264 conflicts.put(my, other);
265 } else {
266 my.cloneFrom(other);
267 }
268 } else if (my.modified) {
269 if (myd.before(otherd)) {
270 conflicts.put(my, other);
271 }
272 }
273 merged.put(other, my);
274 return true;
275 }
276 }
277 return false;
278 }
279}
Note: See TracBrowser for help on using the repository browser.