Import/Export speed in GPX and KML files. (#306)

Import/Export speed in GPX and KML files. Fixes #300.
This commit is contained in:
Román Martínez
2020-06-30 14:39:44 +02:00
committed by GitHub
parent c9869bab0e
commit 23c3a4d651
8 changed files with 95 additions and 53 deletions
@@ -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);
@@ -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>");
}
}
@@ -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.");
}
}
}
+1
View File
@@ -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>