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

Last change on this file since 362 was 362, checked in by framm, 17 years ago
  • alleviated #388 by parsing the timestamps only when they are needed.
File size: 13.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.text.ParseException;
10import java.util.ArrayList;
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.Date;
14import java.util.HashMap;
15import java.util.HashSet;
16import java.util.LinkedList;
17import java.util.Map;
18import java.util.Map.Entry;
19
20import javax.xml.parsers.ParserConfigurationException;
21import javax.xml.parsers.SAXParserFactory;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.data.Bounds;
25import org.openstreetmap.josm.data.coor.LatLon;
26import org.openstreetmap.josm.data.osm.DataSet;
27import org.openstreetmap.josm.data.osm.DataSource;
28import org.openstreetmap.josm.data.osm.Relation;
29import org.openstreetmap.josm.data.osm.RelationMember;
30import org.openstreetmap.josm.data.osm.Node;
31import org.openstreetmap.josm.data.osm.OsmPrimitive;
32import org.openstreetmap.josm.data.osm.User;
33import org.openstreetmap.josm.data.osm.Way;
34import org.openstreetmap.josm.data.osm.visitor.AddVisitor;
35import org.openstreetmap.josm.data.osm.visitor.Visitor;
36import org.openstreetmap.josm.gui.PleaseWaitDialog;
37import org.openstreetmap.josm.tools.DateParser;
38import org.xml.sax.Attributes;
39import org.xml.sax.InputSource;
40import org.xml.sax.SAXException;
41import org.xml.sax.helpers.DefaultHandler;
42
43/**
44 * Parser for the Osm Api. Read from an input stream and construct a dataset out of it.
45 *
46 * Reading process takes place in three phases. During the first phase (including xml parse),
47 * all nodes are read and stored. Other information than nodes are stored in a raw list
48 *
49 * The second phase read all ways out of the remaining objects in the raw list.
50 *
51 * @author Imi
52 */
53public class OsmReader {
54
55 /**
56 * This is used as (readonly) source for finding missing references when not transferred in the
57 * file.
58 */
59 private DataSet references;
60
61 /**
62 * The dataset to add parsed objects to.
63 */
64 private DataSet ds = new DataSet();
65
66 /**
67 * The visitor to use to add the data to the set.
68 */
69 private AddVisitor adder = new AddVisitor(ds);
70
71 /**
72 * All read nodes after phase 1.
73 */
74 private Map<Long, Node> nodes = new HashMap<Long, Node>();
75
76 // TODO: What the hack? Is this really from me? Please, clean this up!
77 private static class OsmPrimitiveData extends OsmPrimitive {
78 @Override public void visit(Visitor visitor) {}
79 public int compareTo(OsmPrimitive o) {return 0;}
80
81 public void copyTo(OsmPrimitive osm) {
82 osm.id = id;
83 osm.keys = keys;
84 osm.modified = modified;
85 osm.selected = selected;
86 osm.deleted = deleted;
87 osm.timestamp = timestamp;
88 osm.user = user;
89 osm.visible = visible;
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
124 @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
125 try {
126 if (qName.equals("osm")) {
127 if (atts == null)
128 throw new SAXException(tr("Unknown version"));
129 if (!allowedVersions.contains(atts.getValue("version")))
130 throw new SAXException(tr("Unknown version")+": "+atts.getValue("version"));
131 } else if (qName.equals("bound")) {
132 String bbox = atts.getValue("box");
133 String origin = atts.getValue("origin");
134 if (bbox != null) {
135 String[] b = bbox.split(",");
136 Bounds bounds = null;
137 if (b.length == 4)
138 bounds = new Bounds(
139 new LatLon(Double.parseDouble(b[0]),Double.parseDouble(b[1])),
140 new LatLon(Double.parseDouble(b[2]),Double.parseDouble(b[3])));
141 DataSource src = new DataSource(bounds, origin);
142 ds.dataSources.add(src);
143 }
144
145 // ---- PARSING NODES AND WAYS ----
146
147 } else if (qName.equals("node")) {
148 current = new Node(new LatLon(getDouble(atts, "lat"), getDouble(atts, "lon")));
149 readCommon(atts, current);
150 nodes.put(current.id, (Node)current);
151 } else if (qName.equals("way")) {
152 current = new OsmPrimitiveData();
153 readCommon(atts, current);
154 ways.put((OsmPrimitiveData)current, new ArrayList<Long>());
155 } else if (qName.equals("nd")) {
156 Collection<Long> list = ways.get(current);
157 if (list == null)
158 throw new SAXException(tr("Found <nd> element in non-way."));
159 long id = getLong(atts, "ref");
160 if (id == 0)
161 throw new SAXException(tr("<nd> has zero ref"));
162 list.add(id);
163
164 // ---- PARSING RELATIONS ----
165
166 } else if (qName.equals("relation")) {
167 current = new OsmPrimitiveData();
168 readCommon(atts, current);
169 relations.put((OsmPrimitiveData)current, new LinkedList<RelationMemberData>());
170 } else if (qName.equals("member")) {
171 Collection<RelationMemberData> list = relations.get(current);
172 if (list == null)
173 throw new SAXException(tr("Found <member> tag on non-relation."));
174 RelationMemberData emd = new RelationMemberData();
175 emd.relationMember = new RelationMember();
176 emd.id = getLong(atts, "ref");
177 emd.type=atts.getValue("type");
178 emd.relationMember.role = atts.getValue("role");
179
180 if (emd.id == 0)
181 throw new SAXException(tr("Incomplete <member> specification with ref=0"));
182
183 list.add(emd);
184
185 // ---- PARSING TAGS (applicable to all objects) ----
186
187 } else if (qName.equals("tag")) {
188 current.put(atts.getValue("k"), atts.getValue("v"));
189 }
190 } catch (NumberFormatException x) {
191 x.printStackTrace(); // SAXException does not chain correctly
192 throw new SAXException(x.getMessage(), x);
193 } catch (NullPointerException x) {
194 x.printStackTrace(); // SAXException does not chain correctly
195 throw new SAXException(tr("NullPointerException, Possibly some missing tags."), x);
196 }
197 }
198
199 private double getDouble(Attributes atts, String value) {
200 return Double.parseDouble(atts.getValue(value));
201 }
202 }
203
204 /**
205 * Constructor initializes list of allowed protocol versions.
206 */
207 public OsmReader() {
208 // first add the main server version
209 allowedVersions.add(Main.pref.get("osm-server.version", "0.5"));
210 // now also add all compatible versions
211 String[] additionalVersions =
212 Main.pref.get("osm-server.additional-versions", "").split("/,/");
213 if (additionalVersions.length == 1 && additionalVersions[0].length() == 0)
214 additionalVersions = new String[] {};
215 allowedVersions.addAll(Arrays.asList(additionalVersions));
216 }
217
218 /**
219 * Read out the common attributes from atts and put them into this.current.
220 */
221 void readCommon(Attributes atts, OsmPrimitive current) throws SAXException {
222 current.id = getLong(atts, "id");
223 if (current.id == 0)
224 throw new SAXException(tr("Illegal object with id=0"));
225
226 String time = atts.getValue("timestamp");
227 if (time != null && time.length() != 0) {
228 /* Do not parse the date here since it wastes a HUGE amount of time.
229 * Moved into OsmPrimitive.
230 try {
231 current.timestamp = DateParser.parse(time);
232 } catch (ParseException e) {
233 e.printStackTrace();
234 throw new SAXException(tr("Couldn't read time format \"{0}\".",time));
235 }
236 */
237 current.timestamp = time;
238 }
239
240 // user attribute added in 0.4 API
241 String user = atts.getValue("user");
242 if (user != null) {
243 // do not store literally; get object reference for string
244 current.user = User.get(user);
245 }
246
247 // visible attribute added in 0.4 API
248 String visible = atts.getValue("visible");
249 if (visible != null) {
250 current.visible = Boolean.parseBoolean(visible);
251 }
252
253 String action = atts.getValue("action");
254 if (action == null)
255 return;
256 if (action.equals("delete"))
257 current.delete(true);
258 else if (action.startsWith("modify"))
259 current.modified = true;
260 }
261 private long getLong(Attributes atts, String value) throws SAXException {
262 String s = atts.getValue(value);
263 if (s == null)
264 throw new SAXException(tr("Missing required attribute \"{0}\".",value));
265 return Long.parseLong(s);
266 }
267
268 private Node findNode(long id) {
269 Node n = nodes.get(id);
270 if (n != null)
271 return n;
272 for (Node node : references.nodes)
273 if (node.id == id)
274 return node;
275 // TODO: This has to be changed to support multiple layers.
276 for (Node node : Main.ds.nodes)
277 if (node.id == id)
278 return new Node(node);
279 return null;
280 }
281
282 private void createWays() {
283 for (Entry<OsmPrimitiveData, Collection<Long>> e : ways.entrySet()) {
284 Way w = new Way();
285 boolean failed = false;
286 for (long id : e.getValue()) {
287 Node n = findNode(id);
288 if (n == null) {
289 failed = true;
290 break;
291 }
292 w.nodes.add(n);
293 }
294 if (failed) continue;
295 e.getKey().copyTo(w);
296 adder.visit(w);
297 }
298
299 }
300
301 /**
302 * Return the Way object with the given id, or null if it doesn't
303 * exist yet. This method only looks at ways stored in the data set.
304 *
305 * @param id
306 * @return way object or null
307 */
308 private Way findWay(long id) {
309 for (Way wy : ds.ways)
310 if (wy.id == id)
311 return wy;
312 for (Way wy : Main.ds.ways)
313 if (wy.id == id)
314 return wy;
315 return null;
316 }
317
318 /**
319 * Return the Relation object with the given id, or null if it doesn't
320 * exist yet. This method only looks at relations stored in the data set.
321 *
322 * @param id
323 * @return relation object or null
324 */
325 private Relation findRelation(long id) {
326 for (Relation e : ds.relations)
327 if (e.id == id)
328 return e;
329 for (Relation e : Main.ds.relations)
330 if (e.id == id)
331 return e;
332 return null;
333 }
334
335 /**
336 * Create relations. This is slightly different than n/s/w because
337 * unlike other objects, relations may reference other relations; it
338 * is not guaranteed that a referenced relation will have been created
339 * before it is referenced. So we have to create all relations first,
340 * and populate them later.
341 */
342 private void createRelations() {
343
344 // pass 1 - create all relations
345 for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
346 Relation en = new Relation();
347 e.getKey().copyTo(en);
348 adder.visit(en);
349 }
350
351 // pass 2 - sort out members
352 for (Entry<OsmPrimitiveData, Collection<RelationMemberData>> e : relations.entrySet()) {
353 Relation en = findRelation(e.getKey().id);
354 if (en == null) throw new Error("Failed to create relation " + e.getKey().id);
355
356 for (RelationMemberData emd : e.getValue()) {
357 RelationMember em = emd.relationMember;
358 if (emd.type.equals("node")) {
359 em.member = findNode(emd.id);
360 if (em.member == null) {
361 em.member = new Node(emd.id);
362 adder.visit((Node)em.member);
363 }
364 } else if (emd.type.equals("way")) {
365 em.member = findWay(emd.id);
366 if (em.member == null) {
367 em.member = new Way(emd.id);
368 adder.visit((Way)em.member);
369 }
370 } else if (emd.type.equals("relation")) {
371 em.member = findRelation(emd.id);
372 if (em.member == null) {
373 em.member = new Relation(emd.id);
374 adder.visit((Relation)em.member);
375 }
376 } else {
377 // this is an error.
378 }
379 en.members.add(em);
380 }
381 }
382 }
383
384 /**
385 * Parse the given input source and return the dataset.
386 * @param ref The dataset that is search in for references first. If
387 * the Reference is not found here, Main.ds is searched and a copy of the
388 * elemet found there is returned.
389 */
390 public static DataSet parseDataSet(InputStream source, DataSet ref, PleaseWaitDialog pleaseWaitDlg) throws SAXException, IOException {
391 OsmReader osm = new OsmReader();
392 osm.references = ref == null ? new DataSet() : ref;
393
394 // phase 1: Parse nodes and read in raw ways
395 InputSource inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
396 try {
397 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, osm.new Parser());
398 } catch (ParserConfigurationException e1) {
399 e1.printStackTrace(); // broken SAXException chaining
400 throw new SAXException(e1);
401 }
402
403 if (pleaseWaitDlg != null) {
404 pleaseWaitDlg.progress.setValue(0);
405 pleaseWaitDlg.currentAction.setText(tr("Preparing data..."));
406 }
407
408 for (Node n : osm.nodes.values())
409 osm.adder.visit(n);
410
411 try {
412 osm.createWays();
413 osm.createRelations();
414 } catch (NumberFormatException e) {
415 e.printStackTrace();
416 throw new SAXException(tr("Illformed Node id"));
417 }
418
419 // clear all negative ids (new to this file)
420 for (OsmPrimitive o : osm.ds.allPrimitives())
421 if (o.id < 0)
422 o.id = 0;
423
424 return osm.ds;
425 }
426}
Note: See TracBrowser for help on using the repository browser.