This commit is contained in:
2026-06-07 12:50:04 +02:00
parent eb64b8f2f9
commit 9189f54df5
7 changed files with 126 additions and 9 deletions
+9 -2
View File
@@ -68,7 +68,7 @@ import {
Select, SelectTrigger, SelectValue, SelectContent, SelectItem,
} from '@/components/ui/select';
import { cn } from '@/lib/utils';
import { pathBetween } from '@/lib/maidenhead';
import { pathBetween, pathBetweenLatLon, gridToLatLon } from '@/lib/maidenhead';
import { flagURL } from '@/lib/flags';
type QSO = QSOForm;
@@ -1890,7 +1890,14 @@ export default function App() {
both directly clickable, plus an always-visible Stop. The
old Shift/Ctrl shortcuts were not discoverable enough. */}
{(() => {
const p = pathBetween(station.my_grid, grid);
// Prefer grid-to-grid; fall back to lat/lon when the DX has no
// grid but a known location (e.g. cty.dat-only entities like
// Svalbard → no QRZ grid, but cty.dat gives coordinates).
const myLL = gridToLatLon(station.my_grid);
const p = pathBetween(station.my_grid, grid)
?? (myLL && details.lat != null && details.lon != null
? pathBetweenLatLon(myLL, { lat: details.lat, lon: details.lon })
: null);
const disabled = !p;
const goto = (az: number) => RotatorGoTo(Math.round(az), -1).catch((err) => setError(String(err?.message ?? err)));
return (
+12 -5
View File
@@ -6,7 +6,7 @@ import {
Select, SelectTrigger, SelectValue, SelectContent, SelectItem,
} from '@/components/ui/select';
import { cn } from '@/lib/utils';
import { pathBetween } from '@/lib/maidenhead';
import { pathBetween, pathBetweenLatLon, gridToLatLon } from '@/lib/maidenhead';
import { BandSlotGrid } from '@/components/BandSlotGrid';
import { AwardRefSelector } from '@/components/AwardRefSelector';
@@ -122,10 +122,17 @@ export function DetailsPanel({ callsign: _cs, prefix, operatorGrid, remoteGrid,
const open = tab ?? internalOpen; // controlled when `tab` is provided
// Bearing/distance from operator's home grid to the remote station.
// Recomputed only when either grid actually changes.
const path = useMemo(
() => pathBetween(operatorGrid, remoteGrid),
[operatorGrid, remoteGrid],
);
const path = useMemo(() => {
const byGrid = pathBetween(operatorGrid, remoteGrid);
if (byGrid) return byGrid;
// Fall back to lat/lon when the DX has coordinates but no grid (e.g. a
// cty.dat-only entity like Svalbard: no QRZ grid, but cty.dat coordinates).
const myLL = gridToLatLon(operatorGrid);
if (myLL && details.lat != null && details.lon != null) {
return pathBetweenLatLon(myLL, { lat: details.lat, lon: details.lon });
}
return null;
}, [operatorGrid, remoteGrid, details.lat, details.lon]);
const fmtDeg = (n: number) => `${Math.round(n)}°`;
const fmtKm = (n: number) => `${Math.round(n).toLocaleString()} km`;
+10
View File
@@ -77,6 +77,16 @@ export function pathBetween(fromGrid: string, toGrid: string): PathInfo | null {
const a = gridToLatLon(fromGrid);
const b = gridToLatLon(toGrid);
if (!a || !b) return null;
return pathBetweenLatLon(a, b);
}
// pathBetweenLatLon computes the great-circle path between two lat/lon points.
// Used as a fallback when a station has a known location (e.g. cty.dat entity
// coordinates for Svalbard) but no Maidenhead grid.
export function pathBetweenLatLon(
a: { lat: number; lon: number },
b: { lat: number; lon: number },
): PathInfo {
const φ1 = toRad(a.lat);
const φ2 = toRad(b.lat);
const Δλ = toRad(b.lon - a.lon);