Skip to content

Commit

Permalink
Merge pull request #11 from HWJFish/upstream_main
Browse files Browse the repository at this point in the history
Upstream main
  • Loading branch information
HWJFish authored Apr 17, 2024
2 parents f15a4d6 + 7b70ad8 commit 5ec3b34
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 347 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ android {

buildFeatures {
viewBinding true
dataBinding true
buildConfig true
}

Expand Down
4 changes: 1 addition & 3 deletions src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,7 @@ limitations under the License.
<activity
android:name=".ui.aggregatedStatistics.daySpecificStats.DaySpecificActivity"
android:exported="false" />
<activity
android:name=".ui.aggregatedStatistics.daySpecificStats.DaySpecificActivity"
android:exported="false" />

<activity
android:name=".ui.aggregatedStatistics.SeasonStats.RunAndChairStatActivity"
android:exported="false" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.stats.SplitSegment;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
Expand Down Expand Up @@ -313,6 +314,38 @@ public List<Track.Id> getTrackIds() {
public void cleanImport() {
contentProviderUtils.deleteTracks(context, trackIds);
}
/**
* Calculates the total time spent on a chairlift given the current track point and acceleration threshold.
*
* @param currentTrackPoint The current track point on the chairlift.
* @param acceleration The acceleration threshold to determine if the chairlift is moving.
* @return The total time spent on the chairlift in seconds.
*/
public long getTotalTimeOnChairLift(TrackPoint currentTrackPoint, double acceleration) {
// Initialize start and end times with the current track point's time
Instant startTime = currentTrackPoint.getTime(), endTime = currentTrackPoint.getTime();

// Initialize altitude difference
double altitudeDifference = -1;

// Get the last track point in the list
TrackPoint previousTrackPoint = trackPoints.get(trackPoints.size() - 1);

// Iterate through track points in reverse order
for (int i = trackPoints.size() - 2; i > 0; i--) {
TrackPoint selectedTrackPoint = trackPoints.get(i);
double currentAltitudeDifference = previousTrackPoint.getAltitude().toM() - selectedTrackPoint.getAltitude().toM();

// Check if altitude difference is within the acceleration threshold
if (altitudeDifference == -1 || Math.abs(currentAltitudeDifference - altitudeDifference) <= acceleration) {
startTime = selectedTrackPoint.getTime(); // Update start time
altitudeDifference = currentAltitudeDifference; // Update altitude difference
}
}

// Calculate total time spent on chairlift
return endTime.getEpochSecond() - startTime.getEpochSecond() + SplitSegment.calculateWaitingTime(trackPoints);
}

/**
* Finds the start time of the chair lift ride with respect to the provided acceleration threshold.
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,49 @@ public boolean hasSlope() {
return slopePercent_m != null;
}

// Method to calculate the total skiing duration for the current day
// public Duration getTotalSkiingDuration() {
// return getTotalSkiingDuration(LocalDate.now());
// }
//
// // Method to calculate the total skiing duration for a specific date
// public Duration getTotalSkiingDuration(LocalDate date) {
// Duration time = TrackImporter.getTotalSkiingDuration(date);
//
// return TrackImporter.totalSkiingDuration;
// }
//
// // Method to determine if skiing is detected between two track points
// private boolean isSkiingSegment(TrackPoint startPoint, TrackPoint endPoint) {
// // Thresholds to determine skiing activity
// double altitudeChangeThreshold = 10.0; // Meters
// double speedThreshold = 5.0; // Meters per second
// long timeThresholdInSeconds = 50; // Seconds
//
// // Check if altitude change is significant
// double altitudeChange = Math.abs(startPoint.getAltitude().toM() - endPoint.getAltitude().toM());
// if (altitudeChange < altitudeChangeThreshold) {
// return false; // Altitude change not significant, likely not skiing
// }
//
// // Calculate total distance
//// double totalDistance = startPoint.distanceTo(endPoint).toKM();
//
// // Calculate total time (in seconds)
// long totalTimeInSeconds = Duration.between(startPoint.getTime(), endPoint.getTime()).getSeconds();
//
// // Calculate average speed
//// double averageSpeed = totalDistance / totalTimeInSeconds;
//
// // Check if average speed is above the speed threshold
// return totalTimeInSeconds >= timeThresholdInSeconds;
// }
//
// // Method to check if two Instant objects belong to the same LocalDate
// private boolean isSameDate(Instant instant, LocalDate date) {
// return instant.atZone(ZoneId.systemDefault()).toLocalDate().isEqual(date);
// }
//
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public void addTotalAltitudeLoss(float loss_m) {
if (totalAltitudeLoss_m == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,276 +270,3 @@ public String toString() {
'}';
}
}
=======
/*
* Copyright 2009 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.stats;

import androidx.annotation.NonNull;

import java.time.Duration;
import java.util.List;

import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;

/**
* Updater for {@link TrackStatistics}.
* For updating track {@link TrackStatistics} as new {@link TrackPoint}s are added.
* NOTE: Some of the locations represent pause/resume separator.
* NOTE: Has still support for segments (at the moment unused).
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class TrackStatisticsUpdater {

private static final String TAG = TrackStatisticsUpdater.class.getSimpleName();

private final TrackStatistics trackStatistics;

private float averageHeartRateBPM;
private Duration totalHeartRateDuration = Duration.ZERO;

// The current segment's statistics
private final TrackStatistics currentSegment;
// Current segment's last trackPoint
private TrackPoint lastTrackPoint;

public TrackStatisticsUpdater() {
this(new TrackStatistics());
}

/**
* Creates a new{@link TrackStatisticsUpdater} with a {@link TrackStatisticsUpdater} already existed.
*
* @param trackStatistics a {@link TrackStatisticsUpdater}
*/
public TrackStatisticsUpdater(TrackStatistics trackStatistics) {
this.trackStatistics = trackStatistics;
this.currentSegment = new TrackStatistics();

resetAverageHeartRate();
}

public TrackStatisticsUpdater(TrackStatisticsUpdater toCopy) {
this.currentSegment = new TrackStatistics(toCopy.currentSegment);
this.trackStatistics = new TrackStatistics(toCopy.trackStatistics);

this.lastTrackPoint = toCopy.lastTrackPoint;
resetAverageHeartRate();
}

public TrackStatistics getTrackStatistics() {
// Take a snapshot - we don't want anyone messing with our trackStatistics
TrackStatistics stats = new TrackStatistics(trackStatistics);
stats.merge(currentSegment);
return stats;
}

public void addTrackPoints(List<TrackPoint> trackPoints) {
trackPoints.stream().forEachOrdered(this::addTrackPoint);
}

/**
*
*/
public void addTrackPoint(TrackPoint trackPoint) {
if (trackPoint.isSegmentManualStart()) {
reset(trackPoint);
}

if (!currentSegment.isInitialized()) {
currentSegment.setStartTime(trackPoint.getTime());
}

// Always update time
currentSegment.setStopTime(trackPoint.getTime());
currentSegment.setTotalTime(Duration.between(currentSegment.getStartTime(), trackPoint.getTime()));

// Process sensor data: barometer
if (trackPoint.hasAltitudeGain()) {
currentSegment.addTotalAltitudeGain(trackPoint.getAltitudeGain());
}

if (trackPoint.hasAltitudeLoss()) {
currentSegment.addTotalAltitudeLoss(trackPoint.getAltitudeLoss());
}

// this function will always be called for all trackpoints to check if it is waiting for chairlift
// and also modify values for the check according to current trackpoint.
if (isWaitingForChairlift(trackPoint)){
Duration passedDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
currentSegment.setTotalChairliftWaitingTime(currentSegment.getTotalChairliftWaitingTime().plus(passedDuration));
}

//Update absolute (GPS-based) altitude
if (trackPoint.hasAltitude()) {
currentSegment.updateAltitudeExtremities(trackPoint.getAltitude());
}

// Update heart rate
if (trackPoint.hasHeartRate() && lastTrackPoint != null) {
Duration trackPointDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
Duration newTotalDuration = totalHeartRateDuration.plus(trackPointDuration);

averageHeartRateBPM = (totalHeartRateDuration.toMillis() * averageHeartRateBPM + trackPointDuration.toMillis() * trackPoint.getHeartRate().getBPM()) / newTotalDuration.toMillis();
totalHeartRateDuration = newTotalDuration;

currentSegment.setAverageHeartRate(HeartRate.of(averageHeartRateBPM));
}

{
// Update total distance
Distance movingDistance = null;
if (trackPoint.hasSensorDistance()) {
movingDistance = trackPoint.getSensorDistance();
} else if (lastTrackPoint != null
&& lastTrackPoint.hasLocation()
&& trackPoint.hasLocation()) {
// GPS-based distance/speed
movingDistance = trackPoint.distanceToPrevious(lastTrackPoint);
}
if (movingDistance != null) {
currentSegment.setIdle(false);
currentSegment.addTotalDistance(movingDistance);
}

if (!currentSegment.isIdle() && !trackPoint.isSegmentManualStart()) {
if (lastTrackPoint != null) {
currentSegment.addMovingTime(trackPoint, lastTrackPoint);
}
}

if (trackPoint.getType() == TrackPoint.Type.IDLE) {
currentSegment.setIdle(true);
}

if (trackPoint.hasSpeed()) {
updateSpeed(trackPoint);
}

// Update current Slope= Change in Distance / Change in Altitude
if (movingDistance != null) {
updateSlopePercent(trackPoint, movingDistance);
}
}

if (trackPoint.isSegmentManualEnd()) {
reset(trackPoint);
return;
}

lastTrackPoint = trackPoint;
}

/**
* This function can be used to check if current trackpoint is of type waiting for chairlift.
* It returns true if it is waiting at lower end of the track for more than 5 trackpoints.
* else it increments the counter if it is still at lower end of track.
* */
private boolean isWaitingForChairlift(TrackPoint trackPoint) {
// indicates altitude difference allowed in case of small change elevation change while waiting in queue for chairlift
final float minimumAltitudeChangeAllowed = 0.2f;
final float minimumDeviationAllowedFromLowestAltitude = 1f;
final int thresholdTrackpointsForNoMovement=5;

float altitudeGain = trackPoint.hasAltitudeGain()? trackPoint.getAltitudeGain(): 0f;
float altitudeLoss = trackPoint.hasAltitudeLoss()? trackPoint.getAltitudeLoss(): 0f;
float currentAltitudeChange = Math.max(altitudeGain, altitudeLoss);


if (currentAltitudeChange<=minimumAltitudeChangeAllowed
&& trackPoint.getAltitude()!=null
&& Math.abs(currentSegment.getMinAltitude()-trackPoint.getAltitude().toM())<=minimumDeviationAllowedFromLowestAltitude){
currentSegment.incrementEndOfRunCounter();
if (currentSegment.getEndOfRunCounter()>=thresholdTrackpointsForNoMovement){
return true;
}
} else {
currentSegment.resetEndOfRunCounter();
return false;
}

return false;
}

private void reset(TrackPoint trackPoint) {
if (currentSegment.isInitialized()) {
trackStatistics.merge(currentSegment);
}
currentSegment.reset(trackPoint.getTime());

lastTrackPoint = null;
resetAverageHeartRate();
}

private void resetAverageHeartRate() {
averageHeartRateBPM = 0.0f;
totalHeartRateDuration = Duration.ZERO;
}

/**
* Updates a speed reading while assuming the user is moving.
*/
private void updateSpeed(@NonNull TrackPoint trackPoint) {
Speed currentSpeed = trackPoint.getSpeed();
if (currentSpeed.greaterThan(currentSegment.getMaxSpeed())) {
currentSegment.setMaxSpeed(currentSpeed);
}
}

/**
* Updates the slope percent assuming the user has moved
*/
private void updateSlopePercent(@NonNull TrackPoint trackPoint, Distance distanceMoved) {
Float altituteChanged = null;

// absolute (GPS-based) altitude
if (altituteChanged == null && trackPoint.hasAltitude() && lastTrackPoint != null) {
altituteChanged = (float) (trackPoint.getAltitude().toM() - lastTrackPoint.getAltitude().toM());
}

if (altituteChanged == null) {
if (trackPoint.hasAltitudeGain()) {
altituteChanged = trackPoint.getAltitudeGain();
} else if (trackPoint.hasAltitudeLoss()) {
altituteChanged = -trackPoint.getAltitudeLoss();
}
}

if (altituteChanged == null) {
return;
}

// Slope = Change in Distance / Change in Altitude
Float slopePercentChangedBetweenPoints = (float) ((altituteChanged / distanceMoved.toM()) * 100);
Float prevAggregatedSlopePercent = currentSegment.hasSlope() ? currentSegment.getSlopePercent() : 0;
Float aggregatedSlopePercent = prevAggregatedSlopePercent + slopePercentChangedBetweenPoints;
currentSegment.setSlopePercent(aggregatedSlopePercent);
}

@NonNull
@Override
public String toString() {
return "TrackStatisticsUpdater{" +
"trackStatistics=" + trackStatistics +
'}';
}
}
Loading

0 comments on commit 5ec3b34

Please sign in to comment.