diff --git a/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java b/app/src/main/java/net/osmtracker/activity/DisplayTrackMap.java
index c0e8f3d64..f3d4f66bf 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;
@@ -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;
@@ -109,7 +108,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
@@ -148,7 +147,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,9 +378,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((float)(metrics.densityDpi / 25.4 / 2),this, osmView);
myLocationOverlay = new SimpleLocationOverlay(this);
osmView.getOverlays().add(myLocationOverlay);
@@ -426,7 +423,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, 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
@@ -454,16 +451,26 @@ private void pathChanged() {
c.moveToFirst();
double lastLat = 0;
double lastLon = 0;
+ 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);
-
+ 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()) {
lastLat = c.getDouble(latitudeColumnIndex);
lastLon = c.getDouble(longitudeColumnIndex);
lastTrackPointIdProcessed = c.getInt(primaryKeyColumnIndex);
- pathOverlay.addPoint((int)(lastLat * 1e6), (int)(lastLon * 1e6));
+ newSegment = c.getShort(newSegmentColumnIndex) > 0;
+ isRoute = c.getShort(isRouteColumnIndex) > 0;
+ if(newSegment) {
+ pathOverlay.nextSegment(isRoute);
+ }
+
+ 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/activity/TrackManager.java b/app/src/main/java/net/osmtracker/activity/TrackManager.java
index 19d7e2fe0..6d46e7682 100644
--- a/app/src/main/java/net/osmtracker/activity/TrackManager.java
+++ b/app/src/main/java/net/osmtracker/activity/TrackManager.java
@@ -10,6 +10,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;
@@ -17,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;
@@ -39,6 +41,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 +64,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 +375,53 @@ 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 = data.getData();
+ try {
+ AssetFileDescriptor afd = getContentResolver()
+ .openAssetFileDescriptor(uri, "r");
+ new ImportRoute(this,
+ contextMenuSelectedTrackid)
+ .doImport(afd,()->updateTrackItemsInRecyclerView());
+ } 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 +529,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/db/DataHelper.java b/app/src/main/java/net/osmtracker/db/DataHelper.java
index 8c4934020..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) {
+ 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);
@@ -146,6 +153,12 @@ 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);
+
+ 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 75b3f9e9d..89cb1b34b 100644
--- a/app/src/main/java/net/osmtracker/db/DatabaseHelper.java
+++ b/app/src/main/java/net/osmtracker/db/DatabaseHelper.java
@@ -39,7 +39,10 @@ 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,"
+ + TrackContentProvider.Schema.COL_IS_ROUTE + " integer default 0"
+ + ")";
/**
* SQL for creating index TRACKPOINT_idx (track id)
@@ -125,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 = 17;
+ private static final int DB_VERSION = 19;
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
@@ -178,6 +181,10 @@ 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");
+ 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 ffb551ed0..9662f867d 100644
--- a/app/src/main/java/net/osmtracker/db/TrackContentProvider.java
+++ b/app/src/main/java/net/osmtracker/db/TrackContentProvider.java
@@ -496,7 +496,10 @@ 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";
+ 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";
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..6340a0789 100644
--- a/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java
+++ b/app/src/main/java/net/osmtracker/gpx/ExportTrackTask.java
@@ -358,8 +358,18 @@ 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(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");
+ }
+ havePoint=true;
+
out.append("\t\t\t" + "" + "\n");
@@ -612,4 +622,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/gpx/ImportRoute.java b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java
new file mode 100644
index 000000000..907117ca9
--- /dev/null
+++ b/app/src/main/java/net/osmtracker/gpx/ImportRoute.java
@@ -0,0 +1,566 @@
+package net.osmtracker.gpx;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+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;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.FilterInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+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;
+
+ 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
+ */
+public class ImportRoute {
+ private static final String TAG = ImportRoute.class.getSimpleName();
+
+ private final long trackId;
+ private final DataHelper dataHelper;
+ private final Activity context;
+
+ public ImportRoute(Activity 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, null, tag);
+ String result = readText(parser);
+ parser.require(XmlPullParser.END_TAG, null, 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 :\""+str+"\"");
+ }
+ }
+
+ private interface FloatConsumer {
+ void accept(float f);
+ }
+
+ 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)
+ throws XmlPullParserException, IOException {
+ String str = readString(parser,tag);
+ if(str == null || "".equals(str))
+ return 0;
+ try {
+ return Integer.parseInt(str);
+ } catch(NumberFormatException e) {
+ Log.v(TAG, "Bad integer "+tag+" :\""+str+"\"");
+ return 0;
+ }
+ }
+
+ 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;
+
+ Matcher m = p.matcher(str);
+ if(!m.find()) {
+ Log.v(TAG, "Bad date string \""+str+"\"");
+ }
+
+ 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 {
+ Date d = (tz != null) ? dfz.parse(dstr) : df.parse(dstr);
+ if(d!=null)
+ setter.accept(d.getTime());
+ } catch(ParseException e) {
+ Log.v(TAG, "Bad date string \""+str+"\" => \""+
+ dstr+"\"");
+ }
+ }
+
+ private void readDoubleFromAttribute(XmlPullParser parser,
+ String attribute,
+ DoubleConsumer setter) {
+ String str = parser.getAttributeValue(null, attribute);
+ if(str == null || "".equals(str))
+ return;
+ try {
+ setter.accept(Double.parseDouble(str));
+ } catch(NumberFormatException e) {
+ Log.v(TAG, "Bad double attribute "+attribute+
+ " :\""+str+"\"");
+ }
+ }
+
+ // GPX
+
+ /**
+ * 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,
+ boolean isWaypoint)
+ throws XmlPullParserException, IOException {
+ int depth=1;
+
+ float azimuth=-1.0f;
+ 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);
+
+ /* 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 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);
+
+ 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
+ // 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, null, tag);
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ String name = parser.getName();
+ if (name.equals("trkpt")||
+ name.equals("rtept")) {
+ readPoint(parser, name, segmentStart,false);
+ segmentStart = false;
+ } else {
+ skip(parser);
+ }
+ }
+ 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) {
+ if(position > 30 && position <= totalSize)
+ 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
+ */
+ public void doImport(AssetFileDescriptor afd,
+ Runnable completion) {
+ 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()){
+ Looper.prepare();
+ long totSize = afd.getLength();
+ if(totSize > 0) {
+ pb.setMax((int)totSize);
+ }
+ InputStreamWithPosition isp =
+ 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));
+ context.runOnUiThread(()->showException("importing route", e));
+ } finally {
+ pb.dismiss();
+ }
+ }).start();
+ }
+
+ /**
+ * Import the given input stream into the given track
+ */
+ private void doImport(InputStream is)
+ throws IOException, XmlPullParserException {
+ 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();
+
+ // 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);
+ }
+ }
+ }
+ }
+}
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..2f6dfa593
--- /dev/null
+++ b/app/src/main/java/net/osmtracker/overlay/PathOverlays.java
@@ -0,0 +1,63 @@
+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;
+import android.graphics.Color;
+
+/**
+ * Collection of Overlays, useful to draw interrupted paths
+ */
+public class PathOverlays {
+ private final float width;
+ private final Context ctx;
+ private final MapView osmView;
+ private final boolean[] havePoint = new boolean[] { false, false };
+
+ private final int[] curIdx=new int[] { 0, 0};
+
+ 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);
+ paths.get(slot).add(path);
+ osmView.getOverlays().add(path);
+ }
+
+ public void clearPath() {
+ for(int slot=0; slot<2; slot++) {
+ for(PathOverlay path : paths.get(slot))
+ path.clearPath();
+ curIdx[slot]=0;
+ }
+ }
+
+ public PathOverlays(float width,
+ Context ctx, MapView osmView) {
+ this.width=width;
+ this.ctx=ctx;
+ this.osmView = osmView;
+ for(int slot=0; slot<2; slot++)
+ paths.add(new ArrayList<>());
+ }
+
+ 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(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 ce596da58..353b6c7de 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 = false;
+
/**
* 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, false);
+ newSeg = false;
}
}
}
@@ -151,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);
@@ -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, false);
+ newSeg = false;
}
}
}
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}