// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.xml.v0_6.impl; import org.xml.sax.Attributes; import org.openstreetmap.osmosis.core.container.v0_6.NodeContainer; import org.openstreetmap.osmosis.core.domain.common.TimestampContainer; import org.openstreetmap.osmosis.core.domain.v0_6.CommonEntityData; import org.openstreetmap.osmosis.core.domain.v0_6.Node; import org.openstreetmap.osmosis.core.domain.v0_6.OsmUser; import org.openstreetmap.osmosis.core.domain.v0_6.Tag; import org.openstreetmap.osmosis.core.task.v0_6.Sink; import org.openstreetmap.osmosis.xml.common.BaseElementProcessor; import org.openstreetmap.osmosis.xml.common.ElementProcessor; import org.openstreetmap.osmosis.core.OsmosisRuntimeException; /** * Provides an element processor implementation for a node. * * @author Brett Henderson */ public class NodeElementProcessor extends EntityElementProcessor implements TagListener { private static final String ELEMENT_NAME_TAG = "tag"; private static final String ATTRIBUTE_NAME_ID = "id"; private static final String ATTRIBUTE_NAME_TIMESTAMP = "timestamp"; private static final String ATTRIBUTE_NAME_USER = "user"; private static final String ATTRIBUTE_NAME_USERID = "uid"; private static final String ATTRIBUTE_NAME_CHANGESET_ID = "changeset"; private static final String ATTRIBUTE_NAME_VERSION = "version"; private static final String ATTRIBUTE_NAME_LATITUDE = "lat"; private static final String ATTRIBUTE_NAME_LONGITUDE = "lon"; private TagElementProcessor tagElementProcessor; private Node node; private boolean coordinatesRequired; /** * Creates a new instance. * * @param parentProcessor * The parent of this element processor. * @param sink * The sink for receiving processed data. * @param enableDateParsing * If true, dates will be parsed from xml data, else the current * date will be used thus saving parsing time. */ public NodeElementProcessor(BaseElementProcessor parentProcessor, Sink sink, boolean enableDateParsing) { this(parentProcessor, sink, enableDateParsing, true); } /** * Creates a new instance. * * @param parentProcessor * The parent of this element processor. * @param sink * The sink for receiving processed data. * @param enableDateParsing * If true, dates will be parsed from xml data, else the current * date will be used thus saving parsing time. * @param coordinatesRequired * If true, nodes without lat and lon attributes set will cause an exception. */ public NodeElementProcessor(BaseElementProcessor parentProcessor, Sink sink, boolean enableDateParsing, boolean coordinatesRequired) { super(parentProcessor, sink, enableDateParsing); this.coordinatesRequired = coordinatesRequired; tagElementProcessor = new TagElementProcessor(this, this); } /** * {@inheritDoc} */ public void begin(Attributes attributes) { long id; String sversion; int version; TimestampContainer timestampContainer; String rawUserId; String rawUserName; OsmUser user; long changesetId; double latitude; double longitude; id = Long.parseLong(attributes.getValue(ATTRIBUTE_NAME_ID)); sversion = attributes.getValue(ATTRIBUTE_NAME_VERSION); if (sversion == null) { throw new OsmosisRuntimeException("Node " + id + " does not have a version attribute as OSM 0.6 are required to have. Is this a 0.5 file?"); } else { version = Integer.parseInt(sversion); } timestampContainer = createTimestampContainer(attributes.getValue(ATTRIBUTE_NAME_TIMESTAMP)); rawUserId = attributes.getValue(ATTRIBUTE_NAME_USERID); rawUserName = attributes.getValue(ATTRIBUTE_NAME_USER); changesetId = buildChangesetId(attributes.getValue(ATTRIBUTE_NAME_CHANGESET_ID)); latitude = getLatLonDouble(attributes, ATTRIBUTE_NAME_LATITUDE, id); longitude = getLatLonDouble(attributes, ATTRIBUTE_NAME_LONGITUDE, id); user = buildUser(rawUserId, rawUserName); node = new Node(new CommonEntityData(id, version, timestampContainer, user, changesetId), latitude, longitude); } /** * Retrieves the appropriate child element processor for the newly * encountered nested element. * * @param uri * The element uri. * @param localName * The element localName. * @param qName * The element qName. * @return The appropriate element processor for the nested element. */ @Override public ElementProcessor getChild(String uri, String localName, String qName) { if (ELEMENT_NAME_TAG.equals(qName)) { return tagElementProcessor; } return super.getChild(uri, localName, qName); } /** * {@inheritDoc} */ public void end() { getSink().process(new NodeContainer(node)); } /** * This is called by child element processors when a tag object is * encountered. * * @param tag * The tag to be processed. */ public void processTag(Tag tag) { node.getTags().add(tag); } private double getLatLonDouble(Attributes attributes, String attributeName, long id) { String value = attributes.getValue(attributeName); if (value == null) { if (coordinatesRequired) { throw new OsmosisRuntimeException(String.format( "Node %s does not have its %s attribute set; this attribute is required in current context.", id, attributeName)); } else { return Double.NaN; } } try { return Double.parseDouble(value); } catch (NumberFormatException ex) { throw new OsmosisRuntimeException(String.format( "Node %s: cannot parse the %s attribute as a numeric value", id, attributeName)); } } }