spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1import type { GeoPrecision } from "./types.js";23const R = 6371.0088;4export function haversineKm(lat1: number, lng1: number, lat2: number, lng2: number): number {5 const toRad = (d: number) => (d * Math.PI) / 180;6 const dLat = toRad(lat2 - lat1);7 const dLng = toRad(lng2 - lng1);8 const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) ** 2;9 return 2 * R * Math.asin(Math.sqrt(a));10}1112export function validLatLng(lat: unknown, lng: unknown): lat is number {13 return typeof lat === "number" && typeof lng === "number" && Number.isFinite(lat) && Number.isFinite(lng) && Math.abs(lat) <= 90 && Math.abs(lng) <= 180 && !(lat === 0 && lng === 0);14}1516/** Radius (km) within which two points may be the same facility, depending on the worse precision of the pair. */17export function matchRadiusKm(p: GeoPrecision, q: GeoPrecision): number {18 const rank: Record<GeoPrecision, number> = { exact: 0.25, parcel: 0.5, street: 1, city: 15, metro: 40, approximate: 25, unknown: 25 };19 return Math.max(rank[p], rank[q]);20}2122export const PRECISION_RANK: Record<GeoPrecision, number> = { exact: 6, parcel: 5, street: 4, approximate: 3, city: 2, metro: 1, unknown: 0 };2324/** Simple geohash (base32) for bucketing / cluster ids. */25export function geohash(lat: number, lng: number, precision = 6): string {26 const BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";27 let idx = 0, bit = 0, even = true, hash = "";28 let latMin = -90, latMax = 90, lngMin = -180, lngMax = 180;29 while (hash.length < precision) {30 if (even) {31 const mid = (lngMin + lngMax) / 2;32 if (lng >= mid) { idx = idx * 2 + 1; lngMin = mid; } else { idx *= 2; lngMax = mid; }33 } else {34 const mid = (latMin + latMax) / 2;35 if (lat >= mid) { idx = idx * 2 + 1; latMin = mid; } else { idx *= 2; latMax = mid; }36 }37 even = !even;38 if (++bit === 5) { hash += BASE32[idx]; bit = 0; idx = 0; }39 }40 return hash;41}4243/** Grid cell for map aggregation at a given zoom (degrees per cell shrinks with zoom). */44export function gridCell(lat: number, lng: number, zoom: number): { key: string; lat: number; lng: number } {45 const size = 360 / Math.pow(2, Math.max(0, Math.min(zoom, 12)) + 1);46 const i = Math.floor((lng + 180) / size);47 const j = Math.floor((lat + 90) / size);48 return { key: `${zoom}:${i}:${j}`, lat: j * size - 90 + size / 2, lng: i * size - 180 + size / 2 };49}50