# geo (adapter system)

Geospatial capability pack: great-circle distance (haversine), initial bearing, destination point from bearing+distance, midpoint, a center+radius bounding box, geohash encode/decode, DMS<->decimal...


Geospatial capability pack — pure spherical-earth (WGS-84 mean radius, 6371.0088 km) math with **zero
dependencies**. Every tool takes ONE args object; lat/lon/bearing are degrees at the boundary.

## Tools (`adapters`)

| tool | args | result |
|------|------|--------|
| `haversine` | `{ from:{lat,lon}, to:{lat,lon}, unit='km' }` | `{ distance, distanceMeters, unit, ... }` — great-circle distance |
| `bearing` | `{ from, to }` | `{ bearing, finalBearing }` — initial (and final) bearing in degrees |
| `destination` | `{ from, bearing, distance, unit='km' }` | `{ lat, lon }` at that bearing/distance |
| `midpoint` | `{ from, to }` | `{ lat, lon }` — great-circle midpoint |
| `boundingBox` | `{ center:{lat,lon}, radius, unit='km' }` | `{ minLat, minLon, maxLat, maxLon }` |
| `geohashEncode` | `{ lat, lon, precision=9 }` | `{ hash }` |
| `geohashDecode` | `{ hash }` | `{ lat, lon, latError, lonError, bounds }` |
| `dmsToDecimal` | `{ dms }` | `{ lat, lon }` |
| `decimalToDms` | `{ lat, lon }` | `{ latDms, lonDms, formatted }` |
| `pointInPolygon` | `{ point:{lat,lon}, polygon:[[lat,lon],...] }` | `{ inside }` (boolean, ray casting) |
| `normalizeLon` | `{ lon }` | `{ lon }` wrapped into (-180, 180] |
| `clampLat` | `{ lat }` | `{ lat }` clamped to [-90, 90] |

Distance `unit` is one of `km` (default), `mi`, `m`, `nmi`.

## Usage

```js
import geo from './index.js';
geo.adapters.haversine({ from: { lat: 51.5074, lon: -0.1278 }, to: { lat: 48.8566, lon: 2.3522 } });
// → { distance: 343.55…, distanceMeters: 343556…, unit: 'km', ... }  (London → Paris ≈ 343 km)
geo.adapters.pointInPolygon({ point: { lat: 5, lon: 5 }, polygon: [[0,0],[0,10],[10,10],[10,0]] });
// → { inside: true, vertices: 4 }
```

## DRY boundary note

`geo.boundingBox` here is **CENTER + RADIUS → box** (the enclosing min/max lat/lon around a center
point within a given radius). This is deliberately DIFFERENT from **a-transformation's `geo.bbox`**,
which computes a bounding box **FROM A SET OF POINTS** (the tight envelope of a point cloud). Same word
"bounding box", different inputs and different job — pick the right one and keep them separate. Coordinate
formatting/conversion beyond DMS<->decimal is out of scope here.


---
Source: shared/engines/adapters/domain/geo/README.md
Canonical: https://docs.leumas.tech/p/adapters/domain/geo
