mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-09 04:43:06 +02:00
Merge heads
This commit is contained in:
@@ -57,7 +57,12 @@ public class MyTracksProvider extends ContentProvider {
|
||||
static class DatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public DatabaseHelper(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
this(context, DATABASE_NAME);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public DatabaseHelper(Context context, String databaseName) {
|
||||
super(context, databaseName, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,7 +111,8 @@ public class MyTracksProvider extends ContentProvider {
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
private enum UrlType {
|
||||
@VisibleForTesting
|
||||
enum UrlType {
|
||||
TRACKPOINTS, TRACKPOINTS_ID, TRACKS, TRACKS_ID, WAYPOINTS, WAYPOINTS_ID
|
||||
}
|
||||
|
||||
@@ -139,7 +145,8 @@ public class MyTracksProvider extends ContentProvider {
|
||||
* @param context context to creates database
|
||||
* @return true means run successfully
|
||||
*/
|
||||
public boolean onCreate(Context context) {
|
||||
@VisibleForTesting
|
||||
boolean onCreate(Context context) {
|
||||
if (!canAccess()) {
|
||||
return false;
|
||||
}
|
||||
@@ -457,14 +464,4 @@ public class MyTracksProvider extends ContentProvider {
|
||||
}
|
||||
throw new SQLException("Failed to insert a waypoint " + url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the db.
|
||||
*
|
||||
* @return the SQLiteDatabase object
|
||||
*/
|
||||
@VisibleForTesting
|
||||
SQLiteDatabase getDb() {
|
||||
return db;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,7 +306,7 @@ public class GpxImporter extends DefaultHandler {
|
||||
tripStatisticsBuilder = new TripStatisticsBuilder(0);
|
||||
tripStatisticsBuilder.pauseAt(0);
|
||||
}
|
||||
track.setTripStatistics(tripStatisticsBuilder.getStatistics());
|
||||
track.setTripStatistics(tripStatisticsBuilder.getTripStatistics());
|
||||
track.setNumberOfPoints(numberOfLocations);
|
||||
myTracksProviderUtils.updateTrack(track);
|
||||
tracksIds.add(track.getId());
|
||||
|
||||
@@ -169,6 +169,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
||||
public void writeEndTrack(Location lastPoint) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</Lap>");
|
||||
printWriter.println("<Notes>" + StringUtils.formatCData(track.getDescription()) + "</Notes>");
|
||||
printWriter.println("<Creator xsi:type=\"Device_t\">");
|
||||
printWriter.println("<Name>"
|
||||
+ StringUtils.formatCData(context.getString(R.string.send_google_by_my_tracks, "", ""))
|
||||
|
||||
+2
-2
@@ -253,7 +253,7 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
|
||||
elevationBuffer.setNext(metricUnits ? location.getAltitude()
|
||||
: location.getAltitude() * UnitConversions.M_TO_FT);
|
||||
if (i % elevationSamplingFrequency == 0) {
|
||||
distances.add(tripStatisticsBuilder.getStatistics().getTotalDistance());
|
||||
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
|
||||
elevations.add(elevationBuffer.getAverage());
|
||||
}
|
||||
lastLocation = location;
|
||||
@@ -279,7 +279,7 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
|
||||
|
||||
// Create an end marker
|
||||
if (lastLocation != null) {
|
||||
distances.add(tripStatisticsBuilder.getStatistics().getTotalDistance());
|
||||
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
|
||||
elevations.add(elevationBuffer.getAverage());
|
||||
DescriptionGenerator descriptionGenerator = new DescriptionGeneratorImpl(context);
|
||||
track.setDescription(
|
||||
|
||||
@@ -297,7 +297,7 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
|
||||
elevationBuffer.setNext(metricUnits ? location.getAltitude()
|
||||
: location.getAltitude() * UnitConversions.M_TO_FT);
|
||||
if (i % elevationSamplingFrequency == 0) {
|
||||
distances.add(tripStatisticsBuilder.getStatistics().getTotalDistance());
|
||||
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
|
||||
elevations.add(elevationBuffer.getAverage());
|
||||
}
|
||||
lastLocation = location;
|
||||
@@ -323,7 +323,7 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
|
||||
|
||||
// Create an end marker
|
||||
if (lastLocation != null) {
|
||||
distances.add(tripStatisticsBuilder.getStatistics().getTotalDistance());
|
||||
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
|
||||
elevations.add(elevationBuffer.getAverage());
|
||||
|
||||
track.setDescription(getTrackDescription(track, distances, elevations));
|
||||
|
||||
+12
-19
@@ -19,41 +19,34 @@ package com.google.android.apps.mytracks.services;
|
||||
/**
|
||||
* This is a simple location listener policy that will always dictate the same
|
||||
* polling interval.
|
||||
*
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class AbsoluteLocationListenerPolicy implements LocationListenerPolicy {
|
||||
|
||||
/**
|
||||
* The interval to request for gps signal.
|
||||
*/
|
||||
private final long interval;
|
||||
|
||||
/**
|
||||
* @param interval The interval to request for gps signal
|
||||
* Constructor.
|
||||
*
|
||||
* @param interval the interval to request for gps signal
|
||||
*/
|
||||
public AbsoluteLocationListenerPolicy(final long interval) {
|
||||
public AbsoluteLocationListenerPolicy(long interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The interval given in the constructor
|
||||
*/
|
||||
@Override
|
||||
public long getDesiredPollingInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards the idle time.
|
||||
*/
|
||||
public void updateIdleTime(long idleTime) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum distance between updates.
|
||||
* Get all updates to properly measure moving time.
|
||||
*/
|
||||
@Override
|
||||
public int getMinDistance() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateIdleTime(long idleTime) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
+30
-39
@@ -16,67 +16,58 @@
|
||||
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
|
||||
/**
|
||||
* A LocationListenerPolicy that will change based on how long the user has been
|
||||
* stationary.
|
||||
*
|
||||
* This policy will dictate a policy based on a min, max and idle time.
|
||||
* The policy will dictate an interval bounded by min and max whic is half of
|
||||
* the idle time.
|
||||
*
|
||||
* A {@link LocationListenerPolicy} that will change based on how long the user
|
||||
* has been stationary. This policy will dictate a policy based on a min, max
|
||||
* and idle time. The policy will dictate an interval bounded by min and max,
|
||||
* and is half of the idle time.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class AdaptiveLocationListenerPolicy implements LocationListenerPolicy {
|
||||
|
||||
/**
|
||||
* Smallest interval this policy will dictate, in milliseconds.
|
||||
*/
|
||||
private final long minInterval;
|
||||
|
||||
/**
|
||||
* Largest interval this policy will dictate, in milliseconds.
|
||||
*/
|
||||
private final long maxInterval;
|
||||
|
||||
private final int minDistance;
|
||||
|
||||
/**
|
||||
* The time the user has been at the current location, in milliseconds.
|
||||
*/
|
||||
|
||||
// The time the user has been idle at the current location, in milliseconds.
|
||||
private long idleTime;
|
||||
|
||||
/**
|
||||
* Creates a policy that will be bounded by the given min and max.
|
||||
*
|
||||
* @param min Smallest interval this policy will dictate, in milliseconds
|
||||
* @param max Largest interval this policy will dictate, in milliseconds
|
||||
* Creates a policy that will be bounded by the given minInterval and
|
||||
* maxInterval.
|
||||
*
|
||||
* @param minInterval the smallest interval this policy will dictate, in
|
||||
* milliseconds
|
||||
* @param maxInterval the largest interval this policy will dictate, in
|
||||
* milliseconds
|
||||
* @param minDistance the minimum distance in meters
|
||||
*/
|
||||
public AdaptiveLocationListenerPolicy(long min, long max, int minDistance) {
|
||||
this.minInterval = min;
|
||||
this.maxInterval = max;
|
||||
public AdaptiveLocationListenerPolicy(long minInterval, long maxInterval, int minDistance) {
|
||||
this.minInterval = minInterval;
|
||||
this.maxInterval = maxInterval;
|
||||
this.minDistance = minDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An interval bounded by min and max which is half of the idle time
|
||||
/*
|
||||
* Returns an interval half of the idle time, but bounded by minInteval and
|
||||
* maxInterval.
|
||||
*/
|
||||
@Override
|
||||
public long getDesiredPollingInterval() {
|
||||
long desiredInterval = idleTime / 2;
|
||||
// Round to avoid setting the interval too often.
|
||||
// Round to second to avoid setting the interval too often
|
||||
desiredInterval = (desiredInterval / 1000) * 1000;
|
||||
return Math.max(Math.min(maxInterval, desiredInterval),
|
||||
minInterval);
|
||||
return Math.max(Math.min(maxInterval, desiredInterval), minInterval);
|
||||
}
|
||||
|
||||
public void updateIdleTime(long newIdleTime) {
|
||||
this.idleTime = newIdleTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum distance between updates.
|
||||
*/
|
||||
@Override
|
||||
public int getMinDistance() {
|
||||
return minDistance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateIdleTime(long newIdleTime) {
|
||||
idleTime = newIdleTime;
|
||||
}
|
||||
}
|
||||
|
||||
+11
-12
@@ -17,19 +17,18 @@
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
/**
|
||||
* This is an interface for classes that will manage the location listener policy.
|
||||
* Different policy options are:
|
||||
* Absolute
|
||||
* Addaptive
|
||||
*
|
||||
* This is an interface for classes that will manage the location listener
|
||||
* policy. Different policy options are: {@link AbsoluteLocationListenerPolicy}
|
||||
* and {@link AdaptiveLocationListenerPolicy}.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public interface LocationListenerPolicy {
|
||||
|
||||
/**
|
||||
* Returns the polling time this policy would like at this time.
|
||||
*
|
||||
* @return The polling that this policy dictates
|
||||
* Returns the polling interval this policy would like at this moment.
|
||||
*
|
||||
* @return the polling interval
|
||||
*/
|
||||
public long getDesiredPollingInterval();
|
||||
|
||||
@@ -39,10 +38,10 @@ public interface LocationListenerPolicy {
|
||||
public int getMinDistance();
|
||||
|
||||
/**
|
||||
* Notifies the amount of time the user has been idle at their current
|
||||
* location.
|
||||
*
|
||||
* @param idleTime The time that the user has been idle at this spot
|
||||
* Notifies the amount of time the user has been idle at his current location.
|
||||
*
|
||||
* @param idleTime the time that the user has been idle at his current
|
||||
* location
|
||||
*/
|
||||
public void updateIdleTime(long idleTime);
|
||||
}
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* 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 com.google.android.apps.mytracks.services;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A class that manages reading the shared preferences for the service.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
||||
private TrackRecordingService service;
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
public PreferenceManager(TrackRecordingService service) {
|
||||
this.service = service;
|
||||
this.sharedPreferences = service.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
// Refresh all properties.
|
||||
onSharedPreferenceChanged(sharedPreferences, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies that preferences have changed. Call this with key == null to
|
||||
* update all preferences in one call.
|
||||
*
|
||||
* @param key the key that changed (may be null to update all preferences)
|
||||
*/
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (service == null) {
|
||||
Log.w(Constants.TAG, "onSharedPreferenceChanged: a preference change (key = " + key
|
||||
+ ") after a call to shutdown()");
|
||||
return;
|
||||
}
|
||||
if (key == null
|
||||
|| PreferencesUtils.getKey(service, R.string.recording_track_id_key).equals(key)) {
|
||||
long recordingTrackId = PreferencesUtils.getLong(service, R.string.recording_track_id_key);
|
||||
/*
|
||||
* Only set the id if it is valid. Setting it to -1L should only happen
|
||||
* in TrackRecordingService.endCurrentTrack()
|
||||
*/
|
||||
if (recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
||||
service.setRecordingTrackId(recordingTrackId);
|
||||
}
|
||||
}
|
||||
if (key == null || PreferencesUtils.getKey(service, R.string.metric_units_key).equals(key)) {
|
||||
service.setMetricUnits(PreferencesUtils.getBoolean(
|
||||
service, R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT));
|
||||
}
|
||||
if (key == null
|
||||
|| PreferencesUtils.getKey(service, R.string.announcement_frequency_key).equals(key)) {
|
||||
service.setAnnouncementFrequency(PreferencesUtils.getInt(
|
||||
service, R.string.announcement_frequency_key,
|
||||
PreferencesUtils.ANNOUNCEMENT_FREQUENCY_DEFAULT));
|
||||
}
|
||||
if (key == null || PreferencesUtils.getKey(service, R.string.split_frequency_key).equals(key)) {
|
||||
service.setSplitFrequency(PreferencesUtils.getInt(
|
||||
service, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT));
|
||||
}
|
||||
if (key == null
|
||||
|| PreferencesUtils.getKey(service, R.string.min_recording_interval_key).equals(key)) {
|
||||
int minRecordingInterval = PreferencesUtils.getInt(service,
|
||||
R.string.min_recording_interval_key, PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT);
|
||||
switch (minRecordingInterval) {
|
||||
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE:
|
||||
/*
|
||||
* Choose battery life over moving time accuracy. min: 30 seconds,
|
||||
* max: 5 minutes, inDist: 5 meters.
|
||||
*/
|
||||
service.setLocationListenerPolicy(new AdaptiveLocationListenerPolicy(30000, 300000, 5));
|
||||
break;
|
||||
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_ACCURACY:
|
||||
/*
|
||||
* Get all the updates. min: 1 second, max: 30 seconds, minDist: 0
|
||||
* meter.
|
||||
*/
|
||||
service.setLocationListenerPolicy(new AdaptiveLocationListenerPolicy(1000, 30000, 0));
|
||||
break;
|
||||
default:
|
||||
service.setLocationListenerPolicy(
|
||||
new AbsoluteLocationListenerPolicy(minRecordingInterval * 1000));
|
||||
}
|
||||
}
|
||||
if (key == null
|
||||
|| PreferencesUtils.getKey(service, R.string.min_recording_distance_key).equals(key)) {
|
||||
service.setMinRecordingDistance(PreferencesUtils.getInt(
|
||||
service, R.string.min_recording_distance_key,
|
||||
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT));
|
||||
}
|
||||
if (key == null
|
||||
|| PreferencesUtils.getKey(service, R.string.max_recording_distance_key).equals(key)) {
|
||||
service.setMaxRecordingDistance(PreferencesUtils.getInt(
|
||||
service, R.string.max_recording_distance_key,
|
||||
PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT));
|
||||
}
|
||||
|
||||
if (key == null
|
||||
|| PreferencesUtils.getKey(service, R.string.min_required_accuracy_key).equals(key)) {
|
||||
service.setMinRequiredAccuracy(PreferencesUtils.getInt(
|
||||
service, R.string.min_required_accuracy_key,
|
||||
PreferencesUtils.MIN_REQUIRED_ACCURACY_DEFAULT));
|
||||
}
|
||||
if (key == null
|
||||
|| PreferencesUtils.getKey(service, R.string.auto_resume_track_timeout_key).equals(key)) {
|
||||
service.setAutoResumeTrackTimeout(PreferencesUtils.getInt(
|
||||
service, R.string.auto_resume_track_timeout_key,
|
||||
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
|
||||
service = null;
|
||||
}
|
||||
}
|
||||
+859
-954
File diff suppressed because it is too large
Load Diff
+12
-2
@@ -16,6 +16,7 @@
|
||||
package com.google.android.apps.mytracks.services.tasks;
|
||||
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingService;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
|
||||
@@ -118,7 +119,11 @@ public class PeriodicTaskExecutor {
|
||||
if (!isDistanceFrequency() || periodicTask == null) {
|
||||
return;
|
||||
}
|
||||
double distance = trackRecordingService.getTripStatistics().getTotalDistance()
|
||||
TripStatistics tripStatistics = trackRecordingService.getTripStatistics();
|
||||
if (tripStatistics == null) {
|
||||
return;
|
||||
}
|
||||
double distance = tripStatistics.getTotalDistance()
|
||||
* UnitConversions.M_TO_KM;
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
@@ -158,13 +163,18 @@ public class PeriodicTaskExecutor {
|
||||
return;
|
||||
}
|
||||
|
||||
TripStatistics tripStatistics = trackRecordingService.getTripStatistics();
|
||||
if (tripStatistics == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDistanceFrequency()) {
|
||||
nextTaskDistance = Double.MAX_VALUE;
|
||||
Log.d(TAG, "SplitManager: Distance splits disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
double distance = trackRecordingService.getTripStatistics().getTotalDistance()
|
||||
double distance = tripStatistics.getTotalDistance()
|
||||
* UnitConversions.M_TO_KM;
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.google.android.apps.mytracks.services.tasks;
|
||||
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingService;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Timer;
|
||||
@@ -53,6 +54,11 @@ public class TimerTaskExecutor {
|
||||
return;
|
||||
}
|
||||
|
||||
TripStatistics tripStatistics = trackRecordingService.getTripStatistics();
|
||||
if (tripStatistics == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
@@ -67,7 +73,7 @@ public class TimerTaskExecutor {
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long next = trackRecordingService.getTripStatistics().getStartTime();
|
||||
long next = tripStatistics.getStartTime();
|
||||
if (next < now) {
|
||||
next = now + interval - ((now - next) % interval);
|
||||
}
|
||||
|
||||
@@ -20,66 +20,35 @@ package com.google.android.apps.mytracks.stats;
|
||||
* This class maintains a buffer of doubles. This buffer is a convenient class
|
||||
* for storing a series of doubles and calculating information about them. This
|
||||
* is a FIFO buffer.
|
||||
*
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class DoubleBuffer {
|
||||
|
||||
/**
|
||||
* The location that the next write will occur at.
|
||||
*/
|
||||
// The location that the next write will occur at.
|
||||
private int index;
|
||||
|
||||
/**
|
||||
* The sliding buffer of doubles.
|
||||
*/
|
||||
// The sliding buffer of doubles.
|
||||
private final double[] buffer;
|
||||
|
||||
/**
|
||||
* Have all of the slots in the buffer been filled?
|
||||
*/
|
||||
// True if the buffer is full
|
||||
private boolean isFull;
|
||||
|
||||
/**
|
||||
* Creates a buffer with size elements.
|
||||
*
|
||||
* @param size the number of elements in the buffer
|
||||
* @throws IllegalArgumentException if the size is not a positive value
|
||||
* Creates a buffer with a certain size.
|
||||
*
|
||||
* @param size the size
|
||||
*/
|
||||
public DoubleBuffer(int size) {
|
||||
if (size < 1) {
|
||||
throw new IllegalArgumentException("The buffer size must be positive.");
|
||||
throw new IllegalArgumentException("The buffer size must be greater than 1.");
|
||||
}
|
||||
buffer = new double[size];
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a double to the buffer. If the buffer is full the oldest element is
|
||||
* overwritten.
|
||||
*
|
||||
* @param d the double to add
|
||||
*/
|
||||
public void setNext(double d) {
|
||||
if (index == buffer.length) {
|
||||
index = 0;
|
||||
}
|
||||
buffer[index] = d;
|
||||
index++;
|
||||
if (index == buffer.length) {
|
||||
isFull = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Are all of the entries in the buffer used?
|
||||
*/
|
||||
public boolean isFull() {
|
||||
return isFull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the buffer to the initial state.
|
||||
* Resets the buffer.
|
||||
*/
|
||||
public void reset() {
|
||||
index = 0;
|
||||
@@ -87,16 +56,20 @@ public class DoubleBuffer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the average of values from the buffer.
|
||||
*
|
||||
* @return The average of the buffer
|
||||
* Returns true if the buffer is full.
|
||||
*/
|
||||
public boolean isFull() {
|
||||
return isFull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the average of the buffer.
|
||||
*/
|
||||
public double getAverage() {
|
||||
int numberOfEntries = isFull ? buffer.length : index;
|
||||
if (numberOfEntries == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double sum = 0;
|
||||
for (int i = 0; i < numberOfEntries; i++) {
|
||||
sum += buffer[i];
|
||||
@@ -105,15 +78,15 @@ public class DoubleBuffer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the average and standard deviation of the buffer.
|
||||
*
|
||||
* @return An array of two elements - the first is the average, and the second
|
||||
* is the variance
|
||||
* Gets the average and the variance of the buffer.
|
||||
*
|
||||
* @return an array of two elements - the first is the average, the second is
|
||||
* the variance
|
||||
*/
|
||||
public double[] getAverageAndVariance() {
|
||||
int numberOfEntries = isFull ? buffer.length : index;
|
||||
if (numberOfEntries == 0) {
|
||||
return new double[]{0, 0};
|
||||
return new double[] { 0, 0 };
|
||||
}
|
||||
|
||||
double sum = 0;
|
||||
@@ -124,8 +97,25 @@ public class DoubleBuffer {
|
||||
}
|
||||
|
||||
double average = sum / numberOfEntries;
|
||||
return new double[]{average,
|
||||
sumSquares / numberOfEntries - Math.pow(average, 2)};
|
||||
double variance = sumSquares / numberOfEntries - Math.pow(average, 2);
|
||||
return new double[] { average, variance };
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a double to the buffer. If the buffer is full the oldest element is
|
||||
* overwritten.
|
||||
*
|
||||
* @param value the double to add
|
||||
*/
|
||||
public void setNext(double value) {
|
||||
if (index == buffer.length) {
|
||||
index = 0;
|
||||
}
|
||||
buffer[index] = value;
|
||||
index++;
|
||||
if (index == buffer.length) {
|
||||
isFull = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,352 +20,307 @@ import static com.google.android.apps.mytracks.Constants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Statistics keeper for a trip.
|
||||
* Builder for {@link TripStatistics}. For keeping statistics as a track is
|
||||
* paused/resumed and new locations are added.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TripStatisticsBuilder {
|
||||
/**
|
||||
* Statistical data about the trip, which can be displayed to the user.
|
||||
*/
|
||||
private final TripStatistics data;
|
||||
|
||||
/**
|
||||
* The last location that the gps reported.
|
||||
*/
|
||||
private Location lastLocation;
|
||||
|
||||
/**
|
||||
* The last location that contributed to the stats. It is also the last
|
||||
* location the user was found to be moving.
|
||||
*/
|
||||
private Location lastMovingLocation;
|
||||
|
||||
/**
|
||||
* The current speed in meters/second as reported by the gps.
|
||||
*/
|
||||
private double currentSpeed;
|
||||
|
||||
/**
|
||||
* The current grade. This value is very noisy and not reported to the user.
|
||||
*/
|
||||
private double currentGrade;
|
||||
|
||||
/**
|
||||
* Is the trip currently paused?
|
||||
* All trips start paused.
|
||||
*/
|
||||
private boolean paused = true;
|
||||
|
||||
/**
|
||||
* A buffer of the last speed readings in meters/second.
|
||||
*/
|
||||
private final DoubleBuffer speedBuffer =
|
||||
new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
|
||||
|
||||
/**
|
||||
* A buffer of the recent elevation readings in meters.
|
||||
*/
|
||||
private final DoubleBuffer elevationBuffer =
|
||||
new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
|
||||
|
||||
/**
|
||||
* A buffer of the distance between recent gps readings in meters.
|
||||
*/
|
||||
private final DoubleBuffer distanceBuffer =
|
||||
new DoubleBuffer(Constants.DISTANCE_SMOOTHING_FACTOR);
|
||||
|
||||
/**
|
||||
* A buffer of the recent grade calculations.
|
||||
*/
|
||||
private final DoubleBuffer gradeBuffer =
|
||||
new DoubleBuffer(Constants.GRADE_SMOOTHING_FACTOR);
|
||||
|
||||
/**
|
||||
* The total number of locations in this trip.
|
||||
*/
|
||||
private long totalLocations = 0;
|
||||
// The trip statistics.
|
||||
private final TripStatistics tripStatistics;
|
||||
|
||||
// The minimum recording distance.
|
||||
private int minRecordingDistance = PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT;
|
||||
|
||||
// True if the trip is paused. All trips start as paused.
|
||||
private boolean paused = true;
|
||||
|
||||
// The last location as reported by GPS.
|
||||
private Location lastLocation;
|
||||
|
||||
// The last moving location that contributed to the moving statistics.
|
||||
private Location lastMovingLocation;
|
||||
|
||||
// A buffer of the recent speed readings (m/s) for calculating max speed.
|
||||
private final DoubleBuffer speedBuffer = new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
|
||||
|
||||
// A buffer of the recent elevation readings (m).
|
||||
private final DoubleBuffer elevationBuffer = new DoubleBuffer(
|
||||
Constants.ELEVATION_SMOOTHING_FACTOR);
|
||||
|
||||
// A buffer of the recent distance readings for calculating grade.
|
||||
private final DoubleBuffer distanceBuffer = new DoubleBuffer(Constants.DISTANCE_SMOOTHING_FACTOR);
|
||||
|
||||
// A buffer of the recent grade calculations
|
||||
private final DoubleBuffer gradeBuffer = new DoubleBuffer(Constants.GRADE_SMOOTHING_FACTOR);
|
||||
|
||||
/**
|
||||
* Creates a new trip starting at the given time.
|
||||
* Creates a new trip starting at a start time.
|
||||
*
|
||||
* @param startTime the start time.
|
||||
* @param startTime the start time
|
||||
*/
|
||||
public TripStatisticsBuilder(long startTime) {
|
||||
data = new TripStatistics();
|
||||
tripStatistics = new TripStatistics();
|
||||
resumeAt(startTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new trip, starting with existing statistics data.
|
||||
*
|
||||
* @param statsData the statistics data to copy and start from
|
||||
*/
|
||||
public TripStatisticsBuilder(TripStatistics statsData) {
|
||||
data = new TripStatistics(statsData);
|
||||
if (data.getStartTime() > 0) {
|
||||
resumeAt(data.getStartTime());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a location to the current trip. This will update all of the internal
|
||||
* variables with this new location.
|
||||
* Creates a new trip, starting with an existing {@link TripStatistics}.
|
||||
*
|
||||
* @param currentLocation the current gps location
|
||||
* @param systemTime the time used for calculation of totalTime. This should
|
||||
* be the phone's time (not GPS time)
|
||||
* @return true if the person is moving
|
||||
* @param other the existing {@link TripStatistics}
|
||||
*/
|
||||
public boolean addLocation(Location currentLocation, long systemTime) {
|
||||
if (paused) {
|
||||
Log.w(TAG,
|
||||
"Tried to account for location while track is paused");
|
||||
return false;
|
||||
public TripStatisticsBuilder(TripStatistics other) {
|
||||
tripStatistics = new TripStatistics(other);
|
||||
if (tripStatistics.getStartTime() > 0) {
|
||||
resumeAt(tripStatistics.getStartTime());
|
||||
}
|
||||
|
||||
totalLocations++;
|
||||
|
||||
double elevationDifference = updateElevation(currentLocation.getAltitude());
|
||||
|
||||
// Update the "instant" values:
|
||||
data.setTotalTime(systemTime - data.getStartTime());
|
||||
currentSpeed = currentLocation.getSpeed();
|
||||
|
||||
// This was the 1st location added, remember it and do nothing else:
|
||||
if (lastLocation == null) {
|
||||
lastLocation = currentLocation;
|
||||
lastMovingLocation = currentLocation;
|
||||
return false;
|
||||
}
|
||||
|
||||
updateBounds(currentLocation);
|
||||
|
||||
// Don't do anything if we didn't move since last fix:
|
||||
double distance = lastLocation.distanceTo(currentLocation);
|
||||
if (distance < minRecordingDistance &&
|
||||
currentSpeed < Constants.MAX_NO_MOVEMENT_SPEED) {
|
||||
lastLocation = currentLocation;
|
||||
return false;
|
||||
}
|
||||
|
||||
data.addTotalDistance(lastMovingLocation.distanceTo(currentLocation));
|
||||
updateSpeed(currentLocation.getTime(), currentSpeed,
|
||||
lastLocation.getTime(), lastLocation.getSpeed());
|
||||
|
||||
updateGrade(distance, elevationDifference);
|
||||
lastLocation = currentLocation;
|
||||
lastMovingLocation = currentLocation;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the track's bounding box to include the given location.
|
||||
*/
|
||||
private void updateBounds(Location location) {
|
||||
data.updateLatitudeExtremities(location.getLatitude());
|
||||
data.updateLongitudeExtremities(location.getLongitude());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the elevation measurements.
|
||||
* Sets the min recording distance.
|
||||
*
|
||||
* @param elevation the current elevation
|
||||
* @param minRecordingDistance the min recording distance
|
||||
*/
|
||||
// @VisibleForTesting
|
||||
double updateElevation(double elevation) {
|
||||
double oldSmoothedElevation = getSmoothedElevation();
|
||||
elevationBuffer.setNext(elevation);
|
||||
double smoothedElevation = getSmoothedElevation();
|
||||
data.updateElevationExtremities(smoothedElevation);
|
||||
double elevationDifference = elevationBuffer.isFull()
|
||||
? smoothedElevation - oldSmoothedElevation
|
||||
: 0.0;
|
||||
if (elevationDifference > 0) {
|
||||
data.addTotalElevationGain(elevationDifference);
|
||||
}
|
||||
return elevationDifference;
|
||||
public void setMinRecordingDistance(int minRecordingDistance) {
|
||||
this.minRecordingDistance = minRecordingDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the speed measurements.
|
||||
* Resumes the current track at a given time.
|
||||
*
|
||||
* @param updateTime the time of the speed update
|
||||
* @param speed the current speed
|
||||
* @param lastLocationTime the time of the last speed update
|
||||
* @param lastLocationSpeed the speed of the last update
|
||||
* @param time the time
|
||||
*/
|
||||
// @VisibleForTesting
|
||||
void updateSpeed(long updateTime, double speed, long lastLocationTime,
|
||||
double lastLocationSpeed) {
|
||||
// We are now sure the user is moving.
|
||||
long timeDifference = updateTime - lastLocationTime;
|
||||
if (timeDifference < 0) {
|
||||
Log.e(TAG,
|
||||
"Found negative time change: " + timeDifference);
|
||||
}
|
||||
data.addMovingTime(timeDifference);
|
||||
|
||||
if (isValidSpeed(updateTime, speed, lastLocationTime, lastLocationSpeed,
|
||||
speedBuffer)) {
|
||||
speedBuffer.setNext(speed);
|
||||
if (speed > data.getMaxSpeed()) {
|
||||
data.setMaxSpeed(speed);
|
||||
}
|
||||
double movingSpeed = data.getAverageMovingSpeed();
|
||||
if (speedBuffer.isFull() && (movingSpeed > data.getMaxSpeed())) {
|
||||
data.setMaxSpeed(movingSpeed);
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG,
|
||||
"TripStatistics ignoring big change: Raw Speed: " + speed
|
||||
+ " old: " + lastLocationSpeed + " [" + toString() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if this is a valid speed.
|
||||
*
|
||||
* @param updateTime The time at the current reading
|
||||
* @param speed The current speed
|
||||
* @param lastLocationTime The time at the last location
|
||||
* @param lastLocationSpeed Speed at the last location
|
||||
* @param speedBuffer A buffer of recent readings
|
||||
* @return True if this is likely a valid speed
|
||||
*/
|
||||
public static boolean isValidSpeed(long updateTime, double speed,
|
||||
long lastLocationTime, double lastLocationSpeed,
|
||||
DoubleBuffer speedBuffer) {
|
||||
|
||||
// We don't want to count 0 towards the speed.
|
||||
if (speed == 0) {
|
||||
return false;
|
||||
}
|
||||
// We are now sure the user is moving.
|
||||
long timeDifference = updateTime - lastLocationTime;
|
||||
|
||||
// There are a lot of noisy speed readings.
|
||||
// Do the cheapest checks first, most expensive last.
|
||||
// The following code will ignore unlikely to be real readings.
|
||||
// - 128 m/s seems to be an internal android error code.
|
||||
if (Math.abs(speed - 128) < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Another check for a spurious reading. See if the path seems physically
|
||||
// likely. Ignore any speeds that imply accelaration greater than 2g's
|
||||
// Really who can accelerate faster?
|
||||
double speedDifference = Math.abs(lastLocationSpeed - speed);
|
||||
if (speedDifference > Constants.MAX_ACCELERATION * timeDifference) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There are three additional checks if the reading gets this far:
|
||||
// - Only use the speed if the buffer is full
|
||||
// - Check that the current speed is less than 10x the recent smoothed speed
|
||||
// - Double check that the current speed does not imply crazy acceleration
|
||||
double smoothedSpeed = speedBuffer.getAverage();
|
||||
double smoothedDiff = Math.abs(smoothedSpeed - speed);
|
||||
return !speedBuffer.isFull() ||
|
||||
(speed < smoothedSpeed * 10
|
||||
&& smoothedDiff < Constants.MAX_ACCELERATION * timeDifference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the grade measurements.
|
||||
*
|
||||
* @param distance the distance the user just traveled
|
||||
* @param elevationDifference the elevation difference between the current
|
||||
* reading and the previous reading
|
||||
*/
|
||||
// @VisibleForTesting
|
||||
void updateGrade(double distance, double elevationDifference) {
|
||||
distanceBuffer.setNext(distance);
|
||||
double smoothedDistance = distanceBuffer.getAverage();
|
||||
|
||||
// With the error in the altitude measurement it is dangerous to divide
|
||||
// by anything less than 5.
|
||||
if (!elevationBuffer.isFull() || !distanceBuffer.isFull()
|
||||
|| smoothedDistance < 5.0) {
|
||||
public void resumeAt(long time) {
|
||||
if (!paused) {
|
||||
return;
|
||||
}
|
||||
currentGrade = elevationDifference / smoothedDistance;
|
||||
gradeBuffer.setNext(currentGrade);
|
||||
data.updateGradeExtremities(gradeBuffer.getAverage());
|
||||
|
||||
tripStatistics.setStartTime(time);
|
||||
tripStatistics.setStopTime(-1L);
|
||||
paused = false;
|
||||
lastLocation = null;
|
||||
lastMovingLocation = null;
|
||||
speedBuffer.reset();
|
||||
elevationBuffer.reset();
|
||||
distanceBuffer.reset();
|
||||
gradeBuffer.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the track at the given time.
|
||||
* Pauses the track at a given time.
|
||||
*
|
||||
* @param time the time to pause at
|
||||
*/
|
||||
public void pauseAt(long time) {
|
||||
if (paused) { return; }
|
||||
|
||||
data.setStopTime(time);
|
||||
data.setTotalTime(time - data.getStartTime());
|
||||
lastLocation = null; // Make sure the counter restarts.
|
||||
if (paused) {
|
||||
return;
|
||||
}
|
||||
tripStatistics.setStopTime(time);
|
||||
// TODO: total time needs to take into account pauses
|
||||
tripStatistics.setTotalTime(time - tripStatistics.getStartTime());
|
||||
paused = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the current track at the given time.
|
||||
*
|
||||
* @param time the time to resume at
|
||||
* Gets the trip statistics.
|
||||
*/
|
||||
public void resumeAt(long time) {
|
||||
if (!paused) { return; }
|
||||
|
||||
// TODO: The times are bogus if the track is paused then resumed again
|
||||
data.setStartTime(time);
|
||||
data.setStopTime(-1);
|
||||
paused = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TripStatistics { Data: " + data.toString()
|
||||
+ "; Total Locations: " + totalLocations
|
||||
+ "; Paused: " + paused
|
||||
+ "; Current speed: " + currentSpeed
|
||||
+ "; Current grade: " + currentGrade
|
||||
+ "}";
|
||||
public TripStatistics getTripStatistics() {
|
||||
// Take a snapshot - we don't want anyone messing with our internals
|
||||
return new TripStatistics(tripStatistics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of time the user has been idle or 0 if they are moving.
|
||||
* Returns the amount of time the user has been idle or 0 if he is moving.
|
||||
*/
|
||||
public long getIdleTime() {
|
||||
if (lastLocation == null || lastMovingLocation == null)
|
||||
if (lastLocation == null || lastMovingLocation == null) {
|
||||
return 0;
|
||||
}
|
||||
return lastLocation.getTime() - lastMovingLocation.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current elevation smoothed over several readings. The elevation
|
||||
* data is very noisy so it is better to use the smoothed elevation than the
|
||||
* raw elevation for many tasks.
|
||||
*
|
||||
* @return The elevation smoothed over several readings
|
||||
* Gets the smoothed elevation over several readings. The elevation readings
|
||||
* is noisy so the smoothed elevation is better than the raw elevation for
|
||||
* many tasks.
|
||||
*/
|
||||
public double getSmoothedElevation() {
|
||||
return elevationBuffer.getAverage();
|
||||
}
|
||||
|
||||
public TripStatistics getStatistics() {
|
||||
// Take a snapshot - we don't want anyone messing with our internals
|
||||
return new TripStatistics(data);
|
||||
/**
|
||||
* Adds a location. This will update all of the internal variables with this
|
||||
* new location.
|
||||
*
|
||||
* @param location the location
|
||||
* @param systemTime the system time for calculating totalTime. This should be
|
||||
* the phone's system time (not GPS time)
|
||||
* @return true if the person is moving
|
||||
*/
|
||||
public boolean addLocation(Location location, long systemTime) {
|
||||
if (paused) {
|
||||
Log.w(TAG, "Track is paused. Ignore addLocation.");
|
||||
return false;
|
||||
}
|
||||
|
||||
tripStatistics.setTotalTime(systemTime - tripStatistics.getStartTime());
|
||||
|
||||
double elevationDifference = updateElevation(location.getAltitude());
|
||||
tripStatistics.updateLatitudeExtremities(location.getLatitude());
|
||||
tripStatistics.updateLongitudeExtremities(location.getLongitude());
|
||||
|
||||
// If this is the first location, remember it and return.
|
||||
if (lastLocation == null || lastMovingLocation == null) {
|
||||
lastLocation = location;
|
||||
lastMovingLocation = location;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't do anything more if we didn't move since the last location.
|
||||
double distance = lastLocation.distanceTo(location);
|
||||
if (distance < minRecordingDistance && location.getSpeed() < Constants.MAX_NO_MOVEMENT_SPEED) {
|
||||
lastLocation = location;
|
||||
return false;
|
||||
}
|
||||
|
||||
long timeDifference = location.getTime() - lastLocation.getTime();
|
||||
if (timeDifference < 0) {
|
||||
Log.e(TAG, "Negative time difference: " + timeDifference);
|
||||
lastLocation = location;
|
||||
return false;
|
||||
}
|
||||
|
||||
tripStatistics.addTotalDistance(lastMovingLocation.distanceTo(location));
|
||||
tripStatistics.addMovingTime(timeDifference);
|
||||
updateSpeed(
|
||||
location.getTime(), location.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed());
|
||||
updateGrade(distance, elevationDifference);
|
||||
lastLocation = location;
|
||||
lastMovingLocation = location;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setMinRecordingDistance(int minRecordingDistance) {
|
||||
this.minRecordingDistance = minRecordingDistance;
|
||||
/**
|
||||
* Updates a speed reading. Assumes the user is moving.
|
||||
*
|
||||
* @param time the time
|
||||
* @param speed the speed
|
||||
* @param lastLocationTime the last location time
|
||||
* @param lastLocationSpeed the last location speed
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void updateSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) {
|
||||
if (!isValidSpeed(time, speed, lastLocationTime, lastLocationSpeed, speedBuffer)) {
|
||||
Log.d(TAG, "Invalid speed. speed: " + speed + " lastLocationSpeed: " + lastLocationSpeed);
|
||||
return;
|
||||
}
|
||||
speedBuffer.setNext(speed);
|
||||
if (speed > tripStatistics.getMaxSpeed()) {
|
||||
tripStatistics.setMaxSpeed(speed);
|
||||
}
|
||||
double movingSpeed = tripStatistics.getAverageMovingSpeed();
|
||||
if (speedBuffer.isFull() && movingSpeed > tripStatistics.getMaxSpeed()) {
|
||||
tripStatistics.setMaxSpeed(movingSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an elevation reading.
|
||||
*
|
||||
* @param elevation the elevation
|
||||
*/
|
||||
@VisibleForTesting
|
||||
double updateElevation(double elevation) {
|
||||
double oldAverage = elevationBuffer.getAverage();
|
||||
elevationBuffer.setNext(elevation);
|
||||
double newAverage = elevationBuffer.getAverage();
|
||||
tripStatistics.updateElevationExtremities(newAverage);
|
||||
double elevationDifference = elevationBuffer.isFull() ? newAverage - oldAverage : 0.0;
|
||||
if (elevationDifference > 0) {
|
||||
tripStatistics.addTotalElevationGain(elevationDifference);
|
||||
}
|
||||
return elevationDifference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a grade reading.
|
||||
*
|
||||
* @param distance the distance the user just traveled
|
||||
* @param elevationDifference the elevation difference between the current
|
||||
* reading and the previous reading
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void updateGrade(double distance, double elevationDifference) {
|
||||
distanceBuffer.setNext(distance);
|
||||
double smoothedDistance = distanceBuffer.getAverage();
|
||||
|
||||
/*
|
||||
* With the error in the altitude measurement it is dangerous to divide by
|
||||
* anything less than 5.
|
||||
*/
|
||||
if (!elevationBuffer.isFull() || !distanceBuffer.isFull() || smoothedDistance < 5.0) {
|
||||
return;
|
||||
}
|
||||
gradeBuffer.setNext(elevationDifference / smoothedDistance);
|
||||
tripStatistics.updateGradeExtremities(gradeBuffer.getAverage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the speed is valid.
|
||||
*
|
||||
* @param time the time
|
||||
* @param speed the speed
|
||||
* @param lastLocationTime the last location time
|
||||
* @param lastLocationSpeed the last location speed
|
||||
* @param speedBuffer a buffer of speed readings
|
||||
*/
|
||||
public static boolean isValidSpeed(long time, double speed, long lastLocationTime,
|
||||
double lastLocationSpeed, DoubleBuffer speedBuffer) {
|
||||
|
||||
/*
|
||||
* There are a lot of noisy speed readings. Do the cheapest checks first,
|
||||
* most expensive last.
|
||||
*/
|
||||
if (speed == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* The following code will ignore unlikely readings. 128 m/s seems to be an
|
||||
* internal android error code.
|
||||
*/
|
||||
if (Math.abs(speed - 128) < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* See if the speed seems physically likely. Ignore any speeds that imply
|
||||
* acceleration greater than 2g.
|
||||
*/
|
||||
long timeDifference = time - lastLocationTime;
|
||||
double speedDifference = Math.abs(lastLocationSpeed - speed);
|
||||
if (speedDifference > Constants.MAX_ACCELERATION * timeDifference) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only check if the speed buffer is full. Check that the speed is less than
|
||||
* 10X the smoothed average and the speed difference doesn't imply 2g
|
||||
* acceleration.
|
||||
*/
|
||||
if (!speedBuffer.isFull()) {
|
||||
return true;
|
||||
}
|
||||
double average = speedBuffer.getAverage();
|
||||
double diff = Math.abs(average - speed);
|
||||
return (speed < average * 10) && (diff < Constants.MAX_ACCELERATION * timeDifference);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user