map
This commit is contained in:
@@ -49,6 +49,19 @@ export function gridToLatLon(grid: string): { lat: number; lon: number } | null
|
||||
return { lat, lon };
|
||||
}
|
||||
|
||||
// gridSquareBounds returns the SW/NE corners of a Maidenhead square so a map
|
||||
// can draw its outline. Half-extents shrink with locator precision.
|
||||
export function gridSquareBounds(grid: string):
|
||||
{ south: number; west: number; north: number; east: number } | null {
|
||||
const c = gridToLatLon(grid);
|
||||
if (!c) return null;
|
||||
const g = grid.trim();
|
||||
let dLon = 1, dLat = 0.5; // 4-char square: 2°×1°
|
||||
if (g.length >= 6) { dLon = 1 / 24; dLat = 0.5 / 24; }
|
||||
if (g.length >= 8) { dLon = 1 / 24 / 10; dLat = 0.5 / 24 / 10; }
|
||||
return { south: c.lat - dLat, north: c.lat + dLat, west: c.lon - dLon, east: c.lon + dLon };
|
||||
}
|
||||
|
||||
// PathInfo describes both short and long great-circle path between two
|
||||
// points. Bearing in degrees from true north (0–360). Distance in km.
|
||||
export interface PathInfo {
|
||||
@@ -88,5 +101,41 @@ export function pathBetween(fromGrid: string, toGrid: string): PathInfo | null {
|
||||
};
|
||||
}
|
||||
|
||||
// greatCirclePoints returns n+1 [lat, lon] points along the short great-circle
|
||||
// path between two lat/lon points (spherical slerp). Longitudes are unwrapped
|
||||
// to stay continuous (no ±180 jump) so a map polyline draws as one smooth arc.
|
||||
export function greatCirclePoints(
|
||||
lat1: number, lon1: number, lat2: number, lon2: number, n = 96,
|
||||
): [number, number][] {
|
||||
const φ1 = toRad(lat1), λ1 = toRad(lon1);
|
||||
const φ2 = toRad(lat2), λ2 = toRad(lon2);
|
||||
// Angular distance between the two points.
|
||||
const sinΔφ = Math.sin((φ2 - φ1) / 2);
|
||||
const sinΔλ = Math.sin((λ2 - λ1) / 2);
|
||||
const h = sinΔφ * sinΔφ + Math.cos(φ1) * Math.cos(φ2) * sinΔλ * sinΔλ;
|
||||
const d = 2 * Math.asin(Math.min(1, Math.sqrt(h)));
|
||||
const out: [number, number][] = [];
|
||||
if (d === 0) return [[lat1, lon1]];
|
||||
let prevLon = NaN;
|
||||
for (let i = 0; i <= n; i++) {
|
||||
const f = i / n;
|
||||
const A = Math.sin((1 - f) * d) / Math.sin(d);
|
||||
const B = Math.sin(f * d) / Math.sin(d);
|
||||
const x = A * Math.cos(φ1) * Math.cos(λ1) + B * Math.cos(φ2) * Math.cos(λ2);
|
||||
const y = A * Math.cos(φ1) * Math.sin(λ1) + B * Math.cos(φ2) * Math.sin(λ2);
|
||||
const z = A * Math.sin(φ1) + B * Math.sin(φ2);
|
||||
const lat = toDeg(Math.atan2(z, Math.sqrt(x * x + y * y)));
|
||||
let lon = toDeg(Math.atan2(y, x));
|
||||
// Unwrap longitude so the polyline never snaps across the whole map.
|
||||
if (!Number.isNaN(prevLon)) {
|
||||
while (lon - prevLon > 180) lon -= 360;
|
||||
while (lon - prevLon < -180) lon += 360;
|
||||
}
|
||||
prevLon = lon;
|
||||
out.push([lat, lon]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function toRad(d: number): number { return (d * Math.PI) / 180; }
|
||||
function toDeg(r: number): number { return (r * 180) / Math.PI; }
|
||||
|
||||
Reference in New Issue
Block a user