blob: 0394ea61ead9506966843d233710cb10c2870c73 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.internal.storage.gpx;
import java.io.IOException;
import java.util.Collection;
import javax.xml.stream.XMLStreamException;
import javax.xml.bind.JAXBException;
import org.apache.sis.storage.gps.Fix;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.IllegalFeatureTypeException;
import org.apache.sis.internal.storage.xml.stream.StaxStreamWriter;
import org.apache.sis.internal.feature.AttributeConvention;
import org.apache.sis.internal.feature.Geometries;
import org.apache.sis.util.Version;
// Branch-dependent imports
import org.apache.sis.feature.AbstractFeature;
import org.apache.sis.feature.DefaultFeatureType;
/**
* Writer for GPX 1.0 and 1.1 files.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @version 0.8
* @since 0.8
* @module
*/
final class Writer extends StaxStreamWriter {
/**
* The GPX file version: 0 for GPX 1.0 or 1 for GPX 1.1.
*/
private final int version;
/**
* The metadata to write, or {@code null} if none.
*/
private final Metadata metadata;
/**
* Creates a new GPX writer for the given data store.
*
* @param owner the data store for which this writer is created.
* @param metadata the metadata to write, or {@code null} if none.
* @throws DataStoreException if the output type is not recognized or the data store is closed.
* @throws XMLStreamException if an error occurred while opening the XML file.
* @throws IOException if an error occurred while preparing the output stream.
*/
public Writer(final Store owner, final Metadata metadata)
throws DataStoreException, XMLStreamException, IOException
{
super(owner);
this.metadata = metadata;
final Version ver = owner.version;
if (ver != null && ver.compareTo(StoreProvider.V1_0, 2) <= 0) {
version = 0;
} else {
version = 1;
}
}
/**
* Writes the XML declaration followed by GPX metadata.
* This method shall be invoked exactly once before {@code write(Feature)}.
*
* @throws Exception if an error occurred while writing to the XML file.
*/
@Override
public void writeStartDocument() throws Exception {
final String namespace;
final Version ver;
switch (version) {
default:
case 1: ver = StoreProvider.V1_1; namespace = Tags.NAMESPACE_V11; break;
case 0: ver = StoreProvider.V1_0; namespace = Tags.NAMESPACE_V10; break;
}
super.writeStartDocument();
writer.setDefaultNamespace(namespace);
writer.writeStartElement(Tags.GPX);
writer.writeDefaultNamespace(namespace);
writer.writeAttribute(Attributes.VERSION, ver.toString());
if (metadata != null) {
final String creator = metadata.creator;
if (creator != null) {
writer.writeAttribute(Attributes.CREATOR, creator);
}
switch (version) {
default:
case 1: {
/*
* In GPX 1.1 format, the metadata are stored under a <metadata> node.
* This can conveniently be written by JAXB.
*/
marshal(Tags.NAMESPACE_V11, Tags.METADATA, Metadata.class, metadata);
break;
}
case 0: {
/*
* In GPX 1.0 format, the metadata were written inline in the root <gpx> element.
* We need to write them ourself. Not all metadata can be written in that legacy format.
*/
writeSingleValue(Tags.NAME, metadata.name);
writeSingleValue(Tags.DESCRIPTION, metadata.description);
final Person author = metadata.author;
if (author != null) {
writeSingleValue(Tags.AUTHOR, author.name);
writeSingleValue(Tags.EMAIL, author.email);
}
writeLinks(metadata.links);
writeSingle(Tags.TIME, metadata.time);
writeList(Tags.KEYWORDS, metadata.keywords);
// Really 1.1 namespace below, not 1.0. See 'marshal(…)' javadoc for explanation.
marshal(Tags.NAMESPACE_V11, Tags.BOUNDS, Bounds.class, metadata.bounds);
}
}
}
}
/**
* Writes the given feature.
*
* @param feature the feature to write, or {@code null} if none.
* @throws DataStoreException if the given feature is not a recognized type.
* @throws XMLStreamException if underlying STAX writer encounter an error.
* @throws JAXBException if underlying JAXB marshaller encounter an error.
*/
@Override
public void write(final AbstractFeature feature) throws DataStoreException, XMLStreamException, JAXBException {
if (feature != null) {
final Types types = ((Store) owner).types;
final DefaultFeatureType type = feature.getType();
if (types.wayPoint.isAssignableFrom(type)) {
writeWayPoint(feature, Tags.WAY_POINT);
} else {
final boolean isRoute = types.route.isAssignableFrom(type);
if (!isRoute && !types.track.isAssignableFrom(type)) {
throw new IllegalFeatureTypeException(owner.getLocale(), owner.getFormatName(), type.getName());
}
writer.writeStartElement(isRoute ? Tags.ROUTES : Tags.TRACKS);
writeSingleValue(Tags.NAME, feature.getPropertyValue(Tags.NAME));
writeSingleValue(Tags.COMMENT, feature.getPropertyValue(Tags.COMMENT));
writeSingleValue(Tags.DESCRIPTION, feature.getPropertyValue(Tags.DESCRIPTION));
writeSingleValue(Tags.SOURCE, feature.getPropertyValue(Tags.SOURCE));
writeLinks((Collection<?>) feature.getPropertyValue(Tags.LINK));
writeSingleValue(Tags.NUMBER, feature.getPropertyValue(Tags.NUMBER));
if (version != 0) {
writeSingleValue(Tags.TYPE, feature.getPropertyValue(Tags.TYPE));
}
if (isRoute) {
for (Object prop : (Collection<?>) feature.getPropertyValue(Tags.ROUTE_POINTS)) {
writeWayPoint((AbstractFeature) prop, Tags.ROUTE_POINTS);
}
} else {
for (Object segment : (Collection<?>) feature.getPropertyValue(Tags.TRACK_SEGMENTS)) {
if (segment != null) {
writer.writeStartElement(Tags.TRACK_SEGMENTS);
for (Object prop : (Collection<?>) ((AbstractFeature) segment).getPropertyValue(Tags.TRACK_POINTS)) {
writeWayPoint((AbstractFeature) prop, Tags.TRACK_POINTS);
}
writer.writeEndElement();
}
}
}
writer.writeEndElement();
}
}
}
/**
* Writes a way point, which may be standalone or part of a route or a track segment.
*
* @param feature feature to write, or {@code null} if none.
* @param tagName way point tag name (can not be {@code null}).
* @throws XMLStreamException if underlying STAX writer encounter an error.
* @throws JAXBException if underlying JAXB marshaller encounter an error.
*/
private void writeWayPoint(final AbstractFeature feature, final String tagName) throws XMLStreamException, JAXBException {
if (feature != null) {
final double[] pt = Geometries.getCoordinate(feature.getPropertyValue(AttributeConvention.GEOMETRY));
if (pt != null && pt.length >= 2) {
writer.writeStartElement(tagName);
writer.writeAttribute(Attributes.LATITUDE, Double.toString(pt[1]));
writer.writeAttribute(Attributes.LONGITUDE, Double.toString(pt[0]));
writeSingleValue(Tags.ELEVATION, feature.getPropertyValue(Tags.ELEVATION));
writeSingleValue(Tags.TIME, feature.getPropertyValue(Tags.TIME));
writeSingleValue(Tags.MAGNETIC_VAR, feature.getPropertyValue(Tags.MAGNETIC_VAR));
writeSingleValue(Tags.GEOID_HEIGHT, feature.getPropertyValue(Tags.GEOID_HEIGHT));
writeSingleValue(Tags.NAME, feature.getPropertyValue(Tags.NAME));
writeSingleValue(Tags.COMMENT, feature.getPropertyValue(Tags.COMMENT));
writeSingleValue(Tags.DESCRIPTION, feature.getPropertyValue(Tags.DESCRIPTION));
writeSingleValue(Tags.SOURCE, feature.getPropertyValue(Tags.SOURCE));
writeLinks((Collection<?>) feature.getPropertyValue(Tags.LINK));
writeSingleValue(Tags.SYMBOL, feature.getPropertyValue(Tags.SYMBOL));
writeSingleValue(Tags.TYPE, feature.getPropertyValue(Tags.TYPE));
writeSingle((Fix) feature.getPropertyValue(Tags.FIX));
writeSingleValue(Tags.SATELITTES, feature.getPropertyValue(Tags.SATELITTES));
writeSingleValue(Tags.HDOP, feature.getPropertyValue(Tags.HDOP));
writeSingleValue(Tags.VDOP, feature.getPropertyValue(Tags.VDOP));
writeSingleValue(Tags.PDOP, feature.getPropertyValue(Tags.PDOP));
writeSingleValue(Tags.AGE_OF_GPS_DATA, feature.getPropertyValue(Tags.AGE_OF_GPS_DATA));
writeSingleValue(Tags.DGPS_ID, feature.getPropertyValue(Tags.DGPS_ID));
writer.writeEndElement();
}
}
}
/**
* Writes the value of the given enumeration. This method does nothing if the given value is null.
*/
private void writeSingle(final Fix fix) throws XMLStreamException {
if (fix != null) {
writeSingleValue(Tags.FIX, fix.toGPX());
}
}
/**
* Writes multiple links. This method does nothing if the given list is null.
*
* @param links the links to write.
* @throws XMLStreamException if underlying STAX writer encounter an error.
* @throws JAXBException if underlying JAXB marshaller encounter an error.
*/
private void writeLinks(final Collection<?> links) throws XMLStreamException, JAXBException {
if (links != null) {
for (final Object link : links) {
if (link != null) {
switch (version) {
default:
case 1: {
marshal(Tags.NAMESPACE_V11, Tags.LINK, Link.class, (Link) link);
break;
}
case 0: {
writeSingleValue(Tags.URL, ((Link) link).uri.toASCIIString());
writeSingleValue(Tags.URL_NAME, ((Link) link).text);
return; // GPX 1.0 allows only 1 URL.
}
}
}
}
}
}
}