forked from upstream-mirrors/OpenTracks
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7664bd248d | |||
| 73615aa0d6 | |||
| 81bb6699d8 | |||
| 93c67e3728 | |||
| 993816a34a | |||
| 3c0999c3a0 | |||
| 4528e7148f | |||
| 74042f5d85 | |||
| 92f6fbe04a | |||
| 5fa50250de | |||
| 8ee9daf7af | |||
| a54c8c1d25 | |||
| 8137ec8de2 | |||
| ae2a97576b | |||
| 3e830a086e | |||
| 618a1df3ea | |||
| 90cfc67c59 | |||
| cecc586868 | |||
| f956e7fab3 | |||
| 128b95cd83 | |||
| 895a5c2b2a | |||
| 91ce5cf8d7 | |||
| a283464331 | |||
| 1c3a2bd2e4 | |||
| 0b326a77c2 |
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: Data not recorded
|
||||
about: Somehow OpenTracks did not record GPS data
|
||||
title: ''
|
||||
labels: potential bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Please describe the problem and how to reproduce it:**
|
||||
|
||||
...
|
||||
|
||||
**Checklist**
|
||||
|
||||
- [ ] Consulted [https://dontkillmyapp.com/]
|
||||
- [ ] Power management: battery optimization is disabled for OpenTracks
|
||||
|
||||
**Technical information**
|
||||
|
||||
- Device: [e.g. Nexus 7 (2013)]
|
||||
- OS: [e.g. LineageOS 15.1]
|
||||
- OpenTracks version: [e.g. v3.2.4]
|
||||
- OpenTracks commit id (only for nightly builds): [e.g. 4b333bb]
|
||||
@@ -108,7 +108,7 @@ The API can be invoked by sending an explicit Intent to start an activity.
|
||||
|
||||
`Class`:
|
||||
* **Start a recording:** `de.dennisguse.opentracks.publicapi.StartRecording`
|
||||
* **Start a recording:** `de.dennisguse.opentracks.publicapi.StopRecording`
|
||||
* **Stop a recording:** `de.dennisguse.opentracks.publicapi.StopRecording`
|
||||
|
||||
For testing via adb: `adb shell am start -n "package/class"`
|
||||
|
||||
|
||||
+3
-3
@@ -59,8 +59,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 4926
|
||||
versionName "v4.0.1"
|
||||
versionCode 4951
|
||||
versionName "v4.0.3"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -117,7 +117,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
v4.0.2: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Add nautical miles
|
||||
- Settings can be reset while recording
|
||||
|
||||
Bugfix:
|
||||
- Voice announcement rounding
|
||||
- Chart: elevation was weirdly smoothed
|
||||
- Export was not checking if file exists
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.0.3: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Max. speed as reported by sensor (GPS or BLE)
|
||||
|
||||
Bugfix:
|
||||
- Prevent crash if OSM Dashboard is not installed
|
||||
- GPX track export: do not export empty track segments
|
||||
@@ -0,0 +1,11 @@
|
||||
v3.20.2 : OpenTracks
|
||||
|
||||
Changements :
|
||||
- Le gain et la perte d'altitude sont des entiers au lieu de floats
|
||||
- Demander la permission avant d'utiliser Bluetooth par un message Toast
|
||||
|
||||
Correction d'un bug :
|
||||
- Définir FLAG_IMMUTABLE pour PendingIntents pour éviter un crash sur Android 12
|
||||
- Le capteur de cadence renvoie 0 au lieu de None lorsque l'utilisateur ne pédale pas.
|
||||
- Le capteur de vitesse renvoie 0 au lieu de la dernière vitesse connue quand c'est arrêté
|
||||
- Avec les capteurs, le temps de déplacement s'arrête lorsqu'il n'est pas en mouvement
|
||||
@@ -7,5 +7,11 @@ Fonctionnalités :
|
||||
* Dénivelé positif et négatif: via capteur de pression barométrique
|
||||
* Synthèse vocale
|
||||
* Prise en charge des capteurs Bluetooth LE : rythme cardiaque, vitesse et distance (vélo), cadence de pédalage (vélo), et capteur de puissance (vélo)
|
||||
* Export : export des traces au format KMZ (photos incluses), KML ou GPX
|
||||
* Export : export des traces au format KMZ (photos incluses), KML ou GPX
|
||||
* Pas d'accès internet requis : Internet n'est pas nécessaire pour le fonctionnement de l'application
|
||||
* Thème sombre ou claire, avec le respect des paramètres du système
|
||||
* Pas de publicités
|
||||
|
||||
Logiciel Open Source
|
||||
Vous pouvez utiliser, étudier, changer et partager le code source.
|
||||
Licence Apache 2.0
|
||||
|
||||
@@ -27,7 +27,7 @@ public class ChartPointTest {
|
||||
statistics.setTotalTime(Duration.ofSeconds(1000));
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), Altitude.EGM2008.of(0), false, UnitSystem.IMPERIAL);
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), false, UnitSystem.IMPERIAL);
|
||||
|
||||
// then
|
||||
assertEquals(1000000, (long) point.getTimeOrDistance());
|
||||
@@ -40,7 +40,7 @@ public class ChartPointTest {
|
||||
statistics.setTotalDistance(Distance.of(1000));
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), Altitude.EGM2008.of(0), true, UnitSystem.METRIC);
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), true, UnitSystem.METRIC);
|
||||
|
||||
// then
|
||||
assertEquals(1, (long) point.getTimeOrDistance());
|
||||
@@ -50,9 +50,11 @@ public class ChartPointTest {
|
||||
public void create_get_altitude_speed_and_pace() {
|
||||
// given
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint()
|
||||
.setAltitude(Altitude.EGM2008.of(50));
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), Altitude.EGM2008.of(50), false, UnitSystem.METRIC);
|
||||
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), false, UnitSystem.METRIC);
|
||||
|
||||
// then
|
||||
assertEquals(50, point.getAltitude(), 0.01);
|
||||
@@ -64,9 +66,10 @@ public class ChartPointTest {
|
||||
public void create_sensorNotAvailable() {
|
||||
// given
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint()
|
||||
.setAltitude(Altitude.EGM2008.of(50));
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), Altitude.EGM2008.of(50), false, UnitSystem.METRIC);
|
||||
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), false, UnitSystem.METRIC);
|
||||
|
||||
// then
|
||||
assertNull(point.getHeartRate());
|
||||
@@ -77,15 +80,16 @@ public class ChartPointTest {
|
||||
@Test
|
||||
public void create_sensorAvailable() {
|
||||
// given
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint.setHeartRate(100f);
|
||||
trackPoint.setCadence(101f);
|
||||
trackPoint.setPower(102f);
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint()
|
||||
.setAltitude(Altitude.EGM2008.of(50))
|
||||
.setHeartRate(100f)
|
||||
.setCadence(101f)
|
||||
.setPower(102f);
|
||||
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), Altitude.EGM2008.of(50), false, UnitSystem.METRIC);
|
||||
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), false, UnitSystem.METRIC);
|
||||
|
||||
// then
|
||||
assertEquals(100.0, point.getHeartRate(), 0.01);
|
||||
|
||||
@@ -47,6 +47,7 @@ public class ChartValueSeriesTest {
|
||||
new int[]{1, 100, 1000},
|
||||
R.string.description_altitude_metric,
|
||||
R.string.description_altitude_imperial,
|
||||
R.string.description_altitude_imperial,
|
||||
R.color.chart_altitude_fill,
|
||||
R.color.chart_altitude_border,
|
||||
15,
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.util.Pair;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
@@ -24,10 +23,7 @@ public class ShareContentProviderTest {
|
||||
|
||||
@Test
|
||||
public void testCreateAndParseURI_valid() {
|
||||
Set<Track.Id> trackIds = new HashSet<>();
|
||||
trackIds.add(new Track.Id(1));
|
||||
trackIds.add(new Track.Id(3));
|
||||
trackIds.add(new Track.Id(5));
|
||||
Set<Track.Id> trackIds = Set.of(new Track.Id(1), new Track.Id(3), new Track.Id(5));
|
||||
|
||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "filename.suffix", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||
|
||||
@@ -36,9 +32,7 @@ public class ShareContentProviderTest {
|
||||
|
||||
@Test
|
||||
public void testCreateURIescapeFilename() {
|
||||
Set<Track.Id> trackIds = new HashSet<>();
|
||||
trackIds.add(new Track.Id(1));
|
||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(Set.of(new Track.Id(1)), "../../&1=1", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||
|
||||
assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/KML_WITH_TRACKDETAIL_AND_SENSORDATA/1/..%2F..%2F%261%3D1"), shareURIandMIME.first);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
|
||||
//TODO Parametrized tests
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DistanceFormatterTest {
|
||||
|
||||
@@ -42,4 +43,17 @@ public class DistanceFormatterTest {
|
||||
// A small number in imperial
|
||||
assertEquals("328.08 ft", formatter.formatDistance(Distance.of(100)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatDistance_nautical() {
|
||||
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.NAUTICAL_IMPERIAL)
|
||||
.build(context);
|
||||
|
||||
// A large number in nautical
|
||||
assertEquals("2.70 NM", formatter.formatDistance(Distance.of(5000)));
|
||||
// A small number in nautical
|
||||
assertEquals("328.08 ft", formatter.formatDistance(Distance.of(100)));
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
|
||||
//TODO Use make parametrized tests?
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SpeedFormatterTest {
|
||||
|
||||
@@ -39,12 +40,22 @@ public class SpeedFormatterTest {
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(context);
|
||||
|
||||
// assertEquals("TODO", formatter.getSpeedParts(Speed.of(3.34)).first);
|
||||
// assertEquals("TODO", formatter.getSpeedParts(Speed.of(3.33)).first);
|
||||
|
||||
assertEquals("8:02", formatter.getSpeedParts(Speed.of(3.34)).first);
|
||||
assertEquals("min/mi", formatter.getSpeedParts(Speed.zero()).second);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSpeedParts_pace_nautical() {
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.NAUTICAL_IMPERIAL)
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(context);
|
||||
|
||||
assertEquals("9:14", formatter.getSpeedParts(Speed.of(3.34)).first);
|
||||
assertEquals("min/NM", formatter.getSpeedParts(Speed.zero()).second);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSpeedParts_speed_metric() {
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||
@@ -69,6 +80,18 @@ public class SpeedFormatterTest {
|
||||
assertEquals("mph", formatter.getSpeedParts(Speed.zero()).second);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSpeedParts_speed_nautical() {
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.NAUTICAL_IMPERIAL)
|
||||
.setReportSpeedOrPace(true)
|
||||
.build(context);
|
||||
|
||||
assertEquals("6.5", formatter.getSpeedParts(Speed.of(3.34)).first);
|
||||
assertEquals("kn", formatter.getSpeedParts(Speed.zero()).second);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatSpeed() {
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||
|
||||
+1
-1
@@ -181,7 +181,7 @@ public class GPXTrackImporterTest {
|
||||
|
||||
// 3. trackstatistics
|
||||
TrackStatistics trackStatistics = importedTrack.getTrackStatistics();
|
||||
assertEquals(4, trackStatistics.getMaxSpeed().toMPS(), 0.01);
|
||||
assertEquals(4.0, trackStatistics.getMaxSpeed().toMPS(), 0.01);
|
||||
assertEquals(Duration.ofSeconds(101), trackStatistics.getMovingTime());
|
||||
|
||||
// 4. trackpoints
|
||||
|
||||
+48
@@ -67,6 +67,54 @@ public class VoiceAnnouncementUtilsTest {
|
||||
assertEquals("Total distance 20.00 kilometers. 1 hour 5 minutes 10 seconds. Speed 18.4 kilometers per hour.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_metric_speed_rounding_check() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusSeconds(1));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 20.00 kilometers. 1 hour 1 second. Speed 20.0 kilometers per hour.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_metric_distance_rounding_check() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(19999));
|
||||
stats.setTotalTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 20.00 kilometers. 1 hour. Speed 20.0 kilometers per hour.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_metric_distance_rounding_check_two() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(19990));
|
||||
stats.setTotalTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 19.99 kilometers. 1 hour. Speed 20.0 kilometers per hour.", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval_metric_speed() {
|
||||
// given
|
||||
|
||||
+66
-1
@@ -11,6 +11,7 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
@@ -86,7 +87,7 @@ public class TrackStatisticsUpdaterTest {
|
||||
assertEquals(Duration.ofSeconds(9), statistics.getMovingTime());
|
||||
|
||||
assertEquals(2.5, statistics.getMinAltitude(), 0.01);
|
||||
assertEquals(28.75, statistics.getMaxAltitude(), 0.01);
|
||||
assertEquals(32.5, statistics.getMaxAltitude(), 0.01);
|
||||
assertEquals(36, statistics.getTotalAltitudeGain(), 0.01);
|
||||
assertEquals(36, statistics.getTotalAltitudeLoss(), 0.01);
|
||||
|
||||
@@ -245,6 +246,70 @@ public class TrackStatisticsUpdaterTest {
|
||||
public void addTrackPoint_speed_from_GPS_moving_and_sensor_speed() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_maxSpeed_ignore_above_acceleration() {
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
assertEquals(Speed.of(0f), subject.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
subject.addTrackPoint(new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)));
|
||||
assertEquals(Speed.of(0f), subject.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
// Ignore as we set max speed if two consecutive trackpoints were considered moving
|
||||
subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1))
|
||||
.setSpeed(Speed.of(1f)));
|
||||
assertEquals(Speed.of(0f), subject.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
// Update max speed
|
||||
subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2))
|
||||
.setSpeed(Speed.of(1f)));
|
||||
assertEquals(Speed.of(1f), subject.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
// Update max speed
|
||||
subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(12))
|
||||
.setSpeed(Speed.of(50f)));
|
||||
assertEquals(Speed.of(50f), subject.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
// Ignore; we were getting slower
|
||||
subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(13))
|
||||
.setSpeed(Speed.of(5f)));
|
||||
assertEquals(Speed.of(50f), subject.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
// Ignore acceleration above 2g
|
||||
subject.addTrackPoint(new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(14))
|
||||
.setSpeed(Speed.of(500f)));
|
||||
assertEquals(Speed.of(50f), subject.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_maxSpeed_multiple_segments() {
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
assertEquals(Speed.of(0f), subject.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
subject.addTrackPoints(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)),
|
||||
new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(1))
|
||||
.setSpeed(Speed.of(2f)),
|
||||
new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(2))
|
||||
.setSpeed(Speed.of(2f)),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(4))
|
||||
));
|
||||
assertEquals(Speed.of(2f), subject.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
// when
|
||||
subject.addTrackPoints(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(5)),
|
||||
new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(6))
|
||||
.setSpeed(Speed.of(1f)),
|
||||
new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(7))
|
||||
.setSpeed(Speed.of(1f)),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(8))
|
||||
));
|
||||
|
||||
// then
|
||||
assertEquals(Speed.of(2f), subject.getTrackStatistics().getMaxSpeed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void copy_constructor() {
|
||||
// given
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class ExportUtilsTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testIsExportFileExists_exists() {
|
||||
// given
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String format = "kmz";
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
|
||||
fileNames.add(uuid.toString().substring(0, 8) + "_other name name.kmz");
|
||||
|
||||
// when
|
||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
||||
|
||||
// then
|
||||
assertTrue(exists);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsExportFileExists_not_exists1() {
|
||||
// given
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String format = "gpx";
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_name.gpx");
|
||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
|
||||
|
||||
// when
|
||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
||||
|
||||
// then
|
||||
assertFalse(exists);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsExportFileExists_not_exists2() {
|
||||
// given
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String format = "kmz";
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
|
||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
|
||||
|
||||
// when
|
||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
||||
|
||||
// then
|
||||
assertFalse(exists);
|
||||
}
|
||||
}
|
||||
@@ -41,12 +41,12 @@ import java.time.format.TextStyle;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.LocaleRule;
|
||||
import de.dennisguse.opentracks.TimezoneRule;
|
||||
|
||||
/**
|
||||
* Tests for {@link StringUtils}.
|
||||
@@ -59,6 +59,9 @@ public class StringUtilsTest {
|
||||
@Rule
|
||||
public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH);
|
||||
|
||||
@Rule
|
||||
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
/**
|
||||
@@ -99,51 +102,13 @@ public class StringUtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTime_fractional() {
|
||||
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
assertGetTime("2010-05-04T03:02:01.3529Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
}
|
||||
public void testParseTime() {
|
||||
assertEquals(Instant.ofEpochMilli(352), StringUtils.parseTime("1970-01-01T00:00:00.352").toInstant());
|
||||
assertEquals(Instant.ofEpochMilli(352), StringUtils.parseTime("1970-01-01T00:00:00.352Z").toInstant());
|
||||
assertEquals(Instant.ofEpochMilli(352), StringUtils.parseTime("1970-01-01T00:00:00.352+00:00").toInstant());
|
||||
|
||||
@Test
|
||||
public void testGetTime_timezone() {
|
||||
assertGetTime("2010-05-04T03:02:01", 2010, 5, 4, 3, 2, 1, 0);
|
||||
assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0);
|
||||
assertGetTime("2010-05-04T03:02:01+00:00", 2010, 5, 4, 3, 2, 1, 0);
|
||||
assertGetTime("2010-05-04T03:02:01-00:00", 2010, 5, 4, 3, 2, 1, 0);
|
||||
assertGetTime("2010-05-04T03:02:01+01:00", 2010, 5, 4, 2, 2, 1, 0);
|
||||
assertGetTime("2010-05-04T03:02:01+10:30", 2010, 5, 3, 16, 32, 1, 0);
|
||||
assertGetTime("2010-05-04T03:02:01-09:30", 2010, 5, 4, 12, 32, 1, 0);
|
||||
assertGetTime("2010-05-04T03:02:01-05:00", 2010, 5, 4, 8, 2, 1, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTime_fractionalAndTimezone() {
|
||||
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
assertGetTime("2010-05-04T03:02:01.47+00:00", 2010, 5, 4, 3, 2, 1, 470);
|
||||
assertGetTime("2010-05-04T03:02:01.5791+03:00", 2010, 5, 4, 0, 2, 1, 579);
|
||||
assertGetTime("2010-05-04T03:02:01.8-05:30", 2010, 5, 4, 8, 32, 1, 800);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the {@link StringUtils#parseTime(String)} returns the expected values.
|
||||
*
|
||||
* @param xmlDateTime the xml date time string
|
||||
* @param year the expected year
|
||||
* @param month the expected month
|
||||
* @param day the expected day
|
||||
* @param hour the expected hour
|
||||
* @param minute the expected minute
|
||||
* @param second the expected second
|
||||
* @param millisecond the expected milliseconds
|
||||
*/
|
||||
private void assertGetTime(String xmlDateTime, int year, int month, int day, int hour, int minute, int second, int millisecond) {
|
||||
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||
calendar.set(year, month - 1, day, hour, minute, second);
|
||||
calendar.set(GregorianCalendar.MILLISECOND, millisecond);
|
||||
|
||||
// This comparision tends to be flaky (difference of 1ms)
|
||||
// Assert.assertEquals(calendar.getTimeInMillis(), StringUtils.parseTime(xmlDateTime));
|
||||
assertTrue(calendar.getTimeInMillis() + " vs. " + StringUtils.parseTime(xmlDateTime), Math.abs(calendar.getTimeInMillis() - StringUtils.parseTime(xmlDateTime).toInstant().toEpochMilli()) <= 1);
|
||||
assertEquals(Instant.ofEpochMilli(352), StringUtils.parseTime("1970-01-01T01:00:00.352+01:00").toInstant());
|
||||
assertEquals(Instant.ofEpochMilli(352).plus(Duration.ofHours(1)), StringUtils.parseTime("1970-01-01T00:00:00.352-01:00").toInstant());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -188,9 +188,9 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
|
||||
}
|
||||
}
|
||||
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, Altitude smoothedAltitude) {
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics) {
|
||||
if (isResumed()) {
|
||||
ChartPoint point = new ChartPoint(trackStatistics, trackPoint, smoothedSpeed, smoothedAltitude, chartByDistance, viewBinding.chartView.getUnitSystem());
|
||||
ChartPoint point = new ChartPoint(trackStatistics, trackPoint, trackPoint.getSpeed(), chartByDistance, viewBinding.chartView.getUnitSystem());
|
||||
pendingPoints.add(point);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,15 +29,15 @@ public class ChartPoint {
|
||||
this.altitude = altitude;
|
||||
}
|
||||
|
||||
public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, Altitude smoothedAltitude, boolean chartByDistance, UnitSystem unitSystem) {
|
||||
public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, boolean chartByDistance, UnitSystem unitSystem) {
|
||||
if (chartByDistance) {
|
||||
timeOrDistance = trackStatistics.getTotalDistance().toKM_Miles(unitSystem);
|
||||
} else {
|
||||
timeOrDistance = trackStatistics.getTotalTime().toMillis();
|
||||
}
|
||||
|
||||
if (smoothedAltitude != null) {
|
||||
altitude = Distance.of(smoothedAltitude.toM()).toM_FT(unitSystem);
|
||||
if (trackPoint.hasAltitude()) {
|
||||
altitude = Distance.of(trackPoint.getAltitude().toM()).toM_FT(unitSystem);
|
||||
}
|
||||
|
||||
if (smoothedSpeed != null) {
|
||||
|
||||
@@ -44,6 +44,7 @@ abstract class ChartValueSeries {
|
||||
private final int[] intervalValues;
|
||||
private final int metricTitleId;
|
||||
private final int imperialTitleId;
|
||||
private final int nauticalTitleId;
|
||||
private final Paint fillPaint;
|
||||
private final Paint strokePaint;
|
||||
private final Paint titlePaint;
|
||||
@@ -57,24 +58,13 @@ abstract class ChartValueSeries {
|
||||
private int maxMarkerValue = interval * ChartView.Y_AXIS_INTERVALS;
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
* @param absoluteMin the absolute min value
|
||||
* @param absoluteMax the absolute max value
|
||||
* @param intervalValues the list of interval values
|
||||
* @param metricTitleId the metric title id
|
||||
* @param imperialTitleId the imperial title id
|
||||
* @param fillColor the fill color
|
||||
* @param strokeColor the stroke color
|
||||
*/
|
||||
ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues, int metricTitleId, int imperialTitleId, int fillColor, int strokeColor, int fontSizeSmall, int fontSizeMedium) {
|
||||
ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues, int metricTitleId, int imperialTitleId, int nauticalTitleId, int fillColor, int strokeColor, int fontSizeSmall, int fontSizeMedium) {
|
||||
this.absoluteMin = absoluteMin;
|
||||
this.absoluteMax = absoluteMax;
|
||||
this.intervalValues = intervalValues;
|
||||
this.metricTitleId = metricTitleId;
|
||||
this.imperialTitleId = imperialTitleId;
|
||||
this.nauticalTitleId = nauticalTitleId;
|
||||
|
||||
fillPaint = new Paint();
|
||||
fillPaint.setStyle(Style.FILL);
|
||||
@@ -224,39 +214,27 @@ abstract class ChartValueSeries {
|
||||
return metricTitleId;
|
||||
case IMPERIAL:
|
||||
return imperialTitleId;
|
||||
case NAUTICAL_IMPERIAL:
|
||||
return nauticalTitleId;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title paint.
|
||||
*/
|
||||
Paint getTitlePaint() {
|
||||
return titlePaint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the marker paint.
|
||||
*/
|
||||
Paint getMarkerPaint() {
|
||||
return markerPaint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the largest marker.
|
||||
*/
|
||||
String getLargestMarker() {
|
||||
String minMarker = numberFormat.format(getMinMarkerValue());
|
||||
String maxMarker = numberFormat.format(getMaxMarkerValue());
|
||||
return minMarker.length() >= maxMarker.length() ? minMarker : maxMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a marker value.
|
||||
*
|
||||
* @param value the value
|
||||
*/
|
||||
String formatMarker(int value) {
|
||||
return numberFormat.format(value);
|
||||
}
|
||||
|
||||
@@ -203,6 +203,7 @@ public class ChartView extends View {
|
||||
new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000},
|
||||
R.string.description_altitude_metric,
|
||||
R.string.description_altitude_imperial,
|
||||
R.string.description_altitude_imperial,
|
||||
R.color.chart_altitude_fill,
|
||||
R.color.chart_altitude_border,
|
||||
fontSizeSmall,
|
||||
@@ -224,6 +225,7 @@ public class ChartView extends View {
|
||||
new int[]{1, 5, 10, 20, 50, 100},
|
||||
R.string.description_speed_metric,
|
||||
R.string.description_speed_imperial,
|
||||
R.string.description_speed_nautical,
|
||||
R.color.chart_speed_fill,
|
||||
R.color.chart_speed_border,
|
||||
fontSizeSmall,
|
||||
@@ -246,6 +248,7 @@ public class ChartView extends View {
|
||||
new int[]{1, 2, 5, 10, 15, 20, 30, 60, 120},
|
||||
R.string.description_pace_metric,
|
||||
R.string.description_pace_imperial,
|
||||
R.string.description_pace_nautical,
|
||||
R.color.chart_pace_fill,
|
||||
R.color.chart_pace_border,
|
||||
fontSizeSmall,
|
||||
@@ -268,6 +271,7 @@ public class ChartView extends View {
|
||||
new int[]{25, 50},
|
||||
R.string.description_sensor_heart_rate,
|
||||
R.string.description_sensor_heart_rate,
|
||||
R.string.description_sensor_heart_rate,
|
||||
R.color.chart_heart_rate_fill,
|
||||
R.color.chart_heart_rate_border,
|
||||
fontSizeSmall,
|
||||
@@ -289,6 +293,7 @@ public class ChartView extends View {
|
||||
new int[]{5, 10, 25, 50},
|
||||
R.string.description_sensor_cadence,
|
||||
R.string.description_sensor_cadence,
|
||||
R.string.description_sensor_cadence,
|
||||
R.color.chart_cadence_fill,
|
||||
R.color.chart_cadence_border,
|
||||
fontSizeSmall,
|
||||
@@ -309,6 +314,7 @@ public class ChartView extends View {
|
||||
new int[]{5, 50, 100, 200},
|
||||
R.string.description_sensor_power,
|
||||
R.string.description_sensor_power,
|
||||
R.string.description_sensor_power,
|
||||
R.color.chart_power_fill,
|
||||
R.color.chart_power_border,
|
||||
fontSizeSmall,
|
||||
@@ -732,6 +738,8 @@ public class ChartView extends View {
|
||||
return context.getString(R.string.unit_kilometer);
|
||||
case IMPERIAL:
|
||||
return context.getString(R.string.unit_mile);
|
||||
case NAUTICAL_IMPERIAL:
|
||||
return context.getString(R.string.unit_nautical_mile);
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
@@ -72,9 +73,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
* @return An URI for one file containing this tracks.
|
||||
*/
|
||||
public static Pair<Uri, String> createURI(Track.Id trackId, String trackName, @NonNull TrackFileFormat trackFileFormat) {
|
||||
Set<Track.Id> trackIds = new HashSet<>(1);
|
||||
trackIds.add(trackId);
|
||||
return createURI(trackIds, trackName, trackFileFormat);
|
||||
return createURI(Set.of(trackId), trackName, trackFileFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,11 +107,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
|
||||
String[] uriTrackIds = uriPaths.get(2).split(TRACKID_DELIMITER);
|
||||
|
||||
Set<Track.Id> trackIds = new HashSet<>();
|
||||
for (String uriTrackId : uriTrackIds) {
|
||||
trackIds.add(new Track.Id(Long.parseLong(uriTrackId)));
|
||||
}
|
||||
return trackIds;
|
||||
return Arrays.stream(uriTrackIds).map(uriTrackId -> new Track.Id(Long.parseLong(uriTrackId))).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,14 +25,12 @@ import android.os.HandlerThread;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
@@ -367,7 +365,7 @@ public class TrackDataHub {
|
||||
// Also include the last point if the selected track is not recording.
|
||||
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||
for (Listener trackDataListener : listeners) {
|
||||
trackDataListener.onSampledInTrackPoint(trackPoint, currentUpdater.getTrackStatistics(), currentUpdater.getSmoothedSpeed(), currentUpdater.getSmoothedAltitude());
|
||||
trackDataListener.onSampledInTrackPoint(trackPoint, currentUpdater.getTrackStatistics());
|
||||
}
|
||||
} else {
|
||||
for (Listener trackDataListener : listeners) {
|
||||
@@ -432,7 +430,7 @@ public class TrackDataHub {
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, @Nullable Altitude smoothedAltitude_m) {
|
||||
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,6 +29,10 @@ public class Distance {
|
||||
return of(distance_mile * UnitConversions.MI_TO_M);
|
||||
}
|
||||
|
||||
public static Distance ofNauticalMile(double distance_mile) {
|
||||
return of(distance_mile * UnitConversions.NAUTICAL_MILE_TO_M);
|
||||
}
|
||||
|
||||
public static Distance ofKilometer(double distance_km) {
|
||||
return of(distance_km * UnitConversions.KM_TO_M);
|
||||
}
|
||||
@@ -51,6 +55,8 @@ public class Distance {
|
||||
return Distance.ofKilometer(1);
|
||||
case IMPERIAL:
|
||||
return Distance.ofMile(1);
|
||||
case NAUTICAL_IMPERIAL:
|
||||
return Distance.ofNauticalMile(1);
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
@@ -114,12 +120,18 @@ public class Distance {
|
||||
return toKM() * UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
public double toNauticalMiles() {
|
||||
return toKM() * UnitConversions.KM_TO_NAUTICAL_MILE;
|
||||
}
|
||||
|
||||
public double toKM_Miles(UnitSystem unitSystem) {
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return toKM();
|
||||
case IMPERIAL:
|
||||
return toMI();
|
||||
case NAUTICAL_IMPERIAL:
|
||||
return toNauticalMiles();
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
@@ -129,6 +141,7 @@ public class Distance {
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return toM();
|
||||
case NAUTICAL_IMPERIAL:
|
||||
case IMPERIAL:
|
||||
return toFT();
|
||||
default:
|
||||
|
||||
@@ -46,6 +46,7 @@ public class DistanceFormatter {
|
||||
case METRIC:
|
||||
return new Pair<>(null, resources.getString(R.string.unit_meter));
|
||||
case IMPERIAL:
|
||||
case NAUTICAL_IMPERIAL:
|
||||
return new Pair<>(null, resources.getString(R.string.unit_feet));
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
@@ -65,6 +66,12 @@ public class DistanceFormatter {
|
||||
} else {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
|
||||
}
|
||||
case NAUTICAL_IMPERIAL:
|
||||
if (distance.greaterThan(Distance.ofNauticalMile(0.5))) {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toNauticalMiles(), decimalCount), resources.getString(R.string.unit_nautical_mile));
|
||||
} else {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
|
||||
}
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
@@ -93,6 +93,10 @@ public class Speed {
|
||||
return toKMH() * UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
public double toKnots() {
|
||||
return toKMH() * UnitConversions.KM_TO_NAUTICAL_MILE;
|
||||
}
|
||||
|
||||
public Duration toPace(UnitSystem unitSystem) {
|
||||
if (isZero()) {
|
||||
return Duration.ofSeconds(0);
|
||||
@@ -106,6 +110,9 @@ public class Speed {
|
||||
case IMPERIAL:
|
||||
distance *= UnitConversions.M_TO_MI;
|
||||
break;
|
||||
case NAUTICAL_IMPERIAL:
|
||||
distance *= UnitConversions.M_TO_NAUTICAL_MILE;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
@@ -119,6 +126,8 @@ public class Speed {
|
||||
return toKMH();
|
||||
case IMPERIAL:
|
||||
return toMPH();
|
||||
case NAUTICAL_IMPERIAL:
|
||||
return toKnots();
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ public class SpeedFormatter {
|
||||
case IMPERIAL:
|
||||
unitId = reportSpeedOrPace ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile;
|
||||
break;
|
||||
case NAUTICAL_IMPERIAL:
|
||||
unitId = reportSpeedOrPace ? R.string.unit_knots : R.string.unit_minute_per_nautical_mile;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public class TrackPoint {
|
||||
private Double longitude;
|
||||
private Distance horizontalAccuracy;
|
||||
private Distance verticalAccuracy;
|
||||
private Altitude altitude;
|
||||
private Altitude altitude; //TODO use Altitude.WGS84
|
||||
private Speed speed;
|
||||
private Float bearing;
|
||||
private Distance sensorDistance;
|
||||
|
||||
@@ -24,6 +24,7 @@ public class UnitConversions {
|
||||
|
||||
// Time //TODO Use Duration
|
||||
// multiplication factor to convert seconds to milliseconds
|
||||
@Deprecated
|
||||
public static final long S_TO_MS = 1000;
|
||||
|
||||
// multiplication factor to convert milliseconds to seconds
|
||||
@@ -53,6 +54,9 @@ public class UnitConversions {
|
||||
public static final double M_TO_MI = M_TO_KM * KM_TO_MI;
|
||||
// multiplication factor to convert meters to feet
|
||||
public static final double M_TO_FT = M_TO_MI * MI_TO_FT;
|
||||
public static final double NAUTICAL_MILE_TO_M = 1852.0;
|
||||
public static final double M_TO_NAUTICAL_MILE = 1 / NAUTICAL_MILE_TO_M;
|
||||
public static final double KM_TO_NAUTICAL_MILE = 1000 * M_TO_NAUTICAL_MILE;
|
||||
// multiplication factor to convert meters per second to kilometers per hour
|
||||
public static final double MPS_TO_KMH = M_TO_KM / (S_TO_MIN * MIN_TO_HR);
|
||||
// multiplication factor to convert miles to km
|
||||
|
||||
@@ -39,6 +39,7 @@ import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.databinding.ExportActivityBinding;
|
||||
import de.dennisguse.opentracks.io.file.ErrorListDialog;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.ExportUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
@@ -234,7 +235,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
* @param conflictResolution conflict resolution to be applied if needed.
|
||||
*/
|
||||
private void export(Track track, int conflictResolution) {
|
||||
boolean fileExists = ExportUtils.isExportFileExists(track.getUuid(), trackFileFormat.getExtension(), directoryFiles);
|
||||
boolean fileExists = exportFileExists(track);
|
||||
|
||||
if (fileExists && conflictResolution == CONFLICT_NONE) {
|
||||
conflict(track);
|
||||
@@ -250,6 +251,11 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
export(track, autoConflict);
|
||||
}
|
||||
|
||||
private boolean exportFileExists(Track track) {
|
||||
String filename = PreferencesUtils.getTrackFileformatGenerator().format(track, trackFileFormat);
|
||||
return directoryFiles.stream().anyMatch(filename::equals);
|
||||
}
|
||||
|
||||
private void setConflictVisibility(int visibility) {
|
||||
viewBinding.exportProgressAlertIcon.setVisibility(visibility);
|
||||
viewBinding.exportProgressAlertMsg.setVisibility(visibility);
|
||||
@@ -315,7 +321,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
public void onExportSuccess(Track.Id trackId) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
if (ExportUtils.isExportFileExists(track.getUuid(), trackFileFormat.getExtension(), directoryFiles)) {
|
||||
if (exportFileExists(track)) {
|
||||
trackExportOverwrittenCount++;
|
||||
} else {
|
||||
trackExportSuccessCount++;
|
||||
|
||||
@@ -146,13 +146,10 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
|
||||
switch (trackPoint.getType()) {
|
||||
case SEGMENT_START_MANUAL:
|
||||
if (wroteSegment) writeCloseSegment();
|
||||
writeOpenSegment();
|
||||
wroteSegment = true;
|
||||
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_START_MANUAL.name() + " is not supported.");
|
||||
break;
|
||||
case SEGMENT_END_MANUAL:
|
||||
writeCloseSegment();
|
||||
if (wroteSegment) writeCloseSegment();
|
||||
wroteSegment = false;
|
||||
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_END_MANUAL.name() + " is not supported.");
|
||||
break;
|
||||
|
||||
+24
-22
@@ -57,6 +57,13 @@ class VoiceAnnouncementUtils {
|
||||
unitDistanceTTS = "mile";
|
||||
unitSpeedTTS = "mile per hour";
|
||||
break;
|
||||
case NAUTICAL_IMPERIAL:
|
||||
perUnitStringId = R.string.voice_per_nautical_mile;
|
||||
distanceId = R.plurals.voiceDistanceNauticalMiles;
|
||||
speedId = R.plurals.voiceSpeedMKnots;
|
||||
unitDistanceTTS = "nautical mile";
|
||||
unitSpeedTTS = "knots";
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
@@ -65,12 +72,9 @@ class VoiceAnnouncementUtils {
|
||||
|
||||
if (shouldVoiceAnnounceTotalDistance()) {
|
||||
builder.append(context.getString(R.string.total_distance));
|
||||
long distanceIntegerPart = (long) distanceInUnit;
|
||||
// Extract the decimal part
|
||||
String distanceFractionalPart = String.format("%.2f", (distanceInUnit - distanceIntegerPart)).substring(2);
|
||||
// Units should always be english singular for TTS.
|
||||
// See https://developer.android.com/reference/android/text/style/TtsSpan?hl=en#TYPE_MEASURE
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(distanceId, getQuantityCount(distanceInUnit), distanceInUnit), distanceIntegerPart, distanceFractionalPart, unitDistanceTTS);
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(distanceId, getQuantityCount(distanceInUnit), distanceInUnit), distanceInUnit, 2, unitDistanceTTS);
|
||||
// Punctuation helps introduce natural pauses in TTS
|
||||
builder.append(".");
|
||||
}
|
||||
@@ -88,27 +92,17 @@ class VoiceAnnouncementUtils {
|
||||
if (isReportSpeed) {
|
||||
if (shouldVoiceAnnounceAverageSpeedPace()) {
|
||||
double speedInUnit = distancePerTime.to(unitSystem);
|
||||
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.speed));
|
||||
long speedIntegerPart = (long) speedInUnit;
|
||||
// Extract the decimal part
|
||||
String speedFractionalPart = String.format("%.1f", (speedInUnit - speedIntegerPart)).substring(2);
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(speedId, getQuantityCount(speedInUnit), speedInUnit), speedIntegerPart, speedFractionalPart, unitSpeedTTS);
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(speedId, getQuantityCount(speedInUnit), speedInUnit), speedInUnit, 1, unitSpeedTTS);
|
||||
builder.append(".");
|
||||
}
|
||||
|
||||
if (shouldVoiceAnnounceLapSpeedPace() && currentDistancePerTime != null) {
|
||||
double currentDistancePerTimeInUnit = currentDistancePerTime.to(unitSystem);
|
||||
|
||||
if (currentDistancePerTimeInUnit > 0) {
|
||||
|
||||
builder.append(" ")
|
||||
.append(context.getString(R.string.lap_speed));
|
||||
long currentDistanceIntegerPart = (long) currentDistancePerTimeInUnit;
|
||||
// Extract the decimal part
|
||||
String currentDistanceFractionalPart = String.format("%.1f", (currentDistancePerTimeInUnit - currentDistanceIntegerPart)).substring(2);
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTimeInUnit), currentDistancePerTimeInUnit), currentDistanceIntegerPart, currentDistanceFractionalPart, unitSpeedTTS);
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTimeInUnit), currentDistancePerTimeInUnit), currentDistancePerTimeInUnit, 1, unitSpeedTTS);
|
||||
builder.append(".");
|
||||
}
|
||||
}
|
||||
@@ -164,26 +158,34 @@ class VoiceAnnouncementUtils {
|
||||
int seconds = (int) (duration.getSeconds() % 60);
|
||||
|
||||
if (hours > 0) {
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(R.plurals.voiceHours, hours, hours), hours, null, "hour");
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(R.plurals.voiceHours, hours, hours), hours, 0, "hour");
|
||||
}
|
||||
if (minutes > 0) {
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(R.plurals.voiceMinutes, minutes, minutes), minutes, null, "minute");
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(R.plurals.voiceMinutes, minutes, minutes), minutes, 0, "minute");
|
||||
}
|
||||
if (seconds > 0 || duration.isZero()) {
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(R.plurals.voiceSeconds, seconds, seconds), seconds, null, "second");
|
||||
appendDecimalUnit(builder, context.getResources().getQuantityString(R.plurals.voiceSeconds, seconds, seconds), seconds, 0, "second");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Speaks as: 98.14 [UNIT] - ninety eight point one four [UNIT with correct plural form]
|
||||
*
|
||||
* @param number The number to speak
|
||||
* @param precision The number of decimal places to announce
|
||||
*/
|
||||
private static void appendDecimalUnit(@NonNull SpannableStringBuilder builder, @NonNull String localizedText, long integerPart, @Nullable String fractionalPart, @NonNull String unit) {
|
||||
private static void appendDecimalUnit(@NonNull SpannableStringBuilder builder, @NonNull String localizedText, double number, int precision, @NonNull String unit) {
|
||||
TtsSpan.MeasureBuilder measureBuilder = new TtsSpan.MeasureBuilder()
|
||||
.setUnit(unit);
|
||||
|
||||
if (fractionalPart == null) {
|
||||
measureBuilder.setNumber(integerPart);
|
||||
if (precision == 0) {
|
||||
measureBuilder.setNumber((long)number);
|
||||
} else {
|
||||
// Round before extracting integral and decimal parts
|
||||
double roundedNumber = Math.round(Math.pow(10, precision) * number) / Math.pow(10.0, precision);
|
||||
long integerPart = (long) roundedNumber;
|
||||
// Extract the decimal part
|
||||
String fractionalPart = String.format("%." + precision + "f", (roundedNumber - integerPart)).substring(2);
|
||||
measureBuilder.setIntegerPart(integerPart)
|
||||
.setFractionalPart(fractionalPart);
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ public class DefaultsSettingsFragment extends PreferenceFragmentCompat implement
|
||||
entriesId = R.array.stats_rate_metric_options;
|
||||
break;
|
||||
case IMPERIAL:
|
||||
case NAUTICAL_IMPERIAL:
|
||||
entriesId = R.array.stats_rate_imperial_options;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -214,7 +214,6 @@ public class PreferencesUtils {
|
||||
//TODO Check if actually needed or can be superseeded by a flexible default in getUnit()
|
||||
public static void applyDefaultUnit() {
|
||||
if (getString(R.string.stats_units_key, "").equals("")) {
|
||||
|
||||
if (!Locale.US.equals(Locale.getDefault())) {
|
||||
setUnit(UnitSystem.METRIC);
|
||||
} else {
|
||||
@@ -324,7 +323,7 @@ public class PreferencesUtils {
|
||||
UnitSystem unitSystem = getUnitSystem();
|
||||
|
||||
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||
.setDecimalCount(0)
|
||||
.setDecimalCount(1)
|
||||
.setUnit(unitSystem)
|
||||
.build(resources);
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
@@ -418,7 +417,7 @@ public class PreferencesUtils {
|
||||
|
||||
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setDecimalCount(0)
|
||||
.setDecimalCount(1)
|
||||
.build(resources);
|
||||
for (int i = 0; i < entryValues.length; i++) {
|
||||
int value = Integer.parseInt(entryValues[i]);
|
||||
@@ -434,6 +433,7 @@ public class PreferencesUtils {
|
||||
}
|
||||
break;
|
||||
case IMPERIAL:
|
||||
case NAUTICAL_IMPERIAL:
|
||||
if (value == recordingDistanceIntervalDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||
} else {
|
||||
@@ -461,7 +461,7 @@ public class PreferencesUtils {
|
||||
UnitSystem unitSystem = getUnitSystem();
|
||||
|
||||
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||
.setDecimalCount(0)
|
||||
.setDecimalCount(1)
|
||||
.setUnit(unitSystem)
|
||||
.build(resources);
|
||||
for (int i = 0; i < entryValues.length; i++) {
|
||||
@@ -478,6 +478,7 @@ public class PreferencesUtils {
|
||||
}
|
||||
break;
|
||||
case IMPERIAL:
|
||||
case NAUTICAL_IMPERIAL:
|
||||
if (value == maxRecordingDistanceDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||
} else {
|
||||
@@ -533,7 +534,7 @@ public class PreferencesUtils {
|
||||
UnitSystem unitSystem = getUnitSystem();
|
||||
|
||||
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||
.setDecimalCount(0)
|
||||
.setDecimalCount(1)
|
||||
.setUnit(unitSystem)
|
||||
.build(resources);
|
||||
|
||||
@@ -555,6 +556,7 @@ public class PreferencesUtils {
|
||||
}
|
||||
break;
|
||||
case IMPERIAL:
|
||||
case NAUTICAL_IMPERIAL:
|
||||
if (value == recordingGPSAccuracyDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||
} else if (value == recordingGPSAccuracyExcellent) {
|
||||
@@ -606,6 +608,14 @@ public class PreferencesUtils {
|
||||
entries[i] = resources.getString(R.string.value_float_mile_hour, valueMPH);
|
||||
}
|
||||
break;
|
||||
case NAUTICAL_IMPERIAL:
|
||||
double valueKnots = Speed.ofKMH(value).toKnots();
|
||||
if (value == idleSpeedDefault) {
|
||||
entries[i] = resources.getString(R.string.value_float_knots_recommended, valueKnots);
|
||||
} else {
|
||||
entries[i] = resources.getString(R.string.value_float_knots, valueKnots);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import de.dennisguse.opentracks.R;
|
||||
|
||||
public enum UnitSystem {
|
||||
METRIC(R.string.stats_units_metric),
|
||||
IMPERIAL(R.string.stats_units_imperial);
|
||||
IMPERIAL(R.string.stats_units_imperial),
|
||||
NAUTICAL_IMPERIAL(R.string.stats_units_nautical); // Nautical miles with feet
|
||||
|
||||
private final int preferenceIdentifier;
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
|
||||
public class AltitudeRingBuffer extends RingBuffer<Altitude> {
|
||||
|
||||
private Altitude firstAltitude;
|
||||
|
||||
AltitudeRingBuffer(int size) {
|
||||
super(size);
|
||||
}
|
||||
|
||||
AltitudeRingBuffer(RingBuffer<Altitude> toCopy) {
|
||||
super(toCopy);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Number from(Altitude object) {
|
||||
if (firstAltitude == null) {
|
||||
firstAltitude = object;
|
||||
}
|
||||
return object.toM();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Altitude to(double object) {
|
||||
return firstAltitude.replace(object);
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This class maintains a ring buffer of doubles.
|
||||
* This buffer is a convenient class for storing a series of doubles and calculating information about them.
|
||||
* This is a FIFO buffer.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
abstract class RingBuffer<T> {
|
||||
|
||||
private final ArrayList<T> buffer;
|
||||
|
||||
// The location that the next write will occur at.
|
||||
private int index;
|
||||
|
||||
// True if the buffer is full
|
||||
private boolean isFull;
|
||||
|
||||
/**
|
||||
* Creates a buffer with a certain size.
|
||||
*
|
||||
* @param size the size
|
||||
*/
|
||||
RingBuffer(int size) {
|
||||
if (size < 1) {
|
||||
throw new IllegalArgumentException("The buffer size must be greater than 1.");
|
||||
}
|
||||
|
||||
buffer = new ArrayList<T>(size);
|
||||
reset();
|
||||
}
|
||||
|
||||
RingBuffer(RingBuffer<T> toCopy) {
|
||||
this.buffer = new ArrayList<>(toCopy.buffer);
|
||||
this.index = toCopy.index;
|
||||
this.isFull = toCopy.isFull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the buffer.
|
||||
*/
|
||||
public void reset() {
|
||||
index = 0;
|
||||
isFull = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the buffer is full.
|
||||
*/
|
||||
boolean isFull() {
|
||||
return isFull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the average of the buffer.
|
||||
*/
|
||||
public T getAverage() {
|
||||
int numberOfEntries = isFull ? buffer.size() : index;
|
||||
if (numberOfEntries == 0) {
|
||||
return null;
|
||||
}
|
||||
Double sum = null;
|
||||
int numberOfUsedEntries = 0;
|
||||
for (int i = 0; i < numberOfEntries; i++) {
|
||||
Number value = from(buffer.get(i));
|
||||
if (value != null) {
|
||||
if (sum == null) {
|
||||
sum = 0.0;
|
||||
}
|
||||
sum += value.doubleValue();
|
||||
numberOfUsedEntries++;
|
||||
}
|
||||
}
|
||||
if (sum == null) {
|
||||
return null;
|
||||
} else {
|
||||
return to(sum / numberOfUsedEntries);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract Number from(T object);
|
||||
|
||||
protected abstract T to(double object);
|
||||
|
||||
/**
|
||||
* Adds a double to the buffer.
|
||||
* If the buffer is full the oldest element is overwritten.
|
||||
*
|
||||
* @param value the double to add
|
||||
*/
|
||||
public void setNext(T value) {
|
||||
if (index == buffer.size()) {
|
||||
index = 0;
|
||||
}
|
||||
buffer.add(index, value);
|
||||
index++;
|
||||
if (index == buffer.size()) {
|
||||
isFull = true;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder("Full: ");
|
||||
builder.append(isFull);
|
||||
builder.append("\n");
|
||||
for (int i = 0; i < buffer.size(); i++) {
|
||||
builder.append((i == index) ? "<<" : "[");
|
||||
builder.append(buffer.get(i));
|
||||
builder.append((i == index) ? ">> " : "] ");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
|
||||
public class SpeedRingBuffer extends RingBuffer<Speed> {
|
||||
|
||||
SpeedRingBuffer(int size) {
|
||||
super(size);
|
||||
}
|
||||
|
||||
SpeedRingBuffer(RingBuffer<Speed> toCopy) {
|
||||
super(toCopy);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Number from(Speed object) {
|
||||
return object.toMPS();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Speed to(double object) {
|
||||
return Speed.of(object);
|
||||
}
|
||||
}
|
||||
@@ -19,16 +19,15 @@ package de.dennisguse.opentracks.stats;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.data.models.UnitConversions;
|
||||
|
||||
/**
|
||||
* Updater for {@link TrackStatistics}.
|
||||
@@ -41,30 +40,15 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
*/
|
||||
public class TrackStatisticsUpdater {
|
||||
|
||||
/**
|
||||
* The number of altitude readings to smooth to get a somewhat accurate signal.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private static final int ALTITUDE_SMOOTHING_FACTOR = 25;
|
||||
|
||||
/**
|
||||
* The number of speed reading to smooth to get a somewhat accurate signal.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private static final int SPEED_SMOOTHING_FACTOR = 25;
|
||||
|
||||
private static final String TAG = TrackStatisticsUpdater.class.getSimpleName();
|
||||
/**
|
||||
* Ignore any acceleration faster than this.
|
||||
* Will ignore any speeds that imply acceleration greater than 2g's
|
||||
* 2g = 19.6 m/s^2 = 0.0002 m/ms^2 = 0.02 m/(m*ms)
|
||||
*/
|
||||
private static final double MAX_ACCELERATION = 0.02;
|
||||
private static final double SPEED_MAX_ACCELERATION = 2 * 9.81;
|
||||
|
||||
private final TrackStatistics trackStatistics;
|
||||
|
||||
private final AltitudeRingBuffer altitudeBuffer;
|
||||
private final SpeedRingBuffer speedBuffer;
|
||||
private float averageHeartRateBPM;
|
||||
private Duration totalHeartRateDuration = Duration.ZERO;
|
||||
|
||||
@@ -86,8 +70,6 @@ public class TrackStatisticsUpdater {
|
||||
this.trackStatistics = trackStatistics;
|
||||
this.currentSegment = new TrackStatistics();
|
||||
|
||||
altitudeBuffer = new AltitudeRingBuffer(ALTITUDE_SMOOTHING_FACTOR);
|
||||
speedBuffer = new SpeedRingBuffer(SPEED_SMOOTHING_FACTOR);
|
||||
resetAverageHeartRate();
|
||||
}
|
||||
|
||||
@@ -95,9 +77,6 @@ public class TrackStatisticsUpdater {
|
||||
this.currentSegment = new TrackStatistics(toCopy.currentSegment);
|
||||
this.trackStatistics = new TrackStatistics(toCopy.trackStatistics);
|
||||
|
||||
this.altitudeBuffer = new AltitudeRingBuffer(toCopy.altitudeBuffer);
|
||||
this.speedBuffer = new SpeedRingBuffer(toCopy.speedBuffer);
|
||||
|
||||
this.lastTrackPoint = toCopy.lastTrackPoint;
|
||||
resetAverageHeartRate();
|
||||
}
|
||||
@@ -140,11 +119,7 @@ public class TrackStatisticsUpdater {
|
||||
|
||||
//Update absolute (GPS-based) altitude
|
||||
if (trackPoint.hasAltitude()) {
|
||||
// Update altitude using the smoothed average
|
||||
altitudeBuffer.setNext(trackPoint.getAltitude());
|
||||
Altitude newAverage = altitudeBuffer.getAverage();
|
||||
|
||||
currentSegment.updateAltitudeExtremities(newAverage);
|
||||
currentSegment.updateAltitudeExtremities(trackPoint.getAltitude());
|
||||
}
|
||||
|
||||
// Update heart rate
|
||||
@@ -178,11 +153,8 @@ public class TrackStatisticsUpdater {
|
||||
|
||||
// Update max speed
|
||||
updateSpeed(trackPoint, lastTrackPoint);
|
||||
} else {
|
||||
speedBuffer.reset();
|
||||
}
|
||||
|
||||
|
||||
if (trackPoint.isSegmentEnd()) {
|
||||
reset(trackPoint);
|
||||
return;
|
||||
@@ -198,8 +170,6 @@ public class TrackStatisticsUpdater {
|
||||
currentSegment.reset(trackPoint.getTime());
|
||||
|
||||
lastTrackPoint = null;
|
||||
altitudeBuffer.reset();
|
||||
speedBuffer.reset();
|
||||
resetAverageHeartRate();
|
||||
}
|
||||
|
||||
@@ -208,30 +178,14 @@ public class TrackStatisticsUpdater {
|
||||
totalHeartRateDuration = Duration.ZERO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the smoothed altitude over several readings.
|
||||
* The altitude readings is noisy so the smoothed altitude is better than the raw altitude for many tasks.
|
||||
*/
|
||||
public Altitude getSmoothedAltitude() {
|
||||
return altitudeBuffer.getAverage();
|
||||
}
|
||||
|
||||
public Speed getSmoothedSpeed() {
|
||||
return speedBuffer.getAverage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a speed reading while assuming the user is moving.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private void updateSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
|
||||
if (!trackPoint.isMoving()) {
|
||||
speedBuffer.reset();
|
||||
} else if (isValidSpeed(trackPoint, lastTrackPoint)) {
|
||||
speedBuffer.setNext(trackPoint.getSpeed());
|
||||
Speed average = speedBuffer.getAverage();
|
||||
if (average.greaterThan(currentSegment.getMaxSpeed())) {
|
||||
currentSegment.setMaxSpeed(average);
|
||||
if (isValidSpeed(trackPoint, lastTrackPoint)) {
|
||||
Speed currentSpeed = trackPoint.getSpeed();
|
||||
if (currentSpeed.greaterThan(currentSegment.getMaxSpeed())) {
|
||||
currentSegment.setMaxSpeed(currentSpeed);
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Invalid speed. speed: " + trackPoint.getSpeed() + " lastLocationSpeed: " + lastTrackPoint.getSpeed());
|
||||
@@ -239,30 +193,13 @@ public class TrackStatisticsUpdater {
|
||||
}
|
||||
|
||||
private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
|
||||
// There are a lot of noisy speed readings. Do the cheapest checks first, most expensive last.
|
||||
if (trackPoint.getSpeed().isZero()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g.
|
||||
Duration timeDifference = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
|
||||
Speed maxAcceleration = Speed.of(MAX_ACCELERATION * timeDifference.toMillis());
|
||||
{
|
||||
// See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g.
|
||||
Speed speedDifference = Speed.absDiff(lastTrackPoint.getSpeed(), trackPoint.getSpeed());
|
||||
if (speedDifference.greaterThan(maxAcceleration)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Speed maxSpeedDifference = Speed.of(Distance.of(SPEED_MAX_ACCELERATION), Duration.ofMillis(1000))
|
||||
.mul(timeDifference.toMillis() / UnitConversions.S_TO_MS);
|
||||
|
||||
// Only check if the speed buffer is full. Check that the speed is less than 10X the smoothed average and the speed difference doesn't imply 2g acceleration.
|
||||
if (speedBuffer.isFull()) {
|
||||
Speed average = speedBuffer.getAverage();
|
||||
Speed speedDifference = Speed.absDiff(average, trackPoint.getSpeed());
|
||||
|
||||
return trackPoint.getSpeed().lessThan(average.mul(10)) && speedDifference.lessThan(maxAcceleration);
|
||||
}
|
||||
|
||||
return true;
|
||||
Speed speedDifference = Speed.absDiff(lastTrackPoint.getSpeed(), trackPoint.getSpeed());
|
||||
return speedDifference.lessThan(maxSpeedDifference);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -107,17 +107,17 @@ public class IntervalsFragment extends Fragment {
|
||||
// TODO handle empty view: before we did viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
|
||||
viewBinding.intervalList.setAdapter(adapter);
|
||||
|
||||
final DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||
.setDecimalCount(0)
|
||||
.setUnit(unitSystem)
|
||||
.build(getContext());
|
||||
|
||||
intervalsAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, IntervalStatisticsModel.IntervalOption.values()) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
TextView v = (TextView) super.getView(position, convertView, parent);
|
||||
|
||||
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||
.setDecimalCount(0)
|
||||
.setUnit(unitSystem)
|
||||
.build(getContext());
|
||||
|
||||
IntervalStatisticsModel.IntervalOption option = getItem(position);
|
||||
String stringValue = formatter.formatDistance(option.getDistance(unitSystem));
|
||||
v.setText(stringValue);
|
||||
|
||||
@@ -85,12 +85,6 @@ public class ExportUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isExportFileExists(UUID uuuid, String trackFileFormatExtension, List<String> filesName) {
|
||||
String word = uuuid.toString().substring(0, 8) + ".*\\." + trackFileFormatExtension;
|
||||
Pattern pattern = Pattern.compile(word);
|
||||
return filesName.stream().anyMatch(w -> pattern.matcher(w).matches());
|
||||
}
|
||||
|
||||
public static List<String> getAllFiles(Context context, Uri directoryUri) {
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
@@ -173,7 +175,12 @@ public class IntentDashboardUtils {
|
||||
Log.i(TAG, "Starting dashboard activity with generic intent (package=" + targetPackage + ", class=" + targetClass + ")");
|
||||
}
|
||||
|
||||
context.startActivity(intent);
|
||||
try {
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "Dashboard not installed; cannot start it.");
|
||||
Toast.makeText(context, R.string.show_on_dashboard_not_installed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,14 +15,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
--><resources>
|
||||
<string name="about_preference_title">O OpenTracks</string>
|
||||
<string name="about_description"><i> OpenTracks </i> je sportovní sledovací aplikace, která zcela respektuje vaše soukromí.</string>
|
||||
<string name="about_description"><i> OpenTracks </i> je aplikace pro záznam vašich sportů, která zcela respektuje vaše soukromí.</string>
|
||||
<string name="about_url">Webová stránka projektu: <a href="%1$s">%1$s</a></string>
|
||||
<string name="about_version_name">Název verze: %1$s</string>
|
||||
<string name="about_version_code">Kód verze: %1$d</string>
|
||||
<string name="about_history">OpenTracks je fork aplikace <a href="https://code.google.com/archive/p/mytracks/"> <i> MyTracks Google </i> </a> (v roce 2016 ukončena).</string>
|
||||
<string name="about_history">OpenTracks je fork aplikace <a href="https://code.google.com/archive/p/mytracks/"> <i> MyTracks Google </i> </a> (ukončena v roce 2016).</string>
|
||||
<string name="about_libraries">Knihovny</string>
|
||||
<!-- Help -->
|
||||
<string name="help_feature_title">OpenTracks má následující funkce:</string>
|
||||
<string name="help_feature_title">OpenTracks dokáže:</string>
|
||||
<string name="help_feature_content">* vytvářet GPS stopy
|
||||
\n* použít snímač srdeční frekvence (pouze Bluetooth LE)
|
||||
\n* hlasová oznámení (podle času nebo vzdálenosti)
|
||||
@@ -34,14 +34,14 @@ limitations under the License.
|
||||
\n a) (živé aktualizace) Nainstalujte <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Poskytuje online nebo offline mapy prostřednictvím <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
\n Ve skutečnosti je OSMDashboard samostatná aplikace, která má přístup (dočasně) pouze k datům stop, které se budou zobrazovat.
|
||||
\n b) Prostřednictvím „normální“ mapové aplikace (např. <a href="http://osmand.net/">OsmAnd</a> a <a href="https://maps.me/">MAPS.ME</a>). Tyto aplikace musí podporovat formát souboru KMZ (pouze umístění a časová razítka). Ve skutečnosti musí aplikace podporovat <i>ACTION_VIEW</i> pomocí MIME <i>application/vnd.google-earth.kmz</i>. </string>
|
||||
<string name="help_storage_pictures_title">Jak ukládá OpenTracks obrázky pořízené jako značky?</string>
|
||||
<string name="help_storage_pictures_title">Jak ukládá OpenTracks obrázky pořízené jako značky na cestě\?</string>
|
||||
<string name="help_storage_pictures_content">Jsou uloženy v interním úložišti aplikace (tj. přístupné pouze v rámci OpenTracks).</string>
|
||||
<string name="help_track_export_title">Jak mohu exportovat/sdílet zaznamenané stopy\?</string>
|
||||
<string name="help_track_export_content"> Trasy lze exportovat z Nastavení jako KMZ, KML nebo GPX. KMZ a KML obsahuje všechna data, GPX pouze umístění. Trasy sdílené pomocí <i>ikony sdílení</i> jsou sdíleny jako KMZ. </string>
|
||||
<string name="help_track_export_content"> Trasy lze exportovat z Nastavení jako KMZ, KML nebo GPX. KMZ a KML obsahuje všechna data, GPX pouze polohu. Trasy sdílené pomocí <i>ikony sdílení</i> jsou sdíleny jako KMZ. </string>
|
||||
<string name="help_data_google_play_services_title">Vyžaduje/používá OpenTracks službu Google Play\?</string>
|
||||
<string name="help_recording_title">Jak OpenTracks zaznamenává data?</string>
|
||||
<string name="help_recording_content">OpenTracks používá GPS senzor zařízení k nepřetržitému zaznamenávání polohy. Pokud zařízení GPS hlásí nepřesná data (např. polohu, rychlost, nadmořskou výšku), OpenTracks s tím nemůže nic udělat.</string>
|
||||
<string name="help_data_storage_title">Jak OpenTracks ukládá data senzorů?</string>
|
||||
<string name="help_data_storage_title">Jak OpenTracks ukládá data ze senzorů\?</string>
|
||||
<string name="help_data_storage_content"> Data senzoru se ukládají na zaznamenané místo. Pokud jde o snímač srdeční frekvence, znamená to, že na každé místo je uloženo pouze <i> jedno </i> měření srdeční frekvence. Pokud stojíte v klidu nebo jste uvnitř, nebudou zaznamenána žádná data ze senzorů (ale budou zobrazena). </string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks nezaznamenává místa?</string>
|
||||
<string name="help_gps_troubleshooting_content"> Co můžete udělat:
|
||||
|
||||
@@ -581,4 +581,10 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<string name="select_show_on_map_behavior">Sélectionner le comportement \"Montrer sur la carte\"</string>
|
||||
<string name="settings_public_api_dashboard_enabled_title">Transfert automatique des données</string>
|
||||
<string name="settings_public_api_dashboard_enabled_summary_on">Une application qui a commencé un enregistrement peut aussi accéder aux données enregistrées.</string>
|
||||
<string name="settings_announcements_average_speed_pace">Allure/vitesse moyenne</string>
|
||||
<string name="settings_announcements_lap_speed_pace">Allure/vitesse au tour</string>
|
||||
<string name="settings_announcements_average_heart_rate">Moyenne de fréquence cardiaque</string>
|
||||
<string name="settings_announcements_lap_heart_rate">Fréquence cardiaque au tour</string>
|
||||
<string name="image_discard">Ignorer</string>
|
||||
<string name="track_discarding">Ignorer la trace</string>
|
||||
</resources>
|
||||
@@ -206,10 +206,10 @@ limitations under the License.
|
||||
\n%1$s</string>
|
||||
<string name="share_track_subject">Jeg vil dele et spor med deg</string>
|
||||
<!-- Stats -->
|
||||
<string name="stats_average_moving_pace">Gjennomsnittlig forflytningstempo</string>
|
||||
<string name="stats_average_moving_speed">Gjennomsnittlig forflytningshastighet</string>
|
||||
<string name="stats_average_pace">Gjennomsnittlig tempo</string>
|
||||
<string name="stats_average_speed">Gjennomsnittlig hastighet</string>
|
||||
<string name="stats_average_moving_pace">Gj. forflytningstempo</string>
|
||||
<string name="stats_average_moving_speed">Gj. forflytningshastighet</string>
|
||||
<string name="stats_average_pace">Gj. tempo</string>
|
||||
<string name="stats_average_speed">Gj. hastighet</string>
|
||||
<string name="stats_altitude">Høyde</string>
|
||||
<string name="stats_fastest_pace">Raskeste tempo</string>
|
||||
<string name="stats_gain">Total stigning</string>
|
||||
@@ -449,11 +449,11 @@ limitations under the License.
|
||||
<string name="generic_open_track">Åpne spor</string>
|
||||
<string name="import_progress_summary_exists_msg">Finnes allerede</string>
|
||||
<string name="import_progress_summary_errors_msg">Feil</string>
|
||||
<string name="sensor_state_cadence_avg">Gjennomsnittlig kadens</string>
|
||||
<string name="sensor_state_cadence_avg">Gj. kadens</string>
|
||||
<string name="sensor_state_cadence_max">Maks kadens</string>
|
||||
<string name="sensor_state_heart_rate_avg">Gjennomsnittlig puls</string>
|
||||
<string name="sensor_state_heart_rate_avg">Gj. puls</string>
|
||||
<string name="sensor_state_heart_rate_max">Maks puls</string>
|
||||
<string name="sensor_state_power_avg">Gjennomsnittlig kraft</string>
|
||||
<string name="sensor_state_power_avg">Gj. kraft</string>
|
||||
<string name="settings_sensor_wheel_circumference">Hjulomkrets (mm)</string>
|
||||
<string name="track_distance_notification">Distanse: %1$s</string>
|
||||
<string name="track_speed_notification">Hastighet: %1$s</string>
|
||||
@@ -575,4 +575,28 @@ limitations under the License.
|
||||
<string name="show_on_map_format_ask">Spør</string>
|
||||
<string name="show_on_dashboard">OpenTracks Dashboard API</string>
|
||||
<string name="always">Alltid</string>
|
||||
<string name="settings_announcements_average_heart_rate">Gjennomsnittlig puls</string>
|
||||
<string name="settings_announcements_lap_heart_rate">Runde-puls</string>
|
||||
<string name="settings_announcements_average_speed_pace">Gjennomsnittlig hastighet/tempo</string>
|
||||
<string name="image_discard">Forkast</string>
|
||||
<string name="settings_announcements_lap_speed_pace">Runde-hastighet/tempo</string>
|
||||
<string name="description_pace_nautical">Tempo (min/NM)</string>
|
||||
<string name="description_speed_nautical">Hastighet (knop)</string>
|
||||
<string name="settings_stats_units_nautical">Nautisk (NM, ft)</string>
|
||||
<string name="track_discarding">Forkaster spor</string>
|
||||
<string name="unit_nautical_mile">NM</string>
|
||||
<string name="unit_knots">kn</string>
|
||||
<string name="unit_minute_per_nautical_mile">min/NM</string>
|
||||
<string name="value_integer_knots">%1$d knop</string>
|
||||
<string name="value_float_knots">%1$.1f knop</string>
|
||||
<string name="value_float_knots_recommended">%1$.1f knop (anbefales)</string>
|
||||
<string name="voice_per_nautical_mile">per nautisk mil</string>
|
||||
<plurals name="voiceSpeedMKnots">
|
||||
<item quantity="one">1 knop</item>
|
||||
<item quantity="other">%1$.1f knop</item>
|
||||
</plurals>
|
||||
<plurals name="voiceDistanceNauticalMiles">
|
||||
<item quantity="one">1 nautisk mil</item>
|
||||
<item quantity="other">%1$.2f nautiske mil</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -462,7 +462,7 @@ limitations under the License.
|
||||
<string name="permission_gps_failed">OpenTracks GPS kullanmak için izin gerektirir.</string>
|
||||
<string name="import_progress_summary_ok_msg">Dosyalar içe aktarıldı</string>
|
||||
<string name="settings_defaults_title">Varsayılan Birimler ve Etkinlik</string>
|
||||
<string name="settings_ui_title">Kullanıcı arayüzü</string>
|
||||
<string name="settings_ui_title">Kullanıcı Arayüzü</string>
|
||||
<string name="settings_default_trackfileformat">Dosya biçimini dışa aktarma/paylaşma</string>
|
||||
<string name="settings_export_filename_title">Dosya adının biçimi</string>
|
||||
<string name="settings_import_export_title">İçe/Dışa Aktar</string>
|
||||
@@ -477,7 +477,7 @@ limitations under the License.
|
||||
<string name="settings_recording_keep_screen_on_while_recording_summary">Kayıt esnasında ekranı açık tutar.</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_summary">Kayıt esnasında tam ekranda sunar.</string>
|
||||
<string name="settings_recording_idle_speed_title">Boşta hız eşiği</string>
|
||||
<string name="settings_night_mode_title">Kullanıcı Arayüzü Teması</string>
|
||||
<string name="settings_night_mode_title">Kullanıcı arayüzü teması</string>
|
||||
<string name="settings_night_mode_option_system">Sistem</string>
|
||||
<string name="settings_night_mode_option_night">Gece</string>
|
||||
<string name="custom_layout_select_layout">Dizilim seçin</string>
|
||||
@@ -487,8 +487,8 @@ limitations under the License.
|
||||
<string name="settings_layout_reset_done">Tüm dizilim ayarları varsayılan değerlere döndürüldü</string>
|
||||
<string name="settings_public_api_enabled_title">Genel API</string>
|
||||
<string name="settings_public_api_enabled_summary_on">Diğer yüklü uygulamalar kayıtları başlatabilir veya durdurabilir.</string>
|
||||
<string name="settings_import_section">İçe aktar</string>
|
||||
<string name="settings_export_section">Dışa aktar</string>
|
||||
<string name="settings_import_section">İçe Aktarma</string>
|
||||
<string name="settings_export_section">Dışa Aktarma</string>
|
||||
<string name="settings_prevent_reimport_tracks_title">Parçaların yeniden içe aktarılmasını önleme</string>
|
||||
<string name="stats_coordinates">Enlem/Boylam</string>
|
||||
<string name="stats_sensors_power">Güç</string>
|
||||
@@ -571,4 +571,23 @@ limitations under the License.
|
||||
<string name="report_bug">Hata bildir</string>
|
||||
<string name="error_crash_title">%1$s çöktü</string>
|
||||
<string name="select_show_on_map_behavior">\"Haritada göster\" eylemini seçin</string>
|
||||
<string name="settings_announcements_average_heart_rate">Ortalama kalp atış hızı</string>
|
||||
<string name="description_pace_nautical">Tempo (dk/NM)</string>
|
||||
<string name="description_speed_nautical">Hız (düğüm)</string>
|
||||
<string name="settings_announcements_average_speed_pace">Ortalama hız/tempo</string>
|
||||
<string name="settings_announcements_lap_speed_pace">Tur hızı/temposu</string>
|
||||
<string name="settings_stats_units_nautical">Deniz (NM, ft)</string>
|
||||
<string name="unit_minute_per_nautical_mile">dk/NM</string>
|
||||
<string name="value_integer_knots">%1$d düğüm</string>
|
||||
<string name="value_float_knots">%1$.1f düğüm</string>
|
||||
<string name="value_float_knots_recommended">%1$.1f düğüm (önerilen)</string>
|
||||
<plurals name="voiceSpeedMKnots">
|
||||
<item quantity="one">1 düğüm</item>
|
||||
<item quantity="other">%1$.1f düğüm</item>
|
||||
</plurals>
|
||||
<string name="voice_per_nautical_mile">deniz mili başına</string>
|
||||
<plurals name="voiceDistanceNauticalMiles">
|
||||
<item quantity="one">1 deniz mili</item>
|
||||
<item quantity="other">%1$.2f deniz mili</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -204,15 +204,20 @@
|
||||
|
||||
<string name="stats_rate_key" translatable="false">statsRate</string>
|
||||
<string name="stats_rate_default" translatable="false">@string/stats_rate_speed_or_pace_default</string>
|
||||
<string-array name="stats_rate_metric_options">
|
||||
<item>@string/description_default_speed_or_pace</item>
|
||||
<item>@string/description_speed_metric</item>
|
||||
<item>@string/description_pace_metric</item>
|
||||
</string-array>
|
||||
<string-array name="stats_rate_imperial_options">
|
||||
<item>@string/description_default_speed_or_pace</item>
|
||||
<item>@string/description_speed_imperial</item>
|
||||
<item>@string/description_pace_imperial</item>
|
||||
</string-array>
|
||||
<string-array name="stats_rate_metric_options">
|
||||
<string-array name="stats_rate_nautical_options">
|
||||
<item>@string/description_default_speed_or_pace</item>
|
||||
<item>@string/description_speed_metric</item>
|
||||
<item>@string/description_pace_metric</item>
|
||||
<item>@string/description_speed_nautical</item>
|
||||
<item>@string/description_pace_imperial</item>
|
||||
</string-array>
|
||||
|
||||
<string name="stats_rate_speed_or_pace_default" translatable="false">ACTIVITY_TYPE</string>
|
||||
@@ -229,12 +234,15 @@
|
||||
<string-array name="stats_units_values">
|
||||
<item>@string/stats_units_metric</item>
|
||||
<item>@string/stats_units_imperial</item>
|
||||
<item>@string/stats_units_nautical</item>
|
||||
</string-array>
|
||||
<string name="stats_units_metric" translatable="false">METRIC</string>
|
||||
<string name="stats_units_imperial" translatable="false">IMPERIAL</string>
|
||||
<string name="stats_units_nautical" translatable="false">NAUTICAL</string>
|
||||
<string-array name="stats_units_options">
|
||||
<item>@string/settings_stats_units_metric</item>
|
||||
<item>@string/settings_stats_units_imperial</item>
|
||||
<item>@string/settings_stats_units_nautical</item>
|
||||
</string-array>
|
||||
|
||||
<string name="track_name_key" translatable="false">trackName</string>
|
||||
|
||||
@@ -187,6 +187,7 @@ limitations under the License.
|
||||
<string name="description_min_altitude">Min elevation: %1$d m (%2$d ft)</string>
|
||||
<string name="description_moving_time">Moving time: %1$s</string>
|
||||
<string name="description_pace_imperial">Pace (min/mi)</string>
|
||||
<string name="description_pace_nautical">Pace (min/NM)</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>
|
||||
@@ -195,6 +196,7 @@ limitations under the License.
|
||||
<string name="description_sensor_power">Power (W)</string>
|
||||
<string name="description_default_speed_or_pace">By activity type</string>
|
||||
<string name="description_speed_imperial">Speed (mi/hr)</string>
|
||||
<string name="description_speed_nautical">Speed (knots)</string>
|
||||
<string name="description_speed_metric">Speed (km/hr)</string>
|
||||
<string name="description_time">Time</string>
|
||||
<string name="description_total_distance">Total distance: %1$.2f km (%2$.1f mi)</string>
|
||||
@@ -435,6 +437,7 @@ limitations under the License.
|
||||
<string name="settings_stats_rate_title">Preferred rate</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metric (km, m)</string>
|
||||
<string name="settings_stats_units_nautical">Nautical (NM, ft)</string>
|
||||
<string name="settings_stats_units_title">Preferred units</string>
|
||||
|
||||
<!-- Settings public api -->
|
||||
@@ -513,8 +516,11 @@ limitations under the License.
|
||||
<string name="unit_meter">m</string>
|
||||
<string name="unit_mile">mi</string>
|
||||
<string name="unit_mile_per_hour">mph</string>
|
||||
<string name="unit_nautical_mile">NM</string>
|
||||
<string name="unit_knots">kn</string>
|
||||
<string name="unit_minute_per_kilometer">min/km</string>
|
||||
<string name="unit_minute_per_mile">min/mi</string>
|
||||
<string name="unit_minute_per_nautical_mile">min/NM</string>
|
||||
<!-- Location Coordinates -->
|
||||
<string name="location_coordinate">%1$s°</string>
|
||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||
@@ -529,8 +535,11 @@ limitations under the License.
|
||||
<string name="value_integer_meter_poor_gps">%1$d m (poor GPS)</string>
|
||||
<string name="value_integer_meter_recommended">%1$d m (recommended)</string>
|
||||
<string name="value_integer_mile_hour">%1$d mi/h</string>
|
||||
<string name="value_integer_knots">%1$d knots</string>
|
||||
<string name="value_float_mile_hour">%1$.1f mi/h</string>
|
||||
<string name="value_float_knots">%1$.1f knots</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (recommended)</string>
|
||||
<string name="value_float_knots_recommended">%1$.1f knots (recommended)</string>
|
||||
<string name="value_integer_minute">%1$d min</string>
|
||||
<string name="value_integer_second">%1$d s</string>
|
||||
<string name="value_none">None</string>
|
||||
@@ -557,8 +566,13 @@ limitations under the License.
|
||||
<item quantity="one">1 mile per hour</item>
|
||||
<item quantity="other">%1$.1f miles per hour</item>
|
||||
</plurals>
|
||||
<plurals name="voiceSpeedMKnots">
|
||||
<item quantity="one">1 knot</item>
|
||||
<item quantity="other">%1$.1f knots</item>
|
||||
</plurals>
|
||||
<string name="voice_per_kilometer">per kilometer</string>
|
||||
<string name="voice_per_mile">per mile</string>
|
||||
<string name="voice_per_nautical_mile">per nautical mile</string>
|
||||
<string name="lap_time">Lap time</string>
|
||||
<string name="pace">Pace</string>
|
||||
<string name="lap_speed">Lap speed</string>
|
||||
@@ -572,6 +586,10 @@ limitations under the License.
|
||||
<item quantity="one">1 mile</item>
|
||||
<item quantity="other">%1$.2f miles</item>
|
||||
</plurals>
|
||||
<plurals name="voiceDistanceNauticalMiles">
|
||||
<item quantity="one">1 nautical mile</item>
|
||||
<item quantity="other">%1$.2f nautical miles</item>
|
||||
</plurals>
|
||||
<string name="average_heart_rate">Average heart rate</string>
|
||||
<string name="current_heart_rate">Current heart rate</string>
|
||||
<!-- Waypoint Type -->
|
||||
@@ -648,5 +666,6 @@ limitations under the License.
|
||||
<string name="select_show_on_map_behavior">Select \"show on map\" behavior</string>
|
||||
<string name="show_on_map_format_ask">Ask</string>
|
||||
<string name="show_on_dashboard">OpenTracks Dashboard API</string>
|
||||
<string name="show_on_dashboard_not_installed">No application supporting OpenTracks Dashboard API installed.</string>
|
||||
<string name="always">Always</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user