geo
bun add @stopcock/geo2D geometry, geodesic distance, point-in-polygon, convex hulls, and a grid-based spatial index for proximity queries.
import { distance, haversine, convexHull, createGrid, query } from '@stopcock/geo'
distance({ x: 0, y: 0 }, { x: 3, y: 4 }) // 5
// Real-world distance between two coordinateshaversine( { x: -0.1276, y: 51.5074 }, // London { x: 2.3522, y: 48.8566 }, // Paris)Real-world patterns
Section titled “Real-world patterns””Restaurants within 5km”
Section titled “”Restaurants within 5km””import { createGrid, insert, nearest, haversine } from '@stopcock/geo'
const grid = createGrid({ minX: -180, minY: -90, maxX: 180, maxY: 90 }, 0.05)for (const r of restaurants) insert(grid, { x: r.lon, y: r.lat }, r)
const nearby = nearest(grid, { x: userLon, y: userLat }, 10) .filter(r => haversine(userLat, userLon, r.data.lat, r.data.lon) < 5000)Point-in-polygon geofence
Section titled “Point-in-polygon geofence”import { contains } from '@stopcock/geo'
const deliveryZone = [ { x: -0.15, y: 51.50 }, { x: -0.08, y: 51.53 }, { x: -0.02, y: 51.50 }, { x: -0.08, y: 51.47 },]
const canDeliver = contains(deliveryZone, { x: orderLon, y: orderLat })Convex hull of a point cloud
Section titled “Convex hull of a point cloud”import { convexHull, area } from '@stopcock/geo'
const hull = convexHull(sensorReadings.map(r => ({ x: r.lon, y: r.lat })))const coverage = area(hull) // coverage area in coordinate unitstype Point = { x: number; y: number }type Polygon = Point[]type Line = [Point, Point]type Circle = { center: Point; radius: number }type BBox = { min: Point; max: Point }Basic operations
Section titled “Basic operations”distance(a: Point, b: Point): numbermidpoint(a: Point, b: Point): Pointangle(a: Point, b: Point): numberrotate(p: Point, origin: Point, radians: number): Pointtranslate(p: Point, dx: number, dy: number): Pointlerp(a: Point, b: Point, t: number): PointPolygon operations
Section titled “Polygon operations”area(polygon: Polygon): numberperimeter(polygon: Polygon): numbercentroid(polygon: Polygon): Pointcontains(polygon: Polygon, point: Point): booleanisConvex(polygon: Polygon): booleanboundingBox(polygon: Polygon): BBoxconvexHull(points: Point[]): Polygontriangulate(polygon: Polygon): [Point, Point, Point][]Intersection
Section titled “Intersection”lineIntersection(a: Line, b: Line): Point | nullsegmentIntersection(a: Line, b: Line): Point | nullcircleContains(circle: Circle, point: Point): booleancirclesIntersect(a: Circle, b: Circle): booleanpolygonsIntersect(a: Polygon, b: Polygon): booleanSpatial indexing
Section titled “Spatial indexing”Grid-based spatial index for efficient proximity queries.
type Grid<T> = { ... }type GridEntry<T> = { point: Point; data: T }
createGrid<T>(cellSize: number): Grid<T>insert<T>(grid: Grid<T>, point: Point, data: T): Grid<T>query<T>(grid: Grid<T>, bbox: BBox): GridEntry<T>[]nearest<T>(grid: Grid<T>, point: Point, k?: number): GridEntry<T>[]Geodesic
Section titled “Geodesic”Great-circle calculations for real-world coordinates.
haversine(a: Point, b: Point): number // distance in metersbearing(from: Point, to: Point): number // bearing in degrees