diff --git a/src/androidTest/java/de/dennisguse/opentracks/data/models/DistanceFormatterTest.java b/src/androidTest/java/de/dennisguse/opentracks/data/models/DistanceFormatterTest.java index 95eff8d34..7cbbcad47 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/data/models/DistanceFormatterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/data/models/DistanceFormatterTest.java @@ -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("0.05 NM", formatter.formatDistance(Distance.of(100))); + } } \ No newline at end of file diff --git a/src/androidTest/java/de/dennisguse/opentracks/data/models/SpeedFormatterTest.java b/src/androidTest/java/de/dennisguse/opentracks/data/models/SpeedFormatterTest.java index c9ce7ee1f..2a5df4026 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/data/models/SpeedFormatterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/data/models/SpeedFormatterTest.java @@ -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("knots", formatter.getSpeedParts(Speed.zero()).second); + } + @Test public void testFormatSpeed() { SpeedFormatter formatter = SpeedFormatter.Builder() diff --git a/src/main/java/de/dennisguse/opentracks/chart/ChartValueSeries.java b/src/main/java/de/dennisguse/opentracks/chart/ChartValueSeries.java index dcdd57df1..07bdc50d4 100644 --- a/src/main/java/de/dennisguse/opentracks/chart/ChartValueSeries.java +++ b/src/main/java/de/dennisguse/opentracks/chart/ChartValueSeries.java @@ -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); } diff --git a/src/main/java/de/dennisguse/opentracks/chart/ChartView.java b/src/main/java/de/dennisguse/opentracks/chart/ChartView.java index 3ed5e2188..2c1a4b3a9 100644 --- a/src/main/java/de/dennisguse/opentracks/chart/ChartView.java +++ b/src/main/java/de/dennisguse/opentracks/chart/ChartView.java @@ -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"); } diff --git a/src/main/java/de/dennisguse/opentracks/data/models/Distance.java b/src/main/java/de/dennisguse/opentracks/data/models/Distance.java index 6f14452fc..bcb80d971 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/Distance.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/Distance.java @@ -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: diff --git a/src/main/java/de/dennisguse/opentracks/data/models/DistanceFormatter.java b/src/main/java/de/dennisguse/opentracks/data/models/DistanceFormatter.java index 717858890..b22747619 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/DistanceFormatter.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/DistanceFormatter.java @@ -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"); } diff --git a/src/main/java/de/dennisguse/opentracks/data/models/Speed.java b/src/main/java/de/dennisguse/opentracks/data/models/Speed.java index f6decb183..0aaf47b80 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/Speed.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/Speed.java @@ -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"); } diff --git a/src/main/java/de/dennisguse/opentracks/data/models/SpeedFormatter.java b/src/main/java/de/dennisguse/opentracks/data/models/SpeedFormatter.java index e33975f11..61b473960 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/SpeedFormatter.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/SpeedFormatter.java @@ -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"); } diff --git a/src/main/java/de/dennisguse/opentracks/data/models/UnitConversions.java b/src/main/java/de/dennisguse/opentracks/data/models/UnitConversions.java index c9355fbf7..eee25ea63 100644 --- a/src/main/java/de/dennisguse/opentracks/data/models/UnitConversions.java +++ b/src/main/java/de/dennisguse/opentracks/data/models/UnitConversions.java @@ -53,6 +53,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 diff --git a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java index 861190804..d2a8767ef 100644 --- a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java +++ b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java @@ -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"); } diff --git a/src/main/java/de/dennisguse/opentracks/settings/DefaultsSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/DefaultsSettingsFragment.java index 66208a964..92b37fa69 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/DefaultsSettingsFragment.java +++ b/src/main/java/de/dennisguse/opentracks/settings/DefaultsSettingsFragment.java @@ -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: diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java index 0615259d0..6a102eea1 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -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"); } diff --git a/src/main/java/de/dennisguse/opentracks/settings/UnitSystem.java b/src/main/java/de/dennisguse/opentracks/settings/UnitSystem.java index 1c73de145..2cdeff1e6 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/UnitSystem.java +++ b/src/main/java/de/dennisguse/opentracks/settings/UnitSystem.java @@ -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; diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml index 2dd63380d..ab2cd1818 100644 --- a/src/main/res/values/settings.xml +++ b/src/main/res/values/settings.xml @@ -204,15 +204,20 @@ statsRate @string/stats_rate_speed_or_pace_default + + @string/description_default_speed_or_pace + @string/description_speed_metric + @string/description_pace_metric + @string/description_default_speed_or_pace @string/description_speed_imperial @string/description_pace_imperial - + @string/description_default_speed_or_pace - @string/description_speed_metric - @string/description_pace_metric + @string/description_speed_nautical + @string/description_pace_imperial ACTIVITY_TYPE @@ -229,12 +234,15 @@ @string/stats_units_metric @string/stats_units_imperial + @string/stats_units_nautical METRIC IMPERIAL + NAUTICAL @string/settings_stats_units_metric @string/settings_stats_units_imperial + @string/settings_stats_units_nautical trackName diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 42a6caa5d..5274ec704 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -187,6 +187,7 @@ limitations under the License. Min elevation: %1$d m (%2$d ft) Moving time: %1$s Pace (min/mi) + Pace (min/NM) Pace (min/km) Recorded: %1$s Speed (m/s) @@ -195,6 +196,7 @@ limitations under the License. Power (W) By activity type Speed (mi/hr) + Speed (knots) Speed (km/hr) Time Total distance: %1$.2f km (%2$.1f mi) @@ -435,6 +437,7 @@ limitations under the License. Preferred rate Imperial (mi, ft) Metric (km, m) + Nautical (NM, ft) Preferred units @@ -513,8 +516,11 @@ limitations under the License. m mi mph + NM + kn min/km min/mi + min/NM %1$s° %1$s, %2$s @@ -529,8 +535,11 @@ limitations under the License. %1$d m (poor GPS) %1$d m (recommended) %1$d mi/h + %1$d knots %1$.1f mi/h + %1$.1f knots %1$.1f mi/h (recommended) + %1$.1f knots (recommended) %1$d min %1$d s None @@ -557,8 +566,13 @@ limitations under the License. 1 mile per hour %1$.1f miles per hour + + 1 knot + %1$.1f knots + per kilometer per mile + per nautical mile Lap time Pace Lap speed @@ -572,6 +586,10 @@ limitations under the License. 1 mile %1$.2f miles + + 1 nautical mile + %1$.2f nautical miles + Average heart rate Current heart rate