source: josm/trunk/src/org/openstreetmap/josm/data/APIDataSet.java@ 5589

Last change on this file since 5589 was 5589, checked in by bastiK, 12 years ago

drop unnecessary properties for upload to the OSM API in order to save bandwidth

The properties are: user, uid, visible, action and timestamp. In addition drop lat & lon for deleted nodes. (To undelete a primitive, it suffices to include it in a <modify> section, the 'visible' property is ignored by the server.)

  • Property svn:eol-style set to native
File size: 11.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data;
3
4import java.util.ArrayList;
5import java.util.Collection;
6import java.util.Collections;
7import java.util.Comparator;
8import java.util.HashMap;
9import java.util.HashSet;
10import java.util.LinkedList;
11import java.util.List;
12import java.util.Set;
13import java.util.Stack;
14
15import org.openstreetmap.josm.actions.upload.CyclicUploadDependencyException;
16import org.openstreetmap.josm.data.conflict.Conflict;
17import org.openstreetmap.josm.data.conflict.ConflictCollection;
18import org.openstreetmap.josm.data.osm.DataSet;
19import org.openstreetmap.josm.data.osm.IPrimitive;
20import org.openstreetmap.josm.data.osm.Node;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.OsmPrimitiveComparator;
23import org.openstreetmap.josm.data.osm.PrimitiveId;
24import org.openstreetmap.josm.data.osm.Relation;
25import org.openstreetmap.josm.data.osm.RelationMember;
26import org.openstreetmap.josm.data.osm.Way;
27import org.openstreetmap.josm.tools.Utils;
28
29/**
30 * Represents a collection of {@link OsmPrimitive}s which should be uploaded to the
31 * API.
32 * The collection is derived from the modified primitives of an {@link DataSet} and it provides methods
33 * for sorting the objects in upload order.
34 *
35 */
36public class APIDataSet {
37 private LinkedList<OsmPrimitive> toAdd;
38 private LinkedList<OsmPrimitive> toUpdate;
39 private LinkedList<OsmPrimitive> toDelete;
40
41 /**
42 * creates a new empty data set
43 */
44 public APIDataSet() {
45 toAdd = new LinkedList<OsmPrimitive>();
46 toUpdate = new LinkedList<OsmPrimitive>();
47 toDelete = new LinkedList<OsmPrimitive>();
48 }
49
50 /**
51 * initializes the API data set with the modified primitives in <code>ds</code>
52 *
53 * @param ds the data set. Ignored, if null.
54 */
55 public void init(DataSet ds) {
56 if (ds == null) return;
57 init(ds.allPrimitives());
58 }
59
60 public void init(Collection<OsmPrimitive> primitives) {
61 toAdd.clear();
62 toUpdate.clear();
63 toDelete.clear();
64
65 for (OsmPrimitive osm :primitives) {
66 if (osm.get("josm/ignore") != null) {
67 continue;
68 }
69 if (osm.isNewOrUndeleted() && !osm.isDeleted()) {
70 toAdd.add(osm);
71 } else if (osm.isModified() && !osm.isDeleted()) {
72 toUpdate.add(osm);
73 } else if (osm.isDeleted() && !osm.isNew() && osm.isModified() && osm.isVisible()) {
74 toDelete.add(osm);
75 }
76 }
77 OsmPrimitiveComparator c = new OsmPrimitiveComparator();
78 c.relationsFirst = true;
79 Collections.sort(toDelete, c);
80 Collections.sort(toAdd, c);
81 Collections.sort(toUpdate, c);
82 }
83
84 /**
85 * initializes the API data set with the modified primitives in <code>ds</code>
86 *
87 * @param ds the data set. Ignored, if null.
88 */
89 public APIDataSet(DataSet ds) {
90 this();
91 init(ds);
92 }
93
94 /**
95 * Replies true if one of the primitives to be updated or to be deleted
96 * participates in the conflict <code>conflict</code>
97 *
98 * @param conflict the conflict
99 * @return true if one of the primitives to be updated or to be deleted
100 * participates in the conflict <code>conflict</code>
101 */
102 public boolean participatesInConflict(Conflict<?> conflict) {
103 if (conflict == null) return false;
104 for (OsmPrimitive p: toUpdate) {
105 if (conflict.isParticipating(p)) return true;
106 }
107 for (OsmPrimitive p: toDelete) {
108 if (conflict.isParticipating(p)) return true;
109 }
110 return false;
111 }
112
113 /**
114 * Replies true if one of the primitives to be updated or to be deleted
115 * participates in at least one conflict in <code>conflicts</code>
116 *
117 * @param conflicts the collection of conflicts
118 * @return true if one of the primitives to be updated or to be deleted
119 * participates in at least one conflict in <code>conflicts</code>
120 */
121 public boolean participatesInConflict(ConflictCollection conflicts) {
122 if (conflicts == null || conflicts.isEmpty()) return false;
123 Set<PrimitiveId> idsParticipatingInConflicts = new HashSet<PrimitiveId>();
124 for (OsmPrimitive p: conflicts.getMyConflictParties()) {
125 idsParticipatingInConflicts.add(p.getPrimitiveId());
126 }
127 for (OsmPrimitive p: conflicts.getTheirConflictParties()) {
128 idsParticipatingInConflicts.add(p.getPrimitiveId());
129 }
130 for (OsmPrimitive p: toUpdate) {
131 if (idsParticipatingInConflicts.contains(p.getPrimitiveId())) return true;
132 }
133 for (OsmPrimitive p: toDelete) {
134 if (idsParticipatingInConflicts.contains(p.getPrimitiveId())) return true;
135 }
136 return false;
137 }
138
139 /**
140 * initializes the API data set with the primitives in <code>primitives</code>
141 *
142 * @param primitives the collection of primitives
143 */
144 public APIDataSet(Collection<OsmPrimitive> primitives) {
145 this();
146 init(primitives);
147 }
148
149 /**
150 * Replies true if there are no primitives to upload
151 *
152 * @return true if there are no primitives to upload
153 */
154 public boolean isEmpty() {
155 return toAdd.isEmpty() && toUpdate.isEmpty() && toDelete.isEmpty();
156 }
157
158 /**
159 * Replies the primitives which should be added to the OSM database
160 *
161 * @return the primitives which should be added to the OSM database
162 */
163 public List<OsmPrimitive> getPrimitivesToAdd() {
164 return toAdd;
165 }
166
167 /**
168 * Replies the primitives which should be updated in the OSM database
169 *
170 * @return the primitives which should be updated in the OSM database
171 */
172 public List<OsmPrimitive> getPrimitivesToUpdate() {
173 return toUpdate;
174 }
175
176 /**
177 * Replies the primitives which should be deleted in the OSM database
178 *
179 * @return the primitives which should be deleted in the OSM database
180 */
181 public List<OsmPrimitive> getPrimitivesToDelete() {
182 return toDelete;
183 }
184
185 /**
186 * Replies all primitives
187 *
188 * @return all primitives
189 */
190 public List<OsmPrimitive> getPrimitives() {
191 LinkedList<OsmPrimitive> ret = new LinkedList<OsmPrimitive>();
192 ret.addAll(toAdd);
193 ret.addAll(toUpdate);
194 ret.addAll(toDelete);
195 return ret;
196 }
197
198 /**
199 * Replies the number of objects to upload
200 *
201 * @return the number of objects to upload
202 */
203 public int getSize() {
204 return toAdd.size() + toUpdate.size() + toDelete.size();
205 }
206
207 public void removeProcessed(Collection<IPrimitive> processed) {
208 if (processed == null) return;
209 toAdd.removeAll(processed);
210 toUpdate.removeAll(processed);
211 toDelete.removeAll(processed);
212 }
213
214 /**
215 * Adjusts the upload order for new relations. Child relations are uploaded first,
216 * parent relations second.
217 *
218 * This method detects cyclic dependencies in new relation. Relations with cyclic
219 * dependencies can't be uploaded.
220 *
221 * @throws CyclicUploadDependencyException thrown, if a cyclic dependency is detected
222 */
223 public void adjustRelationUploadOrder() throws CyclicUploadDependencyException{
224 LinkedList<OsmPrimitive> newToAdd = new LinkedList<OsmPrimitive>();
225 newToAdd.addAll(Utils.filteredCollection(toAdd, Node.class));
226 newToAdd.addAll(Utils.filteredCollection(toAdd, Way.class));
227
228 List<Relation> relationsToAdd = new ArrayList<Relation>(Utils.filteredCollection(toAdd, Relation.class));
229 List<Relation> noProblemRelations = filterRelationsNotReferringToNewRelations(relationsToAdd);
230 newToAdd.addAll(noProblemRelations);
231 relationsToAdd.removeAll(noProblemRelations);
232
233 RelationUploadDependencyGraph graph = new RelationUploadDependencyGraph(relationsToAdd);
234 newToAdd.addAll(graph.computeUploadOrder());
235 toAdd = newToAdd;
236 }
237
238 /**
239 * Replies the subset of relations in <code>relations</code> which are not referring to any
240 * new relation
241 *
242 * @param relations a list of relations
243 * @return the subset of relations in <code>relations</code> which are not referring to any
244 * new relation
245 */
246 protected List<Relation> filterRelationsNotReferringToNewRelations(Collection<Relation> relations) {
247 List<Relation> ret = new LinkedList<Relation>();
248 for (Relation relation: relations) {
249 boolean refersToNewRelation = false;
250 for (RelationMember m : relation.getMembers()) {
251 if (m.isRelation() && m.getMember().isNewOrUndeleted()) {
252 refersToNewRelation = true;
253 break;
254 }
255 }
256 if (!refersToNewRelation) {
257 ret.add(relation);
258 }
259 }
260 return ret;
261 }
262
263 /**
264 * Utility class to sort a collection of new relations with their dependencies
265 * topologically.
266 *
267 */
268 private static class RelationUploadDependencyGraph {
269 private HashMap<Relation, Set<Relation>> children;
270 private Collection<Relation> relations;
271 private Set<Relation> visited;
272 private List<Relation> uploadOrder;
273
274 public RelationUploadDependencyGraph() {
275 this.children = new HashMap<Relation, Set<Relation>>();
276 this.visited = new HashSet<Relation>();
277 }
278
279 public RelationUploadDependencyGraph(Collection<Relation> relations) {
280 this();
281 build(relations);
282 }
283
284 public void build(Collection<Relation> relations) {
285 this.relations = new HashSet<Relation>();
286 for(Relation relation: relations) {
287 if (!relation.isNewOrUndeleted() ) {
288 continue;
289 }
290 this.relations.add(relation);
291 for (RelationMember m: relation.getMembers()) {
292 if (m.isRelation() && m.getMember().isNewOrUndeleted()) {
293 addDependency(relation, (Relation)m.getMember());
294 }
295 }
296 }
297 }
298
299 public Set<Relation> getChildren(Relation relation) {
300 Set<Relation> p = children.get(relation);
301 if (p == null) {
302 p = new HashSet<Relation>();
303 children.put(relation, p);
304 }
305 return p;
306 }
307
308 public void addDependency(Relation relation, Relation child) {
309 getChildren(relation).add(child);
310 }
311
312 protected void visit(Stack<Relation> path, Relation current) throws CyclicUploadDependencyException{
313 if (path.contains(current)) {
314 path.push(current);
315 throw new CyclicUploadDependencyException(path);
316 }
317 if (!visited.contains(current)) {
318 path.push(current);
319 visited.add(current);
320 for (Relation dependent : getChildren(current)) {
321 visit(path,dependent);
322 }
323 uploadOrder.add(current);
324 path.pop();
325 }
326 }
327
328 public List<Relation> computeUploadOrder() throws CyclicUploadDependencyException {
329 visited = new HashSet<Relation>();
330 uploadOrder = new LinkedList<Relation>();
331 Stack<Relation> path = new Stack<Relation>();
332 for (Relation relation: relations) {
333 visit(path, relation);
334 }
335 ArrayList<Relation> ret = new ArrayList<Relation>(relations);
336 Collections.sort(
337 ret,
338 new Comparator<Relation>() {
339 public int compare(Relation o1, Relation o2) {
340 return Integer.valueOf(uploadOrder.indexOf(o1)).compareTo(uploadOrder.indexOf(o2));
341 }
342 }
343 );
344 return ret;
345 }
346 }
347}
Note: See TracBrowser for help on using the repository browser.