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

Last change on this file since 1023 was 769, checked in by framm, 16 years ago
  • add support for <bounds> tag in .osm files using four attributes minlat,maxlat,minlon,maxlon. old <bound> still supported for a transition period. fixes #1112.
  • Property svn:eol-style set to native
File size: 19.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 /**
53 * This is used as (readonly) source for finding missing references when not transferred in the
54 * file.
55 */
56 private DataSet references;
57
58 /**
59 * The dataset to add parsed objects to.
60 */
61 private DataSet ds = new DataSet();
62
63 /**
64 * The visitor to use to add the data to the set.
65 */
66 private AddVisitor adder = new AddVisitor(ds);
67
68 /**
69 * All read nodes after phase 1.
70 */
71 private Map<Long, Node> nodes = new HashMap<Long, Node>();
72
73 // TODO: What the hack? Is this really from me? Please, clean this up!
74 private static class OsmPrimitiveData extends OsmPrimitive {
75 @Override public void visit(Visitor visitor) {}
76 public int compareTo(OsmPrimitive o) {return 0;}
77
78 public void copyTo(OsmPrimitive osm) {
79 osm.id = id;
80 osm.keys = keys;
81 osm.modified = modified;
82 osm.selected = selected;
83 osm.deleted = deleted;
84 osm.timestamp = timestamp;
85 osm.user = user;
86 osm.visible = visible;
87 osm.version = version;
88 osm.checkTagged();
89 osm.checkDirectionTagged();
90 }
91 }
92
93 /**
94 * Used as a temporary storage for relation members, before they
95 * are resolved into pointers to real objects.
96 */
97 private static class RelationMemberData {
98 public String type;
99 public long id;
100 public RelationMember relationMember;
101 }
102
103 /**
104 * Data structure for the remaining way objects
105 */
106 private Map<OsmPrimitiveData, Collection<Long>> ways = new HashMap<OsmPrimitiveData, Collection<Long>>();
107
108 /**
109 * Data structure for relation objects
110 */
111 private Map<OsmPrimitiveData, Collection<RelationMemberData>> relations = new HashMap<OsmPrimitiveData, Collection<RelationMemberData>>();
112
113 /**
114 * List of protocol versions that will be accepted on reading
115 */
116 private HashSet<String> allowedVersions = new HashSet<String>();
117
118 private class Parser extends DefaultHandler {
119 /**
120 * The current osm primitive to be read.
121 */
122 private OsmPrimitive current;
123 private String generator;
124
125 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
126 try {
127 if (qName.equals("osm")) {
128 if (atts == null)
129 throw new SAXException(tr("Unknown version"));
130 if (!allowedVersions.contains(atts.getValue("version")))
131 throw new SAXException(tr("Unknown version")+": "+atts.getValue("version"));
132 // save generator attribute for later use when creating DataSource objects
133 generator = atts.getValue("generator");
134
135
136 } else if (qName.equals("bound")) {
137 // old style bounds.
138 // TODO: remove this around 1st October 2008.
139 // - this is a bit of a hack; since we still write out old style bound objects,
140 // we don't want to load them both. so when writing, we add a "note" tag the our
141 // old-style bound, and when reading, ignore those with a "note".
142 String note = atts.getValue("note");
143 if (note == null) {
144 System.out.println("Notice: old style <bound> element detected; support for these will be dropped in a future release.");
145 String bbox = atts.getValue("box");
146 String origin = atts.getValue("origin");
147 if (origin == null) origin = "";
148 if (bbox != null) {
149 String[] b = bbox.split(",");
150 Bounds bounds = new Bounds();
151 if (b.length == 4)
152 bounds = new Bounds(
153 new LatLon(Double.parseDouble(b[0]),Double.parseDouble(b[1])),
154 new LatLon(Double.parseDouble(b[2]),Double.parseDouble(b[3])));
155 DataSource src = new DataSource(bounds, origin);
156 ds.dataSources.add(src);
157 }
158 }
159 } else if (qName.equals("bounds")) {
160 // new style bounds.
161 String minlon = atts.getValue("minlon");
162 String minlat = atts.getValue("minlat");
163 String maxlon = atts.getValue("maxlon");
164 String maxlat = atts.getValue("maxlat");
165 String origin = atts.getValue("origin");
166 if (minlon != null && maxlon != null && minlat != null && maxlat != null) {
167 if (origin == null) origin = generator;
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 Node(new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon")));
179 readCommon(atts, current);
180 nodes.put(current.id, (Node)current);
181 } else if (qName.equals("way")) {
182 current = new OsmPrimitiveData();
183 readCommon(atts, current);
184 ways.put((OsmPrimitiveData)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((OsmPrimitiveData)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> tag on non-relation."));
204 RelationMemberData emd = new RelationMemberData();
205 emd.relationMember = new RelationMember();
206 emd.id = getLong(atts, "ref");
207 emd.type=atts.getValue("type");
208 emd.relationMember.role = atts.getValue("role");
209
210 if (emd.id == 0)
211 throw new SAXException(tr("Incomplete <member> specification with ref=0"));
212
213 list.add(emd);
214
215 // ---- PARSING TAGS (applicable to all objects) ----
216
217 } else if (qName.equals("tag")) {
218 current.put(atts.getValue("k"), atts.getValue("v"));
219 }
220 } catch (NumberFormatException x) {
221 x.printStackTrace(); // SAXException does not chain correctly
222 throw new SAXException(x.getMessage(), x);
223 } catch (NullPointerException x) {
224 x.printStackTrace(); // SAXException does not chain correctly
225 throw new SAXException(tr("NullPointerException, Possibly some missing tags."), x);
226 }
227 }
228
229 private double getDouble(Attributes atts, String value) {
230 return Double.parseDouble(atts.getValue(value));
231 }
232 }
233
234 /**
235 * Constructor initializes list of allowed protocol versions.
236 */
237 public OsmReader() {
238 // first add the main server version
239 allowedVersions.add(Main.pref.get("osm-server.version", "0.5"));
240 // now also add all compatible versions
241 String[] additionalVersions =
242 Main.pref.get("osm-server.additional-versions", "").split("/,/");
243 if (additionalVersions.length == 1 && additionalVersions[0].length() == 0)
244 additionalVersions = new String[] {};
245 allowedVersions.addAll(Arrays.asList(additionalVersions));
246 }
247
248 /**
249 * Read out the common attributes from atts and put them into this.current.
250 */
251 void readCommon(Attributes atts, OsmPrimitive current) throws SAXException {
252 current.id = getLong(atts, "id");
253 if (current.id == 0)
254 throw new SAXException(tr("Illegal object with id=0"));
255
256 String time = atts.getValue("timestamp");
257 if (time != null && time.length() != 0) {
258 /* Do not parse the date here since it wastes a HUGE amount of time.
259 * Moved into OsmPrimitive.
260 try {
261 current.timestamp = DateParser.parse(time);
262 } catch (ParseException e) {
263 e.printStackTrace();
264 throw new SAXException(tr("Couldn't read time format \"{0}\".",time));
265 }
266 */
267 current.timestamp = time;
268 }
269
270 // user attribute added in 0.4 API
271 String user = atts.getValue("user");
272 if (user != null) {
273 // do not store literally; get object reference for string
274 current.user = User.get(user);
275 }
276
277 // visible attribute added in 0.4 API
278 String visible = atts.getValue("visible");
279 if (visible != null) {
280 current.visible = Boolean.parseBoolean(visible);
281 }
282
283 // oldversion attribute added in 0.6 API
284
285 // Note there is an asymmetry here: the server will send
286 // the version as "version" the client sends it as
287 // "oldversion". So we take both since which we receive will
288 // depend on reading from a file or reading from the server
289
290 String version = atts.getValue("version");
291 if (version != null) {
292 current.version = Integer.parseInt(version);
293 }
294 version = atts.getValue("old_version");
295 if (version != null) {
296 current.version = Integer.parseInt(version);
297 }
298
299 String action = atts.getValue("action");
300 if (action == null)
301 return;
302 if (action.equals("delete"))
303 current.delete(true);
304 else if (action.startsWith("modify"))
305 current.modified = true;
306 }
307 private long getLong(Attributes atts, String value) throws SAXException {
308 String s = atts.getValue(value);
309 if (s == null)
310 throw new SAXException(tr("Missing required attribute \"{0}\".",value));
311 return Long.parseLong(s);
312 }
313
314 private Node findNode(long id) {
315 Node n = nodes.get(id);
316 if (n != null)
317 return n;
318 for (Node node : references.nodes)
319 if (node.id == id)
320 return node;
321 // TODO: This has to be changed to support multiple layers.
322 for (Node node : Main.ds.nodes)
323 if (node.id == id)
324 return new Node(node);
325 return null;
326 }
327
328 private void createWays() {
329 for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) {
330 Way w = new Way();
331 boolean failed = false;
332 for (long id : e.getValue()) {
333 Node n = findNode(id);
334 if (n == null) {
335 failed = true;
336 break;
337 }
338 w.nodes.add(n);
339 }
340 if (failed) continue;
341 e.getKey().copyTo(w);
342 adder.visit(w);
343 }
344
345 }
346
347 /**
348 * Return the Way object with the given id, or null if it doesn't
349 * exist yet. This method only looks at ways stored in the data set.
350 *
351 * @param id
352 * @return way object or null
353 */
354 private Way findWay(long id) {
355 for (Way wy : ds.ways)
356 if (wy.id == id)
357 return wy;
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 // pass 2 - sort out members
398 for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
399 Relation en = findRelation(e.getKey().id);
400 if (en == null) throw new Error("Failed to create relation " + e.getKey().id);
401
402 for (RelationMemberData emd : e.getValue()) {
403 RelationMember em = emd.relationMember;
404 if (emd.type.equals("node")) {
405 em.member = findNode(emd.id);
406 if (em.member == null) {
407 em.member = new Node(emd.id);
408 adder.visit((Node)em.member);
409 }
410 } else if (emd.type.equals("way")) {
411 em.member = findWay(emd.id);
412 if (em.member == null) {
413 em.member = new Way(emd.id);
414 adder.visit((Way)em.member);
415 }
416 } else if (emd.type.equals("relation")) {
417 em.member = findRelation(emd.id);
418 if (em.member == null) {
419 em.member = new Relation(emd.id);
420 adder.visit((Relation)em.member);
421 }
422 } else {
423 // this is an error.
424 }
425 en.members.add(em);
426 }
427 }
428 }
429
430 /**
431 * Parse the given input source and return the dataset.
432 * @param ref The dataset that is search in for references first. If
433 * the Reference is not found here, Main.ds is searched and a copy of the
434 * elemet found there is returned.
435 */
436 public static DataSet parseDataSet(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
437 OsmReader osm = new OsmReader();
438 osm.references = ref == null ? new DataSet() : ref;
439
440 // phase 1: Parse nodes and read in raw ways
441 InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
442 try {
443 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, osm.new Parser());
444 } catch (ParserConfigurationException e1) {
445 e1.printStackTrace(); // broken SAXException chaining
446 throw new SAXException(e1);
447 }
448
449 if (pleaseWaitDlg != null) {
450 pleaseWaitDlg.progress.setValue(0);
451 pleaseWaitDlg.currentAction.setText(tr("Preparing data..."));
452 }
453
454 for (Node n : osm.nodes.values())
455 osm.adder.visit(n);
456
457 try {
458 osm.createWays();
459 osm.createRelations();
460 } catch (NumberFormatException e) {
461 e.printStackTrace();
462 throw new SAXException(tr("Illformed Node id"));
463 }
464
465 // clear all negative ids (new to this file)
466 for (OsmPrimitive o : osm.ds.allPrimitives())
467 if (o.id < 0)
468 o.id = 0;
469
470 return osm.ds;
471 }
472}
Note: See TracBrowser for help on using the repository browser.