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

Last change on this file since 4490 was 4490, checked in by Don-vip, 13 years ago

see #6886 - Allow JOSM plugin writers to create their own *Reader / *Importer, first for the upcoming PBF plugin.

  • Property svn:eol-style set to native
File size: 23.5 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.InputStream;
7import java.io.InputStreamReader;
8import java.text.MessageFormat;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.regex.Matcher;
12import java.util.regex.Pattern;
13
14import javax.xml.stream.XMLInputFactory;
15import javax.xml.stream.XMLStreamReader;
16import javax.xml.stream.XMLStreamConstants;
17import javax.xml.stream.XMLStreamException;
18import javax.xml.stream.Location;
19
20import org.openstreetmap.josm.data.Bounds;
21import org.openstreetmap.josm.data.coor.LatLon;
22import org.openstreetmap.josm.data.osm.Changeset;
23import org.openstreetmap.josm.data.osm.DataSet;
24import org.openstreetmap.josm.data.osm.DataSource;
25import org.openstreetmap.josm.data.osm.Node;
26import org.openstreetmap.josm.data.osm.NodeData;
27import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
28import org.openstreetmap.josm.data.osm.PrimitiveData;
29import org.openstreetmap.josm.data.osm.Relation;
30import org.openstreetmap.josm.data.osm.RelationData;
31import org.openstreetmap.josm.data.osm.RelationMemberData;
32import org.openstreetmap.josm.data.osm.Tagged;
33import org.openstreetmap.josm.data.osm.User;
34import org.openstreetmap.josm.data.osm.Way;
35import org.openstreetmap.josm.data.osm.WayData;
36import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
37import org.openstreetmap.josm.gui.progress.ProgressMonitor;
38import org.openstreetmap.josm.tools.CheckParameterUtil;
39import org.openstreetmap.josm.tools.DateUtils;
40
41/**
42 * Parser for the Osm Api. Read from an input stream and construct a dataset out of it.
43 *
44 * For each xml element, there is a dedicated method.
45 * The XMLStreamReader cursor points to the start of the element, when the method is
46 * entered, and it must point to the end of the same element, when it is exited.
47 */
48public class OsmReader extends AbstractReader {
49
50 private XMLStreamReader parser;
51
52 /**
53 * constructor (for private use only)
54 *
55 * @see #parseDataSet(InputStream, DataSet, ProgressMonitor)
56 */
57 private OsmReader() {
58 }
59
60 public void setParser(XMLStreamReader parser) {
61 this.parser = parser;
62 }
63
64 protected void throwException(String msg) throws XMLStreamException {
65 throw new OsmParsingException(msg, parser.getLocation());
66 }
67
68 public void parse() throws XMLStreamException {
69 int event = parser.getEventType();
70 while (true) {
71 if (event == XMLStreamConstants.START_ELEMENT) {
72 if (parser.getLocalName().equals("osm") || parser.getLocalName().equals("osmChange")) {
73 parseOsm();
74 } else {
75 parseUnkown();
76 }
77 } else if (event == XMLStreamConstants.END_ELEMENT) {
78 return;
79 }
80 if (parser.hasNext()) {
81 event = parser.next();
82 } else {
83 break;
84 }
85 }
86 parser.close();
87 }
88
89 private void parseOsm() throws XMLStreamException {
90 String v = parser.getAttributeValue(null, "version");
91 if (v == null) {
92 throwException(tr("Missing mandatory attribute ''{0}''.", "version"));
93 }
94 if (!(v.equals("0.5") || v.equals("0.6"))) {
95 throwException(tr("Unsupported version: {0}", v));
96 }
97 ds.setVersion(v);
98 String generator = parser.getAttributeValue(null, "generator");
99 Long uploadChangesetId = null;
100 if (parser.getAttributeValue(null, "upload-changeset") != null) {
101 uploadChangesetId = getLong("upload-changeset");
102 }
103 while (true) {
104 int event = parser.next();
105 if (event == XMLStreamConstants.START_ELEMENT) {
106 if (parser.getLocalName().equals("bounds")) {
107 parseBounds(generator);
108 } else if (parser.getLocalName().equals("node")) {
109 parseNode();
110 } else if (parser.getLocalName().equals("way")) {
111 parseWay();
112 } else if (parser.getLocalName().equals("relation")) {
113 parseRelation();
114 } else if (parser.getLocalName().equals("changeset")) {
115 parseChangeset(uploadChangesetId);
116 } else {
117 parseUnkown();
118 }
119 } else if (event == XMLStreamConstants.END_ELEMENT) {
120 return;
121 }
122 }
123 }
124
125 private void parseBounds(String generator) throws XMLStreamException {
126 String minlon = parser.getAttributeValue(null, "minlon");
127 String minlat = parser.getAttributeValue(null, "minlat");
128 String maxlon = parser.getAttributeValue(null, "maxlon");
129 String maxlat = parser.getAttributeValue(null, "maxlat");
130 String origin = parser.getAttributeValue(null, "origin");
131 if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
132 if (origin == null) {
133 origin = generator;
134 }
135 Bounds bounds = new Bounds(
136 Double.parseDouble(minlat), Double.parseDouble(minlon),
137 Double.parseDouble(maxlat), Double.parseDouble(maxlon));
138 if (bounds.isOutOfTheWorld()) {
139 Bounds copy = new Bounds(bounds);
140 bounds.normalize();
141 System.out.println("Bbox " + copy + " is out of the world, normalized to " + bounds);
142 }
143 DataSource src = new DataSource(bounds, origin);
144 ds.dataSources.add(src);
145 } else {
146 throwException(tr(
147 "Missing mandatory attributes on element ''bounds''. Got minlon=''{0}'',minlat=''{1}'',maxlon=''{3}'',maxlat=''{4}'', origin=''{5}''.",
148 minlon, minlat, maxlon, maxlat, origin
149 ));
150 }
151 jumpToEnd();
152 }
153
154 private void parseNode() throws XMLStreamException {
155 NodeData nd = new NodeData();
156 nd.setCoor(new LatLon(Double.parseDouble(parser.getAttributeValue(null, "lat")), Double.parseDouble(parser.getAttributeValue(null, "lon"))));
157 readCommon(nd);
158 Node n = new Node(nd.getId(), nd.getVersion());
159 n.setVisible(nd.isVisible());
160 n.load(nd);
161 externalIdMap.put(nd.getPrimitiveId(), n);
162 while (true) {
163 int event = parser.next();
164 if (event == XMLStreamConstants.START_ELEMENT) {
165 if (parser.getLocalName().equals("tag")) {
166 parseTag(n);
167 } else {
168 parseUnkown();
169 }
170 } else if (event == XMLStreamConstants.END_ELEMENT) {
171 return;
172 }
173 }
174 }
175
176 private void parseWay() throws XMLStreamException {
177 WayData wd = new WayData();
178 readCommon(wd);
179 Way w = new Way(wd.getId(), wd.getVersion());
180 w.setVisible(wd.isVisible());
181 w.load(wd);
182 externalIdMap.put(wd.getPrimitiveId(), w);
183
184 Collection<Long> nodeIds = new ArrayList<Long>();
185 while (true) {
186 int event = parser.next();
187 if (event == XMLStreamConstants.START_ELEMENT) {
188 if (parser.getLocalName().equals("nd")) {
189 nodeIds.add(parseWayNode(w));
190 } else if (parser.getLocalName().equals("tag")) {
191 parseTag(w);
192 } else {
193 parseUnkown();
194 }
195 } else if (event == XMLStreamConstants.END_ELEMENT) {
196 break;
197 }
198 }
199 if (w.isDeleted() && nodeIds.size() > 0) {
200 System.out.println(tr("Deleted way {0} contains nodes", w.getUniqueId()));
201 nodeIds = new ArrayList<Long>();
202 }
203 ways.put(wd.getUniqueId(), nodeIds);
204 }
205
206 private long parseWayNode(Way w) throws XMLStreamException {
207 if (parser.getAttributeValue(null, "ref") == null) {
208 throwException(
209 tr("Missing mandatory attribute ''{0}'' on <nd> of way {1}.", "ref", w.getUniqueId())
210 );
211 }
212 long id = getLong("ref");
213 if (id == 0) {
214 throwException(
215 tr("Illegal value of attribute ''ref'' of element <nd>. Got {0}.", id)
216 );
217 }
218 jumpToEnd();
219 return id;
220 }
221
222 private void parseRelation() throws XMLStreamException {
223 RelationData rd = new RelationData();
224 readCommon(rd);
225 Relation r = new Relation(rd.getId(), rd.getVersion());
226 r.setVisible(rd.isVisible());
227 r.load(rd);
228 externalIdMap.put(rd.getPrimitiveId(), r);
229
230 Collection<RelationMemberData> members = new ArrayList<RelationMemberData>();
231 while (true) {
232 int event = parser.next();
233 if (event == XMLStreamConstants.START_ELEMENT) {
234 if (parser.getLocalName().equals("member")) {
235 members.add(parseRelationMember(r));
236 } else if (parser.getLocalName().equals("tag")) {
237 parseTag(r);
238 } else {
239 parseUnkown();
240 }
241 } else if (event == XMLStreamConstants.END_ELEMENT) {
242 break;
243 }
244 }
245 if (r.isDeleted() && members.size() > 0) {
246 System.out.println(tr("Deleted relation {0} contains members", r.getUniqueId()));
247 members = new ArrayList<RelationMemberData>();
248 }
249 relations.put(rd.getUniqueId(), members);
250 }
251
252 private RelationMemberData parseRelationMember(Relation r) throws XMLStreamException {
253 String role = null;
254 OsmPrimitiveType type = null;
255 long id = 0;
256 String value = parser.getAttributeValue(null, "ref");
257 if (value == null) {
258 throwException(tr("Missing attribute ''ref'' on member in relation {0}.",r.getUniqueId()));
259 }
260 try {
261 id = Long.parseLong(value);
262 } catch(NumberFormatException e) {
263 throwException(tr("Illegal value for attribute ''ref'' on member in relation {0}. Got {1}", Long.toString(r.getUniqueId()),value));
264 }
265 value = parser.getAttributeValue(null, "type");
266 if (value == null) {
267 throwException(tr("Missing attribute ''type'' on member {0} in relation {1}.", Long.toString(id), Long.toString(r.getUniqueId())));
268 }
269 try {
270 type = OsmPrimitiveType.fromApiTypeName(value);
271 } catch(IllegalArgumentException e) {
272 throwException(tr("Illegal value for attribute ''type'' on member {0} in relation {1}. Got {2}.", Long.toString(id), Long.toString(r.getUniqueId()), value));
273 }
274 value = parser.getAttributeValue(null, "role");
275 role = value;
276
277 if (id == 0) {
278 throwException(tr("Incomplete <member> specification with ref=0"));
279 }
280 jumpToEnd();
281 return new RelationMemberData(role, type, id);
282 }
283
284 private void parseChangeset(Long uploadChangesetId) throws XMLStreamException {
285 long id = getLong("id");
286
287 if (id == uploadChangesetId) {
288 uploadChangeset = new Changeset((int) getLong("id"));
289 while (true) {
290 int event = parser.next();
291 if (event == XMLStreamConstants.START_ELEMENT) {
292 if (parser.getLocalName().equals("tag")) {
293 parseTag(uploadChangeset);
294 } else {
295 parseUnkown();
296 }
297 } else if (event == XMLStreamConstants.END_ELEMENT) {
298 return;
299 }
300 }
301 } else {
302 jumpToEnd(false);
303 }
304 }
305
306 private void parseTag(Tagged t) throws XMLStreamException {
307 String key = parser.getAttributeValue(null, "k");
308 String value = parser.getAttributeValue(null, "v");
309 if (key == null || value == null) {
310 throwException(tr("Missing key or value attribute in tag."));
311 }
312 t.put(key.intern(), value.intern());
313 jumpToEnd();
314 }
315
316 private void parseUnkown(boolean printWarning) throws XMLStreamException {
317 if (printWarning) {
318 System.out.println(tr("Undefined element ''{0}'' found in input stream. Skipping.", parser.getLocalName()));
319 }
320 while (true) {
321 int event = parser.next();
322 if (event == XMLStreamConstants.START_ELEMENT) {
323 parseUnkown(false); /* no more warning for inner elements */
324 } else if (event == XMLStreamConstants.END_ELEMENT) {
325 return;
326 }
327 }
328 }
329
330 private void parseUnkown() throws XMLStreamException {
331 parseUnkown(true);
332 }
333
334 /**
335 * When cursor is at the start of an element, moves it to the end tag of that element.
336 * Nested content is skipped.
337 *
338 * This is basically the same code as parseUnkown(), except for the warnings, which
339 * are displayed for inner elements and not at top level.
340 */
341 private void jumpToEnd(boolean printWarning) throws XMLStreamException {
342 while (true) {
343 int event = parser.next();
344 if (event == XMLStreamConstants.START_ELEMENT) {
345 parseUnkown(printWarning);
346 } else if (event == XMLStreamConstants.END_ELEMENT) {
347 return;
348 }
349 }
350 }
351
352 private void jumpToEnd() throws XMLStreamException {
353 jumpToEnd(true);
354 }
355
356 private User createUser(String uid, String name) throws XMLStreamException {
357 if (uid == null) {
358 if (name == null)
359 return null;
360 return User.createLocalUser(name);
361 }
362 try {
363 long id = Long.parseLong(uid);
364 return User.createOsmUser(id, name);
365 } catch(NumberFormatException e) {
366 throwException(MessageFormat.format("Illegal value for attribute ''uid''. Got ''{0}''.", uid));
367 }
368 return null;
369 }
370
371 /**
372 * Read out the common attributes and put them into current OsmPrimitive.
373 */
374 private void readCommon(PrimitiveData current) throws XMLStreamException {
375 current.setId(getLong("id"));
376 if (current.getUniqueId() == 0) {
377 throwException(tr("Illegal object with ID=0."));
378 }
379
380 String time = parser.getAttributeValue(null, "timestamp");
381 if (time != null && time.length() != 0) {
382 current.setTimestamp(DateUtils.fromString(time));
383 }
384
385 // user attribute added in 0.4 API
386 String user = parser.getAttributeValue(null, "user");
387 // uid attribute added in 0.6 API
388 String uid = parser.getAttributeValue(null, "uid");
389 current.setUser(createUser(uid, user));
390
391 // visible attribute added in 0.4 API
392 String visible = parser.getAttributeValue(null, "visible");
393 if (visible != null) {
394 current.setVisible(Boolean.parseBoolean(visible));
395 }
396
397 String versionString = parser.getAttributeValue(null, "version");
398 int version = 0;
399 if (versionString != null) {
400 try {
401 version = Integer.parseInt(versionString);
402 } catch(NumberFormatException e) {
403 throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", Long.toString(current.getUniqueId()), versionString));
404 }
405 if (ds.getVersion().equals("0.6")){
406 if (version <= 0 && current.getUniqueId() > 0) {
407 throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", Long.toString(current.getUniqueId()), versionString));
408 } else if (version < 0 && current.getUniqueId() <= 0) {
409 System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.getUniqueId(), version, 0, "0.6"));
410 version = 0;
411 }
412 } else if (ds.getVersion().equals("0.5")) {
413 if (version <= 0 && current.getUniqueId() > 0) {
414 System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.getUniqueId(), version, 1, "0.5"));
415 version = 1;
416 } else if (version < 0 && current.getUniqueId() <= 0) {
417 System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.getUniqueId(), version, 0, "0.5"));
418 version = 0;
419 }
420 } else {
421 // should not happen. API version has been checked before
422 throwException(tr("Unknown or unsupported API version. Got {0}.", ds.getVersion()));
423 }
424 } else {
425 // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6
426 //
427 if (current.getUniqueId() > 0 && ds.getVersion() != null && ds.getVersion().equals("0.6")) {
428 throwException(tr("Missing attribute ''version'' on OSM primitive with ID {0}.", Long.toString(current.getUniqueId())));
429 } else if (current.getUniqueId() > 0 && ds.getVersion() != null && ds.getVersion().equals("0.5")) {
430 // default version in 0.5 files for existing primitives
431 System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.getUniqueId(), version, 1, "0.5"));
432 version= 1;
433 } else if (current.getUniqueId() <= 0 && ds.getVersion() != null && ds.getVersion().equals("0.5")) {
434 // default version in 0.5 files for new primitives, no warning necessary. This is
435 // (was) legal in API 0.5
436 version= 0;
437 }
438 }
439 current.setVersion(version);
440
441 String action = parser.getAttributeValue(null, "action");
442 if (action == null) {
443 // do nothing
444 } else if (action.equals("delete")) {
445 current.setDeleted(true);
446 current.setModified(current.isVisible());
447 } else if (action.equals("modify")) {
448 current.setModified(true);
449 }
450
451 String v = parser.getAttributeValue(null, "changeset");
452 if (v == null) {
453 current.setChangesetId(0);
454 } else {
455 try {
456 current.setChangesetId(Integer.parseInt(v));
457 } catch(NumberFormatException e) {
458 if (current.getUniqueId() <= 0) {
459 // for a new primitive we just log a warning
460 System.out.println(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.", v, current.getUniqueId()));
461 current.setChangesetId(0);
462 } else {
463 // for an existing primitive this is a problem
464 throwException(tr("Illegal value for attribute ''changeset''. Got {0}.", v));
465 }
466 }
467 if (current.getChangesetId() <=0) {
468 if (current.getUniqueId() <= 0) {
469 // for a new primitive we just log a warning
470 System.out.println(tr("Illegal value for attribute ''changeset'' on new object {1}. Got {0}. Resetting to 0.", v, current.getUniqueId()));
471 current.setChangesetId(0);
472 } else {
473 // for an existing primitive this is a problem
474 throwException(tr("Illegal value for attribute ''changeset''. Got {0}.", v));
475 }
476 }
477 }
478 }
479
480 private long getLong(String name) throws XMLStreamException {
481 String value = parser.getAttributeValue(null, name);
482 if (value == null) {
483 throwException(tr("Missing required attribute ''{0}''.",name));
484 }
485 try {
486 return Long.parseLong(value);
487 } catch(NumberFormatException e) {
488 throwException(tr("Illegal long value for attribute ''{0}''. Got ''{1}''.",name, value));
489 }
490 return 0; // should not happen
491 }
492
493 private static class OsmParsingException extends XMLStreamException {
494 public OsmParsingException() {
495 super();
496 }
497
498 public OsmParsingException(String msg) {
499 super(msg);
500 }
501
502 public OsmParsingException(String msg, Location location) {
503 super(msg); /* cannot use super(msg, location) because it messes with the message preventing localization */
504 this.location = location;
505 }
506
507 public OsmParsingException(String msg, Location location, Throwable th) {
508 super(msg, th);
509 this.location = location;
510 }
511
512 public OsmParsingException(String msg, Throwable th) {
513 super(msg, th);
514 }
515
516 public OsmParsingException(Throwable th) {
517 super(th);
518 }
519
520 @Override
521 public String getMessage() {
522 String msg = super.getMessage();
523 if (msg == null) {
524 msg = getClass().getName();
525 }
526 if (getLocation() == null)
527 return msg;
528 msg = msg + " " + tr("(at line {0}, column {1})", getLocation().getLineNumber(), getLocation().getColumnNumber());
529 return msg;
530 }
531 }
532
533 /**
534 * Parse the given input source and return the dataset.
535 *
536 * @param source the source input stream. Must not be null.
537 * @param progressMonitor the progress monitor. If null, {@see NullProgressMonitor#INSTANCE} is assumed
538 *
539 * @return the dataset with the parsed data
540 * @throws IllegalDataException thrown if the an error was found while parsing the data from the source
541 * @throws IllegalArgumentException thrown if source is null
542 */
543 public static DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
544 if (progressMonitor == null) {
545 progressMonitor = NullProgressMonitor.INSTANCE;
546 }
547 CheckParameterUtil.ensureParameterNotNull(source, "source");
548 OsmReader reader = new OsmReader();
549 try {
550 progressMonitor.beginTask(tr("Prepare OSM data...", 2));
551 progressMonitor.indeterminateSubTask(tr("Parsing OSM data..."));
552
553 InputStreamReader ir = UTFInputStreamReader.create(source, "UTF-8");
554 XMLStreamReader parser = XMLInputFactory.newInstance().createXMLStreamReader(ir);
555 reader.setParser(parser);
556 reader.parse();
557 progressMonitor.worked(1);
558
559 progressMonitor.indeterminateSubTask(tr("Preparing data set..."));
560 reader.prepareDataSet();
561 progressMonitor.worked(1);
562 return reader.getDataSet();
563 } catch(IllegalDataException e) {
564 throw e;
565 } catch(OsmParsingException e) {
566 throw new IllegalDataException(e.getMessage(), e);
567 } catch(XMLStreamException e) {
568 String msg = e.getMessage();
569 Pattern p = Pattern.compile("Message: (.+)");
570 Matcher m = p.matcher(msg);
571 if (m.find()) {
572 msg = m.group(1);
573 }
574 if (e.getLocation() != null) {
575 throw new IllegalDataException(tr("Line {0} column {1}: ", e.getLocation().getLineNumber(), e.getLocation().getColumnNumber()) + msg, e);
576 } else {
577 throw new IllegalDataException(msg, e);
578 }
579 } catch(Exception e) {
580 throw new IllegalDataException(e);
581 } finally {
582 progressMonitor.finishTask();
583 }
584 }
585}
Note: See TracBrowser for help on using the repository browser.