forked from upstream-mirrors/OpenTracks
Import/Export speed in GPX and KML files. (#306)
Import/Export speed in GPX and KML files. Fixes #300.
This commit is contained in:
@@ -65,6 +65,7 @@ public class TestDataUtil {
|
||||
trackPoint.setAccuracy((float) i / 100.0f);
|
||||
trackPoint.setAltitude(i * ALTITUDE_INTERVAL);
|
||||
trackPoint.setTime(i + 1);
|
||||
trackPoint.setSpeed(5f + (i / 10));
|
||||
|
||||
trackPoint.setHeartRate_bpm(100f + i);
|
||||
trackPoint.setCyclingCadence_rpm(200f + i);
|
||||
|
||||
+5
-9
@@ -36,7 +36,6 @@ import static org.junit.Assert.assertNotNull;
|
||||
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
|
||||
* <p>
|
||||
* TODO: test ignores {@link TrackStatistics} for now.
|
||||
* TODO: enable verify speed.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class ExportImportTest {
|
||||
@@ -130,7 +129,7 @@ public class ExportImportTest {
|
||||
assertWaypoints();
|
||||
|
||||
// 3. trackpoints
|
||||
assertTrackpoints(false, false, false, false);
|
||||
assertTrackpoints(false, false, false);
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@@ -165,7 +164,7 @@ public class ExportImportTest {
|
||||
assertWaypoints();
|
||||
|
||||
// 3. trackpoints
|
||||
assertTrackpoints(false, true, true, true);
|
||||
assertTrackpoints(true, true, true);
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@@ -230,8 +229,7 @@ public class ExportImportTest {
|
||||
assertWaypoints();
|
||||
|
||||
// 3. trackpoints
|
||||
//TODO Verify speed
|
||||
assertTrackpoints(false, false, false, false);
|
||||
assertTrackpoints(false, false, false);
|
||||
}
|
||||
|
||||
private void assertWaypoints() {
|
||||
@@ -253,7 +251,7 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackpoints(boolean verifySpeed, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence) {
|
||||
private void assertTrackpoints(boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence) {
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
assertEquals(trackPoints.size(), importedTrackPoints.size());
|
||||
|
||||
@@ -269,9 +267,7 @@ public class ExportImportTest {
|
||||
assertEquals(trackPoint.getLatitude(), importedTrackPoint.getLatitude(), 0.001);
|
||||
assertEquals(trackPoint.getLongitude(), importedTrackPoint.getLongitude(), 0.001);
|
||||
assertEquals(trackPoint.getAltitude(), importedTrackPoint.getAltitude(), 0.001);
|
||||
if (verifySpeed) {
|
||||
assertEquals(trackPoint.getSpeed(), importedTrackPoint.getSpeed(), 0.01);
|
||||
}
|
||||
assertEquals(trackPoint.getSpeed(), importedTrackPoint.getSpeed(), 0.001);
|
||||
if (verifyHeartrate) {
|
||||
assertEquals(trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
|
||||
}
|
||||
|
||||
@@ -176,6 +176,7 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
}
|
||||
printWriter.println(
|
||||
"<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
||||
printWriter.println("<speed>" + trackPoint.getSpeed() + "</speed>");
|
||||
printWriter.println("</trkpt>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,10 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
private static final String TRACK_STYLE = "track";
|
||||
private static final String SCHEMA_ID = "schema";
|
||||
|
||||
public static final String SENSOR_TYPE_CADENCE = "cadence";
|
||||
public static final String SENSOR_TYPE_HEART_RATE = "heart_rate";
|
||||
public static final String SENSOR_TYPE_POWER = "power";
|
||||
public static final String EXTENDED_DATA_TYPE_SPEED = "speed";
|
||||
public static final String EXTENDED_DATA_TYPE_CADENCE = "cadence";
|
||||
public static final String EXTENDED_DATA_TYPE_HEART_RATE = "heart_rate";
|
||||
public static final String EXTENDED_DATA_TYPE_POWER = "power";
|
||||
|
||||
private static final String WAYPOINT_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
|
||||
private static final String START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png";
|
||||
@@ -64,6 +65,7 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private PrintWriter printWriter;
|
||||
private final List<Float> speedList = new ArrayList<>();
|
||||
private final List<Float> powerList = new ArrayList<>();
|
||||
private final List<Float> cadenceList = new ArrayList<>();
|
||||
private final List<Float> heartRateList = new ArrayList<>();
|
||||
@@ -123,10 +125,12 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
writePlacemarkerStyle(WAYPOINT_STYLE, WAYPOINT_ICON, 20, 2);
|
||||
printWriter.println("<Schema id=\"" + SCHEMA_ID + "\">");
|
||||
|
||||
writeSimpleArrayStyle(EXTENDED_DATA_TYPE_SPEED, context.getString(R.string.description_speed_ms));
|
||||
|
||||
if (exportSensorData) {
|
||||
writeSensorStyle(SENSOR_TYPE_POWER, context.getString(R.string.description_sensor_power));
|
||||
writeSensorStyle(SENSOR_TYPE_CADENCE, context.getString(R.string.description_sensor_cadence));
|
||||
writeSensorStyle(SENSOR_TYPE_HEART_RATE, context.getString(R.string.description_sensor_heart_rate));
|
||||
writeSimpleArrayStyle(EXTENDED_DATA_TYPE_POWER, context.getString(R.string.description_sensor_power));
|
||||
writeSimpleArrayStyle(EXTENDED_DATA_TYPE_CADENCE, context.getString(R.string.description_sensor_cadence));
|
||||
writeSimpleArrayStyle(EXTENDED_DATA_TYPE_HEART_RATE, context.getString(R.string.description_sensor_heart_rate));
|
||||
}
|
||||
printWriter.println("</Schema>");
|
||||
}
|
||||
@@ -229,6 +233,7 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
public void writeOpenSegment() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<gx:Track>");
|
||||
speedList.clear();
|
||||
powerList.clear();
|
||||
cadenceList.clear();
|
||||
heartRateList.clear();
|
||||
@@ -240,15 +245,18 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<ExtendedData>");
|
||||
printWriter.println("<SchemaData schemaUrl=\"#" + SCHEMA_ID + "\">");
|
||||
if (speedList.size() > 0) {
|
||||
writeSimpleArrayData(speedList, EXTENDED_DATA_TYPE_SPEED);
|
||||
}
|
||||
if (exportSensorData) {
|
||||
if (powerList.size() > 0) {
|
||||
writeSensorData(powerList, SENSOR_TYPE_POWER);
|
||||
writeSimpleArrayData(powerList, EXTENDED_DATA_TYPE_POWER);
|
||||
}
|
||||
if (cadenceList.size() > 0) {
|
||||
writeSensorData(cadenceList, SENSOR_TYPE_CADENCE);
|
||||
writeSimpleArrayData(cadenceList, EXTENDED_DATA_TYPE_CADENCE);
|
||||
}
|
||||
if (heartRateList.size() > 0) {
|
||||
writeSensorData(heartRateList, SENSOR_TYPE_HEART_RATE);
|
||||
writeSimpleArrayData(heartRateList, EXTENDED_DATA_TYPE_HEART_RATE);
|
||||
}
|
||||
}
|
||||
printWriter.println("</SchemaData>");
|
||||
@@ -266,6 +274,10 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
|
||||
printWriter.println("<gx:coord>" + getCoordinates(trackPoint.getLocation(), " ") + "</gx:coord>");
|
||||
|
||||
if (trackPoint.hasSpeed()) {
|
||||
speedList.add(trackPoint.getSpeed());
|
||||
}
|
||||
|
||||
if (exportSensorData) {
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
heartRateList.add(trackPoint.getHeartRate_bpm());
|
||||
@@ -281,12 +293,12 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the sensor data.
|
||||
* Writes the simple array data.
|
||||
*
|
||||
* @param list a list of sensor data
|
||||
* @param name the name of the sensor data
|
||||
* @param list a list of simple array data
|
||||
* @param name the name of the simple array data
|
||||
*/
|
||||
private void writeSensorData(List<Float> list, String name) {
|
||||
private void writeSimpleArrayData(List<Float> list, String name) {
|
||||
printWriter.println("<gx:SimpleArrayData name=\"" + name + "\">");
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
printWriter.println("<gx:value>" + list.get(i) + "</gx:value>");
|
||||
@@ -446,14 +458,14 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a sensor style.
|
||||
* Writes a simple array style.
|
||||
*
|
||||
* @param name the name of the sesnor
|
||||
* @param sensorType the sensor display name
|
||||
* @param name the name of the simple array.
|
||||
* @param extendedDataType the extended data display name
|
||||
*/
|
||||
private void writeSensorStyle(String name, String sensorType) {
|
||||
private void writeSimpleArrayStyle(String name, String extendedDataType) {
|
||||
printWriter.println("<gx:SimpleArrayField name=\"" + name + "\" type=\"float\">");
|
||||
printWriter.println("<displayName>" + StringUtils.formatCData(sensorType) + "</displayName>");
|
||||
printWriter.println("<displayName>" + StringUtils.formatCData(extendedDataType) + "</displayName>");
|
||||
printWriter.println("</gx:SimpleArrayField>");
|
||||
}
|
||||
}
|
||||
|
||||
+29
-13
@@ -80,6 +80,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
protected String longitude;
|
||||
protected String altitude;
|
||||
protected String time;
|
||||
protected String speed;
|
||||
protected String waypointType;
|
||||
protected String photoUrl;
|
||||
|
||||
@@ -319,20 +320,22 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
|
||||
// Calculate derived attributes from the previous point
|
||||
if (trackData.lastLocationInCurrentSegment != null && trackData.lastLocationInCurrentSegment.getTime() != 0) {
|
||||
long timeDifference = trackPoint.getTime() - trackData.lastLocationInCurrentSegment.getTime();
|
||||
if (!trackPoint.hasSpeed()) {
|
||||
long timeDifference = trackPoint.getTime() - trackData.lastLocationInCurrentSegment.getTime();
|
||||
|
||||
// Check for negative time change
|
||||
if (timeDifference <= 0) {
|
||||
Log.w(TAG, "Time difference not positive.");
|
||||
} else {
|
||||
// Check for negative time change
|
||||
if (timeDifference <= 0) {
|
||||
Log.w(TAG, "Time difference not positive.");
|
||||
} else {
|
||||
|
||||
/*
|
||||
* We don't have a speed and bearing in GPX, make something up from the last two points.
|
||||
* GPS points tend to have some inherent imprecision, speed and bearing will likely be off, so the statistics for things like max speed will also be off.
|
||||
*/
|
||||
double duration = timeDifference * UnitConversions.MS_TO_S;
|
||||
double speed = trackData.lastLocationInCurrentSegment.distanceTo(trackPoint) / duration;
|
||||
trackPoint.setSpeed((float) speed);
|
||||
/*
|
||||
* We don't have a speed and bearing in GPX, make something up from the last two points.
|
||||
* GPS points tend to have some inherent imprecision, speed and bearing will likely be off, so the statistics for things like max speed will also be off.
|
||||
*/
|
||||
double duration = timeDifference * UnitConversions.MS_TO_S;
|
||||
double speed = trackData.lastLocationInCurrentSegment.distanceTo(trackPoint) / duration;
|
||||
trackPoint.setSpeed((float) speed);
|
||||
}
|
||||
}
|
||||
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
|
||||
}
|
||||
@@ -427,7 +430,20 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
||||
}
|
||||
}
|
||||
return new TrackPoint(latitudeValue, longitudeValue, altitudeValue, timeValue);
|
||||
|
||||
TrackPoint trackPoint = new TrackPoint(latitudeValue, longitudeValue, altitudeValue, timeValue);
|
||||
|
||||
float speedValue;
|
||||
if (speed != null) {
|
||||
try {
|
||||
speedValue = Float.valueOf(speed);
|
||||
trackPoint.setSpeed(speedValue);
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e);
|
||||
}
|
||||
}
|
||||
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,6 +39,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
private static final String TAG_GPX = "gpx";
|
||||
private static final String TAG_NAME = "name";
|
||||
private static final String TAG_TIME = "time";
|
||||
private static final String TAG_SPEED = "speed";
|
||||
private static final String TAG_TRACK = "trk";
|
||||
private static final String TAG_TRACK_POINT = "trkpt";
|
||||
private static final String TAG_TRACK_SEGMENT = "trkseg";
|
||||
@@ -115,6 +116,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
time = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_SPEED:
|
||||
if (content != null) {
|
||||
speed = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_ELEVATION:
|
||||
if (content != null) {
|
||||
altitude = content.trim();
|
||||
@@ -149,6 +155,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
longitude = attributes.getValue(ATTRIBUTE_LON);
|
||||
altitude = null;
|
||||
time = null;
|
||||
speed = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,8 +61,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
private static final String ATTRIBUTE_NAME = "name";
|
||||
|
||||
private boolean trackStarted = false;
|
||||
private String sensorType;
|
||||
private String extendedDataType;
|
||||
private ArrayList<TrackPoint> trackPoints;
|
||||
private ArrayList<Float> speedList;
|
||||
private ArrayList<Float> cadenceList;
|
||||
private ArrayList<Float> heartRateList;
|
||||
private ArrayList<Float> powerList;
|
||||
@@ -102,7 +103,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
onTrackSegmentStart();
|
||||
break;
|
||||
case TAG_GX_SIMPLE_ARRAY_DATA:
|
||||
onSensorDataStart(attributes);
|
||||
onExtendedDataStart(attributes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -124,7 +125,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
} else if (tag.equals(TAG_GX_COORD)) {
|
||||
onTrackPointEnd();
|
||||
} else if (tag.equals(TAG_GX_VALUE)) {
|
||||
onSensorValueEnd();
|
||||
onExtendedDataValueEnd();
|
||||
} else if (tag.equals(TAG_NAME)) {
|
||||
if (content != null) {
|
||||
name = content.trim();
|
||||
@@ -209,6 +210,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
protected void onTrackSegmentStart() {
|
||||
super.onTrackSegmentStart();
|
||||
trackPoints = new ArrayList<>();
|
||||
speedList = new ArrayList<>();
|
||||
heartRateList = new ArrayList<>();
|
||||
cadenceList = new ArrayList<>();
|
||||
powerList = new ArrayList<>();
|
||||
@@ -222,6 +224,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
for (int i = 0; i < trackPoints.size(); i++) {
|
||||
TrackPoint trackPoint = trackPoints.get(i);
|
||||
|
||||
if (i < speedList.size()) {
|
||||
trackPoint.setSpeed(speedList.get(i));
|
||||
}
|
||||
if (i < heartRateList.size()) {
|
||||
trackPoint.setHeartRate_bpm(heartRateList.get(i));
|
||||
}
|
||||
@@ -261,16 +266,16 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
}
|
||||
|
||||
/**
|
||||
* On sensor data start. gx:SimpleArrayData start tag.
|
||||
* On extended data start. gx:SimpleArrayData start tag.
|
||||
*/
|
||||
private void onSensorDataStart(Attributes attributes) {
|
||||
sensorType = attributes.getValue(ATTRIBUTE_NAME);
|
||||
private void onExtendedDataStart(Attributes attributes) {
|
||||
extendedDataType = attributes.getValue(ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* On sensor value end. gx:value end tag.
|
||||
* On extended data value end. gx:value end tag.
|
||||
*/
|
||||
private void onSensorValueEnd() throws SAXException {
|
||||
private void onExtendedDataValueEnd() throws SAXException {
|
||||
if (content == null) {
|
||||
return;
|
||||
}
|
||||
@@ -284,18 +289,21 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e);
|
||||
}
|
||||
switch (sensorType) {
|
||||
case KmlTrackWriter.SENSOR_TYPE_POWER:
|
||||
switch (extendedDataType) {
|
||||
case KmlTrackWriter.EXTENDED_DATA_TYPE_SPEED:
|
||||
speedList.add(value);
|
||||
break;
|
||||
case KmlTrackWriter.EXTENDED_DATA_TYPE_POWER:
|
||||
powerList.add(value);
|
||||
break;
|
||||
case KmlTrackWriter.SENSOR_TYPE_HEART_RATE:
|
||||
case KmlTrackWriter.EXTENDED_DATA_TYPE_HEART_RATE:
|
||||
heartRateList.add(value);
|
||||
break;
|
||||
case KmlTrackWriter.SENSOR_TYPE_CADENCE:
|
||||
case KmlTrackWriter.EXTENDED_DATA_TYPE_CADENCE:
|
||||
cadenceList.add(value);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Data from sensor " + sensorType + " is not (yet) supported.");
|
||||
Log.w(TAG, "Data from extended data " + extendedDataType + " is not (yet) supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +193,7 @@ limitations under the License.
|
||||
<string name="description_pace_imperial">Pace (min/mi)</string>
|
||||
<string name="description_pace_metric">Pace (min/km)</string>
|
||||
<string name="description_recorded_time">Recorded: %1$s</string>
|
||||
<string name="description_speed_ms">Speed (m/s)</string>
|
||||
<string name="description_sensor_cadence">Cadence (rpm)</string>
|
||||
<string name="description_sensor_heart_rate">Heart rate (bpm)</string>
|
||||
<string name="description_sensor_power">Power (W)</string>
|
||||
|
||||
Reference in New Issue
Block a user