Open
Description
Remote control mode:
- Enter timing session request
- Check "Drag Race" option
- Number of laps parameter is hidden, we will use 1
- configure additional parameters:
- START DELAY: how long to wait after the session request is picked up to start the timer
- After start delay, play countdown beeps
- Start timing session after the last beep
- Stop timing session after motion is detected
Drag race sessions should be displayed differently:
- Best Lap, Worst lap should be ignored (there is only one lap)
Component to measure 132 foot track?
- Configurable track length? Default to 132?
PROTOTYPE:
https://claude.ai/chat/7a0e4310-d9b5-41dd-a21d-4f9d01e3bd7c
This implementation:
- Uses GPS to mark start line
- Continuously updates distance as user walks
- Shows directional arrow to maintain straight line
- Alerts when 132ft reached
- Main limitation: GPS accuracy (typically ±15-50ft). Best results in open areas away from buildings.
import React, { useState, useEffect } from 'react';
import { MapPin, Navigation2 } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
const TrackMeasurer = () => {
const [startPosition, setStartPosition] = useState(null);
const [currentPosition, setCurrentPosition] = useState(null);
const [distance, setDistance] = useState(0);
const [heading, setHeading] = useState(0);
const TARGET_DISTANCE = 132; // feet
// Calculate distance between two points using Haversine formula
const calculateDistance = (lat1, lon1, lat2, lon2) => {
const R = 20902231; // Earth radius in feet
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
};
const handleStartPosition = () => {
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
setStartPosition({
lat: position.coords.latitude,
lon: position.coords.longitude
});
});
}
};
useEffect(() => {
let watchId;
if (startPosition && 'geolocation' in navigator) {
watchId = navigator.geolocation.watchPosition((position) => {
const current = {
lat: position.coords.latitude,
lon: position.coords.longitude
};
setCurrentPosition(current);
const dist = calculateDistance(
startPosition.lat,
startPosition.lon,
current.lat,
current.lon
);
setDistance(dist);
// Calculate heading
const dLon = (current.lon - startPosition.lon) * Math.PI / 180;
const y = Math.sin(dLon) * Math.cos(current.lat * Math.PI / 180);
const x = Math.cos(startPosition.lat * Math.PI / 180) * Math.sin(current.lat * Math.PI / 180) -
Math.sin(startPosition.lat * Math.PI / 180) * Math.cos(current.lat * Math.PI / 180) * Math.cos(dLon);
const brng = Math.atan2(y, x) * 180 / Math.PI;
setHeading((brng + 360) % 360);
});
}
return () => {
if (watchId) navigator.geolocation.clearWatch(watchId);
};
}, [startPosition]);
return (
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>RC Track Measurer</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-col items-center gap-4">
{!startPosition ? (
<button
onClick={handleStartPosition}
className="flex items-center gap-2 px-4 py-2 bg-blue-500 text-white rounded-lg"
>
<MapPin size={20} />
Mark Start Line
</button>
) : (
<div className="text-center space-y-4">
<div className="flex items-center justify-center text-4xl">
<Navigation2
size={48}
style={{ transform: `rotate(${heading}deg)` }}
className="text-blue-500"
/>
</div>
<div className="space-y-2">
<p className="text-2xl font-bold">
{distance.toFixed(1)} ft
</p>
<p className={`text-lg ${Math.abs(distance - TARGET_DISTANCE) < 1 ? 'text-green-500' : ''}`}>
{distance < TARGET_DISTANCE
? `Keep walking: ${(TARGET_DISTANCE - distance).toFixed(1)} ft to go`
: `Too far: ${(distance - TARGET_DISTANCE).toFixed(1)} ft past`}
</p>
</div>
</div>
)}
</div>
</CardContent>
</Card>
);
};
export default TrackMeasurer;