Introduced Position.

Internal value object replacing android.location.Location.

Follow-up to #1898.
This commit is contained in:
Dennis Guse
2024-06-22 00:18:20 +02:00
parent 382acadc6a
commit e442558eed
8 changed files with 160 additions and 67 deletions
@@ -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;
}
}
@@ -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<Float> 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<Float> bearingTo(@NonNull Location dest) {
if (!hasLocation()) {
return Optional.empty();
}
return Optional.of(getLocation().bearingTo(dest));
}
// Sensor data
@@ -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("<when>" + getTime(zoneOffset, trackPoint.getLocation()) + "</when>");
printWriter.println("<when>" + getTime(zoneOffset, trackPoint.getTime()) + "</when>");
trackpointTypeList.add(trackPoint.getType());
@@ -411,12 +412,12 @@ public class KMLTrackExporter implements TrackExporter {
printWriter.println("</SimpleArrayData>");
}
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("<Placemark>");
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
printWriter.println("<description>" + StringUtils.formatCData(description) + "</description>");
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, location) + "</when></TimeStamp>");
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, time) + "</when></TimeStamp>");
printWriter.println("<styleUrl>#" + KMLTrackExporter.MARKER_STYLE + "</styleUrl>");
writeTypeLocalized(activityType);
printWriter.println("<Point>");
@@ -437,7 +438,7 @@ public class KMLTrackExporter implements TrackExporter {
printWriter.print("<heading>" + heading + "</heading>");
printWriter.print("<tilt>90</tilt>");
printWriter.println("</Camera>");
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, marker.getLocation()) + "</when></TimeStamp>");
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, marker.getTime()) + "</when></TimeStamp>");
printWriter.println("<styleUrl>#" + MARKER_STYLE + "</styleUrl>");
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<Float> bearing = viewLocation.bearingTo(location);
if (bearing.isPresent()) {
return bearing.get();
}
}
return location.getBearing();
@@ -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)) {
@@ -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<Location, Optional<Location>> {
public class AggregatorGPS extends Aggregator<Position, Position> {
public AggregatorGPS(String sensorAddress) {
@@ -14,8 +12,8 @@ public class AggregatorGPS extends Aggregator<Location, Optional<Location>> {
}
@Override
protected void computeValue(Raw<Location> current) {
value = Optional.of(current.value());
protected void computeValue(Raw<Position> current) {
value = current.value();
}
@Override
@@ -25,7 +23,7 @@ public class AggregatorGPS extends Aggregator<Location, Optional<Location>> {
@NonNull
@Override
protected Optional<Location> getNoneValue() {
return Optional.empty();
protected Position getNoneValue() {
return Position.empty();
}
}
@@ -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<AtmosphericPressure>) data);
return;
}
if (value instanceof Location) {
this.gps.add((Raw<Location>) data);
if (value instanceof Position) {
this.gps.add((Raw<Position>) 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) {
@@ -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