package net.osmand.plus.activities;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.text.format.DateFormat;
import net.osmand.PlatformUtil;
import net.osmand.data.LatLon;
import net.osmand.plus.GPXUtilities;
import net.osmand.plus.GPXUtilities.GPXFile;
import net.osmand.plus.GPXUtilities.GPXTrackAnalysis;
import net.osmand.plus.GPXUtilities.Track;
import net.osmand.plus.GPXUtilities.TrkSegment;
import net.osmand.plus.GPXUtilities.WptPt;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.OsmAndLocationProvider;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.monitoring.OsmandMonitoringPlugin;
import net.osmand.plus.notifications.OsmandNotification;
import net.osmand.plus.notifications.OsmandNotification.NotificationType;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class SavingTrackHelper extends SQLiteOpenHelper {
public final static String DATABASE_NAME = "tracks"; //$NON-NLS-1$
public final static int DATABASE_VERSION = 5;
public final static String TRACK_NAME = "track"; //$NON-NLS-1$
public final static String TRACK_COL_DATE = "date"; //$NON-NLS-1$
public final static String TRACK_COL_LAT = "lat"; //$NON-NLS-1$
public final static String TRACK_COL_LON = "lon"; //$NON-NLS-1$
public final static String TRACK_COL_ALTITUDE = "altitude"; //$NON-NLS-1$
public final static String TRACK_COL_SPEED = "speed"; //$NON-NLS-1$
public final static String TRACK_COL_HDOP = "hdop"; //$NON-NLS-1$
public final static String POINT_NAME = "point"; //$NON-NLS-1$
public final static String POINT_COL_DATE = "date"; //$NON-NLS-1$
public final static String POINT_COL_LAT = "lat"; //$NON-NLS-1$
public final static String POINT_COL_LON = "lon"; //$NON-NLS-1$
public final static String POINT_COL_NAME = "pname"; //$NON-NLS-1$
public final static String POINT_COL_CATEGORY = "category"; //$NON-NLS-1$
public final static String POINT_COL_DESCRIPTION = "description"; //$NON-NLS-1$
public final static String POINT_COL_COLOR = "color"; //$NON-NLS-1$
public final static Log log = PlatformUtil.getLog(SavingTrackHelper.class);
private String updateScript;
private String insertPointsScript;
private long lastTimeUpdated = 0;
private final OsmandApplication ctx;
private LatLon lastPoint;
private float distance = 0;
private long duration = 0;
private SelectedGpxFile currentTrack;
private int points;
public SavingTrackHelper(OsmandApplication ctx){
super(ctx, DATABASE_NAME, null, DATABASE_VERSION);
this.ctx = ctx;
this.currentTrack = new SelectedGpxFile();
this.currentTrack.setShowCurrentTrack(true);
GPXFile gx = new GPXFile();
gx.showCurrentTrack = true;
this.currentTrack.setGpxFile(gx);
prepareCurrentTrackForRecording();
updateScript = "INSERT INTO " + TRACK_NAME + " (" + TRACK_COL_LAT + ", " + TRACK_COL_LON + ", "
+ TRACK_COL_ALTITUDE + ", " + TRACK_COL_SPEED + ", " + TRACK_COL_HDOP + ", " + TRACK_COL_DATE + ")"
+ " VALUES (?, ?, ?, ?, ?, ?)"; //$NON-NLS-1$ //$NON-NLS-2$
insertPointsScript = "INSERT INTO " + POINT_NAME + " VALUES (?, ?, ?, ?, ?, ?, ?)"; //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public void onCreate(SQLiteDatabase db) {
createTableForTrack(db);
createTableForPoints(db);
}
private void createTableForTrack(SQLiteDatabase db){
db.execSQL("CREATE TABLE " + TRACK_NAME + " (" + TRACK_COL_LAT + " double, " + TRACK_COL_LON + " double, " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ TRACK_COL_ALTITUDE + " double, " + TRACK_COL_SPEED + " double, " //$NON-NLS-1$ //$NON-NLS-2$
+ TRACK_COL_HDOP + " double, " + TRACK_COL_DATE + " long )"); //$NON-NLS-1$ //$NON-NLS-2$
}
private void createTableForPoints(SQLiteDatabase db){
try {
db.execSQL("CREATE TABLE " + POINT_NAME + " (" + POINT_COL_LAT + " double, " + POINT_COL_LON + " double, " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ POINT_COL_DATE + " long, " + POINT_COL_DESCRIPTION + " text, " + POINT_COL_NAME + " text, "
+ POINT_COL_CATEGORY + " text, " + POINT_COL_COLOR + " long" + ")"); //$NON-NLS-1$ //$NON-NLS-2$
} catch (RuntimeException e) {
// ignore if already exists
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if(oldVersion < 2){
createTableForPoints(db);
}
if(oldVersion < 3){
db.execSQL("ALTER TABLE " + TRACK_NAME + " ADD " + TRACK_COL_HDOP + " double");
}
if(oldVersion < 4){
db.execSQL("ALTER TABLE " + POINT_NAME + " ADD " + POINT_COL_NAME + " text");
db.execSQL("ALTER TABLE " + POINT_NAME + " ADD " + POINT_COL_CATEGORY + " text");
}
if(oldVersion < 5){
db.execSQL("ALTER TABLE " + POINT_NAME + " ADD " + POINT_COL_COLOR + " long");
}
}
public long getLastTrackPointTime() {
long res = 0;
try {
SQLiteDatabase db = getWritableDatabase();
if (db != null) {
try {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_DATE + " FROM " + TRACK_NAME + " ORDER BY " + TRACK_COL_DATE + " DESC", null); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if(query.moveToFirst()) {
res = query.getLong(0);
}
query.close();
} finally {
db.close();
}
}
} catch(RuntimeException e) {
}
return res;
}
public synchronized boolean hasDataToSave() {
try {
SQLiteDatabase db = getWritableDatabase();
if (db != null) {
try {
Cursor q = db.query(false, TRACK_NAME, new String[0], null, null, null, null, null, null);
boolean has = q.moveToFirst();
q.close();
if (has) {
return true;
}
q = db.query(false, POINT_NAME, new String[]{POINT_COL_LAT, POINT_COL_LON}, null, null, null, null, null, null);
has = q.moveToFirst();
while(has) {
if(q.getDouble(0) != 0 || q.getDouble(1) != 0) {
break;
}
if(!q.moveToNext()) {
has = false;
break;
}
}
q.close();
if (has) {
return true;
}
} finally {
db.close();
}
}
} catch(RuntimeException e) {
return false;
}
return false;
}
/**
* @return warnings
*/
public synchronized List<String> saveDataToGpx(File dir ) {
List<String> warnings = new ArrayList<String>();
dir.mkdirs();
if (dir.getParentFile().canWrite()) {
if (dir.exists()) {
Map<String, GPXFile> data = collectRecordedData();
// save file
for (final String f : data.keySet()) {
File fout = new File(dir, f + ".gpx"); //$NON-NLS-1$
if (!data.get(f).isEmpty()) {
WptPt pt = data.get(f).findPointToShow();
String fileName = f + "_" + new SimpleDateFormat("HH-mm_EEE", Locale.US).format(new Date(pt.time)); //$NON-NLS-1$
fout = new File(dir, fileName + ".gpx"); //$NON-NLS-1$
int ind = 1;
while (fout.exists()) {
fout = new File(dir, fileName + "_" + (++ind) + ".gpx"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
String warn = GPXUtilities.writeGpxFile(fout, data.get(f), ctx);
if (warn != null) {
warnings.add(warn);
return warnings;
}
}
}
}
SQLiteDatabase db = getWritableDatabase();
if (db != null && warnings.isEmpty() && db.isOpen()) {
try {
// remove all from db
db.execSQL("DELETE FROM " + TRACK_NAME + " WHERE " + TRACK_COL_DATE + " <= ?", new Object[] { System.currentTimeMillis() }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
db.execSQL("DELETE FROM " + POINT_NAME + " WHERE " + POINT_COL_DATE + " <= ?", new Object[] { System.currentTimeMillis() }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
// delete all
// db.execSQL("DELETE FROM " + TRACK_NAME + " WHERE 1 = 1", new Object[] { }); //$NON-NLS-1$ //$NON-NLS-2$
// db.execSQL("DELETE FROM " + POINT_NAME + " WHERE 1 = 1", new Object[] { }); //$NON-NLS-1$ //$NON-NLS-2$
} finally {
db.close();
}
}
distance = 0;
points = 0;
duration = 0;
currentTrack.getModifiableGpxFile().points.clear();
currentTrack.getModifiableGpxFile().tracks.clear();
currentTrack.getModifiablePointsToDisplay().clear();
currentTrack.getModifiableGpxFile().modifiedTime = System.currentTimeMillis();
prepareCurrentTrackForRecording();
return warnings;
}
public Map<String, GPXFile> collectRecordedData() {
Map<String, GPXFile> data = new LinkedHashMap<String, GPXFile>();
SQLiteDatabase db = getReadableDatabase();
if (db != null && db.isOpen()) {
try {
collectDBPoints(db, data);
collectDBTracks(db, data);
} finally {
db.close();
}
}
return data;
}
private void collectDBPoints(SQLiteDatabase db, Map<String, GPXFile> dataTracks) {
Cursor query = db.rawQuery("SELECT " + POINT_COL_LAT + "," + POINT_COL_LON + "," + POINT_COL_DATE + "," //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ POINT_COL_DESCRIPTION + "," + POINT_COL_NAME + "," + POINT_COL_CATEGORY + "," + POINT_COL_COLOR + " FROM " + POINT_NAME+" ORDER BY " + POINT_COL_DATE +" ASC", null); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (query.moveToFirst()) {
do {
WptPt pt = new WptPt();
pt.lat = query.getDouble(0);
pt.lon = query.getDouble(1);
long time = query.getLong(2);
pt.time = time;
pt.desc = query.getString(3);
pt.name = query.getString(4);
pt.category = query.getString(5);
int color = query.getInt(6);
if (color != 0) {
pt.setColor(color);
}
// check if name is extension (needed for audio/video plugin & josm integration)
if(pt.name != null && pt.name.length() > 4 && pt.name.charAt(pt.name.length() - 4) == '.') {
pt.link = pt.name;
}
String date = DateFormat.format("yyyy-MM-dd", time).toString(); //$NON-NLS-1$
GPXFile gpx;
if (dataTracks.containsKey(date)) {
gpx = dataTracks.get(date);
} else {
gpx = new GPXFile();
dataTracks.put(date, gpx);
}
gpx.points.add(pt);
} while (query.moveToNext());
}
query.close();
}
private void collectDBTracks(SQLiteDatabase db, Map<String, GPXFile> dataTracks) {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_LAT + "," + TRACK_COL_LON + "," + TRACK_COL_ALTITUDE + "," //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ TRACK_COL_SPEED + "," + TRACK_COL_HDOP + "," + TRACK_COL_DATE + " FROM " + TRACK_NAME +" ORDER BY " + TRACK_COL_DATE +" ASC", null); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
long previousTime = 0;
long previousInterval = 0;
TrkSegment segment = null;
Track track = null;
if (query.moveToFirst()) {
do {
WptPt pt = new WptPt();
pt.lat = query.getDouble(0);
pt.lon = query.getDouble(1);
pt.ele = query.getDouble(2);
pt.speed = query.getDouble(3);
pt.hdop = query.getDouble(4);
long time = query.getLong(5);
pt.time = time;
long currentInterval = Math.abs(time - previousTime);
boolean newInterval = pt.lat == 0 && pt.lon == 0;
if (track != null && !newInterval && (!ctx.getSettings().AUTO_SPLIT_RECORDING.get() || currentInterval < 6 * 60 * 1000 || currentInterval < 10 * previousInterval)) {
// 6 minute - same segment
segment.points.add(pt);
} else if (track != null && (ctx.getSettings().AUTO_SPLIT_RECORDING.get() && currentInterval < 2 * 60 * 60 * 1000)) {
// 2 hour - same track
segment = new TrkSegment();
if(!newInterval) {
segment.points.add(pt);
}
track.segments.add(segment);
} else {
// check if date the same - new track otherwise new file
track = new Track();
segment = new TrkSegment();
track.segments.add(segment);
if(!newInterval) {
segment.points.add(pt);
}
String date = DateFormat.format("yyyy-MM-dd", time).toString(); //$NON-NLS-1$
if (dataTracks.containsKey(date)) {
GPXFile gpx = dataTracks.get(date);
gpx.tracks.add(track);
} else {
GPXFile file = new GPXFile();
file.tracks.add(track);
dataTracks.put(date, file);
}
}
previousInterval = currentInterval;
previousTime = time;
} while (query.moveToNext());
}
query.close();
}
public void startNewSegment() {
lastTimeUpdated = 0;
lastPoint = null;
execWithClose(updateScript, new Object[] { 0, 0, 0, 0, 0, System.currentTimeMillis()});
addTrackPoint(null, true, System.currentTimeMillis());
}
public void updateLocation(net.osmand.Location location) {
// use because there is a bug on some devices with location.getTime()
long locationTime = System.currentTimeMillis();
OsmandSettings settings = ctx.getSettings();
boolean record = false;
if(OsmAndLocationProvider.isPointAccurateForRouting(location) &&
OsmAndLocationProvider.isNotSimulatedLocation(location) ) {
if (OsmandPlugin.getEnabledPlugin(OsmandMonitoringPlugin.class) != null) {
if (settings.SAVE_TRACK_TO_GPX.get()
&& locationTime - lastTimeUpdated > settings.SAVE_TRACK_INTERVAL.get()
&& ctx.getRoutingHelper().isFollowingMode()) {
record = true;
} else if (settings.SAVE_GLOBAL_TRACK_TO_GPX.get()
&& locationTime - lastTimeUpdated > settings.SAVE_GLOBAL_TRACK_INTERVAL.get()) {
record = true;
}
float minDistance = settings.SAVE_TRACK_MIN_DISTANCE.get();
if(minDistance > 0 && lastPoint != null && MapUtils.getDistance(lastPoint, location.getLatitude(), location.getLongitude()) <
minDistance) {
record = false;
}
float precision = settings.SAVE_TRACK_PRECISION.get();
if(precision > 0 && (!location.hasAccuracy() || location.getAccuracy() > precision)) {
record = false;
}
float minSpeed = settings.SAVE_TRACK_MIN_SPEED.get();
if(minSpeed > 0 && (!location.hasSpeed() || location.getSpeed() < minSpeed)) {
record = false;
}
}
}
if (record) {
insertData(location.getLatitude(), location.getLongitude(), location.getAltitude(), location.getSpeed(),
location.getAccuracy(), locationTime, settings);
ctx.getNotificationHelper().refreshNotification(NotificationType.GPX);
}
}
public void insertData(double lat, double lon, double alt, double speed, double hdop, long time,
OsmandSettings settings) {
// * 1000 in next line seems to be wrong with new IntervalChooseDialog
// if (time - lastTimeUpdated > settings.SAVE_TRACK_INTERVAL.get() * 1000) {
execWithClose(updateScript, new Object[] { lat, lon, alt, speed, hdop, time });
boolean newSegment = false;
if (lastPoint == null || (time - lastTimeUpdated) > 180 * 1000) {
lastPoint = new LatLon(lat, lon);
newSegment = true;
} else {
float[] lastInterval = new float[1];
net.osmand.Location.distanceBetween(lat, lon, lastPoint.getLatitude(), lastPoint.getLongitude(),
lastInterval);
if (lastTimeUpdated > 0 && time > lastTimeUpdated) {
duration += time - lastTimeUpdated;
}
distance += lastInterval[0];
lastPoint = new LatLon(lat, lon);
}
lastTimeUpdated = time;
WptPt pt = new GPXUtilities.WptPt(lat, lon, time, alt, speed, hdop);
addTrackPoint(pt, newSegment, time);
}
private void addTrackPoint(WptPt pt, boolean newSegment, long time) {
List<TrkSegment> points = currentTrack.getModifiablePointsToDisplay();
Track track = currentTrack.getModifiableGpxFile().tracks.get(0);
assert track.segments.size() == points.size();
if (points.size() == 0 || newSegment) {
points.add(new TrkSegment());
}
boolean segmentAdded = false;
if (track.segments.size() == 0 || newSegment) {
track.segments.add(new TrkSegment());
segmentAdded = true;
}
if (pt != null) {
int ind = points.size() - 1;
TrkSegment last = points.get(ind);
last.points.add(pt);
TrkSegment lt = track.segments.get(track.segments.size() - 1);
lt.points.add(pt);
}
if (segmentAdded) {
currentTrack.processPoints();
}
currentTrack.getModifiableGpxFile().modifiedTime = time;
}
public WptPt insertPointData(double lat, double lon, long time, String description, String name, String category, int color) {
final WptPt pt = new WptPt(lat, lon, time, Double.NaN, 0, Double.NaN);
pt.name = name;
pt.category = category;
pt.desc = description;
if (color != 0) {
pt.setColor(color);
}
currentTrack.getModifiableGpxFile().points.add(pt);
currentTrack.getModifiableGpxFile().modifiedTime = time;
points++;
execWithClose(insertPointsScript, new Object[] { lat, lon, time, description, name, category, color });
return pt;
}
public void updatePointData(WptPt pt, double lat, double lon, long time, String description, String name, String category, int color) {
currentTrack.getModifiableGpxFile().modifiedTime = time;
List<Object> params = new ArrayList<>();
params.add(lat);
params.add(lon);
params.add(time);
params.add(description);
params.add(name);
params.add(category);
params.add(color);
params.add(pt.getLatitude());
params.add(pt.getLongitude());
params.add(pt.time);
StringBuilder sb = new StringBuilder();
String prefix = "UPDATE " + POINT_NAME
+ " SET "
+ POINT_COL_LAT + "=?, "
+ POINT_COL_LON + "=?, "
+ POINT_COL_DATE + "=?, "
+ POINT_COL_DESCRIPTION + "=?, "
+ POINT_COL_NAME + "=?, "
+ POINT_COL_CATEGORY + "=?, "
+ POINT_COL_COLOR + "=? "
+ "WHERE "
+ POINT_COL_LAT + "=? AND "
+ POINT_COL_LON + "=? AND "
+ POINT_COL_DATE + "=?";
sb.append(prefix);
if (pt.desc != null) {
sb.append(" AND ").append(POINT_COL_DESCRIPTION).append("=?");
params.add(pt.desc);
} else {
sb.append(" AND ").append(POINT_COL_DESCRIPTION).append(" IS NULL");
}
if (pt.name != null) {
sb.append(" AND ").append(POINT_COL_NAME).append("=?");
params.add(pt.name);
} else {
sb.append(" AND ").append(POINT_COL_NAME).append(" IS NULL");
}
if (pt.category != null) {
sb.append(" AND ").append(POINT_COL_CATEGORY).append("=?");
params.add(pt.category);
} else {
sb.append(" AND ").append(POINT_COL_CATEGORY).append(" IS NULL");
}
execWithClose(sb.toString(), params.toArray());
pt.lat = lat;
pt.lon = lon;
pt.time = time;
pt.desc = description;
pt.name = name;
pt.category = category;
if (color != 0) {
pt.setColor(color);
}
}
public void deletePointData(WptPt pt) {
currentTrack.getModifiableGpxFile().points.remove(pt);
currentTrack.getModifiableGpxFile().modifiedTime = System.currentTimeMillis();
points--;
List<Object> params = new ArrayList<>();
params.add(pt.getLatitude());
params.add(pt.getLongitude());
params.add(pt.time);
StringBuilder sb = new StringBuilder();
String prefix = "DELETE FROM "
+ POINT_NAME
+ " WHERE "
+ POINT_COL_LAT + "=? AND "
+ POINT_COL_LON + "=? AND "
+ POINT_COL_DATE + "=?";
sb.append(prefix);
if (pt.desc != null) {
sb.append(" AND ").append(POINT_COL_DESCRIPTION).append("=?");
params.add(pt.desc);
} else {
sb.append(" AND ").append(POINT_COL_DESCRIPTION).append(" IS NULL");
}
if (pt.name != null) {
sb.append(" AND ").append(POINT_COL_NAME).append("=?");
params.add(pt.name);
} else {
sb.append(" AND ").append(POINT_COL_NAME).append(" IS NULL");
}
if (pt.category != null) {
sb.append(" AND ").append(POINT_COL_CATEGORY).append("=?");
params.add(pt.category);
} else {
sb.append(" AND ").append(POINT_COL_CATEGORY).append(" IS NULL");
}
execWithClose(sb.toString(), params.toArray());
}
private synchronized void execWithClose(String script, Object[] objects) {
SQLiteDatabase db = getWritableDatabase();
try {
if (db != null) {
db.execSQL(script, objects);
}
} finally {
if (db != null) {
db.close();
}
}
}
public void loadGpxFromDatabase(){
Map<String, GPXFile> files = collectRecordedData();
currentTrack.getModifiableGpxFile().tracks.clear();
for (Map.Entry<String, GPXFile> entry : files.entrySet()){
currentTrack.getModifiableGpxFile().points.addAll(entry.getValue().points);
currentTrack.getModifiableGpxFile().tracks.addAll(entry.getValue().tracks);
}
currentTrack.processPoints();
prepareCurrentTrackForRecording();
GPXTrackAnalysis analysis = currentTrack.getModifiableGpxFile().getAnalysis(System.currentTimeMillis());
distance = analysis.totalDistance;
points = analysis.wptPoints;
duration = analysis.timeSpan;
}
private void prepareCurrentTrackForRecording() {
if(currentTrack.getModifiableGpxFile().tracks.size() == 0) {
currentTrack.getModifiableGpxFile().tracks.add(new Track());
}
while(currentTrack.getPointsToDisplay().size() < currentTrack.getModifiableGpxFile().tracks.size()) {
TrkSegment trkSegment = new TrkSegment();
currentTrack.getModifiablePointsToDisplay().add(trkSegment);
}
}
public boolean getIsRecording() {
if (OsmandPlugin.getEnabledPlugin(OsmandMonitoringPlugin.class) != null) {
if (ctx.getSettings().SAVE_GLOBAL_TRACK_TO_GPX.get() ||
(ctx.getSettings().SAVE_TRACK_TO_GPX.get() && ctx.getRoutingHelper().isFollowingMode())) {
return true;
}
}
return false;
}
public float getDistance() {
return distance;
}
public long getDuration() {
return duration;
}
public int getPoints() {
return points;
}
public long getLastTimeUpdated() {
return lastTimeUpdated;
}
public GPXFile getCurrentGpx() {
return currentTrack.getGpxFile();
}
public SelectedGpxFile getCurrentTrack() {
return currentTrack;
}
}