refactoring

This commit is contained in:
vcoppe committed 2026-10-11 11:18:36 +02:00
1 parent 4795513344
commit 890bcd765a
169 files changed
+263 -256

No files matched your search

+75
View File
@@ -0,0 +1,75 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
celsiusToFahrenheit,
convertDistance,
convertDistanceToKilometers,
convertElevation,
convertTemperature,
convertVelocity,
distancePerHourToSecondsPerDistance,
kilometersToMiles,
kilometersToNauticalMiles,
metersToFeet,
milesToKilometers,
nauticalMilesToKilometers,
secondsToHHMMSS,
} from '$lib/units/conversions';
const close = (actual: number, expected: number, epsilon = 1e-9) =>
assert.ok(Math.abs(actual - expected) < epsilon, `${actual} is not ${expected}`);
describe('unit conversions', () => {
it('converts distances both ways', () => {
close(kilometersToMiles(10), 6.21371);
close(milesToKilometers(kilometersToMiles(42.195)), 42.195, 1e-3);
close(kilometersToNauticalMiles(1.852), 1.852 * 0.539957);
close(nauticalMilesToKilometers(kilometersToNauticalMiles(100)), 100, 0.01);
close(metersToFeet(100), 328.084);
close(celsiusToFahrenheit(0), 32);
close(celsiusToFahrenheit(100), 212);
close(celsiusToFahrenheit(-40), -40);
});
it('converts to the units that are asked for', () => {
assert.equal(convertDistance(5, 'metric'), 5);
close(convertDistance(5, 'imperial'), kilometersToMiles(5));
close(convertDistance(5, 'nautical'), kilometersToNauticalMiles(5));
assert.equal(convertDistanceToKilometers(5, 'metric'), 5);
close(convertDistanceToKilometers(5, 'imperial'), milesToKilometers(5));
close(convertDistanceToKilometers(5, 'nautical'), nauticalMilesToKilometers(5));
// elevations are in feet only with the imperial units
assert.equal(convertElevation(100, 'metric'), 100);
assert.equal(convertElevation(100, 'nautical'), 100);
close(convertElevation(100, 'imperial'), metersToFeet(100));
assert.equal(convertTemperature(20, 'celsius'), 20);
close(convertTemperature(20, 'fahrenheit'), 68);
});
it('converts speeds and paces', () => {
assert.equal(convertVelocity(12, 'speed', 'metric'), 12);
close(convertVelocity(12, 'speed', 'imperial'), kilometersToMiles(12));
// a pace is the time it takes to go one unit of distance
assert.equal(convertVelocity(12, 'pace', 'metric'), 300);
close(convertVelocity(12, 'pace', 'imperial'), 3600 / kilometersToMiles(12));
close(convertVelocity(12, 'pace', 'nautical'), 3600 / kilometersToNauticalMiles(12));
// no speed is no pace, not infinity
assert.equal(distancePerHourToSecondsPerDistance(0), 0);
assert.equal(convertVelocity(0, 'pace', 'metric'), 0);
});
it('formats durations, without the hours when there are none', () => {
assert.equal(secondsToHHMMSS(0), '00:00');
assert.equal(secondsToHHMMSS(5), '00:05');
assert.equal(secondsToHHMMSS(59), '00:59');
assert.equal(secondsToHHMMSS(60), '01:00');
assert.equal(secondsToHHMMSS(300), '05:00');
assert.equal(secondsToHHMMSS(3599), '59:59');
assert.equal(secondsToHHMMSS(3600), '01:00:00');
assert.equal(secondsToHHMMSS(3725), '01:02:05');
assert.equal(secondsToHHMMSS(36000 + 61), '10:01:01');
// the seconds are rounded, and never reach 60
assert.equal(secondsToHHMMSS(61.4), '01:01');
assert.equal(secondsToHHMMSS(119.9), '01:59');
});
});
+94
View File
@@ -0,0 +1,94 @@
// The conversions between units, with the units given: they do not depend on the settings (see
// `units.ts`, which converts to the units of the settings).
export type DistanceUnits = 'metric' | 'imperial' | 'nautical';
export type VelocityUnits = 'speed' | 'pace';
export type TemperatureUnits = 'celsius' | 'fahrenheit';
export function kilometersToMiles(value: number) {
return value * 0.621371;
}
export function milesToKilometers(value: number) {
return value * 1.60934;
}
export function metersToFeet(value: number) {
return value * 3.28084;
}
export function kilometersToNauticalMiles(value: number) {
return value * 0.539957;
}
export function nauticalMilesToKilometers(value: number) {
return value * 1.852;
}
export function celsiusToFahrenheit(value: number) {
return value * 1.8 + 32;
}
/** The pace (seconds per distance unit) of a speed (distance units per hour). 0 for no speed. */
export function distancePerHourToSecondsPerDistance(value: number) {
if (value === 0) {
return 0;
}
return 3600 / value;
}
/** A duration as `HH:MM:SS`, or `MM:SS` when it has no hours. */
export function secondsToHHMMSS(value: number) {
const hours = Math.floor(value / 3600);
const minutes = Math.floor(value / 60) % 60;
const seconds = Math.min(59, Math.round(value % 60));
return [hours, minutes, seconds]
.map((v) => (v < 10 ? '0' + v : v))
.filter((v, i) => v !== '00' || i > 0)
.join(':');
}
/** A distance in kilometers, in the given units. */
export function convertDistance(value: number, units: DistanceUnits) {
switch (units) {
case 'metric':
return value;
case 'imperial':
return kilometersToMiles(value);
case 'nautical':
return kilometersToNauticalMiles(value);
}
}
/** A distance in the given units, in kilometers. */
export function convertDistanceToKilometers(value: number, units: DistanceUnits) {
switch (units) {
case 'metric':
return value;
case 'imperial':
return milesToKilometers(value);
case 'nautical':
return nauticalMilesToKilometers(value);
}
}
/** An elevation in meters, in the units of the distances (nautical miles go with meters). */
export function convertElevation(value: number, units: DistanceUnits) {
return units === 'imperial' ? metersToFeet(value) : value;
}
/** A speed in km/h, in the given units: a speed or a pace, per kilometer, mile or nautical mile. */
export function convertVelocity(
value: number,
velocityUnits: VelocityUnits,
distanceUnits: DistanceUnits
) {
const speed = convertDistance(value, distanceUnits);
return velocityUnits === 'speed' ? speed : distancePerHourToSecondsPerDistance(speed);
}
/** A temperature in degrees Celsius, in the given units. */
export function convertTemperature(value: number, units: TemperatureUnits) {
return units === 'celsius' ? value : celsiusToFahrenheit(value);
}
+166
View File
@@ -0,0 +1,166 @@
import { settings } from '$lib/state/settings';
import { i18n } from '$lib/i18n/i18n.svelte';
import { get } from 'svelte/store';
import {
convertDistance,
convertDistanceToKilometers,
convertElevation,
convertTemperature,
convertVelocity,
secondsToHHMMSS,
} from '$lib/units/conversions';
export * from '$lib/units/conversions';
const { distanceUnits, velocityUnits, temperatureUnits } = settings;
// Get a string representation of the value with units
export function getDistanceWithUnits(value: number, convert: boolean = true) {
if (convert) {
return getConvertedDistance(value).toFixed(2) + ' ' + getDistanceUnits();
} else {
return value.toFixed(2) + ' ' + getDistanceUnits();
}
}
export function getVelocityWithUnits(value: number, convert: boolean = true) {
if (get(velocityUnits) === 'speed') {
if (convert) {
return getConvertedVelocity(value).toFixed(2) + ' ' + getVelocityUnits();
} else {
return value.toFixed(2) + ' ' + getVelocityUnits();
}
} else {
if (convert) {
return secondsToHHMMSS(getConvertedVelocity(value)) + ' ' + getVelocityUnits();
} else {
return secondsToHHMMSS(value) + ' ' + getVelocityUnits();
}
}
}
export function getElevationWithUnits(value: number, convert: boolean = true) {
if (convert) {
return getConvertedElevation(value).toFixed(0) + ' ' + getElevationUnits();
} else {
return value.toFixed(0) + ' ' + getElevationUnits();
}
}
export function getHeartRateWithUnits(value: number) {
return value.toFixed(0) + ' ' + getHeartRateUnits();
}
export function getCadenceWithUnits(value: number) {
return value.toFixed(0) + ' ' + getCadenceUnits();
}
export function getPowerWithUnits(value: number) {
return value.toFixed(0) + ' ' + getPowerUnits();
}
export function getTemperatureWithUnits(value: number, convert: boolean = true) {
if (convert) {
return getConvertedTemperature(value).toFixed(0) + ' ' + getTemperatureUnits();
} else {
return value.toFixed(0) + ' ' + getTemperatureUnits();
}
}
// Get the units
export function getDistanceUnits(targetDistanceUnits = get(distanceUnits)) {
switch (targetDistanceUnits) {
case 'metric':
return i18n._('units.kilometers');
case 'imperial':
return i18n._('units.miles');
case 'nautical':
return i18n._('units.nautical_miles');
}
}
export function getVelocityUnits(
targetVelocityUnits = get(velocityUnits),
targetDistanceUnits = get(distanceUnits)
) {
if (targetVelocityUnits === 'speed') {
switch (targetDistanceUnits) {
case 'metric':
return i18n._('units.kilometers_per_hour');
case 'imperial':
return i18n._('units.miles_per_hour');
case 'nautical':
return i18n._('units.knots');
}
} else {
switch (targetDistanceUnits) {
case 'metric':
return i18n._('units.minutes_per_kilometer');
case 'imperial':
return i18n._('units.minutes_per_mile');
case 'nautical':
return i18n._('units.minutes_per_nautical_mile');
}
}
}
export function getElevationUnits(targetDistanceUnits = get(distanceUnits)) {
switch (targetDistanceUnits) {
case 'metric':
return i18n._('units.meters');
case 'imperial':
return i18n._('units.feet');
case 'nautical':
// See https://github.com/gpxstudio/gpx.studio/pull/66#issuecomment-2306568997
return i18n._('units.meters');
}
}
export function getHeartRateUnits() {
return i18n._('units.heartrate');
}
export function getCadenceUnits() {
return i18n._('units.cadence');
}
export function getPowerUnits() {
return i18n._('units.power');
}
export function getTemperatureUnits() {
return get(temperatureUnits) === 'celsius'
? i18n._('units.celsius')
: i18n._('units.fahrenheit');
}
// Convert only the value, to the units of the settings by default
export function getConvertedDistance(value: number, targetDistanceUnits = get(distanceUnits)) {
return convertDistance(value, targetDistanceUnits);
}
export function getConvertedDistanceToKilometers(
value: number,
sourceDistanceUnits = get(distanceUnits)
) {
return convertDistanceToKilometers(value, sourceDistanceUnits);
}
export function getConvertedElevation(value: number, targetDistanceUnits = get(distanceUnits)) {
return convertElevation(value, targetDistanceUnits);
}
export function getConvertedVelocity(
value: number,
targetVelocityUnits = get(velocityUnits),
targetDistanceUnits = get(distanceUnits)
) {
return convertVelocity(value, targetVelocityUnits, targetDistanceUnits);
}
export function getConvertedTemperature(
value: number,
targetTemperatureUnits = get(temperatureUnits)
) {
return convertTemperature(value, targetTemperatureUnits);
}