From b90b29a441ff0ade96714ac050ff5203c4430bf6 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 5 Jan 2016 12:42:02 +0100 Subject: [PATCH 1/5] Display distance, average and maximum speed --- .../osmtracker/activity/TrackDetail.java | 19 ++ .../osmtracker/activity/TrackManager.java | 3 + .../android/osmtracker/db/DataHelper.java | 23 ++ .../osmtracker/db/TracklistAdapter.java | 35 +++ .../osmtracker/db/model/TrackStatistics.java | 246 ++++++++++++++++++ .../osmtracker/layout/GpsStatusRecord.java | 4 + app/src/main/res/layout-iw/tracklist_item.xml | 44 ++++ app/src/main/res/layout/tracklist_item.xml | 18 ++ app/src/main/res/values/strings.xml | 8 + 9 files changed, 400 insertions(+) create mode 100644 app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java index 8d593294..8058f47f 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java @@ -9,9 +9,12 @@ import me.guillaumin.android.osmtracker.OSMTracker; import me.guillaumin.android.osmtracker.R; +import me.guillaumin.android.osmtracker.db.DataHelper; import me.guillaumin.android.osmtracker.db.TrackContentProvider; import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema; +import me.guillaumin.android.osmtracker.db.TracklistAdapter; import me.guillaumin.android.osmtracker.db.model.Track; +import me.guillaumin.android.osmtracker.db.model.TrackStatistics; import me.guillaumin.android.osmtracker.gpx.ExportToStorageTask; import me.guillaumin.android.osmtracker.util.MercatorProjection; import android.content.ContentResolver; @@ -125,6 +128,7 @@ protected void onResume() { Track t = Track.build(trackId, cursor, cr, true); bindTrack(t); + TrackStatistics stat = DataHelper.getTrackStatistics(trackId, cr); String from[] = new String[]{ITEM_KEY, ITEM_VALUE}; int[] to = new int[] {R.id.trackdetail_item_key, R.id.trackdetail_item_value}; @@ -144,6 +148,21 @@ protected void onResume() { map.put(ITEM_VALUE, Integer.toString(t.getTpCount())); data.add(map); + // Distance + map = new HashMap(); + map.put(ITEM_KEY, getResources().getString(R.string.trackmgr_distance)); + map.put(ITEM_VALUE, TracklistAdapter.distanceToString(stat.totalLength(), getResources())); + data.add(map); + + // Speed + map = new HashMap(); + map.put(ITEM_KEY, getResources().getString(R.string.trackdetail_speed)); + map.put(ITEM_VALUE, TracklistAdapter.speedToString(stat.averageSpeed(), getResources()) + " " + + getResources().getString(R.string.trackdetail_speed_average) + ", " + + TracklistAdapter.speedToString(stat.maximumSpeed(), getResources()) + " " + + getResources().getString(R.string.trackdetail_speed_max)); + data.add(map); + // Start date map = new HashMap(); map.put(ITEM_KEY, getResources().getString(R.string.trackdetail_startdate)); diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java index d4754fcb..a14c54e5 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java @@ -417,6 +417,9 @@ private void deleteTrack(long id) { if (trackStorageDirectory.exists()) { FileSystemUtils.delete(trackStorageDirectory, true); } + + // Delete the statistics + DataHelper.removeTrackStatistics(id); } /** diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/db/DataHelper.java b/app/src/main/java/me/guillaumin/android/osmtracker/db/DataHelper.java index 34a92807..24cd984c 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/db/DataHelper.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/db/DataHelper.java @@ -2,9 +2,12 @@ import java.io.File; import java.text.SimpleDateFormat; +import java.util.TreeMap; +import java.lang.RuntimeException; import me.guillaumin.android.osmtracker.OSMTracker; import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema; +import me.guillaumin.android.osmtracker.db.model.TrackStatistics; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -65,6 +68,9 @@ public class DataHelper { */ private Context context; + /** Statistics for all existing tracks */ + private static TreeMap tracksStatistics = new TreeMap (); + /** * ContentResolver to interact with content provider */ @@ -125,6 +131,7 @@ public void track(long trackId, Location location, float azimuth, int accuracy) } Uri trackUri = ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId); + getTrackStatistics(trackId, contentResolver).update(location); contentResolver.insert(Uri.withAppendedPath(trackUri, Schema.TBL_TRACKPOINT + "s"), values); } @@ -373,4 +380,20 @@ public static File getTrackDirectory(long trackId) { return _return; } + /** + * Get statistics for a given track + * If doesn't exists, create and calculate from the database + */ + public static TrackStatistics getTrackStatistics(long trackId, ContentResolver resolver) { + if (! tracksStatistics.containsKey(trackId)) + tracksStatistics.put(trackId, new TrackStatistics(trackId, resolver)); // getContentResolver() if non-static member + return tracksStatistics.get(trackId); + } + + /** + * Remove statistics for a given track + */ + public static void removeTrackStatistics(long trackId) { + tracksStatistics.remove(trackId); + } } diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/db/TracklistAdapter.java b/app/src/main/java/me/guillaumin/android/osmtracker/db/TracklistAdapter.java index 48c319bd..da36fba1 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/db/TracklistAdapter.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/db/TracklistAdapter.java @@ -3,7 +3,9 @@ import me.guillaumin.android.osmtracker.R; import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema; import me.guillaumin.android.osmtracker.db.model.Track; +import me.guillaumin.android.osmtracker.db.model.TrackStatistics; import android.content.Context; +import android.content.res.Resources; import android.database.Cursor; import android.view.LayoutInflater; import android.view.View; @@ -21,6 +23,34 @@ */ public class TracklistAdapter extends CursorAdapter { + public static String distanceToString(float distance, Resources resources) { + if (distance < 100) + return String.format("%d %s", Math.round(distance), + resources.getString(R.string.trackmgr_distance_meters)); + else if (distance < 1000) + return String.format("%d %s", 10*Math.round(distance/10), + resources.getString(R.string.trackmgr_distance_meters)); + else if (distance < 10000) + return String.format("%.1f %s", distance/1000, + resources.getString(R.string.trackmgr_distance_kilometers)); + else + return String.format("%d %s", Math.round(distance/1000), + resources.getString(R.string.trackmgr_distance_kilometers)); + } + + public static String speedToString(float speed, Resources resources) { + float kmph = (float)3.6*speed; + if (kmph < 1) + return String.format("%.1g %s", kmph, + resources.getString(R.string.trackmgr_speed_kmph)); + else if (kmph < 20) + return String.format("%.1f %s", kmph, + resources.getString(R.string.trackmgr_speed_kmph)); + else + return String.format("%d %s", Math.round(kmph), + resources.getString(R.string.trackmgr_speed_kmph)); + } + public TracklistAdapter(Context context, Cursor c) { super(context, c); } @@ -53,6 +83,8 @@ private View bind(Cursor cursor, View v, Context context) { TextView vNameOrStartDate = (TextView) v.findViewById(R.id.trackmgr_item_nameordate); TextView vWps = (TextView) v.findViewById(R.id.trackmgr_item_wps); TextView vTps = (TextView) v.findViewById(R.id.trackmgr_item_tps); + TextView vDistance = (TextView) v.findViewById(R.id.trackmgr_item_distance); + TextView vSpeed = (TextView) v.findViewById(R.id.trackmgr_item_speed); ImageView vStatus = (ImageView) v.findViewById(R.id.trackmgr_item_statusicon); ImageView vUploadStatus = (ImageView) v.findViewById(R.id.trackmgr_item_upload_statusicon); @@ -83,8 +115,11 @@ private View bind(Cursor cursor, View v, Context context) { // Bind WP count, TP count, name Track t = Track.build(trackId, cursor, context.getContentResolver(), false); + TrackStatistics stat = DataHelper.getTrackStatistics(trackId, context.getContentResolver()); vTps.setText(Integer.toString(t.getTpCount())); vWps.setText(Integer.toString(t.getWpCount())); + vDistance.setText(distanceToString(stat.totalLength(), context.getResources())); + vSpeed.setText(speedToString(stat.averageSpeed(), context.getResources())); vNameOrStartDate.setText(t.getName()); return v; diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java b/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java new file mode 100644 index 00000000..1825276e --- /dev/null +++ b/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java @@ -0,0 +1,246 @@ +package me.guillaumin.android.osmtracker.db.model; + +import me.guillaumin.android.osmtracker.db.TrackContentProvider; +import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema; +import android.content.ContentResolver; +import android.database.Cursor; +import android.location.Location; +import android.util.Log; + +/** + * Represents statistics, such as total length and maximum speed, for a Track + * + * @author Arseniy Lartsev + * + */ +public class TrackStatistics { + private long trackId; + private long pointCount; + private float length; + private float maxSpeed; + private double lastLatitude; + private double lastLongitude; + private long lastTime; + private float timeMoving; + + /** + * Copypasta from osmand + */ + private static void computeDistanceAndBearing(double lat1, double lon1, + double lat2, double lon2, float[] results) { + // Based on http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf + // using the "Inverse Formula" (section 4) + int MAXITERS = 20; + // Convert lat/long to radians + lat1 *= Math.PI / 180.0; + lat2 *= Math.PI / 180.0; + lon1 *= Math.PI / 180.0; + lon2 *= Math.PI / 180.0; + + double a = 6378137.0; // WGS84 major axis + double b = 6356752.3142; // WGS84 semi-major axis + double f = (a - b) / a; + double aSqMinusBSqOverBSq = (a * a - b * b) / (b * b); + + double L = lon2 - lon1; + double A = 0.0; + double U1 = Math.atan((1.0 - f) * Math.tan(lat1)); + double U2 = Math.atan((1.0 - f) * Math.tan(lat2)); + + double cosU1 = Math.cos(U1); + double cosU2 = Math.cos(U2); + double sinU1 = Math.sin(U1); + double sinU2 = Math.sin(U2); + double cosU1cosU2 = cosU1 * cosU2; + double sinU1sinU2 = sinU1 * sinU2; + + double sigma = 0.0; + double deltaSigma = 0.0; + double cosSqAlpha = 0.0; + double cos2SM = 0.0; + double cosSigma = 0.0; + double sinSigma = 0.0; + double cosLambda = 0.0; + double sinLambda = 0.0; + + double lambda = L; // initial guess + for (int iter = 0; iter < MAXITERS; iter++) { + double lambdaOrig = lambda; + cosLambda = Math.cos(lambda); + sinLambda = Math.sin(lambda); + double t1 = cosU2 * sinLambda; + double t2 = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda; + double sinSqSigma = t1 * t1 + t2 * t2; // (14) + sinSigma = Math.sqrt(sinSqSigma); + cosSigma = sinU1sinU2 + cosU1cosU2 * cosLambda; // (15) + sigma = Math.atan2(sinSigma, cosSigma); // (16) + double sinAlpha = (sinSigma == 0) ? 0.0 : + cosU1cosU2 * sinLambda / sinSigma; // (17) + cosSqAlpha = 1.0 - sinAlpha * sinAlpha; + cos2SM = (cosSqAlpha == 0) ? 0.0 : + cosSigma - 2.0 * sinU1sinU2 / cosSqAlpha; // (18) + + double uSquared = cosSqAlpha * aSqMinusBSqOverBSq; // defn + A = 1 + (uSquared / 16384.0) * // (3) + (4096.0 + uSquared * + (-768 + uSquared * (320.0 - 175.0 * uSquared))); + double B = (uSquared / 1024.0) * // (4) + (256.0 + uSquared * + (-128.0 + uSquared * (74.0 - 47.0 * uSquared))); + double C = (f / 16.0) * + cosSqAlpha * + (4.0 + f * (4.0 - 3.0 * cosSqAlpha)); // (10) + double cos2SMSq = cos2SM * cos2SM; + deltaSigma = B * sinSigma * // (6) + (cos2SM + (B / 4.0) * + (cosSigma * (-1.0 + 2.0 * cos2SMSq) - + (B / 6.0) * cos2SM * + (-3.0 + 4.0 * sinSigma * sinSigma) * + (-3.0 + 4.0 * cos2SMSq))); + + lambda = L + + (1.0 - C) * f * sinAlpha * + (sigma + C * sinSigma * + (cos2SM + C * cosSigma * + (-1.0 + 2.0 * cos2SM * cos2SM))); // (11) + + double delta = (lambda - lambdaOrig) / lambda; + if (Math.abs(delta) < 1.0e-12) { + break; + } + } + + float distance = (float) (b * A * (sigma - deltaSigma)); + results[0] = distance; + if (results.length > 1) { + float initialBearing = (float) Math.atan2(cosU2 * sinLambda, + cosU1 * sinU2 - sinU1 * cosU2 * cosLambda); + initialBearing *= 180.0 / Math.PI; + results[1] = initialBearing; + if (results.length > 2) { + float finalBearing = (float) Math.atan2(cosU1 * sinLambda, + -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda); + finalBearing *= 180.0 / Math.PI; + results[2] = finalBearing; + } + } + } + + /** + * Copypasta from osmand + * + * Computes the approximate distance in meters between two + * locations, and optionally the initial and final bearings of the + * shortest path between them. Distance and bearing are defined using the + * WGS84 ellipsoid. + * + *

The computed distance is stored in results[0]. If results has length + * 2 or greater, the initial bearing is stored in results[1]. If results has + * length 3 or greater, the final bearing is stored in results[2]. + * + * @param startLatitude the starting latitude + * @param startLongitude the starting longitude + * @param endLatitude the ending latitude + * @param endLongitude the ending longitude + * @param results an array of floats to hold the results + * + * @throws IllegalArgumentException if results is null or has length < 1 + */ + public static void distanceBetween(double startLatitude, double startLongitude, + double endLatitude, double endLongitude, float[] results) { + if (results == null || results.length < 1) { + throw new IllegalArgumentException("results is null or has length < 1"); + } + computeDistanceAndBearing(startLatitude, startLongitude, + endLatitude, endLongitude, results); + } + + public TrackStatistics () { + trackId = -1; + length = 0; + maxSpeed = 0; + pointCount = 0; + timeMoving = 0; + } + + /** + * build a track statistics object with the given cursor + * + * @param trackId id of the track that will be built + * @param cr the content resolver to use + */ + public TrackStatistics (final long trackId, ContentResolver cr) { + this.trackId = trackId; + length = 0; + maxSpeed = 0; + pointCount = 0; + timeMoving = 0; + + Cursor cursor = cr.query(TrackContentProvider.trackPointsUri(trackId), null, null, null, null); + if(! cursor.moveToFirst()) + return; + + while (! cursor.isAfterLast()) { + double latitude = cursor.getDouble(cursor.getColumnIndex(Schema.COL_LATITUDE)); + double longitude = cursor.getDouble(cursor.getColumnIndex(Schema.COL_LONGITUDE)); + float speed = cursor.getFloat(cursor.getColumnIndex(Schema.COL_SPEED)); + float accuracy = cursor.getFloat(cursor.getColumnIndex(Schema.COL_ACCURACY)); + long time = cursor.getLong(cursor.getColumnIndex(Schema.COL_TIMESTAMP)); + + update(latitude, longitude, accuracy, speed, time); + cursor.moveToNext(); + } + + cursor.close(); + } + + /** + * Update the statistics upon adding a new point to the track + */ + public void update(Location trackPoint){ + update(trackPoint.getLatitude(), trackPoint.getLongitude(), trackPoint.getAccuracy(), + trackPoint.getSpeed(), trackPoint.getTime()); + } + + private void update(double latitude, double longitude, float accuracy, float speed, long time) { + if (pointCount > 0) { + float[] distance = new float[1]; + distanceBetween(lastLatitude, lastLongitude, latitude, longitude, distance); + + // The "distance and time only counts at non-zero speed" principle is borrowed from osmand + if ((speed > 0) && (time != 0) && (lastTime != 0)) { + length += distance[0]; + timeMoving += time - lastTime; + } + } + pointCount += 1; + + if(speed > maxSpeed) + maxSpeed = speed; + lastLatitude = latitude; + lastLongitude = longitude; + lastTime = time; + } + + /** + * Get track length, in meters + */ + public float totalLength() { + return length; + } + + /** + * Get maximum speed, in meters per second + */ + public float maximumSpeed() { + return maxSpeed; + } + + /** + * Get aveare speed, in meters per second + */ + public float averageSpeed() { + return 1000*length/timeMoving; + } + +} diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/layout/GpsStatusRecord.java b/app/src/main/java/me/guillaumin/android/osmtracker/layout/GpsStatusRecord.java index b20ba423..b00440c4 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/layout/GpsStatusRecord.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/layout/GpsStatusRecord.java @@ -253,4 +253,8 @@ public void manageRecordingIndicator(boolean isTracking) { } } + public TrackLogger getActivity() { + return activity; + } + } diff --git a/app/src/main/res/layout-iw/tracklist_item.xml b/app/src/main/res/layout-iw/tracklist_item.xml index 12ed06e1..96acca97 100644 --- a/app/src/main/res/layout-iw/tracklist_item.xml +++ b/app/src/main/res/layout-iw/tracklist_item.xml @@ -80,6 +80,50 @@ android:layout_toLeftOf="@+id/trackmgr_item_trackpoints" android:text="{y}" > + + + + + + + + + + + + + + + + + Track list: Waypoints: Trackpoints: + Distance: + at + m + km + km/h You don\'t have any tracks. To record a track,\npress Menu then New track Unable to create a new track: {0} @@ -54,6 +59,9 @@ Track Details + Speed: + average + max Start time: End time: Starts at: From 0b3ad703eaadf6849a9be761b1255fdd1fe15541 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 11 Jan 2016 23:15:42 +0100 Subject: [PATCH 2/5] Reorganized distance calculations Updating details for the active track --- .../osmtracker/activity/TrackDetail.java | 102 ++++++--- .../osmtracker/activity/TrackManager.java | 31 ++- .../android/osmtracker/db/DataHelper.java | 23 -- .../osmtracker/db/TracklistAdapter.java | 65 ++++-- .../osmtracker/db/model/TrackStatistics.java | 211 +++++------------- .../osmtracker/layout/GpsStatusRecord.java | 4 - app/src/main/res/values/strings.xml | 6 +- 7 files changed, 205 insertions(+), 237 deletions(-) diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java index 8058f47f..713848bc 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java @@ -21,8 +21,10 @@ import android.content.ContentUris; import android.content.Intent; import android.database.Cursor; +import android.database.ContentObserver; import android.graphics.Paint; import android.os.Bundle; +import android.os.Handler; import android.preference.PreferenceManager; import android.view.Menu; import android.view.MenuInflater; @@ -73,10 +75,32 @@ public class TrackDetail extends TrackDetailEditor implements AdapterView.OnItem * List with track info */ private ListView lv; + + /** + * Adapter for the above list + */ + TrackDetailSimpleAdapter adapter = null; + /** + * Observes changes on trackpoints + */ + private ContentObserver trackpointContentObserver; + + /** + * Statistics for the track + */ + private TrackStatistics trackStatistics; + + /** + * Data for the track info + */ + private List> trackData = new ArrayList> (); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.trackdetail, getIntent().getExtras().getLong(Schema.COL_TRACK_ID)); + + trackStatistics = new TrackStatistics(trackId, getContentResolver()); lv = (ListView) findViewById(R.id.trackdetail_list); @@ -101,19 +125,29 @@ public void onClick(View v) { // Do not show soft keyboard by default getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + // Create content observer for trackpoints + trackpointContentObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + pathChanged(); + } + }; + getContentResolver().registerContentObserver( + TrackContentProvider.trackPointsUri(trackId), + true, trackpointContentObserver); // further work is done in onResume. } - @Override - protected void onResume() { - super.onResume(); - + /** + * Update trackData (doesn't update the list view though) + */ + private void updateTrack() { // Query the track values ContentResolver cr = getContentResolver(); Cursor cursor = cr.query( ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId), null, null, null, null); - + if (! cursor.moveToFirst()) { // This shouldn't occur, it's here just in case. // So, don't make each language translate/localize it. @@ -122,70 +156,69 @@ protected void onResume() { finish(); return; // <--- Early return --- } + + trackData.clear(); // Bind WP count, TP count, start date, etc. // Fill name-field only if empty (in case changed by user/restored by onRestoreInstanceState) Track t = Track.build(trackId, cursor, cr, true); + trackStatistics.update(); bindTrack(t); - TrackStatistics stat = DataHelper.getTrackStatistics(trackId, cr); - - String from[] = new String[]{ITEM_KEY, ITEM_VALUE}; - int[] to = new int[] {R.id.trackdetail_item_key, R.id.trackdetail_item_value}; // Waypoint count final int wpCount = t.getWpCount(); trackHasWaypoints = (wpCount > 0); - List> data = new ArrayList>(); + HashMap map = new HashMap(); map.put(ITEM_KEY, getResources().getString(R.string.trackmgr_waypoints_count)); map.put(ITEM_VALUE, Integer.toString(wpCount)); - data.add(WP_COUNT_INDEX, map); + trackData.add(WP_COUNT_INDEX, map); // Trackpoint count map = new HashMap(); map.put(ITEM_KEY, getResources().getString(R.string.trackmgr_trackpoints_count)); map.put(ITEM_VALUE, Integer.toString(t.getTpCount())); - data.add(map); + trackData.add(map); // Distance map = new HashMap(); map.put(ITEM_KEY, getResources().getString(R.string.trackmgr_distance)); - map.put(ITEM_VALUE, TracklistAdapter.distanceToString(stat.totalLength(), getResources())); - data.add(map); + map.put(ITEM_VALUE, TracklistAdapter.distanceToString(trackStatistics.totalLength(), getResources())); + trackData.add(map); // Speed map = new HashMap(); map.put(ITEM_KEY, getResources().getString(R.string.trackdetail_speed)); - map.put(ITEM_VALUE, TracklistAdapter.speedToString(stat.averageSpeed(), getResources()) + " " + + map.put(ITEM_VALUE, TracklistAdapter.speedToString(trackStatistics.averageSpeed(), getResources()) + " " + getResources().getString(R.string.trackdetail_speed_average) + ", " + - TracklistAdapter.speedToString(stat.maximumSpeed(), getResources()) + " " + + TracklistAdapter.speedToString(trackStatistics.maximumSpeed(), getResources()) + " " + getResources().getString(R.string.trackdetail_speed_max)); - data.add(map); + trackData.add(map); // Start date map = new HashMap(); map.put(ITEM_KEY, getResources().getString(R.string.trackdetail_startdate)); map.put(ITEM_VALUE, t.getStartDateAsString()); - data.add(map); + trackData.add(map); // End date map = new HashMap(); map.put(ITEM_KEY, getResources().getString(R.string.trackdetail_enddate)); map.put(ITEM_VALUE, t.getEndDateAsString()); - data.add(map); + trackData.add(map); // Start point map = new HashMap(); map.put(ITEM_KEY, getResources().getString(R.string.trackdetail_startloc)); map.put(ITEM_VALUE, MercatorProjection.formatDegreesAsDMS(t.getStartLat(), true) + " " + MercatorProjection.formatDegreesAsDMS(t.getStartLong(), false)); - data.add(map); + trackData.add(map); // End point map = new HashMap(); map.put(ITEM_KEY, getResources().getString(R.string.trackdetail_endloc)); map.put(ITEM_VALUE, MercatorProjection.formatDegreesAsDMS(t.getEndLat(), true) + " " + MercatorProjection.formatDegreesAsDMS(t.getEndLong(), false)); - data.add(map); + trackData.add(map); // OSM Upload date map = new HashMap(); @@ -195,7 +228,7 @@ protected void onResume() { } else { map.put(ITEM_VALUE, DateFormat.getDateTimeInstance().format(new Date(cursor.getLong(cursor.getColumnIndex(Schema.COL_EXPORT_DATE))))); } - data.add(map); + trackData.add(map); // Exported date. Should be the last item in order to be refreshed // if the user exports the track @@ -206,11 +239,20 @@ protected void onResume() { } else { map.put(ITEM_VALUE, (DateFormat.getDateTimeInstance().format(new Date(cursor.getLong(cursor.getColumnIndex(Schema.COL_EXPORT_DATE)))))); } - data.add(map); + trackData.add(map); cursor.close(); - - TrackDetailSimpleAdapter adapter = new TrackDetailSimpleAdapter(data, from, to); + } + + @Override + protected void onResume() { + super.onResume(); + + updateTrack(); + + String from[] = new String[]{ITEM_KEY, ITEM_VALUE}; + int[] to = new int[] {R.id.trackdetail_item_key, R.id.trackdetail_item_value}; + adapter = new TrackDetailSimpleAdapter(trackData, from, to); lv.setAdapter(adapter); // Click on Waypoint count to see the track's WaypointList @@ -281,6 +323,16 @@ public void onItemClick(AdapterView parent, View view, final int position, fi startActivity(i); } + /** + * On track path changed, update track info + */ + private void pathChanged() { + updateTrack(); + // Update the list view + if (adapter != null) + adapter.notifyDataSetChanged(); + } + /** * Extend SimpleAdapter so we can underline the clickable Waypoint count. * Always uses R.layout.trackdetail_item as its list item resource. diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java index a14c54e5..5a3f552c 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java @@ -2,6 +2,7 @@ import java.io.File; import java.util.Date; +import java.util.TreeMap; import me.guillaumin.android.osmtracker.OSMTracker; import me.guillaumin.android.osmtracker.R; @@ -9,11 +10,13 @@ import me.guillaumin.android.osmtracker.db.TrackContentProvider; import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema; import me.guillaumin.android.osmtracker.db.TracklistAdapter; +import me.guillaumin.android.osmtracker.db.model.TrackStatistics; import me.guillaumin.android.osmtracker.exception.CreateTrackException; import me.guillaumin.android.osmtracker.gpx.ExportToStorageTask; import me.guillaumin.android.osmtracker.util.FileSystemUtils; import android.app.AlertDialog; import android.app.ListActivity; +import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.DialogInterface; @@ -57,6 +60,32 @@ public class TrackManager extends ListActivity { /** The previous item visible, or -1; for scrolling back to its position in {@link #onResume()} */ private int prevItemVisible = -1; + + /** Statistics for all existing tracks */ + private TreeMap tracksStatistics = new TreeMap (); + + /** + * Get statistics for a given track + * If doesn't exists, create and calculate from the database + */ + public TrackStatistics getTrackStatistics(long trackId) { + TrackStatistics stat; + if (! tracksStatistics.containsKey(trackId)) { + stat = new TrackStatistics(trackId, getContentResolver()); + tracksStatistics.put(trackId, stat); + } else { + stat = tracksStatistics.get(trackId); + stat.update(); + } + return stat; + } + + /** + * Remove statistics for a given track + */ + public void removeTrackStatistics(long trackId) { + tracksStatistics.remove(trackId); + } @Override protected void onCreate(Bundle savedInstanceState) { @@ -419,7 +448,7 @@ private void deleteTrack(long id) { } // Delete the statistics - DataHelper.removeTrackStatistics(id); + removeTrackStatistics(id); } /** diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/db/DataHelper.java b/app/src/main/java/me/guillaumin/android/osmtracker/db/DataHelper.java index 24cd984c..34a92807 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/db/DataHelper.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/db/DataHelper.java @@ -2,12 +2,9 @@ import java.io.File; import java.text.SimpleDateFormat; -import java.util.TreeMap; -import java.lang.RuntimeException; import me.guillaumin.android.osmtracker.OSMTracker; import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema; -import me.guillaumin.android.osmtracker.db.model.TrackStatistics; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -68,9 +65,6 @@ public class DataHelper { */ private Context context; - /** Statistics for all existing tracks */ - private static TreeMap tracksStatistics = new TreeMap (); - /** * ContentResolver to interact with content provider */ @@ -131,7 +125,6 @@ public void track(long trackId, Location location, float azimuth, int accuracy) } Uri trackUri = ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId); - getTrackStatistics(trackId, contentResolver).update(location); contentResolver.insert(Uri.withAppendedPath(trackUri, Schema.TBL_TRACKPOINT + "s"), values); } @@ -380,20 +373,4 @@ public static File getTrackDirectory(long trackId) { return _return; } - /** - * Get statistics for a given track - * If doesn't exists, create and calculate from the database - */ - public static TrackStatistics getTrackStatistics(long trackId, ContentResolver resolver) { - if (! tracksStatistics.containsKey(trackId)) - tracksStatistics.put(trackId, new TrackStatistics(trackId, resolver)); // getContentResolver() if non-static member - return tracksStatistics.get(trackId); - } - - /** - * Remove statistics for a given track - */ - public static void removeTrackStatistics(long trackId) { - tracksStatistics.remove(trackId); - } } diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/db/TracklistAdapter.java b/app/src/main/java/me/guillaumin/android/osmtracker/db/TracklistAdapter.java index da36fba1..d2ef5316 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/db/TracklistAdapter.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/db/TracklistAdapter.java @@ -4,6 +4,8 @@ import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema; import me.guillaumin.android.osmtracker.db.model.Track; import me.guillaumin.android.osmtracker.db.model.TrackStatistics; +import me.guillaumin.android.osmtracker.activity.TrackManager; + import android.content.Context; import android.content.res.Resources; import android.database.Cursor; @@ -22,37 +24,52 @@ * */ public class TracklistAdapter extends CursorAdapter { + + private static final float STOP_SHOWING_DECIMALS_AFTER = 20; + + /** The TrackManager who created us */ + private TrackManager owner; public static String distanceToString(float distance, Resources resources) { - if (distance < 100) - return String.format("%d %s", Math.round(distance), - resources.getString(R.string.trackmgr_distance_meters)); - else if (distance < 1000) - return String.format("%d %s", 10*Math.round(distance/10), - resources.getString(R.string.trackmgr_distance_meters)); - else if (distance < 10000) - return String.format("%.1f %s", distance/1000, - resources.getString(R.string.trackmgr_distance_kilometers)); - else - return String.format("%d %s", Math.round(distance/1000), - resources.getString(R.string.trackmgr_distance_kilometers)); + if (distance < 100) { + // Exact meters + return resources.getString(R.string.trackmgr_distance_in_meters).replace("{0}", + String.valueOf(Math.round(distance))); + } else if (distance < 1000) { + // Round to 10 meters + return resources.getString(R.string.trackmgr_distance_in_meters).replace("{0}", + String.valueOf(10 * Math.round(distance / 10))); + } else if (distance < STOP_SHOWING_DECIMALS_AFTER*1000) { + // Kilometers with 1 decimal + return resources.getString(R.string.trackmgr_distance_in_kilometers).replace("{0}", + String.format("%.1f", distance / 1000)); + } else { + // Whole kilometers + return resources.getString(R.string.trackmgr_distance_in_kilometers).replace("{0}", + String.valueOf(Math.round(distance / 1000))); + } } public static String speedToString(float speed, Resources resources) { - float kmph = (float)3.6*speed; - if (kmph < 1) - return String.format("%.1g %s", kmph, - resources.getString(R.string.trackmgr_speed_kmph)); - else if (kmph < 20) - return String.format("%.1f %s", kmph, - resources.getString(R.string.trackmgr_speed_kmph)); - else - return String.format("%d %s", Math.round(kmph), - resources.getString(R.string.trackmgr_speed_kmph)); + float kmph = (float)3.6*speed; // convert meters per second to kilometers per hour + if (kmph < 1) { + // Round to one non-zero digit + return resources.getString(R.string.trackmgr_speed_in_kmph).replace("{0}", + String.format("%.1g", kmph)); + } else if (kmph < STOP_SHOWING_DECIMALS_AFTER) { + // Round to one decimal + return resources.getString(R.string.trackmgr_speed_in_kmph).replace("{0}", + String.format("%.1f", kmph)); + } else { + // Round to integer + return resources.getString(R.string.trackmgr_speed_in_kmph).replace("{0}", + String.valueOf(Math.round(kmph))); + } } public TracklistAdapter(Context context, Cursor c) { super(context, c); + owner = (TrackManager) context; } @Override @@ -66,7 +83,7 @@ public View newView(Context context, Cursor cursor, ViewGroup vg) { vg, false); return view; } - + /** * Do the binding between data and item view. * @@ -115,7 +132,7 @@ private View bind(Cursor cursor, View v, Context context) { // Bind WP count, TP count, name Track t = Track.build(trackId, cursor, context.getContentResolver(), false); - TrackStatistics stat = DataHelper.getTrackStatistics(trackId, context.getContentResolver()); + TrackStatistics stat = owner.getTrackStatistics(trackId); vTps.setText(Integer.toString(t.getTpCount())); vWps.setText(Integer.toString(t.getWpCount())); vDistance.setText(distanceToString(stat.totalLength(), context.getResources())); diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java b/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java index 1825276e..58d9927f 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java @@ -21,138 +21,32 @@ public class TrackStatistics { private double lastLatitude; private double lastLongitude; private long lastTime; + private int lastId; private float timeMoving; - - /** - * Copypasta from osmand - */ - private static void computeDistanceAndBearing(double lat1, double lon1, - double lat2, double lon2, float[] results) { - // Based on http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf - // using the "Inverse Formula" (section 4) - int MAXITERS = 20; - // Convert lat/long to radians - lat1 *= Math.PI / 180.0; - lat2 *= Math.PI / 180.0; - lon1 *= Math.PI / 180.0; - lon2 *= Math.PI / 180.0; - - double a = 6378137.0; // WGS84 major axis - double b = 6356752.3142; // WGS84 semi-major axis - double f = (a - b) / a; - double aSqMinusBSqOverBSq = (a * a - b * b) / (b * b); - - double L = lon2 - lon1; - double A = 0.0; - double U1 = Math.atan((1.0 - f) * Math.tan(lat1)); - double U2 = Math.atan((1.0 - f) * Math.tan(lat2)); - - double cosU1 = Math.cos(U1); - double cosU2 = Math.cos(U2); - double sinU1 = Math.sin(U1); - double sinU2 = Math.sin(U2); - double cosU1cosU2 = cosU1 * cosU2; - double sinU1sinU2 = sinU1 * sinU2; - - double sigma = 0.0; - double deltaSigma = 0.0; - double cosSqAlpha = 0.0; - double cos2SM = 0.0; - double cosSigma = 0.0; - double sinSigma = 0.0; - double cosLambda = 0.0; - double sinLambda = 0.0; - - double lambda = L; // initial guess - for (int iter = 0; iter < MAXITERS; iter++) { - double lambdaOrig = lambda; - cosLambda = Math.cos(lambda); - sinLambda = Math.sin(lambda); - double t1 = cosU2 * sinLambda; - double t2 = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda; - double sinSqSigma = t1 * t1 + t2 * t2; // (14) - sinSigma = Math.sqrt(sinSqSigma); - cosSigma = sinU1sinU2 + cosU1cosU2 * cosLambda; // (15) - sigma = Math.atan2(sinSigma, cosSigma); // (16) - double sinAlpha = (sinSigma == 0) ? 0.0 : - cosU1cosU2 * sinLambda / sinSigma; // (17) - cosSqAlpha = 1.0 - sinAlpha * sinAlpha; - cos2SM = (cosSqAlpha == 0) ? 0.0 : - cosSigma - 2.0 * sinU1sinU2 / cosSqAlpha; // (18) - - double uSquared = cosSqAlpha * aSqMinusBSqOverBSq; // defn - A = 1 + (uSquared / 16384.0) * // (3) - (4096.0 + uSquared * - (-768 + uSquared * (320.0 - 175.0 * uSquared))); - double B = (uSquared / 1024.0) * // (4) - (256.0 + uSquared * - (-128.0 + uSquared * (74.0 - 47.0 * uSquared))); - double C = (f / 16.0) * - cosSqAlpha * - (4.0 + f * (4.0 - 3.0 * cosSqAlpha)); // (10) - double cos2SMSq = cos2SM * cos2SM; - deltaSigma = B * sinSigma * // (6) - (cos2SM + (B / 4.0) * - (cosSigma * (-1.0 + 2.0 * cos2SMSq) - - (B / 6.0) * cos2SM * - (-3.0 + 4.0 * sinSigma * sinSigma) * - (-3.0 + 4.0 * cos2SMSq))); - - lambda = L + - (1.0 - C) * f * sinAlpha * - (sigma + C * sinSigma * - (cos2SM + C * cosSigma * - (-1.0 + 2.0 * cos2SM * cos2SM))); // (11) - - double delta = (lambda - lambdaOrig) / lambda; - if (Math.abs(delta) < 1.0e-12) { - break; - } - } - - float distance = (float) (b * A * (sigma - deltaSigma)); - results[0] = distance; - if (results.length > 1) { - float initialBearing = (float) Math.atan2(cosU2 * sinLambda, - cosU1 * sinU2 - sinU1 * cosU2 * cosLambda); - initialBearing *= 180.0 / Math.PI; - results[1] = initialBearing; - if (results.length > 2) { - float finalBearing = (float) Math.atan2(cosU1 * sinLambda, - -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda); - finalBearing *= 180.0 / Math.PI; - results[2] = finalBearing; - } - } + private ContentResolver contentResolver; + + private static double square(double x) { + return x*x; } /** - * Copypasta from osmand - * - * Computes the approximate distance in meters between two - * locations, and optionally the initial and final bearings of the - * shortest path between them. Distance and bearing are defined using the - * WGS84 ellipsoid. - * - *

The computed distance is stored in results[0]. If results has length - * 2 or greater, the initial bearing is stored in results[1]. If results has - * length 3 or greater, the final bearing is stored in results[2]. - * - * @param startLatitude the starting latitude - * @param startLongitude the starting longitude - * @param endLatitude the ending latitude - * @param endLongitude the ending longitude - * @param results an array of floats to hold the results - * - * @throws IllegalArgumentException if results is null or has length < 1 + * Compute the approximate distance in meters between two locations, with 0.5% precision */ - public static void distanceBetween(double startLatitude, double startLongitude, - double endLatitude, double endLongitude, float[] results) { - if (results == null || results.length < 1) { - throw new IllegalArgumentException("results is null or has length < 1"); - } - computeDistanceAndBearing(startLatitude, startLongitude, - endLatitude, endLongitude, results); + public static float getDistance(double lat1, double long1, double lat2, double long2) { + final double R = 6370e3; // Earth radius + final double rad_per_deg = Math.PI/180; // radians per degree + + lat1 *= rad_per_deg; + long1 *= rad_per_deg; + lat2 *= rad_per_deg; + long2 *= rad_per_deg; + + double dLat = lat2-lat1; + double dLon = long2-long1; + + double A = square(Math.sin(0.5*dLat)) + Math.cos(lat1) * Math.cos(lat2) * square(Math.sin(0.5*dLon)); + + return (float)(2 * R * Math.asin(Math.sqrt(A))); } public TrackStatistics () { @@ -171,12 +65,42 @@ public TrackStatistics () { */ public TrackStatistics (final long trackId, ContentResolver cr) { this.trackId = trackId; + contentResolver = cr; length = 0; maxSpeed = 0; pointCount = 0; timeMoving = 0; + + update(); + } + + private void addPoint(double latitude, double longitude, float accuracy, float speed, long time, int point_id) { + if (pointCount > 0) { + // The "distance and time only counts when the speed is non-zero" principle has been borrowed from osmand + if ((speed > 0) && (time != 0) && (lastTime != 0)) { + length += getDistance(lastLatitude, lastLongitude, latitude, longitude); + timeMoving += time - lastTime; + } + } + pointCount += 1; - Cursor cursor = cr.query(TrackContentProvider.trackPointsUri(trackId), null, null, null, null); + if(speed > maxSpeed) + maxSpeed = speed; + lastLatitude = latitude; + lastLongitude = longitude; + lastTime = time; + lastId = point_id; + } + + /** + * Update the statistics by reading new points from the database + */ + public void update() { + String selection = null; + if (pointCount > 0) + selection = Schema.COL_ID + " > " + String.valueOf(lastId); + Cursor cursor = contentResolver.query(TrackContentProvider.trackPointsUri(trackId), null, + selection, null, null); if(! cursor.moveToFirst()) return; @@ -186,42 +110,15 @@ public TrackStatistics (final long trackId, ContentResolver cr) { float speed = cursor.getFloat(cursor.getColumnIndex(Schema.COL_SPEED)); float accuracy = cursor.getFloat(cursor.getColumnIndex(Schema.COL_ACCURACY)); long time = cursor.getLong(cursor.getColumnIndex(Schema.COL_TIMESTAMP)); + int id = cursor.getInt(cursor.getColumnIndex(Schema.COL_ID)); - update(latitude, longitude, accuracy, speed, time); + addPoint(latitude, longitude, accuracy, speed, time, id); cursor.moveToNext(); } cursor.close(); } - /** - * Update the statistics upon adding a new point to the track - */ - public void update(Location trackPoint){ - update(trackPoint.getLatitude(), trackPoint.getLongitude(), trackPoint.getAccuracy(), - trackPoint.getSpeed(), trackPoint.getTime()); - } - - private void update(double latitude, double longitude, float accuracy, float speed, long time) { - if (pointCount > 0) { - float[] distance = new float[1]; - distanceBetween(lastLatitude, lastLongitude, latitude, longitude, distance); - - // The "distance and time only counts at non-zero speed" principle is borrowed from osmand - if ((speed > 0) && (time != 0) && (lastTime != 0)) { - length += distance[0]; - timeMoving += time - lastTime; - } - } - pointCount += 1; - - if(speed > maxSpeed) - maxSpeed = speed; - lastLatitude = latitude; - lastLongitude = longitude; - lastTime = time; - } - /** * Get track length, in meters */ diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/layout/GpsStatusRecord.java b/app/src/main/java/me/guillaumin/android/osmtracker/layout/GpsStatusRecord.java index b00440c4..b20ba423 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/layout/GpsStatusRecord.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/layout/GpsStatusRecord.java @@ -253,8 +253,4 @@ public void manageRecordingIndicator(boolean isTracking) { } } - public TrackLogger getActivity() { - return activity; - } - } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ee0a4514..5bcb7b51 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,9 +35,9 @@ Trackpoints: Distance: at - m - km - km/h + {0} m + {0} km + {0} km/h You don\'t have any tracks. To record a track,\npress Menu then New track Unable to create a new track: {0} From 1314844db4747676bfc983d2199b7bf8245714ac Mon Sep 17 00:00:00 2001 From: user Date: Wed, 3 Feb 2016 13:35:12 +0100 Subject: [PATCH 3/5] Sharing track statistics data between TrackManager and TrackDetails --- .../osmtracker/activity/TrackDetail.java | 5 +- .../osmtracker/activity/TrackManager.java | 2 + .../osmtracker/db/model/TrackStatistics.java | 58 ++++++++++++++++++- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java index 713848bc..516e1d97 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java @@ -98,9 +98,10 @@ public class TrackDetail extends TrackDetailEditor implements AdapterView.OnItem @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState, R.layout.trackdetail, getIntent().getExtras().getLong(Schema.COL_TRACK_ID)); + Bundle extras = getIntent().getExtras(); + super.onCreate(savedInstanceState, R.layout.trackdetail, extras.getLong(Schema.COL_TRACK_ID)); - trackStatistics = new TrackStatistics(trackId, getContentResolver()); + trackStatistics = new TrackStatistics(trackId, getContentResolver(), extras); lv = (ListView) findViewById(R.id.trackdetail_list); diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java index 5a3f552c..5e59060a 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java @@ -380,6 +380,7 @@ public void onClick(DialogInterface dialog, int which) { case R.id.trackmgr_contextmenu_details: i = new Intent(this, TrackDetail.class); i.putExtra(Schema.COL_TRACK_ID, info.id); + i.putExtras(getTrackStatistics(info.id).getData()); startActivity(i); break; } @@ -405,6 +406,7 @@ protected void onListItemClick(ListView lv, View iv, final int position, final l // show track info i = new Intent(this, TrackDetail.class); i.putExtra(Schema.COL_TRACK_ID, id); + i.putExtras(getTrackStatistics(id).getData()); } startActivity(i); } diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java b/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java index 58d9927f..1f8d5218 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java @@ -4,8 +4,7 @@ import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema; import android.content.ContentResolver; import android.database.Cursor; -import android.location.Location; -import android.util.Log; +import android.os.Bundle; /** * Represents statistics, such as total length and maximum speed, for a Track @@ -74,6 +73,57 @@ public TrackStatistics (final long trackId, ContentResolver cr) { update(); } + private static final String BUNDLE_POINTCOUNT = "TrackStatistics.pointCount"; + private static final String BUNDLE_LENGTH = "TrackStatistics.length"; + private static final String BUNDLE_MAXSPEED = "TrackStatistics.maxSpeed"; + private static final String BUNDLE_LASTLATITUDE = "TrackStatistics.lastLatitude"; + private static final String BUNDLE_LASTLONGITUDE = "TrackStatistics.lastLongitude"; + private static final String BUNDLE_LASTTIME = "TrackStatistics.lastTime"; + private static final String BUNDLE_LASTID = "TrackStatistics.lastId"; + private static final String BUNDLE_TIMEMOVING = "TrackStatistics.timeMoving"; + + /** + * Encode the statistical data as Bundle + */ + public Bundle getData() { + Bundle data = new Bundle(); + data.putLong(BUNDLE_POINTCOUNT, pointCount); + data.putFloat(BUNDLE_LENGTH, length); + data.putFloat(BUNDLE_MAXSPEED, maxSpeed); + data.putDouble(BUNDLE_LASTLATITUDE, lastLatitude); + data.putDouble(BUNDLE_LASTLONGITUDE, lastLongitude); + data.putLong(BUNDLE_LASTTIME, lastTime); + data.putInt(BUNDLE_LASTID, lastId); + data.putFloat(BUNDLE_TIMEMOVING, timeMoving); + + return data; + } + + /** + * build a track statistics object with the given cursor and pre-existing statistical data + * + * @param trackId id of the track that will be built + * @param cr the content resolver to use + * @param data previously existing statistical data; the bundle isn't required to actually + * contain that data + */ + public TrackStatistics (final long trackId, ContentResolver cr, Bundle data) { + this.trackId = trackId; + contentResolver = cr; + + // If the Bundle doesn't contain the required data, everything will be zero, which is fine + pointCount = data.getLong(BUNDLE_POINTCOUNT); + length = data.getFloat(BUNDLE_LENGTH); + maxSpeed = data.getFloat(BUNDLE_MAXSPEED); + lastLatitude = data.getDouble(BUNDLE_LASTLATITUDE); + lastLongitude = data.getDouble(BUNDLE_LASTLONGITUDE); + lastTime = data.getLong(BUNDLE_LASTTIME); + lastId = data.getInt(BUNDLE_LASTID); + timeMoving = data.getFloat(BUNDLE_TIMEMOVING); + + update(); + } + private void addPoint(double latitude, double longitude, float accuracy, float speed, long time, int point_id) { if (pointCount > 0) { // The "distance and time only counts when the speed is non-zero" principle has been borrowed from osmand @@ -101,8 +151,10 @@ public void update() { selection = Schema.COL_ID + " > " + String.valueOf(lastId); Cursor cursor = contentResolver.query(TrackContentProvider.trackPointsUri(trackId), null, selection, null, null); - if(! cursor.moveToFirst()) + if(! cursor.moveToFirst()) { + cursor.close(); return; + } while (! cursor.isAfterLast()) { double latitude = cursor.getDouble(cursor.getColumnIndex(Schema.COL_LATITUDE)); From 6c9ed799045d4541b365fdc61f73ceb8d1ad41f8 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 3 Feb 2016 13:40:53 +0100 Subject: [PATCH 4/5] cleaned up useless imports --- .../me/guillaumin/android/osmtracker/activity/TrackDetail.java | 1 - .../me/guillaumin/android/osmtracker/activity/TrackManager.java | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java index 516e1d97..c5036652 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackDetail.java @@ -9,7 +9,6 @@ import me.guillaumin.android.osmtracker.OSMTracker; import me.guillaumin.android.osmtracker.R; -import me.guillaumin.android.osmtracker.db.DataHelper; import me.guillaumin.android.osmtracker.db.TrackContentProvider; import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema; import me.guillaumin.android.osmtracker.db.TracklistAdapter; diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java index 5e59060a..f1d60d5b 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java @@ -16,7 +16,6 @@ import me.guillaumin.android.osmtracker.util.FileSystemUtils; import android.app.AlertDialog; import android.app.ListActivity; -import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.DialogInterface; From d5958d8313eada0d7a19d6f35cf55a19a7b0bb28 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 12 Feb 2016 14:51:53 +0100 Subject: [PATCH 5/5] Code improvement/cleanup --- .../osmtracker/activity/TrackManager.java | 36 +++------------ .../osmtracker/db/TracklistAdapter.java | 10 ++-- .../osmtracker/db/model/TrackStatistics.java | 25 +++------- .../db/model/TrackStatisticsCollection.java | 46 +++++++++++++++++++ 4 files changed, 64 insertions(+), 53 deletions(-) create mode 100644 app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatisticsCollection.java diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java index f1d60d5b..f061de5b 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/activity/TrackManager.java @@ -10,7 +10,7 @@ import me.guillaumin.android.osmtracker.db.TrackContentProvider; import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema; import me.guillaumin.android.osmtracker.db.TracklistAdapter; -import me.guillaumin.android.osmtracker.db.model.TrackStatistics; +import me.guillaumin.android.osmtracker.db.model.TrackStatisticsCollection; import me.guillaumin.android.osmtracker.exception.CreateTrackException; import me.guillaumin.android.osmtracker.gpx.ExportToStorageTask; import me.guillaumin.android.osmtracker.util.FileSystemUtils; @@ -61,30 +61,7 @@ public class TrackManager extends ListActivity { private int prevItemVisible = -1; /** Statistics for all existing tracks */ - private TreeMap tracksStatistics = new TreeMap (); - - /** - * Get statistics for a given track - * If doesn't exists, create and calculate from the database - */ - public TrackStatistics getTrackStatistics(long trackId) { - TrackStatistics stat; - if (! tracksStatistics.containsKey(trackId)) { - stat = new TrackStatistics(trackId, getContentResolver()); - tracksStatistics.put(trackId, stat); - } else { - stat = tracksStatistics.get(trackId); - stat.update(); - } - return stat; - } - - /** - * Remove statistics for a given track - */ - public void removeTrackStatistics(long trackId) { - tracksStatistics.remove(trackId); - } + private TrackStatisticsCollection tracksStatistics; @Override protected void onCreate(Bundle savedInstanceState) { @@ -95,6 +72,7 @@ protected void onCreate(Bundle savedInstanceState) { if (savedInstanceState != null) { prevItemVisible = savedInstanceState.getInt(PREV_VISIBLE, -1); } + tracksStatistics = new TrackStatisticsCollection(getContentResolver()); } @Override @@ -103,7 +81,7 @@ protected void onResume() { TrackContentProvider.CONTENT_URI_TRACK, null, null, null, Schema.COL_START_DATE + " desc"); startManagingCursor(cursor); - setListAdapter(new TracklistAdapter(TrackManager.this, cursor)); + setListAdapter(new TracklistAdapter(TrackManager.this, cursor, tracksStatistics)); getListView().setEmptyView(findViewById(R.id.trackmgr_empty)); // undo change from onPause // Is any track active? @@ -379,7 +357,7 @@ public void onClick(DialogInterface dialog, int which) { case R.id.trackmgr_contextmenu_details: i = new Intent(this, TrackDetail.class); i.putExtra(Schema.COL_TRACK_ID, info.id); - i.putExtras(getTrackStatistics(info.id).getData()); + i.putExtras(tracksStatistics.get(info.id).getData()); startActivity(i); break; } @@ -405,7 +383,7 @@ protected void onListItemClick(ListView lv, View iv, final int position, final l // show track info i = new Intent(this, TrackDetail.class); i.putExtra(Schema.COL_TRACK_ID, id); - i.putExtras(getTrackStatistics(id).getData()); + i.putExtras(tracksStatistics.get(id).getData()); } startActivity(i); } @@ -449,7 +427,7 @@ private void deleteTrack(long id) { } // Delete the statistics - removeTrackStatistics(id); + tracksStatistics.remove(id); } /** diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/db/TracklistAdapter.java b/app/src/main/java/me/guillaumin/android/osmtracker/db/TracklistAdapter.java index d2ef5316..e08e6311 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/db/TracklistAdapter.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/db/TracklistAdapter.java @@ -4,6 +4,7 @@ import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema; import me.guillaumin.android.osmtracker.db.model.Track; import me.guillaumin.android.osmtracker.db.model.TrackStatistics; +import me.guillaumin.android.osmtracker.db.model.TrackStatisticsCollection; import me.guillaumin.android.osmtracker.activity.TrackManager; import android.content.Context; @@ -27,8 +28,7 @@ public class TracklistAdapter extends CursorAdapter { private static final float STOP_SHOWING_DECIMALS_AFTER = 20; - /** The TrackManager who created us */ - private TrackManager owner; + private TrackStatisticsCollection tracksStatistics; public static String distanceToString(float distance, Resources resources) { if (distance < 100) { @@ -67,9 +67,9 @@ public static String speedToString(float speed, Resources resources) { } } - public TracklistAdapter(Context context, Cursor c) { + public TracklistAdapter(Context context, Cursor c, TrackStatisticsCollection statistics) { super(context, c); - owner = (TrackManager) context; + tracksStatistics = statistics; } @Override @@ -132,7 +132,7 @@ private View bind(Cursor cursor, View v, Context context) { // Bind WP count, TP count, name Track t = Track.build(trackId, cursor, context.getContentResolver(), false); - TrackStatistics stat = owner.getTrackStatistics(trackId); + TrackStatistics stat = tracksStatistics.get(trackId); vTps.setText(Integer.toString(t.getTpCount())); vWps.setText(Integer.toString(t.getWpCount())); vDistance.setText(distanceToString(stat.totalLength(), context.getResources())); diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java b/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java index 1f8d5218..b6aa391d 100644 --- a/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java +++ b/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatistics.java @@ -22,7 +22,7 @@ public class TrackStatistics { private long lastTime; private int lastId; private float timeMoving; - private ContentResolver contentResolver; + private final ContentResolver contentResolver; private static double square(double x) { return x*x; @@ -35,27 +35,16 @@ public static float getDistance(double lat1, double long1, double lat2, double l final double R = 6370e3; // Earth radius final double rad_per_deg = Math.PI/180; // radians per degree - lat1 *= rad_per_deg; - long1 *= rad_per_deg; - lat2 *= rad_per_deg; - long2 *= rad_per_deg; - double dLat = lat2-lat1; double dLon = long2-long1; - double A = square(Math.sin(0.5*dLat)) + Math.cos(lat1) * Math.cos(lat2) * square(Math.sin(0.5*dLon)); + double A = square(Math.sin(0.5*dLat*rad_per_deg)) + + Math.cos(lat1*rad_per_deg) * Math.cos(lat2*rad_per_deg) * + square(Math.sin(0.5*dLon*rad_per_deg)); return (float)(2 * R * Math.asin(Math.sqrt(A))); } - public TrackStatistics () { - trackId = -1; - length = 0; - maxSpeed = 0; - pointCount = 0; - timeMoving = 0; - } - /** * build a track statistics object with the given cursor * @@ -65,11 +54,9 @@ public TrackStatistics () { public TrackStatistics (final long trackId, ContentResolver cr) { this.trackId = trackId; contentResolver = cr; - length = 0; - maxSpeed = 0; - pointCount = 0; - timeMoving = 0; + // pointCount, length, maxSpeed and timeMoving are all implicitly initialized to + // zero, which is what we need update(); } diff --git a/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatisticsCollection.java b/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatisticsCollection.java new file mode 100644 index 00000000..765af67c --- /dev/null +++ b/app/src/main/java/me/guillaumin/android/osmtracker/db/model/TrackStatisticsCollection.java @@ -0,0 +1,46 @@ +package me.guillaumin.android.osmtracker.db.model; + +import me.guillaumin.android.osmtracker.db.model.TrackStatistics; +import android.content.ContentResolver; +import java.util.TreeMap; + + +/** + * Collection of statistics for several tracks + * + * @author Arseniy Lartsev + * + */ +public class TrackStatisticsCollection { + /** Statistics for all existing tracks */ + private TreeMap tracksStatistics = new TreeMap (); + + private final ContentResolver contentResolver; + + public TrackStatisticsCollection(ContentResolver resolver) { + contentResolver = resolver; + } + + /** + * Get the latest statistics for a given track + * If doesn't exists, create and calculate from the database + */ + public TrackStatistics get(long trackId) { + TrackStatistics stat; + if (! tracksStatistics.containsKey(trackId)) { + stat = new TrackStatistics(trackId, contentResolver); + tracksStatistics.put(trackId, stat); + } else { + stat = tracksStatistics.get(trackId); + stat.update(); + } + return stat; + } + + /** + * Remove statistics for a given track + */ + public void remove(long trackId) { + tracksStatistics.remove(trackId); + } +}