/* * Copyright 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package de.dennisguse.opentracks.data.models; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.time.Instant; /** * Sensor and/or location information for a specific point in time. *
* Time is created using the {@link de.dennisguse.opentracks.services.handlers.MonotonicClock}, because system time jump backwards.
* GPS time is ignored as for non-GPS events, we cannot create GPS-based timestamps.
*/
public record TrackPoint(
@Nullable
TrackPoint.Id id,
@NonNull
TrackPoint.Type type,
//Requires: position.time must be non-null
@NonNull
Position position,
Distance sensorDistance,
HeartRate heartRate,
Temperature temperature,
Cadence cadence,
Power power,
AltitudeGainLoss altitudeGainLoss
) {
public TrackPoint(Type type, Position position) {
this(
null,
type,
position,
null,
null,
null,
null,
null,
null
);
}
@VisibleForTesting
public TrackPoint(Type type, Instant now) {
this(
null,
type,
Position.of(now),
null,
null,
null,
null,
null,
null
);
}
public static TrackPoint createSegmentStartManualWithTime(Instant time) {
return new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, time);
}
public static TrackPoint with(TrackPoint trackPoint, Altitude.EGM2008 correctAltitude) {
return new TrackPoint(
trackPoint.id,
trackPoint.type,
new Position(
trackPoint.position.time(),
trackPoint.position.latitude(),
trackPoint.position.longitude(),
trackPoint.position.horizontalAccuracy(),
correctAltitude,
trackPoint.position.verticalAccuracy(),
trackPoint.position.bearing(),
trackPoint.position.speed()
),
trackPoint.sensorDistance,
trackPoint.heartRate,
null,
trackPoint.cadence,
trackPoint.power,
trackPoint.altitudeGainLoss
);
}
public TrackPoint with(Type newType) {
return new TrackPoint(
id,
newType,
position,
sensorDistance,
heartRate,
null,
cadence,
power,
altitudeGainLoss
);
}
public TrackPoint with(Position newPosition) {
return new TrackPoint(
id,
type,
newPosition,
sensorDistance,
heartRate,
null,
cadence,
power,
altitudeGainLoss
);
}
@NonNull
public Instant getTime() {
return position.time();
}
public TrackPoint minusCumulativeSensorData(@NonNull TrackPoint lastTrackPoint) {
Distance newSensorDistance = sensorDistance;
if (sensorDistance != null && lastTrackPoint.sensorDistance != null) {
newSensorDistance = sensorDistance.minus(lastTrackPoint.sensorDistance);
}
AltitudeGainLoss newAltitudeGainLoss = altitudeGainLoss;
if (altitudeGainLoss != null && lastTrackPoint.altitudeGainLoss() != null) {
newAltitudeGainLoss = new AltitudeGainLoss(
altitudeGainLoss.gain_m() - lastTrackPoint.altitudeGainLoss.gain_m(),
altitudeGainLoss.loss_m() - lastTrackPoint.altitudeGainLoss.loss_m());
}
return new TrackPoint(
id,
type,
position,
newSensorDistance,
heartRate,
null,
cadence,
power,
newAltitudeGainLoss
);
}
@NonNull
public Distance distanceToPrevious(@NonNull TrackPoint previous) {
if (sensorDistance() != null) {
return sensorDistance();
}
return distanceToPreviousFromLocation(previous);
}
@NonNull
public Distance distanceToPreviousFromLocation(@NonNull TrackPoint previous) {
if (!position.hasLocation() || !previous.position().hasLocation()) {
throw new RuntimeException("Cannot compute distance.");
}
return Distance.of(position().toLocation().distanceTo(previous.position().toLocation()));
}
public boolean isIdleTriggered() {
return type == TrackPoint.Type.IDLE;
}
public boolean isSegmentManualStart() {
return type == TrackPoint.Type.SEGMENT_START_MANUAL;
}
public boolean isSegmentManualEnd() {
return type == TrackPoint.Type.SEGMENT_END_MANUAL;
}
public enum Type {
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
SEGMENT_START_AUTOMATIC(-1), //Start of a segment due to too much distance from previous TrackPoint
TRACKPOINT(0), //Was created due to sensor data (may contain GPS or other BLE data)
// Was used to distinguish the source (i.e., GPS vs BLE sensor), but this was too complicated. Everything is now a TRACKPOINT again.
@Deprecated
SENSORPOINT(2),
IDLE(3), //Device became idle
SEGMENT_END_MANUAL(1); //End of a segment
public final int type_db;
Type(int type_db) {
this.type_db = type_db;
}
@NonNull
@Override
public String toString() {
return name() + "(" + type_db + ")";
}
public static Type getById(int id) {
for (Type e : values()) {
if (e.type_db == id) return e;
}
throw new RuntimeException("unknown id: " + id);
}
}
public record Id(long id) implements Parcelable {
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(id);
}
public static final Creator