GPX: export track type

This commit is contained in:
Thomas Kuehne
2026-04-30 18:24:01 +00:00
parent 5bf6032e75
commit c926cadbb8
8 changed files with 40 additions and 15 deletions
@@ -1,4 +1,4 @@
/* Copyright (C) 2017-2024 Carsten Pfeiffer, Daniele Gobbetti, José Rebelo
/* Copyright (C) 2017-2026 Carsten Pfeiffer, Daniele Gobbetti, José Rebelo, Thomas Kuehne
This file is part of Gadgetbridge.
@@ -16,13 +16,16 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.export;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
public interface ActivityTrackExporter {
void performExport(ActivityTrack track, File targetFile) throws IOException, GPXTrackEmptyException;
void performExport(ActivityTrack track, File targetFile, @Nullable BaseActivitySummary summary) throws IOException, GPXTrackEmptyException;
class GPXTrackEmptyException extends Exception {
}
@@ -98,7 +98,7 @@ public class AutoGpxExporter {
try (OutputStream outputStream = context.getContentResolver().openOutputStream(targetFile.getUri());
BufferedOutputStream bos = new BufferedOutputStream(outputStream)) {
final GPXExporter exporter = new GPXExporter();
exporter.performExport(activityTrack, bos);
exporter.performExport(activityTrack, bos, summary);
}
LOG.info("Auto-exported GPX to: {}", targetFile.getUri());
@@ -41,7 +41,9 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
@@ -80,14 +82,14 @@ public class GPXExporter implements ActivityTrackExporter {
}
@Override
public void performExport(ActivityTrack track, File targetFile) throws IOException, GPXTrackEmptyException {
public void performExport(ActivityTrack track, File targetFile, @Nullable BaseActivitySummary summary) throws IOException, GPXTrackEmptyException {
try (FileOutputStream outputStream = new FileOutputStream(targetFile);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
performExport(track, bufferedOutputStream);
performExport(track, bufferedOutputStream, summary);
}
}
public void performExport(ActivityTrack track, OutputStream outputStream) throws IOException, GPXTrackEmptyException {
public void performExport(ActivityTrack track, OutputStream outputStream, @Nullable BaseActivitySummary summary) throws IOException, GPXTrackEmptyException {
String encoding = StandardCharsets.UTF_8.name();
XmlSerializer ser = Xml.newSerializer();
@@ -111,7 +113,19 @@ public class GPXExporter implements ActivityTrackExporter {
+ " " + OPENTRACKS_NAMESPACE_URI + " " + OPENTRACKS_XSD);
exportMetadata(ser, track);
exportTrack(ser, track);
// don't localize trackType - it is used by importing applications
final String trackType;
if (summary != null) {
final ActivityKind activityKind = ActivityKind.fromCode(summary.getActivityKind());
trackType = switch (activityKind) {
case NOT_MEASURED, UNKNOWN, ACTIVITY -> null;
default -> activityKind.name();
};
} else {
trackType = null;
}
exportTrack(ser, track, trackType);
ser.endTag(NS_GPX_URI, "gpx");
ser.endDocument();
@@ -144,7 +158,7 @@ public class GPXExporter implements ActivityTrackExporter {
return DateTimeUtils.formatIso8601UTC(date);
}
private void exportTrack(XmlSerializer ser, ActivityTrack track) throws IOException, GPXTrackEmptyException {
private void exportTrack(XmlSerializer ser, ActivityTrack track, @Nullable String trackType) throws IOException, GPXTrackEmptyException {
String uuid = ((this.uuid != null) ? this.uuid : UUID.randomUUID()).toString();
ser.startTag(NS_GPX_URI, "trk");
@@ -154,6 +168,10 @@ public class GPXExporter implements ActivityTrackExporter {
ser.startTag(NS_GPX_URI, "name").text(trackName).endTag(NS_GPX_URI, "name");
}
if (trackType != null && !trackType.isBlank()) {
ser.startTag(NS_GPX_URI, "type").text(trackType).endTag(NS_GPX_URI, "type");
}
ser.startTag(NS_GPX_URI, "extensions");
ser.startTag(NS_GPX_URI, OPENTRACKS_PREFIX + ":trackid").text(uuid).endTag(NS_GPX_URI, OPENTRACKS_PREFIX + ":trackid");
ser.endTag(NS_GPX_URI, "extensions");
@@ -165,7 +165,7 @@ public class OpenTracksController extends Activity {
gpxDir.mkdirs();
final File gpxFile = new File(gpxDir, gpxName + ".gpx");
final GPXExporter gpxExporter = new GPXExporter();
gpxExporter.performExport(activityTrack, gpxFile);
gpxExporter.performExport(activityTrack, gpxFile, null);
LOG.info("Saved GPX received from OpenTracks to {}", gpxFile.getPath());
} catch (IOException | ActivityTrackExporter.GPXTrackEmptyException e) {
LOG.error("Error while writing generated GPX file", e);
@@ -74,7 +74,7 @@ public final class ActivitySummaryUtils {
final File gpxFile = new File(rawCacheDir, gpxFileName);
final GPXExporter gpxExporter = new GPXExporter();
gpxExporter.performExport(activityTrack, gpxFile);
gpxExporter.performExport(activityTrack, gpxFile, summary);
return gpxFile;
}
@@ -46,9 +46,11 @@ import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter.GPXTrackEmptyException;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
@@ -70,7 +72,7 @@ public class GPXExporterTest extends TestBase {
final File tempFile = File.createTempFile("gpx-exporter-test-track", ".gpx");
tempFile.deleteOnExit();
gpxExporter.performExport(track, tempFile);
gpxExporter.performExport(track, tempFile, null);
validateGpxFile(tempFile);
}
@@ -85,7 +87,7 @@ public class GPXExporterTest extends TestBase {
final File tempFile = File.createTempFile("gpx-exporter-test-track", ".gpx");
tempFile.deleteOnExit();
gpxExporter.performExport(track, tempFile);
gpxExporter.performExport(track, tempFile, null);
validateGpxFile(tempFile);
}
@@ -192,7 +194,9 @@ public class GPXExporterTest extends TestBase {
final File tempFile = File.createTempFile("gpx-exporter-test-import-export", ".gpx");
tempFile.deleteOnExit();
gpxExporter.performExport(track, tempFile);
BaseActivitySummary summary = new BaseActivitySummary();
summary.setActivityKind(ActivityKind.TRAIL_RUN.getCode());
gpxExporter.performExport(track, tempFile, summary);
byte[] exported = Files.readAllBytes(tempFile.toPath());
byte[] expected = readBinaryResource("/TestGpxExport.gpx");
@@ -94,7 +94,7 @@ public class HuamiActivityDetailsParserTest extends TestBase {
exporter.setCreator(getClass().getName());
File targetFile = File.createTempFile("gadgetbridge-track", ".gpx");
System.out.println("Writing GPX file: " + targetFile);
exporter.performExport(track, targetFile);
exporter.performExport(track, targetFile, summary);
assertTrue(targetFile.length() > 1024);
}
+1 -1
View File
@@ -1 +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 https://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.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><name>Test Track</name><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>
<?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 https://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.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><name>Test Track</name><type>TRAIL_RUN</type><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>