package de.blau.android.osm; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import android.util.Log; import de.blau.android.exception.OsmException; import de.blau.android.exception.OsmParseException; import de.blau.android.exception.StorageException; import de.blau.android.util.collections.LongOsmElementMap; /** * Parses a XML (as InputStream), provided by XmlRetriever, and pushes generated OsmElements to the given Storage. * * @author mb */ public class OsmParser extends DefaultHandler { private static final String DEBUG_TAG = OsmParser.class.getSimpleName(); /** The storage, where the data will be stored (e.g. as JavaStorage or SqliteStorage). */ private final Storage storage; /** * Current node (node of OsmElement), where the parser is actually in. Will be used when children of this element * have to been assigned to their parent. */ private Node currentNode; /** Same as {@link currentNode}. */ private Way currentWay; /** Same as {@link currentNode}. */ private Relation currentRelation; private final ArrayList<Exception> exceptions; private ArrayList<RelationMember> missingRelations; private LongOsmElementMap<Node>nodeIndex = null; private LongOsmElementMap<Way>wayIndex = null; public OsmParser() { super(); storage = new Storage(); currentNode = null; currentWay = null; currentRelation = null; exceptions = new ArrayList<Exception>(); missingRelations = new ArrayList<RelationMember>(); } public Storage getStorage() { return storage; } /** * Triggers the beginning of parsing. * @throws SAXException * * @throws SAXException {@see SAXException} * @throws IOException when the xmlRetriever could not provide any data. * @throws ParserConfigurationException */ public void start(final InputStream in) throws SAXException, IOException, ParserConfigurationException { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); saxParser.parse(in, this); } /** * needed for post processing of relations */ @Override public void endDocument() { Log.d(DEBUG_TAG, "Post processing relations."); for (RelationMember rm : missingRelations) { Relation r = storage.getRelation(rm.ref); if (r != null) { rm.setElement(r); Log.d(DEBUG_TAG, "Added relation " + rm.ref); } } Log.d(DEBUG_TAG, "Finished parsing input."); } public List<Exception> getExceptions() { return exceptions; } /** * {@inheritDoc} */ @Override public void startElement(final String uri, final String name, final String qName, final Attributes atts) { try { if (isOsmElement(name)) { parseOsmElement(name, atts); } else if (isWayNode(name)) { parseWayNode(atts); } else if (isRelationMember(name)) { parseRelationMember(atts); } else if (isTag(name)) { parseTag(atts); } else if (isBounds(name)) { parseBounds(atts); } } catch (OsmParseException e) { Log.e(DEBUG_TAG, "OsmParseException", e); exceptions.add(e); } } /** * {@inheritDoc} */ @Override public void endElement(final String uri, final String name, final String qName) throws SAXException { try { if (isNode(name)) { storage.insertNodeUnsafe(currentNode); currentNode = null; } else if (isWay(name)) { if (currentWay.getNodes() != null && currentWay.getNodes().size() > 0) { storage.insertWayUnsafe(currentWay); } else { Log.e(DEBUG_TAG,"Way " + currentWay.getOsmId() + " has no nodes! Ignored."); } currentWay = null; } else if (isRelation(name)) { storage.insertRelationUnsafe(currentRelation); currentRelation = null; } } catch (StorageException sex) { throw new SAXException(sex); } } /** * parse API 0.6 output and JOSM OSM files * @param name * @param atts * @throws OsmParseException */ private void parseOsmElement(final String name, final Attributes atts) throws OsmParseException { try { long osmId = Long.parseLong(atts.getValue("id")); String version = atts.getValue("version"); long osmVersion = version == null ? 0 : Long.parseLong(atts.getValue("version")); // hack for JOSM file format support String action = atts.getValue("action"); byte status = OsmElement.STATE_UNCHANGED; if (action != null) { if (action.equalsIgnoreCase("modify")) { status = OsmElement.STATE_MODIFIED; if (osmId < 0) { status = OsmElement.STATE_CREATED; } } else if (action.equalsIgnoreCase("delete")) { status = OsmElement.STATE_DELETED; } } if (isNode(name)) { int lat = (int) (Double.valueOf(atts.getValue("lat")) * 1E7); int lon = (int) (Double.valueOf(atts.getValue("lon")) * 1E7); currentNode = OsmElementFactory.createNode(osmId, osmVersion, status, lat, lon); // Log.d(DEBUG_TAG, "Creating node " + osmId); } else if (isWay(name)) { currentWay = OsmElementFactory.createWay(osmId, osmVersion, status); if (nodeIndex==null) { nodeIndex = storage.getNodeIndex(); // !!!!! this will fail if input is not ordered } // Log.d(DEBUG_TAG, "Creating way " + osmId); } else if (isRelation(name)) { currentRelation = OsmElementFactory.createRelation(osmId, osmVersion, status); if (nodeIndex==null) { nodeIndex = storage.getNodeIndex(); // !!!!! this will fail if input is not ordered } if (wayIndex==null) { wayIndex = storage.getWayIndex(); // !!!!! this will fail if input is not ordered } // Log.d(DEBUG_TAG, "Creating relation " + osmId); } else { throw new OsmParseException("Unknown element " + name); } } catch (NumberFormatException e) { throw new OsmParseException("Element unparsable"); } } /** * @param atts */ private void parseTag(final Attributes atts) { OsmElement currentOsmElement = getCurrentOsmElement(); if (currentOsmElement == null) { Log.e(DEBUG_TAG, "Parsing Error: no currentOsmElement set!"); } else { String k = atts.getValue("k"); String v = atts.getValue("v"); currentOsmElement.addOrUpdateTag(k, v); } } /** * @param atts * @throws OsmParseException */ private void parseBounds(final Attributes atts) throws OsmParseException { //<bounds minlat="53.56465" minlon="9.95893" maxlat="53.56579" maxlon="9.96022"/> try { double minlat = Double.parseDouble(atts.getValue("minlat")); double maxlat = Double.parseDouble(atts.getValue("maxlat")); double minlon = Double.parseDouble(atts.getValue("minlon")); double maxlon = Double.parseDouble(atts.getValue("maxlon")); try { if (storage.getBoundingBoxes() == null) { storage.setBoundingBox(new BoundingBox(minlon, minlat, maxlon, maxlat)); } else { storage.addBoundingBox(new BoundingBox(minlon, minlat, maxlon, maxlat)); } Log.d(DEBUG_TAG, "Creating bounding box " + minlon + " " + minlat + " " + maxlon + " " + maxlat); } catch (OsmException e) { throw new OsmParseException("Bounds are not correct"); } } catch (NumberFormatException e) { throw new OsmParseException("Bounds unparsable"); } } /** * @param atts * @throws OsmParseException */ private void parseWayNode(final Attributes atts) throws OsmParseException { try { if (currentWay == null) { Log.e(DEBUG_TAG, "No currentWay set!"); } else { long nodeOsmId = Long.parseLong(atts.getValue("ref")); // Log.d("OsmParser","parseWayNode " + nodeOsmId); // Node node = storage.getNode(nodeOsmId); Node node = nodeIndex.get(nodeOsmId); if (node==null) { throw new OsmParseException("parseWayNode node " + nodeOsmId + " not in storage"); } currentWay.addNode(node); } } catch (NumberFormatException e) { throw new OsmParseException("WayNode unparsable"); } } /** * @param atts * @throws OsmParseException */ private void parseRelationMember(final Attributes atts) throws OsmParseException { try { if (currentRelation == null) { Log.e(DEBUG_TAG, "No currentRelation set!"); } else { long ref = Long.parseLong(atts.getValue("ref")); String type = atts.getValue("type"); String role = atts.getValue("role"); RelationMember member = null; if (isNode(type)) { // Node n = storage.getNode(ref); Node n = nodeIndex.get(ref); if (n != null) { n.addParentRelation(currentRelation); member = new RelationMember(role, n); } else { member = new RelationMember(type, ref, role); } // Log.d(DEBUG_TAG, "Added node member"); } else if (isWay(type)) { // Way w = storage.getWay(ref); Way w = wayIndex.get(ref); if (w != null) { w.addParentRelation(currentRelation); member = new RelationMember(role, w); } else { member = new RelationMember(type, ref, role); } // Log.d(DEBUG_TAG, "Added way member"); } else if (isRelation(type)) { Relation r = storage.getRelation(ref); if (r != null) { r.addParentRelation(currentRelation); member = new RelationMember(role, r); } else { // these need to be saved and reprocessed member = new RelationMember(type, ref, role); missingRelations.add(member); // Log.d(DEBUG_TAG, "Parent relation not available yet or downloaded"); } // Log.d(DEBUG_TAG, "Added relation member"); } currentRelation.addMember(member); // Log.d(DEBUG_TAG, "Adding relation member " + ref + " " + type); } } catch (NumberFormatException e) { throw new OsmParseException("RelationMember unparsable"); } } /** * @return the element in which we're in. When we're not in any element, it returns null. */ private OsmElement getCurrentOsmElement() { if (currentNode != null) { return currentNode; } if (currentWay != null) { return currentWay; } if (currentRelation != null) { return currentRelation; } return null; } /** * Checks if the element "name" is an OsmElement (either a node, way or relation). * * @param name the name of the XML element. * @return true if element "name" is a node, way or relation, otherwise false. */ private static boolean isOsmElement(final String name) { return isNode(name) || isWay(name) || isRelation(name); } /** * Checks if the element "name" is a Node * * @param name the name of the XML element. * @return true if element "name" is a node, otherwise false. */ private static boolean isNode(final String name) { return Node.NAME.equalsIgnoreCase(name); } /** * @see isNode() */ private static boolean isWay(final String name) { return Way.NAME.equalsIgnoreCase(name); } /** * @see isNode() */ private static boolean isTag(final String name) { return "tag".equalsIgnoreCase(name); } /** * @see isNode() */ private static boolean isWayNode(final String name) { return Way.NODE.equalsIgnoreCase(name); } /** * @see isNode() */ private static boolean isBounds(final String name) { return BoundingBox.NAME.equalsIgnoreCase(name); } /** * @see isNode() */ private static boolean isRelation(final String name) { return Relation.NAME.equalsIgnoreCase(name); } /** * @see isNode() */ private static boolean isRelationMember(final String name) { return Relation.MEMBER.equalsIgnoreCase(name); } /** * Clear the list of bounding boxes */ public void clearBoundingBoxes() { List<BoundingBox> boundingBoxes = getStorage().getBoundingBoxes(); if (boundingBoxes != null) { boundingBoxes.clear(); } } }