From 53ab332e6d2afe9818914e0026310e4cadcc4202 Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Sun, 16 Aug 2026 14:23:32 +0200 Subject: refactor(web): migrate browser code to TypeScript --- web/frontend/official-climb-profile-logic.ts | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 web/frontend/official-climb-profile-logic.ts (limited to 'web/frontend/official-climb-profile-logic.ts') diff --git a/web/frontend/official-climb-profile-logic.ts b/web/frontend/official-climb-profile-logic.ts new file mode 100644 index 0000000..1b2bc83 --- /dev/null +++ b/web/frontend/official-climb-profile-logic.ts @@ -0,0 +1,56 @@ +import type { ProfileBand, ProfilePoint } from "./types.js"; + +export interface OfficialProfileSection { + startDistanceKm: number; + endDistanceKm: number; + startElevation: number; + endElevation: number; + slopePercent: number; + band: ProfileBand; +} + +const clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(max, value)); +const profileStepSizesM: number[] = [100, 200, 500, 1000]; +const displayStepForLength = (lengthM: number): number => + profileStepSizesM.find((stepM) => Math.ceil(lengthM / stepM) <= 30) || profileStepSizesM[profileStepSizesM.length - 1]; +const profileBandForSlope = (slopePercent: number): ProfileBand => { + if (slopePercent < 0) return "downhill"; + if (slopePercent < 3) return "0-3"; + if (slopePercent < 6) return "3-6"; + if (slopePercent < 9) return "6-9"; + if (slopePercent < 12) return "9-12"; + return "12-plus"; +}; +const officialProfileSections = (points: ProfilePoint[], startIndex: number, endIndex: number, stepM: number): OfficialProfileSection[] => { + const startDistanceM = points[startIndex].distanceKm * 1000; + const endDistanceM = points[endIndex].distanceKm * 1000; + const sections: OfficialProfileSection[] = []; + let pointIndex = startIndex; + const elevationAtDistance = (distanceM: number) => { + while (pointIndex < endIndex - 1 && points[pointIndex + 1].distanceKm * 1000 < distanceM) pointIndex++; + const nextIndex = Math.min(pointIndex + 1, endIndex); + const first = points[pointIndex]; + const next = points[nextIndex]; + const distanceSpan = next.distanceKm * 1000 - first.distanceKm * 1000; + if (distanceSpan <= 0) return first.elevationM; + const fraction = (distanceM - first.distanceKm * 1000) / distanceSpan; + return first.elevationM + clamp(fraction, 0, 1) * (next.elevationM - first.elevationM); + }; + for (let sectionStartM = startDistanceM; sectionStartM < endDistanceM; sectionStartM += stepM) { + const sectionEndM = Math.min(sectionStartM + stepM, endDistanceM); + const startElevation = elevationAtDistance(sectionStartM); + const endElevation = elevationAtDistance(sectionEndM); + const slopePercent = ((endElevation - startElevation) / (sectionEndM - sectionStartM)) * 100; + sections.push({ + startDistanceKm: sectionStartM / 1000, + endDistanceKm: sectionEndM / 1000, + startElevation, + endElevation, + slopePercent, + band: profileBandForSlope(slopePercent), + }); + } + return sections; +}; + +export { displayStepForLength, profileBandForSlope, officialProfileSections }; -- cgit v1.2.3