package com.github.pfichtner.jrunalyser.base.data.jaxb;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.util.JAXBResult;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import com.github.pfichtner.jrunalyser.base.data.DefaultLink;
import com.github.pfichtner.jrunalyser.base.data.DefaultLinkedWayPoint;
import com.github.pfichtner.jrunalyser.base.data.DefaultWayPoint;
import com.github.pfichtner.jrunalyser.base.data.Link;
import com.github.pfichtner.jrunalyser.base.data.LinkedTrackPoint;
import com.github.pfichtner.jrunalyser.base.data.WayPoint;
import com.github.pfichtner.jrunalyser.base.data.segment.DefaultSegment;
import com.github.pfichtner.jrunalyser.base.data.segment.Segment;
import com.github.pfichtner.jrunalyser.base.data.stat.DefaultStatistics;
import com.github.pfichtner.jrunalyser.base.data.track.DefaultTrack;
import com.github.pfichtner.jrunalyser.base.data.track.Metadata;
import com.github.pfichtner.jrunalyser.base.data.track.Track;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.topografix.gpx._1._1.BoundsType;
import com.topografix.gpx._1._1.GpxType;
import com.topografix.gpx._1._1.MetadataType;
import com.topografix.gpx._1._1.TrkType;
import com.topografix.gpx._1._1.TrksegType;
import com.topografix.gpx._1._1.WptType;
public final class GpxUnmarshaller {
public static class MetadataAdapter implements Metadata {
private final Optional<MetadataType> metadata;
private final static MetadataType NULL = createMetadataType();
private static MetadataType createMetadataType() {
MetadataType result = new MetadataType();
BoundsType bounds = new BoundsType();
BigDecimal zero = BigDecimal.ZERO;
bounds.setMinlat(zero);
bounds.setMaxlat(zero);
bounds.setMinlon(zero);
bounds.setMaxlon(zero);
result.setBounds(bounds);
return result;
}
public MetadataAdapter(MetadataType metadata) {
this.metadata = Optional.fromNullable(metadata);
}
@Override
public String getName() {
return this.metadata.or(NULL).getName();
}
@Override
public String getDescription() {
return this.metadata.or(NULL).getDesc();
}
@Override
public Long getTime() {
XMLGregorianCalendar cal = this.metadata.or(NULL).getTime();
return cal == null ? null : Long.valueOf(cal.toGregorianCalendar()
.getTimeInMillis());
}
@Override
public double getMinLatitude() {
return this.metadata.or(NULL).getBounds().getMinlat().doubleValue();
}
@Override
public double getMinLongitude() {
return this.metadata.or(NULL).getBounds().getMinlon().doubleValue();
}
@Override
public double getMaxLatitude() {
return this.metadata.or(NULL).getBounds().getMaxlat().doubleValue();
}
@Override
public double getMaxLongitude() {
return this.metadata.or(NULL).getBounds().getMaxlon().doubleValue();
}
}
private GpxUnmarshaller() {
super();
}
public static class WayPointAdapter implements WayPoint {
private final WptType wptType;
public WayPointAdapter(WptType wptType) {
this.wptType = wptType;
}
public double getLatitude() {
return this.wptType.getLat().doubleValue();
}
public double getLongitude() {
return this.wptType.getLon().doubleValue();
}
public Integer getElevation() {
BigDecimal ele = this.wptType.getEle();
return ele == null ? null : Integer.valueOf(ele.intValue());
}
public Long getTime() {
XMLGregorianCalendar cal = this.wptType.getTime();
return cal == null ? null : Long.valueOf(cal.toGregorianCalendar()
.getTimeInMillis());
}
@Override
public String getName() {
return this.wptType.getName();
}
@Override
public String toString() {
return "TrackPointAdapter [latitude=" + getLatitude()
+ ", longitude=" + getLongitude() + ", elevation="
+ getElevation() + ", time=" + getTime() + "]";
}
}
private static GpxType loadGpxFile(InputStream is) throws IOException {
try {
JAXBContext context = JAXBContext.newInstance(GpxType.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder loader = factory.newDocumentBuilder();
Document document = loader.parse(is);
String namespace = document.getDocumentElement().getNamespaceURI();
Object unmarshal;
if ("http://www.topografix.com/GPX/1/1".equals(namespace)) {
unmarshal = unmarshaller.unmarshal(document);
} else if ("http://www.topografix.com/GPX/1/0".equals(namespace)) {
unmarshal = xsltTransform(unmarshaller, document).getResult();
} else {
throw new IOException(
"Expected GPX 1.0 or GPX1.1 namespace but found \n\""
+ namespace + "\"");
}
@SuppressWarnings("unchecked")
JAXBElement<GpxType> element = (JAXBElement<GpxType>) unmarshal;
return element.getValue();
} catch (JAXBException e) {
throw new IOException(e);
} catch (TransformerConfigurationException e) {
throw new IOException(e);
} catch (TransformerException e) {
throw new IOException(e);
} catch (TransformerFactoryConfigurationError e) {
throw new IOException(e);
} catch (ParserConfigurationException e) {
throw new IOException(e);
} catch (SAXException e) {
throw new IOException(e);
}
}
private static JAXBResult xsltTransform(Unmarshaller unmarshaller,
Document document) throws JAXBException, TransformerException,
TransformerConfigurationException,
TransformerFactoryConfigurationError {
JAXBResult result = new JAXBResult(unmarshaller);
StreamSource source = new StreamSource(
GpxUnmarshaller.class
.getResourceAsStream("/data/gpx/xsl/gpx10to11.xsl"));
TransformerFactory.newInstance().newTransformer(source)
.transform(new DOMSource(document), result);
return result;
}
// TODO Change type to "SimpleTrack"
public static Track loadTrack(InputStream inputStream) throws IOException {
try {
return convert(loadGpxFile(inputStream));
} catch (Exception e) {
throw new IOException("Error reading from InputStream "
+ inputStream, e);
}
}
public static Track loadTrack(File file) throws IOException {
try {
FileInputStream is = new FileInputStream(file);
try {
return convert(loadGpxFile(is));
} catch (IOException e) {
throw new IOException("Error loading " + file, e);
} finally {
is.close();
}
} catch (Exception e) {
throw new IOException("Error reading file " + file, e);
}
}
private static Track convert(GpxType gpxType) {
List<Segment> segments = segments(gpxType);
List<WayPoint> waypoints = waypoints(gpxType);
Metadata metadata = new MetadataAdapter(gpxType.getMetadata());
// we do not trust in bounds pre-calculated!
List<LinkedTrackPoint> trkpts = getAllTrackpoints(segments);
Metadata md = trkpts.isEmpty() ? null
: new Delegate2MetadatForMinMaxLatLon(metadata, trkpts);
return new DefaultTrack(null, md, waypoints, segments, null);
}
private static List<LinkedTrackPoint> getAllTrackpoints(
List<Segment> segments) {
List<LinkedTrackPoint> all = Lists.newArrayList();
for (Segment segment : segments) {
all.addAll(segment.getTrackpoints());
}
return all;
}
private static List<WayPoint> waypoints(GpxType gpx) {
return toWaypoints(gpx.getWpt());
}
private static List<Segment> segments(GpxType gpx) {
List<Segment> result = Lists.newArrayList();
for (TrkType trkType : gpx.getTrk()) {
for (TrksegType trksegType : trkType.getTrkseg()) {
List<WptType> trkpt = trksegType.getTrkpt();
if (!trkpt.isEmpty()) {
result.add(convert(trkpt));
}
}
}
return result;
}
private static Segment convert(List<WptType> toConvert) {
List<LinkedTrackPoint> wps = toLinked(toWaypoints(toConvert));
return new DefaultSegment(wps, DefaultStatistics.ofWaypoints(wps));
}
private static List<WayPoint> toWaypoints(List<WptType> toConvert) {
List<WayPoint> result = Lists.newArrayList();
for (WptType wptType : toConvert) {
result.add(DefaultWayPoint.of(new WayPointAdapter(wptType)));
}
return result;
}
public static List<LinkedTrackPoint> toLinked(
Collection<? extends WayPoint> wps) {
return fill(wps,
Lists.<LinkedTrackPoint> newArrayListWithExpectedSize(wps
.size()));
}
public static List<LinkedTrackPoint> toLinked(
Iterable<? extends WayPoint> wps) {
return fill(wps, Lists.<LinkedTrackPoint> newArrayList());
}
private static List<LinkedTrackPoint> fill(
Iterable<? extends WayPoint> wps, List<LinkedTrackPoint> result) {
WayPoint last = null;
for (WayPoint next : wps) {
if (last != null) {
Link link = DefaultLink.of(last, next);
result.add(DefaultLinkedWayPoint.of(last, link));
}
last = next;
}
if (last != null) {
result.add(DefaultLinkedWayPoint.of(last, null));
}
return result;
}
}