Position: use for Marker.

This commit is contained in:
Dennis Guse
2025-02-21 23:14:26 +01:00
parent ffcee94a07
commit 9a37591651
7 changed files with 89 additions and 126 deletions
@@ -88,9 +88,9 @@ public class TestDataUtil {
stats.setTotalDistance(Distance.of(0));
stats.setTotalTime(Duration.ofMillis(0));
List<Marker> markers = List.of(
new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, trackPoints.get(1), null),
new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, trackPoints.get(4), null),
new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, trackPoints.get(5), null)
new Marker(trackId, trackPoints.get(1), "Marker 1", "Marker description 1", "Marker category 3", "", null),
new Marker(trackId, trackPoints.get(4), "Marker 2", "Marker description 2", "Marker category 3", "", null),
new Marker(trackId, trackPoints.get(5), "Marker 3", "Marker description 3", "Marker category 3", "", null)
);
return new TrackData(track, trackPoints, markers);
@@ -151,7 +151,7 @@ public class TestDataUtil {
stats.setTotalDistance(Distance.of(0));
stats.setTotalTime(Duration.ofMillis(0));
return new Marker("Marker name", "Marker description", "Marker category", "", trackId, trackPoint, photoUri);
return new Marker(trackId, trackPoint, "Marker name", "Marker description", "Marker category", "", photoUri);
}
public static List<TrackPoint> getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
@@ -143,7 +143,7 @@ public class ExportImportTest {
Distance sensorDistance = Distance.of(10); // recording distance interval
sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3, 14, 10, 13, 15, 10, 1f);
contentProviderUtils.insertMarker(new Marker("Marker 1", "Marker 1 desc", "Marker 1 category", null, trackId, service.getLastStoredTrackPointWithLocation(), null));
contentProviderUtils.insertMarker(new Marker(trackId, service.getLastStoredTrackPointWithLocation(), "Marker 1", "Marker 1 desc", "Marker 1 category", null, null));
// A sensor-only TrackPoint
trackPointCreator.setClock("2020-02-02T02:02:04Z");
@@ -158,7 +158,7 @@ public class ExportImportTest {
mockSensorData(trackPointCreator, 5f, Distance.of(2), 69f, 3f, 50f, null); // Distance will be added to next TrackPoint
sendLocation(trackPointCreator, "2020-02-02T02:02:17Z", 3, 14.001, 10, 13, 15, 10, 0f);
contentProviderUtils.insertMarker(new Marker("Marker 2", "Marker 2 desc", "Marker 2 category", null, trackId, service.getLastStoredTrackPointWithLocation(), null));
contentProviderUtils.insertMarker(new Marker(trackId, service.getLastStoredTrackPointWithLocation(), "Marker 2", "Marker 2 desc", "Marker 2 category", null, null));
trackPointCreator.setClock("2020-02-02T02:02:18Z");
trackPointCreator.getSensorManager().sensorDataSet = new SensorDataSet(trackPointCreator);
@@ -543,9 +543,9 @@ public class ExportImportTest {
assertEquals(marker.getName(), importMarker.getName());
assertFalse(importMarker.hasPhoto());
assertEquals(marker.getLocation().getLatitude(), importMarker.getLocation().getLatitude(), 0.001);
assertEquals(marker.getLocation().getLongitude(), importMarker.getLocation().getLongitude(), 0.001);
assertEquals(marker.getLocation().getAltitude(), importMarker.getLocation().getAltitude(), 0.1);
assertEquals(marker.getPosition().latitude(), importMarker.getPosition().latitude(), 0.001);
assertEquals(marker.getPosition().longitude(), importMarker.getPosition().longitude(), 0.001);
assertEquals(marker.getPosition().altitude().toM(), importMarker.getPosition().altitude().toM(), 0.1);
}
}
@@ -21,7 +21,6 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import android.text.TextUtils;
@@ -46,6 +45,7 @@ import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
@@ -385,23 +385,39 @@ public class ContentProviderUtils {
int bearingIndex = cursor.getColumnIndexOrThrow(MarkerColumns.BEARING);
int photoUrlIndex = cursor.getColumnIndexOrThrow(MarkerColumns.PHOTOURL);
Track.Id trackId = new Track.Id(cursor.getLong(trackIdIndex));
Marker marker = new Marker(trackId, Instant.ofEpochMilli(cursor.getLong(timeIndex)));
Double latitude = null;
Double longitude = null;
Altitude.WGS84 altitude = null;
Distance horizontalAccuracy = null;
Float bearing = null;
if (!cursor.isNull(longitudeIndex) && !cursor.isNull(latitudeIndex)) {
marker.setLongitude(((double) cursor.getInt(longitudeIndex)) / 1E6);
marker.setLatitude(((double) cursor.getInt(latitudeIndex)) / 1E6);
latitude = (((double) cursor.getInt(latitudeIndex)) / 1E6);
longitude = (((double) cursor.getInt(longitudeIndex)) / 1E6);
}
if (!cursor.isNull(altitudeIndex)) {
marker.setAltitude(Altitude.WGS84.of(cursor.getFloat(altitudeIndex)));
altitude = Altitude.WGS84.of(cursor.getFloat(altitudeIndex));
}
if (!cursor.isNull(accuracyIndex)) {
marker.setAccuracy(Distance.of(cursor.getFloat(accuracyIndex)));
horizontalAccuracy = Distance.of(cursor.getFloat(accuracyIndex));
}
if (!cursor.isNull(bearingIndex)) {
marker.setBearing(cursor.getFloat(bearingIndex));
bearing = cursor.getFloat(bearingIndex);
}
Position position = new Position(
Instant.ofEpochMilli(cursor.getLong(timeIndex)),
latitude,
longitude,
horizontalAccuracy,
altitude,
null,
bearing,
null);
Track.Id trackId = new Track.Id(cursor.getLong(trackIdIndex));
Marker marker = new Marker(trackId, position);
if (!cursor.isNull(idIndex)) {
marker.setId(new Marker.Id(cursor.getLong(idIndex)));
}
@@ -420,6 +436,8 @@ public class ContentProviderUtils {
if (!cursor.isNull(photoUrlIndex)) {
marker.setPhotoUrl(Uri.parse(cursor.getString(photoUrlIndex)));
}
return marker;
}
@@ -532,8 +550,8 @@ public class ContentProviderUtils {
values.put(MarkerColumns.CATEGORY, marker.getCategory());
values.put(MarkerColumns.ICON, marker.getIcon());
values.put(MarkerColumns.TRACKID, marker.getTrackId().id());
values.put(MarkerColumns.LONGITUDE, (int) (marker.getLongitude() * 1E6));
values.put(MarkerColumns.LATITUDE, (int) (marker.getLatitude() * 1E6));
values.put(MarkerColumns.LONGITUDE, (int) (marker.getPosition().longitude() * 1E6));
values.put(MarkerColumns.LATITUDE, (int) (marker.getPosition().latitude() * 1E6));
values.put(MarkerColumns.TIME, marker.getTime().toEpochMilli());
if (marker.hasAltitude()) {
values.put(MarkerColumns.ALTITUDE, marker.getAltitude().toM());
@@ -692,15 +710,11 @@ public class ContentProviderUtils {
/**
* Gets the trackPoint id for a location.
*
* @param trackId the track id
* @param location the location
* @return trackPoint id if the location is in the track. -1L otherwise.
*/
@Deprecated
public TrackPoint.Id getTrackPointId(Track.Id trackId, Location location) {
public TrackPoint.Id getTrackPointId(Track.Id trackId, Position position) {
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId.id()), Long.toString(location.getTime())};
String[] selectionArgs = new String[]{Long.toString(trackId.id()), Long.toString(position.time().toEpochMilli())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
@@ -16,7 +16,6 @@
package de.dennisguse.opentracks.data.models;
import android.location.Location;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,9 +24,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.time.Instant;
import java.util.Objects;
/**
* NOTE: A marker is indirectly (via it's location) assigned to one {@link TrackPoint} with trackPoint.hasLocation() == true.
* NOTE: A marker is indirectly (via it's {@link Position}) assigned to one {@link TrackPoint} via position.time.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
@@ -42,34 +42,27 @@ public final class Marker {
private String icon = "";
private Track.Id trackId;
private final Instant time;
private Double latitude;
private Double longitude;
@Deprecated //Not needed
private Distance accuracy;
private Altitude altitude;
private Float bearing;
//Some data might not be used.
private final Position position;
private Uri photoUrl = null;
public Marker(@Nullable Track.Id trackId, Instant time) {
this.trackId = trackId;
this.time = time;
}
public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) {
this.trackId = trackId;
this.time = trackPoint.getTime();
if (!trackPoint.hasLocation())
throw new RuntimeException("Marker requires a trackpoint with a location.");
setTrackPoint(trackPoint);
this.position = trackPoint.getPosition();
}
@Deprecated
public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, @NonNull TrackPoint trackPoint, Uri photoUrl) {
public Marker(@Nullable Track.Id trackId, @NonNull Position position) {
this.trackId = trackId;
Objects.requireNonNull(position);
this.position = position;
}
public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint, String name, String description, String category, String icon, Uri photoUrl) {
this(trackId, trackPoint);
this.name = name;
this.description = description;
@@ -78,17 +71,8 @@ public final class Marker {
this.photoUrl = photoUrl;
}
//TODO Is somehow part of the initialization process. Can we at least limit visibility?
public void setTrackPoint(TrackPoint trackPoint) {
this.latitude = trackPoint.getLatitude();
this.longitude = trackPoint.getLongitude();
if (trackPoint.hasHorizontalAccuracy()) this.accuracy = trackPoint.getHorizontalAccuracy();
if (trackPoint.hasAltitude()) this.altitude = trackPoint.getAltitude();
if (trackPoint.hasBearing()) this.bearing = trackPoint.getBearing();
}
/**
* May be null if the it was not loaded from the database.
* May be null if the Marker was not loaded from the database.
*/
@Nullable
public Id getId() {
@@ -100,7 +84,7 @@ public final class Marker {
}
public Instant getTime() {
return time;
return position.time();
}
public String getName() {
@@ -144,80 +128,40 @@ public final class Marker {
this.trackId = trackId;
}
public boolean hasLocation() {
return latitude != null || longitude != null;
}
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 (hasAccuracy()) {
location.setAccuracy((float) accuracy.toM());
}
if (hasAltitude()) {
location.setAltitude(altitude.toM());
}
return location;
public Position getPosition() {
return position;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
return position.latitude();
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
return position.longitude();
}
public boolean hasAccuracy() {
return accuracy != null;
return position.hasHorizontalAccuracy();
}
public Distance getAccuracy() {
return accuracy;
}
public void setAccuracy(Distance accuracy) {
this.accuracy = accuracy;
return position.horizontalAccuracy();
}
public boolean hasAltitude() {
return altitude != null;
return position.hasAltitude();
}
public Altitude getAltitude() {
return altitude;
}
public void setAltitude(Altitude altitude) {
this.altitude = altitude;
return position.altitude();
}
public boolean hasBearing() {
return bearing != null;
return position.hasBearing();
}
public Float getBearing() {
return bearing;
}
public void setBearing(float bearing) {
this.bearing = bearing;
return position.bearing();
}
public Uri getPhotoUrl() {
@@ -353,6 +353,13 @@ public class TrackPoint {
return hasHorizontalAccuracy() && horizontalAccuracy.lessThan(thresholdHorizontalAccuracy);
}
public Optional<Float> bearingTo(@NonNull Position dest) {
if (!dest.hasLocation() || !hasLocation()) {
return Optional.empty();
}
return bearingTo(dest.toLocation());
}
public Optional<Float> bearingTo(@NonNull TrackPoint dest) {
if (!dest.hasLocation() || !hasLocation()) {
return Optional.empty();
@@ -18,10 +18,10 @@ package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.io.OutputStream;
@@ -40,6 +40,7 @@ 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.Position;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
@@ -272,10 +273,10 @@ public class KMLTrackExporter implements TrackExporter {
private void writeMarker(Marker marker, ZoneOffset zoneOffset) {
boolean existsPhoto = MarkerUtils.buildInternalPhotoFile(context, marker.getTrackId(), marker.getPhotoUrl()) != null;
if (marker.hasPhoto() && exportPhotos && existsPhoto) {
float heading = getHeading(marker.getTrackId(), marker.getLocation());
float heading = getHeading(marker.getTrackId(), marker.getPosition());
writePhotoOverlay(marker, heading, zoneOffset);
} else {
writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getLocation(), marker.getTime(), zoneOffset);
writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getPosition(), marker.getTime(), zoneOffset);
}
}
@@ -374,7 +375,7 @@ public class KMLTrackExporter implements TrackExporter {
trackpointTypeList.add(trackPoint.getType());
if (trackPoint.hasLocation()) {
printWriter.println("<coord>" + getCoordinates(trackPoint.getLocation(), " ") + "</coord>");
printWriter.println("<coord>" + getCoordinates(trackPoint.getPosition(), " ") + "</coord>");
} else {
printWriter.println("<coord/>");
}
@@ -412,8 +413,8 @@ public class KMLTrackExporter implements TrackExporter {
printWriter.println("</SimpleArrayData>");
}
private void writePlacemark(String name, String activityType, String description, Location location, Instant time, ZoneOffset zoneOffset) {
if (location != null) {
private void writePlacemark(String name, String activityType, String description, @Nullable Position position, Instant time, ZoneOffset zoneOffset) {
if (position != null) {
printWriter.println("<Placemark>");
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
printWriter.println("<description>" + StringUtils.formatCData(description) + "</description>");
@@ -421,7 +422,7 @@ public class KMLTrackExporter implements TrackExporter {
printWriter.println("<styleUrl>#" + KMLTrackExporter.MARKER_STYLE + "</styleUrl>");
writeTypeLocalized(activityType);
printWriter.println("<Point>");
printWriter.println("<coordinates>" + getCoordinates(location, ",") + "</coordinates>");
printWriter.println("<coordinates>" + getCoordinates(position, ",") + "</coordinates>");
printWriter.println("</Point>");
printWriter.println("</Placemark>");
}
@@ -454,7 +455,7 @@ public class KMLTrackExporter implements TrackExporter {
printWriter.print("<topFov>45</topFov>");
printWriter.println("</ViewVolume>");
printWriter.println("<Point>");
printWriter.println("<coordinates>" + getCoordinates(marker.getLocation(), ",") + "</coordinates>");
printWriter.println("<coordinates>" + getCoordinates(marker.getPosition(), ",") + "</coordinates>");
printWriter.println("</Point>");
printWriter.println("</PhotoOverlay>");
}
@@ -469,30 +470,27 @@ public class KMLTrackExporter implements TrackExporter {
/**
* TODO: check if this is a useful feature (likely not).
* Gets the heading to a location.
*
* @param trackId the track id containing the location
* @param location the location
*/
private float getHeading(Track.Id trackId, Location location) {
TrackPoint.Id trackPointId = contentProviderUtils.getTrackPointId(trackId, location);
private float getHeading(Track.Id trackId, Position position) {
TrackPoint.Id trackPointId = contentProviderUtils.getTrackPointId(trackId, position);
if (trackPointId == null) {
return location.getBearing();
return position.bearing();
}
TrackPoint viewLocation = contentProviderUtils.getLastValidTrackPoint(trackId);
if (viewLocation != null) {
Optional<Float> bearing = viewLocation.bearingTo(location);
Optional<Float> bearing = viewLocation.bearingTo(position);
if (bearing.isPresent()) {
return bearing.get();
}
}
return location.getBearing();
return position.bearing();
}
private static String getCoordinates(Location location, String separator) {
String result = location.getLongitude() + separator + location.getLatitude();
if (location.hasAltitude()) {
result += separator + location.getAltitude();
private static String getCoordinates(Position position, String separator) {
String result = position.longitude() + separator + position.latitude();
if (position.hasAltitude()) {
result += separator + position.altitude().toM();
}
return result;
}
@@ -143,7 +143,7 @@ public class MarkerEditViewModel extends AndroidViewModel {
String name = getApplication().getString(R.string.marker_name_format, nextMarkerNumber + 1);
String icon = getApplication().getString(R.string.marker_icon_url);
Marker marker = new Marker(name, "", "", icon, trackId, trackPoint, null);
Marker marker = new Marker(trackId, trackPoint, name, "", "", icon, null);
if (markerData == null) {
markerData = new MutableLiveData<>();