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 | 28 +++++---- 5 files changed, 80 insertions(+), 54 deletions(-) diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java index 138a52d696..b4034e6dea 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 cf3bb591f6..5217678add 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 void kml_with_trackdetail() { // 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 void kml_with_trackdetail_and_sensordata() { // 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 void gpx() { // 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 void gpx() { 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 7d3ca6b9c2..b340c29964 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 @@ * * @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 void writeHeader(Track[] tracks) { 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 void writeCloseSegment() { 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()) + ""); + } + + printWriter.println(""); - if (trackPoint.hasHeartRate() || trackPoint.hasCyclingCadence()) { + 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 void writeTrackPoint(TrackPoint trackPoint) { 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 002037283c..cad79754ef 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 @@ protected void onTrackEnd() { 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 @@ private TrackPoint createTrackPoint() throws SAXException { 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 7374d677e4..44b678682d 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 @@ /** * 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,23 +120,28 @@ public void endElement(String uri, String localName, String tag) throws SAXExcep time = content.trim(); } break; - case TAG_SPEED: + case TAG_ELEVATION: if (content != null) { - speed = content.trim(); + altitude = content.trim(); } - case TAG_HEARTRATE: + break; + case TAG_COMMENT: if (content != null) { - heartrate = content.trim(); + waypointType = content.trim(); } break; - case TAG_ELEVATION: + case TAG_EXTENSION_SPEED: if (content != null) { - altitude = content.trim(); + speed = content.trim(); + } + case TAG_EXTENSION_HEARTRATE: + if (content != null) { + heartrate = content.trim(); } break; - case TAG_COMMENT: + case TAG_EXTENSION_CADENCE: if (content != null) { - waypointType = content.trim(); + cadence = content.trim(); } break; }