From 3625b0d9a97cb3d3d874d8b9e0f6db9ea0f546ac Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Sun, 2 May 2021 21:00:45 +0200 Subject: [PATCH 01/14] Support for multiple track segments. A new track segment is started whenever tracking is suspended and then resumed --- .../osmtracker/activity/DisplayTrackMap.java | 19 +++--- .../java/net/osmtracker/db/DataHelper.java | 5 +- .../net/osmtracker/db/DatabaseHelper.java | 7 ++- .../osmtracker/db/TrackContentProvider.java | 4 +- .../net/osmtracker/gpx/ExportTrackTask.java | 9 ++- .../net/osmtracker/overlay/PathOverlays.java | 59 +++++++++++++++++++ .../net/osmtracker/service/gps/GPSLogger.java | 9 ++- 7 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/net/osmtracker/overlay/PathOverlays.java diff --git a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java index c0e8f3d64..9e4b02f1c 100644 --- a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java +++ b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java @@ -8,6 +8,7 @@ import net.osmtracker.R; import net.osmtracker.db.TrackContentProvider; import net.osmtracker.overlay.WayPointsOverlay; +import net.osmtracker.overlay.PathOverlays; import org.osmdroid.api.IMapController; import org.osmdroid.config.Configuration; @@ -15,7 +16,6 @@ import org.osmdroid.tileprovider.tilesource.TileSourceFactory; import org.osmdroid.util.GeoPoint; import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.PathOverlay; import org.osmdroid.views.overlay.mylocation.SimpleLocationOverlay; import org.osmdroid.views.overlay.ScaleBarOverlay; @@ -38,6 +38,7 @@ import android.view.View; import android.view.View.OnClickListener; + /** * Display current track over an OSM map. * Based on osmdroid code http://osmdroid.googlecode.com/ @@ -109,7 +110,7 @@ public class DisplayTrackMap extends Activity { /** * OSM view overlay that displays current path */ - private PathOverlay pathOverlay; + private PathOverlays pathOverlay; /** * OSM view overlay that displays waypoints @@ -379,9 +380,7 @@ private void createOverlays() { this.getWindowManager().getDefaultDisplay().getMetrics(metrics); // set with to hopefully DPI independent 0.5mm - pathOverlay = new PathOverlay(Color.BLUE, (float)(metrics.densityDpi / 25.4 / 2),this); - - osmView.getOverlays().add(pathOverlay); + pathOverlay = new PathOverlays(Color.BLUE, (float)(metrics.densityDpi / 25.4 / 2),this, osmView); myLocationOverlay = new SimpleLocationOverlay(this); osmView.getOverlays().add(myLocationOverlay); @@ -426,7 +425,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_NEW_SEGMENT}; // Selection: The where clause to use String selection = null; // SelectionArgs: The parameter replacements to use for the '?' in the selection @@ -454,15 +453,21 @@ private void pathChanged() { c.moveToFirst(); double lastLat = 0; double lastLon = 0; + boolean newSegment = false; 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 newSegmentColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_NEW_SEGMENT); + // Add each new point to the track while(!c.isAfterLast()) { lastLat = c.getDouble(latitudeColumnIndex); lastLon = c.getDouble(longitudeColumnIndex); lastTrackPointIdProcessed = c.getInt(primaryKeyColumnIndex); + newSegment = c.getShort(newSegmentColumnIndex) > 0; + if(newSegment) { + pathOverlay.nextSegment(); + } pathOverlay.addPoint((int)(lastLat * 1e6), (int)(lastLon * 1e6)); if (doInitialBoundsCalc) { if (lastLat < minLat) minLat = lastLat; diff --git a/app/src/main/java/net/osmtracker/db/DataHelper.java b/app/src/main/java/net/osmtracker/db/DataHelper.java index 8c4934020..8c06bac6e 100644 --- a/app/src/main/java/net/osmtracker/db/DataHelper.java +++ b/app/src/main/java/net/osmtracker/db/DataHelper.java @@ -112,7 +112,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) { Log.v(TAG, "Tracking (trackId=" + trackId + ") location: " + location + " azimuth: " + azimuth + ", accuracy: " + accuracy); ContentValues values = new ContentValues(); values.put(TrackContentProvider.Schema.COL_TRACK_ID, trackId); @@ -146,6 +146,9 @@ public void track(long trackId, Location location, float azimuth, int accuracy, values.put(TrackContentProvider.Schema.COL_ATMOSPHERIC_PRESSURE, pressure); } + values.put(TrackContentProvider.Schema.COL_NEW_SEGMENT, + newSeg ? 1 : 0); + 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 75b3f9e9d..f92ebef57 100644 --- a/app/src/main/java/net/osmtracker/db/DatabaseHelper.java +++ b/app/src/main/java/net/osmtracker/db/DatabaseHelper.java @@ -39,7 +39,8 @@ 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_NEW_SEGMENT + " integer default 0" + ")"; /** * SQL for creating index TRACKPOINT_idx (track id) @@ -125,7 +126,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { * v17: add TBL_TRACKPOINT.COL_ATMOSPHERIC_PRESSURE and TBL_WAYPOINT.COL_ATMOSPHERIC_PRESSURE * */ - private static final int DB_VERSION = 17; + private static final int DB_VERSION = 18; public DatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); @@ -178,6 +179,8 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { case 16: db.execSQL("alter table " + TrackContentProvider.Schema.TBL_TRACKPOINT + " add column " + TrackContentProvider.Schema.COL_ATMOSPHERIC_PRESSURE + " double null"); db.execSQL("alter table " + TrackContentProvider.Schema.TBL_WAYPOINT + " add column " + TrackContentProvider.Schema.COL_ATMOSPHERIC_PRESSURE + " double null"); + case 17: + db.execSQL("alter table "+TrackContentProvider.Schema.TBL_TRACKPOINT + " add column " + TrackContentProvider.Schema.COL_NEW_SEGMENT + " 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 ffb551ed0..a0fcc65fd 100644 --- a/app/src/main/java/net/osmtracker/db/TrackContentProvider.java +++ b/app/src/main/java/net/osmtracker/db/TrackContentProvider.java @@ -496,7 +496,9 @@ 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_NEW_SEGMENT = "new_segment"; + // 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"; diff --git a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java index c9cc62751..61728aa34 100644 --- a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java +++ b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java @@ -358,8 +358,15 @@ private void writeTrackPoints(String trackName, Writer fw, Cursor c, boolean fil fw.write("\t\t" + "" + "\n"); int i=0; + boolean havePoint=false; for(c.moveToFirst(); !c.isAfterLast(); c.moveToNext(),i++) { StringBuffer out = new StringBuffer(); + if(havePoint && c.getShort(c.getColumnIndex(TrackContentProvider.Schema.COL_NEW_SEGMENT)) > 0) { + fw.write("\t\t" + "" + "\n"); + fw.write("\t\t" + "" + "\n"); + } + havePoint=true; + out.append("\t\t\t" + "" + "\n"); @@ -612,4 +619,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/PathOverlays.java b/app/src/main/java/net/osmtracker/overlay/PathOverlays.java new file mode 100644 index 000000000..49408de80 --- /dev/null +++ b/app/src/main/java/net/osmtracker/overlay/PathOverlays.java @@ -0,0 +1,59 @@ +package net.osmtracker.overlay; + +import java.util.ArrayList; +import java.util.List; + +import org.osmdroid.views.MapView; +import org.osmdroid.views.overlay.PathOverlay; + +import android.content.Context; + +/** + * Collection of Overlays, useful to draw interrupted paths + */ +public class PathOverlays { + private int color; + private float width; + private Context ctx; + private MapView osmView; + private boolean havePoint; + + private int curIdx=0; + + private List paths = new ArrayList(); + + private void addPath() { + PathOverlay path = new PathOverlay(color, width, ctx); + paths.add(path); + osmView.getOverlays().add(path); + } + + public void clearPath() { + for(PathOverlay path : paths) + path.clearPath(); + curIdx=0; + } + + public PathOverlays(int color, float width, + Context ctx, MapView osmView) { + this.color=color; + this.width=width; + this.ctx=ctx; + this.osmView = osmView; + addPath(); + havePoint=false; + } + + public void addPoint(double lat, double lon) { + if(curIdx >= paths.size()) + addPath(); + paths.get(curIdx).addPoint(lat, lon); + 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 ce596da58..b32c4240f 100644 --- a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java +++ b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java @@ -103,6 +103,8 @@ public class GPSLogger extends Service implements LocationListener { */ private PressureListener pressureListener = new PressureListener(); + private boolean newSeg = true; + /** * Receives Intent for way point tracking, and stop/start logging. */ @@ -129,7 +131,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); + newSeg = false; } } } @@ -243,6 +246,7 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.v(TAG, "Service onStartCommand(-,"+flags+","+startId+")"); + newSeg = true; createNotificationChannel(); startForeground(NOTIFICATION_ID, getNotification()); return Service.START_STICKY; @@ -306,7 +310,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); + newSeg = false; } } } From 7866178141c58adec4f039f055ba687a20bad44f Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Mon, 3 May 2021 07:06:38 +0200 Subject: [PATCH 02/14] Removed extra empty line --- app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java index 9e4b02f1c..27f8d0d83 100644 --- a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java +++ b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java @@ -38,7 +38,6 @@ import android.view.View; import android.view.View.OnClickListener; - /** * Display current track over an OSM map. * Based on osmdroid code http://osmdroid.googlecode.com/ From 9f46f950d4e476e643c6ce45c3fbccd05c13e672 Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Mon, 3 May 2021 21:09:04 +0200 Subject: [PATCH 03/14] Only start a new segment on explicit 'Resume', in order to avoid starting a new segment by just going back to track list --- app/src/main/java/net/osmtracker/service/gps/GPSLogger.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 b32c4240f..35d0a5da8 100644 --- a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java +++ b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java @@ -103,7 +103,7 @@ public class GPSLogger extends Service implements LocationListener { */ private PressureListener pressureListener = new PressureListener(); - private boolean newSeg = true; + private boolean newSeg = false; /** * Receives Intent for way point tracking, and stop/start logging. @@ -154,6 +154,7 @@ public void onReceive(Context context, Intent intent) { dataHelper.deleteWayPoint(uuid); } } 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); @@ -246,7 +247,6 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.v(TAG, "Service onStartCommand(-,"+flags+","+startId+")"); - newSeg = true; createNotificationChannel(); startForeground(NOTIFICATION_ID, getNotification()); return Service.START_STICKY; From a9d2cafabb6e6805bbf747fc3c40271f97db5a2a Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Sun, 9 May 2021 13:59:33 +0200 Subject: [PATCH 04/14] Import routes or tracks --- .../net/osmtracker/activity/TrackManager.java | 59 ++++ .../java/net/osmtracker/gpx/ImportRoute.java | 300 ++++++++++++++++++ .../main/res/menu/trackmgr_contextmenu.xml | 3 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 363 insertions(+) create mode 100644 app/src/main/java/net/osmtracker/gpx/ImportRoute.java diff --git a/app/src/main/java/net/osmtracker/activity/TrackManager.java b/app/src/main/java/net/osmtracker/activity/TrackManager.java index 19d7e2fe0..a676cf150 100644 --- a/app/src/main/java/net/osmtracker/activity/TrackManager.java +++ b/app/src/main/java/net/osmtracker/activity/TrackManager.java @@ -1,5 +1,7 @@ package net.osmtracker.activity; +import java.io.InputStream; + import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.app.ActivityCompat; @@ -10,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView; import android.Manifest; +import android.app.Activity; import android.app.AlertDialog; import android.content.ContentUris; import android.content.ContentValues; @@ -39,6 +42,7 @@ import net.osmtracker.exception.CreateTrackException; import net.osmtracker.gpx.ExportToStorageTask; import net.osmtracker.gpx.ExportToTempFileTask; +import net.osmtracker.gpx.ImportRoute; import net.osmtracker.util.FileSystemUtils; import java.io.File; @@ -61,6 +65,11 @@ public class TrackManager extends AppCompatActivity final private int RC_GPS_PERMISSION = 5; final private int RC_WRITE_PERMISSIONS_SHARE = 6; + /** + * Request code for callback after user has selected an import file + */ + private static final int REQCODE_IMPORT_OPEN = 0; + /** Bundle key for {@link #prevItemVisible} */ private static final String PREV_VISIBLE = "prev_visible"; @@ -367,6 +376,51 @@ public void onClick(DialogInterface dialog, int which) { }.execute(); } + /* Import route + */ + private void importRoute() { + + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); // GPX application type not known to Android... + intent.addCategory(Intent.CATEGORY_OPENABLE); + startActivityForResult(intent, REQCODE_IMPORT_OPEN); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQCODE_IMPORT_OPEN: + if(resultCode == Activity.RESULT_CANCELED) { + // cancelled by user + return; + } + if(resultCode != Activity.RESULT_OK) { + // something unexpected + Toast.makeText(this, + "Result code="+resultCode, + Toast.LENGTH_LONG).show(); + return; + } + + Uri uri = (Uri) data.getData(); + try(InputStream is = getContentResolver() + .openInputStream(uri)) { + new ImportRoute(this, contextMenuSelectedTrackid) + .doImport(is); + } catch(Exception e) { + new AlertDialog.Builder(this) + .setTitle("Exception received") + .setMessage(Log.getStackTraceString(e)) + .setNeutralButton("Ok", + (dlg,id)->dlg.dismiss()) + .create() + .show(); + } + return; + } + super.onActivityResult(requestCode, resultCode, data); + } + @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo, long trackId) { super.onCreateContextMenu(menu, v, menuInfo); @@ -474,6 +528,11 @@ public void onClick(DialogInterface dialog, int which) { i.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, contextMenuSelectedTrackid); startActivity(i); break; + + case R.id.trackmgr_contextmenu_import: + importRoute(); + break; + } return super.onContextItemSelected(item); } diff --git a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java new file mode 100644 index 000000000..ddfb42285 --- /dev/null +++ b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java @@ -0,0 +1,300 @@ +package net.osmtracker.gpx; + +import android.content.Context; +import android.location.Location; +import android.util.Log; + +import net.osmtracker.db.DataHelper; + +import org.xml.sax.SAXException; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.InputStream; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import java.util.function.DoubleConsumer; + +import javax.xml.parsers.ParserConfigurationException; + +/** + * Class to import a route + */ +public class ImportRoute { + private static final String TAG = ImportRoute.class.getSimpleName(); + + private long trackId; + private String ns=null; + private DataHelper dataHelper; + private Context context; + + public ImportRoute(Context context, long trackId) { + this.context = context; + this.trackId = trackId; + dataHelper = new DataHelper(context); + } + + private void skip(XmlPullParser parser) + throws XmlPullParserException, IOException { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException(); + } + int depth = 1; + while (depth != 0) { + switch (parser.next()) { + case XmlPullParser.END_TAG: + depth--; + break; + case XmlPullParser.START_TAG: + depth++; + break; + } + } + } + + // For getting the text of a tag. + private String readText(XmlPullParser parser) + throws IOException, XmlPullParserException { + String result = ""; + if (parser.next() == XmlPullParser.TEXT) { + result = parser.getText(); + parser.nextTag(); + } + return result; + } + + private String readString(XmlPullParser parser, String tag) + throws IOException, XmlPullParserException { + parser.require(XmlPullParser.START_TAG, ns, tag); + String result = readText(parser); + parser.require(XmlPullParser.END_TAG, ns, tag); + return result; + } + + private void readDouble(XmlPullParser parser, String tag, + DoubleConsumer setter) + throws XmlPullParserException, IOException { + String str = readString(parser,tag); + if(str == null || "".equals(str)) + return; + try { + setter.accept(Double.parseDouble(str)); + } catch(NumberFormatException e) { + Log.v(TAG, "Bad double "+tag+" :\""+str+"\""); + } + } + + private interface FloatConsumer { + void accept(float f); + } + + private interface LongConsumer { + void accept(long l); + } + + private void readFloat(XmlPullParser parser, String tag, + FloatConsumer setter) + throws XmlPullParserException, IOException { + String str = readString(parser,tag); + if(str == null || "".equals(str)) + return; + try { + setter.accept(Float.parseFloat(str)); + } catch(NumberFormatException e) { + Log.v(TAG, "Bad float "+tag+" :\""+str+"\""); + } + } + + private float readFloat(XmlPullParser parser, String tag, + float dflt) + throws XmlPullParserException, IOException { + String str = readString(parser,tag); + if(str == null || "".equals(str)) + return dflt; + try { + return Float.parseFloat(str); + } catch(NumberFormatException e) { + Log.v(TAG, "Bad float "+tag+" :\""+str+"\""); + return dflt; + } + } + + private int readInt(XmlPullParser parser, String tag, + int dflt) + throws XmlPullParserException, IOException { + String str = readString(parser,tag); + if(str == null || "".equals(str)) + return dflt; + try { + return Integer.parseInt(str); + } catch(NumberFormatException e) { + Log.v(TAG, "Bad integer "+tag+" :\""+str+"\""); + return dflt; + } + } + + private void readTime(XmlPullParser parser, String tag, + LongConsumer setter) + throws XmlPullParserException, IOException { + String str = readString(parser,tag); + if(str == null || "".equals(str)) + return; + + // first try with timezone... + try { + setter.accept(ZonedDateTime + .parse(str) + .toInstant() + .toEpochMilli()); + return; + } catch(DateTimeParseException e) { + Log.v(TAG, "Bad zoned time "+tag+" :\""+str+"\":"+e); + } + + // and then without + try { + setter.accept(LocalDateTime + .parse(str) + .atZone(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli()); + return; + } catch(DateTimeParseException e) { + Log.v(TAG, "Bad time "+tag+" :\""+str+"\":"+e); + } + } + + private void readDoubleFromAttribute(XmlPullParser parser, + String attribute, + DoubleConsumer setter) + throws XmlPullParserException, IOException { + String str = parser.getAttributeValue(ns, attribute); + if(str == null || "".equals(str)) + return; + try { + setter.accept(Double.parseDouble(str)); + } catch(NumberFormatException e) { + Log.v(TAG, "Bad double attribute "+attribute+ + " :\""+str+"\""); + } + } + + /** + * Reads a track point + * @param parser the parser + * @param tag the tag + * @param newSegment true if this point starts a new segment + */ + private void readPoint(XmlPullParser parser, + String tag, + boolean newSegment) + throws XmlPullParserException, IOException { + int depth=1; + + float azimuth=-1.0f; + int compassAccuracy=0; + float pressure=0.0f; + + /* Ensure we have correct tag */ + parser.require(XmlPullParser.START_TAG, ns, tag); + + Location location = new Location("import"); + readDoubleFromAttribute(parser, "lat", location::setLatitude); + readDoubleFromAttribute(parser, "lon", location::setLongitude); + + /* Process attributes */ + while (depth > 0) { + switch(parser.next()) { + case XmlPullParser.END_TAG: + depth--; + continue; + case XmlPullParser.START_TAG: + break; + default: + continue; /* All other tags => ignore */ + } + + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + if (name.equals("ele")) { + readDouble(parser, name, location::setAltitude); + } else if (name.equals("time")) { + readTime(parser, name, location::setTime); + } else if (name.equals("accuracy")) { + readFloat(parser, name, location::setAccuracy); + } else if (name.equals("speed")) { + readFloat(parser, name, location::setSpeed); + } else if (name.equals("baro")) { + pressure = readFloat(parser, name, 0.0f); + } else if (name.equals("compass")) { + azimuth = readFloat(parser, name, -1.0f); + } else if (name.equals("compassAccuracy")) { + compassAccuracy = readInt(parser, name, 0); + } else { + depth++; + // ignore all other tags, but still recurse + // into them + } + } + + parser.require(XmlPullParser.END_TAG, ns, tag); + + dataHelper.track(trackId, location, azimuth, + compassAccuracy, pressure, newSegment); + } + + // Parses the contents of a track or route segment. If it encounters a + // trkpt tag, hands it off to readPoint + private void readSegment(XmlPullParser parser, String tag) + throws XmlPullParserException, IOException { + boolean segmentStart = true; + parser.require(XmlPullParser.START_TAG, ns, tag); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + if (name.equals("trkpt")) { + readPoint(parser, name, segmentStart); + segmentStart = false; + } else { + skip(parser); + } + } + parser.require(XmlPullParser.END_TAG, ns, tag); + } + + /** + * Import the given input stream into the given track + */ + public void doImport(InputStream is) + throws IOException, XmlPullParserException, + ParserConfigurationException, SAXException { + int event; + // Xml Pull parser + // https://www.tutorialspoint.com/android/android_xml_parsers.htm + // https://developer.android.com/reference/org/xmlpull/v1/XmlPullParser + // https://developer.android.com/training/basics/network-ops/xml + XmlPullParserFactory xmlFactoryObject = + XmlPullParserFactory.newInstance(); + XmlPullParser p = xmlFactoryObject.newPullParser(); + p.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + p.setInput(is, null); + + while ((event=p.next()) != XmlPullParser.END_DOCUMENT) { + if(event == XmlPullParser.START_TAG) { + String name=p.getName(); + if("trkseg".equals(name) || + "rte".equals(name)) { + readSegment(p, name); + } + } + } + } +} diff --git a/app/src/main/res/menu/trackmgr_contextmenu.xml b/app/src/main/res/menu/trackmgr_contextmenu.xml index 805e03ef8..7dd8e4d26 100644 --- a/app/src/main/res/menu/trackmgr_contextmenu.xml +++ b/app/src/main/res/menu/trackmgr_contextmenu.xml @@ -25,4 +25,7 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 42f3bc271..2e6a2a926 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -43,6 +43,7 @@ Export as GPX Share GPX Upload to OpenStreetMap + Import route from GPX Display Details Track #{0} From e9097832cc7a5f713bdfbddc1bac9713039fde14 Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Sun, 9 May 2021 15:21:30 +0200 Subject: [PATCH 05/14] Handle route points (rtept) --- app/src/main/java/net/osmtracker/gpx/ImportRoute.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java index ddfb42285..2d5b0a4b6 100644 --- a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java +++ b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java @@ -260,7 +260,8 @@ private void readSegment(XmlPullParser parser, String tag) continue; } String name = parser.getName(); - if (name.equals("trkpt")) { + if (name.equals("trkpt")|| + name.equals("rtept")) { readPoint(parser, name, segmentStart); segmentStart = false; } else { From 8fb99d2bc6722b6dd43ec9fbdb44503f771258e4 Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Sun, 9 May 2021 17:48:41 +0200 Subject: [PATCH 06/14] Progress bar --- .../net/osmtracker/activity/TrackManager.java | 13 ++- .../java/net/osmtracker/gpx/ImportRoute.java | 105 +++++++++++++++++- 2 files changed, 107 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/net/osmtracker/activity/TrackManager.java b/app/src/main/java/net/osmtracker/activity/TrackManager.java index a676cf150..2c4b72b8a 100644 --- a/app/src/main/java/net/osmtracker/activity/TrackManager.java +++ b/app/src/main/java/net/osmtracker/activity/TrackManager.java @@ -1,7 +1,5 @@ package net.osmtracker.activity; -import java.io.InputStream; - import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.app.ActivityCompat; @@ -20,6 +18,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; @@ -403,10 +402,12 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } Uri uri = (Uri) data.getData(); - try(InputStream is = getContentResolver() - .openInputStream(uri)) { - new ImportRoute(this, contextMenuSelectedTrackid) - .doImport(is); + try { + AssetFileDescriptor afd = getContentResolver() + .openAssetFileDescriptor(uri, "r"); + new ImportRoute(this, + contextMenuSelectedTrackid) + .doImport(afd); } catch(Exception e) { new AlertDialog.Builder(this) .setTitle("Exception received") diff --git a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java index 2d5b0a4b6..d33459c35 100644 --- a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java +++ b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java @@ -1,6 +1,9 @@ package net.osmtracker.gpx; +import android.app.AlertDialog; +import android.app.ProgressDialog; import android.content.Context; +import android.content.res.AssetFileDescriptor; import android.location.Location; import android.util.Log; @@ -11,6 +14,7 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; +import java.io.FilterInputStream; import java.io.InputStream; import java.io.IOException; import java.time.LocalDateTime; @@ -21,6 +25,55 @@ import javax.xml.parsers.ParserConfigurationException; +interface LongConsumer { + void accept(long l); +} + +class InputStreamWithPosition extends FilterInputStream { + private long position=0; + private LongConsumer report; + + public InputStreamWithPosition(InputStream in, + LongConsumer report) { + super(in); + this.report = report; + } + + public boolean markSupported() { + return false; + } + + private void advancePosition(long delta) { + if(delta == -1) + return; + position += delta; + report.accept(position); + } + + public int read() throws IOException { + int ret = super.read(); + if(ret != -1) + advancePosition(1); + return ret; + } + + public int read(byte[] b, int off, int len) throws IOException { + int ret = super.read(b, off, len); + advancePosition(ret); + return ret; + } + + public long skip(long n) throws IOException { + long ret = super.skip(n); + advancePosition(ret); + return ret; + } + + public long getPosition() { + return position; + } +} + /** * Class to import a route */ @@ -92,10 +145,6 @@ private interface FloatConsumer { void accept(float f); } - private interface LongConsumer { - void accept(long l); - } - private void readFloat(XmlPullParser parser, String tag, FloatConsumer setter) throws XmlPullParserException, IOException { @@ -271,10 +320,56 @@ private void readSegment(XmlPullParser parser, String tag) parser.require(XmlPullParser.END_TAG, ns, tag); } + public void reportPosition(long position, + long totalSize, + ProgressDialog pb) { + if(position > 30 && position <= totalSize) + pb.setProgress((int)position); + } + + /** + * Import the given input stream into the given track + */ + public void doImport(AssetFileDescriptor afd) + throws IOException, XmlPullParserException, + ParserConfigurationException, SAXException { + ProgressDialog pb = new ProgressDialog(context); + pb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + pb.setIndeterminate(false); + pb.setCancelable(false); + pb.setProgress(0); + pb.setMax(100); + pb.setTitle("Import"); + pb.show(); + + new Thread(()-> { + try(InputStream is = afd.createInputStream();){ + long totSize = afd.getLength(); + if(totSize > 0) { + pb.setMax((int)totSize); + } + InputStreamWithPosition isp = + new InputStreamWithPosition(is, + p->reportPosition(p, totSize, pb)); + doImport(isp); + } catch(Exception e) { + new AlertDialog.Builder(context) + .setTitle("Exception received") + .setMessage(Log.getStackTraceString(e)) + .setNeutralButton("Ok", + (dlg,id)->dlg.dismiss()) + .create() + .show(); + } finally { + pb.dismiss(); + } + }).start(); + } + /** * Import the given input stream into the given track */ - public void doImport(InputStream is) + private void doImport(InputStream is) throws IOException, XmlPullParserException, ParserConfigurationException, SAXException { int event; From a65e0e38344088ada690a5cc64f74b6436021644 Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Sun, 9 May 2021 20:12:33 +0200 Subject: [PATCH 07/14] Routes different color than track --- .../osmtracker/activity/DisplayTrackMap.java | 14 +++-- .../java/net/osmtracker/db/DataHelper.java | 12 ++++- .../net/osmtracker/db/DatabaseHelper.java | 8 ++- .../osmtracker/db/TrackContentProvider.java | 1 + .../net/osmtracker/gpx/ExportTrackTask.java | 3 ++ .../java/net/osmtracker/gpx/ImportRoute.java | 2 +- .../net/osmtracker/overlay/PathOverlays.java | 52 ++++++++++--------- .../net/osmtracker/service/gps/GPSLogger.java | 4 +- 8 files changed, 61 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java index 27f8d0d83..76b53cf88 100644 --- a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java +++ b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java @@ -148,7 +148,7 @@ public class DisplayTrackMap extends Activity { * Initially null, to indicate that no data has yet been read. */ private Integer lastTrackPointIdProcessed = null; - + /** * Observes changes on trackpoints */ @@ -379,7 +379,7 @@ private void createOverlays() { this.getWindowManager().getDefaultDisplay().getMetrics(metrics); // set with to hopefully DPI independent 0.5mm - pathOverlay = new PathOverlays(Color.BLUE, (float)(metrics.densityDpi / 25.4 / 2),this, osmView); + pathOverlay = new PathOverlays((float)(metrics.densityDpi / 25.4 / 2),this, osmView); myLocationOverlay = new SimpleLocationOverlay(this); osmView.getOverlays().add(myLocationOverlay); @@ -424,7 +424,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, TrackContentProvider.Schema.COL_NEW_SEGMENT}; + String[] projection = {TrackContentProvider.Schema.COL_LATITUDE, TrackContentProvider.Schema.COL_LONGITUDE, TrackContentProvider.Schema.COL_ID, TrackContentProvider.Schema.COL_NEW_SEGMENT, TrackContentProvider.Schema.COL_IS_ROUTE}; // Selection: The where clause to use String selection = null; // SelectionArgs: The parameter replacements to use for the '?' in the selection @@ -453,10 +453,12 @@ private void pathChanged() { double lastLat = 0; double lastLon = 0; boolean newSegment = false; + boolean isRoute = false; 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 newSegmentColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_NEW_SEGMENT); + int isRouteColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_IS_ROUTE); // Add each new point to the track while(!c.isAfterLast()) { @@ -464,10 +466,12 @@ private void pathChanged() { lastLon = c.getDouble(longitudeColumnIndex); lastTrackPointIdProcessed = c.getInt(primaryKeyColumnIndex); newSegment = c.getShort(newSegmentColumnIndex) > 0; + isRoute = c.getShort(isRouteColumnIndex) > 0; if(newSegment) { - pathOverlay.nextSegment(); + pathOverlay.nextSegment(isRoute); } - pathOverlay.addPoint((int)(lastLat * 1e6), (int)(lastLon * 1e6)); + + pathOverlay.addPoint((int)(lastLat * 1e6), (int)(lastLon * 1e6), isRoute); 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 8c06bac6e..bfebeb2b8 100644 --- a/app/src/main/java/net/osmtracker/db/DataHelper.java +++ b/app/src/main/java/net/osmtracker/db/DataHelper.java @@ -111,8 +111,15 @@ public DataHelper(Context c) { * ignored if azimuth is invalid. * @param pressure * atmospheric pressure + * @param newSeg + * whether this point is to start a new track segment + * @param isRoute + * whether this point is a point of a route, rather + * than a track (routes are imported paths that we + * want to follow, and shown in green rather than + * blue) */ - public void track(long trackId, Location location, float azimuth, int accuracy, float pressure, boolean newSeg) { + public void track(long trackId, Location location, float azimuth, int accuracy, float pressure, boolean newSeg, boolean isRoute) { Log.v(TAG, "Tracking (trackId=" + trackId + ") location: " + location + " azimuth: " + azimuth + ", accuracy: " + accuracy); ContentValues values = new ContentValues(); values.put(TrackContentProvider.Schema.COL_TRACK_ID, trackId); @@ -149,6 +156,9 @@ public void track(long trackId, Location location, float azimuth, int accuracy, values.put(TrackContentProvider.Schema.COL_NEW_SEGMENT, newSeg ? 1 : 0); + values.put(TrackContentProvider.Schema.COL_IS_ROUTE, + isRoute ? 1 : 0); + 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 f92ebef57..0d3c9edfc 100644 --- a/app/src/main/java/net/osmtracker/db/DatabaseHelper.java +++ b/app/src/main/java/net/osmtracker/db/DatabaseHelper.java @@ -40,7 +40,9 @@ public class DatabaseHelper extends SQLiteOpenHelper { + TrackContentProvider.Schema.COL_COMPASS + " double null," + TrackContentProvider.Schema.COL_COMPASS_ACCURACY + " integer null," + TrackContentProvider.Schema.COL_ATMOSPHERIC_PRESSURE + " double null," - + TrackContentProvider.Schema.COL_NEW_SEGMENT + " integer default 0" + ")"; + + TrackContentProvider.Schema.COL_NEW_SEGMENT + " integer default 0," + + TrackContentProvider.Schema.COL_NEW_SEGMENT + " integer default 0" + + ")"; /** * SQL for creating index TRACKPOINT_idx (track id) @@ -126,7 +128,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { * v17: add TBL_TRACKPOINT.COL_ATMOSPHERIC_PRESSURE and TBL_WAYPOINT.COL_ATMOSPHERIC_PRESSURE * */ - private static final int DB_VERSION = 18; + private static final int DB_VERSION = 19; public DatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); @@ -181,6 +183,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("alter table "+TrackContentProvider.Schema.TBL_TRACKPOINT + " add column " + TrackContentProvider.Schema.COL_NEW_SEGMENT + " integer default 0"); + case 18: + db.execSQL("alter table "+TrackContentProvider.Schema.TBL_TRACKPOINT + " add column " + TrackContentProvider.Schema.COL_IS_ROUTE + " 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 a0fcc65fd..9662f867d 100644 --- a/app/src/main/java/net/osmtracker/db/TrackContentProvider.java +++ b/app/src/main/java/net/osmtracker/db/TrackContentProvider.java @@ -498,6 +498,7 @@ public static final class Schema { public static final String COL_ATMOSPHERIC_PRESSURE = "atmospheric_pressure"; public static final String COL_NEW_SEGMENT = "new_segment"; + public static final String COL_IS_ROUTE = "is_route"; // virtual colums that are used in some sqls but dont exist in database public static final String COL_TRACKPOINT_COUNT = "tp_count"; diff --git a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java index 61728aa34..6340a0789 100644 --- a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java +++ b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java @@ -361,6 +361,9 @@ private void writeTrackPoints(String trackName, Writer fw, Cursor c, boolean fil boolean havePoint=false; for(c.moveToFirst(); !c.isAfterLast(); c.moveToNext(),i++) { StringBuffer out = new StringBuffer(); + if(c.getShort(c.getColumnIndex(TrackContentProvider.Schema.COL_IS_ROUTE)) > 0) + // do not re-export route points + continue; if(havePoint && c.getShort(c.getColumnIndex(TrackContentProvider.Schema.COL_NEW_SEGMENT)) > 0) { fw.write("\t\t" + "" + "\n"); fw.write("\t\t" + "" + "\n"); diff --git a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java index d33459c35..1082a262a 100644 --- a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java +++ b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java @@ -295,7 +295,7 @@ private void readPoint(XmlPullParser parser, parser.require(XmlPullParser.END_TAG, ns, tag); dataHelper.track(trackId, location, azimuth, - compassAccuracy, pressure, newSegment); + compassAccuracy, pressure, newSegment, true); } // Parses the contents of a track or route segment. If it encounters a diff --git a/app/src/main/java/net/osmtracker/overlay/PathOverlays.java b/app/src/main/java/net/osmtracker/overlay/PathOverlays.java index 49408de80..fb9dbda91 100644 --- a/app/src/main/java/net/osmtracker/overlay/PathOverlays.java +++ b/app/src/main/java/net/osmtracker/overlay/PathOverlays.java @@ -7,53 +7,57 @@ import org.osmdroid.views.overlay.PathOverlay; import android.content.Context; +import android.graphics.Color; /** * Collection of Overlays, useful to draw interrupted paths */ public class PathOverlays { - private int color; private float width; private Context ctx; private MapView osmView; - private boolean havePoint; + private boolean[] havePoint = new boolean[] { false, false }; - private int curIdx=0; + private int[] curIdx=new int[] { 0, 0}; - private List paths = new ArrayList(); - - private void addPath() { - PathOverlay path = new PathOverlay(color, width, ctx); - paths.add(path); + private List> paths = new ArrayList<>(); + private int[] colors = new int[] { Color.BLUE, Color.GREEN }; + + private void addPath(int slot) { + PathOverlay path = new PathOverlay(colors[slot], width, ctx); + paths.get(slot).add(path); osmView.getOverlays().add(path); } public void clearPath() { - for(PathOverlay path : paths) - path.clearPath(); - curIdx=0; + for(int slot=0; slot<2; slot++) { + for(PathOverlay path : paths.get(slot)) + path.clearPath(); + curIdx[slot]=0; + } } - public PathOverlays(int color, float width, + public PathOverlays(float width, Context ctx, MapView osmView) { - this.color=color; this.width=width; this.ctx=ctx; this.osmView = osmView; - addPath(); - havePoint=false; + for(int slot=0; slot<2; slot++) + paths.add(new ArrayList()); } - public void addPoint(double lat, double lon) { - if(curIdx >= paths.size()) - addPath(); - paths.get(curIdx).addPoint(lat, lon); - havePoint=true; + public void addPoint(double lat, double lon, boolean isRoute) { + int slot= isRoute ? 1 : 0; + if(curIdx[slot] >= paths.get(slot).size()) + addPath(slot); + paths.get(slot).get(curIdx[slot]).addPoint(lat, lon); + havePoint[slot]=true; } - public void nextSegment() { - if(havePoint) - curIdx++; - havePoint=false; + public void nextSegment(boolean isRoute) { + int slot= isRoute ? 1 : 0; + if(havePoint[slot]) + curIdx[slot]++; + havePoint[slot]=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 35d0a5da8..353b6c7de 100644 --- a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java +++ b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java @@ -131,7 +131,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); + dataHelper.track(currentTrackId, lastLocation, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure(),newSeg, false); newSeg = false; } } @@ -310,7 +310,7 @@ public void onLocationChanged(Location location) { lastLocation = location; if (isTracking) { - dataHelper.track(currentTrackId, location, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure(), newSeg); + dataHelper.track(currentTrackId, location, sensorListener.getAzimuth(), sensorListener.getAccuracy(), pressureListener.getPressure(), newSeg, false); newSeg = false; } } From 601cb22780ea8b8f3bd14fe4095cae60c262db82 Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Sun, 9 May 2021 21:12:49 +0200 Subject: [PATCH 08/14] Address warnings --- .../osmtracker/activity/DisplayTrackMap.java | 5 +- .../net/osmtracker/activity/TrackManager.java | 2 +- .../java/net/osmtracker/gpx/ImportRoute.java | 84 +++++++++---------- .../net/osmtracker/overlay/PathOverlays.java | 16 ++-- 4 files changed, 52 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java index 76b53cf88..f3d4f66bf 100644 --- a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java +++ b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java @@ -25,7 +25,6 @@ import android.content.SharedPreferences; import android.database.ContentObserver; import android.database.Cursor; -import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; @@ -452,8 +451,8 @@ private void pathChanged() { c.moveToFirst(); double lastLat = 0; double lastLon = 0; - boolean newSegment = false; - boolean isRoute = false; + boolean newSegment; + boolean isRoute; int primaryKeyColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_ID); int latitudeColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_LATITUDE); int longitudeColumnIndex = c.getColumnIndex(TrackContentProvider.Schema.COL_LONGITUDE); diff --git a/app/src/main/java/net/osmtracker/activity/TrackManager.java b/app/src/main/java/net/osmtracker/activity/TrackManager.java index 2c4b72b8a..0978c7352 100644 --- a/app/src/main/java/net/osmtracker/activity/TrackManager.java +++ b/app/src/main/java/net/osmtracker/activity/TrackManager.java @@ -401,7 +401,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { return; } - Uri uri = (Uri) data.getData(); + Uri uri = data.getData(); try { AssetFileDescriptor afd = getContentResolver() .openAssetFileDescriptor(uri, "r"); diff --git a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java index 1082a262a..60e8a3fd2 100644 --- a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java +++ b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java @@ -9,7 +9,6 @@ import net.osmtracker.db.DataHelper; -import org.xml.sax.SAXException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; @@ -23,15 +22,13 @@ import java.time.format.DateTimeParseException; import java.util.function.DoubleConsumer; -import javax.xml.parsers.ParserConfigurationException; - interface LongConsumer { void accept(long l); } class InputStreamWithPosition extends FilterInputStream { private long position=0; - private LongConsumer report; + private final LongConsumer report; public InputStreamWithPosition(InputStream in, LongConsumer report) { @@ -80,10 +77,9 @@ public long getPosition() { public class ImportRoute { private static final String TAG = ImportRoute.class.getSimpleName(); - private long trackId; - private String ns=null; - private DataHelper dataHelper; - private Context context; + private final long trackId; + private final DataHelper dataHelper; + private final Context context; public ImportRoute(Context context, long trackId) { this.context = context; @@ -122,9 +118,9 @@ private String readText(XmlPullParser parser) private String readString(XmlPullParser parser, String tag) throws IOException, XmlPullParserException { - parser.require(XmlPullParser.START_TAG, ns, tag); + parser.require(XmlPullParser.START_TAG, null, tag); String result = readText(parser); - parser.require(XmlPullParser.END_TAG, ns, tag); + parser.require(XmlPullParser.END_TAG, null, tag); return result; } @@ -172,17 +168,16 @@ private float readFloat(XmlPullParser parser, String tag, } } - private int readInt(XmlPullParser parser, String tag, - int dflt) + private int readInt(XmlPullParser parser, String tag) throws XmlPullParserException, IOException { String str = readString(parser,tag); if(str == null || "".equals(str)) - return dflt; + return 0; try { return Integer.parseInt(str); } catch(NumberFormatException e) { Log.v(TAG, "Bad integer "+tag+" :\""+str+"\""); - return dflt; + return 0; } } @@ -211,7 +206,6 @@ private void readTime(XmlPullParser parser, String tag, .atZone(ZoneId.systemDefault()) .toInstant() .toEpochMilli()); - return; } catch(DateTimeParseException e) { Log.v(TAG, "Bad time "+tag+" :\""+str+"\":"+e); } @@ -219,9 +213,8 @@ private void readTime(XmlPullParser parser, String tag, private void readDoubleFromAttribute(XmlPullParser parser, String attribute, - DoubleConsumer setter) - throws XmlPullParserException, IOException { - String str = parser.getAttributeValue(ns, attribute); + DoubleConsumer setter) { + String str = parser.getAttributeValue(null, attribute); if(str == null || "".equals(str)) return; try { @@ -249,7 +242,7 @@ private void readPoint(XmlPullParser parser, float pressure=0.0f; /* Ensure we have correct tag */ - parser.require(XmlPullParser.START_TAG, ns, tag); + parser.require(XmlPullParser.START_TAG, null, tag); Location location = new Location("import"); readDoubleFromAttribute(parser, "lat", location::setLatitude); @@ -271,28 +264,36 @@ private void readPoint(XmlPullParser parser, continue; } String name = parser.getName(); - if (name.equals("ele")) { - readDouble(parser, name, location::setAltitude); - } else if (name.equals("time")) { - readTime(parser, name, location::setTime); - } else if (name.equals("accuracy")) { - readFloat(parser, name, location::setAccuracy); - } else if (name.equals("speed")) { - readFloat(parser, name, location::setSpeed); - } else if (name.equals("baro")) { - pressure = readFloat(parser, name, 0.0f); - } else if (name.equals("compass")) { - azimuth = readFloat(parser, name, -1.0f); - } else if (name.equals("compassAccuracy")) { - compassAccuracy = readInt(parser, name, 0); - } else { + switch(name) { + case "ele": + readDouble(parser, name, location::setAltitude); + break; + case "time": + readTime(parser, name, location::setTime); + break; + case "accuracy": + readFloat(parser, name, location::setAccuracy); + break; + case "speed": + readFloat(parser, name, location::setSpeed); + break; + case "baro": + pressure = readFloat(parser, name, 0.0f); + break; + case "compass": + azimuth = readFloat(parser, name, -1.0f); + break; + case "compassAccuracy": + compassAccuracy = readInt(parser, name); + break; + default: depth++; // ignore all other tags, but still recurse // into them } } - parser.require(XmlPullParser.END_TAG, ns, tag); + parser.require(XmlPullParser.END_TAG, null, tag); dataHelper.track(trackId, location, azimuth, compassAccuracy, pressure, newSegment, true); @@ -303,7 +304,7 @@ private void readPoint(XmlPullParser parser, private void readSegment(XmlPullParser parser, String tag) throws XmlPullParserException, IOException { boolean segmentStart = true; - parser.require(XmlPullParser.START_TAG, ns, tag); + parser.require(XmlPullParser.START_TAG, null, tag); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; @@ -317,7 +318,7 @@ private void readSegment(XmlPullParser parser, String tag) skip(parser); } } - parser.require(XmlPullParser.END_TAG, ns, tag); + parser.require(XmlPullParser.END_TAG, null, tag); } public void reportPosition(long position, @@ -330,9 +331,7 @@ public void reportPosition(long position, /** * Import the given input stream into the given track */ - public void doImport(AssetFileDescriptor afd) - throws IOException, XmlPullParserException, - ParserConfigurationException, SAXException { + public void doImport(AssetFileDescriptor afd) { ProgressDialog pb = new ProgressDialog(context); pb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); pb.setIndeterminate(false); @@ -343,7 +342,7 @@ public void doImport(AssetFileDescriptor afd) pb.show(); new Thread(()-> { - try(InputStream is = afd.createInputStream();){ + try(InputStream is = afd.createInputStream()){ long totSize = afd.getLength(); if(totSize > 0) { pb.setMax((int)totSize); @@ -370,8 +369,7 @@ public void doImport(AssetFileDescriptor afd) * Import the given input stream into the given track */ private void doImport(InputStream is) - throws IOException, XmlPullParserException, - ParserConfigurationException, SAXException { + throws IOException, XmlPullParserException { int event; // Xml Pull parser // https://www.tutorialspoint.com/android/android_xml_parsers.htm diff --git a/app/src/main/java/net/osmtracker/overlay/PathOverlays.java b/app/src/main/java/net/osmtracker/overlay/PathOverlays.java index fb9dbda91..2f6dfa593 100644 --- a/app/src/main/java/net/osmtracker/overlay/PathOverlays.java +++ b/app/src/main/java/net/osmtracker/overlay/PathOverlays.java @@ -13,15 +13,15 @@ * Collection of Overlays, useful to draw interrupted paths */ public class PathOverlays { - private float width; - private Context ctx; - private MapView osmView; - private boolean[] havePoint = new boolean[] { false, false }; + private final float width; + private final Context ctx; + private final MapView osmView; + private final boolean[] havePoint = new boolean[] { false, false }; - private int[] curIdx=new int[] { 0, 0}; + private final int[] curIdx=new int[] { 0, 0}; - private List> paths = new ArrayList<>(); - private int[] colors = new int[] { Color.BLUE, Color.GREEN }; + private final List> paths = new ArrayList<>(); + private final int[] colors = new int[] { Color.BLUE, Color.GREEN }; private void addPath(int slot) { PathOverlay path = new PathOverlay(colors[slot], width, ctx); @@ -43,7 +43,7 @@ public PathOverlays(float width, this.ctx=ctx; this.osmView = osmView; for(int slot=0; slot<2; slot++) - paths.add(new ArrayList()); + paths.add(new ArrayList<>()); } public void addPoint(double lat, double lon, boolean isRoute) { From d7b2b72983b2fcb9955f336ac3f0e0fd5c74b22a Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Sun, 9 May 2021 23:41:18 +0200 Subject: [PATCH 09/14] Handle waypoints --- .../java/net/osmtracker/gpx/ImportRoute.java | 112 +++++++++++------- 1 file changed, 71 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java index 60e8a3fd2..4996d1dfe 100644 --- a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java +++ b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java @@ -1,10 +1,12 @@ package net.osmtracker.gpx; +import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; -import android.content.Context; import android.content.res.AssetFileDescriptor; import android.location.Location; +import android.os.Bundle; +import android.os.Looper; import android.util.Log; import net.osmtracker.db.DataHelper; @@ -79,9 +81,9 @@ public class ImportRoute { private final long trackId; private final DataHelper dataHelper; - private final Context context; + private final Activity context; - public ImportRoute(Context context, long trackId) { + public ImportRoute(Activity context, long trackId) { this.context = context; this.trackId = trackId; dataHelper = new DataHelper(context); @@ -233,7 +235,8 @@ private void readDoubleFromAttribute(XmlPullParser parser, */ private void readPoint(XmlPullParser parser, String tag, - boolean newSegment) + boolean newSegment, + boolean isWaypoint) throws XmlPullParserException, IOException { int depth=1; @@ -241,10 +244,14 @@ private void readPoint(XmlPullParser parser, int compassAccuracy=0; float pressure=0.0f; + String name=null; + /* Ensure we have correct tag */ parser.require(XmlPullParser.START_TAG, null, tag); Location location = new Location("import"); + if(isWaypoint) + location.setExtras(new Bundle()); readDoubleFromAttribute(parser, "lat", location::setLatitude); readDoubleFromAttribute(parser, "lon", location::setLongitude); @@ -263,40 +270,47 @@ private void readPoint(XmlPullParser parser, if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } - String name = parser.getName(); - switch(name) { - case "ele": - readDouble(parser, name, location::setAltitude); - break; - case "time": - readTime(parser, name, location::setTime); - break; - case "accuracy": - readFloat(parser, name, location::setAccuracy); - break; - case "speed": - readFloat(parser, name, location::setSpeed); - break; - case "baro": - pressure = readFloat(parser, name, 0.0f); - break; - case "compass": - azimuth = readFloat(parser, name, -1.0f); - break; - case "compassAccuracy": - compassAccuracy = readInt(parser, name); - break; - default: - depth++; - // ignore all other tags, but still recurse - // into them + String subTag = parser.getName(); + switch(subTag) { + case "ele": + readDouble(parser, subTag, location::setAltitude); + break; + case "time": + readTime(parser, subTag, location::setTime); + break; + case "accuracy": + readFloat(parser, subTag, location::setAccuracy); + break; + case "speed": + readFloat(parser, subTag, location::setSpeed); + break; + case "baro": + pressure = readFloat(parser, subTag, 0.0f); + break; + case "compass": + azimuth = readFloat(parser, subTag, -1.0f); + break; + case "compassAccuracy": + compassAccuracy = readInt(parser, subTag); + break; + case "name": + name = readString(parser, subTag); + break; + default: + depth++; + // ignore all other tags, but still recurse + // into them } } parser.require(XmlPullParser.END_TAG, null, tag); - dataHelper.track(trackId, location, azimuth, - compassAccuracy, pressure, newSegment, true); + if(isWaypoint) { + dataHelper.wayPoint(trackId, location, name, null, null, azimuth, compassAccuracy, pressure); + } else { + dataHelper.track(trackId, location, azimuth, + compassAccuracy, pressure, newSegment, true); + } } // Parses the contents of a track or route segment. If it encounters a @@ -312,7 +326,7 @@ private void readSegment(XmlPullParser parser, String tag) String name = parser.getName(); if (name.equals("trkpt")|| name.equals("rtept")) { - readPoint(parser, name, segmentStart); + readPoint(parser, name, segmentStart,false); segmentStart = false; } else { skip(parser); @@ -328,6 +342,23 @@ public void reportPosition(long position, pb.setProgress((int)position); } + + private void showException(String msg, Exception e) { + try { + new AlertDialog.Builder(context) + .setTitle("Exception received while "+msg) + .setMessage(Log.getStackTraceString(e)) + .setNeutralButton("Ok", + (dlg,id)->dlg.dismiss()) + .create() + .show(); + } catch(Exception e2) { + Log.v(TAG, "Exception while showing exception "+ + Log.getStackTraceString(e2)); + + } + } + /** * Import the given input stream into the given track */ @@ -343,6 +374,7 @@ public void doImport(AssetFileDescriptor afd) { new Thread(()-> { try(InputStream is = afd.createInputStream()){ + Looper.prepare(); long totSize = afd.getLength(); if(totSize > 0) { pb.setMax((int)totSize); @@ -352,13 +384,9 @@ public void doImport(AssetFileDescriptor afd) { p->reportPosition(p, totSize, pb)); doImport(isp); } catch(Exception e) { - new AlertDialog.Builder(context) - .setTitle("Exception received") - .setMessage(Log.getStackTraceString(e)) - .setNeutralButton("Ok", - (dlg,id)->dlg.dismiss()) - .create() - .show(); + Log.v(TAG, "Exception during import "+ + Log.getStackTraceString(e)); + context.runOnUiThread(()->showException("importing route", e)); } finally { pb.dismiss(); } @@ -387,6 +415,8 @@ private void doImport(InputStream is) if("trkseg".equals(name) || "rte".equals(name)) { readSegment(p, name); + } else if (name.equals("wpt")) { + readPoint(p, name, false, true); } } } From d7c77824dfda63cf4cc6412516623ac025497585 Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Mon, 10 May 2021 08:10:24 +0200 Subject: [PATCH 10/14] Refresh track list display after import --- app/src/main/java/net/osmtracker/activity/TrackManager.java | 2 +- app/src/main/java/net/osmtracker/gpx/ImportRoute.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/net/osmtracker/activity/TrackManager.java b/app/src/main/java/net/osmtracker/activity/TrackManager.java index 0978c7352..6d46e7682 100644 --- a/app/src/main/java/net/osmtracker/activity/TrackManager.java +++ b/app/src/main/java/net/osmtracker/activity/TrackManager.java @@ -407,7 +407,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { .openAssetFileDescriptor(uri, "r"); new ImportRoute(this, contextMenuSelectedTrackid) - .doImport(afd); + .doImport(afd,()->updateTrackItemsInRecyclerView()); } catch(Exception e) { new AlertDialog.Builder(this) .setTitle("Exception received") diff --git a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java index 4996d1dfe..65dc835a2 100644 --- a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java +++ b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java @@ -362,7 +362,8 @@ private void showException(String msg, Exception e) { /** * Import the given input stream into the given track */ - public void doImport(AssetFileDescriptor afd) { + public void doImport(AssetFileDescriptor afd, + Runnable completion) { ProgressDialog pb = new ProgressDialog(context); pb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); pb.setIndeterminate(false); @@ -383,6 +384,7 @@ public void doImport(AssetFileDescriptor afd) { new InputStreamWithPosition(is, p->reportPosition(p, totSize, pb)); doImport(isp); + context.runOnUiThread(completion); } catch(Exception e) { Log.v(TAG, "Exception during import "+ Log.getStackTraceString(e)); From d718bf883bc195989ad7fd750a9b478c17147b95 Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Wed, 12 May 2021 20:50:49 +0200 Subject: [PATCH 11/14] Fix incompatibilities with min API version --- .../java/net/osmtracker/gpx/ImportRoute.java | 66 ++++++++++++------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java index 65dc835a2..a15199953 100644 --- a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java +++ b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java @@ -18,16 +18,21 @@ import java.io.FilterInputStream; import java.io.InputStream; import java.io.IOException; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeParseException; -import java.util.function.DoubleConsumer; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; interface LongConsumer { void accept(long l); } +interface DoubleConsumer { + void accept(double d); +} + class InputStreamWithPosition extends FilterInputStream { private long position=0; private final LongConsumer report; @@ -183,33 +188,48 @@ private int readInt(XmlPullParser parser, String tag) } } + private static final DateFormat dfz = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSz"); + private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + + private static final Pattern p = + Pattern.compile("^(\\d+-\\d+-\\d+T\\d+:\\d+:\\d+)(\\.\\d+)?(Z|(\\+\\d{2}):?(\\d{2})?)?"); + // + + private static String or(String first, String second) { + return (first != null) ? first : second; + } + private void readTime(XmlPullParser parser, String tag, LongConsumer setter) throws XmlPullParserException, IOException { String str = readString(parser,tag); if(str == null || "".equals(str)) return; - - // first try with timezone... - try { - setter.accept(ZonedDateTime - .parse(str) - .toInstant() - .toEpochMilli()); - return; - } catch(DateTimeParseException e) { - Log.v(TAG, "Bad zoned time "+tag+" :\""+str+"\":"+e); + + Matcher m = p.matcher(str); + if(!m.find()) { + Log.v(TAG, "Bad date string \""+str+"\""); } - // and then without + StringBuilder sb = new StringBuilder(); + + sb.append(m.group(1)); // date and time + sb.append(or(m.group(2),".000")); // milliseconds + + String tz = m.group(3); + if("Z".equals(tz)) + sb.append("+0000"); + else if(tz != null) { + sb.append(m.group(4)); + sb.append(or(m.group(5),"00")); + } + String dstr = sb.toString(); try { - setter.accept(LocalDateTime - .parse(str) - .atZone(ZoneId.systemDefault()) - .toInstant() - .toEpochMilli()); - } catch(DateTimeParseException e) { - Log.v(TAG, "Bad time "+tag+" :\""+str+"\":"+e); + Date d = (tz != null) ? dfz.parse(dstr) : df.parse(dstr); + setter.accept(d.getTime()); + } catch(ParseException e) { + Log.v(TAG, "Bad date string \""+str+"\" => \""+ + dstr+"\""); } } From 66256100eb3753e49c0dd2b25a0366f700b84436 Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Sun, 16 May 2021 17:58:42 +0200 Subject: [PATCH 12/14] Fix SQL_CREATE_TABLE_TRACKPOINT --- app/src/main/java/net/osmtracker/db/DatabaseHelper.java | 2 +- 1 file changed, 1 insertion(+), 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 0d3c9edfc..89cb1b34b 100644 --- a/app/src/main/java/net/osmtracker/db/DatabaseHelper.java +++ b/app/src/main/java/net/osmtracker/db/DatabaseHelper.java @@ -41,7 +41,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { + TrackContentProvider.Schema.COL_COMPASS_ACCURACY + " integer null," + TrackContentProvider.Schema.COL_ATMOSPHERIC_PRESSURE + " double null," + TrackContentProvider.Schema.COL_NEW_SEGMENT + " integer default 0," - + TrackContentProvider.Schema.COL_NEW_SEGMENT + " integer default 0" + + TrackContentProvider.Schema.COL_IS_ROUTE + " integer default 0" + ")"; /** From 648261240ce2cf7d594db02c11312b6ad60fe9d4 Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Sun, 16 May 2021 18:28:17 +0200 Subject: [PATCH 13/14] Explicitly test for date being different than null to shut up warning --- app/src/main/java/net/osmtracker/gpx/ImportRoute.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java index a15199953..2135e1593 100644 --- a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java +++ b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java @@ -226,7 +226,8 @@ else if(tz != null) { String dstr = sb.toString(); try { Date d = (tz != null) ? dfz.parse(dstr) : df.parse(dstr); - setter.accept(d.getTime()); + if(d!=null) + setter.accept(d.getTime()); } catch(ParseException e) { Log.v(TAG, "Bad date string \""+str+"\" => \""+ dstr+"\""); From 3ed7b3ebd6d07454423dc85675520bdf19e94392 Mon Sep 17 00:00:00 2001 From: Alain Knaff Date: Sun, 16 May 2021 21:16:58 +0200 Subject: [PATCH 14/14] Support for KML --- .../java/net/osmtracker/gpx/ImportRoute.java | 121 +++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java index 2135e1593..907117ca9 100644 --- a/app/src/main/java/net/osmtracker/gpx/ImportRoute.java +++ b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java @@ -131,16 +131,21 @@ private String readString(XmlPullParser parser, String tag) return result; } + private void readDouble(XmlPullParser parser, String tag, DoubleConsumer setter) throws XmlPullParserException, IOException { String str = readString(parser,tag); + storeDouble(str, setter); + } + + private void storeDouble(String str, DoubleConsumer setter) { if(str == null || "".equals(str)) return; try { setter.accept(Double.parseDouble(str)); } catch(NumberFormatException e) { - Log.v(TAG, "Bad double "+tag+" :\""+str+"\""); + Log.v(TAG, "Bad double :\""+str+"\""); } } @@ -248,6 +253,8 @@ private void readDoubleFromAttribute(XmlPullParser parser, } } + // GPX + /** * Reads a track point * @param parser the parser @@ -356,6 +363,112 @@ private void readSegment(XmlPullParser parser, String tag) parser.require(XmlPullParser.END_TAG, null, tag); } + + // KML + private static final Pattern pat = + Pattern.compile("\\G\\s*([0-9.]+)\\s*,\\s*([0-9.]+)\\s*(?:,\\s*([0-9.]+)\\s*)?"); + + private Location nextCoordinate(Matcher m) { + Location location = new Location("import"); + storeDouble(m.group(1), location::setLongitude); + storeDouble(m.group(2), location::setLatitude); + storeDouble(m.group(3), location::setAltitude); + return location; + } + + private Matcher parseCoordinates(XmlPullParser parser, String tag) + throws XmlPullParserException, IOException + { + return pat.matcher(readString(parser, tag)); + } + + private void readKmlTrackSegment(XmlPullParser parser, String tag) + throws XmlPullParserException, IOException + { + parser.require(XmlPullParser.START_TAG, null, tag); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + + String subTag = parser.getName(); + if("coordinates".equals(subTag)) { + boolean newSegment = true; + Matcher m = parseCoordinates(parser, subTag); + while (m.find()) { + Location point = nextCoordinate(m); + dataHelper.track(trackId, point, + -1.0f, 0, 0.0f, + newSegment, true); + newSegment = false; + } + } else + skip(parser); + } + parser.require(XmlPullParser.END_TAG, null, tag); + } + + private Location readKmlWayPoint(XmlPullParser parser, String tag) + throws XmlPullParserException, IOException + { + Location point=null; + parser.require(XmlPullParser.START_TAG, null, tag); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String subTag = parser.getName(); + if("coordinates".equals(subTag)) { + Matcher m = parseCoordinates(parser, subTag); + if (!m.find()) + return null; + point = nextCoordinate(m); + } else { + skip(parser); + } + } + parser.require(XmlPullParser.END_TAG, null, tag); + return point; + } + + private void readPlacemark(XmlPullParser parser, String tag) + throws XmlPullParserException, IOException { + + Location waypoint=null; + String wptName=null; + + parser.require(XmlPullParser.START_TAG, null, tag); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String subTag = parser.getName(); + switch(subTag) { + case "Point": + // a waypoint + waypoint = readKmlWayPoint(parser, subTag); + break; + case "name": + // the name of the waypoint or track + wptName = readString(parser, subTag); + break; + case "LineString": + // a track segment + readKmlTrackSegment(parser, subTag); + break; + default: + skip(parser); + } + } + parser.require(XmlPullParser.END_TAG, null, tag); + + if(waypoint != null) { + waypoint.setExtras(new Bundle()); + dataHelper.wayPoint(trackId, waypoint, wptName, + null, null, -1.0f, 0, 0.0f); + } + } + public void reportPosition(long position, long totalSize, ProgressDialog pb) { @@ -435,11 +548,17 @@ private void doImport(InputStream is) while ((event=p.next()) != XmlPullParser.END_DOCUMENT) { if(event == XmlPullParser.START_TAG) { String name=p.getName(); + + // GPX if("trkseg".equals(name) || "rte".equals(name)) { readSegment(p, name); } else if (name.equals("wpt")) { readPoint(p, name, false, true); + + // KML + } else if (name.equals("Placemark")) { + readPlacemark(p, name); } } }