Sign in

    moon_geocodec

    MoonGeoCodec is a MoonBit geometry interchange library for WKT, WKB, EWKB, and GeoJSON.

    geospatial
    gis
    wkt
    wkb
    ewkb
    geojson
    Download zip
    Version
    0.2.0
    License
    Apache-2.0
    Last updated
    8 hours ago
    Downloads
    12

    #MoonGeoCodec

    MoonGeoCodec is a MoonBit geometry interchange library for WKT, WKB, EWKB, and GeoJSON. It provides a typed geometry model, strict format parsing, canonical serialization, resource limits, and cross-language interoperability vectors for GIS and spatial data pipelines.

    #Status

    0.2.0 is the first release with the complete WKT/WKB/EWKB/GeoJSON pipeline, validation, metrics, interoperability vectors, and CLI.

    #WKT parser

    let geometry = match @moon_geocodec.parse_wkt(
    "POLYGON ((0 0, 1 0, 1 1, 0 0))",
    @moon_geocodec.GeoLimits::default(),
    ) {
    Ok(value) => value
    Err(err) => fail(err.to_string())
    }
    assert_eq(geometry.kind(), @moon_geocodec.GeometryKind::Polygon)

    The parser reports malformed syntax as GeoError::InvalidWkt with a source offset, rejects inconsistent coordinate dimensions, and enforces coordinate, geometry-count, and collection-depth limits while reading.

    #WKT writer

    let text = match @moon_geocodec.format_wkt(geometry) {
    Ok(value) => value
    Err(err) => fail(err.to_string())
    }
    assert_eq(text, "POLYGON ((0 0, 1 0, 1 1, 0 0))")

    The writer emits uppercase geometry names, deterministic spacing, Z, M, and ZM suffixes, and EMPTY forms. It rejects non-finite coordinates, inconsistent dimensions, and empty multipart components.

    #WKT2 and EWKT

    let wkt2 = match @moon_geocodec.parse_wkt2(
    "MULTIPOINT ((1 2), (3 4))",
    @moon_geocodec.GeoLimits::default(),
    ) {
    Ok(value) => value
    Err(err) => fail(err.to_string())
    }
    assert_eq(
    @moon_geocodec.format_wkt2(wkt2),
    Ok("MULTIPOINT ((1 2), (3 4))"),
    )

    let ewkt = match @moon_geocodec.parse_ewkt(
    "SRID=4326;POINT (1 2)",
    @moon_geocodec.GeoLimits::default(),
    ) {
    Ok(value) => value
    Err(err) => fail(err.to_string())
    }
    assert_eq(ewkt.srid, Some(4326))
    assert_eq(
    @moon_geocodec.format_ewkt(ewkt),
    Ok("SRID=4326;POINT (1 2)"),
    )

    WKT 2 uses parenthesized MULTIPOINT coordinates. Curve and surface types outside the current geometry model are reported as UnsupportedGeometry. EWKT SRIDs are optional, must be non-negative, and are stored separately from the geometry.

    #WKB reader

    let geometry = match @moon_geocodec.parse_wkb(
    bytes.exact_view(),
    @moon_geocodec.GeoLimits::default(),
    ) {
    Ok(value) => value
    Err(err) => fail(err.to_string())
    }

    The reader supports all seven WKB geometry types in XY, XYZ, XYM, and XYZM forms using both byte orders. Nested geometries may use independent byte order, while component dimensions must match their parent. Truncation, invalid type codes, dimension mismatches, trailing bytes, and resource-limit violations produce typed errors.

    #WKB writer

    let bytes = match @moon_geocodec.format_wkb(geometry) {
    Ok(value) => value
    Err(err) => fail(err.to_string())
    }
    let big_endian = match @moon_geocodec.format_wkb_with_order(
    geometry,
    @moon_geocodec.WkbByteOrder::BigEndian,
    ) {
    Ok(value) => value
    Err(err) => fail(err.to_string())
    }

    format_wkb emits little-endian WKB by default. The writer supports all seven geometry types in XY, XYZ, XYM, and XYZM forms, empty geometries, multipart components, and GeometryCollection recursion. Dimensioned empty children inherit the enclosing geometry dimension. The writer rejects mixed dimensions, non-finite ordinates, and empty polygon rings with typed errors.

    #EWKB

    let value = match @moon_geocodec.parse_ewkb(
    ewkb_bytes.exact_view(),
    @moon_geocodec.GeoLimits::default(),
    ) {
    Ok(value) => value
    Err(err) => fail(err.to_string())
    }
    let encoded = match @moon_geocodec.format_ewkb(value) {
    Ok(bytes) => bytes
    Err(err) => fail(err.to_string())
    }

    EWKB supports PostGIS Z, M, and SRID type flags, including combined Z/M/SRID geometries. SRIDs are stored on SpatialGeometry; the writer emits the SRID on the root geometry while nested components carry only matching dimension flags. parse_wkb remains strict ISO WKB and rejects EWKB high-bit type codes.

    #GeoJSON import

    let geometry = match @moon_geocodec.parse_geojson(
    "{\"type\":\"Point\",\"coordinates\":[1,2]}",
    @moon_geocodec.GeoLimits::default(),
    ) {
    Ok(value) => value
    Err(err) => fail(err.to_string())
    }
    assert_eq(
    geometry,
    @moon_geocodec.Geometry::Point(
    @moon_geocodec.Coordinate::xy(1.0, 2.0),
    ),
    )

    The importer supports all seven GeoJSON geometry types, two- and three-dimensional positions, empty geometry arrays, Features, and FeatureCollections. Feature properties and bounding boxes are ignored because the output is the typed geometry model. Null feature geometries are skipped; FeatureCollections are represented as GeometryCollection.

    #GeoJSON export and conversions

    let text = match @moon_geocodec.format_geojson(geometry) {
    Ok(value) => value
    Err(err) => fail(err.to_string())
    }
    assert_eq(
    text,
    "{\"type\":\"Point\",\"coordinates\":[1,2]}",
    )

    format_geojson writes deterministic compact GeoJSON with fixed key order. It supports XY and XYZ positions, empty geometries, multipart geometries, and GeometryCollections. M-only and ZM geometries are rejected because GeoJSON has no standard M ordinate.

    Conversion helpers bridge formats without exposing intermediate steps:

    • wkt_to_geojson
    • geojson_to_wkt
    • wkb_to_geojson
    • geojson_to_wkb

    #Geometry validation and normalization

    let normalized = match @moon_geocodec.normalize_geometry(geometry) {
    Ok(value) => value
    Err(err) => fail(err.to_string())
    }
    assert_eq(@moon_geocodec.validate_geometry(normalized), Ok(()))

    Validation checks finite coordinates, dimension consistency, line minimum length and distinctness, polygon ring length, closure and area, and multipart component structure. Normalization removes consecutive duplicate coordinates, closes polygon rings, orients exterior rings counter-clockwise and interior rings clockwise, then validates the result.

    #3D bounds and centroids

    let bounds = geometry.bounds_3d()
    let center = geometry.centroid()

    bounds_3d returns a BoundingBox3D with width, height, depth, and an extent tuple. Missing Z ordinates contribute 0.0, so 2D and mixed-height data retain deterministic three-dimensional bounds.

    centroid is geometry-aware: points use coordinate averages, lines use segment-length weighting, polygons use signed area with holes subtracted, and GeometryCollections prefer their highest-dimensional members. Z is included when all contributing coordinates provide it.

    #CLI

    The executable supports geometry inspection, structural validation, and format conversion:

    moon run cmd/main -- inspect wkt "POINT Z (1 2 3)" moon run cmd/main -- validate wkt "POLYGON ((0 0, 1 0, 1 1, 0 0))" moon run cmd/main -- convert wkt geojson "POINT (1 2)" moon run cmd/main -- convert ewkt ewkb "SRID=4326;POINT (1 2)"

    Supported formats are wkt, wkt2, ewkt, wkb, ewkb, and geojson. WKB and EWKB values use hexadecimal strings. inspect reports kind, dimension, coordinate count, 2D/3D bounds, extent, centroid, SRID, and structural validity.

    #Interoperability vectors

    Reference WKB, EWKB, and GeoJSON vectors live in interop/reference_vectors.json. The MoonBit test suite validates them directly. If Shapely is available, run:

    python -m pip install -r interop/requirements.txt python interop/verify_shapely.py

    The vector set includes 2D points, line strings, polygons, SRID-bearing EWKB, and Z-bearing EWKB.

    #Fuzzing and hardening

    The test suite includes deterministic pseudorandom WKB/EWKB inputs, truncated WKT/WKT2/GeoJSON inputs, and byte-size limits for binary parsers. Malformed inputs are expected to return typed errors without panics.

    #Examples and release

    Run the end-to-end example with:

    moon run examples/quickstart

    The release includes the reusable library, the cmd/cli command layer, the cmd/main executable, and the reference vectors under interop/.

    #WKT lexer

    let tokens = match @moon_geocodec.tokenize_wkt(
    "POINT Z (1 2 3)",
    @moon_geocodec.GeoLimits::default(),
    ) {
    Ok(value) => value
    Err(err) => fail(err.to_string())
    }
    assert_eq(tokens[0].text(), "POINT")
    assert_eq(tokens[1].text(), "Z")

    The lexer returns token kinds, source text, and code-unit offsets. It rejects malformed numbers and unexpected characters with GeoError::InvalidWkt, and enforces both text-byte and token-count limits.

    #Scope

    • Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, and GeometryCollection
    • 2D, XYZ, XYM, and XYZM coordinates
    • Strict WKT 1/2 parsing and canonical formatting
    • WKB and EWKB binary codecs with endian and SRID support
    • GeoJSON import/export
    • Bounded parsing with typed diagnostics
    • CLI inspection, validation, and conversion commands
    • Interoperability vectors for WKB, EWKB, and GeoJSON

    #Explicit non-goals

    MoonGeoCodec is not a geometry engine. Buffer, union, intersection, overlay, spatial indexing, and GEOS-level operations are outside the initial scope.

    #Install

    moon add ggbond44439/moon_geocodec@0.2.0

    #Build and test

    moon check --target all moon test --target all moon build

    #License

    Apache-2.0.

    BoundingBox

    pub(all) struct BoundingBox {
    min_x : Double
    min_y : Double
    max_x : Double
    max_y : Double
    } derive(Eq,
    Debug
    )

    A closed coordinate bounds box. Empty geometries have no bounds.

    BoundingBox::equal

    fn BoundingBox::equal(BoundingBox, BoundingBox) -> Bool

    BoundingBox::height

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

    BoundingBox::not_equal

    fn BoundingBox::not_equal(x : BoundingBox, y : BoundingBox) -> Bool

    BoundingBox::width

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

    BoundingBox3D

    pub(all) struct BoundingBox3D {
    min_x : Double
    min_y : Double
    min_z : Double
    max_x : Double
    max_y : Double
    max_z : Double
    } derive(Eq,
    Debug
    )

    A closed three-dimensional bounds box.

    BoundingBox3D::depth

    fn BoundingBox3D::depth(self : BoundingBox3D) -> Double

    BoundingBox3D::equal

    BoundingBox3D::extent

    fn BoundingBox3D::extent(self : BoundingBox3D) -> (Double, Double, Double)

    BoundingBox3D::height

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

    BoundingBox3D::not_equal

    fn BoundingBox3D::not_equal(x : BoundingBox3D, y : BoundingBox3D) -> Bool

    BoundingBox3D::width

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

    Coordinate

    pub(all) struct Coordinate {
    x : Double
    y : Double
    z : Double?
    m : Double?
    } derive(Eq,
    Debug
    )

    A coordinate with optional Z and M ordinates.

    Coordinate::dimension

    fn Coordinate::dimension(self : Coordinate) -> CoordinateDimension

    Coordinate::equal

    fn Coordinate::equal(Coordinate, Coordinate) -> Bool

    Coordinate::has_m

    fn Coordinate::has_m(self : Coordinate) -> Bool

    Coordinate::has_z

    fn Coordinate::has_z(self : Coordinate) -> Bool

    Coordinate::not_equal

    fn Coordinate::not_equal(x : Coordinate, y : Coordinate) -> Bool

    Coordinate::xy

    fn Coordinate::xy(x : Double, y : Double) -> Coordinate

    Coordinate::xym

    fn Coordinate::xym(x : Double, y : Double, m : Double) -> Coordinate

    Coordinate::xyz

    fn Coordinate::xyz(x : Double, y : Double, z : Double) -> Coordinate

    Coordinate::xyzm

    fn Coordinate::xyzm(x : Double, y : Double, z : Double, m : Double) -> Coordinate

    CoordinateDimension

    pub(all) enum CoordinateDimension {
    Xy
    Xyz
    Xym
    Xyzm
    } derive(Eq,
    Debug
    )

    Coordinate dimensionality supported by geometry interchange formats.

    CoordinateDimension::equal

    CoordinateDimension::not_equal

    GeoError

    pub(all) enum GeoError {
    InvalidWkt(String, Int)
    InvalidWkb(String, Int)
    InvalidGeoJson(String, Int)
    InvalidCoordinate(String)
    InvalidGeometry(String)
    InvalidBoundary(String, Int)
    UnsupportedGeometry(String)
    InvalidLimit(String, Int)
    LimitExceeded(String, Int, Int)
    Truncated(String, Int)
    TrailingBytes(Int)
    InvalidSrid(Int)
    } derive(Eq,
    Debug
    )

    Typed failures produced by MoonGeoCodec parsers and validators.
    impl Show for GeoError

    GeoError::equal

    fn GeoError::equal(GeoError, GeoError) -> Bool

    GeoError::not_equal

    fn GeoError::not_equal(x : GeoError, y : GeoError) -> Bool

    GeoError::output

    fn GeoError::output(self : GeoError, logger : &Logger) -> Unit

    GeoError::to_repr

    GeoError::to_string

    fn GeoError::to_string(self : GeoError) -> String

    GeoLimits

    pub(all) struct GeoLimits {
    max_text_bytes : Int
    max_depth : Int
    max_geometries : Int
    max_coordinates : Int
    max_tokens : Int
    } derive(Eq,
    Debug
    )

    Resource limits shared by all MoonGeoCodec parsers.

    GeoLimits::check_coordinates

    fn GeoLimits::check_coordinates(self : GeoLimits, actual : Int) -> Result[Unit, GeoError]

    GeoLimits::check_depth

    fn GeoLimits::check_depth(self : GeoLimits, actual : Int) -> Result[Unit, GeoError]

    GeoLimits::check_geometries

    fn GeoLimits::check_geometries(self : GeoLimits, actual : Int) -> Result[Unit, GeoError]

    GeoLimits::check_text

    fn GeoLimits::check_text(self : GeoLimits, actual : Int) -> Result[Unit, GeoError]

    GeoLimits::check_tokens

    fn GeoLimits::check_tokens(self : GeoLimits, actual : Int) -> Result[Unit, GeoError]

    GeoLimits::default

    fn GeoLimits::default() -> GeoLimits

    GeoLimits::equal

    fn GeoLimits::equal(GeoLimits, GeoLimits) -> Bool

    GeoLimits::new

    fn GeoLimits::new(max_text_bytes : Int, max_depth : Int, max_geometries : Int, max_coordinates : Int) -> Result[GeoLimits, GeoError]

    GeoLimits::not_equal

    fn GeoLimits::not_equal(x : GeoLimits, y : GeoLimits) -> Bool

    GeoLimits::with_token_limit

    fn GeoLimits::with_token_limit(self : GeoLimits, max_tokens : Int) -> Result[GeoLimits, GeoError]

    Geometry

    pub(all) enum Geometry {
    Point(Coordinate)
    EmptyPoint
    LineString(Array[Coordinate])
    Polygon(Array[Array[Coordinate]])
    MultiPoint(Array[Coordinate])
    MultiLineString(Array[Array[Coordinate]])
    MultiPolygon(Array[Array[Array[Coordinate]]])
    GeometryCollection(Array[Geometry])
    } derive(Eq,
    Debug
    )

    Typed geometry model independent of text and binary encodings.

    Geometry::bounds

    fn Geometry::bounds(self : Geometry) -> BoundingBox?

    Geometry::bounds_3d

    fn Geometry::bounds_3d(self : Geometry) -> BoundingBox3D?

    Geometry::centroid

    fn Geometry::centroid(self : Geometry) -> Coordinate?

    Computes a dimension-weighted centroid.

    Geometry::coordinate_count

    fn Geometry::coordinate_count(self : Geometry) -> Int

    Geometry::dimension

    fn Geometry::dimension(self : Geometry) -> CoordinateDimension?

    Geometry::equal

    fn Geometry::equal(Geometry, Geometry) -> Bool

    Geometry::is_empty

    fn Geometry::is_empty(self : Geometry) -> Bool

    Geometry::kind

    fn Geometry::kind(self : Geometry) -> GeometryKind

    Geometry::not_equal

    fn Geometry::not_equal(x : Geometry, y : Geometry) -> Bool

    Geometry::to_repr

    GeometryKind

    pub(all) enum GeometryKind {
    Point
    LineString
    Polygon
    MultiPoint
    MultiLineString
    MultiPolygon
    GeometryCollection
    } derive(Eq,
    Debug
    )

    GeometryKind::equal

    GeometryKind::not_equal

    fn GeometryKind::not_equal(x : GeometryKind, y : GeometryKind) -> Bool

    SpatialGeometry

    pub(all) struct SpatialGeometry {
    srid : Int?
    geometry : Geometry
    } derive(Eq,
    Debug
    )

    A geometry with optional spatial reference system metadata.

    SpatialGeometry::equal

    SpatialGeometry::new

    fn SpatialGeometry::new(geometry : Geometry, srid : Int?) -> Result[SpatialGeometry, GeoError]

    SpatialGeometry::not_equal

    fn SpatialGeometry::not_equal(x : SpatialGeometry, y : SpatialGeometry) -> Bool

    WkbByteOrder

    pub(all) enum WkbByteOrder {
    LittleEndian
    BigEndian
    } derive(Eq,
    Debug
    )

    WkbByteOrder::equal

    WkbByteOrder::not_equal

    fn WkbByteOrder::not_equal(x : WkbByteOrder, y : WkbByteOrder) -> Bool

    WktToken

    pub(all) struct WktToken {
    kind : WktTokenKind
    text : String
    offset : Int
    } derive(Eq,
    Debug
    )

    A WKT token with its source code-unit offset.

    WktToken::equal

    fn WktToken::equal(WktToken, WktToken) -> Bool

    WktToken::kind

    fn WktToken::kind(self : WktToken) -> WktTokenKind

    WktToken::not_equal

    fn WktToken::not_equal(x : WktToken, y : WktToken) -> Bool

    WktToken::offset

    fn WktToken::offset(self : WktToken) -> Int

    WktToken::text

    fn WktToken::text(self : WktToken) -> String

    WktToken::to_repr

    WktTokenKind

    pub(all) enum WktTokenKind {
    Word
    Number
    LParen
    RParen
    Comma
    Semicolon
    Equals
    End
    } derive(Eq,
    Debug
    )

    Token categories emitted by the WKT lexer.

    WktTokenKind::equal

    WktTokenKind::name

    fn WktTokenKind::name(self : WktTokenKind) -> String

    WktTokenKind::not_equal

    fn WktTokenKind::not_equal(x : WktTokenKind, y : WktTokenKind) -> Bool

    VERSION

    let VERSION : String

    Public version marker for the MoonGeoCodec library.

    format_ewkb

    fn format_ewkb(value : SpatialGeometry) -> Result[Bytes, GeoError]

    Serializes a spatial geometry as little-endian EWKB.

    format_ewkb_with_order

    fn format_ewkb_with_order(value : SpatialGeometry, order : WkbByteOrder) -> Result[Bytes, GeoError]

    Serializes a spatial geometry as EWKB using the requested byte order.

    format_ewkt

    fn format_ewkt(value : SpatialGeometry) -> Result[String, GeoError]

    Serializes a geometry with an optional EWKT SRID prefix.

    format_geojson

    fn format_geojson(geometry : Geometry) -> Result[String, GeoError]

    Serializes a geometry as compact GeoJSON.

    format_wkb

    fn format_wkb(geometry : Geometry) -> Result[Bytes, GeoError]

    Serializes a geometry as little-endian WKB.

    format_wkb_with_order

    fn format_wkb_with_order(geometry : Geometry, order : WkbByteOrder) -> Result[Bytes, GeoError]

    Serializes a geometry as WKB using the requested byte order.

    format_wkt

    fn format_wkt(geometry : Geometry) -> Result[String, GeoError]

    Serializes a geometry as canonical WKT 1.

    format_wkt2

    fn format_wkt2(geometry : Geometry) -> Result[String, GeoError]

    Serializes a geometry as canonical WKT 2.

    geojson_to_wkb

    fn geojson_to_wkb(source : StringView, limits : GeoLimits) -> Result[Bytes, GeoError]

    geojson_to_wkt

    fn geojson_to_wkt(source : StringView, limits : GeoLimits) -> Result[String, GeoError]

    normalize_geometry

    fn normalize_geometry(geometry : Geometry) -> Result[Geometry, GeoError]

    Normalizes ring closure, consecutive duplicates, and ring direction.

    parse_ewkb

    fn parse_ewkb(source : BytesView, limits : GeoLimits) -> Result[SpatialGeometry, GeoError]

    parse_ewkt

    fn parse_ewkt(source : StringView, limits : GeoLimits) -> Result[SpatialGeometry, GeoError]

    parse_geojson

    fn parse_geojson(source : StringView, limits : GeoLimits) -> Result[Geometry, GeoError]

    parse_wkb

    fn parse_wkb(source : BytesView, limits : GeoLimits) -> Result[Geometry, GeoError]

    parse_wkt

    fn parse_wkt(source : StringView, limits : GeoLimits) -> Result[Geometry, GeoError]

    parse_wkt2

    fn parse_wkt2(source : StringView, limits : GeoLimits) -> Result[Geometry, GeoError]

    tokenize_wkt

    fn tokenize_wkt(source : StringView, limits : GeoLimits) -> Result[Array[WktToken], GeoError]

    validate_geometry

    fn validate_geometry(geometry : Geometry) -> Result[Unit, GeoError]

    Validates the structural invariants of a geometry.

    wkb_to_geojson

    fn wkb_to_geojson(source : BytesView, limits : GeoLimits) -> Result[String, GeoError]

    wkt_to_geojson

    fn wkt_to_geojson(source : StringView, limits : GeoLimits) -> Result[String, GeoError]

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io