// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.xml.v0_6.impl; import java.util.Calendar; import java.util.logging.Logger; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import org.openstreetmap.osmosis.core.OsmosisRuntimeException; import org.openstreetmap.osmosis.core.container.v0_6.BoundContainer; import org.openstreetmap.osmosis.core.container.v0_6.NodeContainer; import org.openstreetmap.osmosis.core.container.v0_6.RelationContainer; import org.openstreetmap.osmosis.core.container.v0_6.WayContainer; import org.openstreetmap.osmosis.core.domain.common.SimpleTimestampContainer; import org.openstreetmap.osmosis.core.domain.common.TimestampContainer; import org.openstreetmap.osmosis.core.domain.common.TimestampFormat; import org.openstreetmap.osmosis.core.domain.common.UnparsedTimestampContainer; import org.openstreetmap.osmosis.core.domain.v0_6.Bound; import org.openstreetmap.osmosis.core.domain.v0_6.CommonEntityData; import org.openstreetmap.osmosis.core.domain.v0_6.EntityType; 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.Relation; import org.openstreetmap.osmosis.core.domain.v0_6.RelationMember; import org.openstreetmap.osmosis.core.domain.v0_6.Tag; import org.openstreetmap.osmosis.core.domain.v0_6.Way; import org.openstreetmap.osmosis.core.domain.v0_6.WayNode; import org.openstreetmap.osmosis.core.task.v0_6.Sink; import org.openstreetmap.osmosis.xml.common.XmlTimestampFormat; /** * Reads the contents of an osm file using a Stax parser. * * @author Jiri Klement * @author Brett Henderson * @author Igor Podolskiy */ public class FastXmlParser { private static final String ELEMENT_NAME_BOUND = "bound"; private static final String ELEMENT_NAME_NODE = "node"; private static final String ELEMENT_NAME_WAY = "way"; private static final String ELEMENT_NAME_RELATION = "relation"; private static final String ELEMENT_NAME_TAG = "tag"; private static final String ELEMENT_NAME_NODE_REFERENCE = "nd"; private static final String ELEMENT_NAME_MEMBER = "member"; private static final String ATTRIBUTE_NAME_ID = "id"; private static final String ATTRIBUTE_NAME_VERSION = "version"; private static final String ATTRIBUTE_NAME_GENERATOR = "generator"; private static final String ATTRIBUTE_NAME_TIMESTAMP = "timestamp"; private static final String ATTRIBUTE_NAME_USER_ID = "uid"; private static final String ATTRIBUTE_NAME_USER = "user"; private static final String ATTRIBUTE_NAME_CHANGESET_ID = "changeset"; private static final String ATTRIBUTE_NAME_LATITUDE = "lat"; private static final String ATTRIBUTE_NAME_LONGITUDE = "lon"; private static final String ATTRIBUTE_NAME_KEY = "k"; private static final String ATTRIBUTE_NAME_VALUE = "v"; private static final String ATTRIBUTE_NAME_REF = "ref"; private static final String ATTRIBUTE_NAME_TYPE = "type"; private static final String ATTRIBUTE_NAME_ROLE = "role"; private static final String ATTRIBUTE_NAME_BOX = "box"; private static final String ATTRIBUTE_NAME_ORIGIN = "origin"; private static final Logger LOG = Logger.getLogger(FastXmlParser.class.getName()); private static final Object ELEMENT_NAME_BOUNDS = "bounds"; /** * Creates a new instance. * * @param sink * The sink receiving all output data. * @param reader * The input xml reader. * @param enableDateParsing * If true, parsing of dates in the xml will be enabled, * otherwise the current system time will be used. */ public FastXmlParser(Sink sink, XMLStreamReader reader, boolean enableDateParsing) { this.sink = sink; this.enableDateParsing = enableDateParsing; this.reader = reader; if (enableDateParsing) { timestampFormat = new XmlTimestampFormat(); } else { Calendar calendar; calendar = Calendar.getInstance(); calendar.set(Calendar.MILLISECOND, 0); dummyTimestampContainer = new SimpleTimestampContainer(calendar.getTime()); } memberTypeParser = new MemberTypeParser(); } private final XMLStreamReader reader; private final Sink sink; private final boolean enableDateParsing; private final MemberTypeParser memberTypeParser; private TimestampFormat timestampFormat; private TimestampContainer dummyTimestampContainer; private TimestampContainer parseTimestamp(String data) { if (enableDateParsing) { return new UnparsedTimestampContainer(timestampFormat, data); } else { return dummyTimestampContainer; } } private void readUnknownElement() throws XMLStreamException { int level = 0; do { if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) { level++; } else if (reader.getEventType() == XMLStreamConstants.END_ELEMENT) { level--; } reader.nextTag(); } while (level > 0); } /** * Creates a user instance based on the current entity attributes. This includes identifying the * case where no user is available. * * @return The appropriate user instance. */ private OsmUser readUser() { String rawUserId; String rawUserName; rawUserId = reader.getAttributeValue(null, ATTRIBUTE_NAME_USER_ID); rawUserName = reader.getAttributeValue(null, ATTRIBUTE_NAME_USER); if (rawUserId != null) { int userId; String userName; userId = Integer.parseInt(rawUserId); if (rawUserName == null) { userName = ""; } else { userName = rawUserName; } return new OsmUser(userId, userName); } else { return OsmUser.NONE; } } /** * Parses a changeset id from the current entity. * * @return The changeset id as a long. 0 is returned if no attribute is available. */ private long readChangesetId() { String changesetIdAttribute; changesetIdAttribute = reader.getAttributeValue(null, ATTRIBUTE_NAME_CHANGESET_ID); if (changesetIdAttribute != null) { return Long.parseLong(changesetIdAttribute); } else { return 0; } } private Bound readBound() throws Exception { String boxString; String origin; String[] boundStrings; Double right; Double left; Double top; Double bottom; boxString = reader.getAttributeValue(null, ATTRIBUTE_NAME_BOX); if (boxString == null) { throw new OsmosisRuntimeException("Missing required box attribute of bound element"); } boundStrings = boxString.split(","); if (boundStrings.length != 4) { throw new OsmosisRuntimeException("Badly formed box attribute of bound element"); } try { bottom = Double.parseDouble(boundStrings[0]); left = Double.parseDouble(boundStrings[1]); top = Double.parseDouble(boundStrings[2]); right = Double.parseDouble(boundStrings[3]); } catch (NumberFormatException e) { throw new OsmosisRuntimeException("Can't parse box attribute of bound element", e); } origin = reader.getAttributeValue(null, ATTRIBUTE_NAME_ORIGIN); if (origin == null || origin.equals("")) { throw new OsmosisRuntimeException("Origin attribute of bound element is empty or missing."); } Bound bound = new Bound(right, left, top, bottom, origin); reader.nextTag(); reader.nextTag(); return bound; } private Bound readBounds(String defaultOrigin) throws Exception { double bottom = getRequiredDoubleValue(XmlConstants.ATTRIBUTE_NAME_MINLAT); double left = getRequiredDoubleValue(XmlConstants.ATTRIBUTE_NAME_MINLON); double top = getRequiredDoubleValue(XmlConstants.ATTRIBUTE_NAME_MAXLAT); double right = getRequiredDoubleValue(XmlConstants.ATTRIBUTE_NAME_MAXLON); String origin = reader.getAttributeValue(null, ATTRIBUTE_NAME_ORIGIN); if (origin == null) { origin = defaultOrigin; } reader.nextTag(); reader.nextTag(); return new Bound(right, left, top, bottom, origin); } private double getRequiredDoubleValue(String attributeName) { String valueString = reader.getAttributeValue(null, attributeName); if (valueString == null) { throw new OsmosisRuntimeException(String.format( "Required attribute %s of the bounds element is missing", attributeName)); } try { return Double.parseDouble(valueString); } catch (NumberFormatException e) { throw new OsmosisRuntimeException( String.format("Cannot parse the %s attribute of the bounds element", attributeName), e); } } private Tag readTag() throws Exception { Tag tag = new Tag(reader.getAttributeValue(null, ATTRIBUTE_NAME_KEY), reader.getAttributeValue(null, ATTRIBUTE_NAME_VALUE)); reader.nextTag(); reader.nextTag(); return tag; } private Node readNode() throws Exception { long id; int version; TimestampContainer timestamp; OsmUser user; long changesetId; double latitude; double longitude; Node node; id = Long.parseLong(reader.getAttributeValue(null, ATTRIBUTE_NAME_ID)); version = Integer.parseInt(reader.getAttributeValue(null, ATTRIBUTE_NAME_VERSION)); timestamp = parseTimestamp(reader.getAttributeValue(null, ATTRIBUTE_NAME_TIMESTAMP)); changesetId = Long.parseLong(reader.getAttributeValue(null, ATTRIBUTE_NAME_CHANGESET_ID)); user = readUser(); changesetId = readChangesetId(); latitude = Double.parseDouble(reader.getAttributeValue(null, ATTRIBUTE_NAME_LATITUDE)); longitude = Double.parseDouble(reader.getAttributeValue(null, ATTRIBUTE_NAME_LONGITUDE)); node = new Node(new CommonEntityData(id, version, timestamp, user, changesetId), latitude, longitude); reader.nextTag(); while (reader.getEventType() == XMLStreamConstants.START_ELEMENT) { if (reader.getLocalName().equals(ELEMENT_NAME_TAG)) { node.getTags().add(readTag()); } else { readUnknownElement(); } } reader.nextTag(); return node; } private WayNode readWayNode() throws Exception { WayNode node = new WayNode( Long.parseLong(reader.getAttributeValue(null, ATTRIBUTE_NAME_REF))); reader.nextTag(); reader.nextTag(); return node; } private Way readWay() throws Exception { long id; int version; TimestampContainer timestamp; OsmUser user; long changesetId; Way way; id = Long.parseLong(reader.getAttributeValue(null, ATTRIBUTE_NAME_ID)); version = Integer.parseInt(reader.getAttributeValue(null, ATTRIBUTE_NAME_VERSION)); timestamp = parseTimestamp(reader.getAttributeValue(null, ATTRIBUTE_NAME_TIMESTAMP)); user = readUser(); changesetId = readChangesetId(); way = new Way(new CommonEntityData(id, version, timestamp, user, changesetId)); reader.nextTag(); while (reader.getEventType() == XMLStreamConstants.START_ELEMENT) { if (reader.getLocalName().equals(ELEMENT_NAME_TAG)) { way.getTags().add(readTag()); } else if (reader.getLocalName().equals(ELEMENT_NAME_NODE_REFERENCE)) { way.getWayNodes().add(readWayNode()); } else { readUnknownElement(); } } reader.nextTag(); return way; } private RelationMember readRelationMember() throws Exception { long id; EntityType type; String role; id = Long.parseLong(reader.getAttributeValue(null, ATTRIBUTE_NAME_REF)); type = memberTypeParser.parse(reader.getAttributeValue(null, ATTRIBUTE_NAME_TYPE)); role = reader.getAttributeValue(null, ATTRIBUTE_NAME_ROLE); RelationMember relationMember = new RelationMember(id, type, role); reader.nextTag(); reader.nextTag(); return relationMember; } private Relation readRelation() throws Exception { long id; int version; TimestampContainer timestamp; OsmUser user; long changesetId; Relation relation; id = Long.parseLong(reader.getAttributeValue(null, ATTRIBUTE_NAME_ID)); version = Integer.parseInt(reader.getAttributeValue(null, ATTRIBUTE_NAME_VERSION)); timestamp = parseTimestamp(reader.getAttributeValue(null, ATTRIBUTE_NAME_TIMESTAMP)); user = readUser(); changesetId = readChangesetId(); relation = new Relation(new CommonEntityData(id, version, timestamp, user, changesetId)); reader.nextTag(); while (reader.getEventType() == XMLStreamConstants.START_ELEMENT) { if (reader.getLocalName().equals(ELEMENT_NAME_TAG)) { relation.getTags().add(readTag()); } else if (reader.getLocalName().equals(ELEMENT_NAME_MEMBER)) { relation.getMembers().add(readRelationMember()); } else { readUnknownElement(); } } reader.nextTag(); return relation; } /** * Parses the xml and sends all data to the sink. */ public void readOsm() { try { String generator = null; if (reader.nextTag() == XMLStreamConstants.START_ELEMENT && reader.getLocalName().equals("osm")) { String fileVersion; fileVersion = reader.getAttributeValue(null, ATTRIBUTE_NAME_VERSION); if (!XmlConstants.OSM_VERSION.equals(fileVersion)) { LOG.warning( "Expected version " + XmlConstants.OSM_VERSION + " but received " + fileVersion + "." ); } generator = reader.getAttributeValue(null, ATTRIBUTE_NAME_GENERATOR); reader.nextTag(); if (reader.getEventType() == XMLStreamConstants.START_ELEMENT && reader.getLocalName().equals(ELEMENT_NAME_BOUND)) { LOG.fine("Legacy <bound> element encountered."); sink.process(new BoundContainer(readBound())); } if (reader.getEventType() == XMLStreamConstants.START_ELEMENT && reader.getLocalName().equals(ELEMENT_NAME_BOUNDS)) { sink.process(new BoundContainer(readBounds(generator))); } while (reader.getEventType() == XMLStreamConstants.START_ELEMENT) { // Node, way, relation if (reader.getLocalName().equals(ELEMENT_NAME_NODE)) { sink.process(new NodeContainer(readNode())); } else if (reader.getLocalName().equals(ELEMENT_NAME_WAY)) { sink.process(new WayContainer(readWay())); } else if (reader.getLocalName().equals(ELEMENT_NAME_RELATION)) { sink.process(new RelationContainer(readRelation())); } else { readUnknownElement(); } } } else { throw new XMLStreamException(); } } catch (Exception e) { throw new OsmosisRuntimeException(e); } } }