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

Last change on this file since 2198 was 2181, checked in by stoecker, 15 years ago

lots of i18n fixes

  • Property svn:eol-style set to native
File size: 22.6 KB
Line 
1package org.openstreetmap.josm.io;
2
3import static org.openstreetmap.josm.tools.I18n.tr;
4
5import java.io.InputStream;
6import java.io.InputStreamReader;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.Date;
10import java.util.HashMap;
11import java.util.LinkedList;
12import java.util.List;
13import java.util.Map;
14import java.util.logging.Logger;
15
16import javax.xml.parsers.ParserConfigurationException;
17import javax.xml.parsers.SAXParserFactory;
18
19import org.openstreetmap.josm.data.Bounds;
20import org.openstreetmap.josm.data.coor.LatLon;
21import org.openstreetmap.josm.data.osm.DataSet;
22import org.openstreetmap.josm.data.osm.DataSource;
23import org.openstreetmap.josm.data.osm.Node;
24import org.openstreetmap.josm.data.osm.OsmPrimitive;
25import org.openstreetmap.josm.data.osm.Relation;
26import org.openstreetmap.josm.data.osm.RelationMember;
27import org.openstreetmap.josm.data.osm.User;
28import org.openstreetmap.josm.data.osm.Way;
29import org.openstreetmap.josm.gui.progress.ProgressMonitor;
30import org.openstreetmap.josm.tools.DateUtils;
31import org.xml.sax.Attributes;
32import org.xml.sax.InputSource;
33import org.xml.sax.Locator;
34import org.xml.sax.SAXException;
35import org.xml.sax.helpers.DefaultHandler;
36
37/**
38 * Parser for the Osm Api. Read from an input stream and construct a dataset out of it.
39 *
40 */
41public class OsmReader {
42 static private final Logger logger = Logger.getLogger(OsmReader.class.getName());
43
44 /**
45 * The dataset to add parsed objects to.
46 */
47 private DataSet ds = new DataSet();
48
49 /**
50 * Replies the parsed data set
51 *
52 * @return the parsed data set
53 */
54 public DataSet getDataSet() {
55 return ds;
56 }
57
58 /** the map from external ids to read OsmPrimitives. External ids are
59 * longs too, but in contrast to internal ids negative values are used
60 * to identify primitives unknown to the OSM server
61 *
62 * The keys are strings composed as follows
63 * <ul>
64 * <li>"n" + id for nodes</li>
65 * <li>"w" + id for nodes</li>
66 * <li>"r" + id for nodes</li>
67 * </ul>
68 */
69 private Map<String, OsmPrimitive> externalIdMap = new HashMap<String, OsmPrimitive>();
70
71
72 /**
73 * constructor (for private use only)
74 *
75 * @see #parseDataSet(InputStream, DataSet, ProgressMonitor)
76 * @see #parseDataSetOsm(InputStream, DataSet, ProgressMonitor)
77 */
78 private OsmReader() {
79 externalIdMap = new HashMap<String, OsmPrimitive>();
80 }
81
82 private static class OsmPrimitiveData {
83 public long id = 0;
84 public boolean modified = false;
85 public boolean deleted = false;
86 public Date timestamp = new Date();
87 public User user = null;
88 public boolean visible = true;
89 public int version = 0;
90 public LatLon latlon = new LatLon(0,0);
91 private OsmPrimitive primitive;
92
93 public void copyTo(OsmPrimitive osm) {
94 // id < 0 possible if read from a file
95 if (id <= 0) {
96 osm.clearOsmId();
97 } else {
98 osm.setOsmId(id, version);
99 }
100 osm.setDeleted(deleted);
101 osm.setModified(modified);
102 osm.setTimestamp(timestamp);
103 osm.user = user;
104 osm.setVisible(visible);
105 osm.mappaintStyle = null;
106 }
107
108 public Node createNode() {
109 Node node = new Node();
110 node.setCoor(latlon);
111 copyTo(node);
112 primitive = node;
113 return node;
114 }
115
116 public Way createWay() {
117 Way way = new Way();
118 copyTo(way);
119 primitive = way;
120 return way;
121 }
122 public Relation createRelation() {
123 Relation relation= new Relation();
124 copyTo(relation);
125 primitive = relation;
126 return relation;
127 }
128
129 public void rememberTag(String key, String value) {
130 primitive.put(key, value);
131 }
132 }
133
134 /**
135 * Used as a temporary storage for relation members, before they
136 * are resolved into pointers to real objects.
137 */
138 private static class RelationMemberData {
139 public String type;
140 public long id;
141 public String role;
142 }
143
144 /**
145 * Data structure for the remaining way objects
146 */
147 private Map<Long, Collection<Long>> ways = new HashMap<Long, Collection<Long>>();
148
149 /**
150 * Data structure for relation objects
151 */
152 private Map<Long, Collection<RelationMemberData>> relations = new HashMap<Long, Collection<RelationMemberData>>();
153
154 private class Parser extends DefaultHandler {
155 private Locator locator;
156
157 @Override
158 public void setDocumentLocator(Locator locator) {
159 this.locator = locator;
160 }
161
162 protected void throwException(String msg) throws OsmDataParsingException{
163 throw new OsmDataParsingException(msg).rememberLocation(locator);
164 }
165 /**
166 * The current osm primitive to be read.
167 */
168 private OsmPrimitiveData current;
169 private String generator;
170
171 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
172 if (qName.equals("osm")) {
173 if (atts == null) {
174 throwException(tr("Missing mandatory attribute ''{0}'' of XML element {1}.", "version", "osm"));
175 }
176 String v = atts.getValue("version");
177 if (v == null) {
178 throwException(tr("Missing mandatory attribute ''{0}''.", "version"));
179 }
180 if (!(v.equals("0.5") || v.equals("0.6"))) {
181 throwException(tr("Unsupported version: {0}", v));
182 }
183 // save generator attribute for later use when creating DataSource objects
184 generator = atts.getValue("generator");
185 ds.version = v;
186
187 } else if (qName.equals("bounds")) {
188 // new style bounds.
189 String minlon = atts.getValue("minlon");
190 String minlat = atts.getValue("minlat");
191 String maxlon = atts.getValue("maxlon");
192 String maxlat = atts.getValue("maxlat");
193 String origin = atts.getValue("origin");
194 if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
195 if (origin == null) {
196 origin = generator;
197 }
198 Bounds bounds = new Bounds(
199 new LatLon(Double.parseDouble(minlat), Double.parseDouble(minlon)),
200 new LatLon(Double.parseDouble(maxlat), Double.parseDouble(maxlon)));
201 DataSource src = new DataSource(bounds, origin);
202 ds.dataSources.add(src);
203 } else {
204 throwException(tr(
205 "Missing manadatory attributes on element ''bounds''. Got minlon=''{0}'',minlat=''{1}00,maxlon=''{3}'',maxlat=''{4}'', origin=''{5}''.",
206 minlon, minlat, maxlon, maxlat, origin
207 ));
208 }
209
210 // ---- PARSING NODES AND WAYS ----
211
212 } else if (qName.equals("node")) {
213 current = new OsmPrimitiveData();
214 current.latlon = new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon"));
215 readCommon(atts, current);
216 Node n = current.createNode();
217 externalIdMap.put("n"+current.id, n);
218 } else if (qName.equals("way")) {
219 current = new OsmPrimitiveData();
220 readCommon(atts, current);
221 Way w = current.createWay();
222 externalIdMap.put("w"+current.id, w);
223 ways.put(current.id, new ArrayList<Long>());
224 } else if (qName.equals("nd")) {
225 Collection<Long> list = ways.get(current.id);
226 if (list == null) {
227 throwException(
228 tr("Found XML element <nd> not as direct child of element <way>.")
229 );
230 }
231 if (atts.getValue("ref") == null) {
232 throwException(
233 tr("Missing mandatory attribute ''{0}'' on <nd> of way {1}.", "ref", current.id)
234 );
235 }
236 long id = getLong(atts, "ref");
237 if (id == 0) {
238 throwException(
239 tr("Illegal value of attribute ''ref'' of element <nd>. Got {0}.", id)
240 );
241 }
242 list.add(id);
243
244 // ---- PARSING RELATIONS ----
245
246 } else if (qName.equals("relation")) {
247 current = new OsmPrimitiveData();
248 readCommon(atts, current);
249 Relation r = current.createRelation();
250 externalIdMap.put("r"+current.id, r );
251 relations.put(current.id, new LinkedList<RelationMemberData>());
252 } else if (qName.equals("member")) {
253 Collection<RelationMemberData> list = relations.get(current.id);
254 if (list == null) {
255 throwException(
256 tr("Found XML element <member> not as direct child of element <relation>.")
257 );
258 }
259 RelationMemberData emd = new RelationMemberData();
260 String value = atts.getValue("ref");
261 if (value == null) {
262 throwException(tr("Missing attribute ''ref'' on member in relation {0}.",current.id));
263 }
264 try {
265 emd.id = Long.parseLong(value);
266 } catch(NumberFormatException e) {
267 throwException(tr("Illegal value for attribute ''ref'' on member in relation {0}. Got {1}", Long.toString(current.id),value));
268 }
269 value = atts.getValue("type");
270 if (value == null) {
271 throwException(tr("Missing attribute ''type'' on member {0} in relation {1}.", Long.toString(emd.id), Long.toString(current.id)));
272 }
273 if (! (value.equals("way") || value.equals("node") || value.equals("relation"))) {
274 throwException(tr("Illegal value for attribute ''type'' on member {0} in relation {1}. Got {2}.", Long.toString(emd.id), Long.toString(current.id), value));
275 }
276 emd.type= value;
277 value = atts.getValue("role");
278 emd.role = value;
279
280 if (emd.id == 0) {
281 throwException(tr("Incomplete <member> specification with ref=0"));
282 }
283
284 list.add(emd);
285
286 // ---- PARSING TAGS (applicable to all objects) ----
287
288 } else if (qName.equals("tag")) {
289 String key = atts.getValue("k");
290 String value = atts.getValue("v");
291 current.rememberTag(key, value);
292 } else {
293 System.out.println(tr("Undefined element ''{0}'' found in input stream. Skipping.", qName));
294 }
295 }
296
297 private double getDouble(Attributes atts, String value) {
298 return Double.parseDouble(atts.getValue(value));
299 }
300
301 private User createUser(String uid, String name) throws SAXException {
302 if (uid == null) {
303 if (name == null)
304 return null;
305 return User.createLocalUser(name);
306 }
307 try {
308 long id = Long.parseLong(uid);
309 return User.createOsmUser(id, name);
310 } catch(NumberFormatException e) {
311 throwException(tr("Illegal value for attribute ''uid''. Got ''{0}''.", uid));
312 }
313 return null;
314 }
315 /**
316 * Read out the common attributes from atts and put them into this.current.
317 */
318 void readCommon(Attributes atts, OsmPrimitiveData current) throws SAXException {
319 current.id = getLong(atts, "id");
320 if (current.id == 0) {
321 throwException(tr("Illegal object with ID=0."));
322 }
323
324 String time = atts.getValue("timestamp");
325 if (time != null && time.length() != 0) {
326 current.timestamp = DateUtils.fromString(time);
327 }
328
329 // user attribute added in 0.4 API
330 String user = atts.getValue("user");
331 // uid attribute added in 0.6 API
332 String uid = atts.getValue("uid");
333 current.user = createUser(uid, user);
334
335 // visible attribute added in 0.4 API
336 String visible = atts.getValue("visible");
337 if (visible != null) {
338 current.visible = Boolean.parseBoolean(visible);
339 }
340
341 String version = atts.getValue("version");
342 current.version = 0;
343 if (version != null) {
344 try {
345 current.version = Integer.parseInt(version);
346 } catch(NumberFormatException e) {
347 throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", Long.toString(current.id), version));
348 }
349 if (ds.version.equals("0.6")){
350 if (current.version <= 0 && current.id > 0) {
351 throwException(tr("Illegal value for attribute ''version'' on OSM primitive with ID {0}. Got {1}.", Long.toString(current.id), version));
352 } else if (current.version < 0 && current.id <=0) {
353 System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.id, current.version, 0, "0.6"));
354 current.version = 0;
355 }
356 } else if (ds.version.equals("0.5")) {
357 if (current.version <= 0 && current.id > 0) {
358 System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.id, current.version, 1, "0.5"));
359 current.version = 1;
360 } else if (current.version < 0 && current.id <=0) {
361 System.out.println(tr("WARNING: Normalizing value of attribute ''version'' of element {0} to {2}, API version is ''{3}''. Got {1}.", current.id, current.version, 0, "0.5"));
362 current.version = 0;
363 }
364 } else {
365 // should not happen. API version has been checked before
366 throwException(tr("Unknown or unsupported API version. Got {0}.", ds.version));
367 }
368 } else {
369 // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6
370 //
371 if (current.id > 0 && ds.version != null && ds.version.equals("0.6")) {
372 throwException(tr("Missing attribute ''version'' on OSM primitive with ID {0}.", Long.toString(current.id)));
373 }
374 }
375
376 String action = atts.getValue("action");
377 if (action == null)
378 return;
379 if (action.equals("delete")) {
380 current.deleted = true;
381 } else if (action.startsWith("modify")) {
382 current.modified = true;
383 }
384 }
385
386 private long getLong(Attributes atts, String name) throws SAXException {
387 String value = atts.getValue(name);
388 if (value == null) {
389 throwException(tr("Missing required attribute ''{0}''.",name));
390 }
391 try {
392 return Long.parseLong(value);
393 } catch(NumberFormatException e) {
394 throwException(tr("Illegal long value for attribute ''{0}''. Got ''{1}''.",name, value));
395 }
396 return 0; // should not happen
397 }
398 }
399
400
401 /**
402 * Processes the ways after parsing. Rebuilds the list of nodes of each way and
403 * adds the way to the dataset
404 *
405 * @throws IllegalDataException thrown if a data integrity problem is detected
406 */
407 protected void processWaysAfterParsing() throws IllegalDataException{
408 for (Long externalWayId: ways.keySet()) {
409 Way w = (Way)externalIdMap.get("w" + externalWayId);
410 boolean incomplete = false;
411 List<Node> wayNodes = new ArrayList<Node>();
412 for (long id : ways.get(externalWayId)) {
413 Node n = (Node)externalIdMap.get("n" +id);
414 if (n == null) {
415 if (id <= 0)
416 throw new IllegalDataException (
417 tr(
418 "Way with external ID ''{0}'' includes missing node with external ID ''{1}''.",
419 externalWayId,
420 id
421 )
422 );
423 n = new Node(id);
424 n.incomplete = true;
425 incomplete = true;
426 }
427 wayNodes.add(n);
428 }
429 w.setNodes(wayNodes);
430 if (incomplete) {
431 logger.warning(tr("Marked way {0} with {1} nodes incomplete because at least one node was missing in the " +
432 "loaded data and is therefore incomplete too.", externalWayId, w.getNodesCount()));
433 w.incomplete = true;
434 ds.addPrimitive(w);
435 } else {
436 w.incomplete = false;
437 ds.addPrimitive(w);
438 }
439 }
440 }
441
442 /**
443 * Processes the parsed nodes after parsing. Just adds them to
444 * the dataset
445 *
446 */
447 protected void processNodesAfterParsing() {
448 for (OsmPrimitive primitive: externalIdMap.values()) {
449 if (primitive instanceof Node) {
450 this.ds.addPrimitive(primitive);
451 }
452 }
453 }
454
455 /**
456 * Completes the parsed relations with its members.
457 *
458 * @throws IllegalDataException thrown if a data integrity problem is detected, i.e. if a
459 * relation member refers to a local primitive which wasn't available in the data
460 *
461 */
462 private void processRelationsAfterParsing() throws IllegalDataException {
463 for (Long externalRelationId : relations.keySet()) {
464 Relation relation = (Relation) externalIdMap.get("r" +externalRelationId);
465 List<RelationMember> relationMembers = new ArrayList<RelationMember>();
466 for (RelationMemberData rm : relations.get(externalRelationId)) {
467 OsmPrimitive primitive = null;
468
469 // lookup the member from the map of already created primitives
470 //
471 if (rm.type.equals("node")) {
472 primitive = externalIdMap.get("n" + rm.id);
473 } else if (rm.type.equals("way")) {
474 primitive = externalIdMap.get("w" + rm.id);
475 } else if (rm.type.equals("relation")) {
476 primitive = externalIdMap.get("r" + rm.id);
477 } else
478 throw new IllegalDataException(
479 tr("Unknown relation member type ''{0}'' in relation with external id ''{1}''.", rm.type,externalRelationId)
480 );
481
482 if (primitive == null) {
483 if (rm.id <= 0)
484 // relation member refers to a primitive with a negative id which was not
485 // found in the data. This is always a data integrity problem and we abort
486 // with an exception
487 //
488 throw new IllegalDataException(
489 tr(
490 "Relation with external id ''{0}'' refers to missing primitive with external id ''{1}''.",
491 externalRelationId,
492 rm.id
493 )
494 );
495
496 // member refers to OSM primitive which was not present in the parsed data
497 // -> create a new incomplete primitive and add it to the dataset
498 //
499 if (rm.type.equals("node")) {
500 primitive = new Node(rm.id);
501 } else if (rm.type.equals("way")) {
502 primitive = new Way(rm.id);
503 } else if (rm.type.equals("relation")) {
504 primitive = new Relation(rm.id);
505 } else {
506 // can't happen, we've been testing for valid member types
507 // at the beginning of this method
508 //
509 }
510 ds.addPrimitive(primitive);
511 }
512 relationMembers.add(new RelationMember(rm.role, primitive));
513 }
514 relation.setMembers(relationMembers);
515 ds.addPrimitive(relation);
516 }
517 }
518
519 /**
520 * Parse the given input source and return the dataset.
521 *
522 * @param source the source input stream
523 * @param progressMonitor the progress monitor
524 *
525 * @return the dataset with the parsed data
526 * @throws IllegalDataException thrown if the an error was found while parsing the data from the source
527 */
528 public static DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
529 OsmReader reader = new OsmReader();
530 try {
531 progressMonitor.beginTask(tr("Prepare OSM data...", 2));
532 progressMonitor.subTask(tr("Parsing OSM data..."));
533 InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
534 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, reader.new Parser());
535 progressMonitor.worked(1);
536
537 progressMonitor.subTask(tr("Preparing data set..."));
538 reader.processNodesAfterParsing();
539 reader.processWaysAfterParsing();
540 reader.processRelationsAfterParsing();
541 progressMonitor.worked(1);
542 return reader.getDataSet();
543 } catch(IllegalDataException e) {
544 throw e;
545 } catch(ParserConfigurationException e) {
546 throw new IllegalDataException(e.getMessage(), e);
547 } catch(SAXException e) {
548 throw new IllegalDataException(e.getMessage(), e);
549 } catch(Exception e) {
550 throw new IllegalDataException(e);
551 } finally {
552 progressMonitor.finishTask();
553 }
554 }
555}
Note: See TracBrowser for help on using the repository browser.