From 0d67b48251e56c5b795e7ff65839172698598727 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Tue, 3 Oct 2023 16:41:31 +0200 Subject: [PATCH] ActivityType non-localized: import and export for GPX, KML, and KMZ. Fixes #1608. --- doc/opentracks-schema-1.0.xsd | 9 +++++ .../io/file/importer/ExportImportTest.java | 6 +-- src/androidTest/res/raw/gpx_timezone.gpx | 2 +- .../io/file/exporter/GPXTrackExporter.java | 8 +++- .../io/file/exporter/KMLTrackExporter.java | 27 +++++++++---- .../io/file/importer/GpxTrackImporter.java | 24 ++++++------ .../io/file/importer/KmlTrackImporter.java | 39 +++++++++---------- 7 files changed, 69 insertions(+), 46 deletions(-) diff --git a/doc/opentracks-schema-1.0.xsd b/doc/opentracks-schema-1.0.xsd index 9b9ed8e36..89b187fb1 100644 --- a/doc/opentracks-schema-1.0.xsd +++ b/doc/opentracks-schema-1.0.xsd @@ -51,4 +51,13 @@ + + + + User-defined translation of GPX's ]]>. + Only used in GPX. + + + \ No newline at end of file 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 59464b9af..7065be17d 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 @@ -309,10 +309,10 @@ public void kmz_with_trackdetail_and_sensordata() throws TimeoutException, IOExc // 1. track Track importedTrack = contentProviderUtils.getTrack(importTrackId); assertNotNull(importedTrack); + assertEquals(track.getActivityType(), importedTrack.getActivityType()); assertEquals(track.getActivityTypeLocalized(), importedTrack.getActivityTypeLocalized()); assertEquals(track.getDescription(), importedTrack.getDescription()); assertEquals(track.getName(), importedTrack.getName()); - assertEquals(track.getActivityType(), importedTrack.getActivityType()); // 2. trackpoints TrackPointAssert a = new TrackPointAssert(); @@ -380,13 +380,11 @@ public void gpx() throws TimeoutException, IOException { // 1. track Track importedTrack = contentProviderUtils.getTrack(importTrackId); assertNotNull(importedTrack); + assertEquals(track.getActivityType(), importedTrack.getActivityType()); assertEquals(track.getActivityTypeLocalized(), importedTrack.getActivityTypeLocalized()); assertEquals(track.getDescription(), importedTrack.getDescription()); assertEquals(track.getName(), importedTrack.getName()); - //TODO exporting and importing a track icon is not yet supported by GpxTrackWriter. - //assertEquals(track.getIcon(), importedTrack.getIcon()); - // 2. trackpoints // The GPX exporter does not support exporting TrackPoints without lat/lng. // Therefore, the track segmentation is changes. diff --git a/src/androidTest/res/raw/gpx_timezone.gpx b/src/androidTest/res/raw/gpx_timezone.gpx index 23d754843..9d0f4cec3 100644 --- a/src/androidTest/res/raw/gpx_timezone.gpx +++ b/src/androidTest/res/raw/gpx_timezone.gpx @@ -21,7 +21,7 @@ xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/ - + c0c0c0 7002101e-4198-4613-8c24-544e01ca3981 diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java index 7a7362a05..535d14486 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java @@ -261,7 +261,7 @@ public void writeMarker(ZoneOffset zoneOffset, Marker marker) { printWriter.println(""); printWriter.println("" + StringUtils.formatCData(marker.getName()) + ""); printWriter.println("" + StringUtils.formatCData(marker.getDescription()) + ""); - printWriter.println("" + StringUtils.formatCData(marker.getCategory()) + ""); + printWriter.println("" + StringUtils.formatCData(marker.getCategory()) + ""); //TODO This is localized; may be better to export in English only. See #1608 printWriter.println(""); } @@ -269,12 +269,16 @@ public void writeBeginTrack(Track track) { printWriter.println(""); printWriter.println("" + StringUtils.formatCData(track.getName()) + ""); printWriter.println("" + StringUtils.formatCData(track.getDescription()) + ""); - printWriter.println("" + StringUtils.formatCData(track.getActivityTypeLocalized()) + ""); + printWriter.println("" + StringUtils.formatCData(track.getActivityType().getId()) + ""); printWriter.println(""); printWriter.println("c0c0c0"); printWriter.println("" + track.getUuid() + ""); + if (track.getActivityTypeLocalized() != null || !track.getActivityTypeLocalized().isBlank()) { + printWriter.println("" + StringUtils.formatCData(track.getActivityTypeLocalized()) + ""); + } + TrackStatistics trackStatistics = track.getTrackStatistics(); printWriter.println(""); printWriter.println("" + trackStatistics.getTotalDistance().toM() + ""); diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java index b9264ee62..b76dd3305 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java @@ -37,6 +37,7 @@ import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.TrackPointIterator; +import de.dennisguse.opentracks.data.models.ActivityType; import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.TrackPoint; @@ -59,7 +60,8 @@ public class KMLTrackExporter implements TrackExporter { private static final String TRACK_STYLE = "track"; private static final String SCHEMA_ID = "schema"; - public static final String EXTENDED_DATA_TYPE_ACTIVITYTYPE = "type"; + public static final String EXTENDED_DATA_TYPE_LOCALIZED = "type"; + public static final String EXTENDED_DATA_ACTIVITY_TYPE = "activityType"; public static final String EXTENDED_DATA_TYPE_TRACKPOINT = "trackpoint_type"; public static final String EXTENDED_DATA_TYPE_SPEED = "speed"; @@ -298,13 +300,13 @@ private void writeBeginTrack(Track track) { printWriter.println("" + track.getUuid() + ""); printWriter.println("#" + TRACK_STYLE + ""); - writeActivityType(track.getActivityTypeLocalized()); + writeActivityType(track.getActivityType()); + writeTypeLocalized(track.getActivityTypeLocalized()); printWriter.println(""); printWriter.println("absolute"); printWriter.println("1"); } - private void writeEndTrack() { printWriter.println(""); printWriter.println(""); @@ -416,7 +418,7 @@ private void writePlacemark(String name, String activityType, String description printWriter.println("" + StringUtils.formatCData(description) + ""); printWriter.println("" + getTime(zoneOffset, location) + ""); printWriter.println("#" + KMLTrackExporter.MARKER_STYLE + ""); - writeActivityType(activityType); + writeTypeLocalized(activityType); printWriter.println(""); printWriter.println("" + getCoordinates(location, ",") + ""); printWriter.println(""); @@ -437,7 +439,7 @@ private void writePhotoOverlay(Marker marker, float heading, ZoneOffset zoneOffs printWriter.println(""); printWriter.println("" + getTime(zoneOffset, marker.getLocation()) + ""); printWriter.println("#" + MARKER_STYLE + ""); - writeActivityType(marker.getCategory()); + writeTypeLocalized(marker.getCategory()); if (exportPhotos) { printWriter.println("" + KmzTrackExporter.buildKmzImageFilePath(marker) + ""); @@ -492,12 +494,21 @@ private static String getCoordinates(Location location, String separator) { return result; } - private void writeActivityType(String activityTypeLocalized) { - if (activityTypeLocalized == null || "".equals(activityTypeLocalized)) { + private void writeTypeLocalized(String localizedValue) { + if (localizedValue == null || localizedValue.equals("")) { + return; + } + printWriter.println(""); + printWriter.println("" + StringUtils.formatCData(localizedValue) + ""); + printWriter.println(""); + } + + private void writeActivityType(ActivityType value) { + if (value == null) { return; } printWriter.println(""); - printWriter.println("" + StringUtils.formatCData(activityTypeLocalized) + ""); + printWriter.println("" + StringUtils.formatCData(value.getId()) + ""); printWriter.println(""); } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxTrackImporter.java index 7d7cc5cd3..8840d2c36 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxTrackImporter.java @@ -55,7 +55,6 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac private static final String TAG = GpxTrackImporter.class.getSimpleName(); private static final String TAG_DESCRIPTION = "desc"; - private static final String TAG_COMMENT = "cmt"; private static final String TAG_ALTITUDE = "ele"; private static final String TAG_GPX = "gpx"; private static final String TAG_NAME = "name"; @@ -64,6 +63,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac private static final String TAG_TRACK_POINT = "trkpt"; private static final String TAG_TRACK_SEGMENT = "trkseg"; private static final String TAG_TYPE = "type"; + private static final String TAG_TYPE_LOCALIZED = "opentracks:typeTranslated"; private static final String TAG_MARKER = "wpt"; private static final String TAG_ID = "opentracks:trackid"; @@ -99,6 +99,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac private String name; private String description; private String activityType; + private String activityTypeLocalized; private String latitude; private String longitude; private String altitude; @@ -159,7 +160,7 @@ public void endElement(String uri, String localName, String tag) { case TAG_GPX -> onFileEnd(); case TAG_MARKER -> onMarkerEnd(); case TAG_TRACK -> { - trackImporter.setTrack(context, name, uuid, description, activityType, null, zoneOffset); + trackImporter.setTrack(context, name, uuid, description, activityTypeLocalized, activityType, zoneOffset); zoneOffset = null; } case TAG_TRACK_SEGMENT -> onTrackSegmentEnd(); @@ -174,9 +175,16 @@ public void endElement(String uri, String localName, String tag) { description = content.trim(); } } - case TAG_TYPE -> { + case TAG_TYPE -> { //Track or Marker/WPT if (content != null) { + // In older version this might be localized content. activityType = content.trim(); + markerType = content.trim(); + } + } + case TAG_TYPE_LOCALIZED -> { + if (content != null) { + activityTypeLocalized = content.trim(); } } case TAG_TIME -> { @@ -189,11 +197,6 @@ public void endElement(String uri, String localName, String tag) { altitude = content.trim(); } } - case TAG_COMMENT -> { - if (content != null) { - markerType = content.trim(); - } - } case TAG_EXTENSION_SPEED, TAG_EXTENSION_SPEED_COMPAT -> { if (content != null) { speed = content.trim(); @@ -385,7 +388,6 @@ private void onTrackPointStart(Attributes attributes) { private void onMarkerStart(Attributes attributes) { name = null; description = null; - activityType = null; photoUrl = null; latitude = attributes.getValue(ATTRIBUTE_LAT); longitude = attributes.getValue(ATTRIBUTE_LON); @@ -415,8 +417,8 @@ private void onMarkerEnd() { if (description != null) { marker.setDescription(description); } - if (activityType != null) { - marker.setCategory(activityType); + if (markerType != null) { + marker.setCategory(markerType); } if (photoUrl != null) { diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlTrackImporter.java index e6b097f4d..1d278f126 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlTrackImporter.java @@ -53,7 +53,6 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac private static final String TAG_COORDINATES = "coordinates"; private static final String TAG_DESCRIPTION = "description"; - private static final String TAG_ICON = "icon"; private static final String TAG_COORD = "coord"; private static final String TAG_KML22_COORD = "gx:coord"; @@ -61,7 +60,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac private static final String TAG_MULTI_TRACK = "MultiTrack"; private static final String TAG_KML22_MULTI_TRACK = "gx:MultiTrack"; - private static final String TAG_DATA_ACTIVITYTYPE = "Data"; + private static final String TAG_EXTENDED_DATA = "Data"; private static final String TAG_SIMPLE_ARRAY_DATA = "SimpleArrayData"; private static final String TAG_KML22_SIMPLE_ARRAY_DATA = "gx:SimpleArrayData"; @@ -94,7 +93,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac private final ArrayList whenList = new ArrayList<>(); private final ArrayList locationList = new ArrayList<>(); - private String dataType; //Could be converted to an ENUM + private String dataType; private final ArrayList trackpointTypeList = new ArrayList<>(); private final ArrayList sensorSpeedList = new ArrayList<>(); @@ -112,10 +111,10 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac // The current element content private String content = ""; - private String icon; private String name; private String description; private String activityType; + private String activityTypeLocalized; private String latitude; private String longitude; private String altitude; @@ -148,7 +147,7 @@ public void startElement(String uri, String localName, String tag, Attributes at } onTrackSegmentStart(); } - case TAG_DATA_ACTIVITYTYPE, TAG_SIMPLE_ARRAY_DATA, TAG_KML22_SIMPLE_ARRAY_DATA -> + case TAG_EXTENDED_DATA, TAG_SIMPLE_ARRAY_DATA, TAG_KML22_SIMPLE_ARRAY_DATA -> dataType = attributes.getValue(ATTRIBUTE_NAME); } } @@ -167,18 +166,24 @@ public void endElement(String uri, String localName, String tag) throws SAXExcep onMarkerEnd(); case TAG_COORDINATES -> onMarkerLocationEnd(); case TAG_MULTI_TRACK, TAG_KML22_MULTI_TRACK -> { - trackImporter.setTrack(context, name, uuid, description, activityType, icon, zoneOffset); + trackImporter.setTrack(context, name, uuid, description, activityTypeLocalized, activityType, zoneOffset); zoneOffset = null; } case TAG_TRACK, TAG_KML22_TRACK -> onTrackSegmentEnd(); case TAG_COORD, TAG_KML22_COORD -> onCoordEnded(); case TAG_VALUE, TAG_KML22_VALUE -> { - if (KMLTrackExporter.EXTENDED_DATA_TYPE_ACTIVITYTYPE.equals(dataType)) { - if (content != null) { - activityType = content.trim(); + switch (dataType) { + case KMLTrackExporter.EXTENDED_DATA_ACTIVITY_TYPE -> { + if (content != null) { + activityType = content.trim(); + } } - } else { - onExtendedDataValueEnd(); + case KMLTrackExporter.EXTENDED_DATA_TYPE_LOCALIZED -> { + if (content != null) { + activityTypeLocalized = content.trim(); + } + } + default -> onExtendedDataValueEnd(); } } case TAG_NAME -> { @@ -196,11 +201,6 @@ public void endElement(String uri, String localName, String tag) throws SAXExcep description = content.trim(); } } - case TAG_ICON -> { - if (content != null) { - icon = content.trim(); - } - } case TAG_WHEN -> { if (content != null) { try { @@ -233,9 +233,8 @@ public void endElement(String uri, String localName, String tag) throws SAXExcep private void onMarkerStart() { // Reset all Placemark variables name = null; - icon = null; description = null; - activityType = null; + activityTypeLocalized = null; photoUrl = null; latitude = null; longitude = null; @@ -262,13 +261,13 @@ private void onMarkerEnd() { Marker marker = new Marker(null, new TrackPoint(TrackPoint.Type.TRACKPOINT, location, whenList.get(0))); //TODO Creating marker without need marker.setName(name != null ? name : ""); marker.setDescription(description != null ? description : ""); - marker.setCategory(activityType != null ? activityType : ""); + marker.setCategory(activityTypeLocalized != null ? activityTypeLocalized : ""); marker.setPhotoUrl(photoUrl); markers.add(marker); name = null; description = null; - activityType = null; + activityTypeLocalized = null; photoUrl = null; whenList.clear(); }