commit 6d05ae8af95954bd2ec4af0fd7c5966e87a0997e
parent 423edb9a929979a01cd28a35fe9ca023b59d5fce
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date: Sat, 21 Jun 2025 15:55:22 -0700
Convert most utils to TypeScript
Diffstat:
14 files changed, 1114 insertions(+), 963 deletions(-)
diff --git a/src/utils/format.js b/src/utils/format.js
@@ -1,99 +0,0 @@
-/**
- * Format a number as a string
- * @param {Number} value The number
- * @param {Number} minPadding The minimum number of digits to show before the decimal point
- * @param {Number} maxDigits The maximum number of digits to show after the decimal point
- * @param {Boolean} extraDigits Whether to show extra zeros after the decimal point
- * @returns {String} The formatted value
- */
-export function formatNumber(value, minPadding = 0, maxDigits = 2, extraDigits = true) {
- // Initialize result
- let result = '';
-
- // Remove sign
- const negative = value < 0;
- const fixedValue = Math.abs(value);
-
- // Address edge cases
- if (Number.isNaN(fixedValue)) {
- return 'NaN';
- }
- if (fixedValue === Infinity) {
- return negative ? '-Infinity' : 'Infinity';
- }
-
- // Convert number to string
- if (extraDigits) {
- result = fixedValue.toFixed(maxDigits);
- } else {
- const power = 10 ** maxDigits;
- result = (Math.round((fixedValue + Number.EPSILON) * power) / power).toString();
- }
-
- // Add padding
- const currentPadding = result.split('.')[0].length;
- result = result.padStart(result.length - currentPadding + minPadding, '0');
-
- // Add negative sign
- if (negative) {
- result = `-${result}`;
- }
-
- // Return result
- return result;
-}
-
-/**
- * Format a duration as a string
- * @param {Number} value The duration (in seconds)
- * @param {Number} minPadding The minimum number of digits to show before the decimal point
- * @param {Number} maxDigits The maximum number of digits to show after the decimal point
- * @param {Boolean} extraDigits Whether to show extra zeros after the decimal point
- * @returns {String} The formatted value
- */
-export function formatDuration(value, minPadding = 6, maxDigits = 2, extraDigits = true) {
- // Check if value is NaN
- if (Number.isNaN(value)) {
- return 'NaN';
- }
-
- // Initialize result
- let result = '';
-
- // Check value sign
- if (value < 0) {
- result += '-';
- }
-
- // Check if value is valid
- if (Math.abs(value) === Infinity) {
- return `${result}Infinity`;
- }
-
- // Validate padding
- let fixedPadding = Math.min(minPadding, 6);
-
- // Prevent rounding errors
- const fixedValue = parseFloat(Math.abs(value).toFixed(maxDigits));
-
- // Calculate parts
- const hours = Math.floor(fixedValue / 3600);
- const minutes = Math.floor((fixedValue % 3600) / 60);
- const seconds = fixedValue % 60;
-
- // Format parts
- if (hours !== 0 || fixedPadding >= 5) {
- result += hours.toString().padStart(fixedPadding - 4, '0');
- result += ':';
- fixedPadding = 4;
- }
- if (minutes !== 0 || fixedPadding >= 3) {
- result += minutes.toString().padStart(fixedPadding - 2, '0');
- result += ':';
- fixedPadding = 2;
- }
- result += formatNumber(seconds, fixedPadding, maxDigits, extraDigits);
-
- // Return result
- return result;
-}
diff --git a/src/utils/format.ts b/src/utils/format.ts
@@ -0,0 +1,102 @@
+/**
+ * Format a number as a string
+ * @param {number} value The number
+ * @param {number} minPadding The minimum number of digits to show before the decimal point
+ * @param {number} maxDigits The maximum number of digits to show after the decimal point
+ * @param {boolean} extraDigits Whether to show extra zeros after the decimal point
+ * @returns {string} The formatted value
+ */
+export function formatNumber(value: number, minPadding: number = 0, maxDigits: number = 2,
+ extraDigits: boolean = true): string {
+
+ // Initialize result
+ let result = '';
+
+ // Remove sign
+ const negative = value < 0;
+ const fixedValue = Math.abs(value);
+
+ // Address edge cases
+ if (Number.isNaN(fixedValue)) {
+ return 'NaN';
+ }
+ if (fixedValue === Infinity) {
+ return negative ? '-Infinity' : 'Infinity';
+ }
+
+ // Convert number to string
+ if (extraDigits) {
+ result = fixedValue.toFixed(maxDigits);
+ } else {
+ const power = 10 ** maxDigits;
+ result = (Math.round((fixedValue + Number.EPSILON) * power) / power).toString();
+ }
+
+ // Add padding
+ const currentPadding = result.split('.')[0].length;
+ result = result.padStart(result.length - currentPadding + minPadding, '0');
+
+ // Add negative sign
+ if (negative) {
+ result = `-${result}`;
+ }
+
+ // Return result
+ return result;
+}
+
+/**
+ * Format a duration as a string
+ * @param {number} value The duration (in seconds)
+ * @param {number} minPadding The minimum number of digits to show before the decimal point
+ * @param {number} maxDigits The maximum number of digits to show after the decimal point
+ * @param {boolean} extraDigits Whether to show extra zeros after the decimal point
+ * @returns {string} The formatted value
+ */
+export function formatDuration(value: number, minPadding: number = 6, maxDigits: number = 2,
+ extraDigits: boolean = true): string {
+ // Check if value is NaN
+ if (Number.isNaN(value)) {
+ return 'NaN';
+ }
+
+ // Initialize result
+ let result = '';
+
+ // Check value sign
+ if (value < 0) {
+ result += '-';
+ }
+
+ // Check if value is valid
+ if (Math.abs(value) === Infinity) {
+ return `${result}Infinity`;
+ }
+
+ // Validate padding
+ let fixedPadding = Math.min(minPadding, 6);
+
+ // Prevent rounding errors
+ const fixedValue = parseFloat(Math.abs(value).toFixed(maxDigits));
+
+ // Calculate parts
+ const hours = Math.floor(fixedValue / 3600);
+ const minutes = Math.floor((fixedValue % 3600) / 60);
+ const seconds = fixedValue % 60;
+
+ // Format parts
+ if (hours !== 0 || fixedPadding >= 5) {
+ result += hours.toString().padStart(fixedPadding - 4, '0');
+ result += ':';
+ fixedPadding = 4;
+ }
+ if (minutes !== 0 || fixedPadding >= 3) {
+ result += minutes.toString().padStart(fixedPadding - 2, '0');
+ result += ':';
+ fixedPadding = 2;
+ }
+ result += formatNumber(seconds, fixedPadding, maxDigits, extraDigits);
+
+ // Return result
+ return result;
+}
diff --git a/src/utils/misc.js b/src/utils/misc.js
@@ -1,18 +0,0 @@
-/**
- * Create a deep copy of an object
- * @param {Object} value The object to copy
- * @returns {Object} The copied object
- */
-export function deepCopy(value) {
- return JSON.parse(JSON.stringify(value));
-}
-
-/**
- * Test whether two objects are deeply equal
- * @param {Object} value1 The first object
- * @param {Object} value2 The second object
- * @returns {Boolean} Whether the two objects are equal
- */
-export function deepEqual(value1, value2) {
- return JSON.stringify(value1) === JSON.stringify(value2);
-}
diff --git a/src/utils/misc.ts b/src/utils/misc.ts
@@ -0,0 +1,18 @@
+/**
+ * Create a deep copy of an object
+ * @param {object} value The object to copy
+ * @returns {object} The copied object
+ */
+export function deepCopy(value: object): object {
+ return JSON.parse(JSON.stringify(value));
+}
+
+/**
+ * Test whether two objects are deeply equal
+ * @param {object} value1 The first object
+ * @param {object} value2 The second object
+ * @returns {boolean} Whether the two objects are equal
+ */
+export function deepEqual(value1: object, value2: object): boolean {
+ return JSON.stringify(value1) === JSON.stringify(value2);
+}
diff --git a/src/utils/paces.js b/src/utils/paces.js
@@ -1,21 +0,0 @@
-/**
- * Calculate time from a distance and input pace
- * @param {Number} d1 The input pace distance (in any unit)
- * @param {Number} t1 The input pace time (in seconds)
- * @param {Number} d2 The output distance (in the same unit as d1)
- * @returns {Number} The output time (in seconds)
- */
-export function calculateTime(d1, t1, d2) {
- return (t1 / d1) * d2
-}
-
-/**
- * Calculate distance from a time and input pace
- * @param {Number} t1 The input pace time (in seconds)
- * @param {Number} d1 The input pace distance (in any unit)
- * @param {Number} t2 The output time (in seconds)
- * @returns {Number} The output distance (in the same unit as d1)
- */
-export function calculateDistance(t1, d1, t2) {
- return (d1 / t1) * t2
-}
diff --git a/src/utils/paces.ts b/src/utils/paces.ts
@@ -0,0 +1,21 @@
+/**
+ * Calculate time from a distance and input pace
+ * @param {number} d1 The input pace distance (in any unit)
+ * @param {number} t1 The input pace time (in seconds)
+ * @param {number} d2 The output distance (in the same unit as d1)
+ * @returns {number} The output time (in seconds)
+ */
+export function calculateTime(d1: number, t1: number, d2: number): number {
+ return (t1 / d1) * d2
+}
+
+/**
+ * Calculate distance from a time and input pace
+ * @param {number} t1 The input pace time (in seconds)
+ * @param {number} d1 The input pace distance (in any unit)
+ * @param {number} t2 The output time (in seconds)
+ * @returns {number} The output distance (in the same unit as d1)
+ */
+export function calculateDistance(t1: number, d1: number, t2: number): number {
+ return (d1 / t1) * t2
+}
diff --git a/src/utils/races.js b/src/utils/races.js
@@ -1,461 +0,0 @@
-/**
- * Estimate the point at which a function returns a target value using Newton's Method
- * @param {Number} initialEstimate The initial estimate
- * @param {Number} target The target function output
- * @param {Function} method The function
- * @param {Function} derivative The function derivative
- * @param {Number} precision The acceptable precision
- * @returns {Number} The refined estimate
- */
-function NewtonsMethod(initialEstimate, target, method, derivative, precision) {
- // Initialize estimate
- let estimate = initialEstimate;
- let estimateValue;
-
- for (let i = 0; i < 500; i += 1) {
- // Evaluate function at estimate
- estimateValue = method(estimate);
-
- // Check if estimate is close enough (usually occurs way before i = 500)
- if (Math.abs(target - estimateValue) < precision) {
- break;
- }
-
- // Refine estimate
- estimate -= (estimateValue - target) / derivative(estimate);
- }
-
- // Return refined estimate
- return estimate;
-}
-
-/*
- * Methods that implement the Purdy Points race prediction model
- * https://www.cs.uml.edu/~phoffman/xcinfo3.html
- */
-const PurdyPointsModel = {
- /**
- * Calculate the Purdy Point variables for a distance
- * @param {Number} d The distance in meters
- * @returns {Object} The Purdy Point variables
- */
- getVariables(d) {
- // Declare constants
- const c1 = 11.15895;
- const c2 = 4.304605;
- const c3 = 0.5234627;
- const c4 = 4.031560;
- const c5 = 2.316157;
- const r1 = 3.796158e-2;
- const r2 = 1.646772e-3;
- const r3 = 4.107670e-4;
- const r4 = 7.068099e-6;
- const r5 = 5.220990e-9;
-
- // Calculate world record velocity from running curve
- const v = (-c1 * Math.exp(-r1 * d))
- + (c2 * Math.exp(-r2 * d))
- + (c3 * Math.exp(-r3 * d))
- + (c4 * Math.exp(-r4 * d))
- + (c5 * Math.exp(-r5 * d));
-
- // Calculate world record time
- const twsec = d / v;
-
- // Calculate constants
- const k = 0.0654 - (0.00258 * v);
- const a = 85 / k;
- const b = 1 - (1035 / a);
-
- // Return Purdy Point variables
- return {
- twsec,
- a,
- b,
- };
- },
-
- /**
- * Get the Purdy Points for a race
- * @param {Number} d The distance of the race in meters
- * @param {Number} t The finish time of the race in seconds
- * @returns {Number} The Purdy Points for the race
- */
- getPurdyPoints(d, t) {
- // Get variables
- const variables = PurdyPointsModel.getVariables(d);
-
- // Calculate Purdy Points
- const points = variables.a * ((variables.twsec / t) - variables.b);
-
- // Return Purdy Points
- return points;
- },
-
- /**
- * Predict a race time using the Purdy Points Model
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d2 The distance of the output race in meters
- * @returns {Number} The predicted time for the output race in seconds
- */
- predictTime(d1, t1, d2) {
- // Calculate Purdy Points for distance 1
- const points = PurdyPointsModel.getPurdyPoints(d1, t1);
-
- // Calculate time for distance 2
- const variables = PurdyPointsModel.getVariables(d2);
- const seconds = (variables.a * variables.twsec) / (points + (variables.a * variables.b));
-
- // Return predicted time
- return seconds;
- },
-
- /**
- * Calculate the derivative with respect to distance of the Purdy Points curve at a specific point
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d2 The distance of the output race in meters
- * @return {Number} The derivative with respect to distance
- */
- derivative(d1, t1, d2) {
- const result = (85 * d2) / (((2316157 * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000
- + (100789 * Math.exp(-(7068099 * d2) / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767
- * d2) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000
- - (223179 * Math.exp(-(1898079 * d2) / 50000000)) / 20000) * (327 / 5000 - (129 * ((2316157
- * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d2)
- / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 * d2) / 1000000000)) / 10000000
- + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079
- * d2) / 50000000)) / 20000)) / 50000) * ((85 * (1 - (207 * (327 / 5000 - (129 * ((2316157
- * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d2)
- / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 * d2) / 1000000000)) / 10000000
- + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079
- * d2) / 50000000)) / 20000)) / 50000)) / 17)) / (327 / 5000 - (129 * ((2316157
- * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d2)
- / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 * d2) / 1000000000)) / 10000000
- + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079
- * d2) / 50000000)) / 20000)) / 50000) + (85 * (d1 / (((2316157 * Math.exp(-(522099 * d1)
- / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d1) / 1000000000000)) / 25000
- + (5234627 * Math.exp(-(410767 * d1) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693
- * d1) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 * d1) / 50000000)) / 20000) * t1)
- + (207 * (327 / 5000 - (129 * ((2316157 * Math.exp(-(522099 * d1) / 100000000000000))
- / 1000000 + (100789 * Math.exp(-(7068099 * d1) / 1000000000000)) / 25000 + (5234627
- * Math.exp(-(410767 * d1) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 * d1)
- / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 * d1) / 50000000)) / 20000)) / 50000))
- / 17 - 1)) / (327 / 5000 - (129 * ((2316157 * Math.exp(-(522099 * d1) / 100000000000000))
- / 1000000 + (100789 * Math.exp(-(7068099 * d1) / 1000000000000)) / 25000 + (5234627
- * Math.exp(-(410767 * d1) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 * d1)
- / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 * d1) / 50000000)) / 20000)) / 50000)));
- return result;
- },
-
- /**
- * Predict a race distance using the Purdy Points Model
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t2 The finish time of the output race in seconds
- * @returns {Number} The predicted distance for the output race in meters
- */
- predictDistance(t1, d1, t2) {
- // Initialize estimate
- let estimate = (d1 * t2) / t1;
-
- // Refine estimate (derivative on its own is too slow)
- const method = (x) => PurdyPointsModel.predictTime(d1, t1, x);
- const derivative = (x) => PurdyPointsModel.derivative(d1, t1, x) / 500;
- estimate = NewtonsMethod(estimate, t2, method, derivative, 0.01);
-
- // Return estimate
- return estimate;
- },
-};
-
-/*
- * Methods that implement the VO2 Max race prediction model
- * http://run-down.com/statistics/calcs_explained.php
- * https://vdoto2.com/Calculator
- */
-const VO2MaxModel = {
- /**
- * Calculate the VO2 of a runner during a race
- * @param {Number} d The race distance in meters
- * @param {Number} t The finish time in seconds
- * @returns {Number} The VO2
- */
- getVO2(d, t) {
- const minutes = t / 60;
- const v = d / minutes;
- const result = -4.6 + (0.182258 * v) + (0.000104 * (v ** 2));
- return result;
- },
-
- /**
- * Calculate the percentage of VO2 max a runner is at during a race
- * @param {Number} t The race time in seconds
- * @returns {Number} The percentage of VO2 max
- */
- getVO2Percentage(t) {
- const minutes = t / 60;
- const result = 0.8 + (0.189439 * Math.exp(-0.012778 * minutes)) + (0.298956 * Math.exp(-0.193261
- * minutes));
- return result;
- },
-
- /**
- * Calculate a runner's VO2 max from a race result
- * @param {Number} d The race distance in meters
- * @param {Number} t The finish time in seconds
- * @returns {Number} The runner's VO2 max
- */
- getVO2Max(d, t) {
- const result = VO2MaxModel.getVO2(d, t) / VO2MaxModel.getVO2Percentage(t);
- return result;
- },
-
- /**
- * Calculate the derivative with respect to time of the VO2 max curve at a specific point
- * @param {Number} d The race distance in meters
- * @param {Number} t The finish time in seconds
- * @return {Number} The derivative with respect to time
- */
- VO2MaxTimeDerivative(d, t) {
- const result = (-(273 * d) / (25 * (t ** 2)) - (468 * (d ** 2)) / (625 * (t ** 3))) / ((189
- * Math.exp(-(2 * t) / 9375)) / 1000 + (299 * Math.exp(-(193 * t) / 60000)) / 1000 + 4 / 5)
- - (((273 * d) / (25 * t) + (234 * (d ** 2)) / (625 * (t ** 2)) - 23 / 5) * (-(63
- * Math.exp(-(2 * t) / 9375)) / 1562500 - (57707 * Math.exp(-(193 * t) / 60000)) / 60000000))
- / (((189 * Math.exp(-(2 * t) / 9375)) / 1000 + (299 * Math.exp(-(193 * t) / 60000)) / 1000
- + 4 / 5) ** 2);
- return result;
- },
-
- /**
- * Predict a race time using the VO2 Max Model
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d2 The distance of the output race in meters
- * @returns {Number} The predicted time for the output race in seconds
- */
- predictTime(d1, t1, d2) {
- // Calculate input VO2 max
- const inputVO2Max = VO2MaxModel.getVO2Max(d1, t1);
-
- // Initialize estimate
- let estimate = (t1 * d2) / d1;
-
- // Refine estimate
- const method = (x) => VO2MaxModel.getVO2Max(d2, x);
- const derivative = (x) => VO2MaxModel.VO2MaxTimeDerivative(d2, x);
- estimate = NewtonsMethod(estimate, inputVO2Max, method, derivative, 0.0001);
-
- // Return estimate
- return estimate;
- },
-
- /**
- * Calculate the derivative with respect to distance of the VO2 max curve at a specific point
- * @param {Number} d The race distance in meters
- * @param {Number} t The finish time in seconds
- * @return {Number} The derivative with respect to distance
- */
- VO2MaxDistanceDerivative(d, t) {
- const result = ((468 * d) / (625 * (t ** 2)) + 273 / (25 * t)) / ((189 * Math.exp(-(2 * t)
- / 9375)) / 1000 + (299 * Math.exp(-(193 * t) / 60000)) / 1000 + 4 / 5);
- return result;
- },
-
- /**
- * Predict a race distance using the VO2 Max Model
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t2 The finish time of the output race in seconds
- * @returns {Number} The predicted distance for the output race in meters
- */
- predictDistance(t1, d1, t2) {
- // Calculate input VO2 max
- const inputVO2 = VO2MaxModel.getVO2Max(d1, t1);
-
- // Initialize estimate
- let estimate = (d1 * t2) / t1;
-
- // Refine estimate
- const method = (x) => VO2MaxModel.getVO2Max(x, t2);
- const derivative = (x) => VO2MaxModel.VO2MaxDistanceDerivative(x, t2);
- estimate = NewtonsMethod(estimate, inputVO2, method, derivative, 0.0001);
-
- // Return estimate
- return estimate;
- },
-};
-
-/*
- * Methods that implement Dave Cameron's race prediction model
- * https://www.cs.uml.edu/~phoffman/cammod.html
- */
-const CameronModel = {
- /**
- * Predict a race time using Dave Cameron's Model
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d2 The distance of the output race in meters
- * @returns {Number} The predicted time for the output race in seconds
- */
- predictTime(d1, t1, d2) {
- const a = 13.49681 - (0.000030363 * d1) + (835.7114 / (d1 ** 0.7905));
- const b = 13.49681 - (0.000030363 * d2) + (835.7114 / (d2 ** 0.7905));
- return (t1 / d1) * (a / b) * d2;
- },
-
- /**
- * Calculate the derivative with respect to distance of the Cameron curve at a specific point
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d2 The distance of the output race in meters
- * @return {Number} The derivative with respect to distance
- */
- derivative(d1, t1, d2) {
- const result = -(100 * (30363 * (d1 ** (3581 / 2000)) - 13496810000 * (d1 ** (1581 / 2000))
- - 835711400000) * t1 * (134968100 * (d2 ** (3581 / 2000)) + 14963412617 * d2)) / ((d1 ** (3581
- / 2000)) * (d2 ** (419 / 2000)) * ((30363 * (d2 ** (3581 / 2000)) - 13496810000 * (d2 ** (1581
- / 2000)) - 835711400000) ** 2));
- return result;
- },
-
- /**
- * Predict a race distance using Dave Cameron's Model
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t2 The finish time of the output race in seconds
- * @returns {Number} The predicted distance for the output race in meters
- */
- predictDistance(t1, d1, t2) {
- // Initialize estimate
- let estimate = (d1 * t2) / t1;
-
- // Refine estimate
- const method = (x) => CameronModel.predictTime(d1, t1, x);
- const derivative = (x) => CameronModel.derivative(d1, t1, x);
- estimate = NewtonsMethod(estimate, t2, method, derivative, 0.01);
-
- // Return estimate
- return estimate;
- },
-};
-
-/*
- * Methods that implement Pete Riegel's race prediction model
- * https://en.wikipedia.org/wiki/Peter_Riegel
- */
-const RiegelModel = {
- /**
- * Predict a race time using Pete Riegel's Model
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d2 The distance of the output race in meters
- * @param {Number} c The value of the exponent in the equation
- * @returns {Number} The predicted time for the output race in seconds
- */
- predictTime(d1, t1, d2, c = 1.06) {
- return t1 * ((d2 / d1) ** c);
- },
-
- /**
- * Predict a race distance using Pete Riegel's Model
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t2 The finish time of the output race in seconds
- * @param {Number} c The value of the exponent in the equation
- * @returns {Number} The predicted distance for the output race in meters
- */
- predictDistance(t1, d1, t2, c = 1.06) {
- return d1 * ((t2 / t1) ** (1 / c));
- },
-};
-
-/*
- * Methods that average the results of different race prediction models
- */
-const AverageModel = {
- /**
- * Predict a race time by averaging the results of different models
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d2 The distance of the output race in meters
- * @param {Number} c The value of the exponent in Pete Riegel's Model
- * @returns {Number} The predicted time for the output race in seconds
- */
- predictTime(d1, t1, d2, c = 1.06) {
- const purdy = PurdyPointsModel.predictTime(d1, t1, d2);
- const vo2max = VO2MaxModel.predictTime(d1, t1, d2);
- const cameron = CameronModel.predictTime(d1, t1, d2);
- const riegel = RiegelModel.predictTime(d1, t1, d2, c);
- return (purdy + vo2max + cameron + riegel) / 4;
- },
-
- /**
- * Predict a race distance by averaging the results of different models
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t2 The finish time of the output race in seconds
- * @param {Number} c The value of the exponent in Pete Riegel's Model
- * @returns {Number} The predicted distance for the output race in meters
- */
- predictDistance(t1, d1, t2, c = 1.06) {
- const purdy = PurdyPointsModel.predictDistance(t1, d1, t2);
- const vo2max = VO2MaxModel.predictDistance(t1, d1, t2);
- const cameron = CameronModel.predictDistance(t1, d1, t2);
- const riegel = RiegelModel.predictDistance(t1, d1, t2, c);
- return (purdy + vo2max + cameron + riegel) / 4;
- },
-};
-
-/**
- * Predict a race time
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d2 The distance of the output race in meters
- * @param {String} model The race prediction model to use
- * @param {Number} c The value of the exponent in Pete Riegel's Model
- */
-export function predictTime(d1, t1, d2, model='AverageModel', c=1.06) {
- switch (model) {
- case 'AverageModel':
- return AverageModel.predictTime(d1, t1, d2, c);
- case 'PurdyPointsModel':
- return PurdyPointsModel.predictTime(d1, t1, d2);
- case 'VO2MaxModel':
- return VO2MaxModel.predictTime(d1, t1, d2);
- case 'RiegelModel':
- return RiegelModel.predictTime(d1, t1, d2, c);
- case 'CameronModel':
- return CameronModel.predictTime(d1, t1, d2);
- }
-}
-
-/**
- * Predict a race distance
- * @param {Number} t1 The finish time of the input race in seconds
- * @param {Number} d1 The distance of the input race in meters
- * @param {Number} t2 The finish time of the output race in seconds
- * @param {String} model The race prediction model to use
- * @param {Number} c The value of the exponent in Pete Riegel's Model
- */
-export function predictDistance(t1, d1, t2, model='AverageModel', c=1.06) {
- switch (model) {
- default:
- case 'AverageModel':
- return AverageModel.predictDistance(t1, d1, t2, c);
- case 'PurdyPointsModel':
- return PurdyPointsModel.predictDistance(t1, d1, t2);
- case 'VO2MaxModel':
- return VO2MaxModel.predictDistance(t1, d1, t2);
- case 'RiegelModel':
- return RiegelModel.predictDistance(t1, d1, t2, c);
- case 'CameronModel':
- return CameronModel.predictDistance(t1, d1, t2);
- }
-}
-
-export const getPurdyPoints = PurdyPointsModel.getPurdyPoints;
-export const getVO2 = VO2MaxModel.getVO2;
-export const getVO2Percentage = VO2MaxModel.getVO2Percentage;
-export const getVO2Max = VO2MaxModel.getVO2Max;
diff --git a/src/utils/races.ts b/src/utils/races.ts
@@ -0,0 +1,484 @@
+export enum RacePredictionModel {
+ AverageModel = 'AverageModel',
+ PurdyPointsModel = 'PurdyPointsModel',
+ VO2MaxModel = 'VO2MaxModel',
+ RiegelModel = 'RiegelModel',
+ CameronModel = 'CameronModel',
+}
+
+/**
+ * Estimate the point at which a function returns a target value using Newton's Method
+ * @param {number} initialEstimate The initial estimate
+ * @param {number} target The target function output
+ * @param {Function} method The function
+ * @param {Function} derivative The function derivative
+ * @param {number} precision The acceptable precision
+ * @returns {number} The refined estimate
+ */
+function NewtonsMethod(initialEstimate: number, target: number, method: (x: number) => number,
+ derivative: (x: number) => number, precision: number): number {
+ // Initialize estimate
+ let estimate = initialEstimate;
+ let estimateValue;
+
+ for (let i = 0; i < 500; i += 1) {
+ // Evaluate function at estimate
+ estimateValue = method(estimate);
+
+ // Check if estimate is close enough (usually occurs way before i = 500)
+ if (Math.abs(target - estimateValue) < precision) {
+ break;
+ }
+
+ // Refine estimate
+ estimate -= (estimateValue - target) / derivative(estimate);
+ }
+
+ // Return refined estimate
+ return estimate;
+}
+
+/*
+ * The internal variables used by the Purdy Points race prediction model
+ */
+interface PurdyPointsVariables {
+ twsec: number,
+ a: number,
+ b: number,
+}
+
+/*
+ * Methods that implement the Purdy Points race prediction model
+ * https://www.cs.uml.edu/~phoffman/xcinfo3.html
+ */
+const PurdyPointsModel = {
+ /**
+ * Calculate the Purdy Point variables for a distance
+ * @param {number} d The distance in meters
+ * @returns {PurdyPointsVariables} The Purdy Point variables
+ */
+ getVariables(d: number): PurdyPointsVariables {
+ // Declare constants
+ const c1 = 11.15895;
+ const c2 = 4.304605;
+ const c3 = 0.5234627;
+ const c4 = 4.031560;
+ const c5 = 2.316157;
+ const r1 = 3.796158e-2;
+ const r2 = 1.646772e-3;
+ const r3 = 4.107670e-4;
+ const r4 = 7.068099e-6;
+ const r5 = 5.220990e-9;
+
+ // Calculate world record velocity from running curve
+ const v = (-c1 * Math.exp(-r1 * d))
+ + (c2 * Math.exp(-r2 * d))
+ + (c3 * Math.exp(-r3 * d))
+ + (c4 * Math.exp(-r4 * d))
+ + (c5 * Math.exp(-r5 * d));
+
+ // Calculate world record time
+ const twsec = d / v;
+
+ // Calculate constants
+ const k = 0.0654 - (0.00258 * v);
+ const a = 85 / k;
+ const b = 1 - (1035 / a);
+
+ // Return Purdy Point variables
+ return {
+ twsec,
+ a,
+ b,
+ };
+ },
+
+ /**
+ * Get the Purdy Points for a race
+ * @param {number} d The distance of the race in meters
+ * @param {number} t The finish time of the race in seconds
+ * @returns {number} The Purdy Points for the race
+ */
+ getPurdyPoints(d: number, t: number): number {
+ // Get variables
+ const variables = PurdyPointsModel.getVariables(d);
+
+ // Calculate Purdy Points
+ const points = variables.a * ((variables.twsec / t) - variables.b);
+
+ // Return Purdy Points
+ return points;
+ },
+
+ /**
+ * Predict a race time using the Purdy Points Model
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d2 The distance of the output race in meters
+ * @returns {number} The predicted time for the output race in seconds
+ */
+ predictTime(d1: number, t1: number, d2: number): number {
+ // Calculate Purdy Points for distance 1
+ const points = PurdyPointsModel.getPurdyPoints(d1, t1);
+
+ // Calculate time for distance 2
+ const variables = PurdyPointsModel.getVariables(d2);
+ const seconds = (variables.a * variables.twsec) / (points + (variables.a * variables.b));
+
+ // Return predicted time
+ return seconds;
+ },
+
+ /**
+ * Calculate the derivative with respect to distance of the Purdy Points curve at a specific point
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d2 The distance of the output race in meters
+ * @return {number} The derivative with respect to distance
+ */
+ derivative(d1: number, t1: number, d2: number): number {
+ const result = (85 * d2) / (((2316157 * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000
+ + (100789 * Math.exp(-(7068099 * d2) / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767
+ * d2) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000
+ - (223179 * Math.exp(-(1898079 * d2) / 50000000)) / 20000) * (327 / 5000 - (129 * ((2316157
+ * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d2)
+ / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 * d2) / 1000000000)) / 10000000
+ + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079
+ * d2) / 50000000)) / 20000)) / 50000) * ((85 * (1 - (207 * (327 / 5000 - (129 * ((2316157
+ * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d2)
+ / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 * d2) / 1000000000)) / 10000000
+ + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079
+ * d2) / 50000000)) / 20000)) / 50000)) / 17)) / (327 / 5000 - (129 * ((2316157
+ * Math.exp(-(522099 * d2) / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d2)
+ / 1000000000000)) / 25000 + (5234627 * Math.exp(-(410767 * d2) / 1000000000)) / 10000000
+ + (860921 * Math.exp(-(411693 * d2) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079
+ * d2) / 50000000)) / 20000)) / 50000) + (85 * (d1 / (((2316157 * Math.exp(-(522099 * d1)
+ / 100000000000000)) / 1000000 + (100789 * Math.exp(-(7068099 * d1) / 1000000000000)) / 25000
+ + (5234627 * Math.exp(-(410767 * d1) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693
+ * d1) / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 * d1) / 50000000)) / 20000) * t1)
+ + (207 * (327 / 5000 - (129 * ((2316157 * Math.exp(-(522099 * d1) / 100000000000000))
+ / 1000000 + (100789 * Math.exp(-(7068099 * d1) / 1000000000000)) / 25000 + (5234627
+ * Math.exp(-(410767 * d1) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 * d1)
+ / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 * d1) / 50000000)) / 20000)) / 50000))
+ / 17 - 1)) / (327 / 5000 - (129 * ((2316157 * Math.exp(-(522099 * d1) / 100000000000000))
+ / 1000000 + (100789 * Math.exp(-(7068099 * d1) / 1000000000000)) / 25000 + (5234627
+ * Math.exp(-(410767 * d1) / 1000000000)) / 10000000 + (860921 * Math.exp(-(411693 * d1)
+ / 250000000)) / 200000 - (223179 * Math.exp(-(1898079 * d1) / 50000000)) / 20000)) / 50000)));
+ return result;
+ },
+
+ /**
+ * Predict a race distance using the Purdy Points Model
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t2 The finish time of the output race in seconds
+ * @returns {number} The predicted distance for the output race in meters
+ */
+ predictDistance(t1: number, d1: number, t2: number): number {
+ // Initialize estimate
+ let estimate = (d1 * t2) / t1;
+
+ // Refine estimate (derivative on its own is too slow)
+ const method = (x: number) => PurdyPointsModel.predictTime(d1, t1, x);
+ const derivative = (x: number) => PurdyPointsModel.derivative(d1, t1, x) / 500;
+ estimate = NewtonsMethod(estimate, t2, method, derivative, 0.01);
+
+ // Return estimate
+ return estimate;
+ },
+};
+
+/*
+ * Methods that implement the VO2 Max race prediction model
+ * http://run-down.com/statistics/calcs_explained.php
+ * https://vdoto2.com/Calculator
+ */
+const VO2MaxModel = {
+ /**
+ * Calculate the VO2 of a runner during a race
+ * @param {number} d The race distance in meters
+ * @param {number} t The finish time in seconds
+ * @returns {number} The VO2
+ */
+ getVO2(d: number, t: number): number {
+ const minutes = t / 60;
+ const v = d / minutes;
+ const result = -4.6 + (0.182258 * v) + (0.000104 * (v ** 2));
+ return result;
+ },
+
+ /**
+ * Calculate the percentage of VO2 max a runner is at during a race
+ * @param {number} t The race time in seconds
+ * @returns {number} The percentage of VO2 max
+ */
+ getVO2Percentage(t: number): number {
+ const minutes = t / 60;
+ const result = 0.8 + (0.189439 * Math.exp(-0.012778 * minutes)) + (0.298956 * Math.exp(-0.193261
+ * minutes));
+ return result;
+ },
+
+ /**
+ * Calculate a runner's VO2 max from a race result
+ * @param {number} d The race distance in meters
+ * @param {number} t The finish time in seconds
+ * @returns {number} The runner's VO2 max
+ */
+ getVO2Max(d: number, t: number): number {
+ const result = VO2MaxModel.getVO2(d, t) / VO2MaxModel.getVO2Percentage(t);
+ return result;
+ },
+
+ /**
+ * Calculate the derivative with respect to time of the VO2 max curve at a specific point
+ * @param {number} d The race distance in meters
+ * @param {number} t The finish time in seconds
+ * @return {number} The derivative with respect to time
+ */
+ VO2MaxTimeDerivative(d: number, t: number): number {
+ const result = (-(273 * d) / (25 * (t ** 2)) - (468 * (d ** 2)) / (625 * (t ** 3))) / ((189
+ * Math.exp(-(2 * t) / 9375)) / 1000 + (299 * Math.exp(-(193 * t) / 60000)) / 1000 + 4 / 5)
+ - (((273 * d) / (25 * t) + (234 * (d ** 2)) / (625 * (t ** 2)) - 23 / 5) * (-(63
+ * Math.exp(-(2 * t) / 9375)) / 1562500 - (57707 * Math.exp(-(193 * t) / 60000)) / 60000000))
+ / (((189 * Math.exp(-(2 * t) / 9375)) / 1000 + (299 * Math.exp(-(193 * t) / 60000)) / 1000
+ + 4 / 5) ** 2);
+ return result;
+ },
+
+ /**
+ * Predict a race time using the VO2 Max Model
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d2 The distance of the output race in meters
+ * @returns {number} The predicted time for the output race in seconds
+ */
+ predictTime(d1: number, t1: number, d2: number): number {
+ // Calculate input VO2 max
+ const inputVO2Max = VO2MaxModel.getVO2Max(d1, t1);
+
+ // Initialize estimate
+ let estimate = (t1 * d2) / d1;
+
+ // Refine estimate
+ const method = (x: number) => VO2MaxModel.getVO2Max(d2, x);
+ const derivative = (x: number) => VO2MaxModel.VO2MaxTimeDerivative(d2, x);
+ estimate = NewtonsMethod(estimate, inputVO2Max, method, derivative, 0.0001);
+
+ // Return estimate
+ return estimate;
+ },
+
+ /**
+ * Calculate the derivative with respect to distance of the VO2 max curve at a specific point
+ * @param {number} d The race distance in meters
+ * @param {number} t The finish time in seconds
+ * @return {number} The derivative with respect to distance
+ */
+ VO2MaxDistanceDerivative(d: number, t: number): number {
+ const result = ((468 * d) / (625 * (t ** 2)) + 273 / (25 * t)) / ((189 * Math.exp(-(2 * t)
+ / 9375)) / 1000 + (299 * Math.exp(-(193 * t) / 60000)) / 1000 + 4 / 5);
+ return result;
+ },
+
+ /**
+ * Predict a race distance using the VO2 Max Model
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t2 The finish time of the output race in seconds
+ * @returns {number} The predicted distance for the output race in meters
+ */
+ predictDistance(t1: number, d1: number, t2: number): number {
+ // Calculate input VO2 max
+ const inputVO2 = VO2MaxModel.getVO2Max(d1, t1);
+
+ // Initialize estimate
+ let estimate = (d1 * t2) / t1;
+
+ // Refine estimate
+ const method = (x: number) => VO2MaxModel.getVO2Max(x, t2);
+ const derivative = (x: number) => VO2MaxModel.VO2MaxDistanceDerivative(x, t2);
+ estimate = NewtonsMethod(estimate, inputVO2, method, derivative, 0.0001);
+
+ // Return estimate
+ return estimate;
+ },
+};
+
+/*
+ * Methods that implement Dave Cameron's race prediction model
+ * https://www.cs.uml.edu/~phoffman/cammod.html
+ */
+const CameronModel = {
+ /**
+ * Predict a race time using Dave Cameron's Model
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d2 The distance of the output race in meters
+ * @returns {number} The predicted time for the output race in seconds
+ */
+ predictTime(d1: number, t1: number, d2: number): number {
+ const a = 13.49681 - (0.000030363 * d1) + (835.7114 / (d1 ** 0.7905));
+ const b = 13.49681 - (0.000030363 * d2) + (835.7114 / (d2 ** 0.7905));
+ return (t1 / d1) * (a / b) * d2;
+ },
+
+ /**
+ * Calculate the derivative with respect to distance of the Cameron curve at a specific point
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d2 The distance of the output race in meters
+ * @return {number} The derivative with respect to distance
+ */
+ derivative(d1: number, t1: number, d2: number): number {
+ const result = -(100 * (30363 * (d1 ** (3581 / 2000)) - 13496810000 * (d1 ** (1581 / 2000))
+ - 835711400000) * t1 * (134968100 * (d2 ** (3581 / 2000)) + 14963412617 * d2)) / ((d1 ** (3581
+ / 2000)) * (d2 ** (419 / 2000)) * ((30363 * (d2 ** (3581 / 2000)) - 13496810000 * (d2 ** (1581
+ / 2000)) - 835711400000) ** 2));
+ return result;
+ },
+
+ /**
+ * Predict a race distance using Dave Cameron's Model
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t2 The finish time of the output race in seconds
+ * @returns {number} The predicted distance for the output race in meters
+ */
+ predictDistance(t1: number, d1: number, t2: number): number {
+ // Initialize estimate
+ let estimate = (d1 * t2) / t1;
+
+ // Refine estimate
+ const method = (x: number) => CameronModel.predictTime(d1, t1, x);
+ const derivative = (x: number) => CameronModel.derivative(d1, t1, x);
+ estimate = NewtonsMethod(estimate, t2, method, derivative, 0.01);
+
+ // Return estimate
+ return estimate;
+ },
+};
+
+/*
+ * Methods that implement Pete Riegel's race prediction model
+ * https://en.wikipedia.org/wiki/Peter_Riegel
+ */
+const RiegelModel = {
+ /**
+ * Predict a race time using Pete Riegel's Model
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d2 The distance of the output race in meters
+ * @param {number} c The value of the exponent in the equation
+ * @returns {number} The predicted time for the output race in seconds
+ */
+ predictTime(d1: number, t1: number, d2: number, c: number = 1.06): number {
+ return t1 * ((d2 / d1) ** c);
+ },
+
+ /**
+ * Predict a race distance using Pete Riegel's Model
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t2 The finish time of the output race in seconds
+ * @param {number} c The value of the exponent in the equation
+ * @returns {number} The predicted distance for the output race in meters
+ */
+ predictDistance(t1: number, d1: number, t2: number, c: number = 1.06) {
+ return d1 * ((t2 / t1) ** (1 / c));
+ },
+};
+
+/*
+ * Methods that average the results of different race prediction models
+ */
+const AverageModel = {
+ /**
+ * Predict a race time by averaging the results of different models
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d2 The distance of the output race in meters
+ * @param {number} c The value of the exponent in Pete Riegel's Model
+ * @returns {number} The predicted time for the output race in seconds
+ */
+ predictTime(d1: number, t1: number, d2: number, c: number = 1.06): number {
+ const purdy = PurdyPointsModel.predictTime(d1, t1, d2);
+ const vo2max = VO2MaxModel.predictTime(d1, t1, d2);
+ const cameron = CameronModel.predictTime(d1, t1, d2);
+ const riegel = RiegelModel.predictTime(d1, t1, d2, c);
+ return (purdy + vo2max + cameron + riegel) / 4;
+ },
+
+ /**
+ * Predict a race distance by averaging the results of different models
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t2 The finish time of the output race in seconds
+ * @param {number} c The value of the exponent in Pete Riegel's Model
+ * @returns {number} The predicted distance for the output race in meters
+ */
+ predictDistance(t1: number, d1: number, t2: number, c: number = 1.06) {
+ const purdy = PurdyPointsModel.predictDistance(t1, d1, t2);
+ const vo2max = VO2MaxModel.predictDistance(t1, d1, t2);
+ const cameron = CameronModel.predictDistance(t1, d1, t2);
+ const riegel = RiegelModel.predictDistance(t1, d1, t2, c);
+ return (purdy + vo2max + cameron + riegel) / 4;
+ },
+};
+
+/**
+ * Predict a race time
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d2 The distance of the output race in meters
+ * @param {string} model The race prediction model to use
+ * @param {number} c The value of the exponent in Pete Riegel's Model
+ */
+export function predictTime(d1: number, t1: number, d2: number,
+ model: RacePredictionModel = RacePredictionModel.AverageModel,
+ c: number = 1.06): number {
+ switch (model) {
+ default:
+ case RacePredictionModel.AverageModel:
+ return AverageModel.predictTime(d1, t1, d2, c);
+ case RacePredictionModel.PurdyPointsModel:
+ return PurdyPointsModel.predictTime(d1, t1, d2);
+ case RacePredictionModel.VO2MaxModel:
+ return VO2MaxModel.predictTime(d1, t1, d2);
+ case RacePredictionModel.RiegelModel:
+ return RiegelModel.predictTime(d1, t1, d2, c);
+ case RacePredictionModel.CameronModel:
+ return CameronModel.predictTime(d1, t1, d2);
+ }
+}
+
+/**
+ * Predict a race distance
+ * @param {number} t1 The finish time of the input race in seconds
+ * @param {number} d1 The distance of the input race in meters
+ * @param {number} t2 The finish time of the output race in seconds
+ * @param {string} model The race prediction model to use
+ * @param {number} c The value of the exponent in Pete Riegel's Model
+ */
+export function predictDistance(t1: number, d1: number, t2: number,
+ model: RacePredictionModel = RacePredictionModel.AverageModel,
+ c: number = 1.06) {
+ switch (model) {
+ default:
+ case RacePredictionModel.AverageModel:
+ return AverageModel.predictDistance(t1, d1, t2, c);
+ case RacePredictionModel.PurdyPointsModel:
+ return PurdyPointsModel.predictDistance(t1, d1, t2);
+ case RacePredictionModel.VO2MaxModel:
+ return VO2MaxModel.predictDistance(t1, d1, t2);
+ case RacePredictionModel.RiegelModel:
+ return RiegelModel.predictDistance(t1, d1, t2, c);
+ case RacePredictionModel.CameronModel:
+ return CameronModel.predictDistance(t1, d1, t2);
+ }
+}
+
+export const getPurdyPoints = PurdyPointsModel.getPurdyPoints;
+export const getVO2 = VO2MaxModel.getVO2;
+export const getVO2Percentage = VO2MaxModel.getVO2Percentage;
+export const getVO2Max = VO2MaxModel.getVO2Max;
diff --git a/src/utils/storage.js b/src/utils/storage.js
@@ -1,36 +0,0 @@
-// The global localStorage prefix
-const prefix = 'running-tools';
-
-/**
- * Read an object from a localStorage item
- * @param {String} key The localStorage item's key
- * @returns {Object} The object
- */
-export function get(key) {
- try {
- return JSON.parse(localStorage.getItem(`${prefix}.${key}`));
- } catch {
- return null;
- }
-}
-
-/**
- * Write an object to a localStorage item
- * @param {String} key The localStorage item's key
- * @param {Object} value The object to write
- */
-export function set(key, value) {
- localStorage.setItem(`${prefix}.${key}`, JSON.stringify(value));
-}
-
-/**
- * Migrate outdated localStorage options
- */
-export function migrate() {
- // Add customTargetNames property to workout options (>1.4.1)
- let workoutOptions = get('workout-calculator-options');
- if (workoutOptions !== null && workoutOptions.customTargetNames === undefined) {
- workoutOptions.customTargetNames = false;
- set('workout-calculator-options', workoutOptions);
- }
-}
diff --git a/src/utils/storage.ts b/src/utils/storage.ts
@@ -0,0 +1,37 @@
+// The global localStorage prefix
+const prefix = 'running-tools';
+
+/**
+ * Read an object from a localStorage item
+ * @param {string} key The localStorage item's key
+ * @returns {object} The object
+ */
+export function get(key: string): object | null {
+ try {
+ return JSON.parse(localStorage.getItem(`${prefix}.${key}`) || '');
+ } catch {
+ return null;
+ }
+}
+
+/**
+ * Write an object to a localStorage item
+ * @param {string} key The localStorage item's key
+ * @param {object} value The object to write
+ */
+export function set(key: string, value: object) {
+ localStorage.setItem(`${prefix}.${key}`, JSON.stringify(value));
+}
+
+/**
+ * Migrate outdated localStorage options
+ */
+export function migrate() {
+ // Add customTargetNames property to workout options (>1.4.1)
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const workoutOptions = get('workout-calculator-options') as any; // TODO: update types
+ if (workoutOptions !== null && workoutOptions.customTargetNames === undefined) {
+ workoutOptions.customTargetNames = false;
+ set('workout-calculator-options', workoutOptions);
+ }
+}
diff --git a/src/utils/targets.js b/src/utils/targets.js
@@ -1,129 +0,0 @@
-import { formatDuration, formatNumber } from '@/utils/format';
-import { DISTANCE_UNITS, convertDistance } from '@/utils/units';
-
-/**
- * Sort an array of targets
- * @param {Array} targets The array of targets
- * @returns {Array} The sorted targets
- */
-export function sort(targets) {
- return [
- ...targets.filter((item) => item.type === 'distance')
- .sort((a, b) => convertDistance(a.distanceValue, a.distanceUnit, 'meters')
- - convertDistance(b.distanceValue, b.distanceUnit, 'meters')),
-
- ...targets.filter((item) => item.type === 'time')
- .sort((a, b) => a.time - b.time),
- ];
-}
-
-/**
- * Generate a string description of a workout target
- * @param {Object} target The workout target
- * @return {String} The string description
- */
-export function workoutTargetToString(target) {
- let result = formatNumber(target.splitValue, 0, 2, false) + ' ' +
- DISTANCE_UNITS[target.splitUnit].symbol;
- if (target.type === 'time') {
- result += ' @ ' + formatDuration(target.time, 3, 2, false);
- } else if (target.distanceValue != target.splitValue || target.distanceUnit != target.splitUnit) {
- result += ' @ ' + formatNumber(target.distanceValue, 0, 2, false) + ' ' +
- DISTANCE_UNITS[target.distanceUnit].symbol;
- }
- return result;
-}
-
-export const defaultTargetSets = {
- '_pace_targets': {
- name: 'Common Pace Targets',
- targets: sort([
- { type: 'distance', distanceValue: 100, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 300, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 600, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1000, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
-
- { type: 'distance', distanceValue: 2, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 3, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 4, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
-
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 6, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 8, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 10, distanceUnit: 'miles' },
-
- { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
-
- { type: 'time', time: 600 },
- { type: 'time', time: 1800 },
- { type: 'time', time: 3600 },
- ]),
- },
- '_race_targets': {
- name: 'Common Race Targets',
- targets: sort([
- { type: 'distance', distanceValue: 400, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 800, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1500, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1600, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 3000, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 3200, distanceUnit: 'meters' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
-
- { type: 'distance', distanceValue: 3, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 6, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 8, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 10, distanceUnit: 'kilometers' },
- { type: 'distance', distanceValue: 15, distanceUnit: 'kilometers' },
-
- { type: 'distance', distanceValue: 0.5, distanceUnit: 'marathons' },
- { type: 'distance', distanceValue: 1, distanceUnit: 'marathons' },
- ]),
- },
- '_split_targets': {
- name: '5K Mile Splits',
- targets: [
- { type: 'distance', distanceValue: 1, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 2, distanceUnit: 'miles' },
- { type: 'distance', distanceValue: 5, distanceUnit: 'kilometers' },
- ],
- },
- '_workout_targets': {
- name: 'Common Workout Targets',
- targets: [
- {
- splitValue: 400, splitUnit: 'meters',
- type: 'distance', distanceValue: 1, distanceUnit: 'miles',
- },
- {
- splitValue: 800, splitUnit: 'meters',
- type: 'distance', distanceValue: 5, distanceUnit: 'kilometers',
- },
- {
- splitValue: 1600, splitUnit: 'meters',
- type: 'time', time: 3600,
- },
- {
- splitValue: 1, splitUnit: 'miles',
- type: 'distance', distanceValue: 1, distanceUnit: 'marathons',
- },
- ],
- },
-};
diff --git a/src/utils/targets.ts b/src/utils/targets.ts
@@ -0,0 +1,224 @@
+import { formatDuration, formatNumber } from '@/utils/format';
+import { DISTANCE_UNITS, DISTANCE_UNIT_KEYS, convertDistance } from '@/utils/units';
+
+/*
+ * Enumeration for the two basic types of targets: those defined by distance vs time
+ */
+export enum TargetType {
+ Distance = 'distance',
+ Time = 'time',
+};
+
+/**
+ * Type for basic distance-defined targets
+ */
+interface DistanceTarget {
+ type: TargetType.Distance,
+ distanceValue: number,
+ distanceUnit: DISTANCE_UNIT_KEYS,
+};
+
+/**
+ * Type for basic time-defined targets
+ */
+interface TimeTarget {
+ type: TargetType.Time,
+ time: number,
+};
+
+/**
+ * Type for pace and race calculator targets
+ */
+export type StandardTarget = DistanceTarget | TimeTarget;
+
+/*
+ * Type for pace and race calculator target sets
+ */
+export interface StandardTargetSet {
+ name: string,
+ targets: Array<StandardTarget>,
+}
+
+/*
+ * Type for split calculator targets
+ */
+export type SplitTarget = DistanceTarget & {
+ splitTime?: number
+};
+
+/*
+ * Type for split calculator target sets
+ */
+export interface SplitTargetSet {
+ name: string,
+ targets: Array<SplitTarget>,
+}
+
+/*
+ * Type for workout calculator targets
+ */
+export type WorkoutTarget = StandardTarget & {
+ splitValue: number,
+ splitUnit: DISTANCE_UNIT_KEYS,
+};
+
+/*
+ * Type for workout calculator target sets
+ */
+export interface WorkoutTargetSet {
+ name: string,
+ targets: Array<WorkoutTarget>,
+}
+
+/*
+ * Type for generic targets
+ */
+export type Target = StandardTarget | SplitTarget | WorkoutTarget;
+
+/**
+ * Sort an array of targets
+ * @param {Array<Target>} targets The array of targets
+ * @returns {Array<Target>} The sorted targets
+ */
+export function sort(targets: Array<Target>): Array<Target> {
+ return [
+ ...targets.filter((item) => item.type === TargetType.Distance)
+ .sort((a, b) => convertDistance(a.distanceValue, a.distanceUnit, DISTANCE_UNIT_KEYS.meters)
+ - convertDistance(b.distanceValue, b.distanceUnit, DISTANCE_UNIT_KEYS.meters)),
+
+ ...targets.filter((item) => item.type === TargetType.Time)
+ .sort((a, b) => a.time - b.time),
+ ];
+}
+
+/**
+ * Generate a string description of a workout target
+ * @param {WorkoutTarget} target The workout target
+ * @return {String} The string description
+ */
+export function workoutTargetToString(target: WorkoutTarget): string {
+ let result = formatNumber(target.splitValue, 0, 2, false) + ' ' +
+ DISTANCE_UNITS[target.splitUnit].symbol;
+ if (target.type === TargetType.Time) {
+ result += ' @ ' + formatDuration(target.time, 3, 2, false);
+ } else if (target.distanceValue != target.splitValue || target.distanceUnit != target.splitUnit) {
+ result += ' @ ' + formatNumber(target.distanceValue, 0, 2, false) + ' ' +
+ DISTANCE_UNITS[target.distanceUnit].symbol;
+ }
+ return result;
+}
+
+/**
+ * A set of common pace calculator targets
+ */
+const common_pace_targets: StandardTargetSet = {
+ name: 'Common Pace Targets',
+ targets: sort([
+ { type: TargetType.Distance, distanceValue: 100, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 200, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 300, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 400, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 600, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 800, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 1000, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 1200, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 1500, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 1600, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 3200, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+
+ { type: TargetType.Distance, distanceValue: 2, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+ { type: TargetType.Distance, distanceValue: 3, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+ { type: TargetType.Distance, distanceValue: 4, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+ { type: TargetType.Distance, distanceValue: 5, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+ { type: TargetType.Distance, distanceValue: 6, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+ { type: TargetType.Distance, distanceValue: 8, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+ { type: TargetType.Distance, distanceValue: 10, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+
+ { type: TargetType.Distance, distanceValue: 1, distanceUnit: DISTANCE_UNIT_KEYS.miles },
+ { type: TargetType.Distance, distanceValue: 2, distanceUnit: DISTANCE_UNIT_KEYS.miles },
+ { type: TargetType.Distance, distanceValue: 3, distanceUnit: DISTANCE_UNIT_KEYS.miles },
+ { type: TargetType.Distance, distanceValue: 5, distanceUnit: DISTANCE_UNIT_KEYS.miles },
+ { type: TargetType.Distance, distanceValue: 6, distanceUnit: DISTANCE_UNIT_KEYS.miles },
+ { type: TargetType.Distance, distanceValue: 8, distanceUnit: DISTANCE_UNIT_KEYS.miles },
+ { type: TargetType.Distance, distanceValue: 10, distanceUnit: DISTANCE_UNIT_KEYS.miles },
+
+ { type: TargetType.Distance, distanceValue: 0.5, distanceUnit: DISTANCE_UNIT_KEYS.marathons },
+ { type: TargetType.Distance, distanceValue: 1, distanceUnit: DISTANCE_UNIT_KEYS.marathons },
+
+ { type: TargetType.Time, time: 600 },
+ { type: TargetType.Time, time: 1800 },
+ { type: TargetType.Time, time: 3600 },
+ ]),
+};
+
+/**
+ * A set of common race calculator targets
+ */
+const common_race_targets: StandardTargetSet = {
+ name: 'Common Race Targets',
+ targets: sort([
+ { type: TargetType.Distance, distanceValue: 400, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 800, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 1500, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 1600, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 1, distanceUnit: DISTANCE_UNIT_KEYS.miles },
+ { type: TargetType.Distance, distanceValue: 3000, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 3200, distanceUnit: DISTANCE_UNIT_KEYS.meters },
+ { type: TargetType.Distance, distanceValue: 2, distanceUnit: DISTANCE_UNIT_KEYS.miles },
+
+ { type: TargetType.Distance, distanceValue: 3, distanceUnit: DISTANCE_UNIT_KEYS.miles },
+ { type: TargetType.Distance, distanceValue: 5, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+ { type: TargetType.Distance, distanceValue: 6, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+ { type: TargetType.Distance, distanceValue: 8, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+ { type: TargetType.Distance, distanceValue: 10, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+ { type: TargetType.Distance, distanceValue: 15, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+
+ { type: TargetType.Distance, distanceValue: 0.5, distanceUnit: DISTANCE_UNIT_KEYS.marathons },
+ { type: TargetType.Distance, distanceValue: 1, distanceUnit: DISTANCE_UNIT_KEYS.marathons },
+ ]),
+};
+
+
+/**
+ * A set of targets for 5K mile splits
+ */
+const five_k_mile_splits: SplitTargetSet = {
+ name: '5K Mile Splits',
+ targets: [
+ { type: TargetType.Distance, distanceValue: 1, distanceUnit: DISTANCE_UNIT_KEYS.miles },
+ { type: TargetType.Distance, distanceValue: 2, distanceUnit: DISTANCE_UNIT_KEYS.miles },
+ { type: TargetType.Distance, distanceValue: 5, distanceUnit: DISTANCE_UNIT_KEYS.kilometers },
+ ],
+};
+
+/**
+ * A set of common workout calculator targets
+ */
+const common_workout_targets: WorkoutTargetSet = {
+ name: 'Common Workout Targets',
+ targets: [
+ {
+ splitValue: 400, splitUnit: DISTANCE_UNIT_KEYS.meters,
+ type: TargetType.Distance, distanceValue: 1, distanceUnit: DISTANCE_UNIT_KEYS.miles,
+ },
+ {
+ splitValue: 800, splitUnit: DISTANCE_UNIT_KEYS.meters,
+ type: TargetType.Distance, distanceValue: 5, distanceUnit: DISTANCE_UNIT_KEYS.kilometers,
+ },
+ {
+ splitValue: 1600, splitUnit: DISTANCE_UNIT_KEYS.meters,
+ type: TargetType.Time, time: 3600,
+ },
+ {
+ splitValue: 1, splitUnit: DISTANCE_UNIT_KEYS.miles,
+ type: TargetType.Distance, distanceValue: 1, distanceUnit: DISTANCE_UNIT_KEYS.marathons,
+ },
+ ],
+};
+
+export const defaultTargetSets = {
+ _pace_targets: common_pace_targets,
+ _race_targets: common_race_targets,
+ _split_targets: five_k_mile_splits,
+ _workout_targets: common_workout_targets,
+};
diff --git a/src/utils/units.js b/src/utils/units.js
@@ -1,199 +0,0 @@
-/**
- * The time units
- */
-export const TIME_UNITS = {
- seconds: {
- name: 'Seconds',
- symbol: 's',
- value: 1,
- },
- minutes: {
- name: 'Minutes',
- symbol: 'min',
- value: 60,
- },
- hours: {
- name: 'Hours',
- symbol: 'hr',
- value: 3600,
- },
-};
-
-/**
- * The distance units
- */
-export const DISTANCE_UNITS = {
- meters: {
- name: 'Meters',
- symbol: 'm',
- value: 1,
- },
- yards: {
- name: 'Yards',
- symbol: 'yd',
- value: 0.9144,
- },
- kilometers: {
- name: 'Kilometers',
- symbol: 'km',
- value: 1000,
- },
- miles: {
- name: 'Miles',
- symbol: 'mi',
- value: 1609.3499,
- },
- marathons: {
- name: 'Marathons',
- symbol: 'Mar',
- value: 42195,
- },
-};
-
-/**
- * The speed units
- */
-export const SPEED_UNITS = {
- meters_per_second: {
- name: 'Meters per Second',
- symbol: 'm/s',
- value: 1,
- },
- kilometers_per_hour: {
- name: 'Kilometers per Hour',
- symbol: 'kph',
- value: DISTANCE_UNITS.kilometers.value / TIME_UNITS.hours.value,
- },
- miles_per_hour: {
- name: 'Miles per Hour',
- symbol: 'mph',
- value: DISTANCE_UNITS.miles.value / TIME_UNITS.hours.value,
- },
-};
-
-/**
- * The value of each pace unit in seconds per meter
- */
-export const PACE_UNITS = {
- seconds_per_meter: {
- name: 'Seconds per Meter',
- symbol: 's/m',
- value: 1,
- },
- seconds_per_kilometer: {
- name: 'Time per Kilometer',
- symbol: '/ km',
- value: TIME_UNITS.seconds.value / DISTANCE_UNITS.kilometers.value,
- },
- seconds_per_mile: {
- name: 'Time per Mile',
- symbol: '/ mi',
- value: TIME_UNITS.seconds.value / DISTANCE_UNITS.miles.value,
- },
-};
-
-/**
- * Convert between time units
- * @param {Number} inputValue The input value
- * @param {String} inputUnit The unit of the input
- * @param {String} outputUnit The unit of the output
- * @returns {Number} The output
- */
-export function convertTime(inputValue, inputUnit, outputUnit) {
- return (inputValue * TIME_UNITS[inputUnit].value) / TIME_UNITS[outputUnit].value;
-}
-
-/**
- * Convert between distance units
- * @param {Number} inputValue The input value
- * @param {String} inputUnit The unit of the input
- * @param {String} outputUnit The unit of the output
- * @returns {Number} The output
- */
-export function convertDistance(inputValue, inputUnit, outputUnit) {
- return (inputValue * DISTANCE_UNITS[inputUnit].value) / DISTANCE_UNITS[outputUnit].value;
-}
-
-/**
- * Convert between speed units
- * @param {Number} inputValue The input value
- * @param {String} inputUnit The unit of the input
- * @param {String} outputUnit The unit of the output
- * @returns {Number} The output
- */
-export function convertSpeed(inputValue, inputUnit, outputUnit) {
- return (inputValue * SPEED_UNITS[inputUnit].value) / SPEED_UNITS[outputUnit].value;
-}
-
-/**
- * Convert between pace units
- * @param {Number} inputValue The input value
- * @param {String} inputUnit The unit of the input
- * @param {String} outputUnit The unit of the output
- * @returns {Number} The output
- */
-export function convertPace(inputValue, inputUnit, outputUnit) {
- return (inputValue * PACE_UNITS[inputUnit].value) / PACE_UNITS[outputUnit].value;
-}
-
-/**
- * Convert between speed and/or pace units
- * @param {Number} inputValue The input value
- * @param {String} inputUnit The unit of the input
- * @param {String} outputUnit The unit of the output
- * @returns {Number} The output
- */
-export function convertSpeedPace(inputValue, inputUnit, outputUnit) {
- // Calculate input speed
- let speed;
- if (inputUnit in PACE_UNITS) {
- speed = 1 / (inputValue * PACE_UNITS[inputUnit].value);
- } else {
- speed = inputValue * SPEED_UNITS[inputUnit].value;
- }
-
- // Calculate output
- if (outputUnit in PACE_UNITS) {
- return (1 / speed) / PACE_UNITS[outputUnit].value;
- }
- return speed / SPEED_UNITS[outputUnit].value;
-}
-
-/**
- * Detect the user's default unit system
- * @returns {String} The default unit system
- */
-export function detectDefaultUnitSystem() {
- const language = (navigator.language || navigator.userLanguage).toLowerCase();
- if (language.endsWith('-us') || language.endsWith('-mm')) {
- return 'imperial';
- }
- return 'metric';
-}
-
-/**
- * Get the default distance unit in a unit system
- * @param {String} unitSystem The unit system
- * @returns {String} The default distance unit
- */
-export function getDefaultDistanceUnit(unitSystem) {
- return unitSystem === 'metric' ? 'kilometers' : 'miles';
-}
-
-/**
- * Get the default speed unit in a unit system
- * @param {String} unitSystem The unit system
- * @returns {String} The default speed unit
- */
-export function getDefaultSpeedUnit(unitSystem) {
- return unitSystem === 'metric' ? 'kilometers_per_hour' : 'miles_per_hour';
-}
-
-/**
- * Get the default pace unit in a unit system
- * @param {String} unitSystem The unit system
- * @returns {String} The default pace unit
- */
-export function getDefaultPaceUnit(unitSystem) {
- return unitSystem === 'metric' ? 'seconds_per_kilometer' : 'seconds_per_mile';
-}
diff --git a/src/utils/units.ts b/src/utils/units.ts
@@ -0,0 +1,228 @@
+export const enum TIME_UNIT_KEYS {
+ seconds = 'seconds',
+ minutes = 'minutes',
+ hours = 'hours',
+}
+export const enum DISTANCE_UNIT_KEYS {
+ meters = 'meters',
+ yards = 'yards',
+ kilometers = 'kilometers',
+ miles = 'miles',
+ marathons = 'marathons',
+}
+export const enum SPEED_UNIT_KEYS {
+ meters_per_second = 'meters_per_second',
+ kilometers_per_hour = 'kilometers_per_hour',
+ miles_per_hour = 'miles_per_hour',
+}
+export const enum PACE_UNIT_KEYS {
+ seconds_per_meter = 'seconds_per_meter',
+ time_per_kilometer = 'seconds_per_kilometer',
+ time_per_mile = 'seconds_per_mile',
+}
+
+/**
+ * The time units
+ */
+export const TIME_UNITS = {
+ seconds: {
+ name: 'Seconds',
+ symbol: 's',
+ value: 1,
+ },
+ minutes: {
+ name: 'Minutes',
+ symbol: 'min',
+ value: 60,
+ },
+ hours: {
+ name: 'Hours',
+ symbol: 'hr',
+ value: 3600,
+ },
+};
+
+/**
+ * The distance units
+ */
+export const DISTANCE_UNITS = {
+ meters: {
+ name: 'Meters',
+ symbol: 'm',
+ value: 1,
+ },
+ yards: {
+ name: 'Yards',
+ symbol: 'yd',
+ value: 0.9144,
+ },
+ kilometers: {
+ name: 'Kilometers',
+ symbol: 'km',
+ value: 1000,
+ },
+ miles: {
+ name: 'Miles',
+ symbol: 'mi',
+ value: 1609.3499,
+ },
+ marathons: {
+ name: 'Marathons',
+ symbol: 'Mar',
+ value: 42195,
+ },
+};
+
+/**
+ * The speed units
+ */
+export const SPEED_UNITS = {
+ meters_per_second: {
+ name: 'Meters per Second',
+ symbol: 'm/s',
+ value: 1,
+ },
+ kilometers_per_hour: {
+ name: 'Kilometers per Hour',
+ symbol: 'kph',
+ value: DISTANCE_UNITS.kilometers.value / TIME_UNITS.hours.value,
+ },
+ miles_per_hour: {
+ name: 'Miles per Hour',
+ symbol: 'mph',
+ value: DISTANCE_UNITS.miles.value / TIME_UNITS.hours.value,
+ },
+};
+
+/**
+ * The value of each pace unit in seconds per meter
+ */
+export const PACE_UNITS = {
+ seconds_per_meter: {
+ name: 'Seconds per Meter',
+ symbol: 's/m',
+ value: 1,
+ },
+ seconds_per_kilometer: {
+ name: 'Time per Kilometer',
+ symbol: '/ km',
+ value: TIME_UNITS.seconds.value / DISTANCE_UNITS.kilometers.value,
+ },
+ seconds_per_mile: {
+ name: 'Time per Mile',
+ symbol: '/ mi',
+ value: TIME_UNITS.seconds.value / DISTANCE_UNITS.miles.value,
+ },
+};
+
+/**
+ * Convert between time units
+ * @param {number} inputValue The input value
+ * @param {string} inputUnit The unit of the input
+ * @param {string} outputUnit The unit of the output
+ * @returns {number} The output
+ */
+export function convertTime(inputValue: number, inputUnit: TIME_UNIT_KEYS,
+ outputUnit: TIME_UNIT_KEYS): number {
+ return (inputValue * TIME_UNITS[inputUnit].value) / TIME_UNITS[outputUnit].value;
+}
+
+/**
+ * Convert between distance units
+ * @param {number} inputValue The input value
+ * @param {string} inputUnit The unit of the input
+ * @param {string} outputUnit The unit of the output
+ * @returns {number} The output
+ */
+export function convertDistance(inputValue: number, inputUnit: DISTANCE_UNIT_KEYS,
+ outputUnit: DISTANCE_UNIT_KEYS): number {
+ return (inputValue * DISTANCE_UNITS[inputUnit].value) / DISTANCE_UNITS[outputUnit].value;
+}
+
+/**
+ * Convert between speed units
+ * @param {number} inputValue The input value
+ * @param {string} inputUnit The unit of the input
+ * @param {string} outputUnit The unit of the output
+ * @returns {number} The output
+ */
+export function convertSpeed(inputValue: number, inputUnit: SPEED_UNIT_KEYS,
+ outputUnit: SPEED_UNIT_KEYS): number {
+ return (inputValue * SPEED_UNITS[inputUnit].value) / SPEED_UNITS[outputUnit].value;
+}
+
+/**
+ * Convert between pace units
+ * @param {number} inputValue The input value
+ * @param {string} inputUnit The unit of the input
+ * @param {string} outputUnit The unit of the output
+ * @returns {number} The output
+ */
+export function convertPace(inputValue: number, inputUnit: PACE_UNIT_KEYS,
+ outputUnit: PACE_UNIT_KEYS): number {
+ return (inputValue * PACE_UNITS[inputUnit].value) / PACE_UNITS[outputUnit].value;
+}
+
+/**
+ * Convert between speed and/or pace units
+ * @param {number} inputValue The input value
+ * @param {string} inputUnit The unit of the input
+ * @param {string} outputUnit The unit of the output
+ * @returns {number} The output
+ */
+export function convertSpeedPace(inputValue: number, inputUnit: SPEED_UNIT_KEYS | PACE_UNIT_KEYS,
+ outputUnit: SPEED_UNIT_KEYS | PACE_UNIT_KEYS): number {
+ // Calculate input speed
+ let speed;
+ if (inputUnit in PACE_UNITS) {
+ speed = 1 / (inputValue * PACE_UNITS[inputUnit as PACE_UNIT_KEYS].value);
+ } else {
+ speed = inputValue * SPEED_UNITS[inputUnit as SPEED_UNIT_KEYS].value;
+ }
+
+ // Calculate output
+ if (outputUnit in PACE_UNITS) {
+ return (1 / speed) / PACE_UNITS[outputUnit as PACE_UNIT_KEYS].value;
+ }
+ return speed / SPEED_UNITS[outputUnit as SPEED_UNIT_KEYS].value;
+}
+
+/**
+ * Detect the user's default unit system
+ * @returns {string} The default unit system
+ */
+export function detectDefaultUnitSystem(): string {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const language = (navigator.language || (navigator as any).userLanguage).toLowerCase();
+ if (language.endsWith('-us') || language.endsWith('-mm')) {
+ return 'imperial';
+ }
+ return 'metric';
+}
+
+/**
+ * Get the default distance unit in a unit system
+ * @param {string} unitSystem The unit system
+ * @returns {string} The default distance unit
+ */
+export function getDefaultDistanceUnit(unitSystem: string): string {
+ return unitSystem === 'metric' ? 'kilometers' : 'miles';
+}
+
+/**
+ * Get the default speed unit in a unit system
+ * @param {string} unitSystem The unit system
+ * @returns {string} The default speed unit
+ */
+export function getDefaultSpeedUnit(unitSystem: string): string {
+ return unitSystem === 'metric' ? 'kilometers_per_hour' : 'miles_per_hour';
+}
+
+/**
+ * Get the default pace unit in a unit system
+ * @param {string} unitSystem The unit system
+ * @returns {string} The default pace unit
+ */
+export function getDefaultPaceUnit(unitSystem: string): string {
+ return unitSystem === 'metric' ? 'seconds_per_kilometer' : 'seconds_per_mile';
+}