mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
GPXExporter: fix generated XML and export depth and ambient temperature
This commit is contained in:
committed by
José Rebelo
parent
8e95699e07
commit
84dab742b1
@@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2017-2024 Andreas Shimokawa, AndrewH, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Dikay900, José Rebelo, Nick Spacek, Petr Vaněk
|
||||
/* Copyright (C) 2017-2025 Andreas Shimokawa, AndrewH, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, Dikay900, José Rebelo, Nick Spacek, Petr Vaněk, Thomas Kuehne
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@@ -21,16 +21,20 @@ import android.util.Xml;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@@ -45,15 +49,33 @@ public class GPXExporter implements ActivityTrackExporter {
|
||||
private static final String NS_GPX_URI = "http://www.topografix.com/GPX/1/1";
|
||||
private static final String NS_GPX_PREFIX = "";
|
||||
private static final String NS_TRACKPOINT_EXTENSION = "gpxtpx";
|
||||
private static final String NS_TRACKPOINT_EXTENSION_URI = "https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd";
|
||||
private static final String NS_TRACKPOINT_EXTENSION_URI = "http://www.garmin.com/xmlschemas/TrackPointExtension/v2";
|
||||
private static final String NS_XSI_URI = "http://www.w3.org/2001/XMLSchema-instance";
|
||||
private static final String TRACKPOINT_EXTENSION_XSD = "http://www.garmin.com/xmlschemas/TrackPointExtensionv2.xsd";
|
||||
private static final String TOPOGRAFIX_NAMESPACE_XSD = "http://www.topografix.com/GPX/1/1/gpx.xsd";
|
||||
private static final String OPENTRACKS_PREFIX = "opentracks";
|
||||
private static final String OPENTRACKS_NAMESPACE_URI = "http://opentracksapp.com/xmlschemas/v1";
|
||||
private static final String OPENTRACKS_XSD = "https://raw.githubusercontent.com/OpenTracksApp/OpenTracks/main/doc/opentracks-schema-1.0.xsd";
|
||||
|
||||
private String creator;
|
||||
private Date date;
|
||||
private boolean includeHeartRate = true;
|
||||
private boolean includeHeartRateOfNearestSample = true;
|
||||
private UUID uuid;
|
||||
|
||||
private final DecimalFormat doubleFormat;
|
||||
private final DecimalFormat locationFormat;
|
||||
|
||||
public GPXExporter(){
|
||||
doubleFormat = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ROOT));
|
||||
doubleFormat.setMaximumFractionDigits(15);
|
||||
doubleFormat.setRoundingMode(RoundingMode.HALF_UP);
|
||||
|
||||
locationFormat = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ROOT));
|
||||
locationFormat.setMinimumFractionDigits(GPSCoordinate.GPS_DECIMAL_DEGREES_SCALE);
|
||||
locationFormat.setMaximumFractionDigits(GPSCoordinate.GPS_DECIMAL_DEGREES_SCALE);
|
||||
locationFormat.setRoundingMode(RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performExport(ActivityTrack track, File targetFile) throws IOException, GPXTrackEmptyException {
|
||||
@@ -74,7 +96,9 @@ public class GPXExporter implements ActivityTrackExporter {
|
||||
} else {
|
||||
ser.attribute(null, "creator", GBApplication.app().getNameAndVersion());
|
||||
}
|
||||
ser.attribute(NS_XSI_URI, "schemaLocation",NS_GPX_URI + " " + TOPOGRAFIX_NAMESPACE_XSD);
|
||||
ser.attribute(NS_XSI_URI, "schemaLocation",NS_GPX_URI + " " + TOPOGRAFIX_NAMESPACE_XSD
|
||||
+ " " + NS_TRACKPOINT_EXTENSION_URI + " " + TRACKPOINT_EXTENSION_XSD
|
||||
+ " " + OPENTRACKS_NAMESPACE_URI + " " + OPENTRACKS_XSD);
|
||||
|
||||
exportMetadata(ser, track);
|
||||
exportTrack(ser, track);
|
||||
@@ -98,17 +122,21 @@ public class GPXExporter implements ActivityTrackExporter {
|
||||
ser.endTag(NS_GPX_URI, "author");
|
||||
}
|
||||
|
||||
ser.startTag(NS_GPX_URI, "time").text(formatTime(new Date())).endTag(NS_GPX_URI, "time");
|
||||
Date time = date;
|
||||
if (time == null){
|
||||
time = new Date();
|
||||
}
|
||||
ser.startTag(NS_GPX_URI, "time").text(formatTime(time)).endTag(NS_GPX_URI, "time");
|
||||
|
||||
ser.endTag(NS_GPX_URI, "metadata");
|
||||
}
|
||||
|
||||
private String formatTime(Date date) {
|
||||
return DateTimeUtils.formatIso8601(date);
|
||||
return DateTimeUtils.formatIso8601UTC(date);
|
||||
}
|
||||
|
||||
private void exportTrack(XmlSerializer ser, ActivityTrack track) throws IOException, GPXTrackEmptyException {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
String uuid = ((this.uuid != null) ? this.uuid : UUID.randomUUID()).toString();
|
||||
ser.startTag(NS_GPX_URI, "trk");
|
||||
ser.startTag(NS_GPX_URI, "extensions");
|
||||
ser.startTag(NS_GPX_URI, OPENTRACKS_PREFIX + ":trackid").text(uuid).endTag(NS_GPX_URI, OPENTRACKS_PREFIX + ":trackid");
|
||||
@@ -143,12 +171,15 @@ public class GPXExporter implements ActivityTrackExporter {
|
||||
}
|
||||
ser.startTag(NS_GPX_URI, "trkpt");
|
||||
// lon and lat attributes do not have an explicit namespace
|
||||
ser.attribute(null, "lon", formatDouble(location.getLongitude()));
|
||||
ser.attribute(null, "lat", formatDouble(location.getLatitude()));
|
||||
if (location.getAltitude() != GPSCoordinate.UNKNOWN_ALTITUDE) {
|
||||
ser.attribute(null, "lon", formatLocation(location.getLongitude()));
|
||||
ser.attribute(null, "lat", formatLocation(location.getLatitude()));
|
||||
if (location.hasAltitude()) {
|
||||
ser.startTag(NS_GPX_URI, "ele").text(formatDouble(location.getAltitude())).endTag(NS_GPX_URI, "ele");
|
||||
}
|
||||
ser.startTag(NS_GPX_URI, "time").text(DateTimeUtils.formatIso8601UTC(point.getTime())).endTag(NS_GPX_URI, "time");
|
||||
Date time = point.getTime();
|
||||
if (time != null) {
|
||||
ser.startTag(NS_GPX_URI, "time").text(formatTime(time)).endTag(NS_GPX_URI, "time");
|
||||
}
|
||||
String description = point.getDescription();
|
||||
if (description != null) {
|
||||
ser.startTag(NS_GPX_URI, "desc").text(description).endTag(NS_GPX_URI, "desc");
|
||||
@@ -176,6 +207,8 @@ public class GPXExporter implements ActivityTrackExporter {
|
||||
return;
|
||||
}
|
||||
|
||||
double temperature = point.getTemperature();
|
||||
double depth = point.getDepth();
|
||||
float speed = point.getSpeed();
|
||||
int cadence = point.getCadence();
|
||||
int hr = point.getHeartRate();
|
||||
@@ -189,8 +222,12 @@ public class GPXExporter implements ActivityTrackExporter {
|
||||
}
|
||||
|
||||
boolean exportHr = HeartRateUtils.getInstance().isValidHeartRateValue(hr) && includeHeartRate;
|
||||
boolean exportCadence = cadence >= 0;
|
||||
boolean exportSpeed = speed >= 0.0f;
|
||||
boolean exportTemperature = temperature > -273.0;
|
||||
boolean exportDepth = !Double.isNaN(depth) && depth != -1.0;
|
||||
|
||||
if (!exportHr && speed < 0 && cadence < 0) {
|
||||
if (!(exportHr || exportCadence || exportSpeed || exportTemperature || exportDepth)) {
|
||||
// No valid data to export in extensions
|
||||
return;
|
||||
}
|
||||
@@ -198,13 +235,19 @@ public class GPXExporter implements ActivityTrackExporter {
|
||||
ser.startTag(NS_GPX_URI, "extensions");
|
||||
ser.setPrefix(NS_TRACKPOINT_EXTENSION, NS_TRACKPOINT_EXTENSION_URI);
|
||||
ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "TrackPointExtension");
|
||||
if (exportTemperature) {
|
||||
ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "atemp").text(formatDouble(temperature)).endTag(NS_TRACKPOINT_EXTENSION_URI, "atemp");
|
||||
}
|
||||
if (exportDepth) {
|
||||
ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "depth").text(formatDouble(depth)).endTag(NS_TRACKPOINT_EXTENSION_URI, "depth");
|
||||
}
|
||||
if (exportHr) {
|
||||
ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "hr").text(String.valueOf(hr)).endTag(NS_TRACKPOINT_EXTENSION_URI, "hr");
|
||||
ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "hr").text(formatLong(hr)).endTag(NS_TRACKPOINT_EXTENSION_URI, "hr");
|
||||
}
|
||||
if (cadence >= 0) {
|
||||
ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "cad").text(String.valueOf(cadence)).endTag(NS_TRACKPOINT_EXTENSION_URI, "cad");
|
||||
if (exportCadence) {
|
||||
ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "cad").text(formatLong(cadence)).endTag(NS_TRACKPOINT_EXTENSION_URI, "cad");
|
||||
}
|
||||
if (speed >= 0) {
|
||||
if (exportSpeed) {
|
||||
ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "speed").text(formatDouble(speed)).endTag(NS_TRACKPOINT_EXTENSION_URI, "speed");
|
||||
}
|
||||
ser.endTag(NS_TRACKPOINT_EXTENSION_URI, "TrackPointExtension");
|
||||
@@ -212,6 +255,9 @@ public class GPXExporter implements ActivityTrackExporter {
|
||||
}
|
||||
|
||||
private @Nullable ActivityPoint findClosestSensibleActivityPoint(Date time, Iterable<ActivityPoint> trackPoints) {
|
||||
if (time == null) {
|
||||
return null;
|
||||
}
|
||||
ActivityPoint closestPointItem = null;
|
||||
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
|
||||
|
||||
@@ -220,6 +266,9 @@ public class GPXExporter implements ActivityTrackExporter {
|
||||
int hrItem = pointItem.getHeartRate();
|
||||
if (heartRateUtilsInstance.isValidHeartRateValue(hrItem)) {
|
||||
Date timeItem = pointItem.getTime();
|
||||
if (timeItem == null) {
|
||||
continue;
|
||||
}
|
||||
if (timeItem.after(time) || timeItem.equals(time)) {
|
||||
break; // we assume that the given trackPoints are sorted in time ascending order (oldest first)
|
||||
}
|
||||
@@ -233,8 +282,16 @@ public class GPXExporter implements ActivityTrackExporter {
|
||||
return closestPointItem;
|
||||
}
|
||||
|
||||
private String formatLocation(double value) {
|
||||
return locationFormat.format(value);
|
||||
}
|
||||
|
||||
private String formatDouble(double value) {
|
||||
return new BigDecimal(value).setScale(GPSCoordinate.GPS_DECIMAL_DEGREES_SCALE, RoundingMode.HALF_UP).toPlainString();
|
||||
return doubleFormat.format(value);
|
||||
}
|
||||
|
||||
private String formatLong(long value) {
|
||||
return NumberFormat.getNumberInstance(Locale.ROOT).format(value);
|
||||
}
|
||||
|
||||
public String getCreator() {
|
||||
@@ -245,6 +302,10 @@ public class GPXExporter implements ActivityTrackExporter {
|
||||
this.creator = creator;
|
||||
}
|
||||
|
||||
public void setDate(Date date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public void setIncludeHeartRate(boolean includeHeartRate) {
|
||||
this.includeHeartRate = includeHeartRate;
|
||||
}
|
||||
@@ -252,4 +313,9 @@ public class GPXExporter implements ActivityTrackExporter {
|
||||
public boolean isIncludeHeartRate() {
|
||||
return includeHeartRate;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void setUuid(@Nullable UUID id) {
|
||||
uuid = id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,10 @@ public class GPSCoordinate implements Parcelable {
|
||||
return altitude;
|
||||
}
|
||||
|
||||
public boolean hasAltitude() {
|
||||
return altitude > UNKNOWN_ALTITUDE;
|
||||
}
|
||||
|
||||
public void setHdop(double hdop) {
|
||||
this.hdop = hdop;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,26 @@
|
||||
/* Copyright (C) 2019-2025 Nick Spacek, 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.export;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.GarminSupportTest.readBinaryResource;
|
||||
|
||||
import com.google.gson.internal.bind.util.ISO8601Utils;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
@@ -10,11 +29,14 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
@@ -30,7 +52,11 @@ import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter.GPXTrac
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.test.GBTestApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.GpxParseException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.GpxParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.gpx.model.GpxFile;
|
||||
|
||||
public class GPXExporterTest extends TestBase {
|
||||
@Test
|
||||
@@ -39,7 +65,7 @@ public class GPXExporterTest extends TestBase {
|
||||
|
||||
final GPXExporter gpxExporter = new GPXExporter();
|
||||
gpxExporter.setCreator("Gadgetbridge Test");
|
||||
final ActivityTrack track = createTestTrack(points);
|
||||
final ActivityTrack track = createTestTrack(points, new Date());
|
||||
|
||||
final File tempFile = File.createTempFile("gpx-exporter-test-track", ".gpx");
|
||||
tempFile.deleteOnExit();
|
||||
@@ -54,7 +80,7 @@ public class GPXExporterTest extends TestBase {
|
||||
|
||||
final GPXExporter gpxExporter = new GPXExporter();
|
||||
gpxExporter.setCreator("Gadgetbridge Test");
|
||||
final ActivityTrack track = createTestTrack(points);
|
||||
final ActivityTrack track = createTestTrack(points, new Date());
|
||||
|
||||
final File tempFile = File.createTempFile("gpx-exporter-test-track", ".gpx");
|
||||
tempFile.deleteOnExit();
|
||||
@@ -63,7 +89,7 @@ public class GPXExporterTest extends TestBase {
|
||||
validateGpxFile(tempFile);
|
||||
}
|
||||
|
||||
private ActivityTrack createTestTrack(List<ActivityPoint> points) {
|
||||
private ActivityTrack createTestTrack(List<ActivityPoint> points, Date time) {
|
||||
final User user = new User();
|
||||
user.setName("Test User");
|
||||
|
||||
@@ -72,7 +98,7 @@ public class GPXExporterTest extends TestBase {
|
||||
|
||||
final ActivityTrack track = new ActivityTrack();
|
||||
track.setName("Test Track");
|
||||
track.setBaseTime(new Date());
|
||||
track.setBaseTime(time);
|
||||
track.setUser(user);
|
||||
track.setDevice(device);
|
||||
|
||||
@@ -117,8 +143,59 @@ public class GPXExporterTest extends TestBase {
|
||||
private void validateGpxFile(File tempFile) throws SAXException, IOException {
|
||||
final Source xmlFile = new StreamSource(tempFile);
|
||||
final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
||||
final Schema schema = schemaFactory.newSchema(new StreamSource(getClass().getResourceAsStream("/gpx.xsd")));
|
||||
final StreamSource[] sources = {
|
||||
new StreamSource(getClass().getResourceAsStream("/gpx.xsd")),
|
||||
new StreamSource(getClass().getResourceAsStream("/opentracks-schema-1.0.xsd")),
|
||||
new StreamSource(getClass().getResourceAsStream("/TrackPointExtensionv2.xsd"))
|
||||
};
|
||||
final Schema schema = schemaFactory.newSchema(sources);
|
||||
final Validator validator = schema.newValidator();
|
||||
validator.validate(xmlFile);
|
||||
}
|
||||
|
||||
/// GPX (import -> export) with different locales to ensure parsing and formating work correctly
|
||||
@Test
|
||||
public void TestImportExport() throws Exception {
|
||||
try {
|
||||
GBTestApplication.setLanguage("bn");
|
||||
Assert.assertEquals("-১২,৩৫৬.৬৫৪৩২১", String.format("%,f", -12356.654321));
|
||||
ImportExport();
|
||||
GBTestApplication.setLanguage("dz");
|
||||
Assert.assertEquals("-༡༢,༣༥༦.༦༥༤༣༢༡", String.format("%,f", -12356.654321));
|
||||
ImportExport();
|
||||
GBTestApplication.setLanguage("my");
|
||||
Assert.assertEquals("-၁၂,၃၅၆.၆၅၄၃၂၁", String.format("%,f", -12356.654321));
|
||||
ImportExport();
|
||||
GBTestApplication.setLanguage("sa");
|
||||
Assert.assertEquals("-१२,३५६.६५४३२१", String.format("%,f", -12356.654321));
|
||||
ImportExport();
|
||||
}finally {
|
||||
GBTestApplication.setLanguage("en");
|
||||
Assert.assertEquals("-12,356.654321", String.format("%,f", -12356.654321));
|
||||
}
|
||||
}
|
||||
|
||||
public void ImportExport() throws IOException, GpxParseException, GPXTrackEmptyException {
|
||||
final GpxFile imported;
|
||||
try (final InputStream gpx = getClass().getResourceAsStream("/TestGpxImport.gpx")) {
|
||||
imported = new GpxParser(gpx).getGpxFile();
|
||||
}
|
||||
|
||||
final List<ActivityPoint> points = imported.getActivityPoints();
|
||||
|
||||
final GPXExporter gpxExporter = new GPXExporter();
|
||||
gpxExporter.setCreator("Gadgetbridge Test");
|
||||
gpxExporter.setDate(Date.from(Instant.parse("2025-10-17T21:00:00-00:00")));
|
||||
gpxExporter.setUuid(UUID.fromString("c5185301-d578-4e52-bb9f-c2a7afa044e2"));
|
||||
final ActivityTrack track = createTestTrack(points, imported.getTime());
|
||||
|
||||
final File tempFile = File.createTempFile("gpx-exporter-test-import-export", ".gpx");
|
||||
tempFile.deleteOnExit();
|
||||
|
||||
gpxExporter.performExport(track, tempFile);
|
||||
|
||||
byte[] exported = Files.readAllBytes(tempFile.toPath());
|
||||
byte[] expected = readBinaryResource("/TestGpxExport.gpx");
|
||||
Assert.assertArrayEquals(expected, exported);
|
||||
}
|
||||
}
|
||||
@@ -163,6 +163,9 @@ public class GPXParserTest extends TestBase {
|
||||
Assert.assertEquals(28.3f, trkpt0.getTemperature(), 0.0001f);
|
||||
Assert.assertEquals(26.3f, trkpt0.getDepth(), 0.0001f);
|
||||
|
||||
GpxTrackPoint trkpt1 = trkseg0.getTrackPoints().get(1);
|
||||
Assert.assertEquals(Date.from(Instant.parse("2025-10-03T21:06:07+05:45")), trkpt1.getTime());
|
||||
|
||||
byte[] raw = readBinaryResource("/TestGpxImport.gpx");
|
||||
GpxFile file2 = GpxParser.parseGpx(raw);
|
||||
Assert.assertNotNull(file2);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><gpx version="1.1" creator="Gadgetbridge Test" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v2 http://www.garmin.com/xmlschemas/TrackPointExtensionv2.xsd http://opentracksapp.com/xmlschemas/v1 https://raw.githubusercontent.com/OpenTracksApp/OpenTracks/main/doc/opentracks-schema-1.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v2" xmlns="http://www.topografix.com/GPX/1/1" xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1"><metadata><name>Test Track</name><author><name>Test User</name></author><time>2025-10-17T21:00:00Z</time></metadata><trk><extensions><opentracks:trackid>c5185301-d578-4e52-bb9f-c2a7afa044e2</opentracks:trackid></extensions><trkseg><trkpt lon="9.739380" lat="47.504883"><ele>-440.2</ele><time>2025-10-03T15:10:59Z</time><desc>a description</desc><hdop>1.4</hdop><vdop>3.2</vdop><pdop>1.5</pdop><extensions><gpxtpx:TrackPointExtension><gpxtpx:atemp>28.299999237060547</gpxtpx:atemp><gpxtpx:depth>26.299999237060547</gpxtpx:depth><gpxtpx:hr>123</gpxtpx:hr><gpxtpx:cad>22</gpxtpx:cad><gpxtpx:speed>23.299999237060547</gpxtpx:speed></gpxtpx:TrackPointExtension></extensions></trkpt><trkpt lon="9.556050" lat="47.608993"><ele>8849.2</ele><time>2025-10-03T15:21:07Z</time><desc>a comment</desc><extensions><gpxtpx:TrackPointExtension><gpxtpx:atemp>34.29999923706055</gpxtpx:atemp><gpxtpx:depth>10345</gpxtpx:depth></gpxtpx:TrackPointExtension></extensions></trkpt><trkpt lon="9.472961" lat="47.655945"><time>2025-10-03T15:30:00Z</time><extensions><gpxtpx:TrackPointExtension><gpxtpx:atemp>40.400001525878906</gpxtpx:atemp><gpxtpx:depth>41.400001525878906</gpxtpx:depth><gpxtpx:hr>42</gpxtpx:hr><gpxtpx:cad>43</gpxtpx:cad><gpxtpx:speed>44.400001525878906</gpxtpx:speed></gpxtpx:TrackPointExtension></extensions></trkpt><trkpt lon="-19.000000" lat="-43.000000" /></trkseg></trk></gpx>
|
||||
@@ -96,18 +96,19 @@
|
||||
</trkpt>
|
||||
<trkpt lat="47.60899250395596" lon="9.55604980699718">
|
||||
<ele>8849.2</ele>
|
||||
<time>2025-10-03T15:20:59Z</time>
|
||||
<time>2025-10-03T21:06:07+05:45</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:Depth>10345</ge:Depth>
|
||||
</ge:TrackPointExtension>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
<trkpt lat="47.65594482421875" lon="9.47296142578125">
|
||||
<time>2025-10-03T03:30:00-12:00</time>
|
||||
<extensions>
|
||||
<t2:TrackPointExtension>
|
||||
<t2:atemp>40.4</t2:atemp>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<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" temperature="40.4" depth="41.4" hr="42" cad="43" speed="44.4"/>
|
||||
<trkpt lat="47.60899250395596" lon="9.55604980699718" ele="8849.2" time="2025-10-03T21:06:07+05:45" name="Tp2" desc="a comment" sym="Water" temperature="34.3" depth="10345"/>
|
||||
<trkpt lat="47.65594482421875" lon="9.47296142578125" time="2025-10-03T03:30:00-12:00" temperature="40.4" depth="41.4" hr="42" cad="43" speed="44.4"/>
|
||||
<trkpt lat="-43" lon="-19"/>
|
||||
</trkseg>
|
||||
</trk>
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0"?>
|
||||
<xsd:schema targetNamespace="http://www.garmin.com/xmlschemas/TrackPointExtension/v2"
|
||||
elementFormDefault="qualified"
|
||||
xmlns="http://www.garmin.com/xmlschemas/TrackPointExtension/v2"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xsd:annotation><xsd:documentation>
|
||||
This schema defines Garmin extensions to be used with the GPX 1.1 schema.
|
||||
The root element defined by this schema is intended to be used as a child
|
||||
element of the "extensions" elements in the trkpt element in the GPX 1.1 schema.
|
||||
The GPX 1.1 schema is available at http://www.topografix.com/GPX/1/1/gpx.xsd.
|
||||
This is a replacement for TrackPointExtension in
|
||||
http://www.garmin.com/xmlschemas/GpxExtensions/v3
|
||||
</xsd:documentation></xsd:annotation>
|
||||
|
||||
<xsd:element name="TrackPointExtension" type="TrackPointExtension_t" />
|
||||
|
||||
<xsd:complexType name="TrackPointExtension_t">
|
||||
<xsd:annotation><xsd:documentation>
|
||||
This type contains data fields that cannot
|
||||
be represented in track points in GPX 1.1 instances.
|
||||
</xsd:documentation></xsd:annotation>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="atemp" type="DegreesCelsius_t" minOccurs="0" />
|
||||
<xsd:element name="wtemp" type="DegreesCelsius_t" minOccurs="0" />
|
||||
<xsd:element name="depth" type="Meters_t" minOccurs="0" />
|
||||
<xsd:element name="hr" type="BeatsPerMinute_t" minOccurs="0"/>
|
||||
<xsd:element name="cad" type="RevolutionsPerMinute_t" minOccurs="0"/>
|
||||
<xsd:element name="speed" type="MetersPerSecond_t" minOccurs="0"/>
|
||||
<xsd:element name="course" type="DegreesTrue_t" minOccurs="0"/>
|
||||
<xsd:element name="bearing" type="DegreesTrue_t" minOccurs="0"/>
|
||||
<xsd:element name="Extensions" type="Extensions_t" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:simpleType name="DegreesCelsius_t">
|
||||
<xsd:annotation><xsd:documentation>
|
||||
This type contains a temperature value measured in degrees Celsius.
|
||||
</xsd:documentation></xsd:annotation>
|
||||
<xsd:restriction base="xsd:double"/>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="Meters_t">
|
||||
<xsd:annotation><xsd:documentation>
|
||||
This type contains a distance value measured in meters.
|
||||
</xsd:documentation></xsd:annotation>
|
||||
<xsd:restriction base="xsd:double"/>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="BeatsPerMinute_t">
|
||||
<xsd:annotation><xsd:documentation>
|
||||
This type contains a heart rate measured in beats per minute.
|
||||
</xsd:documentation></xsd:annotation>
|
||||
<xsd:restriction base="xsd:unsignedByte">
|
||||
<xsd:minInclusive value="1"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="RevolutionsPerMinute_t">
|
||||
<xsd:annotation><xsd:documentation>
|
||||
This type contains a cadence measured in revolutions per minute.
|
||||
</xsd:documentation></xsd:annotation>
|
||||
<xsd:restriction base="xsd:unsignedByte">
|
||||
<xsd:maxInclusive value="254"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="MetersPerSecond_t">
|
||||
<xsd:annotation><xsd:documentation>
|
||||
This type contains a speed measured in meters per second.
|
||||
</xsd:documentation></xsd:annotation>
|
||||
<xsd:restriction base="xsd:double"/>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="DegreesTrue_t">
|
||||
<xsd:annotation><xsd:documentation>
|
||||
This type contains an angle measured in degrees in a clockwise direction from the true north line.
|
||||
</xsd:documentation></xsd:annotation>
|
||||
<xsd:restriction base="xsd:decimal">
|
||||
<xsd:minInclusive value="0"/>
|
||||
<xsd:maxInclusive value="360"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:complexType name="Extensions_t">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>This type provides the ability to extend any data type that includes it.</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence>
|
||||
<xsd:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
</xsd:schema>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://opentracksapp.com/xmlschemas/v1">
|
||||
|
||||
<xsd:element name="trackuuid"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
A unique track identifier stored as UUID.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="loss"
|
||||
type="xsd:float">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
The lost elevation in meters from the previous to the current TrackPoint.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="gain"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
The gained elevation in meters from the previous to the current TrackPoint.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="accuracy_horizontal"
|
||||
type="xsd:float">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
Horizontal accuracy in meters of the current TrackPoint; 68% chance that the actual
|
||||
location is
|
||||
within this radius around the measured location.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="accuracy_vertical"
|
||||
type="xsd:float">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
Vertical accuracy in meters of the current TrackPoint; 68% chance that the actual
|
||||
location is
|
||||
within this radius around the measured location.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="typeTranslated"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
User-defined translation of GPX's <![CDATA[<type>]]>.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
Reference in New Issue
Block a user