From 065f42fbef00810c0f69417c7eb0c0f108d7469a Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Wed, 22 Jul 2020 22:23:32 +0200 Subject: [PATCH] GPX import/export: speed via TrackPointExtension/v2. Fixed data type for heartrate/speed (no decimals) allowed. Fixes #335. Fixes #181. GPXTrackWriter: set Trackpointv2 header correctly and export time in correct sequence. --- .../opentracks/content/SearchEngineTest.java | 1 - .../io/file/importer/ExportImportTest.java | 11 ++-- .../io/file/exporter/GpxTrackWriter.java | 36 +++++++----- .../importer/AbstractFileTrackImporter.java | 58 +++++++++++-------- .../file/importer/GpxFileTrackImporter.java | 30 ++++++---- 5 files changed, 81 insertions(+), 55 deletions(-) diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java index 138a52d69..b4034e6de 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java @@ -58,7 +58,6 @@ public class SearchEngineTest { private final Context context = ApplicationProvider.getApplicationContext(); - @Before public void setUp() { providerUtils = new ContentProviderUtils(context); diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java index cf3bb591f..5217678ad 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java @@ -103,8 +103,7 @@ public class ExportImportTest { // given Track track = contentProviderUtils.getTrack(trackId); - TrackFileFormat trackFileFormat = TrackFileFormat.KML_WITH_TRACKDETAIL; - TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}); + TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.newTrackExporter(context, new Track[]{track}); // when // 1. export @@ -138,8 +137,7 @@ public class ExportImportTest { // given Track track = contentProviderUtils.getTrack(trackId); - TrackFileFormat trackFileFormat = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA; - TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}); + TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context, new Track[]{track}); // when // 1. export @@ -201,8 +199,7 @@ public class ExportImportTest { // given Track track = contentProviderUtils.getTrack(trackId); - TrackFileFormat trackFileFormat = TrackFileFormat.GPX; - TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}); + TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context, new Track[]{track}); // when // 1. export @@ -229,7 +226,7 @@ public class ExportImportTest { assertWaypoints(); // 3. trackpoints - assertTrackpoints(false, false, false); + assertTrackpoints(false, true, true); } private void assertWaypoints() { diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GpxTrackWriter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GpxTrackWriter.java index 7d3ca6b9c..b340c2996 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GpxTrackWriter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GpxTrackWriter.java @@ -33,11 +33,11 @@ import de.dennisguse.opentracks.util.StringUtils; * * @author Sandor Dornbush */ -//TODO Can we export SensorData in GPX? public class GpxTrackWriter implements TrackWriter { private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat COORDINATE_FORMAT = NumberFormat.getInstance(Locale.US); + private static final NumberFormat SPEED_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat HEARTRATE_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat CADENCE_FORMAT = NumberFormat.getInstance(Locale.US); @@ -53,10 +53,13 @@ public class GpxTrackWriter implements TrackWriter { COORDINATE_FORMAT.setMaximumIntegerDigits(3); COORDINATE_FORMAT.setGroupingUsed(false); - HEARTRATE_FORMAT.setMaximumFractionDigits(1); + SPEED_FORMAT.setMaximumFractionDigits(2); + SPEED_FORMAT.setGroupingUsed(false); + + HEARTRATE_FORMAT.setMaximumFractionDigits(0); HEARTRATE_FORMAT.setGroupingUsed(false); - CADENCE_FORMAT.setMaximumFractionDigits(1); + CADENCE_FORMAT.setMaximumFractionDigits(0); CADENCE_FORMAT.setGroupingUsed(false); } @@ -90,12 +93,12 @@ public class GpxTrackWriter implements TrackWriter { printWriter.println("xmlns=\"http://www.topografix.com/GPX/1/1\""); printWriter.println("xmlns:topografix=\"http://www.topografix.com/GPX/Private/TopoGrafix/0/1\""); printWriter.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""); - printWriter.println("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1" - + " http://www.topografix.com/GPX/1/1/gpx.xsd" - + " http://www.topografix.com/GPX/Private/TopoGrafix/0/1" - + " http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd\""); - printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemes/TrackPointExtension/v1\">"); + printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemes/TrackPointExtension/v2\""); + printWriter.println("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" + + " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd" + + " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd\">"); printWriter.println(""); + Track track = tracks[0]; printWriter.println("" + StringUtils.formatCData(track.getName()) + ""); printWriter.println("" + StringUtils.formatCData(track.getDescription()) + ""); @@ -180,10 +183,20 @@ public class GpxTrackWriter implements TrackWriter { public void writeTrackPoint(TrackPoint trackPoint) { if (printWriter != null) { printWriter.println(""); - if (trackPoint.hasAltitude()) { printWriter.println("" + ELEVATION_FORMAT.format(trackPoint.getAltitude()) + ""); } + if (trackPoint.hasAltitude()) { + printWriter.println("" + ELEVATION_FORMAT.format(trackPoint.getAltitude()) + ""); + } - if (trackPoint.hasHeartRate() || trackPoint.hasCyclingCadence()) { + printWriter.println(""); + + if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCyclingCadence()) { printWriter.println(""); + + + if (trackPoint.hasSpeed()) { + printWriter.println("" + SPEED_FORMAT.format(trackPoint.getSpeed()) + ""); + } + if (trackPoint.hasHeartRate()) { printWriter.println("" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + ""); } @@ -195,9 +208,6 @@ public class GpxTrackWriter implements TrackWriter { printWriter.println(""); } - printWriter.println( - ""); - printWriter.println("" + trackPoint.getSpeed() + ""); printWriter.println(""); } } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java index 002037283..cad79754e 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java @@ -83,6 +83,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track protected String altitude; protected String time; protected String speed; + protected String heartrate; + protected String cadence; + protected String power; protected String waypointType; protected String photoUrl; protected String uuid; @@ -248,14 +251,12 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track trackData.track.setName(name); } - UUID uuidParsed; try { - uuidParsed = UUID.fromString(uuid); + trackData.track.setUuid(UUID.fromString(uuid)); } catch (IllegalArgumentException | NullPointerException e) { Log.w(TAG, "could not parse Track UUID, generating a new one."); - uuidParsed = UUID.randomUUID(); + trackData.track.setUuid(UUID.randomUUID()); } - trackData.track.setUuid(uuidParsed); if (description != null) { trackData.track.setDescription(description); @@ -416,45 +417,56 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track if (latitude == null || longitude == null) { return null; } - double latitudeValue; - double longitudeValue; + + TrackPoint trackPoint = new TrackPoint(); + try { - latitudeValue = Double.parseDouble(latitude); - longitudeValue = Double.parseDouble(longitude); + trackPoint.setLatitude(Double.parseDouble(latitude)); + trackPoint.setLongitude(Double.parseDouble(longitude)); } catch (NumberFormatException e) { throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e); } - Double altitudeValue = null; - if (altitude != null) { - try { - altitudeValue = Double.parseDouble(altitude); - } catch (NumberFormatException e) { - throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e); - } - } - long timeValue; if (time == null) { - timeValue = trackData.importTime; + trackPoint.setTime(trackData.importTime); } else { try { - timeValue = StringUtils.parseTime(time); + trackPoint.setTime(StringUtils.parseTime(time)); } catch (Exception e) { throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e); } } - TrackPoint trackPoint = new TrackPoint(latitudeValue, longitudeValue, altitudeValue, timeValue); + if (altitude != null) { + try { + trackPoint.setAltitude(Double.parseDouble(altitude)); + } catch (NumberFormatException e) { + throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e); + } + } - float speedValue; if (speed != null) { try { - speedValue = Float.parseFloat(speed); - trackPoint.setSpeed(speedValue); + trackPoint.setSpeed(Float.parseFloat(speed)); } catch (Exception e) { throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e); } } + if (heartrate != null) { + try { + trackPoint.setHeartRate_bpm(Float.parseFloat(heartrate)); + } catch (Exception e) { + throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), e); + } + } + + if (cadence != null) { + try { + trackPoint.setCyclingCadence_rpm(Float.parseFloat(cadence)); + } catch (Exception e) { + throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e); + } + } return trackPoint; } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxFileTrackImporter.java index 7374d677e..44b678682 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxFileTrackImporter.java @@ -28,6 +28,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils; /** * Imports a GPX file. + * Uses https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd * * @author Jimmy Shih */ @@ -39,8 +40,6 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter { private static final String TAG_GPX = "gpx"; private static final String TAG_NAME = "name"; private static final String TAG_TIME = "time"; - private static final String TAG_SPEED = "speed"; - private static final String TAG_HEARTRATE = "gpx:hr"; private static final String TAG_TRACK = "trk"; private static final String TAG_TRACK_POINT = "trkpt"; private static final String TAG_TRACK_SEGMENT = "trkseg"; @@ -50,6 +49,10 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter { private static final String ATTRIBUTE_LAT = "lat"; private static final String ATTRIBUTE_LON = "lon"; + private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed"; + private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr"; + private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad"; + /** * Constructor. * @@ -117,15 +120,6 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter { time = content.trim(); } break; - case TAG_SPEED: - if (content != null) { - speed = content.trim(); - } - case TAG_HEARTRATE: - if (content != null) { - heartrate = content.trim(); - } - break; case TAG_ELEVATION: if (content != null) { altitude = content.trim(); @@ -136,6 +130,20 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter { waypointType = content.trim(); } break; + case TAG_EXTENSION_SPEED: + if (content != null) { + speed = content.trim(); + } + case TAG_EXTENSION_HEARTRATE: + if (content != null) { + heartrate = content.trim(); + } + break; + case TAG_EXTENSION_CADENCE: + if (content != null) { + cadence = content.trim(); + } + break; } // Reset element content