/** * Created by Nicholas Hallahan on 12/24/14. * nhallahan@spatialdev.com */ package com.spatialdev.osm.model; import android.content.Context; import android.text.TextUtils; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.InputStream; public class OSMXmlParser { // We are not using namespaces. private static final String ns = null; private XmlPullParser parser; // This is the data set that gets populated from the XML. private OSMDataSet ds; // Count of elements that have been read so far protected long elementReadCount = 0; protected long nodeReadCount = 0; protected long wayReadCount = 0; protected long relationReadCount = 0; protected long tagReadCount = 0; /** * Access the parser through public static methods which function * as factories creating parser instances. */ public static OSMDataSet parseFromAssets(final Context context, final String fileName) throws IOException { if (TextUtils.isEmpty(fileName)) { throw new NullPointerException("No OSM XML File Name passed in."); } InputStream in = context.getAssets().open(fileName); return parseFromInputStream(in); } public static OSMDataSet parseFromInputStream(InputStream in) throws IOException { OSMXmlParser osmXmlParser = new OSMXmlParser(); try { osmXmlParser.parse(in); } catch (XmlPullParserException e) { e.printStackTrace(); } finally { if (in != null) { in.close(); } } return osmXmlParser.getDataSet(); } protected OSMXmlParser() { ds = new OSMDataSet(); } public OSMDataSet getDataSet() { return ds; } /** * Should only be called by static method parseFromInputStream * @param in * @throws XmlPullParserException * @throws IOException */ public void parse(InputStream in) throws XmlPullParserException, IOException { try { parser = Xml.newPullParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); parser.setInput(in, null); parser.nextTag(); readOsm(); ds.postProcessing(); } finally { in.close(); } } private void readOsm() throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, ns, "osm"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); if (name.equals("note")) { readNote(); } else if (name.equals("meta")) { readMeta(); } else if (name.equals("node")) { readNode(); } else if (name.equals("way")) { readWay(); } else if (name.equals("relation")) { readRelation(); } else { skip(); } ++elementReadCount; if (elementReadCount % 500 == 0) { notifyProgress(); // broadcasting read updates every 500 elements } } } /** * Override this in a subclass if you want to notify a progress bar that * an element(s) have been read. * * * * */ protected void notifyProgress() {} private void readNote() throws XmlPullParserException, IOException { String note = readText(); ds.createNote(note); } private void readMeta() throws XmlPullParserException, IOException { parser.next(); String osmBase = parser.getAttributeValue(ns, "osm_base"); ds.createMeta(osmBase); parser.next(); } private void readNode() throws XmlPullParserException, IOException { String idStr = parser.getAttributeValue(ns, "id"); String latStr = parser.getAttributeValue(ns, "lat"); String lonStr = parser.getAttributeValue(ns, "lon"); String versionStr = parser.getAttributeValue(ns, "version"); String timestampStr = parser.getAttributeValue(ns, "timestamp"); String changesetStr = parser.getAttributeValue(ns, "changeset"); String uidStr = parser.getAttributeValue(ns, "uid"); String userStr = parser.getAttributeValue(ns, "user"); String action = parser.getAttributeValue(ns, "action"); OSMNode node = ds.createNode( idStr, latStr, lonStr, versionStr, timestampStr, changesetStr, uidStr, userStr, action); // If the next thing is not an END_TAG, we have some tag elements in the node... if (parser.nextTag() != XmlPullParser.END_TAG && parser.getName().equals("tag")) { readTags(node); } ++nodeReadCount; } private void readWay() throws XmlPullParserException, IOException { String idStr = parser.getAttributeValue(ns, "id"); String versionStr = parser.getAttributeValue(ns, "version"); String timestampStr = parser.getAttributeValue(ns, "timestamp"); String changesetStr = parser.getAttributeValue(ns, "changeset"); String uidStr = parser.getAttributeValue(ns, "uid"); String userStr = parser.getAttributeValue(ns, "user"); String action = parser.getAttributeValue(ns, "action"); OSMWay way = ds.createWay( idStr, versionStr, timestampStr, changesetStr, uidStr, userStr, action ); if (parser.nextTag() != XmlPullParser.END_TAG) { if (parser.getName().equals("nd")) { readNds(way); } else if (parser.getName().equals("tag")) { readTags(way); } else { skip(); } } ++wayReadCount; } private void readRelation() throws XmlPullParserException, IOException { String idStr = parser.getAttributeValue(ns, "id"); String versionStr = parser.getAttributeValue(ns, "version"); String timestampStr = parser.getAttributeValue(ns, "timestamp"); String changesetStr = parser.getAttributeValue(ns, "changeset"); String uidStr = parser.getAttributeValue(ns, "uid"); String userStr = parser.getAttributeValue(ns, "user"); String action = parser.getAttributeValue(ns, "action"); OSMRelation relation = ds.createRelation( idStr, versionStr, timestampStr, changesetStr, uidStr, userStr, action ); if (parser.nextTag() != XmlPullParser.END_TAG) { if (parser.getName().equals("member")) { readMembers(relation); } else if (parser.getName().equals("tag")) { readTags(relation); } else { skip(); } } ++relationReadCount; } private void readTags(OSMElement el) throws XmlPullParserException, IOException { String k = parser.getAttributeValue(ns, "k"); String v = parser.getAttributeValue(ns, "v"); el.addParsedTag(k, v); OSMDataSet.addTagValue(v); // we do this twice, because these are singular nodes that // function as start and end tags parser.nextTag(); parser.nextTag(); if (parser.getName().equals("tag")){ readTags(el); } else if (parser.getName().equals("nd")) { readNds((OSMWay)el); } else if (parser.getName().equals("member")) { readMembers((OSMRelation)el); } ++tagReadCount; } private void readNds(OSMWay way) throws XmlPullParserException, IOException { String ref = parser.getAttributeValue(ns, "ref"); long id = Long.valueOf(ref); way.addNodeRef(id); // we do this twice, because these are singular nodes that // function as start and end tags parser.nextTag(); parser.nextTag(); if (parser.getName().equals("tag")){ readTags(way); } else if (parser.getName().equals("nd")) { readNds(way); } } private void readMembers(OSMRelation relation) throws XmlPullParserException, IOException { String type = parser.getAttributeValue(ns, "type"); String ref = parser.getAttributeValue(ns, "ref"); String role = parser.getAttributeValue(ns, "role"); long id = Long.valueOf(ref); if (type.equals("node")) { relation.addNodeRef(id, role); } else if (type.equals("way")) { relation.addWayRef(id, role); } else if (type.equals("relation")) { relation.addRelationRef(id, role); } // we do this twice, because these are singular nodes that // function as start and end tags parser.nextTag(); parser.nextTag(); if (parser.getName().equals("tag")){ readTags(relation); } else if (parser.getName().equals("member")) { readMembers(relation); } } // Skips tags the parser isn't interested in. Uses depth to handle nested tags. i.e., // if the next tag after a START_TAG isn't a matching END_TAG, it keeps going until it // finds the matching END_TAG (as indicated by the value of "depth" being 0). private void skip() throws XmlPullParserException, IOException { if (parser.getEventType() != XmlPullParser.START_TAG) { throw new IllegalStateException(); } int depth = 1; while (depth != 0) { switch (parser.next()) { case XmlPullParser.END_TAG: depth--; break; case XmlPullParser.START_TAG: depth++; break; } } } private String readText() throws IOException, XmlPullParserException { String result = ""; if (parser.next() == XmlPullParser.TEXT) { result = parser.getText(); parser.nextTag(); } return result; } }