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, 16 years ago

forgot to add some files...

File size: 12.8 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.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.