mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
GxpParser: extraction more waypoint and trackpoint data (#5465)
This commit is contained in:
committed by
José Rebelo
parent
2f127e1982
commit
c8f6b8cfaf
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2017-2024 Carsten Pfeiffer, José Rebelo, Petr Vaněk
|
/* Copyright (C) 2017-2025 Carsten Pfeiffer, José Rebelo, Petr Vaněk, Thomas Kuehne
|
||||||
|
|
||||||
This file is part of Gadgetbridge.
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
@@ -42,12 +42,16 @@ public class GPSCoordinate implements Parcelable {
|
|||||||
public static final int GPS_DECIMAL_DEGREES_SCALE = 6; // precise to 111.132mm at equator: https://en.wikipedia.org/wiki/Decimal_degrees
|
public static final int GPS_DECIMAL_DEGREES_SCALE = 6; // precise to 111.132mm at equator: https://en.wikipedia.org/wiki/Decimal_degrees
|
||||||
|
|
||||||
public GPSCoordinate(double longitude, double latitude, double altitude) {
|
public GPSCoordinate(double longitude, double latitude, double altitude) {
|
||||||
|
this(longitude, latitude, altitude, UNKNOWN_DOP, UNKNOWN_DOP, UNKNOWN_DOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GPSCoordinate(double longitude, double latitude, double altitude, double hdop, double vdop, double pdop) {
|
||||||
this.longitude = longitude;
|
this.longitude = longitude;
|
||||||
this.latitude = latitude;
|
this.latitude = latitude;
|
||||||
this.altitude = altitude;
|
this.altitude = Double.isNaN(altitude) ? UNKNOWN_ALTITUDE : altitude;
|
||||||
this.hdop = UNKNOWN_DOP;
|
this.hdop = Double.isNaN(hdop) ? UNKNOWN_DOP : hdop;
|
||||||
this.vdop = UNKNOWN_DOP;
|
this.vdop = Double.isNaN(vdop) ? UNKNOWN_DOP : vdop;
|
||||||
this.pdop = UNKNOWN_DOP;
|
this.pdop = Double.isNaN(pdop) ? UNKNOWN_DOP : pdop;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GPSCoordinate(double longitude, double latitude) {
|
public GPSCoordinate(double longitude, double latitude) {
|
||||||
@@ -58,6 +62,9 @@ public class GPSCoordinate implements Parcelable {
|
|||||||
latitude = in.readDouble();
|
latitude = in.readDouble();
|
||||||
longitude = in.readDouble();
|
longitude = in.readDouble();
|
||||||
altitude = in.readDouble();
|
altitude = in.readDouble();
|
||||||
|
hdop = in.readDouble();
|
||||||
|
vdop = in.readDouble();
|
||||||
|
pdop = in.readDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getLatitude() {
|
public double getLatitude() {
|
||||||
@@ -171,6 +178,9 @@ public class GPSCoordinate implements Parcelable {
|
|||||||
dest.writeDouble(latitude);
|
dest.writeDouble(latitude);
|
||||||
dest.writeDouble(longitude);
|
dest.writeDouble(longitude);
|
||||||
dest.writeDouble(altitude);
|
dest.writeDouble(altitude);
|
||||||
|
dest.writeDouble(hdop);
|
||||||
|
dest.writeDouble(vdop);
|
||||||
|
dest.writeDouble(pdop);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import com.google.gson.internal.bind.util.ISO8601Utils;
|
import com.google.gson.internal.bind.util.ISO8601Utils;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.TestOnly;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
@@ -27,11 +28,20 @@ import org.xmlpull.v1.XmlPullParserException;
|
|||||||
import org.xmlpull.v1.XmlPullParserFactory;
|
import org.xmlpull.v1.XmlPullParserFactory;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.text.ParsePosition;
|
import java.text.ParsePosition;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import javax.xml.transform.Source;
|
||||||
|
import javax.xml.transform.Transformer;
|
||||||
|
import javax.xml.transform.TransformerFactory;
|
||||||
|
import javax.xml.transform.stream.StreamResult;
|
||||||
|
import javax.xml.transform.stream.StreamSource;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
|
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrack;
|
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrack;
|
||||||
@@ -85,6 +95,28 @@ public class GpxParser {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// simplify the GPX for parsing
|
||||||
|
@Nullable
|
||||||
|
@TestOnly
|
||||||
|
public static byte[] transformGpx(InputStream input){
|
||||||
|
try {
|
||||||
|
Source xmlSource = new StreamSource(input);
|
||||||
|
try (final InputStream xsltStream = GBApplication.getContext().getResources().openRawResource(R.raw.gpx_xslt)) {
|
||||||
|
Source xsltSource = new StreamSource(xsltStream);
|
||||||
|
|
||||||
|
TransformerFactory factory = TransformerFactory.newInstance();
|
||||||
|
Transformer transformer = factory.newTransformer(xsltSource);
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
final StreamResult result = new StreamResult(outputStream);
|
||||||
|
transformer.transform(xmlSource, result);
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("transformGpx ", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isGpxFile(final byte[] data) {
|
public static boolean isGpxFile(final byte[] data) {
|
||||||
for(byte[] header : XML_HEADER){
|
for(byte[] header : XML_HEADER){
|
||||||
if(ArrayUtils.equals(data, header, 0)){
|
if(ArrayUtils.equals(data, header, 0)){
|
||||||
@@ -103,8 +135,8 @@ public class GpxParser {
|
|||||||
public GpxParser(final InputStream stream) throws GpxParseException {
|
public GpxParser(final InputStream stream) throws GpxParseException {
|
||||||
this.fileBuilder = new GpxFile.Builder();
|
this.fileBuilder = new GpxFile.Builder();
|
||||||
|
|
||||||
try {
|
try (InputStream simple = new ByteArrayInputStream(transformGpx(stream))){
|
||||||
parser = createXmlParser(stream);
|
parser = createXmlParser(simple);
|
||||||
parseGpx();
|
parseGpx();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
throw new GpxParseException("Failed to parse gpx", e);
|
throw new GpxParseException("Failed to parse gpx", e);
|
||||||
@@ -123,43 +155,84 @@ public class GpxParser {
|
|||||||
return parser;
|
return parser;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseGpx() throws Exception {
|
private Date getDate(String attribute, Date defaultDate) throws Exception {
|
||||||
eventType = parser.getEventType();
|
final String text = parser.getAttributeValue(null, attribute);
|
||||||
|
if (text == null || text.length() < 1) {
|
||||||
|
return defaultDate;
|
||||||
|
}
|
||||||
|
return ISO8601Utils.parse(text, new ParsePosition(0));
|
||||||
|
}
|
||||||
|
|
||||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
private double getDouble(String attribute, double defaultDouble) {
|
||||||
|
final String text = parser.getAttributeValue(null, attribute);
|
||||||
|
if (text == null || text.length() < 1) {
|
||||||
|
return defaultDouble;
|
||||||
|
}
|
||||||
|
return Double.parseDouble(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getFloat(String attribute, float defaultFloat) {
|
||||||
|
final String text = parser.getAttributeValue(null, attribute);
|
||||||
|
if (text == null || text.length() < 1) {
|
||||||
|
return defaultFloat;
|
||||||
|
}
|
||||||
|
return Float.parseFloat(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getInt(String attribute, int defaultInt) {
|
||||||
|
final String text = parser.getAttributeValue(null, attribute);
|
||||||
|
if (text == null || text.length() < 1) {
|
||||||
|
return defaultInt;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseGpx() throws Exception {
|
||||||
|
for (eventType = parser.getEventType(); eventType != XmlPullParser.END_DOCUMENT; eventType = parser.next()) {
|
||||||
if (eventType == XmlPullParser.START_TAG) {
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
switch (parser.getName()) {
|
String name = parser.getName();
|
||||||
|
switch (name) {
|
||||||
|
case "gpx":
|
||||||
|
fileBuilder.withName(parser.getAttributeValue(null, "name"));
|
||||||
|
fileBuilder.withAuthor(parser.getAttributeValue(null, "author"));
|
||||||
|
fileBuilder.withTime(getDate("time", null));
|
||||||
|
break;
|
||||||
case "trk":
|
case "trk":
|
||||||
final GpxTrack track = parseTrack();
|
final GpxTrack track = parseTrack();
|
||||||
if (!track.isEmpty()) {
|
if (!track.isEmpty()) {
|
||||||
fileBuilder.withTrack(track);
|
fileBuilder.withTrack(track);
|
||||||
}
|
}
|
||||||
continue;
|
break;
|
||||||
case "wpt":
|
case "wpt":
|
||||||
fileBuilder.withWaypoints(parseWaypoint());
|
final GpxWaypoint waypoint = new GpxWaypoint(
|
||||||
continue;
|
getDouble("lon", 0.0),
|
||||||
case "metadata":
|
getDouble("lat", 0.0),
|
||||||
parseMetadata();
|
getDouble("ele", Double.NaN),
|
||||||
continue;
|
getDate("time", null),
|
||||||
|
parser.getAttributeValue(null, "name"),
|
||||||
|
parser.getAttributeValue(null, "desc"),
|
||||||
|
parser.getAttributeValue(null, "sym"),
|
||||||
|
getDouble("hdop", Double.NaN),
|
||||||
|
getDouble("vdop", Double.NaN),
|
||||||
|
getDouble("pdop", Double.NaN),
|
||||||
|
getFloat("temperature", Float.NaN),
|
||||||
|
getFloat("depth", Float.NaN)
|
||||||
|
);
|
||||||
|
fileBuilder.withWaypoints(waypoint);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eventType = parser.next();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private GpxTrack parseTrack() throws Exception {
|
private GpxTrack parseTrack() throws Exception {
|
||||||
final GpxTrack.Builder trackBuilder = new GpxTrack.Builder();
|
final GpxTrack.Builder trackBuilder = new GpxTrack.Builder();
|
||||||
|
|
||||||
|
trackBuilder.withName(parser.getAttributeValue(null, "name"));
|
||||||
|
trackBuilder.withType(parser.getAttributeValue(null, "type"));
|
||||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("trk")) {
|
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("trk")) {
|
||||||
if (eventType == XmlPullParser.START_TAG) {
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
switch (parser.getName()) {
|
switch (parser.getName()) {
|
||||||
case "name":
|
|
||||||
trackBuilder.withName(parseStringContent("name"));
|
|
||||||
continue;
|
|
||||||
case "type":
|
|
||||||
trackBuilder.withType(parseStringContent("type"));
|
|
||||||
continue;
|
|
||||||
case "trkseg":
|
case "trkseg":
|
||||||
final GpxTrackSegment segment = parseTrackSegment();
|
final GpxTrackSegment segment = parseTrackSegment();
|
||||||
if (!segment.getTrackPoints().isEmpty()) {
|
if (!segment.getTrackPoints().isEmpty()) {
|
||||||
@@ -182,9 +255,25 @@ public class GpxParser {
|
|||||||
if (eventType == XmlPullParser.START_TAG) {
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
switch (parser.getName()) {
|
switch (parser.getName()) {
|
||||||
case "trkpt":
|
case "trkpt":
|
||||||
final GpxTrackPoint trackPoint = parseTrackPoint();
|
final GpxTrackPoint trackPoint = new GpxTrackPoint(
|
||||||
|
getDouble("lon", 0.0),
|
||||||
|
getDouble("lat", 0.0),
|
||||||
|
getDouble("ele", Double.NaN),
|
||||||
|
getDate("time", null),
|
||||||
|
parser.getAttributeValue(null, "name"),
|
||||||
|
parser.getAttributeValue(null, "desc"),
|
||||||
|
parser.getAttributeValue(null, "sym"),
|
||||||
|
getDouble("hdop", Double.NaN),
|
||||||
|
getDouble("vdop", Double.NaN),
|
||||||
|
getDouble("pdop", Double.NaN),
|
||||||
|
getInt("hr", -1),
|
||||||
|
getFloat("speed", -1),
|
||||||
|
getInt("cad", -1),
|
||||||
|
getFloat("temperature", Float.NaN),
|
||||||
|
getFloat("depth", Float.NaN)
|
||||||
|
);
|
||||||
segmentBuilder.withTrackPoint(trackPoint);
|
segmentBuilder.withTrackPoint(trackPoint);
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,129 +282,4 @@ public class GpxParser {
|
|||||||
|
|
||||||
return segmentBuilder.build();
|
return segmentBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String parseStringContent(final String tag) throws Exception {
|
|
||||||
String retString = "";
|
|
||||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals(tag)) {
|
|
||||||
if (eventType == XmlPullParser.TEXT) {
|
|
||||||
retString = parser.getText();
|
|
||||||
}
|
|
||||||
eventType = parser.next();
|
|
||||||
}
|
|
||||||
return retString;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double parseElevation() throws Exception {
|
|
||||||
return Double.parseDouble(parseStringContent("ele"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Date parseTime() throws Exception {
|
|
||||||
return ISO8601Utils.parse(parseStringContent("time"), new ParsePosition(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
private GpxTrackPoint parseTrackPoint() throws Exception {
|
|
||||||
final GpxTrackPoint.Builder trackPointBuilder = new GpxTrackPoint.Builder();
|
|
||||||
|
|
||||||
final String latString = parser.getAttributeValue(null, "lat");
|
|
||||||
final String lonString = parser.getAttributeValue(null, "lon");
|
|
||||||
trackPointBuilder.withLatitude(latString != null ? Double.parseDouble(latString) : 0);
|
|
||||||
trackPointBuilder.withLongitude(lonString != null ? Double.parseDouble(lonString) : 0);
|
|
||||||
|
|
||||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("trkpt")) {
|
|
||||||
if (parser.getEventType() == XmlPullParser.START_TAG) {
|
|
||||||
switch (parser.getName()) {
|
|
||||||
case "ele":
|
|
||||||
trackPointBuilder.withAltitude(parseElevation());
|
|
||||||
continue;
|
|
||||||
case "time":
|
|
||||||
trackPointBuilder.withTime(parseTime());
|
|
||||||
continue;
|
|
||||||
case "extensions":
|
|
||||||
parseExtensions(trackPointBuilder);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eventType = parser.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
return trackPointBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseExtensions(final GpxTrackPoint.Builder trackPointBuilder) throws Exception {
|
|
||||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("extensions")) {
|
|
||||||
if (parser.getEventType() == XmlPullParser.START_TAG) {
|
|
||||||
switch (parser.getName()) {
|
|
||||||
case "TrackPointExtension":
|
|
||||||
parseTrackPointExtensions(trackPointBuilder);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eventType = parser.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseTrackPointExtensions(final GpxTrackPoint.Builder trackPointBuilder) throws Exception {
|
|
||||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("TrackPointExtension")) {
|
|
||||||
if (parser.getEventType() == XmlPullParser.START_TAG) {
|
|
||||||
switch (parser.getName()) {
|
|
||||||
case "cad":
|
|
||||||
trackPointBuilder.withCadence(Integer.parseInt(parseStringContent("cad")));
|
|
||||||
continue;
|
|
||||||
case "hr":
|
|
||||||
trackPointBuilder.withHeartRate(Integer.parseInt(parseStringContent("hr")));
|
|
||||||
continue;
|
|
||||||
case "speed":
|
|
||||||
trackPointBuilder.withSpeed(Float.parseFloat(parseStringContent("speed")));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eventType = parser.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private GpxWaypoint parseWaypoint() throws Exception {
|
|
||||||
final GpxWaypoint.Builder waypointBuilder = new GpxWaypoint.Builder();
|
|
||||||
|
|
||||||
final String latString = parser.getAttributeValue(null, "lat");
|
|
||||||
final String lonString = parser.getAttributeValue(null, "lon");
|
|
||||||
waypointBuilder.withLatitude(latString != null ? Double.parseDouble(latString) : 0);
|
|
||||||
waypointBuilder.withLongitude(lonString != null ? Double.parseDouble(lonString) : 0);
|
|
||||||
|
|
||||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("wpt")) {
|
|
||||||
if (parser.getEventType() == XmlPullParser.START_TAG) {
|
|
||||||
switch (parser.getName()) {
|
|
||||||
case "ele":
|
|
||||||
waypointBuilder.withAltitude(parseElevation());
|
|
||||||
continue;
|
|
||||||
case "name":
|
|
||||||
waypointBuilder.withName(parseStringContent("name"));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eventType = parser.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
return waypointBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseMetadata() throws Exception {
|
|
||||||
while (eventType != XmlPullParser.END_TAG || !parser.getName().equals("metadata")) {
|
|
||||||
if (parser.getEventType() == XmlPullParser.START_TAG) {
|
|
||||||
switch (parser.getName()) {
|
|
||||||
case "name":
|
|
||||||
fileBuilder.withName(parseStringContent("name"));
|
|
||||||
continue;
|
|
||||||
case "author":
|
|
||||||
// TODO parse author
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eventType = parser.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2023-2024 José Rebelo
|
/* Copyright (C) 2023-2025 José Rebelo, Thomas Kuehne
|
||||||
|
|
||||||
This file is part of Gadgetbridge.
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
@@ -16,7 +16,10 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
|
package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -25,17 +28,26 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
|||||||
public class GpxFile {
|
public class GpxFile {
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String author;
|
private final String author;
|
||||||
|
@Nullable
|
||||||
|
private final Date time;
|
||||||
|
|
||||||
private final List<GpxTrack> tracks;
|
private final List<GpxTrack> tracks;
|
||||||
private final List<GpxWaypoint> waypoints;
|
private final List<GpxWaypoint> waypoints;
|
||||||
|
|
||||||
public GpxFile(final String name, final String author, final List<GpxTrack> tracks, final List<GpxWaypoint> waypoints) {
|
public GpxFile(@Nullable final String name, @Nullable final String author, @Nullable final Date time,
|
||||||
|
final List<GpxTrack> tracks, final List<GpxWaypoint> waypoints) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.author = author;
|
this.author = author;
|
||||||
|
this.time = time;
|
||||||
this.tracks = tracks;
|
this.tracks = tracks;
|
||||||
this.waypoints = waypoints;
|
this.waypoints = waypoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Date getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -75,10 +87,16 @@ public class GpxFile {
|
|||||||
public static class Builder {
|
public static class Builder {
|
||||||
private String name;
|
private String name;
|
||||||
private String author;
|
private String author;
|
||||||
|
private Date time;
|
||||||
|
|
||||||
private List<GpxTrack> tracks = new ArrayList<>();
|
private List<GpxTrack> tracks = new ArrayList<>();
|
||||||
private List<GpxWaypoint> waypoints = new ArrayList<>();
|
private List<GpxWaypoint> waypoints = new ArrayList<>();
|
||||||
|
|
||||||
|
public Builder withTime(final Date date) {
|
||||||
|
this.time = date;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder withName(final String name) {
|
public Builder withName(final String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
return this;
|
return this;
|
||||||
@@ -100,7 +118,7 @@ public class GpxFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GpxFile build() {
|
public GpxFile build() {
|
||||||
return new GpxFile(name, author, tracks, waypoints);
|
return new GpxFile(name, author, time, tracks, waypoints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-55
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2023-2024 José Rebelo
|
/* Copyright (C) 2023-2025 José Rebelo, Thomas Kuehne
|
||||||
|
|
||||||
This file is part of Gadgetbridge.
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
|
package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -25,27 +26,54 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||||
|
|
||||||
public class GpxTrackPoint extends GPSCoordinate {
|
public class GpxTrackPoint extends GPSCoordinate {
|
||||||
|
@Nullable
|
||||||
|
private final String name;
|
||||||
|
@Nullable
|
||||||
|
private final String symbol;
|
||||||
|
@Nullable
|
||||||
|
private final String description;
|
||||||
private final Date time;
|
private final Date time;
|
||||||
private final int heartRate;
|
private final int heartRate;
|
||||||
private final float speed;
|
private final float speed;
|
||||||
private final int cadence;
|
private final int cadence;
|
||||||
|
private final float temperature;
|
||||||
|
private final float depth;
|
||||||
|
|
||||||
public GpxTrackPoint(final double longitude, final double latitude, final double altitude, final Date time) {
|
public GpxTrackPoint(final double longitude, final double latitude, final double altitude, final Date time) {
|
||||||
this(longitude, latitude, altitude, time, -1);
|
this(longitude, latitude, altitude, time, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GpxTrackPoint(final double longitude, final double latitude, final double altitude, final Date time, final int heartRate) {
|
public GpxTrackPoint(final double longitude, final double latitude, final double altitude, final Date time, final int heartRate) {
|
||||||
this(longitude, latitude, altitude, time, heartRate, -1, -1);
|
this(longitude, latitude, altitude, time, null, null, null, Double.NaN, Double.NaN, Double.NaN, heartRate, -1, -1, Float.NaN, Float.NaN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GpxTrackPoint(final double longitude, final double latitude, final double altitude, final Date time, final int heartRate, final float speed, final int cadence) {
|
public GpxTrackPoint(final double longitude, final double latitude, final double altitude,
|
||||||
super(longitude, latitude, altitude);
|
final Date time, final String name, final String description,
|
||||||
|
final String symbol, double hdop, double vdop, double pdop,
|
||||||
|
final int heartRate, final float speed, final int cadence,
|
||||||
|
final float temperature, final float depth) {
|
||||||
|
super(longitude, latitude, altitude, hdop, vdop, pdop);
|
||||||
|
this.name = name;
|
||||||
|
this.symbol = symbol;
|
||||||
|
this.description = description;
|
||||||
this.time = time;
|
this.time = time;
|
||||||
this.heartRate = heartRate;
|
this.heartRate = heartRate;
|
||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
this.cadence = cadence;
|
this.cadence = cadence;
|
||||||
|
this.temperature = temperature;
|
||||||
|
this.depth = depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getName() {return name;}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getSymbol() {return symbol;}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getDescription() {return description;}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public Date getTime() {
|
public Date getTime() {
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
@@ -61,6 +89,12 @@ public class GpxTrackPoint extends GPSCoordinate {
|
|||||||
public int getCadence() {
|
public int getCadence() {
|
||||||
return cadence;
|
return cadence;
|
||||||
}
|
}
|
||||||
|
public float getTemperature() {
|
||||||
|
return temperature;
|
||||||
|
}
|
||||||
|
public float getDepth() {
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
@@ -70,12 +104,17 @@ public class GpxTrackPoint extends GPSCoordinate {
|
|||||||
return Objects.equals(time, that.time) &&
|
return Objects.equals(time, that.time) &&
|
||||||
Objects.equals(heartRate, that.heartRate) &&
|
Objects.equals(heartRate, that.heartRate) &&
|
||||||
Objects.equals(cadence, that.cadence) &&
|
Objects.equals(cadence, that.cadence) &&
|
||||||
Objects.equals(speed, that.speed);
|
Objects.equals(speed, that.speed) &&
|
||||||
|
Objects.equals(name, that.name) &&
|
||||||
|
Objects.equals(symbol, that.symbol) &&
|
||||||
|
Objects.equals(description, that.description) &&
|
||||||
|
Objects.equals(temperature, that.temperature) &&
|
||||||
|
Objects.equals(depth, that.depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(super.hashCode(), time, heartRate, speed, cadence);
|
return Objects.hash(super.hashCode(), time, heartRate, speed, cadence, name, symbol, description, temperature, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -94,53 +133,4 @@ public class GpxTrackPoint extends GPSCoordinate {
|
|||||||
|
|
||||||
return activityPoint;
|
return activityPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
private double longitude;
|
|
||||||
private double latitude;
|
|
||||||
private double altitude;
|
|
||||||
private Date time;
|
|
||||||
private int heartRate = -1;
|
|
||||||
private float speed = -1;
|
|
||||||
private int cadence = -1;
|
|
||||||
|
|
||||||
public Builder withLongitude(final double longitude) {
|
|
||||||
this.longitude = longitude;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withLatitude(final double latitude) {
|
|
||||||
this.latitude = latitude;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withAltitude(final double altitude) {
|
|
||||||
this.altitude = altitude;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withTime(final Date time) {
|
|
||||||
this.time = time;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withHeartRate(final int heartRate) {
|
|
||||||
this.heartRate = heartRate;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withSpeed(final float speed) {
|
|
||||||
this.speed = speed;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withCadence(final int cadence) {
|
|
||||||
this.cadence = cadence;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GpxTrackPoint build() {
|
|
||||||
return new GpxTrackPoint(longitude, latitude, altitude, time, heartRate, speed, cadence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-30
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2023-2024 José Rebelo
|
/* Copyright (C) 2023-2025 José Rebelo, Thomas Kuehne
|
||||||
|
|
||||||
This file is part of Gadgetbridge.
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
@@ -18,15 +18,48 @@ package nodomain.freeyourgadget.gadgetbridge.util.gpx.model;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||||
|
|
||||||
public class GpxWaypoint extends GPSCoordinate {
|
public class GpxWaypoint extends GPSCoordinate {
|
||||||
|
private final float depth;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final String description;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
public GpxWaypoint(final double longitude, final double latitude, final double altitude, @Nullable final String name) {
|
@Nullable
|
||||||
super(longitude, latitude, altitude);
|
private final String symbol;
|
||||||
|
|
||||||
|
private final float temperature;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Date time;
|
||||||
|
|
||||||
|
public GpxWaypoint(final double longitude, final double latitude, final double altitude,
|
||||||
|
@Nullable final Date time, @Nullable final String name,
|
||||||
|
@Nullable final String description, @Nullable final String symbol,
|
||||||
|
final double hdop, final double vdop, final double pdop,
|
||||||
|
final float temperature, final float depth) {
|
||||||
|
super(longitude, latitude, altitude, hdop, vdop, pdop);
|
||||||
|
this.depth = depth;
|
||||||
|
this.description = description;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.symbol = symbol;
|
||||||
|
this.time = time;
|
||||||
|
this.temperature = temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDepth() {
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -34,34 +67,17 @@ public class GpxWaypoint extends GPSCoordinate {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
@Nullable
|
||||||
private double longitude;
|
public String getSymbol() {
|
||||||
private double latitude;
|
return symbol;
|
||||||
private double altitude;
|
}
|
||||||
private String name;
|
|
||||||
|
|
||||||
public Builder withLongitude(final double longitude) {
|
public float getTemperature() {
|
||||||
this.longitude = longitude;
|
return temperature;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withLatitude(final double latitude) {
|
@Nullable
|
||||||
this.latitude = latitude;
|
public Date getTime() {
|
||||||
return this;
|
return time;
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withAltitude(final double altitude) {
|
|
||||||
this.altitude = altitude;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withName(final String name) {
|
|
||||||
this.name = name;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GpxWaypoint build() {
|
|
||||||
return new GpxWaypoint(longitude, latitude, altitude, name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<xsl:stylesheet
|
||||||
|
xmlns:ax="http://www.garmin.com/xmlschemas/ActivityExtension/v1"
|
||||||
|
xmlns:g3="http://www.garmin.com/xmlschemas/GpxExtensions/v3"
|
||||||
|
xmlns:gpx="http://www.topografix.com/GPX/1/1"
|
||||||
|
xmlns:tx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
|
||||||
|
xmlns:w1="http://www.garmin.com/xmlschemas/WaypointExtension/v1"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
exclude-result-prefixes="ax g3 gpx tx w1"
|
||||||
|
version="1.0">
|
||||||
|
<xsl:output
|
||||||
|
encoding="UTF-8"
|
||||||
|
indent="yes"
|
||||||
|
method="xml"
|
||||||
|
version="1.0" />
|
||||||
|
<xsl:strip-space elements="*" />
|
||||||
|
|
||||||
|
<xsl:template match="/gpx:gpx">
|
||||||
|
<gpx>
|
||||||
|
<xsl:if test="gpx:metadata/gpx:name"><xsl:attribute name="name"><xsl:value-of select="gpx:metadata/gpx:name" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:if test="gpx:metadata/gpx:author/gpx:name"><xsl:attribute name="author"><xsl:value-of select="gpx:metadata/gpx:author/gpx:name" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:if test="gpx:metadata/gpx:time"><xsl:attribute name="time"><xsl:value-of select="gpx:metadata/gpx:time" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:apply-templates select="gpx:wpt" />
|
||||||
|
<xsl:apply-templates select="gpx:trk" />
|
||||||
|
</gpx>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template match="gpx:wpt">
|
||||||
|
<wpt>
|
||||||
|
<xsl:call-template name="point" />
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="gpx:extensions/g3:WaypointExtension/g3:Temperature">
|
||||||
|
<xsl:attribute name="temperature"><xsl:value-of select="gpx:extensions/g3:WaypointExtension/g3:Temperature" /></xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="gpx:extensions/w1:WaypointExtension/w1:Temperature">
|
||||||
|
<xsl:attribute name="temperature"><xsl:value-of select="gpx:extensions/w1:WaypointExtension/w1:Temperature" /></xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
</xsl:choose>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="gpx:extensions/g3:WaypointExtension/g3:Depth">
|
||||||
|
<xsl:attribute name="depth"><xsl:value-of select="gpx:extensions/g3:WaypointExtension/g3:Depth" /></xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="gpx:extensions/w1:WaypointExtension/w1:Depth">
|
||||||
|
<xsl:attribute name="depth"><xsl:value-of select="gpx:extensions/w1:WaypointExtension/w1:Depth" /></xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
</xsl:choose>
|
||||||
|
</wpt>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template match="gpx:trk">
|
||||||
|
<trk>
|
||||||
|
<xsl:attribute name="name"><xsl:value-of select="gpx:name" /></xsl:attribute>
|
||||||
|
<xsl:if test="gpx:type"><xsl:attribute name="type"><xsl:value-of select="gpx:type" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:apply-templates select="gpx:trkseg" />
|
||||||
|
</trk>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template match="gpx:trkseg">
|
||||||
|
<trkseg>
|
||||||
|
<xsl:apply-templates select="gpx:trkpt" />
|
||||||
|
</trkseg>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template match="gpx:trkpt">
|
||||||
|
<trkpt>
|
||||||
|
<xsl:call-template name="point" />
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="gpx:extensions/tx:TrackPointExtension/tx:atemp">
|
||||||
|
<xsl:attribute name="temperature"><xsl:value-of select="gpx:extensions/tx:TrackPointExtension/tx:atemp" /></xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="gpx:extensions/g3:TrackPointExtension/g3:Temperature">
|
||||||
|
<xsl:attribute name="temperature"><xsl:value-of select="gpx:extensions/g3:TrackPointExtension/g3:Temperature" /></xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
</xsl:choose>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="gpx:extensions/tx:TrackPointExtension/tx:depth">
|
||||||
|
<xsl:attribute name="depth"><xsl:value-of select="gpx:extensions/tx:TrackPointExtension/tx:depth" /></xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="gpx:extensions/g3:TrackPointExtension/g3:Depth">
|
||||||
|
<xsl:attribute name="depth"><xsl:value-of select="gpx:extensions/g3:TrackPointExtension/g3:Depth" /></xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
</xsl:choose>
|
||||||
|
<xsl:if test="gpx:extensions/tx:TrackPointExtension/tx:hr"><xsl:attribute name="hr"><xsl:value-of select="gpx:extensions/tx:TrackPointExtension/tx:hr" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:if test="gpx:extensions/tx:TrackPointExtension/tx:cad"><xsl:attribute name="cad"><xsl:value-of select="gpx:extensions/tx:TrackPointExtension/tx:cad" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:if test="gpx:extensions/ax:TrackPointExtension/ax:speed"><xsl:attribute name="speed"><xsl:value-of select="gpx:extensions/ax:TrackPointExtension/ax:speed" /></xsl:attribute></xsl:if>
|
||||||
|
</trkpt>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template name="point">
|
||||||
|
<xsl:attribute name="lat"><xsl:value-of select="@lat" /></xsl:attribute>
|
||||||
|
<xsl:attribute name="lon"><xsl:value-of select="@lon" /></xsl:attribute>
|
||||||
|
<xsl:if test="gpx:ele"><xsl:attribute name="ele"><xsl:value-of select="gpx:ele" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:if test="gpx:time"><xsl:attribute name="time"><xsl:value-of select="gpx:time" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:if test="gpx:name"><xsl:attribute name="name"><xsl:value-of select="gpx:name" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:if test="gpx:desc"><xsl:attribute name="desc"><xsl:value-of select="gpx:desc" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="gpx:desc">
|
||||||
|
<xsl:attribute name="desc"><xsl:value-of select="gpx:desc" /></xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="gpx:cmt">
|
||||||
|
<xsl:attribute name="desc"><xsl:value-of select="gpx:cmt" /></xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
</xsl:choose>
|
||||||
|
<xsl:if test="gpx:sym"><xsl:attribute name="sym"><xsl:value-of select="gpx:sym" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:if test="gpx:hdop"><xsl:attribute name="hdop"><xsl:value-of select="gpx:hdop" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:if test="gpx:vdop"><xsl:attribute name="vdop"><xsl:value-of select="gpx:vdop" /></xsl:attribute></xsl:if>
|
||||||
|
<xsl:if test="gpx:pdop"><xsl:attribute name="pdop"><xsl:value-of select="gpx:pdop" /></xsl:attribute></xsl:if>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
||||||
@@ -1,3 +1,19 @@
|
|||||||
|
/* Copyright (C) 2023-2025 José Rebelo, Thomas Kuehne
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.util.gpx;
|
package nodomain.freeyourgadget.gadgetbridge.util.gpx;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
@@ -7,6 +23,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -14,10 +31,14 @@ import java.util.List;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
|
import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
|
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrack;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackPoint;
|
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackPoint;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxTrackSegment;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxWaypoint;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.anyOf;
|
import static org.hamcrest.Matchers.anyOf;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupportTest.readBinaryResource;
|
||||||
|
|
||||||
public class GPXParserTest extends TestBase {
|
public class GPXParserTest extends TestBase {
|
||||||
|
|
||||||
@@ -79,4 +100,74 @@ public class GPXParserTest extends TestBase {
|
|||||||
Assert.assertEquals(gpxFile.getTracks().get(0).getTrackSegments().get(0).getTrackPoints(), segment1);
|
Assert.assertEquals(gpxFile.getTracks().get(0).getTrackSegments().get(0).getTrackPoints(), segment1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void TestGpxImport() throws Exception {
|
||||||
|
final byte[] actual;
|
||||||
|
try (final InputStream gpx = getClass().getResourceAsStream("/TestGpxImport.gpx")) {
|
||||||
|
actual = GpxParser.transformGpx(gpx);
|
||||||
|
}
|
||||||
|
byte[] expected = readBinaryResource("/TestGpxImport.xml");
|
||||||
|
Assert.assertArrayEquals(expected, actual);
|
||||||
|
|
||||||
|
final GpxFile file1;
|
||||||
|
try (final InputStream gpx = getClass().getResourceAsStream("/TestGpxImport.gpx")) {
|
||||||
|
file1 = new GpxParser(gpx).getGpxFile();
|
||||||
|
}
|
||||||
|
Assert.assertNotNull(file1);
|
||||||
|
Assert.assertEquals("Gadgetbridge's TestGpxImport.gpx", file1.getName());
|
||||||
|
Assert.assertEquals("Gadgetbridge test author", file1.getAuthor());
|
||||||
|
Assert.assertEquals(Date.from(Instant.parse("2025-10-03T18:20:45Z")), file1.getTime());
|
||||||
|
Assert.assertEquals(3, file1.getWaypoints().size());
|
||||||
|
Assert.assertEquals(1, file1.getTracks().size());
|
||||||
|
Assert.assertEquals(4, file1.getPoints().size());
|
||||||
|
|
||||||
|
GpxWaypoint wp0 = file1.getWaypoints().get(0);
|
||||||
|
Assert.assertEquals(9.7393798828125, wp0.getLongitude(), 0.0001);
|
||||||
|
Assert.assertEquals(47.5048828125, wp0.getLatitude(), 0.0001);
|
||||||
|
Assert.assertEquals(-470.1, wp0.getAltitude(), 0.0001);
|
||||||
|
Assert.assertEquals(Date.from(Instant.parse("2025-10-03T14:10:59Z")), wp0.getTime());
|
||||||
|
Assert.assertEquals("Bregenz", wp0.getName());
|
||||||
|
Assert.assertEquals("Airport", wp0.getSymbol());
|
||||||
|
Assert.assertEquals("city in Österreich", wp0.getDescription());
|
||||||
|
Assert.assertEquals(1.4, wp0.getHdop(), 0.0001);
|
||||||
|
Assert.assertEquals(3.2, wp0.getVdop(),0.0001);
|
||||||
|
Assert.assertEquals(1.5, wp0.getPdop(), 0.0001);
|
||||||
|
Assert.assertEquals(9.1, wp0.getTemperature(), 0.0001);
|
||||||
|
Assert.assertEquals(6.1, wp0.getDepth(), 0.0001);
|
||||||
|
|
||||||
|
Assert.assertEquals("腓特烈港", file1.getWaypoints().get(1).getName());
|
||||||
|
|
||||||
|
GpxTrack trk0 = file1.getTracks().get(0);
|
||||||
|
Assert.assertEquals("Gadgetbridge import test track", trk0.getName());
|
||||||
|
Assert.assertEquals(1, trk0.getTrackSegments().size());
|
||||||
|
|
||||||
|
GpxTrackSegment trkseg0 = trk0.getTrackSegments().get(0);
|
||||||
|
Assert.assertEquals(4, trkseg0.getTrackPoints().size());
|
||||||
|
|
||||||
|
GpxTrackPoint trkpt0 = trkseg0.getTrackPoints().get(0);
|
||||||
|
Assert.assertEquals(47.5048828125, trkpt0.getLatitude(), 0.0001);
|
||||||
|
Assert.assertEquals(9.7393798828125, trkpt0.getLongitude(), 0.0001);
|
||||||
|
Assert.assertEquals(-440.2, trkpt0.getAltitude(), 0.0001);
|
||||||
|
Assert.assertEquals(Date.from(Instant.parse("2025-10-03T15:10:59Z")), trkpt0.getTime());
|
||||||
|
Assert.assertEquals("Tp1", trkpt0.getName());
|
||||||
|
Assert.assertEquals("Summit", trkpt0.getSymbol());
|
||||||
|
Assert.assertEquals("a description", trkpt0.getDescription());
|
||||||
|
Assert.assertEquals(1.4, trkpt0.getHdop(), 0.0001);
|
||||||
|
Assert.assertEquals(3.2, trkpt0.getVdop(), 0.0001);
|
||||||
|
Assert.assertEquals(1.5, trkpt0.getPdop(), 0.0001);
|
||||||
|
Assert.assertEquals(123, trkpt0.getHeartRate());
|
||||||
|
Assert.assertEquals(22, trkpt0.getCadence());
|
||||||
|
Assert.assertEquals(23.3f, trkpt0.getSpeed(), 0.0001f);
|
||||||
|
Assert.assertEquals(28.3f, trkpt0.getTemperature(), 0.0001f);
|
||||||
|
Assert.assertEquals(26.3f, trkpt0.getDepth(), 0.0001f);
|
||||||
|
|
||||||
|
byte[] raw = readBinaryResource("/TestGpxImport.gpx");
|
||||||
|
GpxFile file2 = GpxParser.parseGpx(raw);
|
||||||
|
Assert.assertNotNull(file2);
|
||||||
|
Assert.assertEquals(3, file2.getWaypoints().size());
|
||||||
|
Assert.assertEquals(4, file2.getPoints().size());
|
||||||
|
Assert.assertEquals(1, file2.getTracks().size());
|
||||||
|
Assert.assertEquals(trkpt0, file2.getTracks().get(0).getTrackSegments().get(0).getTrackPoints().get(0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1"
|
||||||
|
creator="Garmin Desktop App"
|
||||||
|
xmlns:ax="http://www.garmin.com/xmlschemas/ActivityExtension/v1"
|
||||||
|
xmlns:ge="http://www.garmin.com/xmlschemas/GpxExtensions/v3"
|
||||||
|
xmlns:tpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
|
||||||
|
xmlns:wpx="http://www.garmin.com/xmlschemas/WaypointExtension/v1"
|
||||||
|
>
|
||||||
|
|
||||||
|
<metadata>
|
||||||
|
<name>Gadgetbridge's TestGpxImport.gpx</name>
|
||||||
|
<author>
|
||||||
|
<name>Gadgetbridge test author</name>
|
||||||
|
</author>
|
||||||
|
<time>2025-10-03T18:20:45Z</time>
|
||||||
|
</metadata>
|
||||||
|
|
||||||
|
<wpt lat="47.5048828125" lon="9.7393798828125">
|
||||||
|
<ele>-470.1</ele>
|
||||||
|
<time>2025-10-03T14:10:59Z</time>
|
||||||
|
<name>Bregenz</name>
|
||||||
|
<desc>city in Österreich</desc>
|
||||||
|
<sym>Airport</sym>
|
||||||
|
<hdop>1.4</hdop>
|
||||||
|
<vdop>3.2</vdop>
|
||||||
|
<pdop>1.5</pdop>
|
||||||
|
<extensions>
|
||||||
|
<wpx:WaypointExtension>
|
||||||
|
<wpx:Temperature>9.1</wpx:Temperature>
|
||||||
|
<wpx:Depth>6.1</wpx:Depth>
|
||||||
|
</wpx:WaypointExtension>
|
||||||
|
</extensions>
|
||||||
|
</wpt>
|
||||||
|
|
||||||
|
<wpt lat="47.65594482421875" lon="9.47296142578125">
|
||||||
|
<ele>8850.1</ele>
|
||||||
|
<time>2025-10-03T14:20:59Z</time>
|
||||||
|
<name>腓特烈港</name>
|
||||||
|
<cmt>another comment</cmt>
|
||||||
|
<sym>Anchor</sym>
|
||||||
|
<extensions>
|
||||||
|
<ge:WaypointExtension>
|
||||||
|
<ge:Temperature>19.2</ge:Temperature>
|
||||||
|
<ge:Depth>16.2</ge:Depth>
|
||||||
|
</ge:WaypointExtension>
|
||||||
|
</extensions>
|
||||||
|
</wpt>
|
||||||
|
|
||||||
|
<wpt lat="47.66693115234375" lon="9.1680908203125">
|
||||||
|
<name>קרויצלינגן</name>
|
||||||
|
</wpt>
|
||||||
|
|
||||||
|
<rte>
|
||||||
|
<name>Test Route</name>
|
||||||
|
<rtept lat="47.5048828125" lon="9.7393798828125">
|
||||||
|
<time>2025-10-03T14:16:06Z</time>
|
||||||
|
<name>Bregenz</name>
|
||||||
|
<sym>Triangle, Red</sym>
|
||||||
|
</rtept>
|
||||||
|
<rtept lat="47.65594482421875" lon="9.47296142578125">
|
||||||
|
<time>2025-10-03T14:16:09Z</time>
|
||||||
|
<name>Friedrichshafen</name>
|
||||||
|
<sym>City (Medium)</sym>
|
||||||
|
</rtept>
|
||||||
|
<rtept lat="47.66693115234375" lon="9.1680908203125">
|
||||||
|
<time>2025-10-03T14:16:17Z</time>
|
||||||
|
<name>Konstanz</name>
|
||||||
|
<sym>Square, Blue</sym>
|
||||||
|
</rtept>
|
||||||
|
</rte>
|
||||||
|
|
||||||
|
<trk>
|
||||||
|
<name>Gadgetbridge import test track</name>
|
||||||
|
<trkseg>
|
||||||
|
<trkpt lat="47.5048828125" lon="9.7393798828125">
|
||||||
|
<ele>-440.2</ele>
|
||||||
|
<time>2025-10-03T15:10:59Z</time>
|
||||||
|
<name>Tp1</name>
|
||||||
|
<desc>a description</desc>
|
||||||
|
<sym>Summit</sym>
|
||||||
|
<hdop>1.4</hdop>
|
||||||
|
<vdop>3.2</vdop>
|
||||||
|
<pdop>1.5</pdop>
|
||||||
|
<extensions>
|
||||||
|
<tpx:TrackPointExtension>
|
||||||
|
<tpx:atemp>28.3</tpx:atemp>
|
||||||
|
<tpx:depth>26.3</tpx:depth>
|
||||||
|
<tpx:hr>123</tpx:hr>
|
||||||
|
<tpx:cad>22</tpx:cad>
|
||||||
|
</tpx:TrackPointExtension>
|
||||||
|
<ax:TrackPointExtension>
|
||||||
|
<ax:speed>23.3</ax:speed>
|
||||||
|
</ax:TrackPointExtension>
|
||||||
|
</extensions>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="47.60899250395596" lon="9.55604980699718">
|
||||||
|
<ele>8849.2</ele>
|
||||||
|
<time>2025-10-03T15:20:59Z</time>
|
||||||
|
<name>Tp2</name>
|
||||||
|
<cmt>a comment</cmt>
|
||||||
|
<sym>Water</sym>
|
||||||
|
<extensions>
|
||||||
|
<ge:TrackPointExtension>
|
||||||
|
<ge:Temperature>34.3</ge:Temperature>
|
||||||
|
<ge:Depth>33.3</ge:Depth>
|
||||||
|
</ge:TrackPointExtension>
|
||||||
|
</extensions>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="47.65594482421875" lon="9.47296142578125" />
|
||||||
|
<trkpt lat="-43" lon="-19" />
|
||||||
|
</trkseg>
|
||||||
|
</trk>
|
||||||
|
</gpx>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?><gpx name="Gadgetbridge's TestGpxImport.gpx" author="Gadgetbridge test author" time="2025-10-03T18:20:45Z">
|
||||||
|
<wpt lat="47.5048828125" lon="9.7393798828125" ele="-470.1" time="2025-10-03T14:10:59Z" name="Bregenz" desc="city in Österreich" sym="Airport" hdop="1.4" vdop="3.2" pdop="1.5" temperature="9.1" depth="6.1"/>
|
||||||
|
<wpt lat="47.65594482421875" lon="9.47296142578125" ele="8850.1" time="2025-10-03T14:20:59Z" name="腓特烈港" desc="another comment" sym="Anchor" temperature="19.2" depth="16.2"/>
|
||||||
|
<wpt lat="47.66693115234375" lon="9.1680908203125" name="קרויצלינגן"/>
|
||||||
|
<trk name="Gadgetbridge import test track">
|
||||||
|
<trkseg>
|
||||||
|
<trkpt lat="47.5048828125" lon="9.7393798828125" ele="-440.2" time="2025-10-03T15:10:59Z" name="Tp1" desc="a description" sym="Summit" hdop="1.4" vdop="3.2" pdop="1.5" temperature="28.3" depth="26.3" hr="123" cad="22" speed="23.3"/>
|
||||||
|
<trkpt lat="47.60899250395596" lon="9.55604980699718" ele="8849.2" time="2025-10-03T15:20:59Z" name="Tp2" desc="a comment" sym="Water" temperature="34.3" depth="33.3"/>
|
||||||
|
<trkpt lat="47.65594482421875" lon="9.47296142578125"/>
|
||||||
|
<trkpt lat="-43" lon="-19"/>
|
||||||
|
</trkseg>
|
||||||
|
</trk>
|
||||||
|
</gpx>
|
||||||
Reference in New Issue
Block a user