moontopo

    MoonBit port of TopoJSON Client: shared arcs, features, meshes, merges and quantization

    topojson
    geojson
    topology
    gis
    Download zip
    Version
    0.1.0
    License
    ISC
    Last updated
    5 hours ago
    Downloads
    3

    #MoonTopo

    MoonTopo 把 TopoJSON Client 的共享弧处理算法移植到 MoonBit。用于读取 TopoJSON、输出 GeoJSON、提取行政区边界、合并相邻区域,以及制作量化后的离线数据包。运行时只依赖 MoonBit 标准库,不需要 JavaScript 引擎或 GIS 服务。

    地图中的相邻区域往往共用一段边界。TopoJSON 将边界存成弧,多个区域通过正向或反向索引引用它。MoonTopo 保留这层拓扑关系,直到需要输出坐标时才展开。

    #安装

    moon add chenliyi-cly/moontopo@0.1.0

    在调用方的 moon.pkg 加入:

    import { "chenliyi-cly/moontopo" @topo }

    let topology = @topo.read(
    #|{"type":"Topology","arcs":[[[0,0],[1,0]]],"objects":{"road":{"type":"LineString","arcs":[0]}}}
    )
    println(topology.feature("road").stringify())

    read 和处理方法可能抛出 TopoError;在调用方声明错误效果或用 try/catch 处理。示例使用 MoonBit 2026-09 工具链。库支持 wasm-gc、JS 和 native;差分测试需要 Node.js 22 以上。

    #三个示例

    #地图:只画一次公共边界

    地图前端收到包含东西两个区的 TopoJSON。feature("regions") 生成两个面,mesh(name="regions", boundary=Interior) 只输出公共边界 [[1,0],[1,1]],避免重复描边。源码见 examples/map

    moon run examples/map --target wasm-gc

    #区域统计:查邻区,再合并外轮廓

    统计任务选中 ab 两个区。邻接结果为 [[1],[0]]merge(["a", "b"]) 去掉中间公共弧,得到合并区域的外轮廓。它依赖共享弧身份,不会把仅仅坐标接近的面当成邻区。源码见 examples/regions

    moon run examples/regions --target wasm-gc

    #离线数据:裁掉无用弧,检查量化误差

    离线地图导出程序先 prune(["a", "b"]),从 8 条存储弧中保留 7 条。随后在 101 级网格上量化。示例检查逐轴误差预算并输出可再次读取的 TopoJSON;这组整数坐标刚好落在网格上,实测误差为 0。源码见 examples/offline

    moon run examples/offline --target wasm-gc

    #我们怎么验的

    把同一份输入分别交给 MoonTopo 和固定版本 topojson-client 3.1.0,逐字段比较结果。仓库保留了带 ISC 许可证的上游源码,测试不依赖在线服务。

    检查可复现结果
    上游差分37 个固定用例 + 32 种网格各 6 个用例,共 229 个,JS 结果全部一致
    MoonBit 单元测试30 个,覆盖错误输入、反向弧、额外坐标维度、孔洞、元数据和资源边界
    量化不变量64 组曲线 × 4 种网格,256 组检查逐轴半网格误差与解量化一致性
    128 区网格280 条存储弧、512 次引用;232 条内部弧被消去,合并后保留 48 条边界弧
    1001 点曲线,101 级量化存储点降到 399;最大 X 误差约 0.05,最大 Y 误差 0.009998565,均在半网格预算加浮点容差内

    差分中的数值容差为 1e-9。mesh / merge 的片段排列和闭环起点不影响表示的几何,因此做规范化后比较;featurequantize 的坐标顺序严格比较。399 点是这条测试曲线的结果,实际数据的点数变化取决于网格和坐标。

    moon fmt --check moon check --target wasm-gc --deny-warn moon test --target wasm-gc moon test --target js moon test --target native node tools/differential.mjs node tools/measure.mjs

    结果文件写入 _build/evidence/。native 测试需要本机 C 编译器。CI 在 Linux 上检查三种后端,并运行示例和上游对照。

    #API 和边界

    • 读取与解码:readwritenamesobjectarcfeaturebbox。覆盖 Point、MultiPoint、LineString、MultiLineString、Polygon、MultiPolygon、GeometryCollection 及 null 几何。
    • 拓扑处理:neighborscollection_neighborsstitchmesh / mesh_arcsmerge / merge_arcs
    • 数据整理:quantizerquantizequantize_withdequantizeprunestatisticsquantization_report
    • 命令行:moon run cmd/moontopo --target js -- --request '<JSON>'。请求字段为 optopology,以及按操作需要提供的 namenamesboundarygrid。错误以一行 {"error":"..."} 返回;批量调用方应检查这个字段。

    完整接口见 pkg.generated.mbti,兼容性与输入限制见 docs/porting.md。不包含从 GeoJSON 建拓扑、拓扑简化、投影、通用多边形布尔运算或地图渲染。

    #来源和许可证

    移植基线为 TopoJSON Client v3.1.0,提交 71e5bd6428fd8b7a40fa980d2ef7862140851f03,原作者 Michael Bostock。沿用 ISC,版权与许可见 LICENSE。chenliyi-cly 负责此 MoonBit 移植。AI 辅助范围见 AI_USAGE.md

    TopoError

    pub suberror TopoError {
    Invalid(String)
    } derive(
    Debug
    )

    Boundary

    pub(all) enum Boundary {
    All
    Interior
    Exterior
    } derive(Eq,
    Debug
    )

    QuantizationReport

    pub(all) struct QuantizationReport {
    grid : Int
    samples : Int
    stored_before : Int
    stored_after : Int
    max_error_x : Double
    max_error_y : Double
    half_step_x : Double
    half_step_y : Double
    } derive(ToJson,
    Debug
    )

    Quantizer

    pub struct Quantizer {
    // private fields
    } derive(
    Debug
    )

    Quantizer::arc

    fn Quantizer::arc(self : Quantizer, points : Array[Array[Double]]) -> Array[Array[Double]] raise TopoError

    Quantize and delta-encode; discard consecutive coincident positions.

    Quantizer::point

    fn Quantizer::point(self : Quantizer, p : Array[Double]) -> Array[Double] raise TopoError

    Statistics

    pub(all) struct Statistics {
    objects : Int
    geometries : Int
    arcs : Int
    stored_positions : Int
    arc_references : Int
    shared_arcs : Int
    orphan_arcs : Int
    quantized : Bool
    } derive(Eq, ToJson,
    Debug
    )

    Topology

    pub struct Topology {
    // private fields
    } derive(
    Debug
    )

    Topology::arc

    fn Topology::arc(self : Topology, index : Int) -> Array[Array[Double]] raise TopoError

    Decode one signed arc. Negative i means reverse arc -i-1 after delta decoding.

    Topology::bbox

    fn Topology::bbox(self : Topology) -> Array[Double]? raise TopoError

    Bounds include all stored arcs, including unreferenced arcs, as in the upstream client. None denotes a completely empty topology.

    Topology::collection_neighbors

    fn Topology::collection_neighbors(self : Topology, name : String) -> Array[Array[Int]] raise TopoError

    Each direct member of a GeometryCollection is one neighbor-graph node.

    Topology::dequantize

    fn Topology::dequantize(self : Topology) -> Topology raise TopoError

    Convert stored deltas and points to absolute positions while retaining shared arc references.

    Topology::feature

    fn Topology::feature(self : Topology, name : String) -> Json raise TopoError

    Convert a named object into a GeoJSON Feature or FeatureCollection.

    Topology::merge

    fn Topology::merge(self : Topology, names : Array[String]) -> Json raise TopoError

    Topology::merge_arcs

    fn Topology::merge_arcs(self : Topology, names : Array[String]) -> Json raise TopoError

    Dissolve polygon boundaries shared by the selected objects, retaining holes.

    Topology::mesh

    fn Topology::mesh(self : Topology, name? : String, boundary? : Boundary) -> Json raise TopoError

    Topology::mesh_arcs

    fn Topology::mesh_arcs(self : Topology, name? : String, boundary? : Boundary) -> Json raise TopoError

    Return a MultiLineString with arc references. With no object, selects all stored arcs.

    Topology::names

    fn Topology::names(self : Topology) -> Array[String]

    Topology::neighbors

    fn Topology::neighbors(self : Topology, names : Array[String]) -> Array[Array[Int]] raise TopoError

    Neighbors are based on shared arc identity, not coincident coordinates.

    Topology::object

    fn Topology::object(self : Topology, name : String) -> Json raise TopoError

    Returns a detached topology geometry object (arc references are retained).

    Topology::prune

    fn Topology::prune(self : Topology, names : Array[String]) -> Topology raise TopoError

    Select objects and remove unused arcs. Signed references are renumbered without changing features.

    Topology::quantization_report

    fn Topology::quantization_report(self : Topology, grid : Int) -> QuantizationReport raise TopoError

    Measure coordinate rounding error against every source position before coincident-point removal.

    Topology::quantize

    fn Topology::quantize(self : Topology, n : Int) -> Topology raise TopoError

    Quantize to n grid values per axis (2..1e9); preserves references and metadata.

    Topology::quantize_with

    fn Topology::quantize_with(self : Topology, q : Quantizer) -> Topology raise TopoError

    Explicit transform variant. Source must not already be quantized.

    Topology::statistics

    fn Topology::statistics(self : Topology) -> Statistics raise TopoError

    Count storage and shared ownership. GeometryCollection containers are excluded from geometries.

    Topology::stitch

    fn Topology::stitch(self : Topology, ids : Array[Int]) -> Array[Array[Int]] raise TopoError

    Stitch selected signed arcs into connected chains. Arc IDs must be unique ignoring direction.

    Topology::write

    fn Topology::write(self : Topology) -> String

    execute

    fn execute(request : Json) -> Json raise TopoError

    Execute one JSON request for CLI and cross-language integrations.

    quantizer

    fn quantizer(scale : Array[Double], translate : Array[Double]) -> Quantizer raise TopoError

    read

    fn read(text : String) -> Topology raise TopoError

    Parse a TopoJSON document. Malformed shapes and invalid arc references raise TopoError.