Skip to content

geo

Terminal window
bun add @stopcock/geo

2D 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 coordinates
haversine(
{ x: -0.1276, y: 51.5074 }, // London
{ x: 2.3522, y: 48.8566 }, // Paris
)
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)
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 })
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 units
type Point = { x: number; y: number }
type Polygon = Point[]
type Line = [Point, Point]
type Circle = { center: Point; radius: number }
type BBox = { min: Point; max: Point }
distance(a: Point, b: Point): number
midpoint(a: Point, b: Point): Point
angle(a: Point, b: Point): number
rotate(p: Point, origin: Point, radians: number): Point
translate(p: Point, dx: number, dy: number): Point
lerp(a: Point, b: Point, t: number): Point
area(polygon: Polygon): number
perimeter(polygon: Polygon): number
centroid(polygon: Polygon): Point
contains(polygon: Polygon, point: Point): boolean
isConvex(polygon: Polygon): boolean
boundingBox(polygon: Polygon): BBox
convexHull(points: Point[]): Polygon
triangulate(polygon: Polygon): [Point, Point, Point][]
lineIntersection(a: Line, b: Line): Point | null
segmentIntersection(a: Line, b: Line): Point | null
circleContains(circle: Circle, point: Point): boolean
circlesIntersect(a: Circle, b: Circle): boolean
polygonsIntersect(a: Polygon, b: Polygon): boolean

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>[]

Great-circle calculations for real-world coordinates.

haversine(a: Point, b: Point): number // distance in meters
bearing(from: Point, to: Point): number // bearing in degrees