GdzieWjade/src/lib/geo.ts
2026-10-03 13:33:50 +02:00

67 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const R = 6371008.8;
const rad = (d: number) => (d * Math.PI) / 180;
export type LatLon = readonly [lat: number, lon: number];
export function haversine(a: LatLon, b: LatLon): number {
const dLat = rad(b[0] - a[0]);
const dLon = rad(b[1] - a[1]);
const s = Math.sin(dLat / 2) ** 2 + Math.cos(rad(a[0])) * Math.cos(rad(b[0])) * Math.sin(dLon / 2) ** 2;
return 2 * R * Math.asin(Math.min(1, Math.sqrt(s)));
}
/** Initial bearing a→b in degrees, 0 = north. */
export function bearing(a: LatLon, b: LatLon): number {
const y = Math.sin(rad(b[1] - a[1])) * Math.cos(rad(b[0]));
const x =
Math.cos(rad(a[0])) * Math.sin(rad(b[0])) -
Math.sin(rad(a[0])) * Math.cos(rad(b[0])) * Math.cos(rad(b[1] - a[1]));
return ((Math.atan2(y, x) * 180) / Math.PI + 360) % 360;
}
/** Distance in metres from point p to segment a–b (local equirectangular projection). */
export function distToSegment(p: LatLon, a: LatLon, b: LatLon): number {
const k = Math.cos(rad(p[0]));
const px = p[1] * k, py = p[0];
const ax = a[1] * k, ay = a[0];
const bx = b[1] * k, by = b[0];
const dx = bx - ax, dy = by - ay;
const len2 = dx * dx + dy * dy;
const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / len2));
const cx = ax + t * dx, cy = ay + t * dy;
return Math.hypot(px - cx, py - cy) * (Math.PI / 180) * R;
}
/** Uniform grid for neighbourhood lookups; cell size in degrees of latitude. */
export class Grid<T> {
private cells = new Map<string, T[]>();
constructor(private cell = 0.0006) {}
private key(ix: number, iy: number) {
return `${ix}:${iy}`;
}
private ix(lon: number) {
return Math.floor(lon / (this.cell / Math.cos(rad(50))));
}
private iy(lat: number) {
return Math.floor(lat / this.cell);
}
add(lat: number, lon: number, item: T) {
const k = this.key(this.ix(lon), this.iy(lat));
const list = this.cells.get(k);
if (list) list.push(item);
else this.cells.set(k, [item]);
}
/** Items whose cell is within `radiusM` of the point (superset; caller refines). */
near(lat: number, lon: number, radiusM: number): T[] {
const dLat = radiusM / 111_320;
const x0 = this.ix(lon - dLat / Math.cos(rad(lat))), x1 = this.ix(lon + dLat / Math.cos(rad(lat)));
const y0 = this.iy(lat - dLat), y1 = this.iy(lat + dLat);
const out: T[] = [];
for (let x = x0; x <= x1; x++)
for (let y = y0; y <= y1; y++) {
const l = this.cells.get(this.key(x, y));
if (l) out.push(...l);
}
return out;
}
}