/*
This file is part of RouteConverter.
RouteConverter is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
RouteConverter 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.
You should have received a copy of the GNU General Public License
along with RouteConverter; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Copyright (C) 2007 Christian Pesch. All Rights Reserved.
*/
package slash.navigation.gpx;
import slash.navigation.base.ParserContext;
import slash.navigation.base.RouteCharacteristics;
import slash.navigation.gpx.binding10.Gpx;
import slash.navigation.gpx.binding10.ObjectFactory;
import javax.xml.bind.JAXBException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import static java.util.Arrays.asList;
import static slash.common.io.Transfer.formatDouble;
import static slash.common.io.Transfer.*;
import static slash.navigation.base.RouteCharacteristics.*;
import static slash.navigation.common.NavigationConversion.*;
import static slash.navigation.common.UnitConversion.kmhToMs;
import static slash.navigation.gpx.GpxUtil.marshal10;
import static slash.navigation.gpx.GpxUtil.unmarshal10;
/**
* Reads and writes GPS Exchange Format 1.0 (.gpx) files.
*
* @author Christian Pesch
*/
public class Gpx10Format extends GpxFormat {
static final String VERSION = "1.0";
private final boolean reuseReadObjectsForWriting, splitNameAndDesc;
public Gpx10Format(boolean reuseReadObjectsForWriting, boolean splitNameAndDesc) {
this.reuseReadObjectsForWriting = reuseReadObjectsForWriting;
this.splitNameAndDesc = splitNameAndDesc;
}
public Gpx10Format() {
this(true, true);
}
public String getName() {
return "GPS Exchange Format " + VERSION + " (*" + getExtension() + ")";
}
void process(Gpx gpx, ParserContext<GpxRoute> context) {
if (gpx == null || !VERSION.equals(gpx.getVersion()))
return;
boolean hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond = gpx.getCreator() != null &&
("Mobile Action http://www.mobileaction.com/".equals(gpx.getCreator()) ||
"Holux Utility".equals(gpx.getCreator()));
GpxRoute wayPointsAsRoute = extractWayPoints(gpx, hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond);
if (wayPointsAsRoute != null)
context.appendRoute(wayPointsAsRoute);
context.appendRoutes(extractRoutes(gpx, hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond));
context.appendRoutes(extractTracks(gpx, hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond));
}
public void read(InputStream source, ParserContext<GpxRoute> context) throws Exception {
Gpx gpx = unmarshal10(source);
process(gpx, context);
}
private List<GpxRoute> extractRoutes(Gpx gpx, boolean hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond) {
List<GpxRoute> result = new ArrayList<>();
for (Gpx.Rte rte : gpx.getRte()) {
String name = rte.getName();
String desc = rte.getDesc();
List<String> descriptions = asDescription(desc);
List<GpxPosition> positions = extractRoute(rte, hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond);
result.add(new GpxRoute(this, Route, name, descriptions, positions, gpx, rte));
}
return result;
}
private GpxRoute extractWayPoints(Gpx gpx, boolean hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond) {
String name = gpx.getName();
List<String> descriptions = asDescription(gpx.getDesc());
List<GpxPosition> positions = extractWayPoints(gpx.getWpt(), hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond);
return positions.size() == 0 ? null : new GpxRoute(this, isTripmasterTrack(positions) ? Track : Waypoints, name, descriptions, positions, gpx);
}
boolean isTripmasterTrack(List<GpxPosition> positions) {
for (GpxPosition position : positions) {
if (position.getReason() == null)
return false;
}
return true;
}
private List<GpxRoute> extractTracks(Gpx gpx, boolean hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond) {
List<GpxRoute> result = new ArrayList<>();
for (Gpx.Trk trk : gpx.getTrk()) {
String name = trk.getName();
String desc = trk.getDesc();
List<String> descriptions = asDescription(desc);
List<GpxPosition> positions = extractTrack(trk, hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond);
if (positions.size() > 0)
result.add(new GpxRoute(this, Track, name, descriptions, positions, gpx, trk));
}
return result;
}
private List<GpxPosition> extractRoute(Gpx.Rte rte, boolean hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond) {
List<GpxPosition> positions = new ArrayList<>();
if (rte != null) {
for (Gpx.Rte.Rtept rtept : rte.getRtept()) {
positions.add(new GpxPosition(rtept.getLon(), rtept.getLat(), rtept.getEle(), getSpeed(rtept.getSpeed(), rtept.getCmt(), hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond), formatDouble(rtept.getCourse()), null, parseXMLTime(rtept.getTime()), asDescription(rtept.getName(), rtept.getDesc()), rtept.getHdop(), rtept.getPdop(), rtept.getVdop(), rtept.getSat(), rtept));
}
}
return positions;
}
private List<GpxPosition> extractWayPoints(List<Gpx.Wpt> wpts, boolean hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond) {
List<GpxPosition> positions = new ArrayList<>();
for (Gpx.Wpt wpt : wpts) {
positions.add(new GpxPosition(wpt.getLon(), wpt.getLat(), wpt.getEle(), getSpeed(wpt.getSpeed(), wpt.getCmt(), hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond), formatDouble(wpt.getCourse()), null, parseXMLTime(wpt.getTime()), asWayPointDescription(wpt.getName(), wpt.getDesc()), wpt.getHdop(), wpt.getPdop(), wpt.getVdop(), wpt.getSat(), wpt));
}
return positions;
}
private List<GpxPosition> extractTrack(Gpx.Trk trk, boolean hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond) {
List<GpxPosition> positions = new ArrayList<>();
if (trk != null) {
for (Gpx.Trk.Trkseg trkSeg : trk.getTrkseg()) {
for (Gpx.Trk.Trkseg.Trkpt trkPt : trkSeg.getTrkpt()) {
positions.add(new GpxPosition(trkPt.getLon(), trkPt.getLat(), trkPt.getEle(), getSpeed(trkPt.getSpeed(), trkPt.getCmt(), hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond), formatDouble(trkPt.getCourse()), null, parseXMLTime(trkPt.getTime()), asDescription(trkPt.getName(), trkPt.getDesc()), trkPt.getHdop(), trkPt.getPdop(), trkPt.getVdop(), trkPt.getSat(), trkPt));
}
}
}
return positions;
}
private Double getSpeed(BigDecimal speed, String description, boolean hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond) {
Double result = formatDouble(speed);
// everything is converted from m/s to Km/h except for the exceptional case
if(!hasSpeedInKiloMeterPerHourInsteadOfMeterPerSecond)
result = asKmh(result);
if (result == null)
result = parseSpeed(description);
return result;
}
private String formatSpeed(String description, Double speed) {
if (isEmpty(speed) || parseSpeed(description) != null)
return description;
return (description != null ? description + " " : "") +
"Speed: " + formatSpeedAsString(speed) + " Km/h";
}
private String addHeading(String description, Double heading) {
if (isEmpty(heading))
return description;
return (description != null ? description + " " : "") +
"Heading: " + formatHeadingAsString(heading);
}
private List<Gpx.Wpt> createWayPoints(GpxRoute route, int startIndex, int endIndex) {
ObjectFactory objectFactory = new ObjectFactory();
List<Gpx.Wpt> wpts = new ArrayList<>();
List<GpxPosition> positions = route.getPositions();
for (int i = startIndex; i < endIndex; i++) {
GpxPosition position = positions.get(i);
BigDecimal latitude = formatPosition(position.getLatitude());
BigDecimal longitude = formatPosition(position.getLongitude());
if(latitude == null || longitude == null)
continue;
Gpx.Wpt wpt = position.getOrigin(Gpx.Wpt.class);
if (wpt == null || !reuseReadObjectsForWriting)
wpt = objectFactory.createGpxWpt();
wpt.setLat(latitude);
wpt.setLon(longitude);
wpt.setTime(isWriteTime() ? formatXMLTime(position.getTime()) : null);
wpt.setEle(isWriteElevation() ? formatElevation(position.getElevation()) : null);
wpt.setCourse(isWriteHeading() ? formatHeading(position.getHeading()) : null);
wpt.setSpeed(isWriteSpeed() && position.getSpeed() != null ? formatBigDecimal(kmhToMs(position.getSpeed()), 3) : null);
if (isWriteSpeed() && reuseReadObjectsForWriting)
wpt.setCmt(formatSpeed(wpt.getCmt(), position.getSpeed()));
if (isWriteHeading() && reuseReadObjectsForWriting)
wpt.setCmt(addHeading(wpt.getCmt(), position.getHeading()));
wpt.setName(isWriteName() ? splitNameAndDesc ? asName(position.getDescription()) : trim(position.getDescription()) : null);
wpt.setDesc(isWriteName() && splitNameAndDesc ? asDesc(position.getDescription(), wpt.getDesc()) : null);
wpt.setHdop(isWriteAccuracy() && position.getHdop() != null ? formatBigDecimal(position.getHdop(), 6) : null);
wpt.setPdop(isWriteAccuracy() && position.getPdop() != null ? formatBigDecimal(position.getPdop(), 6) : null);
wpt.setVdop(isWriteAccuracy() && position.getVdop() != null ? formatBigDecimal(position.getVdop(), 6) : null);
wpt.setSat(isWriteAccuracy() && position.getSatellites() != null ? formatInt(position.getSatellites()) : null);
wpts.add(wpt);
}
return wpts;
}
private List<Gpx.Rte> createRoute(GpxRoute route, int startIndex, int endIndex) {
ObjectFactory objectFactory = new ObjectFactory();
List<Gpx.Rte> rtes = new ArrayList<>();
Gpx.Rte rte = route.getOrigin(Gpx.Rte.class);
if (rte != null && reuseReadObjectsForWriting)
rte.getRtept().clear();
else
rte = objectFactory.createGpxRte();
if (isWriteMetaData()) {
rte.setName(asRouteName(route.getName()));
rte.setDesc(asDescription(route.getDescription()));
}
rtes.add(rte);
List<GpxPosition> positions = route.getPositions();
for (int i = startIndex; i < endIndex; i++) {
GpxPosition position = positions.get(i);
BigDecimal latitude = formatPosition(position.getLatitude());
BigDecimal longitude = formatPosition(position.getLongitude());
if(latitude == null || longitude == null)
continue;
Gpx.Rte.Rtept rtept = position.getOrigin(Gpx.Rte.Rtept.class);
if (rtept == null || !reuseReadObjectsForWriting)
rtept = objectFactory.createGpxRteRtept();
rtept.setLat(latitude);
rtept.setLon(longitude);
rtept.setTime(isWriteTime() ? formatXMLTime(position.getTime()) : null);
rtept.setEle(isWriteElevation() ? formatElevation(position.getElevation()) : null);
rtept.setCourse(isWriteHeading() ? formatHeading(position.getHeading()) : null);
rtept.setSpeed(isWriteSpeed() && position.getSpeed() != null ? formatBigDecimal(kmhToMs(position.getSpeed()), 3) : null);
if (isWriteSpeed() && reuseReadObjectsForWriting)
rtept.setCmt(formatSpeed(rtept.getCmt(), position.getSpeed()));
if (isWriteHeading() && reuseReadObjectsForWriting)
rtept.setCmt(addHeading(rtept.getCmt(), position.getHeading()));
rtept.setName(isWriteName() ? splitNameAndDesc ? asName(position.getDescription()) : trim(position.getDescription()) : null);
rtept.setDesc(isWriteName() && splitNameAndDesc ? asDesc(position.getDescription(), rtept.getDesc()) : null);
rtept.setHdop(isWriteAccuracy() && position.getHdop() != null ? formatBigDecimal(position.getHdop(), 6) : null);
rtept.setPdop(isWriteAccuracy() && position.getPdop() != null ? formatBigDecimal(position.getPdop(), 6) : null);
rtept.setVdop(isWriteAccuracy() && position.getVdop() != null ? formatBigDecimal(position.getVdop(), 6) : null);
rtept.setSat(isWriteAccuracy() && position.getSatellites() != null ? formatInt(position.getSatellites()) : null);
rte.getRtept().add(rtept);
}
return rtes;
}
private List<Gpx.Trk> createTrack(GpxRoute route, int startIndex, int endIndex) {
ObjectFactory objectFactory = new ObjectFactory();
List<Gpx.Trk> trks = new ArrayList<>();
Gpx.Trk trk = route.getOrigin(Gpx.Trk.class);
if (trk != null && reuseReadObjectsForWriting)
trk.getTrkseg().clear();
else
trk = objectFactory.createGpxTrk();
if (isWriteMetaData()) {
trk.setName(asRouteName(route.getName()));
trk.setDesc(asDescription(route.getDescription()));
}
trks.add(trk);
Gpx.Trk.Trkseg trkseg = objectFactory.createGpxTrkTrkseg();
List<GpxPosition> positions = route.getPositions();
for (int i = startIndex; i < endIndex; i++) {
GpxPosition position = positions.get(i);
BigDecimal latitude = formatPosition(position.getLatitude());
BigDecimal longitude = formatPosition(position.getLongitude());
if(latitude == null || longitude == null)
continue;
Gpx.Trk.Trkseg.Trkpt trkpt = position.getOrigin(Gpx.Trk.Trkseg.Trkpt.class);
if (trkpt == null || !reuseReadObjectsForWriting)
trkpt = objectFactory.createGpxTrkTrksegTrkpt();
trkpt.setLat(latitude);
trkpt.setLon(longitude);
trkpt.setTime(isWriteTime() ? formatXMLTime(position.getTime()) : null);
trkpt.setEle(isWriteElevation() ? formatElevation(position.getElevation()) : null);
trkpt.setCourse(isWriteHeading() ? formatHeading(position.getHeading()) : null);
trkpt.setSpeed(isWriteSpeed() && position.getSpeed() != null ?
formatBigDecimal(kmhToMs(position.getSpeed()), 3) : null);
trkpt.setName(isWriteName() ? splitNameAndDesc ? asName(position.getDescription()) : trim(position.getDescription()) : null);
trkpt.setDesc(isWriteName() && splitNameAndDesc ? asDesc(position.getDescription(), trkpt.getDesc()) : null);
trkpt.setHdop(isWriteAccuracy() && position.getHdop() != null ? formatBigDecimal(position.getHdop(), 6) : null);
trkpt.setPdop(isWriteAccuracy() && position.getPdop() != null ? formatBigDecimal(position.getPdop(), 6) : null);
trkpt.setVdop(isWriteAccuracy() && position.getVdop() != null ? formatBigDecimal(position.getVdop(), 6) : null);
trkpt.setSat(isWriteAccuracy() && position.getSatellites() != null ? formatInt(position.getSatellites()) : null);
trkseg.getTrkpt().add(trkpt);
}
trk.getTrkseg().add(trkseg);
return trks;
}
private Gpx recycleGpx(GpxRoute route) {
Gpx gpx = route.getOrigin(Gpx.class);
if (gpx != null) {
gpx.getRte().clear();
gpx.getTrk().clear();
gpx.getWpt().clear();
}
return gpx;
}
private void createMetaData(GpxRoute route, Gpx gpx) {
if (isWriteMetaData()) {
gpx.setName(asRouteName(route.getName()));
gpx.setDesc(asDescription(route.getDescription()));
}
}
private Gpx createGpx(GpxRoute route, int startIndex, int endIndex, List<RouteCharacteristics> characteristics) {
Gpx gpx = recycleGpx(route);
if (gpx == null || !reuseReadObjectsForWriting)
gpx = new ObjectFactory().createGpx();
gpx.setCreator(GENERATED_BY);
gpx.setVersion(VERSION);
for (RouteCharacteristics characteristic : characteristics) {
switch (characteristic) {
case Route:
gpx.getRte().addAll(createRoute(route, startIndex, endIndex));
break;
case Track:
gpx.getTrk().addAll(createTrack(route, startIndex, endIndex));
break;
case Waypoints:
createMetaData(route, gpx);
gpx.getWpt().addAll(createWayPoints(route, startIndex, endIndex));
break;
default:
throw new IllegalArgumentException("Unknown RouteCharacteristics " + characteristic);
}
}
return gpx;
}
private Gpx createGpx(List<GpxRoute> routes) {
ObjectFactory objectFactory = new ObjectFactory();
Gpx gpx = null;
for(GpxRoute route : routes) {
gpx = recycleGpx(route);
if(gpx != null)
break;
}
if (gpx == null || !reuseReadObjectsForWriting)
gpx = objectFactory.createGpx();
gpx.setCreator(GENERATED_BY);
gpx.setVersion(VERSION);
for (GpxRoute route : routes) {
switch (route.getCharacteristics()) {
case Waypoints:
createMetaData(route, gpx);
gpx.getWpt().addAll(createWayPoints(route, 0, route.getPositionCount()));
break;
case Route:
gpx.getRte().addAll(createRoute(route, 0, route.getPositionCount()));
break;
case Track:
gpx.getTrk().addAll(createTrack(route, 0, route.getPositionCount()));
break;
default:
throw new IllegalArgumentException("Unknown RouteCharacteristics " + route.getCharacteristics());
}
}
return gpx;
}
public void write(GpxRoute route, OutputStream target, int startIndex, int endIndex) {
write(route, target, startIndex, endIndex, asList(Route, Track, Waypoints));
}
public void write(GpxRoute route, OutputStream target, int startIndex, int endIndex, List<RouteCharacteristics> characteristics) {
try {
marshal10(createGpx(route, startIndex, endIndex, characteristics), target);
} catch (JAXBException e) {
throw new IllegalArgumentException(e);
}
}
public void write(List<GpxRoute> routes, OutputStream target) {
try {
marshal10(createGpx(routes), target);
} catch (JAXBException e) {
throw new IllegalArgumentException(e);
}
}
}