moongeokit

Robust 2D geometry and observable spatial indexing for MoonBit.

geometry
spatial-index
uniform-grid
polygon
gis
moon add cn-xjr/moongeokit@0.3.0
Download zip
Author
Version
0.3.0
License
Apache-2.0
Last updated
last month
Downloads
18
README

#MoonGeoKit

MoonGeoKit provides deterministic two-dimensional geometry and a mutable uniform-grid spatial index for MoonBit. It runs with the same public API on native, JavaScript, Wasm, and Wasm-GC targets.

#Geometry

  • Point, Segment, Bounds, Polyline, and Polygon primitives.
  • Vector arithmetic, projections, distance, intersections, convex hulls, containment, area/perimeter, and area-weighted polygon centroids.
  • Tolerant geometry predicates (*_eps) for measured coordinates.
  • GeoJSON export for Point, LineString, Polygon, bounds, and indexed features.

#Spatial indexing

SpatialIndex is a simple linear baseline. GridSpatialIndex is intended for bounded map/editor worlds: it stores AABBs in every intersected grid cell, deduplicates range candidates, and reports both candidates scanned and buckets visited. It supports incremental single operations as well as batched insert, update, and delete operations. A batch rebuilds the bucket table once.

///|
test {
let index = GridSpatialIndex::new(Bounds::new(0.0, 0.0, 100.0, 100.0), 10, 10)
ignore(
index.insert_many([
SpatialItem::new(1, Bounds::new(10.0, 10.0, 14.0, 14.0)),
SpatialItem::new(2, Bounds::new(40.0, 40.0, 42.0, 42.0)),
]),
)
match index.query_nearest(Point::new(16.0, 12.0), 10.0) {
Some(item) => assert_eq(item.id, 1)
None => fail("expected an item")
}
}

#Verification

moon fmt --check moon check --deny-warn --target all moon info && git diff --exit-code -- '*.mbti' moon test --deny-warn --target all moon run cmd/main --target js moon run cmd/bench --target js

MoonBit 0.10.4 does not support --deny-warn on moon fmt or moon info; the first and third commands are the supported strict equivalents. The CI file runs these exact commands. The benchmark reports deterministic 1k, 10k, and ~100k candidate/bucket evidence instead of machine-specific elapsed-time claims.

#
Bounds

pub(all) struct Bounds {
min_x : Double
min_y : Double
max_x : Double
max_y : Double
} derive(
Debug
)

#
Bounds::contains

fn Bounds::contains(self : Bounds, point : Point) -> Bool

#
Bounds::distance_to_point

fn Bounds::distance_to_point(self : Bounds, point : Point) -> Double

Returns the Euclidean distance from a point to this axis-aligned bounds. The result is zero when the point is inside or on the bounds.

#
Bounds::from_points

fn Bounds::from_points(points : Array[Point]) -> Bounds

#
Bounds::height

fn Bounds::height(self : Bounds) -> Double

#
Bounds::intersects

fn Bounds::intersects(self : Bounds, other : Bounds) -> Bool

#
Bounds::new

fn Bounds::new(min_x : Double, min_y : Double, max_x : Double, max_y : Double) -> Bounds

#
Bounds::to_geojson

fn Bounds::to_geojson(self : Bounds) -> String

Encodes bounds as a GeoJSON Polygon geometry object.

#
Bounds::to_json

fn Bounds::to_json(self : Bounds) -> String

#
Bounds::union

fn Bounds::union(self : Bounds, other : Bounds) -> Bounds

#
Bounds::width

fn Bounds::width(self : Bounds) -> Double

#
GeometrySummary

pub(all) struct GeometrySummary {
points : Int
segments : Int
polygons : Int
bounds : Bounds
} derive(
Debug
)

#
GeometrySummary::new

fn GeometrySummary::new(points : Int, segments : Int, polygons : Int, bounds : Bounds) -> GeometrySummary

#
GeometrySummary::to_json

fn GeometrySummary::to_json(self : GeometrySummary) -> String

#
GridSpatialIndex

pub(all) struct GridSpatialIndex {
world : Bounds
columns : Int
rows : Int
items : Array[SpatialItem]
buckets : Array[Array[Int]]
} derive(
Debug
)

A fixed-world uniform grid index for editor, map, and simulation workloads.

Items spanning several cells are stored in each covered bucket. Queries deduplicate candidates before applying exact AABB tests.

#
GridSpatialIndex::contains_id

fn GridSpatialIndex::contains_id(self : GridSpatialIndex, id : Int) -> Bool

#
GridSpatialIndex::insert

fn GridSpatialIndex::insert(self : GridSpatialIndex, item : SpatialItem) -> Bool

Inserts an item when its bounds overlap the configured world.

#
GridSpatialIndex::insert_many

fn GridSpatialIndex::insert_many(self : GridSpatialIndex, items : Array[SpatialItem]) -> Int

Inserts every in-world item and returns the number accepted. This is the preferred ingestion API for tiles, entity snapshots, and GeoJSON features.

#
GridSpatialIndex::length

fn GridSpatialIndex::length(self : GridSpatialIndex) -> Int

#
GridSpatialIndex::new

fn GridSpatialIndex::new(world : Bounds, columns : Int, rows : Int) -> GridSpatialIndex

#
GridSpatialIndex::query_bounds

fn GridSpatialIndex::query_bounds(self : GridSpatialIndex, query : Bounds) -> SpatialQueryResult

#
GridSpatialIndex::query_nearest

fn GridSpatialIndex::query_nearest(self : GridSpatialIndex, point : Point, max_distance : Double) -> SpatialItem?

Finds the closest indexed bounds within max_distance of point.

It uses a bounded grid query before exact point-to-AABB distance tests, so map clients can keep a stable interaction radius without scanning the whole world. None means no candidate overlaps the search envelope.

#
GridSpatialIndex::query_point

fn GridSpatialIndex::query_point(self : GridSpatialIndex, point : Point) -> SpatialQueryResult

#
GridSpatialIndex::remove

fn GridSpatialIndex::remove(self : GridSpatialIndex, id : Int) -> Bool

Removes the first item with id and rebuilds bucket membership.

Rebuilding is deterministic and keeps deletion semantics simple for map editors where the index is updated less often than it is queried.

#
GridSpatialIndex::remove_many

fn GridSpatialIndex::remove_many(self : GridSpatialIndex, ids : Array[Int]) -> Int

Removes every item whose id occurs in ids and rebuilds once. The result is the number of records actually removed, not the number of requested ids.

#
GridSpatialIndex::update

fn GridSpatialIndex::update(self : GridSpatialIndex, id : Int, bounds : Bounds) -> Bool

Replaces an item's bounds and refreshes its covered grid buckets.

#
GridSpatialIndex::update_many

fn GridSpatialIndex::update_many(self : GridSpatialIndex, items : Array[SpatialItem]) -> Int

Applies a batch of id/bounds replacements and rebuilds bucket membership once. Invalid ids and bounds outside the configured world are skipped.

#
Point

pub(all) struct Point {
x : Double
y : Double
} derive(
Debug
)

#
Point::add

fn Point::add(self : Point, other : Point) -> Point

#
Point::cross

fn Point::cross(self : Point, other : Point) -> Double

#
Point::distance_to

fn Point::distance_to(self : Point, other : Point) -> Double

#
Point::dot

fn Point::dot(self : Point, other : Point) -> Double

#
Point::length

fn Point::length(self : Point) -> Double

#
Point::new

fn Point::new(x : Double, y : Double) -> Point

#
Point::scale

fn Point::scale(self : Point, factor : Double) -> Point

#
Point::sub

fn Point::sub(self : Point, other : Point) -> Point

#
Point::to_geojson

fn Point::to_geojson(self : Point) -> String

Encodes a point as a GeoJSON geometry object. Coordinates intentionally remain numeric so the output can be passed directly to a Web/Wasm client.

#
Point::to_json

fn Point::to_json(self : Point) -> String

#
Polygon

pub(all) struct Polygon {
points : Array[Point]
} derive(
Debug
)

#
Polygon::area

fn Polygon::area(self : Polygon) -> Double

#
Polygon::bounds

fn Polygon::bounds(self : Polygon) -> Bounds

#
Polygon::centroid

fn Polygon::centroid(self : Polygon) -> Point

Computes the area-weighted centroid of a polygon ring.

Degenerate rings have no stable area centroid, so their vertex average is returned instead. This makes the function suitable for editor previews and GeoJSON data that may contain line-like polygons.

#
Polygon::centroid_average

fn Polygon::centroid_average(self : Polygon) -> Point

#
Polygon::contains_point

fn Polygon::contains_point(self : Polygon, point : Point) -> Bool

#
Polygon::new

fn Polygon::new(points : Array[Point]) -> Polygon

#
Polygon::perimeter

fn Polygon::perimeter(self : Polygon) -> Double

#
Polygon::signed_area

fn Polygon::signed_area(self : Polygon) -> Double

#
Polygon::to_geojson

fn Polygon::to_geojson(self : Polygon) -> String

Encodes a polygon as a GeoJSON Polygon geometry object. GeoJSON requires a closed exterior ring; this function closes a non-empty ring on export.

#
Polygon::to_json

fn Polygon::to_json(self : Polygon) -> String

#
Polygon::vertex_count

fn Polygon::vertex_count(self : Polygon) -> Int

#
Polyline

pub(all) struct Polyline {
points : Array[Point]
} derive(
Debug
)

#
Polyline::bounds

fn Polyline::bounds(self : Polyline) -> Bounds

#
Polyline::closest_point

fn Polyline::closest_point(self : Polyline, point : Point) -> Point

#
Polyline::distance_to

fn Polyline::distance_to(self : Polyline, point : Point) -> Double

#
Polyline::length

fn Polyline::length(self : Polyline) -> Double

#
Polyline::new

fn Polyline::new(points : Array[Point]) -> Polyline

#
Polyline::simplify_by_distance

fn Polyline::simplify_by_distance(self : Polyline, min_distance : Double) -> Polyline

#
Polyline::to_geojson

fn Polyline::to_geojson(self : Polyline) -> String

Encodes a polyline as a GeoJSON LineString geometry object.

#
Segment

pub(all) struct Segment {
start : Point
end : Point
} derive(
Debug
)

#
Segment::bounds

fn Segment::bounds(self : Segment) -> Bounds

#
Segment::new

fn Segment::new(start : Point, end : Point) -> Segment

#
SpatialIndex

pub(all) struct SpatialIndex {
items : Array[SpatialItem]
} derive(
Debug
)

#
SpatialIndex::bounds

fn SpatialIndex::bounds(self : SpatialIndex) -> Bounds

#
SpatialIndex::from_items

fn SpatialIndex::from_items(items : Array[SpatialItem]) -> SpatialIndex

#
SpatialIndex::insert

fn SpatialIndex::insert(self : SpatialIndex, item : SpatialItem) -> Unit

#
SpatialIndex::new

#
SpatialIndex::query_bounds

fn SpatialIndex::query_bounds(self : SpatialIndex, bounds : Bounds) -> Array[SpatialItem]

#
SpatialIndex::query_point

fn SpatialIndex::query_point(self : SpatialIndex, point : Point) -> Array[SpatialItem]

#
SpatialIndex::to_json

fn SpatialIndex::to_json(self : SpatialIndex) -> String

#
SpatialItem

pub(all) struct SpatialItem {
id : Int
bounds : Bounds
} derive(
Debug
)

#
SpatialItem::new

fn SpatialItem::new(id : Int, bounds : Bounds) -> SpatialItem

#
SpatialItem::to_geojson_feature

fn SpatialItem::to_geojson_feature(self : SpatialItem) -> String

Encodes an indexed bounds record as a GeoJSON Feature with a numeric id.

#
SpatialItem::to_json

fn SpatialItem::to_json(self : SpatialItem) -> String

#
SpatialQueryResult

pub(all) struct SpatialQueryResult {
items : Array[SpatialItem]
candidates_scanned : Int
buckets_visited : Int
} derive(
Debug
)

Query output with performance evidence exposed to callers and benchmarks.

#
closest_point_on_segment

fn closest_point_on_segment(point : Point, segment : Segment) -> Point

#
convex_hull

fn convex_hull(points : Array[Point]) -> Polygon

#
distance_to_segment

fn distance_to_segment(point : Point, segment : Segment) -> Double

#
orientation

fn orientation(a : Point, b : Point, c : Point) -> Double

#
orientation_sign

fn orientation_sign(a : Point, b : Point, c : Point, epsilon : Double) -> Int

Classifies orientation using a caller-provided tolerance.

Returns 1 for counter-clockwise, -1 for clockwise, and 0 when the signed area lies within the tolerance band.

#
point_on_segment

fn point_on_segment(point : Point, segment : Segment) -> Bool

#
point_on_segment_eps

fn point_on_segment_eps(point : Point, segment : Segment, epsilon : Double) -> Bool

Tolerant point-on-segment test for measured or transformed coordinates.

#
segments_intersect

fn segments_intersect(a : Segment, b : Segment) -> Bool

#
segments_intersect_eps

fn segments_intersect_eps(a : Segment, b : Segment, epsilon : Double) -> Bool

Tolerant segment intersection including endpoint and collinear contact.

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io