source: josm/branch/0.5/src/org/openstreetmap/josm/io/OsmReader.java.orig@ 330

Last change on this file since 330 was 330, checked in by framm, 17 years ago

forgot to add some files...

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