From c7899119716b0263b642c741f214bc2a8f684c7a Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Fri, 15 Aug 2025 18:45:45 +0200 Subject: [PATCH 1/8] Create multiple track segments when tracking is stopped and later resumed to avoid criss-crossing lines on map --- .../osmtracker/activity/DisplayTrackMap.java | 31 ++++++---- .../java/net/osmtracker/db/DataHelper.java | 4 +- .../net/osmtracker/db/DatabaseHelper.java | 6 +- .../osmtracker/db/TrackContentProvider.java | 11 +++- .../java/net/osmtracker/db/model/Track.java | 13 +++- .../net/osmtracker/gpx/ExportTrackTask.java | 10 ++- .../net/osmtracker/overlay/Polylines.java | 61 +++++++++++++++++++ .../net/osmtracker/service/gps/GPSLogger.java | 40 +++++++++++- 8 files changed, 157 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/net/osmtracker/overlay/Polylines.java diff --git a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java index 22720fbbc..2d178e419 100644 --- a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java +++ b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java @@ -23,6 +23,7 @@ import net.osmtracker.R; import net.osmtracker.db.TrackContentProvider; import net.osmtracker.overlay.WayPointsOverlay; +import net.osmtracker.overlay.Polylines; import org.osmdroid.api.IMapController; import org.osmdroid.config.Configuration; @@ -33,7 +34,6 @@ import org.osmdroid.util.GeoPoint; import org.osmdroid.views.CustomZoomButtonsController; import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.Polyline; import org.osmdroid.views.overlay.ScaleBarOverlay; import org.osmdroid.views.overlay.mylocation.SimpleLocationOverlay; @@ -118,7 +118,7 @@ public class DisplayTrackMap extends Activity { /** * OSM view overlay that displays current path */ - private Polyline polyline; + private Polylines polylines; /** * OSM view overlay that displays waypoints @@ -158,6 +158,11 @@ public class DisplayTrackMap extends Activity { */ private Integer lastTrackPointIdProcessed = null; + /** + * The id of the last segment + */ + private int prevSegmentId=-1; + /** * Observes changes on track points */ @@ -303,6 +308,7 @@ private void resumeActivity() { // This ensures that all waypoints for the track will be reloaded // from the database to populate the path layout lastTrackPointIdProcessed = null; + prevSegmentId = -1; // Reload path pathChanged(); @@ -321,7 +327,7 @@ protected void onPause() { getContentResolver().unregisterContentObserver(trackpointContentObserver); // Clear the points list. - polyline.setPoints(new ArrayList<>()); + polylines.clear(); super.onPause(); } @@ -387,12 +393,8 @@ private void createOverlays() { this.getWindowManager().getDefaultDisplay().getMetrics(metrics); // set with to hopefully DPI independent 0.5mm - polyline = new Polyline(); - Paint paint = polyline.getOutlinePaint(); - paint.setColor(Color.BLUE); - paint.setStrokeWidth((float) (metrics.densityDpi / 25.4 / 2)); - osmView.getOverlayManager().add(polyline); - + polylines = new Polylines(Color.BLUE, (float)(metrics.densityDpi / 25.4 / 2), osmView); + myLocationOverlay = new SimpleLocationOverlay(this); osmView.getOverlays().add(myLocationOverlay); @@ -439,7 +441,7 @@ private void pathChanged() { // Projection: The columns to retrieve. Here, we want the latitude, // longitude and primary key only - String[] projection = {TrackContentProvider.Schema.COL_LATITUDE, TrackContentProvider.Schema.COL_LONGITUDE, TrackContentProvider.Schema.COL_ID}; + String[] projection = {TrackContentProvider.Schema.COL_LATITUDE, TrackContentProvider.Schema.COL_LONGITUDE, TrackContentProvider.Schema.COL_ID, TrackContentProvider.Schema.COL_SEG_ID }; // Selection: The where clause to use String selection = null; // SelectionArgs: The parameter replacements to use for the '?' in the selection @@ -470,13 +472,20 @@ private void pathChanged() { int primaryKeyColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_ID); int latitudeColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_LATITUDE); int longitudeColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_LONGITUDE); + int segmentIdColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_SEG_ID); // Add each new point to the track while (!c.isAfterLast()) { lastLat = c.getDouble(latitudeColumnIndex); lastLon = c.getDouble(longitudeColumnIndex); lastTrackPointIdProcessed = c.getInt(primaryKeyColumnIndex); - polyline.addPoint(new GeoPoint(lastLat, lastLon)); + int segmentId = c.getInt(segmentIdColumnIndex); + if(segmentId != prevSegmentId) { + polylines.nextSegment(); + } + prevSegmentId = segmentId; + + polylines.addPoint(new GeoPoint(lastLat, lastLon)); if (doInitialBoundsCalc) { if (lastLat < minLat) minLat = lastLat; if (lastLon < minLon) minLon = lastLon; diff --git a/app/src/main/java/net/osmtracker/db/DataHelper.java b/app/src/main/java/net/osmtracker/db/DataHelper.java index 37935bec1..436f3b60e 100644 --- a/app/src/main/java/net/osmtracker/db/DataHelper.java +++ b/app/src/main/java/net/osmtracker/db/DataHelper.java @@ -146,7 +146,7 @@ public DataHelper(Context c) { * @param pressure * atmospheric pressure */ - public void track(long trackId, Location location, float azimuth, int accuracy, float pressure) { + public void track(long trackId, Location location, float azimuth, int accuracy, float pressure, boolean newSeg, long segId) { Log.v(TAG, "Tracking (trackId=" + trackId + ") location: " + location + " azimuth: " + azimuth + ", accuracy: " + accuracy); ContentValues values = new ContentValues(); values.put(TrackContentProvider.Schema.COL_TRACK_ID, trackId); @@ -180,6 +180,8 @@ public void track(long trackId, Location location, float azimuth, int accuracy, values.put(TrackContentProvider.Schema.COL_ATMOSPHERIC_PRESSURE, pressure); } + values.put(TrackContentProvider.Schema.COL_SEG_ID, segId); + Uri trackUri = ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId); contentResolver.insert(Uri.withAppendedPath(trackUri, TrackContentProvider.Schema.TBL_TRACKPOINT + "s"), values); } diff --git a/app/src/main/java/net/osmtracker/db/DatabaseHelper.java b/app/src/main/java/net/osmtracker/db/DatabaseHelper.java index fda2032c5..70c6885f7 100644 --- a/app/src/main/java/net/osmtracker/db/DatabaseHelper.java +++ b/app/src/main/java/net/osmtracker/db/DatabaseHelper.java @@ -39,7 +39,9 @@ public class DatabaseHelper extends SQLiteOpenHelper { + TrackContentProvider.Schema.COL_TIMESTAMP + " long not null," + TrackContentProvider.Schema.COL_COMPASS + " double null," + TrackContentProvider.Schema.COL_COMPASS_ACCURACY + " integer null," - + TrackContentProvider.Schema.COL_ATMOSPHERIC_PRESSURE + " double null" + ")"; + + TrackContentProvider.Schema.COL_ATMOSPHERIC_PRESSURE + " double null," + + TrackContentProvider.Schema.COL_SEG_ID + " integer not null default 0" + + ")"; /** * SQL for creating index TRACKPOINT_idx (track id) @@ -202,6 +204,8 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("alter table " + TrackContentProvider.Schema.TBL_WAYPOINT + " add column " + TrackContentProvider.Schema.COL_ATMOSPHERIC_PRESSURE + " double null"); case 17: db.execSQL(SQL_CREATE_TABLE_NOTE); + case 18: + db.execSQL("alter table "+TrackContentProvider.Schema.TBL_TRACKPOINT + " add column " + TrackContentProvider.Schema.COL_SEG_ID + " integer default 0"); } } diff --git a/app/src/main/java/net/osmtracker/db/TrackContentProvider.java b/app/src/main/java/net/osmtracker/db/TrackContentProvider.java index a5129c8a4..011362e38 100644 --- a/app/src/main/java/net/osmtracker/db/TrackContentProvider.java +++ b/app/src/main/java/net/osmtracker/db/TrackContentProvider.java @@ -93,6 +93,11 @@ public class TrackContentProvider extends ContentProvider { "WHERE " + Schema.TBL_NOTE + "." + Schema.COL_TRACK_ID +" " + "= " + Schema.TBL_TRACK + "." + Schema.COL_ID +") " + "as " + Schema.COL_NOTE_COUNT, + "(SELECT max("+Schema.TBL_TRACKPOINT+"."+Schema.COL_SEG_ID+") " + + "FROM "+Schema.TBL_TRACKPOINT+" " + + "WHERE "+Schema.TBL_TRACKPOINT+"."+Schema.COL_TRACK_ID+" " + + "= " + Schema.TBL_TRACK + "." + Schema.COL_ID + ") " + + "as " + Schema.COL_MAX_SEG_ID }; /** @@ -593,12 +598,16 @@ public static final class Schema { public static final String COL_COMPASS = "compass_heading"; public static final String COL_COMPASS_ACCURACY = "compass_accuracy"; public static final String COL_ATMOSPHERIC_PRESSURE = "atmospheric_pressure"; - + + public static final String COL_SEG_ID = "segment_id"; + // virtual colums that are used in some sqls but dont exist in database public static final String COL_TRACKPOINT_COUNT = "tp_count"; public static final String COL_WAYPOINT_COUNT = "wp_count"; public static final String COL_NOTE_COUNT = "note_count"; + public static final String COL_MAX_SEG_ID = "max_segment_id"; + // Codes for UriMatcher public static final int URI_CODE_TRACK = 3; public static final int URI_CODE_TRACK_ID = 4; diff --git a/app/src/main/java/net/osmtracker/db/model/Track.java b/app/src/main/java/net/osmtracker/db/model/Track.java index 03261b482..6f6f5491e 100644 --- a/app/src/main/java/net/osmtracker/db/model/Track.java +++ b/app/src/main/java/net/osmtracker/db/model/Track.java @@ -51,7 +51,7 @@ public static OSMVisibility fromPosition(int position) { private String description; private OSMVisibility visibility; private List tags = new ArrayList(); - private int tpCount, wpCount, noteCount; + private int tpCount, wpCount, noteCount, maxSegId; private long trackDate; private long trackId; @@ -95,6 +95,9 @@ public static Track build(final long trackId, Cursor tc, ContentResolver cr, boo out.noteCount = tc.getInt(tc.getColumnIndex(TrackContentProvider.Schema.COL_NOTE_COUNT)); + int maxSegIdIdx = tc.getColumnIndex(TrackContentProvider.Schema.COL_MAX_SEG_ID); + out.maxSegId = tc.isNull(maxSegIdIdx) ? 0 :tc.getInt(maxSegIdIdx); + if(withExtraInformation){ out.readExtraInformation(); } @@ -151,6 +154,10 @@ public void setNoteCount(int noteCount) { this.noteCount = noteCount; } + public void setMaxSegId(int maxSegId) { + this.maxSegId = maxSegId; + } + public void setTracktDate(long tracktDate) { this.trackDate = tracktDate; } @@ -193,6 +200,10 @@ public Integer getWpCount() { return wpCount; } + public Integer getMaxSegId() { + return maxSegId; + } + public Integer getTpCount() { return tpCount; } diff --git a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java index c54faa2a3..662cee106 100644 --- a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java +++ b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java @@ -348,8 +348,16 @@ private void writeTrackPoints(String trackName, Writer fw, Cursor c, boolean fil fw.write("\t\t" + "" + "\n"); int i=0; + int prevSegId=-1; for(c.moveToFirst(); !c.isAfterLast(); c.moveToNext(),i++) { StringBuffer out = new StringBuffer(); + int segId = c.getInt(c.getColumnIndex(TrackContentProvider.Schema.COL_SEG_ID)); + if(prevSegId != -1 && segId != prevSegId) { + fw.write("\t\t" + "" + "\n"); + fw.write("\t\t" + "" + "\n"); + } + prevSegId = segId; + out.append("\t\t\t" + "" + "\n"); @@ -626,4 +634,4 @@ public String sanitizeTrackName(String trackName){ public String getErrorMsg() { return errorMsg; } -} \ No newline at end of file +} diff --git a/app/src/main/java/net/osmtracker/overlay/Polylines.java b/app/src/main/java/net/osmtracker/overlay/Polylines.java new file mode 100644 index 000000000..d695d2324 --- /dev/null +++ b/app/src/main/java/net/osmtracker/overlay/Polylines.java @@ -0,0 +1,61 @@ +package net.osmtracker.overlay; + +import java.util.ArrayList; +import java.util.List; + +import org.osmdroid.util.GeoPoint; +import org.osmdroid.views.MapView; +import org.osmdroid.views.overlay.Polyline; + +import android.graphics.Paint; + +/** + * Collection of Polylines, useful to draw interrupted paths + */ +public class Polylines { + private int color; + private float width; + private MapView osmView; + private boolean havePoint; + + private int curIdx=0; + + private List polylines = new ArrayList(); + + private void addPolyline() { + Polyline polyline = new Polyline(); + Paint paint = polyline.getOutlinePaint(); + paint.setColor(color); + paint.setStrokeWidth(width); + + polylines.add(polyline); + osmView.getOverlayManager().add(polyline); + } + + public void clear() { + for(Polyline polyline : polylines) + polyline.setPoints(new ArrayList<>()); + curIdx=0; + } + + public Polylines(int color, float width, MapView osmView) { + this.color=color; + this.width=width; + this.osmView = osmView; + addPolyline(); + havePoint=false; + } + + public void addPoint(GeoPoint gp) { + if(curIdx >= polylines.size()) + addPolyline(); + polylines.get(curIdx).addPoint(gp); + havePoint=true; + } + + public void nextSegment() { + if(havePoint) + curIdx++; + havePoint=false; + } +} diff --git a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java index 7f180541a..d7c6c003d 100644 --- a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java +++ b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java @@ -6,6 +6,8 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; +import android.content.ContentResolver; +import android.content.ContentUris; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -24,11 +26,14 @@ import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; +import android.database.Cursor; + import net.osmtracker.OSMTracker; import net.osmtracker.R; import net.osmtracker.activity.TrackLogger; import net.osmtracker.db.DataHelper; import net.osmtracker.db.TrackContentProvider; +import net.osmtracker.db.model.Track; import net.osmtracker.listener.PressureListener; import net.osmtracker.listener.SensorListener; @@ -83,6 +88,11 @@ public class GPSLogger extends Service implements LocationListener { */ private long currentTrackId = -1; + /** + * Current Segment ID + */ + private long currentSegmentId = -1; + /** * the timestamp of the last GPS fix we used */ @@ -104,6 +114,8 @@ public class GPSLogger extends Service implements LocationListener { */ private PressureListener pressureListener = new PressureListener(); + private boolean newSeg = false; + /** * Receives Intent for way point and notes tracking, and stop/start logging. */ @@ -130,7 +142,8 @@ public void onReceive(Context context, Intent intent) { dataHelper.wayPoint(trackId, lastLocation, name, link, uuid, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure()); // If there is a waypoint in the track, there should also be a trackpoint - dataHelper.track(currentTrackId, lastLocation, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure()); + dataHelper.track(currentTrackId, lastLocation, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure(), newSeg, currentSegmentId); + newSeg = false; } } } @@ -186,6 +199,7 @@ public void onReceive(Context context, Intent intent) { dataHelper.updateNote(trackId, uuid, name); } } else if (OSMTracker.INTENT_START_TRACKING.equals(intent.getAction())) { + newSeg = true; Bundle extras = intent.getExtras(); if (extras != null) { Long trackId = extras.getLong(TrackContentProvider.Schema.COL_TRACK_ID); @@ -313,12 +327,31 @@ public void onDestroy() { super.onDestroy(); } + private long getSegIdFor(long trackId) { + ContentResolver cr = getContentResolver(); + try(Cursor cursor = + cr.query(ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId), + null, null, null, null)) { + + if (! cursor.moveToFirst()) { + Log.v(TAG, "Track "+trackId+" not found"); + return 0; // <--- Early return --- + } + + return Track + .build(trackId, cursor, cr, true) + .getMaxSegId(); + } + } + /** * Start GPS tracking. */ private void startTracking(long trackId) { currentTrackId = trackId; - Log.v(TAG, "Starting track logging for track #" + trackId); + currentSegmentId = getSegIdFor(trackId)+1; + Log.v(TAG, "Starting track logging for track #" + trackId + + "/" + currentSegmentId); // Refresh notification with correct Track ID NotificationManager nmgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); nmgr.notify(NOTIFICATION_ID, getNotification()); @@ -347,7 +380,8 @@ public void onLocationChanged(Location location) { lastLocation = location; if (isTracking) { - dataHelper.track(currentTrackId, location, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure()); + dataHelper.track(currentTrackId, location, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure(), newSeg, currentSegmentId); + newSeg = false; } } } From 18365f06c3085266335ff812ae14f1667d7ca292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Guti=C3=A9rrez=20Alfaro?= Date: Sat, 21 Feb 2026 07:27:05 -0600 Subject: [PATCH 2/8] Refactor: Deletes unused variable --- app/src/main/java/net/osmtracker/db/DataHelper.java | 4 +++- app/src/main/java/net/osmtracker/service/gps/GPSLogger.java | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/net/osmtracker/db/DataHelper.java b/app/src/main/java/net/osmtracker/db/DataHelper.java index 436f3b60e..e24728cdd 100644 --- a/app/src/main/java/net/osmtracker/db/DataHelper.java +++ b/app/src/main/java/net/osmtracker/db/DataHelper.java @@ -145,8 +145,10 @@ public DataHelper(Context c) { * ignored if azimuth is invalid. * @param pressure * atmospheric pressure + * @param segId + * Id of the segment */ - public void track(long trackId, Location location, float azimuth, int accuracy, float pressure, boolean newSeg, long segId) { + public void track(long trackId, Location location, float azimuth, int accuracy, float pressure, long segId) { Log.v(TAG, "Tracking (trackId=" + trackId + ") location: " + location + " azimuth: " + azimuth + ", accuracy: " + accuracy); ContentValues values = new ContentValues(); values.put(TrackContentProvider.Schema.COL_TRACK_ID, trackId); diff --git a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java index d7c6c003d..40bace7be 100644 --- a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java +++ b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java @@ -142,7 +142,7 @@ public void onReceive(Context context, Intent intent) { dataHelper.wayPoint(trackId, lastLocation, name, link, uuid, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure()); // If there is a waypoint in the track, there should also be a trackpoint - dataHelper.track(currentTrackId, lastLocation, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure(), newSeg, currentSegmentId); + dataHelper.track(currentTrackId, lastLocation, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure(), currentSegmentId); newSeg = false; } } @@ -380,7 +380,7 @@ public void onLocationChanged(Location location) { lastLocation = location; if (isTracking) { - dataHelper.track(currentTrackId, location, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure(), newSeg, currentSegmentId); + dataHelper.track(currentTrackId, location, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure(), currentSegmentId); newSeg = false; } } From 7c4cf6e2c49259154987c629d9226c42c1e7ce7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Guti=C3=A9rrez=20Alfaro?= Date: Sat, 21 Feb 2026 07:52:39 -0600 Subject: [PATCH 3/8] Refactor: Changes in variable names to keep consistency --- .../java/net/osmtracker/db/TrackContentProvider.java | 6 ++---- app/src/main/java/net/osmtracker/db/model/Track.java | 11 ++++------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/net/osmtracker/db/TrackContentProvider.java b/app/src/main/java/net/osmtracker/db/TrackContentProvider.java index 011362e38..31f65ab9b 100644 --- a/app/src/main/java/net/osmtracker/db/TrackContentProvider.java +++ b/app/src/main/java/net/osmtracker/db/TrackContentProvider.java @@ -97,7 +97,7 @@ public class TrackContentProvider extends ContentProvider { "FROM "+Schema.TBL_TRACKPOINT+" " + "WHERE "+Schema.TBL_TRACKPOINT+"."+Schema.COL_TRACK_ID+" " + "= " + Schema.TBL_TRACK + "." + Schema.COL_ID + ") " + - "as " + Schema.COL_MAX_SEG_ID + "as " + Schema.COL_SEG_ID_MAX }; /** @@ -598,15 +598,13 @@ public static final class Schema { public static final String COL_COMPASS = "compass_heading"; public static final String COL_COMPASS_ACCURACY = "compass_accuracy"; public static final String COL_ATMOSPHERIC_PRESSURE = "atmospheric_pressure"; - public static final String COL_SEG_ID = "segment_id"; // virtual colums that are used in some sqls but dont exist in database public static final String COL_TRACKPOINT_COUNT = "tp_count"; public static final String COL_WAYPOINT_COUNT = "wp_count"; public static final String COL_NOTE_COUNT = "note_count"; - - public static final String COL_MAX_SEG_ID = "max_segment_id"; + public static final String COL_SEG_ID_MAX = "segment_id_max"; // Codes for UriMatcher public static final int URI_CODE_TRACK = 3; diff --git a/app/src/main/java/net/osmtracker/db/model/Track.java b/app/src/main/java/net/osmtracker/db/model/Track.java index 6f6f5491e..4610b66e7 100644 --- a/app/src/main/java/net/osmtracker/db/model/Track.java +++ b/app/src/main/java/net/osmtracker/db/model/Track.java @@ -51,9 +51,10 @@ public static OSMVisibility fromPosition(int position) { private String description; private OSMVisibility visibility; private List tags = new ArrayList(); - private int tpCount, wpCount, noteCount, maxSegId; + private int tpCount, wpCount, noteCount; private long trackDate; private long trackId; + private long maxSegId; private Long startDate=null, endDate=null; private Float startLat=null, startLong=null, endLat=null, endLong=null; @@ -95,7 +96,7 @@ public static Track build(final long trackId, Cursor tc, ContentResolver cr, boo out.noteCount = tc.getInt(tc.getColumnIndex(TrackContentProvider.Schema.COL_NOTE_COUNT)); - int maxSegIdIdx = tc.getColumnIndex(TrackContentProvider.Schema.COL_MAX_SEG_ID); + int maxSegIdIdx = tc.getColumnIndex(TrackContentProvider.Schema.COL_SEG_ID_MAX); out.maxSegId = tc.isNull(maxSegIdIdx) ? 0 :tc.getInt(maxSegIdIdx); if(withExtraInformation){ @@ -154,10 +155,6 @@ public void setNoteCount(int noteCount) { this.noteCount = noteCount; } - public void setMaxSegId(int maxSegId) { - this.maxSegId = maxSegId; - } - public void setTracktDate(long tracktDate) { this.trackDate = tracktDate; } @@ -200,7 +197,7 @@ public Integer getWpCount() { return wpCount; } - public Integer getMaxSegId() { + public long getMaxSegId() { return maxSegId; } From c17fdcad754fc04934b24ef4e34dcd9aac9ad0c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Guti=C3=A9rrez=20Alfaro?= Date: Sat, 21 Feb 2026 08:28:05 -0600 Subject: [PATCH 4/8] Refactor: Rely on database default value for max segment ID --- app/src/main/java/net/osmtracker/db/model/Track.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/net/osmtracker/db/model/Track.java b/app/src/main/java/net/osmtracker/db/model/Track.java index 4610b66e7..4d0c76566 100644 --- a/app/src/main/java/net/osmtracker/db/model/Track.java +++ b/app/src/main/java/net/osmtracker/db/model/Track.java @@ -96,8 +96,7 @@ public static Track build(final long trackId, Cursor tc, ContentResolver cr, boo out.noteCount = tc.getInt(tc.getColumnIndex(TrackContentProvider.Schema.COL_NOTE_COUNT)); - int maxSegIdIdx = tc.getColumnIndex(TrackContentProvider.Schema.COL_SEG_ID_MAX); - out.maxSegId = tc.isNull(maxSegIdIdx) ? 0 :tc.getInt(maxSegIdIdx); + out.maxSegId = tc.getLong(tc.getColumnIndex(TrackContentProvider.Schema.COL_SEG_ID_MAX)); if(withExtraInformation){ out.readExtraInformation(); From 83d3768294b5389db72294c0a98878f78abcb1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Guti=C3=A9rrez=20Alfaro?= Date: Sat, 21 Feb 2026 08:31:39 -0600 Subject: [PATCH 5/8] Feature(db): Increment to v19 for track segments support --- app/src/main/java/net/osmtracker/db/DatabaseHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/net/osmtracker/db/DatabaseHelper.java b/app/src/main/java/net/osmtracker/db/DatabaseHelper.java index 70c6885f7..a106badd2 100644 --- a/app/src/main/java/net/osmtracker/db/DatabaseHelper.java +++ b/app/src/main/java/net/osmtracker/db/DatabaseHelper.java @@ -142,9 +142,10 @@ public class DatabaseHelper extends SQLiteOpenHelper { * TBL_WAYPOINT.COL_COMPASS and TBL_WAYPOINT.COL_COMPASS_ACCURACY * v17: add TBL_TRACKPOINT.COL_ATMOSPHERIC_PRESSURE and TBL_WAYPOINT.COL_ATMOSPHERIC_PRESSURE * v18: add TBL_NOTE + * v19: add TBL_TRACKPOINT.COL_SEG_ID for track segments support * */ - private static final int DB_VERSION = 18; + private static final int DB_VERSION = 19; private Context context; From b886562e455d3eb81a9852aa77578c481bc31f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Guti=C3=A9rrez=20Alfaro?= Date: Sat, 21 Feb 2026 12:54:30 -0600 Subject: [PATCH 6/8] Refactor: Deletes unused variable --- app/src/main/java/net/osmtracker/service/gps/GPSLogger.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java index 40bace7be..c2b9eff0c 100644 --- a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java +++ b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java @@ -114,7 +114,6 @@ public class GPSLogger extends Service implements LocationListener { */ private PressureListener pressureListener = new PressureListener(); - private boolean newSeg = false; /** * Receives Intent for way point and notes tracking, and stop/start logging. @@ -143,7 +142,6 @@ public void onReceive(Context context, Intent intent) { // If there is a waypoint in the track, there should also be a trackpoint dataHelper.track(currentTrackId, lastLocation, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure(), currentSegmentId); - newSeg = false; } } } @@ -199,7 +197,6 @@ public void onReceive(Context context, Intent intent) { dataHelper.updateNote(trackId, uuid, name); } } else if (OSMTracker.INTENT_START_TRACKING.equals(intent.getAction())) { - newSeg = true; Bundle extras = intent.getExtras(); if (extras != null) { Long trackId = extras.getLong(TrackContentProvider.Schema.COL_TRACK_ID); @@ -381,7 +378,6 @@ public void onLocationChanged(Location location) { if (isTracking) { dataHelper.track(currentTrackId, location, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure(), currentSegmentId); - newSeg = false; } } } From 5a3c9a961adad71fbdf3696d700a4bea12542c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Guti=C3=A9rrez=20Alfaro?= Date: Sun, 22 Feb 2026 13:10:04 -0600 Subject: [PATCH 7/8] Test: Add test for GPX export with track segments support --- .../net/osmtracker/gpx/ExportTrackTask.java | 6 +- .../gpx/ExportToTempFileTaskTest.java | 155 ++++++++++++++++++ app/src/test/resources/gpx/gpx-test.gpx | 2 + 3 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 app/src/test/java/net/osmtracker/gpx/ExportToTempFileTaskTest.java diff --git a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java index 662cee106..2b92d82d8 100644 --- a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java +++ b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java @@ -273,7 +273,7 @@ protected void exportTrackAsGpx(long trackId) throws ExportTrackException { * @param target Target GPX file * @throws IOException */ - private void writeGpxFile(String trackName, String tags, String track_description, Cursor cTrackPoints, Cursor cWayPoints, File target) throws IOException { + protected void writeGpxFile(String trackName, String tags, String track_description, Cursor cTrackPoints, Cursor cWayPoints, File target) throws IOException { String accuracyOutput = PreferenceManager.getDefaultSharedPreferences(context).getString( OSMTracker.Preferences.KEY_OUTPUT_ACCURACY, @@ -329,7 +329,7 @@ private void writeGpxFile(String trackName, String tags, String track_descriptio * @param compass Indicates if and how to write compass heading to the GPX ('none', 'comment', 'extension') * @throws IOException */ - private void writeTrackPoints(String trackName, Writer fw, Cursor c, boolean fillHDOP, String compass) throws IOException { + protected void writeTrackPoints(String trackName, Writer fw, Cursor c, boolean fillHDOP, String compass) throws IOException { // Update dialog every 1% int dialogUpdateThreshold = c.getCount() / 100; if (dialogUpdateThreshold == 0) { @@ -419,7 +419,7 @@ private void writeTrackPoints(String trackName, Writer fw, Cursor c, boolean fil * @param compass Indicates if and how to write compass heading to the GPX ('none', 'comment', 'extension') * @throws IOException */ - private void writeWayPoints(Writer fw, Cursor c, String accuracyInfo, boolean fillHDOP, String compass) throws IOException { + protected void writeWayPoints(Writer fw, Cursor c, String accuracyInfo, boolean fillHDOP, String compass) throws IOException { // Update dialog every 1% int dialogUpdateThreshold = c.getCount() / 100; diff --git a/app/src/test/java/net/osmtracker/gpx/ExportToTempFileTaskTest.java b/app/src/test/java/net/osmtracker/gpx/ExportToTempFileTaskTest.java new file mode 100644 index 000000000..6b55aa5a4 --- /dev/null +++ b/app/src/test/java/net/osmtracker/gpx/ExportToTempFileTaskTest.java @@ -0,0 +1,155 @@ +package net.osmtracker.gpx; + +import static org.junit.Assert.assertEquals; + +import android.content.ContentValues; +import android.content.Context; +import android.database.MatrixCursor; +import androidx.preference.PreferenceManager; +import androidx.test.core.app.ApplicationProvider; + +import net.osmtracker.OSMTracker; +import net.osmtracker.db.TrackContentProvider; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.android.controller.ContentProviderController; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowContentResolver; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Locale; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = 25, qualifiers = "es") +public class ExportToTempFileTaskTest { + + private Context context; + private final long testTrackId = 123L; + + private ContentProviderController providerController; + + + /** + * Concrete implementation of the abstract ExportToTempFileTask for testing. + */ + private static class TestTempExportTask extends ExportToTempFileTask { + public TestTempExportTask(Context context, long trackId) { + super(context, trackId); + } + @Override + protected void executionCompleted() { + // No-op for testing + } + } + + @Before + public void setUp() { + context = ApplicationProvider.getApplicationContext(); + providerController = Robolectric.buildContentProvider(TrackContentProvider.class) + .create(TrackContentProvider.AUTHORITY); + + // Set up locale and preferences to to match gpx-test.gpx file + Locale esLocale = new Locale("es"); + Locale.setDefault(esLocale); + context.getResources().getConfiguration().setLocale(esLocale); + PreferenceManager.getDefaultSharedPreferences(context).edit() + .putBoolean(OSMTracker.Preferences.KEY_OUTPUT_GPX_HDOP_APPROXIMATION, true) + .putString(OSMTracker.Preferences.KEY_OUTPUT_COMPASS, "extension") + .apply(); + + // Initialize the DB record so the Task constructor doesn't fail + ContentValues values = new ContentValues(); + values.put(TrackContentProvider.Schema.COL_ID, testTrackId); + values.put(TrackContentProvider.Schema.COL_NAME, "2020-12-30_17-20-17"); + values.put(TrackContentProvider.Schema.COL_START_DATE, 990055225000L); + values.put(TrackContentProvider.Schema.COL_TAGS, "osmtracker"); + context.getContentResolver().insert(TrackContentProvider.CONTENT_URI_TRACK, values); + } + + @After + public void tearDown() { + // Shut down the provider explicitly to close database + if (providerController != null) { + providerController.shutdown(); + } + ShadowContentResolver.reset(); + } + + @Test + public void testExportMatchesGpxResource() throws Exception { + try ( + MatrixCursor pointCursor = createPointCursor(); + MatrixCursor wptCursor = createWptCursor() + ) { + TestTempExportTask task = new TestTempExportTask(context, testTrackId); + File outputFile = task.getTmpFile(); + + // Load gpx-test.gpx file + InputStream is = getClass().getClassLoader().getResourceAsStream("gpx/gpx-test.gpx"); + if (is == null) throw new RuntimeException("Resource gpx-test.gpx not found"); + String expectedXml = IOUtils.toString(is, StandardCharsets.UTF_8.name()); + + // Execute GPX writing logic + task.writeGpxFile("2020-12-30_17-20-17","osmtracker",null, + pointCursor,wptCursor,outputFile); + String actualXml = FileUtils.readFileToString(outputFile, StandardCharsets.UTF_8.name()); + + assertEquals(expectedXml, actualXml); + } + } + + // Helper method to create the point cursor + private MatrixCursor createPointCursor() { + String[] ptColumns = new String[] { + TrackContentProvider.Schema.COL_LATITUDE, + TrackContentProvider.Schema.COL_LONGITUDE, + TrackContentProvider.Schema.COL_ELEVATION, + TrackContentProvider.Schema.COL_TIMESTAMP, + TrackContentProvider.Schema.COL_SEG_ID, + TrackContentProvider.Schema.COL_ACCURACY, + TrackContentProvider.Schema.COL_SPEED, + TrackContentProvider.Schema.COL_COMPASS, + TrackContentProvider.Schema.COL_COMPASS_ACCURACY, + TrackContentProvider.Schema.COL_ATMOSPHERIC_PRESSURE, + }; + + MatrixCursor pointCursor = new MatrixCursor(ptColumns); + pointCursor.addRow(new Object[]{10.0375690436648, -84.2122886824885, 1015.13159253262, + 990055225000L, 0, 35.635440826416, 0.361938893795013, 204.690002441406, 2.0, null}); + pointCursor.addRow(new Object[]{10.0377106969496, -84.2123268390527, 1007.3209409276, + 990055226000L, 1, 52.0706405639648, 0.271533757448196, 204.360000610352, 2.0, null}); + return pointCursor; + } + + // Helper method to create the waypoint cursor + private MatrixCursor createWptCursor() { + String[] wptColumns = new String[]{ + TrackContentProvider.Schema.COL_LATITUDE, + TrackContentProvider.Schema.COL_LONGITUDE, + TrackContentProvider.Schema.COL_ELEVATION, + TrackContentProvider.Schema.COL_TIMESTAMP, + TrackContentProvider.Schema.COL_NAME, + TrackContentProvider.Schema.COL_ACCURACY, + TrackContentProvider.Schema.COL_COMPASS, + TrackContentProvider.Schema.COL_COMPASS_ACCURACY, + TrackContentProvider.Schema.COL_LINK, + TrackContentProvider.Schema.COL_NBSATELLITES, + TrackContentProvider.Schema.COL_ATMOSPHERIC_PRESSURE, + }; + + MatrixCursor wptCursor = new MatrixCursor(wptColumns); + wptCursor.addRow(new Object[]{10.0375634848231, -84.2123549868801, 1029.89940680377, + 990055228000L, "Punto de prueba", 24.0, 204.029998779297, 2.0, null, 0, null}); + return wptCursor; + } + +} diff --git a/app/src/test/resources/gpx/gpx-test.gpx b/app/src/test/resources/gpx/gpx-test.gpx index 749a413e7..8a570df45 100644 --- a/app/src/test/resources/gpx/gpx-test.gpx +++ b/app/src/test/resources/gpx/gpx-test.gpx @@ -29,6 +29,8 @@ 2.0 + + 1007.3209409276 From 338860e98b61027470ebc1ee627b60305393ee8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Guti=C3=A9rrez=20Alfaro?= Date: Sun, 22 Feb 2026 14:39:14 -0600 Subject: [PATCH 8/8] Refactor: Move segment ID database logic from GPSLogger to DataHelper --- .../java/net/osmtracker/db/DataHelper.java | 20 +++++++++++++ .../net/osmtracker/gpx/ExportTrackTask.java | 3 +- .../net/osmtracker/service/gps/GPSLogger.java | 28 ++----------------- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/net/osmtracker/db/DataHelper.java b/app/src/main/java/net/osmtracker/db/DataHelper.java index e24728cdd..410e8546b 100644 --- a/app/src/main/java/net/osmtracker/db/DataHelper.java +++ b/app/src/main/java/net/osmtracker/db/DataHelper.java @@ -17,6 +17,8 @@ import net.osmtracker.db.model.TrackPoint; import net.osmtracker.db.model.WayPoint; +import org.jetbrains.annotations.NotNull; + import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -408,6 +410,24 @@ public static long getActiveTrackId(ContentResolver cr) { return currentTrackId; } + /** + * Find the segment ID for a track + * @param trackId Id of the track + * @param cr {@link ContentResolver} for query + * @return the segment ID for the track, or 0 if not found + */ + public static long getSegmentIdFor(long trackId, @NotNull ContentResolver cr) { + Cursor ca = cr.query(ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, + trackId),null, null, null, null); + + if (! ca.moveToFirst()) { + Log.v(TAG, "Track " + trackId + " not found"); + return 0; // <--- Early return --- + } + + return Track.build(trackId, ca, cr, true).getMaxSegId(); + } + /** * Change the name of this track. * @param trackId Id of the track diff --git a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java index 2b92d82d8..0d20d0b64 100644 --- a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java +++ b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java @@ -351,6 +351,7 @@ protected void writeTrackPoints(String trackName, Writer fw, Cursor c, boolean f int prevSegId=-1; for(c.moveToFirst(); !c.isAfterLast(); c.moveToNext(),i++) { StringBuffer out = new StringBuffer(); + int segId = c.getInt(c.getColumnIndex(TrackContentProvider.Schema.COL_SEG_ID)); if(prevSegId != -1 && segId != prevSegId) { fw.write("\t\t" + "" + "\n"); @@ -634,4 +635,4 @@ public String sanitizeTrackName(String trackName){ public String getErrorMsg() { return errorMsg; } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java index c2b9eff0c..901cc5e6a 100644 --- a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java +++ b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java @@ -6,8 +6,6 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; -import android.content.ContentResolver; -import android.content.ContentUris; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -26,14 +24,11 @@ import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; -import android.database.Cursor; - import net.osmtracker.OSMTracker; import net.osmtracker.R; import net.osmtracker.activity.TrackLogger; import net.osmtracker.db.DataHelper; import net.osmtracker.db.TrackContentProvider; -import net.osmtracker.db.model.Track; import net.osmtracker.listener.PressureListener; import net.osmtracker.listener.SensorListener; @@ -114,7 +109,6 @@ public class GPSLogger extends Service implements LocationListener { */ private PressureListener pressureListener = new PressureListener(); - /** * Receives Intent for way point and notes tracking, and stop/start logging. */ @@ -324,31 +318,13 @@ public void onDestroy() { super.onDestroy(); } - private long getSegIdFor(long trackId) { - ContentResolver cr = getContentResolver(); - try(Cursor cursor = - cr.query(ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId), - null, null, null, null)) { - - if (! cursor.moveToFirst()) { - Log.v(TAG, "Track "+trackId+" not found"); - return 0; // <--- Early return --- - } - - return Track - .build(trackId, cursor, cr, true) - .getMaxSegId(); - } - } - /** * Start GPS tracking. */ private void startTracking(long trackId) { currentTrackId = trackId; - currentSegmentId = getSegIdFor(trackId)+1; - Log.v(TAG, "Starting track logging for track #" + trackId + - "/" + currentSegmentId); + currentSegmentId = DataHelper.getSegmentIdFor(trackId, getContentResolver()) + 1; + Log.v(TAG, "Starting track logging for track #" + trackId + " segment #" +"/" + currentSegmentId); // Refresh notification with correct Track ID NotificationManager nmgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); nmgr.notify(NOTIFICATION_ID, getNotification());