moongeokit

    Robust 2D geometry and observable spatial indexing for MoonBit.

    geometry
    spatial-index
    uniform-grid
    polygon
    gis
    Download zip
    Author
    Version
    0.3.0
    License
    Apache-2.0
    Last updated
    2 months ago
    Downloads
    30

    #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