From 1d5f65be489fbcdb67ba0a67d3c8d5f3f0e0f024 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Fri, 28 Feb 2025 22:20:39 +0100 Subject: [PATCH] Position: use for TrackPoint. --- .../opentracks/data/ContentProviderUtils.java | 38 ++-- .../opentracks/data/models/Position.java | 71 ++++++-- .../opentracks/data/models/TrackPoint.java | 166 ++++++++---------- .../io/file/importer/GpxTrackImporter.java | 61 ++++--- .../io/file/importer/KmlTrackImporter.java | 8 +- .../services/handlers/TrackPointCreator.java | 12 +- .../SettingsCustomLayoutEditAdapter.java | 18 +- 7 files changed, 212 insertions(+), 162 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java index c7b4cac77..81fae11d5 100644 --- a/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java @@ -622,31 +622,19 @@ public class ContentProviderUtils { * @param indexes the cached trackPoints indexes */ static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) { - Instant time = Instant.ofEpochMilli(cursor.getLong(indexes.timeIndex)); - TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.getById(cursor.getInt(indexes.typeIndex)), time); - trackPoint.setId(new TrackPoint.Id(cursor.getInt(indexes.idIndex))); - - if (!cursor.isNull(indexes.longitudeIndex)) { - trackPoint.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6); - } - if (!cursor.isNull(indexes.latitudeIndex)) { - trackPoint.setLatitude(((double) cursor.getInt(indexes.latitudeIndex)) / 1E6); - } - if (!cursor.isNull(indexes.altitudeIndex)) { - trackPoint.setAltitude(Altitude.WGS84.of(cursor.getFloat(indexes.altitudeIndex))); - } - if (!cursor.isNull(indexes.accuracyIndex)) { - trackPoint.setHorizontalAccuracy(Distance.of(cursor.getFloat(indexes.accuracyIndex))); - } - if (!cursor.isNull(indexes.accuracyVerticalIndex)) { - trackPoint.setVerticalAccuracy(Distance.of(cursor.getFloat(indexes.accuracyVerticalIndex))); - } - if (!cursor.isNull(indexes.speedIndex)) { - trackPoint.setSpeed(Speed.of(cursor.getFloat(indexes.speedIndex))); - } - if (!cursor.isNull(indexes.bearingIndex)) { - trackPoint.setBearing(cursor.getFloat(indexes.bearingIndex)); - } + TrackPoint trackPoint = new TrackPoint( + new TrackPoint.Id(cursor.getInt(indexes.idIndex)), + TrackPoint.Type.getById(cursor.getInt(indexes.typeIndex)), + new Position( + Instant.ofEpochMilli(cursor.getLong(indexes.timeIndex)), + !cursor.isNull(indexes.latitudeIndex) ? ((double) cursor.getInt(indexes.latitudeIndex)) / 1E6 : null, + !cursor.isNull(indexes.longitudeIndex) ? ((double) cursor.getInt(indexes.longitudeIndex)) / 1E6 : null, + !cursor.isNull(indexes.accuracyIndex) ? Distance.of(cursor.getFloat(indexes.accuracyIndex)) : null, + !cursor.isNull(indexes.altitudeIndex) ? Altitude.WGS84.of(cursor.getFloat(indexes.altitudeIndex)) : null, + !cursor.isNull(indexes.accuracyVerticalIndex) ? Distance.of(cursor.getFloat(indexes.accuracyVerticalIndex)) : null, + !cursor.isNull(indexes.bearingIndex) ? cursor.getFloat(indexes.bearingIndex) : null, + !cursor.isNull(indexes.speedIndex) ? Speed.of(cursor.getFloat(indexes.speedIndex)) : null + )); if (!cursor.isNull(indexes.sensorHeartRateIndex)) { trackPoint.setHeartRate(cursor.getFloat(indexes.sensorHeartRateIndex)); diff --git a/src/main/java/de/dennisguse/opentracks/data/models/Position.java b/src/main/java/de/dennisguse/opentracks/data/models/Position.java index b8ebe0ab1..836d13eee 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/Position.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/Position.java @@ -2,22 +2,29 @@ package de.dennisguse.opentracks.data.models; import android.location.Location; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.time.Instant; //TODO Use everywhere instead of android.location.Location. public record Position( - Instant time, - Double latitude, - Double longitude, - Distance horizontalAccuracy, - Altitude altitude, - Distance verticalAccuracy, - Float bearing, - Speed speed + @Nullable Instant time, + @Nullable Double latitude, + @Nullable Double longitude, + @Nullable Distance horizontalAccuracy, + @Nullable Altitude altitude, + @Nullable Distance verticalAccuracy, + @Nullable Float bearing, + @Nullable Speed speed ) { public static Position empty() { + return of((Instant) null); + } + + public static Position of(@NonNull Instant time) { return new Position( - null, + time, null, null, null, @@ -28,9 +35,13 @@ public record Position( ); } - public static Position of(Location location) { + public static Position of(@NonNull Location location) { + return of(location, Instant.ofEpochMilli(location.getTime())); + } + + public static Position of(@NonNull Location location, @NonNull Instant time) { return new Position( - Instant.ofEpochMilli(location.getTime()), + time, location.getLatitude(), location.getLongitude(), location.hasAccuracy() ? Distance.of(location.getAccuracy()) : null, @@ -53,6 +64,10 @@ public record Position( return altitude != null; } + public boolean hasVerticalAccuracy() { + return verticalAccuracy != null; + } + public boolean hasBearing() { return bearing != null; } @@ -67,7 +82,9 @@ public record Position( } Location location = new Location(""); - location.setTime(time.toEpochMilli()); + if (time != null) { + location.setTime(time.toEpochMilli()); + } location.setLatitude(latitude); location.setLongitude(longitude); if (hasBearing()) { @@ -103,4 +120,34 @@ public record Position( .lessThan(thresholdHorizontalAccuracy); } + + public Position with(Instant time) { + return new Position(time, latitude, longitude, horizontalAccuracy, altitude, verticalAccuracy, bearing, speed); + } + + public Position with(Altitude altitude) { + return new Position(time, latitude, longitude, horizontalAccuracy, altitude, verticalAccuracy, bearing, speed); + } + + public Position with(Speed speed) { + return new Position(time, latitude, longitude, horizontalAccuracy, altitude, verticalAccuracy, bearing, speed); + } + + //TODO Use double + public Position withCoordinates(Double latitude, Double longitude) { + return new Position(time, latitude, longitude, horizontalAccuracy, altitude, verticalAccuracy, bearing, speed); + } + + //TODO Use float + public Position withBearing(Float bearing) { + return new Position(time, latitude, longitude, horizontalAccuracy, altitude, verticalAccuracy, bearing, speed); + } + + public Position withHorizontalAccuracy(Distance horizontalAccuracy) { + return new Position(time, latitude, longitude, horizontalAccuracy, altitude, verticalAccuracy, bearing, speed); + } + + public Position withVerticalAccuracy(Distance verticalAccuracy) { + return new Position(time, latitude, longitude, horizontalAccuracy, altitude, verticalAccuracy, bearing, speed); + } } diff --git a/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java b/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java index 961594214..f75c8475f 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java @@ -38,20 +38,12 @@ public class TrackPoint { private static final Duration MAX_LOCATION_AGE = Duration.ofMinutes(1); + @Nullable private TrackPoint.Id id; + //Requires: position.time must be non-null @NonNull - private final Instant time; - - //TODO We may use Position for these items - private Double latitude; - private Double longitude; - private Distance horizontalAccuracy; - private Distance verticalAccuracy; - private Altitude altitude; //TODO use Altitude.WGS84 - private Speed speed; - private Float bearing; - private Distance sensorDistance; + private Position position; public enum Type { SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume) @@ -73,6 +65,7 @@ public class TrackPoint { this.type_db = type_db; } + @NonNull @Override public String toString() { return name() + "(" + type_db + ")"; @@ -90,33 +83,55 @@ public class TrackPoint { @NonNull private Type type; + private Distance sensorDistance; private HeartRate heartRate = null; private Cadence cadence = null; private Power power = null; private Float altitudeGain_m = null; private Float altitudeLoss_m = null; - public TrackPoint(@NonNull Type type, @NonNull Instant time) { + public TrackPoint(@Nullable TrackPoint.Id id, @NonNull Type type, @NonNull Position position) { + this.id = id; this.type = type; - this.time = time; + this.position = position; } + //TODO Refactor constructors + public TrackPoint(@NonNull Type type, @NonNull Instant time) { + this.type = type; + this.position = Position.of(time); + } + + @Deprecated public TrackPoint(@NonNull Location location, @NonNull Instant time) { - this(Type.TRACKPOINT, location, time); + this.type = Type.TRACKPOINT; + this.position = Position.of(location, time); + } + + public TrackPoint(@NonNull Position position) { + this(null, Type.TRACKPOINT, position); } public TrackPoint(@NonNull Type type, @NonNull Location location, @NonNull Instant time) { - this(type, time); - - setLocation(location); + this.type = type; + this.position = Position.of(location, time); } + //TODO Remove; only used for TrackStatisticUpdaterTest + @Deprecated @VisibleForTesting public TrackPoint(double latitude, double longitude, Altitude altitude, Instant time) { - this(Type.TRACKPOINT, time); - this.latitude = latitude; - this.longitude = longitude; - this.altitude = altitude; + this.type = Type.TRACKPOINT; + this.position = new Position( + time, + latitude, + longitude, + null, + altitude, + null, + null, + null + ); } public static TrackPoint createSegmentStartManualWithTime(Instant time) { @@ -161,54 +176,46 @@ public class TrackPoint { return id; } - public void setId(TrackPoint.Id id) { - this.id = id; - } - public boolean hasLocation() { - return latitude != null && longitude != null; + return position.hasLocation(); } - public double getLatitude() { - return latitude; + //TODO Should be double + @Deprecated //Use getPosition() + public Double getLatitude() { + return position.latitude(); } + @Deprecated public TrackPoint setLatitude(double latitude) { - this.latitude = latitude; + this.position = position.withCoordinates(latitude, getLongitude()); return this; } - public double getLongitude() { - return longitude; + //TODO Should be double + @Deprecated //Use getPosition() + public Double getLongitude() { + return position.longitude(); } + @Deprecated public TrackPoint setLongitude(double longitude) { - this.longitude = longitude; + setCoordinates(getLatitude(), longitude); + return this; + } + + public TrackPoint setCoordinates(double latitude, double longitude) { + this.position = this.position.withCoordinates(latitude, longitude); return this; } @NonNull public Position getPosition() { - return new Position( - time, - latitude, - longitude, - horizontalAccuracy, - altitude, - verticalAccuracy, - bearing, - speed - ); + return position; } - public TrackPoint setPosition(Position location) { - this.latitude = location.latitude(); - this.longitude = location.longitude(); - this.altitude = location.altitude(); - this.speed = location.speed(); - this.horizontalAccuracy = location.horizontalAccuracy(); - this.verticalAccuracy = location.verticalAccuracy(); - + public TrackPoint setPosition(Position position) { + this.position = position.with(this.position.time()); return this; } @@ -218,12 +225,6 @@ public class TrackPoint { return getPosition().toLocation(); } - @Deprecated - public TrackPoint setLocation(@NonNull Location location) { - setPosition(Position.of(location)); - return this; - } - public boolean hasAltitudeGain() { return altitudeGain_m != null; } @@ -252,83 +253,77 @@ public class TrackPoint { @NonNull public Instant getTime() { - return time; + return position.time(); } - public boolean isRecent() { - return Instant.now() - .isBefore(time.plus(MAX_LOCATION_AGE)); - } - - public boolean hasAltitude() { - return altitude != null; + return position.hasAltitude(); } public Altitude getAltitude() { - return altitude; + return position.altitude(); } @VisibleForTesting public TrackPoint setAltitude(double altitude_m) { - this.altitude = Altitude.WGS84.of(altitude_m); + setAltitude(Altitude.WGS84.of(altitude_m)); return this; } public TrackPoint setAltitude(Altitude altitude) { - this.altitude = altitude; + position = position.with(altitude); return this; } public boolean hasSpeed() { - return speed != null; + return position.hasSpeed(); } public Speed getSpeed() { - return speed; + return position.speed(); } public TrackPoint setSpeed(Speed speed) { - this.speed = speed; + this.position = position.with(speed); return this; } public boolean hasBearing() { - return bearing != null; + return position.hasBearing(); } public float getBearing() { - return bearing; + return position.bearing(); } public TrackPoint setBearing(Float bearing) { - this.bearing = bearing; + this.position = this.position.withBearing(bearing); return this; } public boolean hasHorizontalAccuracy() { - return horizontalAccuracy != null; + return position.hasHorizontalAccuracy(); } public Distance getHorizontalAccuracy() { - return horizontalAccuracy; + return position.horizontalAccuracy(); } public TrackPoint setHorizontalAccuracy(Distance horizontalAccuracy) { - this.horizontalAccuracy = horizontalAccuracy; + this.position = this.position.withHorizontalAccuracy(horizontalAccuracy); return this; } public boolean hasVerticalAccuracy() { - return verticalAccuracy != null; + return position.hasVerticalAccuracy(); } public Distance getVerticalAccuracy() { - return verticalAccuracy; + return position.verticalAccuracy(); } - public TrackPoint setVerticalAccuracy(Distance horizontalAccuracy) { - this.verticalAccuracy = horizontalAccuracy; + public TrackPoint setVerticalAccuracy(Distance verticalAccuracy) { + this.position = this.position.withVerticalAccuracy(verticalAccuracy); return this; } @@ -350,7 +345,7 @@ public class TrackPoint { } public boolean fulfillsAccuracy(Distance thresholdHorizontalAccuracy) { - return hasHorizontalAccuracy() && horizontalAccuracy.lessThan(thresholdHorizontalAccuracy); + return position.fulfillsAccuracy(thresholdHorizontalAccuracy); } public Optional bearingTo(@NonNull Position dest) { @@ -459,14 +454,7 @@ public class TrackPoint { public String toString() { return "TrackPoint{" + "id=" + id + - ", time=" + time + - ", latitude=" + latitude + - ", longitude=" + longitude + - ", horizontalAccuracy=" + horizontalAccuracy + - ", verticalAccuracy=" + verticalAccuracy + - ", altitude=" + altitude + - ", speed=" + speed + - ", bearing=" + bearing + + ", position=" + position + ", sensorDistance=" + sensorDistance + ", type=" + type + ", heartRate=" + heartRate + 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 5f980fea3..1dc10bace 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 @@ -34,6 +34,7 @@ import java.util.Locale; import de.dennisguse.opentracks.data.models.Altitude; import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Marker; +import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.TrackPoint; @@ -161,7 +162,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac case TAG_GPX -> onFileEnd(); case TAG_MARKER -> onMarkerEnd(); case TAG_TRACK -> { - if (activityTypeLocalized == null ) { + if (activityTypeLocalized == null) { // Backward compatibility: up v4.9.1 as contained localized content. activityTypeLocalized = activityType; } @@ -282,33 +283,65 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e); } - TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, parsedTime.toInstant()); if (latitude == null || longitude == null) { - return trackPoint; + return new TrackPoint(TrackPoint.Type.TRACKPOINT, parsedTime.toInstant()); } + double latitudeParsed; + double longitudeParsed; + Distance accuracyHorizontalParsed = null; + Altitude.WGS84 altitudeParsed = null; + Distance accuracyVerticalParsed = null; + Speed speedParsed = null; + try { - trackPoint.setLatitude(Double.parseDouble(latitude)); - trackPoint.setLongitude(Double.parseDouble(longitude)); + latitudeParsed = Double.parseDouble(latitude); + longitudeParsed = Double.parseDouble(longitude); } catch (NumberFormatException e) { throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e); } + if (accuracyHorizontal != null) { + try { + accuracyHorizontalParsed = Distance.of(accuracyHorizontal); + } catch (NumberFormatException e) { + throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse accuracy_horizontal: %s", sensorDistance)), e); + } + } if (altitude != null) { try { - trackPoint.setAltitude(Altitude.WGS84.of(Double.parseDouble(altitude))); + altitudeParsed = Altitude.WGS84.of(Double.parseDouble(altitude)); } catch (NumberFormatException e) { throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e); } } + if (accuracyVertical != null) { + try { + accuracyVerticalParsed = Distance.of(accuracyVertical); + } catch (NumberFormatException e) { + throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse accuracy_vertical: %s", accuracyVertical)), e); + } + } if (speed != null) { try { - trackPoint.setSpeed(Speed.of(speed)); + speedParsed = Speed.of(speed); } catch (NumberFormatException e) { throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e); } } + + TrackPoint trackPoint = new TrackPoint(new Position( + parsedTime.toInstant(), + latitudeParsed, + longitudeParsed, + accuracyHorizontalParsed, + altitudeParsed, + accuracyVerticalParsed, + null, + speedParsed + )); + if (heartrate != null) { try { trackPoint.setHeartRate(Float.parseFloat(heartrate)); @@ -354,20 +387,6 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", sensorDistance)), e); } } - if (accuracyHorizontal != null) { - try { - trackPoint.setHorizontalAccuracy(Distance.of(accuracyHorizontal)); - } catch (NumberFormatException e) { - throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse accuracy_horizontal: %s", sensorDistance)), e); - } - } - if (accuracyVertical != null) { - try { - trackPoint.setVerticalAccuracy(Distance.of(accuracyVertical)); - } catch (NumberFormatException e) { - throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse accuracy_vertical: %s", sensorDistance)), e); - } - } return trackPoint; } 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 a1e710ea8..0767a32aa 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 @@ -313,9 +313,11 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac Instant time = whenList.get(i); Location location = locationList.get(i); - TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, time); - if (location != null) { - trackPoint.setLocation(location); + TrackPoint trackPoint; + if (location == null) { + trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, time); + } else { + trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, location, time); } if (i < trackpointTypeList.size() && trackpointTypeList.get(i) != null) { diff --git a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java index 305bb8c40..c1f04504b 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/TrackPointCreator.java @@ -107,19 +107,19 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh public Pair createCurrentTrackPoint(@Nullable TrackPoint lastTrackPointUISpeed, @Nullable TrackPoint lastTrackPointUIAltitude, @Nullable TrackPoint lastStoredTrackPointWithLocation) { TrackPoint currentTrackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, createNow()); + if (lastStoredTrackPointWithLocation != null && lastStoredTrackPointWithLocation.hasLocation()) { + //We are taking the coordinates from the last stored TrackPoint, so the distance is monotonously increasing. + currentTrackPoint.setPosition(lastStoredTrackPointWithLocation.getPosition()); + } + if (lastTrackPointUISpeed != null) { currentTrackPoint.setSpeed(lastTrackPointUISpeed.getSpeed()); } + if (lastTrackPointUIAltitude != null) { currentTrackPoint.setAltitude(lastTrackPointUIAltitude.getAltitude()); } - if (lastStoredTrackPointWithLocation != null && lastStoredTrackPointWithLocation.hasLocation()) { - //We are taking the coordinates from the last stored TrackPoint, so the distance is monotonously increasing. - currentTrackPoint.setLongitude(lastStoredTrackPointWithLocation.getLongitude()); - currentTrackPoint.setLatitude(lastStoredTrackPointWithLocation.getLatitude()); - } - SensorDataSet sensorDataSet = addSensorData(currentTrackPoint); return new Pair<>(currentTrackPoint, sensorDataSet); diff --git a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java index ec85fc469..d943208a6 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java +++ b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java @@ -20,6 +20,7 @@ import java.util.concurrent.Callable; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.models.Altitude; import de.dennisguse.opentracks.data.models.Distance; +import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.TrackPoint; @@ -49,13 +50,18 @@ public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter