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

Last change on this file since 1523 was 1523, checked in by framm, 15 years ago
  • Major redesign of how JOSM talks to the OSM server. Connections now all go through a new OsmApi class that finds out which version the server uses. JOSM should now be able to handle 0.5 and 0.6 without configuration change. Config options osm-server.version and osm-server.additional-versions now obsolete. Handling of error and cancel situations might still need some improvement.
  • Property svn:eol-style set to native
File size: 19.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.util.ArrayList;
10import java.util.Collection;
11import java.util.HashMap;
12import java.util.LinkedList;
13import java.util.Map;
14import java.util.Map.Entry;
15
16import javax.xml.parsers.ParserConfigurationException;
17import javax.xml.parsers.SAXParserFactory;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.data.Bounds;
21import org.openstreetmap.josm.data.coor.LatLon;
22import org.openstreetmap.josm.data.osm.DataSet;
23import org.openstreetmap.josm.data.osm.DataSource;
24import org.openstreetmap.josm.data.osm.Node;
25import org.openstreetmap.josm.data.osm.OsmPrimitive;
26import org.openstreetmap.josm.data.osm.Relation;
27import org.openstreetmap.josm.data.osm.RelationMember;
28import org.openstreetmap.josm.data.osm.User;
29import org.openstreetmap.josm.data.osm.Way;
30import org.openstreetmap.josm.data.osm.visitor.AddVisitor;
31import org.openstreetmap.josm.data.osm.visitor.Visitor;
32import org.openstreetmap.josm.gui.PleaseWaitDialog;
33import org.openstreetmap.josm.tools.DateUtils;
34import org.xml.sax.Attributes;
35import org.xml.sax.InputSource;
36import org.xml.sax.SAXException;
37import org.xml.sax.helpers.DefaultHandler;
38
39/**
40 * Parser for the Osm Api. Read from an input stream and construct a dataset out of it.
41 *
42 * Reading process takes place in three phases. During the first phase (including xml parse),
43 * all nodes are read and stored. Other information than nodes are stored in a raw list
44 *
45 * The second phase read all ways out of the remaining objects in the raw list.
46 *
47 * @author Imi
48 */
49public class OsmReader {
50
51// static long tagsN = 0;
52// static long nodesN = 0;
53// static long waysN = 0;
54// static long relationsN = 0;
55// static long membersN = 0;
56
57 static InputStream currSource;
58
59 /**
60 * This is used as (readonly) source for finding missing references when not transferred in the
61 * file.
62 */
63 private DataSet references;
64
65 /**
66 * The dataset to add parsed objects to.
67 */
68 private DataSet ds = new DataSet();
69 public DataSet getDs() { return ds; }
70
71 /**
72 * Record warnings. If there were any data inconsistencies, append
73 * a newline-terminated string.
74 */
75 private String parseNotes = new String();
76 private int parseNotesCount = 0;
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.setTimestamp(getTimestamp());
103 osm.user = user;
104 osm.visible = visible;
105 osm.version = version;
106 osm.mappaintStyle = null;
107 }
108 }
109
110 /**
111 * Used as a temporary storage for relation members, before they
112 * are resolved into pointers to real objects.
113 */
114 private static class RelationMemberData {
115 public String type;
116 public long id;
117 public RelationMember relationMember;
118 }
119
120 /**
121 * Data structure for the remaining way objects
122 */
123 private Map<OsmPrimitiveData, Collection<Long>> ways = new HashMap<OsmPrimitiveData, Collection<Long>>();
124
125 /**
126 * Data structure for relation objects
127 */
128 private Map<OsmPrimitiveData, Collection<RelationMemberData>> relations = new HashMap<OsmPrimitiveData, Collection<RelationMemberData>>();
129
130 private class Parser extends DefaultHandler {
131 /**
132 * The current osm primitive to be read.
133 */
134 private OsmPrimitive current;
135 private String generator;
136 private Map<String, String> keys = new HashMap<String, String>();
137// int n = 0;
138
139 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
140 try {
141// if(n%100000 == 0) {
142// try {
143// FileInputStream fis = (FileInputStream)currSource;
144// FileChannel channel = fis.getChannel();
145// double perc = (((double)channel.position()) / ((double)channel.size()) * 100.0);
146// System.out.format(" " + (int)perc + "%%");
147// }
148// catch(java.lang.ClassCastException cce) {
149// }
150// catch(IOException e) {
151// System.out.format("Error reading file position " + e);
152// }
153// }
154// n++;
155
156 if (qName.equals("osm")) {
157 if (atts == null)
158 throw new SAXException(tr("Unknown version"));
159 String v = atts.getValue("version");
160 if (v == null)
161 throw new SAXException(tr("Version number missing from OSM data"));
162 if (!(v.equals("0.5") || v.equals("0.6")))
163 throw new SAXException(tr("Unknown version: {0}", v));
164 // save generator attribute for later use when creating DataSource objects
165 generator = atts.getValue("generator");
166 ds.version = v;
167
168 } else if (qName.equals("bounds")) {
169 // new style bounds.
170 String minlon = atts.getValue("minlon");
171 String minlat = atts.getValue("minlat");
172 String maxlon = atts.getValue("maxlon");
173 String maxlat = atts.getValue("maxlat");
174 String origin = atts.getValue("origin");
175 if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
176 if (origin == null) origin = generator;
177 Bounds bounds = new Bounds(
178 new LatLon(Double.parseDouble(minlat), Double.parseDouble(minlon)),
179 new LatLon(Double.parseDouble(maxlat), Double.parseDouble(maxlon)));
180 DataSource src = new DataSource(bounds, origin);
181 ds.dataSources.add(src);
182 }
183
184 // ---- PARSING NODES AND WAYS ----
185
186 } else if (qName.equals("node")) {
187// nodesN++;
188 current = new Node(new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon")));
189 readCommon(atts, current);
190 nodes.put(current.id, (Node)current);
191 } else if (qName.equals("way")) {
192// waysN++;
193 current = new OsmPrimitiveData();
194 readCommon(atts, current);
195 ways.put((OsmPrimitiveData)current, new ArrayList<Long>());
196 } else if (qName.equals("nd")) {
197 Collection<Long> list = ways.get(current);
198 if (list == null)
199 throw new SAXException(tr("Found <nd> element in non-way."));
200 long id = getLong(atts, "ref");
201 if (id == 0)
202 throw new SAXException(tr("<nd> has zero ref"));
203 list.add(id);
204
205 // ---- PARSING RELATIONS ----
206
207 } else if (qName.equals("relation")) {
208// relationsN++;
209 current = new OsmPrimitiveData();
210 readCommon(atts, current);
211 relations.put((OsmPrimitiveData)current, new LinkedList<RelationMemberData>());
212 } else if (qName.equals("member")) {
213// membersN++;
214 Collection<RelationMemberData> list = relations.get(current);
215 if (list == null)
216 throw new SAXException(tr("Found <member> element in non-relation."));
217 RelationMemberData emd = new RelationMemberData();
218 emd.relationMember = new RelationMember();
219 emd.id = getLong(atts, "ref");
220 emd.type=atts.getValue("type");
221 emd.relationMember.role = atts.getValue("role");
222
223 if (emd.id == 0)
224 throw new SAXException(tr("Incomplete <member> specification with ref=0"));
225
226 list.add(emd);
227
228 // ---- PARSING TAGS (applicable to all objects) ----
229
230 } else if (qName.equals("tag")) {
231// tagsN++;
232 String key = atts.getValue("k");
233 String internedKey = keys.get(key);
234 if (internedKey == null) {
235 internedKey = key;
236 keys.put(key, key);
237 }
238 current.put(internedKey, atts.getValue("v"));
239 }
240 } catch (NumberFormatException x) {
241 x.printStackTrace(); // SAXException does not chain correctly
242 throw new SAXException(x.getMessage(), x);
243 } catch (NullPointerException x) {
244 x.printStackTrace(); // SAXException does not chain correctly
245 throw new SAXException(tr("NullPointerException, possibly some missing tags."), x);
246 }
247 }
248
249 private double getDouble(Attributes atts, String value) {
250 return Double.parseDouble(atts.getValue(value));
251 }
252 }
253
254 /**
255 * Read out the common attributes from atts and put them into this.current.
256 */
257 void readCommon(Attributes atts, OsmPrimitive current) throws SAXException {
258 current.id = getLong(atts, "id");
259 if (current.id == 0)
260 throw new SAXException(tr("Illegal object with id=0"));
261
262 String time = atts.getValue("timestamp");
263 if (time != null && time.length() != 0) {
264 current.setTimestamp(DateUtils.fromString(time));
265 }
266
267 // user attribute added in 0.4 API
268 String user = atts.getValue("user");
269 if (user != null) {
270 // do not store literally; get object reference for string
271 current.user = User.get(user);
272 }
273
274 // visible attribute added in 0.4 API
275 String visible = atts.getValue("visible");
276 if (visible != null) {
277 current.visible = Boolean.parseBoolean(visible);
278 }
279
280 // oldversion attribute added in 0.6 API
281
282 // Note there is an asymmetry here: the server will send
283 // the version as "version" the client sends it as
284 // "oldversion". So we take both since which we receive will
285 // depend on reading from a file or reading from the server
286
287 String version = atts.getValue("version");
288 if (version != null) {
289 current.version = Integer.parseInt(version);
290 }
291 version = atts.getValue("old_version");
292 if (version != null) {
293 current.version = Integer.parseInt(version);
294 }
295
296 String action = atts.getValue("action");
297 if (action == null)
298 return;
299 if (action.equals("delete"))
300 current.delete(true);
301 else if (action.startsWith("modify"))
302 current.modified = true;
303 }
304 private long getLong(Attributes atts, String value) throws SAXException {
305 String s = atts.getValue(value);
306 if (s == null)
307 throw new SAXException(tr("Missing required attribute \"{0}\".",value));
308 return Long.parseLong(s);
309 }
310
311 private Node findNode(long id) {
312 Node n = nodes.get(id);
313 if (n != null)
314 return n;
315 for (Node node : references.nodes)
316 if (node.id == id)
317 return node;
318 // TODO: This has to be changed to support multiple layers.
319 for (Node node : Main.ds.nodes)
320 if (node.id == id)
321 return new Node(node);
322 return null;
323 }
324
325 private void createWays() {
326 for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) {
327 Way w = new Way();
328 boolean failed = false;
329 for (long id : e.getValue()) {
330 Node n = findNode(id);
331 if (n == null) {
332 /* don't report ALL of them, just a few */
333 if (parseNotesCount++ < 6) {
334 parseNotes += tr("Skipping a way because it includes a node that doesn''t exist: {0}\n", id);
335 } else if (parseNotesCount == 6) {
336 parseNotes += "...\n";
337 }
338 failed = true;
339 break;
340 }
341 w.nodes.add(n);
342 }
343 if (failed) continue;
344 e.getKey().copyTo(w);
345 adder.visit(w);
346 }
347
348 }
349
350 /**
351 * Return the Way object with the given id, or null if it doesn't
352 * exist yet. This method only looks at ways stored in the data set.
353 *
354 * @param id
355 * @return way object or null
356 */
357 private Way findWay(long id) {
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 // Cache the ways here for much better search performance
398 HashMap<Long, Way> hm = new HashMap<Long, Way>(10000);
399 for (Way wy : ds.ways)
400 hm.put(wy.id, wy);
401
402 // pass 2 - sort out members
403 for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
404 Relation en = findRelation(e.getKey().id);
405 if (en == null) throw new Error("Failed to create relation " + e.getKey().id);
406
407 for (RelationMemberData emd : e.getValue()) {
408 RelationMember em = emd.relationMember;
409 if (emd.type.equals("node")) {
410 em.member = findNode(emd.id);
411 if (em.member == null) {
412 em.member = new Node(emd.id);
413 adder.visit((Node)em.member);
414 }
415 } else if (emd.type.equals("way")) {
416 em.member = hm.get(emd.id);
417 if (em.member == null)
418 em.member = findWay(emd.id);
419 if (em.member == null) {
420 em.member = new Way(emd.id);
421 adder.visit((Way)em.member);
422 }
423 } else if (emd.type.equals("relation")) {
424 em.member = findRelation(emd.id);
425 if (em.member == null) {
426 em.member = new Relation(emd.id);
427 adder.visit((Relation)em.member);
428 }
429 } else {
430 // this is an error.
431 }
432 en.members.add(em);
433 }
434 }
435 hm = null;
436 }
437
438 /**
439 * Parse the given input source and return the dataset.
440 * @param ref The dataset that is search in for references first. If
441 * the Reference is not found here, Main.ds is searched and a copy of the
442 * element found there is returned.
443 */
444 public static DataSet parseDataSet(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
445 return parseDataSetOsm(source, ref, pleaseWaitDlg).ds;
446 }
447
448 public static OsmReader parseDataSetOsm(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
449 OsmReader osm = new OsmReader();
450 osm.references = ref == null ? new DataSet() : ref;
451
452 currSource = source;
453
454 // phase 1: Parse nodes and read in raw ways
455 InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
456 try {
457 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, osm.new Parser());
458 } catch (ParserConfigurationException e1) {
459 e1.printStackTrace(); // broken SAXException chaining
460 throw new SAXException(e1);
461 }
462
463 Main.pleaseWaitDlg.currentAction.setText(tr("Prepare OSM data..."));
464 Main.pleaseWaitDlg.setIndeterminate(true);
465
466// System.out.println("Parser finished: Tags " + tagsN + " Nodes " + nodesN + " Ways " + waysN +
467// " Relations " + relationsN + " Members " + membersN);
468
469 for (Node n : osm.nodes.values())
470 osm.adder.visit(n);
471
472 try {
473 osm.createWays();
474 osm.createRelations();
475 } catch (NumberFormatException e) {
476 e.printStackTrace();
477 throw new SAXException(tr("Ill-formed node id"));
478 }
479
480 // clear all negative ids (new to this file)
481 for (OsmPrimitive o : osm.ds.allPrimitives())
482 if (o.id < 0)
483 o.id = 0;
484
485// System.out.println("Data loaded!");
486 Main.pleaseWaitDlg.setIndeterminate(false);
487 Main.pleaseWaitDlg.progress.setValue(0);
488
489 return osm;
490 }
491}
Note: See TracBrowser for help on using the repository browser.