feat: Calculate and display average pace

Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
2025-08-17 20:19:08 +00:00
parent 1003de33f2
commit 4a19599162

View File

@@ -305,7 +305,7 @@ function Map({data, loading, coords, mapState, setMapState, setSubmenu, showDire
); );
} }
function Menu({data, duration, setDuration, end, setEnd, slider, setSlider, submenu, setSubmenu, showDirection, setShowDirection, showPoints, setShowPoints, setMapState, shareStart, shareEnd, drawnItems, coords}) { function Menu({data, duration, setDuration, end, setEnd, slider, setSlider, submenu, setSubmenu, showDirection, setShowDirection, showPoints, setShowPoints, setMapState, shareStart, shareEnd, drawnItems, coords, pointsInRange}) {
const [showRange, setShowRange] = useState(false); const [showRange, setShowRange] = useState(false);
const [isSearching, setIsSearching] = useState(false); const [isSearching, setIsSearching] = useState(false);
const [searchResults, setSearchResults] = useState(null); const [searchResults, setSearchResults] = useState(null);
@@ -661,9 +661,9 @@ function Menu({data, duration, setDuration, end, setEnd, slider, setSlider, subm
setSearchResults(newResults); setSearchResults(newResults);
}; };
const totalDistance = useMemo(() => { const { totalDistance, averagePace } = useMemo(() => {
if (!coords || coords.length < 2) { if (!coords || coords.length < 2) {
return null; return { totalDistance: null, averagePace: null };
} }
let distance = 0; let distance = 0;
@@ -673,12 +673,30 @@ function Menu({data, duration, setDuration, end, setEnd, slider, setSlider, subm
distance += p1.distanceTo(p2); distance += p1.distanceTo(p2);
} }
if (distance < 1000) { const firstPointTime = moment(pointsInRange[0].time);
return `${distance.toFixed(0)} m`; const lastPointTime = moment(pointsInRange[pointsInRange.length - 1].time);
} else { const durationSeconds = lastPointTime.diff(firstPointTime, 'seconds');
return `${(distance / 1000).toFixed(2)} km`;
let pace = null;
const distanceKm = distance / 1000;
if (distanceKm > 0.01 && durationSeconds > 0) {
const paceSecondsPerKm = durationSeconds / distanceKm;
if (paceSecondsPerKm < 3600) { // cap at 60min/km
const paceMinutes = Math.floor(paceSecondsPerKm / 60);
const paceSeconds = Math.round(paceSecondsPerKm % 60);
pace = `${paceMinutes}:${paceSeconds.toString().padStart(2, '0')} /km`;
} }
}, [coords]); }
let distanceStr;
if (distance < 1000) {
distanceStr = `${distance.toFixed(0)} m`;
} else {
distanceStr = `${distanceKm.toFixed(2)} km`;
}
return { totalDistance: distanceStr, averagePace: pace };
}, [coords, pointsInRange]);
const range = parseSlider(end, duration, slider); const range = parseSlider(end, duration, slider);
const startDate = moment(end).subtract(...duration.delta); const startDate = moment(end).subtract(...duration.delta);
@@ -771,7 +789,7 @@ function Menu({data, duration, setDuration, end, setEnd, slider, setSlider, subm
return ( return (
<div className='menu'> <div className='menu'>
{(showRange || !!submenu) && <div className='range'> {(showRange || !!submenu) && <div className='range'>
{rangeTime(range[0])} - {rangeTime(range[1])} <span style={{ whiteSpace: 'nowrap' }}>({rangeDelta(range)})</span>{totalDistance && <span style={{ whiteSpace: 'nowrap' }}> ({totalDistance})</span>} {rangeTime(range[0])} - {rangeTime(range[1])} <span style={{ whiteSpace: 'nowrap' }}>({rangeDelta(range)})</span>{totalDistance && <span style={{ whiteSpace: 'nowrap' }}> ({totalDistance})</span>}{averagePace && <span style={{ whiteSpace: 'nowrap' }}> ({averagePace})</span>}
</div>} </div>}
<div className='time-slider'> <div className='time-slider'>
@@ -1002,7 +1020,7 @@ function App() {
const range = useMemo(() => parseSlider(end, duration, slider), [end, duration, slider]); const range = useMemo(() => parseSlider(end, duration, slider), [end, duration, slider]);
const coords = useMemo(() => { const pointsInRange = useMemo(() => {
// 1. Guard against invalid top-level data // 1. Guard against invalid top-level data
if (!Array.isArray(data)) { if (!Array.isArray(data)) {
return []; return [];
@@ -1032,12 +1050,14 @@ function App() {
// 6. Now that all data is known to be valid, filter by time // 6. Now that all data is known to be valid, filter by time
if (time >= startTime && time <= endTime) { if (time >= startTime && time <= endTime) {
result.push([lat, lon]); result.push({ lat, lon, time });
} }
} }
return result; return result;
}, [data, range]); }, [data, range]);
const coords = useMemo(() => pointsInRange.map(p => [p.lat, p.lon]), [pointsInRange]);
const shareStart = shareStartParam ? moment.unix(shareStartParam) : null; const shareStart = shareStartParam ? moment.unix(shareStartParam) : null;
const shareEnd = shareEndParam ? moment.unix(shareEndParam) : null; const shareEnd = shareEndParam ? moment.unix(shareEndParam) : null;
@@ -1097,6 +1117,7 @@ function App() {
data={data} data={data}
drawnItems={drawnItems} drawnItems={drawnItems}
coords={coords} coords={coords}
pointsInRange={pointsInRange}
/> />
<Map <Map