/* * Copyright (C) 2013 jonas.oreland@gmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.runnerup.export.format; import android.annotation.TargetApi; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Build; import org.runnerup.common.util.Constants.DB; import org.runnerup.util.KXmlSerializer; import org.runnerup.workout.Sport; import java.io.IOException; import java.io.Writer; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; @TargetApi(Build.VERSION_CODES.FROYO) public class GPX { final boolean export_rest_laps = false; enum RestLapMode { EMPTY_TRKSEG, START_STOP_TRKSEG } final RestLapMode restLapMode = RestLapMode.START_STOP_TRKSEG; long mID = 0; SQLiteDatabase mDB = null; KXmlSerializer mXML = null; String notes = null; SimpleDateFormat simpleDateFormat = null; final private boolean mGarminExt; //Also Cluetrust private final boolean mAccuracyExtensions; public GPX(SQLiteDatabase mDB) { this(mDB, true, false); } public GPX(SQLiteDatabase mDB, boolean garminExt, boolean accuracyExtensions) { this.mDB = mDB; simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); this.mGarminExt = garminExt; this.mAccuracyExtensions = accuracyExtensions; } String formatTime(long time) { return simpleDateFormat.format(new Date(time)); } /** * @param activityId * @param writer * @throws IOException */ public String export(long activityId, Writer writer) throws IOException { String[] aColumns = { DB.ACTIVITY.NAME, DB.ACTIVITY.COMMENT, DB.ACTIVITY.START_TIME, DB.ACTIVITY.SPORT, DB.ACTIVITY.META_DATA }; Cursor cursor = mDB.query(DB.ACTIVITY.TABLE, aColumns, "_id = " + activityId, null, null, null, null); cursor.moveToFirst(); long startTime = cursor.getLong(2); // epoch try { mXML = new KXmlSerializer(); mXML.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); mXML.setOutput(writer); mXML.startDocument("UTF-8", true); mXML.startTag("", "gpx"); mXML.attribute("", "version", "1.1"); mXML.attribute("", "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); mXML.attribute("", "xmlns", "http://www.topografix.com/GPX/1/1"); mXML.attribute("", "xsi:schemaLocation", "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"); if (mGarminExt){ mXML.attribute("", "xmlns:gpxtpx", "http://www.garmin.com/xmlschemas/TrackPointExtension/v1"); } else { mXML.attribute("", "xmlns:gpxtpx", "http://www.cluetrust.com/XML/GPXDATA/1/0"); } String creator = "RunnerUp " + android.os.Build.MODEL; if (!cursor.isNull(4)) { String metaData = cursor.getString(4); if (metaData.contains(DB.ACTIVITY.WITH_BAROMETER)) { creator += " with barometer"; } } mXML.attribute("", "creator", creator); mXML.startTag("", "metadata"); mXML.startTag("", "time"); final String time = formatTime(startTime * 1000); mXML.text(time); mXML.endTag("", "time"); mXML.endTag("", "metadata"); mXML.startTag("", "trk"); mXML.startTag("", "name"); String sportName; if (cursor.isNull(3)) { sportName = "Running"; } else { //Resources are not available, use hardcoded strings sportName = Sport.textOf(cursor.getInt(3)); } mXML.text("RunnerUp-"+sportName+"-"+time); mXML.endTag("", "name"); if (!cursor.isNull(1)) { notes = cursor.getString(1); mXML.startTag("", "desc"); mXML.text(notes); mXML.endTag("", "desc"); } exportLaps(activityId, startTime * 1000); mXML.endTag("", "trk"); mXML.endTag("", "gpx"); mXML.flush(); mXML.endDocument(); mXML = null; cursor.close(); return time; } catch (IOException e) { cursor.close(); mXML = null; throw e; } } private void exportLaps(long activityId, long startTime) throws IOException { String[] lColumns = { DB.LAP.LAP, DB.LAP.DISTANCE, DB.LAP.TIME, DB.LAP.INTENSITY }; Cursor cLap = mDB.query(DB.LAP.TABLE, lColumns, "( " + DB.LAP.DISTANCE + " > 0 or " + DB.LAP.TIME + " > 0) and " + DB.LAP.ACTIVITY + " = " + activityId, null, null, null, null); String[] pColumns = { DB.LOCATION.LAP, DB.LOCATION.TIME, DB.LOCATION.LATITUDE, DB.LOCATION.LONGITUDE, DB.LOCATION.ALTITUDE, DB.LOCATION.TYPE, DB.LOCATION.HR, DB.LOCATION.CADENCE, DB.LOCATION.TEMPERATURE, DB.LOCATION.PRESSURE, DB.LOCATION.ACCURANCY, DB.LOCATION.BEARING, DB.LOCATION.SPEED, DB.LOCATION.SATELLITES, DB.LOCATION.GPS_ALTITUDE }; Cursor cLocation = mDB.query(DB.LOCATION.TABLE, pColumns, DB.LOCATION.ACTIVITY + " = " + activityId, null, null, null, null); boolean lok = cLap.moveToFirst(); boolean pok = cLocation.moveToFirst(); while (lok) { if (cLap.getFloat(1) != 0 && cLap.getLong(2) != 0) { long lap = cLap.getLong(0); while (pok && cLocation.getLong(0) != lap) { pok = cLocation.moveToNext(); } mXML.startTag("", "trkseg"); if (pok && cLocation.getLong(0) == lap) { float last_lat = 0; float last_longi = 0; long last_time = 0; while (pok && cLocation.getLong(0) == lap) { long time = cLocation.getLong(1); float lat = cLocation.getFloat(2); float longi = cLocation.getFloat(3); if (!(time == last_time && lat == last_lat && longi != last_longi)) { mXML.startTag("", "trkpt"); mXML.attribute("", "lon", Float.toString(longi)); mXML.attribute("", "lat", Float.toString(lat)); Float ele = null; if (mAccuracyExtensions && !cLocation.isNull(14)) { //raw elevation ele = cLocation.getFloat(14); } else if (cLocation.isNull(4)) { ele = cLocation.getFloat(4); } if (ele != null) { mXML.startTag("", "ele"); mXML.text("" + ele); mXML.endTag("", "ele"); } mXML.startTag("", "time"); mXML.text(formatTime(time)); mXML.endTag("", "time"); { //Garmin's GPX extensions for non standard data (other variants exists too, like Cluetrust) //Check app specific like Strava: https://strava.github.io/api/v3/uploads/ //Private extensions are normally not used externally boolean isHr = !cLocation.isNull(6); boolean isCad = !cLocation.isNull(7); boolean isTemp = !cLocation.isNull(8); boolean isPres = !cLocation.isNull(9) && mAccuracyExtensions; boolean isAccuracy = !cLocation.isNull(10) && mAccuracyExtensions; boolean isBearing = !cLocation.isNull(11) && mAccuracyExtensions; boolean isSpeed = !cLocation.isNull(12) && mAccuracyExtensions; boolean isSats = !cLocation.isNull(13) && mAccuracyExtensions; boolean isAny = isCad || isTemp || isPres || isAccuracy || isBearing || isSpeed || isHr || isSats; if (isAny) { mXML.startTag("", "extensions"); if (mGarminExt) { mXML.startTag("", "gpxtpx:TrackPointExtension"); } } if (isHr) { //Same ns for Garmin/Cluetrust extensions mXML.startTag("", "gpxtpx:hr"); String bpm = Integer.toString(cLocation.getInt(6)); mXML.text(bpm); mXML.endTag("", "gpxtpx:hr"); } if (isCad) { String ns; if (mGarminExt) { //Seen in some examples, not officially supported by Strava ns = "gpxtpx:cad"; } else { ns = "gpxtpx:cadence"; } mXML.startTag("", ns); String val = Float.toString(cLocation.getFloat(7)); mXML.text(val); mXML.endTag("", ns); } if (isTemp) { String ns; if (mGarminExt) { ns = "gpxtpx:atemp"; } else { ns = "gpxtpx:temp"; } mXML.startTag("", ns); String val = Float.toString(cLocation.getFloat(8)); mXML.text(val); mXML.endTag("", ns); } if (isPres) { //private extension, not recorded by default mXML.startTag("", "pressure"); String val = Float.toString(cLocation.getFloat(9)); mXML.text(val); mXML.endTag("", "pressure"); } if (isAccuracy) { mXML.startTag("", "accuracy"); String val = Float.toString(cLocation.getFloat(10)); mXML.text(val); mXML.endTag("", "accuracy"); } if (isBearing) { mXML.startTag("", "bearing"); String val = Float.toString(cLocation.getFloat(11)); mXML.text(val); mXML.endTag("", "bearing"); } if (isSpeed) { mXML.startTag("", "speed"); String val = Float.toString(cLocation.getFloat(12)); mXML.text(val); mXML.endTag("", "speed"); } if (isSats) { mXML.startTag("", "sat"); String val = Float.toString(cLocation.getFloat(13)); mXML.text(val); mXML.endTag("", "sat"); } if (isAny) { if (mGarminExt) { mXML.endTag("", "gpxtpx:TrackPointExtension"); } mXML.endTag("", "extensions"); } } mXML.endTag("", "trkpt"); last_time = time; last_lat = lat; last_longi = longi; } pok = cLocation.moveToNext(); } } mXML.endTag("", "trkseg"); } else if (export_rest_laps && (cLap.getFloat(1) != 0 || cLap.getLong(2) != 0)) { long lap = cLap.getLong(0); if (restLapMode == RestLapMode.START_STOP_TRKSEG) { if (lap > 0 && !cLap.isLast()) { Cursor cStart = mDB.query(DB.LOCATION.TABLE, pColumns, DB.LOCATION.ACTIVITY + " = " + activityId + " and " + DB.LOCATION.LAP + " = " + (lap - 1), null, null, null, "_id desc", "1"); Cursor cEnd = mDB.query(DB.LOCATION.TABLE, pColumns, DB.LOCATION.ACTIVITY + " = " + activityId + " and " + DB.LOCATION.LAP + " = " + (lap + 1), null, null, null, "_id asc", "1"); if (cStart.moveToFirst() && cEnd.moveToFirst()) { mXML.startTag("", "trkseg"); long time_0 = cStart.getLong(1); float lat_0 = cStart.getFloat(2); float longi_0 = cStart.getFloat(3); long time_1 = cEnd.getLong(1); float lat_1 = cEnd.getFloat(2); float longi_1 = cEnd.getFloat(3); mXML.startTag("", "trkpt"); mXML.attribute("", "lon", Float.toString(longi_0)); mXML.attribute("", "lat", Float.toString(lat_0)); if (!cStart.isNull(4)) { mXML.startTag("", "ele"); mXML.text("" + cStart.getLong(4)); mXML.endTag("", "ele"); } mXML.startTag("", "time"); mXML.text(formatTime(time_0)); mXML.endTag("", "time"); mXML.endTag("", "trkpt"); mXML.startTag("", "trkpt"); mXML.attribute("", "lon", Float.toString(longi_1)); mXML.attribute("", "lat", Float.toString(lat_1)); if (!cEnd.isNull(4)) { mXML.startTag("", "ele"); mXML.text("" + cEnd.getLong(4)); mXML.endTag("", "ele"); } mXML.startTag("", "time"); mXML.text(formatTime(time_1)); mXML.endTag("", "time"); mXML.endTag("", "trkpt"); mXML.endTag("", "trkseg"); } cStart.close(); cEnd.close(); } } else if (restLapMode == RestLapMode.EMPTY_TRKSEG) { mXML.startTag("", "trkseg"); mXML.endTag("", "trkseg"); } } lok = cLap.moveToNext(); } cLap.close(); cLocation.close(); } public String getNotes() { return notes; } }