Spaces:
Sleeping
Sleeping
| import { db } from '../db/database'; | |
| // Great-circle distance between two points in kilometres. | |
| function haversineKm(lat1: number, lng1: number, lat2: number, lng2: number): number { | |
| const R = 6371; | |
| const toRad = (deg: number) => (deg * Math.PI) / 180; | |
| const dLat = toRad(lat2 - lat1); | |
| const dLng = toRad(lng2 - lng1); | |
| const a = | |
| Math.sin(dLat / 2) ** 2 + | |
| Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) ** 2; | |
| return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | |
| } | |
| /** | |
| * Total flight distance a user has covered, summed across every non-cancelled | |
| * flight reservation in their trips. Each flight stores its waypoints in | |
| * reservation_endpoints (from → stops → to, ordered by sequence); we add up the | |
| * legs between consecutive points so multi-stop flights count correctly. | |
| */ | |
| export function getFlightDistanceKm(userId: number): number { | |
| const rows = db.prepare(` | |
| SELECT re.reservation_id, re.lat, re.lng | |
| FROM reservation_endpoints re | |
| JOIN reservations r ON r.id = re.reservation_id | |
| JOIN trips t ON t.id = r.trip_id | |
| LEFT JOIN trip_members tm ON tm.trip_id = t.id AND tm.user_id = ? | |
| WHERE (t.user_id = ? OR tm.user_id IS NOT NULL) | |
| AND r.type = 'flight' | |
| AND r.status != 'cancelled' | |
| ORDER BY re.reservation_id, re.sequence | |
| `).all(userId, userId) as { reservation_id: number; lat: number; lng: number }[]; | |
| let total = 0; | |
| let prev: { id: number; lat: number; lng: number } | null = null; | |
| for (const point of rows) { | |
| if (prev && prev.id === point.reservation_id) { | |
| total += haversineKm(prev.lat, prev.lng, point.lat, point.lng); | |
| } | |
| prev = { id: point.reservation_id, lat: point.lat, lng: point.lng }; | |
| } | |
| return Math.round(total); | |
| } | |