mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-11 13:33:07 +02:00
Timezone support: store timezone offset per Track.
When a recording is started the offset of the phone is used. Timezone is exported to KML and GPX. Fixes #301.
This commit is contained in:
@@ -24,6 +24,7 @@ import androidx.annotation.NonNull;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.NumberFormat;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -158,7 +159,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
writeOpenSegment();
|
||||
wroteSegment = true;
|
||||
|
||||
writeTrackPoint(trackPoint, sensorPoints);
|
||||
writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints);
|
||||
sensorPoints.clear();
|
||||
break;
|
||||
case SENSORPOINT:
|
||||
@@ -171,7 +172,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
wroteSegment = true;
|
||||
}
|
||||
|
||||
writeTrackPoint(trackPoint, sensorPoints);
|
||||
writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints);
|
||||
sensorPoints.clear();
|
||||
break;
|
||||
default:
|
||||
@@ -230,6 +231,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
+ " 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"
|
||||
+ " http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd"
|
||||
+ " http://www.garmin.com/xmlschemas/TrackStatsExtension/v1"
|
||||
+ " http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd\">");
|
||||
}
|
||||
}
|
||||
@@ -248,7 +250,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
Marker marker = contentProviderUtils.createMarker(cursor);
|
||||
writeMarker(marker);
|
||||
writeMarker(track.getZoneOffset(), marker);
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
@@ -256,13 +258,13 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
}
|
||||
}
|
||||
|
||||
public void writeMarker(Marker marker) {
|
||||
public void writeMarker(ZoneOffset zoneOffset, Marker marker) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<wpt " + formatLocation(marker.getLatitude(), marker.getLongitude()) + ">");
|
||||
if (marker.hasAltitude()) {
|
||||
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(marker.getAltitude().toM()) + "</ele>");
|
||||
}
|
||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(marker.getTime()) + "</time>");
|
||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(marker.getTime(), zoneOffset) + "</time>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(marker.getName()) + "</name>");
|
||||
printWriter.println("<desc>" + StringUtils.formatCData(marker.getDescription()) + "</desc>");
|
||||
printWriter.println("<type>" + StringUtils.formatCData(marker.getCategory()) + "</type>");
|
||||
@@ -310,7 +312,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
printWriter.println("</trkseg>");
|
||||
}
|
||||
|
||||
public void writeTrackPoint(TrackPoint trackPoint, List<TrackPoint> sensorPoints) {
|
||||
public void writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint, List<TrackPoint> sensorPoints) {
|
||||
if (printWriter != null) {
|
||||
|
||||
printWriter.println("<trkpt " + formatLocation(trackPoint.getLatitude(), trackPoint.getLongitude()) + ">");
|
||||
@@ -319,7 +321,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(trackPoint.getAltitude().toM()) + "</ele>");
|
||||
}
|
||||
|
||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime(), zoneOffset) + "</time>");
|
||||
|
||||
{
|
||||
String trackPointExtensionContent = "";
|
||||
|
||||
@@ -27,8 +27,8 @@ import androidx.annotation.VisibleForTesting;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.NumberFormat;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -110,10 +110,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
if (hasMultipleTracks) {
|
||||
writeMultiTrackBegin();
|
||||
}
|
||||
//TODO Why use startTime of first track for the others?
|
||||
Instant startTime = tracks[0].getTrackStatistics().getStartTime();
|
||||
for (Track track : tracks) {
|
||||
Duration offset = Duration.between(track.getTrackStatistics().getStartTime(), startTime);
|
||||
writeLocations(track);
|
||||
}
|
||||
if (hasMultipleTracks) {
|
||||
@@ -142,7 +139,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
hasMarkers = true;
|
||||
}
|
||||
Marker marker = contentProviderUtils.createMarker(cursor);
|
||||
writeMarker(marker);
|
||||
writeMarker(marker, track.getZoneOffset());
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
@@ -172,12 +169,12 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
case SEGMENT_START_AUTOMATIC:
|
||||
if (wroteSegment) writeCloseSegment();
|
||||
writeOpenSegment();
|
||||
writeTrackPoint(trackPoint);
|
||||
writeTrackPoint(track.getZoneOffset(), trackPoint);
|
||||
wroteSegment = true;
|
||||
break;
|
||||
case SEGMENT_END_MANUAL:
|
||||
if (!wroteSegment) writeOpenSegment();
|
||||
writeTrackPoint(trackPoint);
|
||||
writeTrackPoint(track.getZoneOffset(), trackPoint);
|
||||
writeCloseSegment();
|
||||
wroteSegment = false;
|
||||
break;
|
||||
@@ -188,7 +185,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
writeOpenSegment();
|
||||
wroteSegment = true;
|
||||
}
|
||||
writeTrackPoint(trackPoint);
|
||||
writeTrackPoint(track.getZoneOffset(), trackPoint);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
|
||||
@@ -268,14 +265,14 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeMarker(Marker marker) {
|
||||
private void writeMarker(Marker marker, ZoneOffset zoneOffset) {
|
||||
if (printWriter != null) {
|
||||
boolean existsPhoto = FileUtils.buildInternalPhotoFile(context, marker.getTrackId(), marker.getPhotoURI()) != null;
|
||||
if (marker.hasPhoto() && exportPhotos && existsPhoto) {
|
||||
float heading = getHeading(marker.getTrackId(), marker.getLocation());
|
||||
writePhotoOverlay(marker, heading);
|
||||
writePhotoOverlay(marker, heading, zoneOffset);
|
||||
} else {
|
||||
writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getLocation());
|
||||
writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getLocation(), zoneOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,12 +369,12 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void writeTrackPoint(TrackPoint trackPoint) {
|
||||
void writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<when>" + getTime(trackPoint.getLocation()) + "</when>");
|
||||
printWriter.println("<when>" + getTime(zoneOffset, trackPoint.getLocation()) + "</when>");
|
||||
|
||||
if (trackPoint.hasLocation()) {
|
||||
printWriter.println("<gx:coord>" + (trackPoint.hasLocation() ? getCoordinates(trackPoint.getLocation(), " ") : "") + "</gx:coord>");
|
||||
printWriter.println("<gx:coord>" + getCoordinates(trackPoint.getLocation(), " ") + "</gx:coord>");
|
||||
} else {
|
||||
printWriter.println("<gx:coord/>");
|
||||
}
|
||||
@@ -420,12 +417,12 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
* @param description the description
|
||||
* @param location the location
|
||||
*/
|
||||
private void writePlacemark(String name, String category, String description, Location location) {
|
||||
private void writePlacemark(String name, String category, String description, Location location, ZoneOffset zoneOffset) {
|
||||
if (location != null) {
|
||||
printWriter.println("<Placemark>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
|
||||
printWriter.println("<description>" + StringUtils.formatCData(description) + "</description>");
|
||||
printWriter.println("<TimeStamp><when>" + getTime(location) + "</when></TimeStamp>");
|
||||
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, location) + "</when></TimeStamp>");
|
||||
printWriter.println("<styleUrl>#" + KMLTrackExporter.MARKER_STYLE + "</styleUrl>");
|
||||
writeCategory(category);
|
||||
printWriter.println("<Point>");
|
||||
@@ -435,7 +432,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void writePhotoOverlay(Marker marker, float heading) {
|
||||
private void writePhotoOverlay(Marker marker, float heading, ZoneOffset zoneOffset) {
|
||||
printWriter.println("<PhotoOverlay>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(marker.getName()) + "</name>");
|
||||
printWriter.println("<description>" + StringUtils.formatCData(marker.getDescription()) + "</description>");
|
||||
@@ -446,7 +443,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
printWriter.print("<heading>" + heading + "</heading>");
|
||||
printWriter.print("<tilt>90</tilt>");
|
||||
printWriter.println("</Camera>");
|
||||
printWriter.println("<TimeStamp><when>" + getTime(marker.getLocation()) + "</when></TimeStamp>");
|
||||
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, marker.getLocation()) + "</when></TimeStamp>");
|
||||
printWriter.println("<styleUrl>#" + MARKER_STYLE + "</styleUrl>");
|
||||
writeCategory(marker.getCategory());
|
||||
|
||||
@@ -472,8 +469,8 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
*
|
||||
* @param location the location
|
||||
*/
|
||||
private String getTime(Location location) {
|
||||
return StringUtils.formatDateTimeIso8601(Instant.ofEpochMilli(location.getTime()));
|
||||
private String getTime(ZoneOffset zoneOffset, Location location) {
|
||||
return StringUtils.formatDateTimeIso8601(Instant.ofEpochMilli(location.getTime()), zoneOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,8 @@ import org.xml.sax.Attributes;
|
||||
import org.xml.sax.Locator;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -86,6 +87,8 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
|
||||
private final Context context;
|
||||
|
||||
private ZoneOffset zoneOffset;
|
||||
|
||||
// Belongs to the current track
|
||||
private final ArrayList<Marker> markers = new ArrayList<>();
|
||||
|
||||
@@ -157,7 +160,8 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
onMarkerEnd();
|
||||
break;
|
||||
case TAG_TRACK:
|
||||
trackImporter.setTrack(context, name, uuid, description, category, null);
|
||||
trackImporter.setTrack(context, name, uuid, description, category, null, zoneOffset);
|
||||
zoneOffset = null;
|
||||
break;
|
||||
case TAG_TRACK_SEGMENT:
|
||||
onTrackSegmentEnd();
|
||||
@@ -256,14 +260,17 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
|
||||
|
||||
private TrackPoint createTrackPoint() throws ParsingException {
|
||||
Instant parsedTime;
|
||||
OffsetDateTime parsedTime;
|
||||
try {
|
||||
parsedTime = StringUtils.parseTime(time);
|
||||
if (zoneOffset == null) {
|
||||
zoneOffset = parsedTime.getOffset();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, parsedTime);
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, parsedTime.toInstant());
|
||||
if (latitude == null || longitude == null) {
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -74,6 +76,8 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
private final Context context;
|
||||
|
||||
// Belongs to the current track
|
||||
private ZoneOffset zoneOffset;
|
||||
|
||||
private final ArrayList<Instant> whenList = new ArrayList<>();
|
||||
private final ArrayList<Location> locationList = new ArrayList<>();
|
||||
|
||||
@@ -157,7 +161,8 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
onMarkerLocationEnd();
|
||||
break;
|
||||
case TAG_GX_MULTI_TRACK:
|
||||
trackImporter.setTrack(context, name, uuid, description, category, icon);
|
||||
trackImporter.setTrack(context, name, uuid, description, category, icon, zoneOffset);
|
||||
zoneOffset = null;
|
||||
break;
|
||||
case TAG_GX_TRACK:
|
||||
onTrackSegmentEnd();
|
||||
@@ -195,7 +200,15 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
break;
|
||||
case TAG_WHEN:
|
||||
if (content != null) {
|
||||
whenList.add(StringUtils.parseTime(content.trim()));
|
||||
try {
|
||||
OffsetDateTime time = StringUtils.parseTime(content.trim());
|
||||
if (zoneOffset == null) {
|
||||
zoneOffset = time.getOffset();
|
||||
}
|
||||
whenList.add(time.toInstant());
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", content.trim())), e);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -4,6 +4,10 @@ import androidx.annotation.NonNull;
|
||||
|
||||
public class ParsingException extends RuntimeException {
|
||||
|
||||
protected ParsingException(@NonNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
protected ParsingException(@NonNull String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@ import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
@@ -89,8 +91,8 @@ public class TrackImporter {
|
||||
this.markers.addAll(markers);
|
||||
}
|
||||
|
||||
void setTrack(Context context, String name, String uuid, String description, String category, String icon) {
|
||||
track = new Track();
|
||||
void setTrack(Context context, String name, String uuid, String description, String category, String icon, @Nullable ZoneOffset zoneOffset) {
|
||||
track = new Track(zoneOffset != null ? zoneOffset : ZoneOffset.UTC);
|
||||
track.setName(name != null ? name : "");
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user