/* * Copyright (C) 2006 - 2012. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 or * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. */ package uk.me.parabola.mkgmap.reader.osm.xml; import uk.me.parabola.imgfmt.app.Coord; import uk.me.parabola.log.Logger; import uk.me.parabola.mkgmap.reader.osm.Element; import uk.me.parabola.mkgmap.reader.osm.GeneralRelation; import uk.me.parabola.mkgmap.reader.osm.Node; import uk.me.parabola.mkgmap.reader.osm.OsmHandler; import uk.me.parabola.mkgmap.reader.osm.Relation; import uk.me.parabola.mkgmap.reader.osm.Way; import uk.me.parabola.util.EnhancedProperties; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; /** * Reads and parses the OSM XML format. * * Creates the nodes/ways and relations that are read from the file and passes * them to the OsmCollector. * * It should not examine tags, or do anything else. * * @author Steve Ratcliffe */ public class Osm5XmlHandler extends OsmHandler { private static final Logger log = Logger.getLogger(Osm5XmlHandler.class); // Set to the currently processing element. private int mode; // Values for mode above. private static final int MODE_NODE = 1; private static final int MODE_WAY = 2; private static final int MODE_BOUND = 3; private static final int MODE_RELATION = 4; private static final int MODE_BOUNDS = 5; // Options private final boolean ignoreBounds; // Current state. protected Node currentNode; protected Way currentWay; protected Relation currentRelation; protected long currentElementId; public Osm5XmlHandler(EnhancedProperties props) { ignoreBounds = props.getProperty("ignore-osm-bounds", false); } /** * The XML handler callbacks. * * Need an inner class here so that the top class can inherit from OsmHandler. */ public class SaxHandler extends DefaultHandler { /** * Receive notification of the start of an element. * * @param uri The Namespace URI, or the empty string if the * element has no Namespace URI or if Namespace * processing is not being performed. * @param localName The local name (without prefix), or the * empty string if Namespace processing is not being * performed. * @param qName The qualified name (with prefix), or the * empty string if qualified names are not available. * @param attributes The attributes attached to the element. If * there are no attributes, it shall be an empty * Attributes object. * @throws SAXException Any SAX exception, possibly * wrapping another exception. * @see ContentHandler#startElement */ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (mode == 0) { if (qName.equals("node")) { mode = MODE_NODE; startNode(attributes.getValue("id"), attributes.getValue("lat"), attributes.getValue("lon")); } else if (qName.equals("way")) { mode = MODE_WAY; startWay(attributes.getValue("id")); } else if (qName.equals("relation")) { mode = MODE_RELATION; currentRelation = new GeneralRelation(idVal(attributes.getValue("id"))); } else if (qName.equals("bound")) { mode = MODE_BOUND; if(!ignoreBounds) { String box = attributes.getValue("box"); setupBBoxFromBound(box); } } else if (qName.equals("bounds")) { mode = MODE_BOUNDS; if(!ignoreBounds) setupBBoxFromBounds(attributes); } } else if (mode == MODE_NODE) { startInNode(qName, attributes); } else if (mode == MODE_WAY) { startInWay(qName, attributes); } else if (mode == MODE_RELATION) { startInRelation(qName, attributes); } } /** * Receive notification of the end of an element. * * @param uri The Namespace URI, or the empty string if the * element has no Namespace URI or if Namespace * processing is not being performed. * @param localName The local name (without prefix), or the * empty string if Namespace processing is not being * performed. * @param qName The qualified name (with prefix), or the * empty string if qualified names are not available. * @throws SAXException Any SAX exception, possibly * wrapping another exception. * @see ContentHandler#endElement */ public void endElement(String uri, String localName, String qName) throws SAXException { if (mode == MODE_NODE) { if (qName.equals("node")) { mode = 0; if (currentNode != null) { saver.addNode(currentNode); hooks.onAddNode(currentNode); } currentElementId = 0; currentNode = null; } } else if (mode == MODE_WAY) { if (qName.equals("way")) { mode = 0; endWay(currentWay); currentWay = null; } } else if (mode == MODE_BOUND) { if (qName.equals("bound")) mode = 0; } else if (mode == MODE_BOUNDS) { if (qName.equals("bounds")) mode = 0; } else if (mode == MODE_RELATION) { if (qName.equals("relation")) { mode = 0; // remove the mkgmap:tagsincomplete tags which is used in multipolygons only if (currentRelation.getTag(TAGS_INCOMPLETE_TAG) != null) { String type = currentRelation.getTag("type"); if ("multipolygon".equals(type) == false && "boundary".equals(type) == false) { currentRelation.deleteTag(TAGS_INCOMPLETE_TAG); } } saver.addRelation(currentRelation); } } } /** * Called on an XML error. Attempt to print a line number to aid in * working out the problem. * @throws SAXException */ public void fatalError(SAXParseException e) throws SAXException { System.err.println("Error at line " + e.getLineNumber() + ", col " + e.getColumnNumber()); super.fatalError(e); } } /** * A new tag has been started while we are inside a node element. * @param qName The new tag name. * @param attributes Its attributes. */ private void startInNode(String qName, Attributes attributes) { if (qName.equals("tag")) { String key = attributes.getValue("k"); String val = attributes.getValue("v"); // We only want to create a full node for nodes that are POI's // and not just one point of a way. Only create if it has tags that // could be used in a POI. key = keepTag(key, val); if (key != null) { if (currentNode == null) { Coord co = saver.getCoord(currentElementId); currentNode = new Node(currentElementId, co); } currentNode.addTagFromRawOSM(key, val); } } } /** * A new tag has been started while we are inside a way element. * @param qName The new tag name. * @param attributes Its attributes. */ private void startInWay(String qName, Attributes attributes) { if (qName.equals("nd")) { long id = idVal(attributes.getValue("ref")); addCoordToWay(currentWay, id); } else if (qName.equals("tag")) { String key = attributes.getValue("k"); String val = attributes.getValue("v"); key = keepTag(key, val); if (key != null) currentWay.addTagFromRawOSM(key, val); } } /** * A new tag has been started while we are inside the relation tag. * @param qName The new tag name. * @param attributes Its attributes. */ private void startInRelation(String qName, Attributes attributes) { if (qName.equals("member")) { long id = idVal(attributes.getValue("ref")); Element el; String type = attributes.getValue("type"); if ("way".equals(type)){ el = saver.getWay(id); } else if ("node".equals(type)) { el = saver.getNode(id); if(el == null) { // we didn't make a node for this point earlier, // do it now (if it exists) Coord co = saver.getCoord(id); if(co != null) { el = new Node(id, co); saver.addNode((Node)el); } } } else if ("relation".equals(type)) { el = saver.getRelation(id); if (el == null) { saver.deferRelation(id, currentRelation, attributes.getValue("role")); } } else el = null; if (el != null) // ignore non existing ways caused by splitting files currentRelation.addElement(attributes.getValue("role"), el); } else if (qName.equals("tag")) { String key = attributes.getValue("k"); String val = attributes.getValue("v"); // the type tag is required for relations - all other tags are filtered if ("type".equals(key)) // intern the key key = "type"; else key = keepTag(key, val); if (key == null) { currentRelation.addTag(TAGS_INCOMPLETE_TAG, "true"); } else { currentRelation.addTagFromRawOSM(key, val); } } } /** * Set a bounding box from the bounds element. * There are two ways of specifying a bounding box in the XML format, this * one uses attributes of the element to give the bounds. * @param xmlattr The bounds element attributes. */ private void setupBBoxFromBounds(Attributes xmlattr) { try { setBBox(Double.parseDouble(xmlattr.getValue("minlat")), Double.parseDouble(xmlattr.getValue("minlon")), Double.parseDouble(xmlattr.getValue("maxlat")), Double.parseDouble(xmlattr.getValue("maxlon"))); } catch (NumberFormatException e) { // just ignore it log.warn("NumberformatException: Cannot read bbox"); } } /** * Set a bounding box from the bound element. There are two ways of * specifying a bounding box, this one has a single 'box' attribute that * is a comma separated list of the bounds values. * @param box The value of the box attribute. */ private void setupBBoxFromBound(String box) { String[] f = box.split(","); try { setBBox(Double.parseDouble(f[0]), Double.parseDouble(f[1]), Double.parseDouble(f[2]), Double.parseDouble(f[3])); } catch (NumberFormatException e) { // just ignore it log.warn("NumberformatException: Cannot read bbox"); } } /** * Save node information. Consists of a location specified by lat/long. * * @param sid The id as a string. * @param slat The lat as a string. * @param slon The longitude as a string. */ private void startNode(String sid, String slat, String slon) { if (sid == null || slat == null || slon == null) return; try { long id = idVal(sid); Coord co = new Coord(Double.parseDouble(slat), Double.parseDouble(slon)); saver.addPoint(id, co); currentElementId = id; } catch (NumberFormatException e) { // ignore bad numeric data. The coord will be discarded } } /** * A new way element has been seen. * @param sid The way id as a string. */ private void startWay(String sid) { try { long id = idVal(sid); currentWay = startWay(id); } catch (NumberFormatException e) { // ignore bad numeric data. The way will be discarded } } }