GPXExporter: fix generated XML and export depth and ambient temperature

This commit is contained in:
Thomas Kuehne
2025-11-22 19:54:21 +01:00
committed by José Rebelo
parent 8e95699e07
commit 84dab742b1
9 changed files with 336 additions and 27 deletions
@@ -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);
+1
View File
@@ -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>
+3 -2
View File
@@ -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>
+2 -2
View File
@@ -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>