spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
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