Compare commits

..

1 Commits

Author SHA1 Message Date
Dennis Guse 53f9432d45 WIP 2025-06-30 07:21:39 +02:00
14 changed files with 206 additions and 132 deletions
+4 -12
View File
@@ -100,8 +100,8 @@ __Only required permissions:__
### Public API
OpenTracks includes an API for starting/stopping recording by another installed application (e.g., [Automate](https://llamalab.com/automate/), [Tasker](https://tasker.joaoapps.com), or [Easer](https://github.com/renyuneyun/Easer)).
The API is disabled by default to protect the user's privacy, but it can easily be enabled in the settings. Once enabled, the API can be invoked by sending an explicit Intent to start an activity.
OpenTracks includes an API for starting/stopping recording by another installed application (e.g., [Automate](https://llamalab.com/automate/), [Tasker](https://tasker.joaoapps.com), or [Easer](https://github.com/renyuneyun/Easer)).
The API can be invoked by sending an explicit Intent to start an activity.
`Package` (depends on the variant installed):
* F-Droid: `de.dennisguse.opentracks`
@@ -121,17 +121,9 @@ The API is disabled by default to protect the user's privacy, but it can easily
* **Stop a recording:** `de.dennisguse.opentracks.publicapi.StopRecording`
* **Create a marker:** `de.dennisguse.opentracks.publicapi.CreateMarker`
For testing the API using adb, the general command syntax is:
For testing via adb: `adb shell am start -e someParameter someValue -n "package/class"`
```shell
adb shell am start -e someParameter someValue -n "package/class"
```
Depending on the package and class, a complete command could look something like this:
```shell
adb shell am start -n "de.dennisguse.opentracks.playstore/de.dennisguse.opentracks.publicapi.StartRecording"
```
The Public API is disabled by default to protect the user's privacy, but it can easily be enabled in the settings.
## File formats compatibility with open-source software
| | [GPX 1.1](https://www.topografix.com/GPX/1/1/) | [KML 2.3](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html) | [KMZ 2.3](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html) |
+5 -5
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.10.0'
classpath 'com.android.tools.build:gradle:8.9.1'
}
}
@@ -46,8 +46,8 @@ android {
defaultConfig {
applicationId 'de.dennisguse.opentracks'
versionCode 6130
versionName "v4.19.3"
versionCode 6102
versionName "v4.19.0"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -144,7 +144,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.preference:preference:1.2.1'
implementation 'androidx.documentfile:documentfile:1.1.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.gridlayout:gridlayout:1.1.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
@@ -158,7 +158,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.6.1'
androidTestImplementation 'androidx.test:runner:1.6.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
androidTestImplementation 'org.mockito:mockito-android:5.18.0'
androidTestImplementation 'org.mockito:mockito-android:5.17.0'
androidTestUtil 'androidx.test:orchestrator:1.5.1'
}
@@ -1,4 +0,0 @@
v4.19.1: OpenTracks
Bugfix:
- Exported GPX files missed one schemaLocation
@@ -1,5 +0,0 @@
v4.19.2: OpenTracks
Bugfix:
- Fix crash due to faulty translations
@@ -1,5 +0,0 @@
v4.19.3: OpenTracks
Bugfix:
- Fix CSV export for altitude (was cut at 1000m)
@@ -0,0 +1,44 @@
package de.dennisguse.opentracks.io.file.exporter;
import org.junit.Test;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class FITv2TrackExporterTest {
private final byte FIT_PROTOCOL_VERSION = 2 << 4 + 0;
private final short FIT_PROFILE_VERSION = 21 * 1000 + 158;
private long checksum;
private final byte FILE_HEADER_SIZE = 14;
@Test
public void run() {
ByteBuffer littleEndian = ByteBuffer
.allocate(FILE_HEADER_SIZE)
.order(ByteOrder.LITTLE_ENDIAN);
littleEndian.put(0, FILE_HEADER_SIZE);
littleEndian.put(1, FIT_PROTOCOL_VERSION);
// Profile version: 1513
littleEndian.putShort(2, FIT_PROFILE_VERSION);
// Data size: 32bit
littleEndian.putInt(4, 0); // We do not calculate the data size.
// .FIT
littleEndian.putChar(8, '.');
littleEndian.putChar(9, 'F');
littleEndian.putChar(10, 'I');
littleEndian.putChar(11, 'T');
// CRC (not implemented)
littleEndian.put(12, (byte) 0);
littleEndian.put(13, (byte) 0);
var a = littleEndian.array();
littleEndian.array();
}
}
@@ -18,7 +18,6 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
@@ -46,7 +45,6 @@ import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TestUtil;
import de.dennisguse.opentracks.TimezoneRule;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils;
@@ -89,9 +87,6 @@ public class ExportImportTest {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
//For csv_export_only() as we the timezone is hardcoded in the expectation.
@Rule
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
@@ -149,7 +144,7 @@ public class ExportImportTest {
Distance sensorDistance = Distance.of(10); // recording distance interval
sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3.1234567, 14.0014567, 10, 13, 15, 1020.25, 1f);
sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3, 14, 10, 13, 15, 10, 1f);
contentProviderUtils.insertMarker(new Marker(trackId, service.getLastStoredTrackPointWithLocation(), "Marker 1", "Marker 1 desc", "Marker 1 category", null, null));
// A sensor-only TrackPoint
@@ -160,10 +155,11 @@ public class ExportImportTest {
mockSensorData(trackPointCreator, 15f, null, 67f, 3f, 50f, null);
trackPointCreator.setClock("2020-02-02T02:02:15Z");
mockSensorData(trackPointCreator, null, null, 68f, 3f, 50f, null);
trackPointCreator.setClock("2020-02-02T02:02:16Z");
mockSensorData(trackPointCreator, 5f, Distance.of(2), 69f, 3f, 50f, null); // Distance will be added to next TrackPoint
sendLocation(trackPointCreator, "2020-02-02T02:02:17Z", 3.1234567, 14.0014567, 10, 13, 15, 1020.25, 0f);
sendLocation(trackPointCreator, "2020-02-02T02:02:17Z", 3, 14.001, 10, 13, 15, 10, 0f);
contentProviderUtils.insertMarker(new Marker(trackId, service.getLastStoredTrackPointWithLocation(), "Marker 2", "Marker 2 desc", "Marker 2 category", null, null));
trackPointCreator.setClock("2020-02-02T02:02:18Z");
@@ -173,14 +169,14 @@ public class ExportImportTest {
trackPointCreator.setClock("2020-02-02T02:03:20Z");
service.resumeTrack(trackId);
sendLocation(trackPointCreator, "2020-02-02T02:03:21Z", 3.1234567, 14.0024567, 10, 13, 15, 999.123, 0f);
sendLocation(trackPointCreator, "2020-02-02T02:03:21Z", 3, 14.002, 10, 13, 15, 10, 0f);
sendLocation(trackPointCreator, "2020-02-02T02:03:22Z", 3.1234567, 16, 10, 13, 15, 999.123, 0f);
sendLocation(trackPointCreator, "2020-02-02T02:03:22Z", 3, 16, 10, 13, 15, 10, 0f);
trackPointCreator.setClock("2020-02-02T02:03:30Z");
service.getTrackRecordingManager().onIdle();
sendLocation(trackPointCreator, "2020-02-02T02:03:50Z", 3.1234567, 16.001, 10, 27, 15, 999.123, 0f);
sendLocation(trackPointCreator, "2020-02-02T02:03:50Z", 3, 16.001, 10, 27, 15, 10, 0f);
trackPointCreator.getSensorManager().sensorDataSet = new SensorDataSet(trackPointCreator);
trackPointCreator.setClock("2020-02-02T02:04:00Z");
@@ -213,16 +209,16 @@ public class ExportImportTest {
assertEquals(Duration.ofSeconds(26), trackStatistics.getMovingTime()); //TODO Likely too low
// Distance
assertEquals(222049.34375, trackStatistics.getTotalDistance().toM(), 0.01); //TODO Too low
assertEquals(222125.53125, trackStatistics.getTotalDistance().toM(), 0.01); //TODO Too low
// Speed
assertEquals(8540.359, trackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(3965.166, trackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(8540.359, trackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
assertEquals(8543.29, trackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(3966.52, trackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(8543.28, trackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
// Altitude
assertEquals(999.122, trackStatistics.getMinAltitude(), 0.01);
assertEquals(1020.25, trackStatistics.getMaxAltitude(), 0.01);
assertEquals(10, trackStatistics.getMinAltitude(), 0.01);
assertEquals(10, trackStatistics.getMaxAltitude(), 0.01);
assertEquals(2, trackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(2, trackStatistics.getTotalAltitudeLoss(), 0.01);
@@ -233,8 +229,8 @@ public class ExportImportTest {
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:02:03Z"),
3.123456, 14.001456, Distance.of(10),
Altitude.WGS84.of(1020.25), null,
3d, 14d, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(15)))
.setAltitudeLoss(1f)
@@ -255,8 +251,8 @@ public class ExportImportTest {
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:02:17Z"),
3.123456, 14.001456, Distance.of(10),
Altitude.WGS84.of(1020.25), null,
3d, 14.001, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(5)))
.setSensorDistance(Distance.of(2))
@@ -270,16 +266,16 @@ public class ExportImportTest {
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:03:21Z"),
3.123456, 14.002456, Distance.of(10),
Altitude.WGS84.of(999.1229858398438), null,
3d, 14.002d, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(15)))
.setAltitudeLoss(0f)
.setAltitudeGain(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position(Instant.parse("2020-02-02T02:03:22Z"),
3.123456, 16d, Distance.of(10),
Altitude.WGS84.of(999.1229858398438), null,
3d, 16d, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(15)))
.setAltitudeLoss(0f)
@@ -290,8 +286,8 @@ public class ExportImportTest {
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:03:50Z"),
3.123456, 16.001, Distance.of(10),
Altitude.WGS84.of(999.1229858398438), null,
3d, 16.001, Distance.of(10),
Altitude.WGS84.of(10), null,
null, Speed.of(15)))
.setAltitudeLoss(0f)
.setAltitudeGain(0f),
@@ -338,26 +334,7 @@ public class ExportImportTest {
// Time
assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset());
assertEquals(Instant.parse("2020-02-02T02:02:02Z"), importedTrackStatistics.getStartTime());
assertEquals(Instant.parse("2020-02-02T02:04:00Z"), importedTrackStatistics.getStopTime());
assertEquals(Duration.ofSeconds(56), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(26), importedTrackStatistics.getMovingTime());
// Distance
assertEquals(222049.421, importedTrackStatistics.getTotalDistance().toM(), 0.01);
// Speed
assertEquals(8540.362, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(3965.168, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(8540.362, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
// Altitude
assertEquals(999.122, importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(1020.25, importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
assertEquals(track.getTrackStatistics(), importedTrackStatistics);
// 4. markers
assertMarkers();
@@ -429,8 +406,8 @@ public class ExportImportTest {
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position(
Instant.parse("2020-02-02T02:02:03Z"),
3.123456, 14.001456d, Distance.of(10),
Altitude.WGS84.of(1020.2), null,
3d, 14d, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(15)))
.setAltitudeLoss(1f)
@@ -438,8 +415,8 @@ public class ExportImportTest {
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:02:17Z"),
3.123456, 14.001456, Distance.of(10),
Altitude.WGS84.of(1020.2), null,
3d, 14.001, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(5)))
.setAltitudeLoss(1f)
@@ -451,8 +428,8 @@ public class ExportImportTest {
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position(
Instant.parse("2020-02-02T02:03:21Z"),
3.123456, 14.002456, Distance.of(10),
Altitude.WGS84.of(999.0999755859375), null,
3d, 14.002, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(15)))
.setAltitudeLoss(0f)
@@ -460,8 +437,8 @@ public class ExportImportTest {
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position(
Instant.parse("2020-02-02T02:03:22Z"),
3.123456, 16d, Distance.of(10),
Altitude.WGS84.of(999.0999755859375), null,
3d, 16d, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(15)))
.setAltitudeLoss(0f)
@@ -469,8 +446,8 @@ public class ExportImportTest {
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:03:50Z"),
3.123456, 16.001, Distance.of(10),
Altitude.WGS84.of(999.0999755859375), null,
3d, 16.001, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(10)))
.setAltitudeLoss(0f)
@@ -490,16 +467,16 @@ public class ExportImportTest {
assertEquals(Duration.ofSeconds(107), importedTrackStatistics.getMovingTime());
// Distance
assertEquals(222271.734, importedTrackStatistics.getTotalDistance().toM(), 0.01);
assertEquals(222347.85, importedTrackStatistics.getTotalDistance().toM(), 0.01);
// Speed
assertEquals(2077.305, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(2077.305, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(2077.305, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
assertEquals(2078.01, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(2078.01, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(2078.01, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
// Altitude
assertEquals(999.099, importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(1020.2, importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(10, importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
@@ -637,7 +614,7 @@ public class ExportImportTest {
}
}
private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, double altitude, Float altitudeGain) {
private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, float altitude, Float altitudeGain) {
Location location = new Location("mock");
location.setLatitude(latitude);
location.setLongitude(longitude);
+5 -5
View File
@@ -1,13 +1,13 @@
#time,trackpoint_type,latitude,longitude,altitude,accuracy_horizontal,accuracy_vertical,speed,altitude_gain,altitude_loss,sensor_distance,heartrate,cadence,power
"2020-02-02T03:02:02+01:00","SEGMENT_START_MANUAL",,,,,,,,,,,,
"2020-02-02T03:02:03+01:00","TRACKPOINT",3.123456,14.001456,1020.2,10,,54,1,1,,,,
"2020-02-02T03:02:03+01:00","TRACKPOINT",3,14,10,10,,54,1,1,,,,
"2020-02-02T03:02:04+01:00","TRACKPOINT",,,,,,54,1,1,10,66,3,50
"2020-02-02T03:02:15+01:00","TRACKPOINT",,,,,,,,,,68,3,50
"2020-02-02T03:02:17+01:00","TRACKPOINT",3.123456,14.001456,1020.2,10,,18,0,0,2,69,3,50
"2020-02-02T03:02:17+01:00","TRACKPOINT",3,14.001,10,10,,18,0,0,2,69,3,50
"2020-02-02T03:02:18+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,,
"2020-02-02T03:03:20+01:00","SEGMENT_START_MANUAL",,,,,,,,,,,,
"2020-02-02T03:03:21+01:00","TRACKPOINT",3.123456,14.002456,999.1,10,,54,0,0,,,,
"2020-02-02T03:03:22+01:00","SEGMENT_START_AUTOMATIC",3.123456,16,999.1,10,,54,0,0,,,,
"2020-02-02T03:03:21+01:00","TRACKPOINT",3,14.002,10,10,,54,0,0,,,,
"2020-02-02T03:03:22+01:00","SEGMENT_START_AUTOMATIC",3,16,10,10,,54,0,0,,,,
"2020-02-02T03:03:30+01:00","IDLE",,,,,,,0,0,,,,
"2020-02-02T03:03:50+01:00","TRACKPOINT",3.123456,16.001,999.1,10,,54,0,0,,,,
"2020-02-02T03:03:50+01:00","TRACKPOINT",3,16.001,10,10,,54,0,0,,,,
"2020-02-02T03:04:00+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,,
1 #time trackpoint_type latitude longitude altitude accuracy_horizontal accuracy_vertical speed altitude_gain altitude_loss sensor_distance heartrate cadence power
2 2020-02-02T03:02:02+01:00 SEGMENT_START_MANUAL
3 2020-02-02T03:02:03+01:00 TRACKPOINT 3.123456 3 14.001456 14 1020.2 10 10 54 1 1
4 2020-02-02T03:02:04+01:00 TRACKPOINT 54 1 1 10 66 3 50
5 2020-02-02T03:02:15+01:00 TRACKPOINT 68 3 50
6 2020-02-02T03:02:17+01:00 TRACKPOINT 3.123456 3 14.001456 14.001 1020.2 10 10 18 0 0 2 69 3 50
7 2020-02-02T03:02:18+01:00 SEGMENT_END_MANUAL
8 2020-02-02T03:03:20+01:00 SEGMENT_START_MANUAL
9 2020-02-02T03:03:21+01:00 TRACKPOINT 3.123456 3 14.002456 14.002 999.1 10 10 54 0 0
10 2020-02-02T03:03:22+01:00 SEGMENT_START_AUTOMATIC 3.123456 3 16 999.1 10 10 54 0 0
11 2020-02-02T03:03:30+01:00 IDLE 0 0
12 2020-02-02T03:03:50+01:00 TRACKPOINT 3.123456 3 16.001 999.1 10 10 54 0 0
13 2020-02-02T03:04:00+01:00 SEGMENT_END_MANUAL
@@ -60,6 +60,7 @@ public class CSVTrackExporter implements TrackExporter {
ALTITUDE_FORMAT.setGroupingUsed(false);
COORDINATE_FORMAT.setMaximumFractionDigits(6);
COORDINATE_FORMAT.setMaximumIntegerDigits(3);
COORDINATE_FORMAT.setGroupingUsed(false);
SPEED_FORMAT.setMaximumFractionDigits(2);
@@ -93,17 +94,17 @@ public class CSVTrackExporter implements TrackExporter {
new Column("trackpoint_type", t -> quote(t.getType().name())),
new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLatitude()) : ""),
new Column("longitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLongitude()) : ""),
new Column("altitude", t -> t.hasAltitude() ? ALTITUDE_FORMAT.format(t.getAltitude().toM()) : ""),
new Column("altitude", t -> t.hasAltitude() ? COORDINATE_FORMAT.format(t.getAltitude().toM()) : ""),
new Column("accuracy_horizontal", t -> t.hasHorizontalAccuracy() ? DISTANCE_FORMAT.format(t.getHorizontalAccuracy().toM()) : ""),
new Column("accuracy_vertical", t -> t.hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.getVerticalAccuracy().toM()) : ""),
new Column("speed", t -> t.hasSpeed() ? SPEED_FORMAT.format(t.getSpeed().toKMH()) : ""),
new Column("altitude_gain", t -> t.hasAltitudeGain() ? ALTITUDE_FORMAT.format(t.getAltitudeGain()) : ""),
new Column("altitude_loss", t -> t.hasAltitudeLoss() ? ALTITUDE_FORMAT.format(t.getAltitudeLoss()) : ""),
new Column("altitude_gain", t -> t.hasAltitudeGain() ? DISTANCE_FORMAT.format(t.getAltitudeGain()) : ""),
new Column("altitude_loss", t -> t.hasAltitudeLoss() ? DISTANCE_FORMAT.format(t.getAltitudeLoss()) : ""),
new Column("sensor_distance", t -> t.hasSensorDistance() ? DISTANCE_FORMAT.format(t.getSensorDistance().toM()) : ""),
new Column("heartrate", t -> t.hasHeartRate() ? HEARTRATE_FORMAT.format(t.getHeartRate().getBPM()) : ""),
new Column("cadence", t -> t.hasCadence() ? CADENCE_FORMAT.format(t.getCadence().getRPM()) : ""),
new Column("power", t -> t.hasPower() ? POWER_FORMAT.format(t.getPower().getW()) : ""));
new Column("power", t -> t.hasPower() ? ALTITUDE_FORMAT.format(t.getPower().getW()) : ""));
try {
prepare(outputStream);
@@ -135,7 +136,7 @@ public class CSVTrackExporter implements TrackExporter {
while (trackPointIterator.hasNext()) {
if (Thread.interrupted()) throw new InterruptedException();
writeTrackPoint(columns, trackPointIterator.next());
writeTrackPoint(columns, trackPointIterator.next());
}
}
}
@@ -0,0 +1,74 @@
package de.dennisguse.opentracks.io.file.exporter;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.util.List;
import de.dennisguse.opentracks.data.models.Track;
/**
* FIT v2.0
* https://developer.garmin.com/fit/
* <p>
* Encoding is little endian.
*/
public class FITv2TrackExporter implements TrackExporter {
private final byte FIT_PROTOCOL_VERSION = 2 << 4 + 0;
private final short FIT_PROFILE_VERSION = 21 * 1000 + 158;
private long checksum;
private final byte FILE_HEADER_SIZE = 14;
@Override
public boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream) {
WritableByteChannel channel = Channels.newChannel(outputStream);
writeFileHeader(channel);
// writeFileId(channel);
writeFooter(channel);
return false;
}
// https://developer.garmin.com/fit/protocol/
@VisibleForTesting
void writeFileHeader(@NonNull WritableByteChannel channel) {
ByteBuffer littleEndian = ByteBuffer
.allocate(FILE_HEADER_SIZE)
.order(ByteOrder.LITTLE_ENDIAN);
littleEndian.put(0, FILE_HEADER_SIZE);
littleEndian.put(1, FIT_PROTOCOL_VERSION);
// Profile version
littleEndian.putShort(2, FIT_PROFILE_VERSION);
// Data size: 32bit
littleEndian.putInt(4, 0); // We do not calculate the data size.
// .FIT
littleEndian.putChar(8, '.');
littleEndian.putChar(9, 'F');
littleEndian.putChar(10, 'I');
littleEndian.putChar(11, 'T');
// CRC (not implemented)
littleEndian.put(12, (byte) 0);
littleEndian.put(13, (byte) 0);
var a = littleEndian.array();
littleEndian.array();
}
private void writeFooter(@NonNull WritableByteChannel channel) {
}
}
+1 -1
View File
@@ -637,7 +637,7 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="settings_announcements_unit">Distanz/Geschwindigkeits Einheit</string>
<string name="power_x_watt">Leistung {x, plural, =1 {1 Watt} other {# Watt} }</string>
<string name="power">Leistung</string>
<string name="voiceDistance">{n, plural, =1 {1 } other {{n,number,#.0}} }</string>
<string name="voiceDistance">{n, plural, =1 {1 } anderes {{n,number,#.0}} }</string>
<string name="settings_recording_min_sampling_interval_title">Abtastzeitintervall</string>
<string name="voiceIdle">Untätig werden.</string>
<string name="voice_announce_lap_power_key">StimmeAnkündigungLapPower</string>
+3 -3
View File
@@ -657,11 +657,11 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
<string name="permission_recording_failed">OpenTracks a besoin de la permission d\'accéder à votre localisation ainsi qu\'aux appareils à proximité.</string>
<string name="create_marker_error">Impossible de créer un marqueur. Veuillez réessayer.</string>
<string name="power">Puissance</string>
<string name="power_x_watt">Puissance {x, plural, =1 {1 watt} other {# watt} }</string>
<string name="power_x_watt">Puissance {x, plural, =1 {1 watt} autres {# watt} }</string>
<string name="settings_announcements_lap_power">Puissance par tour</string>
<string name="activity_type_kickscooter">trottinette</string>
<string name="voiceSpeed">{n, plural, =1 {1} other {{n,number,#.0}} }</string>
<string name="voiceDistance">{n, plural, =1 {1 } other {{n,number,#.0}} }</string>
<string name="voiceSpeed">{n, plural, =1 {1} autres {{n,number,#.0}} }</string>
<string name="voiceDistance">{n, plural, =1 {1 } autres {{n,number,#.0}} }</string>
<string name="settings_locale_system_default">Paramètres par défaut du système</string>
<string name="settings_locale_title">Langue</string>
<string name="stats_gain_loss">Gain et perte</string>
+12 -12
View File
@@ -507,11 +507,11 @@
<string name="settings_announcements_unit">Aonad faid/luas</string>
<string name="value_internal_sensor">Braiteoir inmheánach</string>
<string name="voiceIdle">Ag éirí díomhaoin.</string>
<string name="voiceSecondsPlural">{n, plural, =1 {1 soicind} other {{ n,number} soicind} }</string>
<string name="voiceSpeedKilometersPerHourPlural">{n, plural, =1 {1 chiliméadar in aghaidh na huaire} other {{ n,number, #.0} ciliméadar san uair} }</string>
<string name="voiceSpeedMKnotsPlural">{n, plural, =1 {1 snaidhm} other {{ n,number, #.0} snaidhm} }</string>
<string name="voiceDistanceNauticalMilesPlural">{n, plural, =1 {1 mhuirmhíle} other {{ n,number, #.0} muirmhíle} }</string>
<string name="voiceDistance">{n, plural, =1 {1 } other {{ n,number, #.0}} }</string>
<string name="voiceSecondsPlural">{n, iolra, =1 {1 soicind} eile {{ n,uimhir} soicind} }</string>
<string name="voiceSpeedKilometersPerHourPlural">{n, iolra, =1 {1 chiliméadar in aghaidh na huaire} eile {{ n,uimhir, #.0} ciliméadar san uair} }</string>
<string name="voiceSpeedMKnotsPlural">{n, iolra, =1 {1 snaidhm} eile {{ n,uimhir, #.0} snaidhm} }</string>
<string name="voiceDistanceNauticalMilesPlural">{n, iolra, =1 {1 mhuirmhíle} eile {{ n,uimhir, #.0} muirmhíle} }</string>
<string name="voiceDistance">{n, iolra, =1 {1 } eile {{ n,uimhir, #.0}} }</string>
<string name="settings_recording_min_sampling_interval_title">Eatramh ama samplála</string>
<string name="sensor_state_cadence_unit">rpm</string>
<string name="sensor_state_heart_rate_unit">bpm</string>
@@ -527,20 +527,20 @@
<string name="settings_api_summary">Cianrialú agus staitisticí API le haghaidh aipeanna eile</string>
<string name="create_marker_error">Ní féidir marcóir a chruthú. Déan iarracht arís.</string>
<string name="sensor_state_power_unit">W</string>
<string name="voiceMinutesPlural">{n, plural, =1 {1 nóiméad} other {{ n,number} nóiméad} }</string>
<string name="voiceDistanceKilometersPlural">{n, plural, =1 {1 chiliméadar} other {{ n,number, #.0} ciliméadar} }</string>
<string name="voiceMinutesPlural">{n, iolra, =1 {1 nóiméad} eile {{ n,uimhir} nóiméad} }</string>
<string name="voiceDistanceKilometersPlural">{n, iolra, =1 {1 chiliméadar} eile {{ n,uimhir, #.0} ciliméadar} }</string>
<string name="sensor_state_power_max">Cumhacht Max</string>
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
<string name="voiceHoursPlural">{n, plural, =1 {1 uair} other {{ n,number} uair an chloig} }</string>
<string name="voiceDistanceMilesPlural">{n, plural, =1 {1 mhíle} other {{ n,number, #.0} míle} }</string>
<string name="voiceHoursPlural">{n, iolra, =1 {1 uair} eile {{ n,uimhir} uair an chloig} }</string>
<string name="voiceDistanceMilesPlural">{n, iolra, =1 {1 mhíle} eile {{ n,uimhir, #.0} míle} }</string>
<string name="no_compatible_camera_installed">Níl feidhmchlár ceamara comhoiriúnach suiteáilte</string>
<string name="voiceSpeedMilesPerHourPlural">{n, plural, =1 {1 mhíle san uair} other {{ n,number, #.0} míle san uair} }</string>
<string name="voiceSpeed">{n, plural, =1 {1} other {{ n,number, #.0}} }</string>
<string name="voiceSpeedMilesPerHourPlural">{n, iolra, =1 {1 mhíle san uair} eile {{ n,uimhir, #.0} míle san uair} }</string>
<string name="voiceSpeed">{n, iolra, =1 {1} eile {{ n,uimhir, #.0}} }</string>
<string name="voice_on_device_speaker_title">Úsáid cainteoir an ghléis</string>
<string name="no_compatible_gallery_installed">Níl feidhmchlár gailearaí íomhá comhoiriúnach suiteáilte</string>
<string name="settings_recording_location_distance_summary">%1$s idir suíomhanna taifeadta</string>
<string name="settings_announcements_lap_power">Cumhacht glúine</string>
<string name="power_x_watt">Cumhacht {x, plural, = 1 {1 vata} other {# vata} }</string>
<string name="power_x_watt">Cumhacht {x, iolra, = 1 {1 vata} eile {# vata} }</string>
<string name="empty">" "</string>
<string name="settings_locale_system_default">Réamhshocrú córais</string>
<string name="settings_locale_title">Teanga</string>
+12 -12
View File
@@ -65,7 +65,7 @@
<string name="settings_recording_location_distance_summary">பதிவுசெய்யப்பட்ட இடங்களுக்கு இடையில் %1$s</string>
<string name="voice_announce_lap_power_key">VoiceAnnouncelappower</string>
<string name="settings_announcements_lap_power">மடியில் ஆற்றல்</string>
<string name="power_x_watt">ஆற்றல் {x, plural, = 1 {1 watt} other {# வாட்ச்}}</string>
<string name="power_x_watt">ஆற்றல் {x, பன்மை, = 1 {1 watt} பிற {# வாட்ச்}}</string>
<string name="power">விசை</string>
<string name="help_feature_content">* சி.பி.எச் தடங்களை உருவாக்கவும்\n * இதய துடிப்பு சென்சார் பயன்படுத்தவும் (புளூடூத் லு மட்டும்)\n * குரல் அறிவிப்புகள் (நேரம் அல்லது தூரத்தின் அடிப்படையில்)\n * குறிப்பான்களை உருவாக்கு (படங்கள் உள்ளிட்டவை)\n * முற்றிலும் ஆஃப்லைனில் வேலை செய்கிறது\n * வரைபடத்தில் தடங்களைக் காண்பி (கீழே காண்க)</string>
<string name="help_track_map_title">வரைபடத்தில் ஒரு பாதையை எவ்வாறு காண்பிப்பது?</string>
@@ -411,13 +411,13 @@
<string name="value_smallest_recommended">சிறியது (பரிந்துரைக்கப்படுகிறது)</string>
<string name="value_int_seconds">%1$d கள் (பரிந்துரைக்கப்படுகிறது)</string>
<string name="voiceIdle">சும்மா ஆக.</string>
<string name="voiceHoursPlural">{n, plural, = 1 {1 hour} other {{n,number} மணிநேரம்}}</string>
<string name="voiceMinutesPlural">{n, plural, = 1 {1 minute} other {{n,number} நிமிடங்கள்}}</string>
<string name="voiceSecondsPlural">{n, plural, = 1 {1 second} other {{n,number} விநாடிகள்}}</string>
<string name="voiceSpeedKilometersPerHourPlural">{n, plural, = 1 {1 kilometer per hour} other {{n,number,#. 0} ஒரு மணி நேரத்திற்கு கிலோமீட்டர்}}</string>
<string name="voiceSpeedMKnotsPlural">{n, plural, = 1 {1 knot} other {{n,number,#. 0} முடிச்சுகள்}}</string>
<string name="voiceSpeedMilesPerHourPlural">{n, plural, = 1 {1 mile per hour} other {{n,number,#. 0} ஒரு மணி நேரத்திற்கு மைல்கள்}}</string>
<string name="voiceSpeed">{n, plural, = 1 {1} other {{n,number,#. 0}}}</string>
<string name="voiceHoursPlural">{n, பன்மை, = 1 {1 hour} பிற {{n, எண்} மணிநேரம்}}</string>
<string name="voiceMinutesPlural">{n, பன்மை, = 1 {1 minute} பிற {{n, எண்} நிமிடங்கள்}}</string>
<string name="voiceSecondsPlural">{n, பன்மை, = 1 {1 second} பிற {{n, எண்} விநாடிகள்}}</string>
<string name="voiceSpeedKilometersPerHourPlural">{n, பன்மை, = 1 {1 kilometer per hour} பிற {{n, எண்,#. 0} ஒரு மணி நேரத்திற்கு கிலோமீட்டர்}}</string>
<string name="voiceSpeedMKnotsPlural">{n, பன்மை, = 1 {1 knot} பிற {{n, எண்,#. 0} முடிச்சுகள்}}</string>
<string name="voiceSpeedMilesPerHourPlural">{n, பன்மை, = 1 {1 mile per hour} பிற {{n, எண்,#. 0} ஒரு மணி நேரத்திற்கு மைல்கள்}}</string>
<string name="voiceSpeed">{n, பன்மை, = 1 {1} பிற {{n, எண்,#. 0}}}</string>
<string name="voice_on_device_speaker_title">சாதனத்தின் ச்பீக்கரைப் பயன்படுத்தவும்</string>
<string name="voice_per_kilometer">ஒரு கிலோமீட்டருக்கு</string>
<string name="voice_per_nautical_mile">ஒரு கடல் மைலுக்கு</string>
@@ -426,10 +426,10 @@
<string name="lap_speed">மடியில் விரைவு</string>
<string name="speed">சராசரி நகரும் விரைவு</string>
<string name="total_distance">மொத்த தூரம்</string>
<string name="voiceDistanceKilometersPlural">{n, plural, = 1 {1 kilometer} other {{n,number,#. 0} கிலோமீட்டர்}}</string>
<string name="voiceDistanceMilesPlural">{n, plural, = 1 {1 mile} other {{n,number,#. 0} மைல்கள்}}</string>
<string name="voiceDistanceNauticalMilesPlural">{n, plural, = 1 {1 nautical mile} other {{n,number,#. 0} கடல் மைல்கள்}}</string>
<string name="voiceDistance">{n, plural, = 1 {1 } other {{n,number,#. 0}}}</string>
<string name="voiceDistanceKilometersPlural">{n, பன்மை, = 1 {1 kilometer} பிற {{n, எண்,#. 0} கிலோமீட்டர்}}</string>
<string name="voiceDistanceMilesPlural">{n, பன்மை, = 1 {1 mile} பிற {{n, எண்,#. 0} மைல்கள்}}</string>
<string name="voiceDistanceNauticalMilesPlural">{n, பன்மை, = 1 {1 nautical mile} பிற {{n, எண்,#. 0} கடல் மைல்கள்}}</string>
<string name="voiceDistance">{n, பன்மை, = 1 {1 } பிற {{n, எண்,#. 0}}}</string>
<string name="average_heart_rate">சராசரி இதய துடிப்பு</string>
<string name="current_heart_rate">தற்போதைய இதய துடிப்பு</string>
<string name="lap_heart_rate">மடியில் இதய துடிப்பு</string>