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 f86bad646..545ab9dc8 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 @@ -9,7 +9,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.location.Location; import android.net.Uri; -import android.os.Build; import android.os.Looper; import androidx.preference.PreferenceManager; @@ -65,7 +64,6 @@ import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence; import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpeed; import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower; import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate; -import de.dennisguse.opentracks.sensors.sensorData.Raw; import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.handlers.TrackPointCreator; @@ -596,15 +594,13 @@ public class ExportImportTest { location.setLatitude(latitude); location.setLongitude(longitude); location.setAccuracy(accuracy); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - location.setVerticalAccuracyMeters(verticalAccuracy); - } + location.setVerticalAccuracyMeters(verticalAccuracy); location.setSpeed(speed); location.setAltitude(altitude); mockAltitudeChange(trackPointCreator, altitudeGain); trackPointCreator.setClock(time); - trackPointCreator.getSensorManager().onChanged(new Raw<>(location)); + trackPointCreator.getSensorManager().getGpsManager().onLocationChanged(location); } } \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/data/models/Position.java b/src/main/java/de/dennisguse/opentracks/data/models/Position.java new file mode 100644 index 000000000..9597d0788 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/data/models/Position.java @@ -0,0 +1,88 @@ +package de.dennisguse.opentracks.data.models; + +import android.location.Location; + +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 +) { + public static Position empty() { + return new Position( + null, + null, + null, + null, + null, + null, + null, + null + ); + } + + public static Position of(Location location) { + return new Position( + Instant.ofEpochMilli(location.getTime()), + location.getLatitude(), + location.getLongitude(), + location.hasAccuracy() ? Distance.of(location.getAccuracy()) : null, + location.hasAltitude() ? Altitude.WGS84.of(location.getAltitude()) : null, + location.hasVerticalAccuracy() ? Distance.of(location.getVerticalAccuracyMeters()) : null, + location.hasBearing() ? location.getBearing() : null, + location.hasSpeed() ? Speed.of(location.getSpeed()) : null + ); + } + + public boolean hasLocation() { + return latitude != null && longitude != null; + } + + public boolean hasHorizontalAccuracy() { + return horizontalAccuracy != null; + } + + public boolean hasAltitude() { + return altitude != null; + } + + public boolean hasBearing() { + return bearing != null; + } + + public boolean hasSpeed() { + return speed != null; + } + + public Location toLocation() { + if (!hasLocation()) { + throw new RuntimeException("Cannot convert to Location."); + } + + Location location = new Location(""); + location.setTime(time.toEpochMilli()); + location.setLatitude(latitude); + location.setLongitude(longitude); + if (hasBearing()) { + location.setBearing(bearing); + } + if (hasHorizontalAccuracy()) { + location.setAccuracy((float) horizontalAccuracy.toM()); + } + if (hasAltitude()) { + location.setAltitude(altitude.toM()); + } + if (hasSpeed()) { + location.setSpeed((float) speed.toMPS()); + } + + return location; + } +} 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 8e68fdc13..afeb227c8 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/TrackPoint.java @@ -16,7 +16,6 @@ package de.dennisguse.opentracks.data.models; import android.location.Location; -import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -26,6 +25,7 @@ import androidx.annotation.VisibleForTesting; import java.time.Duration; import java.time.Instant; +import java.util.Optional; /** * Sensor and/or location information for a specific point in time. @@ -42,6 +42,7 @@ public class TrackPoint { @NonNull private final Instant time; + //TODO We may use Position for these items private Double latitude; private Double longitude; private Distance horizontalAccuracy; @@ -185,42 +186,40 @@ public class TrackPoint { return this; } - //TODO Better return null, if no location is present aka latitude == null etc. @NonNull - public Location getLocation() { - Location location = new Location(""); - location.setTime(time.toEpochMilli()); - if (hasLocation()) { - location.setLatitude(latitude); - location.setLongitude(longitude); - } - if (hasBearing()) { - location.setBearing(bearing); - } - if (hasHorizontalAccuracy()) { - location.setAccuracy((float) horizontalAccuracy.toM()); - } - if (hasAltitude()) { - location.setAltitude(altitude.toM()); - } - if (hasSpeed()) { - location.setSpeed((float) speed.toMPS()); - } - - return location; + public Position getPosition() { + return new Position( + time, + latitude, + longitude, + horizontalAccuracy, + altitude, + verticalAccuracy, + bearing, + speed + ); } - public TrackPoint setLocation(@NonNull Location location) { - this.latitude = location.getLatitude(); - this.longitude = location.getLongitude(); - this.altitude = location.hasAltitude() ? Altitude.WGS84.of(location.getAltitude()) : null; - this.speed = location.hasSpeed() ? Speed.of(location.getSpeed()) : null; - this.horizontalAccuracy = location.hasAccuracy() ? Distance.of(location.getAccuracy()) : null; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - this.verticalAccuracy = location.hasVerticalAccuracy() ? Distance.of(location.getVerticalAccuracyMeters()) : null; - } + 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(); - //TODO Should we copy the bearing? + return this; + } + + @Deprecated + @NonNull + public Location getLocation() { + return getPosition().toLocation(); + } + + @Deprecated + public TrackPoint setLocation(@NonNull Location location) { + setPosition(Position.of(location)); return this; } @@ -292,6 +291,7 @@ public class TrackPoint { this.speed = speed; return this; } + public boolean hasBearing() { return bearing != null; } @@ -352,13 +352,20 @@ public class TrackPoint { return hasHorizontalAccuracy() && horizontalAccuracy.lessThan(thresholdHorizontalAccuracy); } - public float bearingTo(@NonNull TrackPoint dest) { + public Optional bearingTo(@NonNull TrackPoint dest) { + if (!dest.hasLocation() || !hasLocation()) { + return Optional.empty(); + } return bearingTo(dest.getLocation()); } //TODO Bearing requires a location; what do we do if we don't have any? - public float bearingTo(@NonNull Location dest) { - return getLocation().bearingTo(dest); + @Deprecated + public Optional bearingTo(@NonNull Location dest) { + if (!hasLocation()) { + return Optional.empty(); + } + return Optional.of(getLocation().bearingTo(dest)); } // Sensor data 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 b76dd3305..aec86930a 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 @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.Optional; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.ContentProviderUtils; @@ -274,7 +275,7 @@ public class KMLTrackExporter implements TrackExporter { float heading = getHeading(marker.getTrackId(), marker.getLocation()); writePhotoOverlay(marker, heading, zoneOffset); } else { - writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getLocation(), zoneOffset); + writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getLocation(), marker.getTime(), zoneOffset); } } @@ -368,7 +369,7 @@ public class KMLTrackExporter implements TrackExporter { @VisibleForTesting void writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint) { - printWriter.println("" + getTime(zoneOffset, trackPoint.getLocation()) + ""); + printWriter.println("" + getTime(zoneOffset, trackPoint.getTime()) + ""); trackpointTypeList.add(trackPoint.getType()); @@ -411,12 +412,12 @@ public class KMLTrackExporter implements TrackExporter { printWriter.println(""); } - private void writePlacemark(String name, String activityType, String description, Location location, ZoneOffset zoneOffset) { + private void writePlacemark(String name, String activityType, String description, Location location, Instant time, ZoneOffset zoneOffset) { if (location != null) { printWriter.println(""); printWriter.println("" + StringUtils.formatCData(name) + ""); printWriter.println("" + StringUtils.formatCData(description) + ""); - printWriter.println("" + getTime(zoneOffset, location) + ""); + printWriter.println("" + getTime(zoneOffset, time) + ""); printWriter.println("#" + KMLTrackExporter.MARKER_STYLE + ""); writeTypeLocalized(activityType); printWriter.println(""); @@ -437,7 +438,7 @@ public class KMLTrackExporter implements TrackExporter { printWriter.print("" + heading + ""); printWriter.print("90"); printWriter.println(""); - printWriter.println("" + getTime(zoneOffset, marker.getLocation()) + ""); + printWriter.println("" + getTime(zoneOffset, marker.getTime()) + ""); printWriter.println("#" + MARKER_STYLE + ""); writeTypeLocalized(marker.getCategory()); @@ -460,14 +461,13 @@ public class KMLTrackExporter implements TrackExporter { /** * Returns the formatted time of the location; either absolute or relative depending exportTrackDetail. - * - * @param location the location */ - private String getTime(ZoneOffset zoneOffset, Location location) { - return StringUtils.formatDateTimeIso8601(Instant.ofEpochMilli(location.getTime()), zoneOffset); + private String getTime(ZoneOffset zoneOffset, Instant instant) { + return StringUtils.formatDateTimeIso8601(instant, zoneOffset); } /** + * TODO: check if this is a useful feature (likely not). * Gets the heading to a location. * * @param trackId the track id containing the location @@ -480,7 +480,10 @@ public class KMLTrackExporter implements TrackExporter { } TrackPoint viewLocation = contentProviderUtils.getLastValidTrackPoint(trackId); if (viewLocation != null) { - return viewLocation.bearingTo(location); + Optional bearing = viewLocation.bearingTo(location); + if (bearing.isPresent()) { + return bearing.get(); + } } return location.getBearing(); diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java index 49bfa109c..8bc2d07c2 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/TrackImporter.java @@ -206,7 +206,8 @@ public class TrackImporter { } if (!current.hasBearing()) { - current.setBearing(previous.bearingTo(current)); + previous.bearingTo(current) + .ifPresent(current::setBearing); } if (current.getType().equals(TrackPoint.Type.TRACKPOINT) && distanceToPrevious.greaterThan(maxRecordingDistance)) { diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorGPS.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorGPS.java index 6463d9c6c..5e8acaa6d 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorGPS.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorGPS.java @@ -1,12 +1,10 @@ package de.dennisguse.opentracks.sensors.sensorData; -import android.location.Location; - import androidx.annotation.NonNull; -import java.util.Optional; +import de.dennisguse.opentracks.data.models.Position; -public class AggregatorGPS extends Aggregator> { +public class AggregatorGPS extends Aggregator { public AggregatorGPS(String sensorAddress) { @@ -14,8 +12,8 @@ public class AggregatorGPS extends Aggregator> { } @Override - protected void computeValue(Raw current) { - value = Optional.of(current.value()); + protected void computeValue(Raw current) { + value = current.value(); } @Override @@ -25,7 +23,7 @@ public class AggregatorGPS extends Aggregator> { @NonNull @Override - protected Optional getNoneValue() { - return Optional.empty(); + protected Position getNoneValue() { + return Position.empty(); } } diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataSet.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataSet.java index 626442cdd..256ea5099 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataSet.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/SensorDataSet.java @@ -1,6 +1,5 @@ package de.dennisguse.opentracks.sensors.sensorData; -import android.location.Location; import android.util.Log; import android.util.Pair; @@ -11,6 +10,7 @@ import androidx.annotation.VisibleForTesting; import de.dennisguse.opentracks.data.models.AtmosphericPressure; import de.dennisguse.opentracks.data.models.Cadence; import de.dennisguse.opentracks.data.models.HeartRate; +import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence; @@ -127,8 +127,8 @@ public final class SensorDataSet { this.barometer.add((Raw) data); return; } - if (value instanceof Location) { - this.gps.add((Raw) data); + if (value instanceof Position) { + this.gps.add((Raw) data); return; } @@ -152,8 +152,7 @@ public final class SensorDataSet { public void fillTrackPoint(TrackPoint trackPoint) { if (gps != null && gps.hasValue()) { - gps.getValue() - .ifPresent(trackPoint::setLocation); + trackPoint.setPosition(gps.getValue()); } if (getHeartRate() != null) { diff --git a/src/main/java/de/dennisguse/opentracks/services/handlers/GPSManager.java b/src/main/java/de/dennisguse/opentracks/services/handlers/GPSManager.java index c2b6dce93..8892a53f5 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/GPSManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/GPSManager.java @@ -18,6 +18,7 @@ import java.time.Duration; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.models.Distance; +import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.sensors.SensorConnector; import de.dennisguse.opentracks.sensors.SensorManager; @@ -138,7 +139,7 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS return; } - listener.onChange(new Raw<>(location)); + listener.onChange(new Raw<>(Position.of(location))); } @Override