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

Last change on this file since 1899 was 1899, checked in by jttt, 15 years ago

Add setter/getter for OsmPrimitive.selected

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