diff --git a/app/src/main/java/de/tadris/fitness/data/WorkoutManager.java b/app/src/main/java/de/tadris/fitness/data/WorkoutManager.java index ee559c2..5068caa 100644 --- a/app/src/main/java/de/tadris/fitness/data/WorkoutManager.java +++ b/app/src/main/java/de/tadris/fitness/data/WorkoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Jannis Scheibe + * Copyright (c) 2020 Jannis Scheibe * * This file is part of FitoTrack * @@ -19,118 +19,10 @@ package de.tadris.fitness.data; -import android.content.Context; -import android.hardware.SensorManager; -import android.util.Log; - import java.util.List; -import de.tadris.fitness.Instance; -import de.tadris.fitness.util.CalorieCalculator; - public class WorkoutManager { - public static void insertWorkout(Context context, Workout workout, List samples){ - AppDatabase db= Instance.getInstance(context).db; - - - workout.id= System.currentTimeMillis(); - - // Delete Samples with same time - for(int i= samples.size()-2; i >= 0; i--){ - WorkoutSample sample= samples.get(i); - WorkoutSample lastSample= samples.get(i+1); - if(sample.absoluteTime == lastSample.absoluteTime){ - samples.remove(lastSample); - Log.i("WorkoutManager", "Removed samples at " + sample.absoluteTime + " rel: " + sample.relativeTime + "; " + lastSample.relativeTime); - } - } - - // Calculating values - double length= 0; - for(int i= 1; i < samples.size(); i++){ - double sampleLength= samples.get(i - 1).toLatLong().sphericalDistance(samples.get(i).toLatLong()); - long timeDiff= (samples.get(i).relativeTime - samples.get(i - 1).relativeTime) / 1000; - length+= sampleLength; - samples.get(i).speed= Math.abs(sampleLength / timeDiff); - } - workout.length= (int)length; - workout.avgSpeed= ((double) workout.length) / ((double) workout.duration / 1000); - workout.avgPace= ((double)workout.duration / 1000 / 60) / ((double) workout.length / 1000); - workout.calorie= CalorieCalculator.calculateCalories(workout, Instance.getInstance(context).userPreferences.getUserWeight()); - - // Setting workoutId in the samples - int i= 0; - double topSpeed= 0; - double elevationSum= 0; // Sum of elevation - double pressureSum= 0; // Sum of elevation - for(WorkoutSample sample : samples){ - i++; - sample.id= workout.id + i; - sample.workoutId= workout.id; - elevationSum+= sample.elevation; - pressureSum+= sample.tmpPressure; - if(sample.speed > topSpeed){ - topSpeed= sample.speed; - } - } - - workout.topSpeed= topSpeed; - - // Calculating height data - boolean pressureDataAvailable= samples.get(0).tmpPressure != -1; - double avgElevation= elevationSum / samples.size(); - double avgPressure= pressureSum / samples.size(); - - workout.ascent = 0; - workout.descent = 0; - - for(i= 0; i < samples.size(); i++){ - WorkoutSample sample= samples.get(i); - - if(pressureDataAvailable){ - // Altitude Difference to Average Elevation in meters - float altitude_difference = - SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, sample.tmpPressure) - - SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, (float) avgPressure); - sample.elevation= avgElevation + altitude_difference; - } // Else: use already set GPS elevation in WorkoutSample.elevation - } - - int range= 3; - for(i= 0; i < samples.size(); i++){ - int min= Math.max(i-range, 0); - int max= Math.min(i+range, samples.size()-1); - samples.get(i).tmpElevation= getAverageElevation(samples.subList(min, max)); - } - - for(i= 0; i < samples.size(); i++) { - WorkoutSample sample = samples.get(i); - sample.elevation= sample.tmpElevation; - if(i >= 1){ - WorkoutSample lastSample= samples.get(i-1); - double diff= sample.elevation - lastSample.elevation; - if(diff > 0){ - workout.ascent += diff; - }else{ - workout.descent += Math.abs(diff); - } - } - } - - // Saving workout and samples - db.workoutDao().insertWorkoutAndSamples(workout, samples.toArray(new WorkoutSample[0])); - - } - - public static double getAverageElevation(List samples){ - double sum= 0; - for(WorkoutSample sample : samples){ - sum+= sample.elevation; - } - return sum / samples.size(); - } - public static void roundSpeedValues(List samples){ for(int i= 0; i < samples.size(); i++){ WorkoutSample sample= samples.get(i); diff --git a/app/src/main/java/de/tadris/fitness/recording/WorkoutRecorder.java b/app/src/main/java/de/tadris/fitness/recording/WorkoutRecorder.java index 1346033..fbadbc1 100644 --- a/app/src/main/java/de/tadris/fitness/recording/WorkoutRecorder.java +++ b/app/src/main/java/de/tadris/fitness/recording/WorkoutRecorder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Jannis Scheibe + * Copyright (c) 2020 Jannis Scheibe * * This file is part of FitoTrack * @@ -31,7 +31,6 @@ import java.util.List; import de.tadris.fitness.Instance; import de.tadris.fitness.data.Workout; -import de.tadris.fitness.data.WorkoutManager; import de.tadris.fitness.data.WorkoutSample; import de.tadris.fitness.util.CalorieCalculator; @@ -53,7 +52,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener /** * Time after which the workout is stopped and saved automatically because there is no activity anymore */ - private static final int AUTO_STOP_TIMEOUT= 1000*60*60*20; + private static final int AUTO_STOP_TIMEOUT= 1000*60*60*20; // 20 minutes private Context context; private Workout workout; @@ -65,7 +64,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener private long lastPause= 0; private long lastSampleTime= 0; private double distance= 0; - private boolean hasBegan = false; + private boolean hasBegun = false; private static final double SIGNAL_BAD_THRESHOLD= 20; // In meters private static final int SIGNAL_LOST_THRESHOLD= 10000; // In milliseconds @@ -135,7 +134,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener } catch (InterruptedException e) { e.printStackTrace(); } - }).start(); + }, "WorkoutWatchdog").start(); } private void checkSignalState(){ @@ -193,7 +192,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener } Log.i("Recorder", "Save"); synchronized (samples){ - WorkoutManager.insertWorkout(context, workout, samples); + new WorkoutSaver(context, workout, samples).saveWorkout(); } } @@ -209,6 +208,8 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener if(isActive()){ double distance= 0; if(getSampleCount() > 0){ + // Checks whether the minimum distance to last sample was reached + // and if the time difference to the last sample is too small synchronized (samples){ WorkoutSample lastSample= samples.get(samples.size() - 1); distance= LocationListener.locationToLatLong(location).sphericalDistance(new LatLong(lastSample.lat, lastSample.lon)); @@ -220,38 +221,44 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener } lastSampleTime= System.currentTimeMillis(); if(state == RecordingState.RUNNING && location.getTime() > workout.start){ - if(samples.size() == 2 && !hasBegan){ - lastResume= System.currentTimeMillis(); - workout.start= System.currentTimeMillis(); - lastPause= 0; - time= 0; - pauseTime= 0; - this.distance= 0; - samples.clear(); - - hasBegan = true; // Do not clear a second time + if(samples.size() == 2 && !hasBegun){ + initialClearValues(); + hasBegun = true; // Do not clear a second time } this.distance+= distance; - WorkoutSample sample= new WorkoutSample(); - sample.lat= location.getLatitude(); - sample.lon= location.getLongitude(); - sample.elevation= location.getAltitude(); - sample.speed= location.getSpeed(); - sample.relativeTime= location.getTime() - workout.start - pauseTime; - sample.absoluteTime= location.getTime(); - if(Instance.getInstance(context).pressureAvailable){ - sample.tmpPressure= Instance.getInstance(context).lastPressure; - }else{ - sample.tmpPressure= -1; - } - synchronized (samples){ - samples.add(sample); - } - + addToSamples(location); } } } + private void addToSamples(Location location){ + WorkoutSample sample= new WorkoutSample(); + sample.lat= location.getLatitude(); + sample.lon= location.getLongitude(); + sample.elevation= location.getAltitude(); + sample.speed= location.getSpeed(); + sample.relativeTime= location.getTime() - workout.start - pauseTime; + sample.absoluteTime= location.getTime(); + if(Instance.getInstance(context).pressureAvailable){ + sample.tmpPressure= Instance.getInstance(context).lastPressure; + }else{ + sample.tmpPressure= -1; + } + synchronized (samples){ + samples.add(sample); + } + } + + private void initialClearValues(){ + lastResume= System.currentTimeMillis(); + workout.start= System.currentTimeMillis(); + lastPause= 0; + time= 0; + pauseTime= 0; + this.distance= 0; + samples.clear(); + } + /** * Returns the distance in meters */ diff --git a/app/src/main/java/de/tadris/fitness/recording/WorkoutSaver.java b/app/src/main/java/de/tadris/fitness/recording/WorkoutSaver.java new file mode 100644 index 0000000..2bf4866 --- /dev/null +++ b/app/src/main/java/de/tadris/fitness/recording/WorkoutSaver.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2020 Jannis Scheibe + * + * This file is part of FitoTrack + * + * FitoTrack is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * FitoTrack is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.tadris.fitness.recording; + +import android.content.Context; +import android.hardware.SensorManager; +import android.util.Log; + +import java.util.List; + +import de.tadris.fitness.Instance; +import de.tadris.fitness.data.AppDatabase; +import de.tadris.fitness.data.Workout; +import de.tadris.fitness.data.WorkoutSample; +import de.tadris.fitness.util.CalorieCalculator; + +public class WorkoutSaver { + + private Context context; + private Workout workout; + private List samples; + private AppDatabase db; + + public WorkoutSaver(Context context, Workout workout, List samples) { + this.context = context; + this.workout = workout; + this.samples = samples; + db= Instance.getInstance(context).db; + } + + public void saveWorkout(){ + setIds(); + clearSamplesWithSameTime(); + setSimpleValues(); + setTopSpeed(); + + setRealElevation(); + setAscentAndDescent(); + + storeInDatabase(); + } + + private void setIds(){ + workout.id= System.currentTimeMillis(); + int i= 0; + for(WorkoutSample sample : samples) { + i++; + sample.id = workout.id + i; + sample.workoutId = workout.id; + } + } + + private void clearSamplesWithSameTime(){ + for(int i= samples.size()-2; i >= 0; i--){ + WorkoutSample sample= samples.get(i); + WorkoutSample lastSample= samples.get(i+1); + if(sample.absoluteTime == lastSample.absoluteTime){ + samples.remove(lastSample); + Log.i("WorkoutManager", "Removed samples at " + sample.absoluteTime + " rel: " + sample.relativeTime + "; " + lastSample.relativeTime); + } + } + } + + private void setSimpleValues(){ + double length= 0; + for(int i= 1; i < samples.size(); i++){ + double sampleLength= samples.get(i - 1).toLatLong().sphericalDistance(samples.get(i).toLatLong()); + long timeDiff= (samples.get(i).relativeTime - samples.get(i - 1).relativeTime) / 1000; + length+= sampleLength; + samples.get(i).speed= Math.abs(sampleLength / timeDiff); + } + workout.length= (int)length; + workout.avgSpeed= ((double) workout.length) / ((double) workout.duration / 1000); + workout.avgPace= ((double)workout.duration / 1000 / 60) / ((double) workout.length / 1000); + workout.calorie= CalorieCalculator.calculateCalories(workout, Instance.getInstance(context).userPreferences.getUserWeight()); + } + + private void setTopSpeed(){ + double topSpeed= 0; + for(WorkoutSample sample : samples){ + if(sample.speed > topSpeed){ + topSpeed= sample.speed; + } + } + workout.topSpeed= topSpeed; + } + + private void setRealElevation(){ + boolean pressureDataAvailable= samples.get(0).tmpPressure != -1; + + if(!pressureDataAvailable){ + // Because pressure data isn't available we just use the use GPS elevation + // in WorkoutSample.elevation which was already set + return; + } + + double avgElevation= getAverageElevation(); + double avgPressure= getAveragePressure(); + + for(int i= 0; i < samples.size(); i++){ + WorkoutSample sample= samples.get(i); + + // Altitude Difference to Average Elevation in meters + float altitude_difference = + SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, sample.tmpPressure) - + SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, (float) avgPressure); + sample.elevation= avgElevation + altitude_difference; + } + } + + private double getAverageElevation(){ + return getAverageElevation(samples); + } + + private double getAverageElevation(List samples){ + double elevationSum= 0; // Sum of elevation + for(WorkoutSample sample : samples){ + elevationSum+= sample.elevation; + } + + return elevationSum / samples.size(); + } + + private double getAveragePressure(){ + double pressureSum= 0; + for(WorkoutSample sample : samples){ + pressureSum+= sample.tmpPressure; + } + return pressureSum / samples.size(); + } + + private void setAscentAndDescent(){ + workout.ascent = 0; + workout.descent = 0; + + // First calculate a floating average to eliminate pressure noise to influence our ascent/descent + int range= 3; + for(int i= 0; i < samples.size(); i++){ + int min= Math.max(i-range, 0); + int max= Math.min(i+range, samples.size()-1); + samples.get(i).tmpElevation= getAverageElevation(samples.subList(min, max)); + } + + // Now sum up the ascent/descent + for(int i= 0; i < samples.size(); i++) { + WorkoutSample sample = samples.get(i); + sample.elevation= sample.tmpElevation; + if(i >= 1){ + WorkoutSample lastSample= samples.get(i-1); + double diff= sample.elevation - lastSample.elevation; + if(diff > 0){ + // If this sample is higher than the last one, add difference to ascent + workout.ascent += diff; + }else{ + // If this sample is lower than the last one, add difference to descent + workout.descent += Math.abs(diff); + } + } + } + + } + + private void storeInDatabase(){ + db.workoutDao().insertWorkoutAndSamples(workout, samples.toArray(new WorkoutSample[0])); + } +}