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

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

applied patch #2185 by bruce89

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