{
  "schema": "leumas.docs.page/1",
  "id": "adapter:domain/geo",
  "slug": "adapters/domain/geo",
  "kind": "capabilities",
  "bucket": "package",
  "title": "geo (adapter system)",
  "name": "geo",
  "eyebrow": "adapter system",
  "chip": null,
  "summary": "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...",
  "keywords": [
    "geo",
    "geospatial",
    "great-circle",
    "haversine",
    "bearing",
    "center",
    "geohash",
    "spherical-earth"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "# geo (adapter system)\n\nGeospatial capability pack — pure spherical-earth (WGS-84 mean radius, 6371.0088 km) math with **zero\ndependencies**. Every tool takes ONE args object; lat/lon/bearing are degrees at the boundary.\n\n## Tools (`adapters`)\n\n| tool | args | result |\n|------|------|--------|\n| `haversine` | `{ from:{lat,lon}, to:{lat,lon}, unit='km' }` | `{ distance, distanceMeters, unit, ... }` — great-circle distance |\n| `bearing` | `{ from, to }` | `{ bearing, finalBearing }` — initial (and final) bearing in degrees |\n| `destination` | `{ from, bearing, distance, unit='km' }` | `{ lat, lon }` at that bearing/distance |\n| `midpoint` | `{ from, to }` | `{ lat, lon }` — great-circle midpoint |\n| `boundingBox` | `{ center:{lat,lon}, radius, unit='km' }` | `{ minLat, minLon, maxLat, maxLon }` |\n| `geohashEncode` | `{ lat, lon, precision=9 }` | `{ hash }` |\n| `geohashDecode` | `{ hash }` | `{ lat, lon, latError, lonError, bounds }` |\n| `dmsToDecimal` | `{ dms }` | `{ lat, lon }` |\n| `decimalToDms` | `{ lat, lon }` | `{ latDms, lonDms, formatted }` |\n| `pointInPolygon` | `{ point:{lat,lon}, polygon:[[lat,lon],...] }` | `{ inside }` (boolean, ray casting) |\n| `normalizeLon` | `{ lon }` | `{ lon }` wrapped into (-180, 180] |\n| `clampLat` | `{ lat }` | `{ lat }` clamped to [-90, 90] |\n\nDistance `unit` is one of `km` (default), `mi`, `m`, `nmi`.\n\n## Usage\n\n```js\nimport geo from './index.js';\ngeo.adapters.haversine({ from: { lat: 51.5074, lon: -0.1278 }, to: { lat: 48.8566, lon: 2.3522 } });\n// → { distance: 343.55…, distanceMeters: 343556…, unit: 'km', ... }  (London → Paris ≈ 343 km)\ngeo.adapters.pointInPolygon({ point: { lat: 5, lon: 5 }, polygon: [[0,0],[0,10],[10,10],[10,0]] });\n// → { inside: true, vertices: 4 }\n```\n\n## DRY boundary note\n\n`geo.boundingBox` here is **CENTER + RADIUS → box** (the enclosing min/max lat/lon around a center\npoint within a given radius). This is deliberately DIFFERENT from **a-transformation's `geo.bbox`**,\nwhich computes a bounding box **FROM A SET OF POINTS** (the tight envelope of a point cloud). Same word\n\"bounding box\", different inputs and different job — pick the right one and keep them separate. Coordinate\nformatting/conversion beyond DMS<->decimal is out of scope here.\n",
  "source": {
    "path": "shared/engines/adapters/domain/geo/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 2454,
    "hash": "dde1d9d174b2885fb964f4ccda99b5acaf38809d"
  },
  "urls": {
    "html": "/p/adapters/domain/geo",
    "json": "/docs/adapters/domain/geo.json",
    "md": "/docs/adapters/domain/geo.md"
  },
  "links": {
    "composes": [],
    "usedBy": [],
    "product": [],
    "howTo": [],
    "skills": []
  }
}
