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

Last change on this file since 1806 was 1806, checked in by Gubaer, 15 years ago

added OsmServerBackreferenceReader - reads primitives referring to a particular primitive (ways including a node, relations referring to a relation)
Extended relation dialog - now supports querying for parent relation(s)

  • Property svn:eol-style set to native
File size: 19.0 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.Date;
12import java.util.HashMap;
13import java.util.LinkedList;
14import java.util.Map;
15import java.util.Map.Entry;
16import java.util.logging.Logger;
17
18import javax.swing.SwingUtilities;
19import javax.xml.parsers.ParserConfigurationException;
20import javax.xml.parsers.SAXParserFactory;
21
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.gui.PleaseWaitDialog;
34import org.openstreetmap.josm.tools.DateUtils;
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 static private final Logger logger = Logger.getLogger(OsmReader.class.getName());
52
53 /**
54 * The dataset to add parsed objects to.
55 */
56 private DataSet ds = new DataSet();
57 public DataSet getDs() { return ds; }
58
59 /**
60 * The visitor to use to add the data to the set.
61 */
62 private AddVisitor adder = new AddVisitor(ds);
63
64 /**
65 * All read nodes after phase 1.
66 */
67 private Map<Long, Node> nodes = new HashMap<Long, Node>();
68
69
70 /**
71 * constructor (for private use only)
72 *
73 * @see #parseDataSet(InputStream, DataSet, PleaseWaitDialog)
74 * @see #parseDataSetOsm(InputStream, DataSet, PleaseWaitDialog)
75 */
76 private OsmReader() {
77 }
78
79 private static class OsmPrimitiveData {
80 public long id = 0;
81 public Map<String,String> keys = new HashMap<String, String>();
82 public boolean modified = false;
83 public boolean selected = false;
84 public boolean deleted = false;
85 public Date timestamp = new Date();
86 public User user = null;
87 public boolean visible = true;
88 public int version = -1;
89 public LatLon latlon = new LatLon(0,0);
90
91 public void copyTo(OsmPrimitive osm) {
92 osm.id = id;
93 osm.keys = keys;
94 osm.modified = modified;
95 osm.selected = selected;
96 osm.deleted = deleted;
97 osm.setTimestamp(timestamp);
98 osm.user = user;
99 osm.visible = visible;
100 osm.version = version;
101 osm.mappaintStyle = null;
102 }
103
104 public Node createNode() {
105 Node node = new Node(latlon);
106 copyTo(node);
107 return node;
108 }
109
110 public Way createWay() {
111 Way way = new Way(id);
112 copyTo(way);
113 return way;
114 }
115
116 public Relation createRelation() {
117 Relation rel = new Relation(id);
118 copyTo(rel);
119 return rel;
120 }
121 }
122
123 /**
124 * Used as a temporary storage for relation members, before they
125 * are resolved into pointers to real objects.
126 */
127 private static class RelationMemberData {
128 public String type;
129 public long id;
130 public RelationMember relationMember;
131 }
132
133 /**
134 * Data structure for the remaining way objects
135 */
136 private Map<OsmPrimitiveData, Collection<Long>> ways = new HashMap<OsmPrimitiveData, Collection<Long>>();
137
138 /**
139 * Data structure for relation objects
140 */
141 private Map<OsmPrimitiveData, Collection<RelationMemberData>> relations = new HashMap<OsmPrimitiveData, Collection<RelationMemberData>>();
142
143 private class Parser extends DefaultHandler {
144 /**
145 * The current osm primitive to be read.
146 */
147 private OsmPrimitiveData current;
148 private String generator;
149
150 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
151 try {
152 if (qName.equals("osm")) {
153 if (atts == null)
154 throw new SAXException(tr("Unknown version"));
155 String v = atts.getValue("version");
156 if (v == null)
157 throw new SAXException(tr("Version number missing from OSM data"));
158 if (!(v.equals("0.5") || v.equals("0.6")))
159 throw new SAXException(tr("Unknown version: {0}", v));
160 // save generator attribute for later use when creating DataSource objects
161 generator = atts.getValue("generator");
162 ds.version = v;
163
164 } else if (qName.equals("bounds")) {
165 // new style bounds.
166 String minlon = atts.getValue("minlon");
167 String minlat = atts.getValue("minlat");
168 String maxlon = atts.getValue("maxlon");
169 String maxlat = atts.getValue("maxlat");
170 String origin = atts.getValue("origin");
171 if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
172 if (origin == null) {
173 origin = generator;
174 }
175 Bounds bounds = new Bounds(
176 new LatLon(Double.parseDouble(minlat), Double.parseDouble(minlon)),
177 new LatLon(Double.parseDouble(maxlat), Double.parseDouble(maxlon)));
178 DataSource src = new DataSource(bounds, origin);
179 ds.dataSources.add(src);
180 }
181
182 // ---- PARSING NODES AND WAYS ----
183
184 } else if (qName.equals("node")) {
185 current = new OsmPrimitiveData();
186 current.latlon = new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon"));
187 readCommon(atts, current);
188 } else if (qName.equals("way")) {
189 current = new OsmPrimitiveData();
190 readCommon(atts, current);
191 ways.put(current, new ArrayList<Long>());
192 } else if (qName.equals("nd")) {
193 Collection<Long> list = ways.get(current);
194 if (list == null)
195 throw new SAXException(tr("Found <nd> element in non-way."));
196 long id = getLong(atts, "ref");
197 if (id == 0)
198 throw new SAXException(tr("<nd> has zero ref"));
199 list.add(id);
200
201 // ---- PARSING RELATIONS ----
202
203 } else if (qName.equals("relation")) {
204 current = new OsmPrimitiveData();
205 readCommon(atts, current);
206 relations.put(current, new LinkedList<RelationMemberData>());
207 } else if (qName.equals("member")) {
208 Collection<RelationMemberData> list = relations.get(current);
209 if (list == null)
210 throw new SAXException(tr("Found <member> element in non-relation."));
211 RelationMemberData emd = new RelationMemberData();
212 emd.relationMember = new RelationMember();
213 String value = atts.getValue("ref");
214 if (value == null)
215 throw new SAXException(tr("Missing attribute \"ref\" on member in relation {0}",current.id));
216 try {
217 emd.id = Long.parseLong(value);
218 } catch(NumberFormatException e) {
219 throw new SAXException(tr("Illegal value for attribute \"ref\" on member in relation {0}, got {1}", Long.toString(current.id),value));
220 }
221 value = atts.getValue("type");
222 if (value == null)
223 throw new SAXException(tr("Missing attribute \"type\" on member {0} in relation {1}", Long.toString(emd.id), Long.toString(current.id)));
224 if (! (value.equals("way") || value.equals("node") || value.equals("relation")))
225 throw new SAXException(tr("Unexpected \"type\" on member {0} in relation {1}, got {2}.", Long.toString(emd.id), Long.toString(current.id), value));
226 emd.type= value;
227 value = atts.getValue("role");
228 emd.relationMember.role = value;
229
230 if (emd.id == 0)
231 throw new SAXException(tr("Incomplete <member> specification with ref=0"));
232
233 list.add(emd);
234
235 // ---- PARSING TAGS (applicable to all objects) ----
236
237 } else if (qName.equals("tag")) {
238 String key = atts.getValue("k");
239 String value = atts.getValue("v");
240 current.keys.put(key,value);
241 }
242 } catch (NumberFormatException x) {
243 x.printStackTrace(); // SAXException does not chain correctly
244 throw new SAXException(x.getMessage(), x);
245 } catch (NullPointerException x) {
246 x.printStackTrace(); // SAXException does not chain correctly
247 throw new SAXException(tr("NullPointerException, possibly some missing tags."), x);
248 }
249 }
250
251 @Override
252 public void endElement(String uri, String localName, String qName) throws SAXException {
253 if (qName.equals("node")) {
254 nodes.put(current.id, current.createNode());
255 }
256 }
257
258 private double getDouble(Attributes atts, String value) {
259 return Double.parseDouble(atts.getValue(value));
260 }
261 }
262
263 /**
264 * Read out the common attributes from atts and put them into this.current.
265 */
266 void readCommon(Attributes atts, OsmPrimitiveData current) throws SAXException {
267 current.id = getLong(atts, "id");
268 if (current.id == 0)
269 throw new SAXException(tr("Illegal object with id=0"));
270
271 String time = atts.getValue("timestamp");
272 if (time != null && time.length() != 0) {
273 current.timestamp = DateUtils.fromString(time);
274 }
275
276 // user attribute added in 0.4 API
277 String user = atts.getValue("user");
278 if (user != null) {
279 // do not store literally; get object reference for string
280 current.user = User.get(user);
281 }
282
283 // uid attribute added in 0.6 API
284 String uid = atts.getValue("uid");
285 if (uid != null) {
286 if (current.user != null) {
287 current.user.uid = uid;
288 }
289 }
290
291 // visible attribute added in 0.4 API
292 String visible = atts.getValue("visible");
293 if (visible != null) {
294 current.visible = Boolean.parseBoolean(visible);
295 }
296
297 String version = atts.getValue("version");
298 current.version = 0;
299 if (version != null) {
300 try {
301 current.version = Integer.parseInt(version);
302 } catch(NumberFormatException e) {
303 throw new SAXException(tr("Illegal value for attribute \"version\" on OSM primitive with id {0}, got {1}", Long.toString(current.id), version));
304 }
305 } else {
306 // version expected for OSM primitives with an id assigned by the server (id > 0), since API 0.6
307 //
308 if (current.id > 0 && ds.version != null && ds.version.equals("0.6"))
309 throw new SAXException(tr("Missing attribute \"version\" on OSM primitive with id {0}", Long.toString(current.id)));
310 }
311
312 String action = atts.getValue("action");
313 if (action == null)
314 return;
315 if (action.equals("delete")) {
316 current.deleted = true;
317 } else if (action.startsWith("modify")) {
318 current.modified = true;
319 }
320 }
321 private long getLong(Attributes atts, String value) throws SAXException {
322 String s = atts.getValue(value);
323 if (s == null)
324 throw new SAXException(tr("Missing required attribute \"{0}\".",value));
325 return Long.parseLong(s);
326 }
327
328 private Node findNode(long id) {
329 Node n = nodes.get(id);
330 if (n != null)
331 return n;
332 return null;
333 }
334
335 protected void createWays() {
336 for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) {
337 Way w = new Way(e.getKey().id);
338 boolean failed = false;
339 for (long id : e.getValue()) {
340 Node n = findNode(id);
341 if (n == null) {
342 failed = true;
343 break;
344 }
345 w.nodes.add(n);
346 }
347 if (failed) {
348 logger.warning(tr("marked way {0} incomplete because referred nodes are missing in the loaded data", e.getKey().id));
349 e.getKey().copyTo(w);
350 w.incomplete = true;
351 w.nodes.clear();
352 adder.visit(w);
353 } else {
354 e.getKey().copyTo(w);
355 w.incomplete = false;
356 adder.visit(w);
357 }
358 }
359 }
360
361 /**
362 * Return the Way object with the given id, or null if it doesn't
363 * exist yet. This method only looks at ways stored in the already parsed
364 * ways.
365 *
366 * @param id
367 * @return way object or null
368 */
369 private Way findWay(long id) {
370 for (Way way : ds.ways)
371 if (way.id == id)
372 return way;
373 return null;
374 }
375
376 /**
377 * Return the Relation object with the given id, or null if it doesn't
378 * exist yet. This method only looks at relations in the already parsed
379 * relations.
380 *
381 * @param id
382 * @return relation object or null
383 */
384 private Relation findRelation(long id) {
385 for (Relation e : ds.relations)
386 if (e.id == id)
387 return e;
388 return null;
389 }
390
391 /**
392 * Create relations. This is slightly different than n/s/w because
393 * unlike other objects, relations may reference other relations; it
394 * is not guaranteed that a referenced relation will have been created
395 * before it is referenced. So we have to create all relations first,
396 * and populate them later.
397 */
398 private void createRelations() {
399
400 // pass 1 - create all relations
401 for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
402 Relation en = new Relation();
403 e.getKey().copyTo(en);
404 adder.visit(en);
405 }
406
407 // Cache the ways here for much better search performance
408 HashMap<Long, Way> hm = new HashMap<Long, Way>(10000);
409 for (Way wy : ds.ways) {
410 hm.put(wy.id, wy);
411 }
412
413 // pass 2 - sort out members
414 for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
415 Relation en = findRelation(e.getKey().id);
416 if (en == null) throw new Error("Failed to create relation " + e.getKey().id);
417
418 for (RelationMemberData emd : e.getValue()) {
419 RelationMember em = emd.relationMember;
420 if (emd.type.equals("node")) {
421 em.member = findNode(emd.id);
422 if (em.member == null) {
423 em.member = new Node(emd.id);
424 adder.visit((Node)em.member);
425 }
426 } else if (emd.type.equals("way")) {
427 em.member = hm.get(emd.id);
428 if (em.member == null) {
429 em.member = findWay(emd.id);
430 }
431 if (em.member == null) {
432 em.member = new Way(emd.id);
433 adder.visit((Way)em.member);
434 }
435 } else if (emd.type.equals("relation")) {
436 em.member = findRelation(emd.id);
437 if (em.member == null) {
438 em.member = new Relation(emd.id);
439 adder.visit((Relation)em.member);
440 }
441 } else {
442 // this is an error.
443 }
444 en.members.add(em);
445 }
446 }
447 hm = null;
448 }
449
450 /**
451 * Parse the given input source and return the dataset.
452 * @param ref The dataset that is search in for references first. If
453 * the Reference is not found here, Main.ds is searched and a copy of the
454 * element found there is returned.
455 */
456 public static DataSet parseDataSet(InputStream source, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
457 return parseDataSetOsm(source, pleaseWaitDlg).ds;
458 }
459
460 public static OsmReader parseDataSetOsm(InputStream source, final PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
461 OsmReader osm = new OsmReader();
462
463 // phase 1: Parse nodes and read in raw ways
464 InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
465 try {
466 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, osm.new Parser());
467 } catch (ParserConfigurationException e1) {
468 e1.printStackTrace(); // broken SAXException chaining
469 throw new SAXException(e1);
470 }
471
472 SwingUtilities.invokeLater(
473 new Runnable() {
474 public void run() {
475 pleaseWaitDlg.currentAction.setText(tr("Prepare OSM data..."));
476 pleaseWaitDlg.setIndeterminate(true);
477 }
478 }
479 );
480
481 for (Node n : osm.nodes.values()) {
482 osm.adder.visit(n);
483 }
484
485 try {
486 osm.createWays();
487 osm.createRelations();
488 } catch (NumberFormatException e) {
489 e.printStackTrace();
490 throw new SAXException(tr("Ill-formed node id"));
491 }
492
493 // clear all negative ids (new to this file)
494 for (OsmPrimitive o : osm.ds.allPrimitives())
495 if (o.id < 0) {
496 o.id = 0;
497 }
498
499 SwingUtilities.invokeLater(
500 new Runnable() {
501 public void run() {
502 pleaseWaitDlg.setIndeterminate(false);
503 pleaseWaitDlg.progress.setValue(0);
504 }
505 }
506 );
507
508 return osm;
509 }
510}
Note: See TracBrowser for help on using the repository browser.