mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-19 09:22:12 +00:00
refactoring
This commit is contained in:
1 parent
4795513344
commit
890bcd765a
169 files changed
+263
-256
No files matched your search
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user