SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
4 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
1.6 KB · 37 lines typescript
Raw Blame History
1/** Geo helpers for reconciliation (bounding boxes, precision comparisons). */2import { haversineKm, PRECISION_RANK, validLatLng, type GeoPoint, type GeoPrecision } from "@dci/core";34export interface BBox {5  minLat: number;6  maxLat: number;7  minLng: number;8  maxLng: number;9}1011/** Bounding box around a point for a radius in km (cheap pre-filter before haversine). */12export function bboxAround(lat: number, lng: number, radiusKm: number): BBox {13  const dLat = radiusKm / 111.32;14  const cos = Math.max(0.05, Math.cos((lat * Math.PI) / 180));15  const dLng = radiusKm / (111.32 * cos);16  return { minLat: Math.max(-90, lat - dLat), maxLat: Math.min(90, lat + dLat), minLng: Math.max(-180, lng - dLng), maxLng: Math.min(180, lng + dLng) };17}1819export function distanceKm(a: { lat: number; lng: number }, b: { lat: number; lng: number }): number {20  return haversineKm(a.lat, a.lng, b.lat, b.lng);21}2223export function asPrecision(p: string | null | undefined): GeoPrecision {24  return p && p in PRECISION_RANK ? (p as GeoPrecision) : "unknown";25}2627/** True when the incoming point may replace the stored one (never degrade precision). */28export function mayReplaceGeo(incoming: GeoPoint | null | undefined, storedPrecision: string | null | undefined, storedHasCoords: boolean): boolean {29  if (!incoming || !validLatLng(incoming.lat, incoming.lng)) return false;30  if (!storedHasCoords) return true;31  return PRECISION_RANK[incoming.precision] >= PRECISION_RANK[asPrecision(storedPrecision)];32}3334export function validGeo(g: GeoPoint | null | undefined): g is GeoPoint {35  return !!g && validLatLng(g.lat, g.lng);36}37