Subversion Repositories mkgmap

Rev

Rev 2906 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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.addTag(key, val.intern());
                        }
                }
        }

        /**
         * 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.addTag(key, val.intern());
                }
        }

        /**
         * 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.addTag(key, val.intern());
                        }
                }
        }

        /**
         * 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
                }
        }
}