From 28fd782a4013f1b734c4a040f510485758259769 Mon Sep 17 00:00:00 2001 From: josh Date: Sat, 22 Aug 2026 13:11:03 +1000 Subject: [PATCH] Add a flight arc on the map for flights with same departing and arrival airport --- .../js/Components/FlightsGoneBy/FlightMap.vue | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/resources/js/Components/FlightsGoneBy/FlightMap.vue b/resources/js/Components/FlightsGoneBy/FlightMap.vue index 58a1202..f337ae6 100644 --- a/resources/js/Components/FlightsGoneBy/FlightMap.vue +++ b/resources/js/Components/FlightsGoneBy/FlightMap.vue @@ -370,13 +370,37 @@ export default defineComponent({ link.click() } + function loopPoints(origin: LngLat, radiusDeg = 0.4, steps = 48): LngLat[] { + const [lng, lat] = origin + + // Widen the longitude radius to compensate for mercator distortion at this latitude + const lngRadius = radiusDeg / Math.max(Math.cos(lat * Math.PI / 180), 0.15) + + // Center the loop above the origin so the airport point sits at the bottom of the loop + const centerLat = lat + radiusDeg + + const points: LngLat[] = [] + for (let i = 0; i <= steps; i++) { + const theta = (i / steps) * Math.PI * 2 + points.push([ + lng + Math.sin(theta) * lngRadius, + centerLat - radiusDeg * Math.cos(theta), + ]) + } + return points // first and last point === origin, at the bottom of the loop + } + const getArc = (flight: Flight): LngLat[] => { const key = routeKey(flight.departure_airport, flight.arrival_airport) if (!arcCache.has(key)) { - arcCache.set(key, greatCirclePoints( - [flight.departure_airport.longitude_deg, flight.departure_airport.latitude_deg], - [flight.arrival_airport.longitude_deg, flight.arrival_airport.latitude_deg], - )) + const isSameAirport = flight.departure_airport.id === flight.arrival_airport.id + + arcCache.set(key, isSameAirport + ? loopPoints([flight.departure_airport.longitude_deg, flight.departure_airport.latitude_deg]) + : greatCirclePoints( + [flight.departure_airport.longitude_deg, flight.departure_airport.latitude_deg], + [flight.arrival_airport.longitude_deg, flight.arrival_airport.latitude_deg], + )) } return arcCache.get(key)! } @@ -666,8 +690,10 @@ export default defineComponent({ ? { top: 20, bottom: 20, left: 55, right: 55 } : { top: 60, bottom: 60, left: 60, right: 60 } - const lngs = props.flights.flatMap(f => [f.departure_airport.longitude_deg, f.arrival_airport.longitude_deg]) - const lats = props.flights.flatMap(f => [f.departure_airport.latitude_deg, f.arrival_airport.latitude_deg]) + // Use full arc/loop geometry, not just endpoints, so loops aren't clipped + const allPoints = props.flights.flatMap(getArc) + const lngs = allPoints.map(p => p[0]) + const lats = allPoints.map(p => p[1]) const minLat = Math.min(...lats) const maxLat = Math.max(...lats) @@ -689,7 +715,6 @@ export default defineComponent({ } map!.fitBounds([[minLng, minLat], [maxLng, maxLat]], { padding, duration: 0 }) } - // ── Map init ────────────────────────────────────────────────────────── const initMap = (): void => { -- 2.54.0