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

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

apply patch by xeen - #2114

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