source: josm/trunk/src/org/openstreetmap/josm/io/OsmReader.java@ 1102

Last change on this file since 1102 was 1071, checked in by framm, 15 years ago

Support for changeset uploads in 0.6. Error handling is not fully done but basics are working. Full changeset upload is now the default if version is 0.6; set osm-server.atomic-upload=false in your configuration file to go back to individual object upload.

  • Property svn:eol-style set to native
File size: 19.3 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
7import java.io.InputStream;
8import java.io.InputStreamReader;
9import java.util.ArrayList;
10import java.util.Arrays;
11import java.util.Collection;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.LinkedList;
15import java.util.Map;
16import java.util.Map.Entry;
17
18import javax.xml.parsers.ParserConfigurationException;
19import javax.xml.parsers.SAXParserFactory;
20
21import org.openstreetmap.josm.Main;
22import org.openstreetmap.josm.data.Bounds;
23import org.openstreetmap.josm.data.coor.LatLon;
24import org.openstreetmap.josm.data.osm.DataSet;
25import org.openstreetmap.josm.data.osm.DataSource;
26import org.openstreetmap.josm.data.osm.Node;
27import org.openstreetmap.josm.data.osm.OsmPrimitive;
28import org.openstreetmap.josm.data.osm.Relation;
29import org.openstreetmap.josm.data.osm.RelationMember;
30import org.openstreetmap.josm.data.osm.User;
31import org.openstreetmap.josm.data.osm.Way;
32import org.openstreetmap.josm.data.osm.visitor.AddVisitor;
33import org.openstreetmap.josm.data.osm.visitor.Visitor;
34import org.openstreetmap.josm.gui.PleaseWaitDialog;
35import org.xml.sax.Attributes;
36import org.xml.sax.InputSource;
37import org.xml.sax.SAXException;
38import org.xml.sax.helpers.DefaultHandler;
39
40/**
41 * Parser for the Osm Api. Read from an input stream and construct a dataset out of it.
42 *
43 * Reading process takes place in three phases. During the first phase (including xml parse),
44 * all nodes are read and stored. Other information than nodes are stored in a raw list
45 *
46 * The second phase read all ways out of the remaining objects in the raw list.
47 *
48 * @author Imi
49 */
50public class OsmReader {
51
52 /**
53 * This is used as (readonly) source for finding missing references when not transferred in the
54 * file.
55 */
56 private DataSet references;
57
58 /**
59 * The dataset to add parsed objects to.
60 */
61 private DataSet ds = new DataSet();
62
63 /**
64 * The visitor to use to add the data to the set.
65 */
66 private AddVisitor adder = new AddVisitor(ds);
67
68 /**
69 * All read nodes after phase 1.
70 */
71 private Map<Long, Node> nodes = new HashMap<Long, Node>();
72
73 // TODO: What the hack? Is this really from me? Please, clean this up!
74 private static class OsmPrimitiveData extends OsmPrimitive {
75 @Override public void visit(Visitor visitor) {}
76 public int compareTo(OsmPrimitive o) {return 0;}
77
78 public void copyTo(OsmPrimitive osm) {
79 osm.id = id;
80 osm.keys = keys;
81 osm.modified = modified;
82 osm.selected = selected;
83 osm.deleted = deleted;
84 osm.timestamp = timestamp;
85 osm.user = user;
86 osm.visible = visible;
87 osm.version = version;
88 osm.checkTagged();
89 osm.checkDirectionTagged();
90 }
91 }
92
93 /**
94 * Used as a temporary storage for relation members, before they
95 * are resolved into pointers to real objects.
96 */
97 private static class RelationMemberData {
98 public String type;
99 public long id;
100 public RelationMember relationMember;
101 }
102
103 /**
104 * Data structure for the remaining way objects
105 */
106 private Map<OsmPrimitiveData, Collection<Long>> ways = new HashMap<OsmPrimitiveData, Collection<Long>>();
107
108 /**
109 * Data structure for relation objects
110 */
111 private Map<OsmPrimitiveData, Collection<RelationMemberData>> relations = new HashMap<OsmPrimitiveData, Collection<RelationMemberData>>();
112
113 /**
114 * List of protocol versions that will be accepted on reading
115 */
116 private HashSet<String> allowedVersions = new HashSet<String>();
117
118 private class Parser extends DefaultHandler {
119 /**
120 * The current osm primitive to be read.
121 */
122 private OsmPrimitive current;
123 private String generator;
124
125 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
126 try {
127 if (qName.equals("osm")) {
128 if (atts == null)
129 throw new SAXException(tr("Unknown version"));
130 if (!allowedVersions.contains(atts.getValue("version")))
131 throw new SAXException(tr("Unknown version")+": "+atts.getValue("version"));
132 // save generator attribute for later use when creating DataSource objects
133 generator = atts.getValue("generator");
134
135
136 } else if (qName.equals("bound")) {
137 // old style bounds.
138 // TODO: remove this around 1st October 2008.
139 // - this is a bit of a hack; since we still write out old style bound objects,
140 // we don't want to load them both. so when writing, we add a "note" tag the our
141 // old-style bound, and when reading, ignore those with a "note".
142 String note = atts.getValue("note");
143 if (note == null) {
144 System.out.println("Notice: old style <bound> element detected; support for these will be dropped in a future release.");
145 String bbox = atts.getValue("box");
146 String origin = atts.getValue("origin");
147 if (origin == null) origin = "";
148 if (bbox != null) {
149 String[] b = bbox.split(",");
150 Bounds bounds = new Bounds();
151 if (b.length == 4)
152 bounds = new Bounds(
153 new LatLon(Double.parseDouble(b[0]),Double.parseDouble(b[1])),
154 new LatLon(Double.parseDouble(b[2]),Double.parseDouble(b[3])));
155 DataSource src = new DataSource(bounds, origin);
156 ds.dataSources.add(src);
157 }
158 }
159 } else if (qName.equals("bounds")) {
160 // new style bounds.
161 String minlon = atts.getValue("minlon");
162 String minlat = atts.getValue("minlat");
163 String maxlon = atts.getValue("maxlon");
164 String maxlat = atts.getValue("maxlat");
165 String origin = atts.getValue("origin");
166 if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
167 if (origin == null) origin = generator;
168 Bounds bounds = new Bounds(
169 new LatLon(Double.parseDouble(minlat), Double.parseDouble(minlon)),
170 new LatLon(Double.parseDouble(maxlat), Double.parseDouble(maxlon)));
171 DataSource src = new DataSource(bounds, origin);
172 ds.dataSources.add(src);
173 }
174
175 // ---- PARSING NODES AND WAYS ----
176
177 } else if (qName.equals("node")) {
178 current = new Node(new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon")));
179 readCommon(atts, current);
180 nodes.put(current.id, (Node)current);
181 } else if (qName.equals("way")) {
182 current = new OsmPrimitiveData();
183 readCommon(atts, current);
184 ways.put((OsmPrimitiveData)current, new ArrayList<Long>());
185 } else if (qName.equals("nd")) {
186 Collection<Long> list = ways.get(current);
187 if (list == null)
188 throw new SAXException(tr("Found <nd> element in non-way."));
189 long id = getLong(atts, "ref");
190 if (id == 0)
191 throw new SAXException(tr("<nd> has zero ref"));
192 list.add(id);
193
194 // ---- PARSING RELATIONS ----
195
196 } else if (qName.equals("relation")) {
197 current = new OsmPrimitiveData();
198 readCommon(atts, current);
199 relations.put((OsmPrimitiveData)current, new LinkedList<RelationMemberData>());
200 } else if (qName.equals("member")) {
201 Collection<RelationMemberData> list = relations.get(current);
202 if (list == null)
203 throw new SAXException(tr("Found <member> tag on non-relation."));
204 RelationMemberData emd = new RelationMemberData();
205 emd.relationMember = new RelationMember();
206 emd.id = getLong(atts, "ref");
207 emd.type=atts.getValue("type");
208 emd.relationMember.role = atts.getValue("role");
209
210 if (emd.id == 0)
211 throw new SAXException(tr("Incomplete <member> specification with ref=0"));
212
213 list.add(emd);
214
215 // ---- PARSING TAGS (applicable to all objects) ----
216
217 } else if (qName.equals("tag")) {
218 current.put(atts.getValue("k"), atts.getValue("v"));
219 }
220 } catch (NumberFormatException x) {
221 x.printStackTrace(); // SAXException does not chain correctly
222 throw new SAXException(x.getMessage(), x);
223 } catch (NullPointerException x) {
224 x.printStackTrace(); // SAXException does not chain correctly
225 throw new SAXException(tr("NullPointerException, Possibly some missing tags."), x);
226 }
227 }
228
229 private double getDouble(Attributes atts, String value) {
230 return Double.parseDouble(atts.getValue(value));
231 }
232 }
233
234 /**
235 * Constructor initializes list of allowed protocol versions.
236 */
237 public OsmReader() {
238 // first add the main server version
239 allowedVersions.add(Main.pref.get("osm-server.version", "0.5"));
240 // now also add all compatible versions
241 String[] additionalVersions =
242 Main.pref.get("osm-server.additional-versions", "").split("/,/");
243 if (additionalVersions.length == 1 && additionalVersions[0].length() == 0)
244 additionalVersions = new String[] {};
245 allowedVersions.addAll(Arrays.asList(additionalVersions));
246 }
247
248 /**
249 * Read out the common attributes from atts and put them into this.current.
250 */
251 void readCommon(Attributes atts, OsmPrimitive current) throws SAXException {
252 current.id = getLong(atts, "id");
253 if (current.id == 0)
254 throw new SAXException(tr("Illegal object with id=0"));
255
256 String time = atts.getValue("timestamp");
257 if (time != null && time.length() != 0) {
258 /* Do not parse the date here since it wastes a HUGE amount of time.
259 * Moved into OsmPrimitive.
260 try {
261 current.timestamp = DateParser.parse(time);
262 } catch (ParseException e) {
263 e.printStackTrace();
264 throw new SAXException(tr("Couldn't read time format \"{0}\".",time));
265 }
266 */
267 current.timestamp = time;
268 }
269
270 // user attribute added in 0.4 API
271 String user = atts.getValue("user");
272 if (user != null) {
273 // do not store literally; get object reference for string
274 current.user = User.get(user);
275 }
276
277 // visible attribute added in 0.4 API
278 String visible = atts.getValue("visible");
279 if (visible != null) {
280 current.visible = Boolean.parseBoolean(visible);
281 }
282
283 // oldversion attribute added in 0.6 API
284
285 // Note there is an asymmetry here: the server will send
286 // the version as "version" the client sends it as
287 // "oldversion". So we take both since which we receive will
288 // depend on reading from a file or reading from the server
289
290 String version = atts.getValue("version");
291 if (version != null) {
292 current.version = Integer.parseInt(version);
293 }
294 version = atts.getValue("old_version");
295 if (version != null) {
296 current.version = Integer.parseInt(version);
297 }
298
299 String action = atts.getValue("action");
300 if (action == null)
301 return;
302 if (action.equals("delete"))
303 current.delete(true);
304 else if (action.startsWith("modify"))
305 current.modified = true;
306 }
307 private long getLong(Attributes atts, String value) throws SAXException {
308 String s = atts.getValue(value);
309 if (s == null)
310 throw new SAXException(tr("Missing required attribute \"{0}\".",value));
311 return Long.parseLong(s);
312 }
313
314 private Node findNode(long id) {
315 Node n = nodes.get(id);
316 if (n != null)
317 return n;
318 for (Node node : references.nodes)
319 if (node.id == id)
320 return node;
321 // TODO: This has to be changed to support multiple layers.
322 for (Node node : Main.ds.nodes)
323 if (node.id == id)
324 return new Node(node);
325 return null;
326 }
327
328 private void createWays() {
329 for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) {
330 Way w = new Way();
331 boolean failed = false;
332 for (long id : e.getValue()) {
333 Node n = findNode(id);
334 if (n == null) {
335 failed = true;
336 break;
337 }
338 w.nodes.add(n);
339 }
340 if (failed) continue;
341 e.getKey().copyTo(w);
342 adder.visit(w);
343 }
344
345 }
346
347 /**
348 * Return the Way object with the given id, or null if it doesn't
349 * exist yet. This method only looks at ways stored in the data set.
350 *
351 * @param id
352 * @return way object or null
353 */
354 private Way findWay(long id) {
355 for (Way wy : ds.ways)
356 if (wy.id == id)
357 return wy;
358 for (Way wy : Main.ds.ways)
359 if (wy.id == id)
360 return wy;
361 return null;
362 }
363
364 /**
365 * Return the Relation object with the given id, or null if it doesn't
366 * exist yet. This method only looks at relations stored in the data set.
367 *
368 * @param id
369 * @return relation object or null
370 */
371 private Relation findRelation(long id) {
372 for (Relation e : ds.relations)
373 if (e.id == id)
374 return e;
375 for (Relation e : Main.ds.relations)
376 if (e.id == id)
377 return e;
378 return null;
379 }
380
381 /**
382 * Create relations. This is slightly different than n/s/w because
383 * unlike other objects, relations may reference other relations; it
384 * is not guaranteed that a referenced relation will have been created
385 * before it is referenced. So we have to create all relations first,
386 * and populate them later.
387 */
388 private void createRelations() {
389
390 // pass 1 - create all relations
391 for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
392 Relation en = new Relation();
393 e.getKey().copyTo(en);
394 adder.visit(en);
395 }
396
397 // pass 2 - sort out members
398 for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
399 Relation en = findRelation(e.getKey().id);
400 if (en == null) throw new Error("Failed to create relation " + e.getKey().id);
401
402 for (RelationMemberData emd : e.getValue()) {
403 RelationMember em = emd.relationMember;
404 if (emd.type.equals("node")) {
405 em.member = findNode(emd.id);
406 if (em.member == null) {
407 em.member = new Node(emd.id);
408 adder.visit((Node)em.member);
409 }
410 } else if (emd.type.equals("way")) {
411 em.member = findWay(emd.id);
412 if (em.member == null) {
413 em.member = new Way(emd.id);
414 adder.visit((Way)em.member);
415 }
416 } else if (emd.type.equals("relation")) {
417 em.member = findRelation(emd.id);
418 if (em.member == null) {
419 em.member = new Relation(emd.id);
420 adder.visit((Relation)em.member);
421 }
422 } else {
423 // this is an error.
424 }
425 en.members.add(em);
426 }
427 }
428 }
429
430 /**
431 * Parse the given input source and return the dataset.
432 * @param ref The dataset that is search in for references first. If
433 * the Reference is not found here, Main.ds is searched and a copy of the
434 * elemet found there is returned.
435 */
436 public static DataSet parseDataSet(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
437 OsmReader osm = new OsmReader();
438 osm.references = ref == null ? new DataSet() : ref;
439
440 // phase 1: Parse nodes and read in raw ways
441 InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
442 try {
443 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, osm.new Parser());
444 } catch (ParserConfigurationException e1) {
445 e1.printStackTrace(); // broken SAXException chaining
446 throw new SAXException(e1);
447 }
448
449 if (pleaseWaitDlg != null) {
450 pleaseWaitDlg.progress.setValue(0);
451 pleaseWaitDlg.currentAction.setText(tr("Preparing data..."));
452 }
453
454 for (Node n : osm.nodes.values())
455 osm.adder.visit(n);
456
457 try {
458 osm.createWays();
459 osm.createRelations();
460 } catch (NumberFormatException e) {
461 e.printStackTrace();
462 throw new SAXException(tr("Illformed Node id"));
463 }
464
465 // clear all negative ids (new to this file)
466 for (OsmPrimitive o : osm.ds.allPrimitives())
467 if (o.id < 0)
468 o.id = 0;
469
470 return osm.ds;
471 }
472}
Note: See TracBrowser for help on using the repository browser.