Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
140 commits
Select commit Hold shift + click to select a range
8134d7f
feat(geo): ownership-partition S-57 adapter + real-ENC de-risk probe
beetlebugorg Jul 8, 2026
acc344f
docs(geo): drop dangling specs refs + plainer coordinate wording
beetlebugorg Jul 8, 2026
a1967d0
perf(geo): bbox-local indexed partition builder (drops the O(cells^2)…
beetlebugorg Jul 8, 2026
7c7bbc0
test(geo): probe cross-checks indexed vs naive builder on real cells …
beetlebugorg Jul 8, 2026
c250c4c
feat(geo): per-band ownership partition band-stack (Partition/BandMap…
beetlebugorg Jul 8, 2026
a78f7b1
feat(bake): `bake --partition-debug` emits the ownership partition as…
beetlebugorg Jul 8, 2026
ece8672
fix(bake): partition-debug PMTiles renders — winding, bounds, center …
beetlebugorg Jul 8, 2026
0d2eb04
fix(bake): partition-debug declares vector_layers (was invisible to v…
beetlebugorg Jul 8, 2026
fec6c1d
feat(geo): partition.ownedFace(ci, z) — a cell's owned face for the c…
beetlebugorg Jul 8, 2026
359f2ed
refactor(bake): single cell-load path + streaming, band-aware partiti…
beetlebugorg Jul 8, 2026
cd27fb1
feat(capi): tile57_bake_partition_debug + Go BakePartitionDebug
beetlebugorg Jul 8, 2026
515dfd2
feat(bake): partition-debug "labels" layer — cell name at each face
beetlebugorg Jul 8, 2026
752c718
feat(geo): clipLineInsidePolys — clip-to-face line primitive for the …
beetlebugorg Jul 8, 2026
d2b1311
feat(scene): compose core — clipFeatureToFace + status doc
beetlebugorg Jul 8, 2026
58e189f
feat(scene): coverage.zig — per-cell coverage sidecar (JSON in PMTile…
beetlebugorg Jul 8, 2026
abf651f
feat(bake): embed per-cell coverage in the single-cell PMTiles metadata
beetlebugorg Jul 8, 2026
f6fa8b6
feat(capi/go): tile57_pmtiles_metadata + Go BakeCell/PMTilesMetadata
beetlebugorg Jul 8, 2026
e2850ee
feat(bake): per-cell bake is native-scale only (drop caller zoom range)
beetlebugorg Jul 8, 2026
48824e8
feat(compose): projectFace — E7 owned-face → tile-pixel space (Step 3…
beetlebugorg Jul 8, 2026
9db2fb2
fix(pmtiles): free StreamWriter.prefix intermediates (merged + direct…
beetlebugorg Jul 8, 2026
829d730
feat(compose): composeArchives — Step 3 per-cell composite driver
beetlebugorg Jul 8, 2026
0c4f1cd
feat(cli): tile57 compose — end-to-end per-cell composite bake
beetlebugorg Jul 8, 2026
02794ef
feat(compose): cross-band overscale (Step 3b) — fill-up one zoom past…
beetlebugorg Jul 8, 2026
2751eb6
feat(capi/go): tile57_compose + Go Compose — in-memory per-cell compo…
beetlebugorg Jul 8, 2026
63cbb18
feat(compose): streaming disk-to-disk composeArchivesToFile
beetlebugorg Jul 8, 2026
f624a50
feat(capi/go): tile57_compose_files + Go ComposeFiles (streaming) + b…
beetlebugorg Jul 8, 2026
e6bccd5
feat(compose): FULL-tile verbatim passthrough (Step 3c) — byte-identi…
beetlebugorg Jul 8, 2026
b7983ff
fix(capi): tile57_chart_open uses the streaming path (metadata + lazy…
beetlebugorg Jul 8, 2026
94a2463
test(go): skip the retired per-value SCAMIN bucket/log2 style tests
beetlebugorg Jul 8, 2026
1fedac3
perf(bake): build the per-feature label cache in the composite per-ce…
beetlebugorg Jul 8, 2026
bdc8d48
perf(bake): cache the label point for EVERY portrayed area + build ge…
beetlebugorg Jul 8, 2026
cf35659
fix(compose): bound partition memory — free per-cell union scratch (4…
beetlebugorg Jul 9, 2026
b2ad9b1
feat(compose): --from-archives recomposes a dir of pre-baked per-cell…
beetlebugorg Jul 9, 2026
d89be20
perf(compose): cap partition participation at each cell's tile reach …
beetlebugorg Jul 9, 2026
64e4ced
perf(compose): tile-major composeZoom — one tile resident, not a whol…
beetlebugorg Jul 9, 2026
9027883
perf(partition): run owned-face boolean scratch on the page allocator…
beetlebugorg Jul 9, 2026
13f79d1
fix(partition): enforce the reach cut as a suffix of the ownership walk
beetlebugorg Jul 9, 2026
10eaf8d
perf(pmtiles): cache deserialized leaf directories per reader
beetlebugorg Jul 9, 2026
e15adf1
fix(compose): make --from-archives ingestion deterministic + loud on …
beetlebugorg Jul 9, 2026
107eec2
feat(compose): stream progress through the zoom ladder (compose_files…
beetlebugorg Jul 9, 2026
9583994
feat(partition): serialize/deserialize the ownership partition to a s…
beetlebugorg Jul 9, 2026
4187bde
fix(partition): hash coverage POINTS in inputKey, not ring-slice poin…
beetlebugorg Jul 9, 2026
7493d6b
feat(compose): precompute the ownership partition to/from a sidecar
beetlebugorg Jul 9, 2026
444a93b
feat(compose): composeTile — on-demand per-tile composition from a re…
beetlebugorg Jul 9, 2026
fd63cb3
feat(compose): ComposeSource + `compose-tile` CLI — resident on-deman…
beetlebugorg Jul 9, 2026
64a4a60
feat(capi/go): runtime compositor ABI — tile57_compose_open/serve/met…
beetlebugorg Jul 9, 2026
bf1ca89
feat(capi/go): tile57_compose_save_partition + Go ComposeSource.SaveP…
beetlebugorg Jul 9, 2026
d4c569a
feat(compose): report tile OWNERSHIP so a host can tell transient-emp…
beetlebugorg Jul 9, 2026
359fa7a
refactor(go): drop legacy bake/compose bindings (Step B — Go bindings)
beetlebugorg Jul 9, 2026
7f32955
refactor(capi): drop the legacy tile-production C ABI (Step C — ABI +…
beetlebugorg Jul 9, 2026
c439abe
refactor(engine): drop the legacy batch bake/compose impl (Step D — Zig)
beetlebugorg Jul 9, 2026
d119629
docs(cli): point `tile57 bake` usage at the live-composite structure
beetlebugorg Jul 9, 2026
d991c76
docs(capi): regroup tile57.h into 7 sections + drop stale removed-fn …
beetlebugorg Jul 9, 2026
76df8bb
docs(site): rewrite c-api.md for the bake-cell + compose tile model
beetlebugorg Jul 9, 2026
5010165
docs(readme): refresh Zig/C/CLI examples for the bake+compose model
beetlebugorg Jul 9, 2026
fa627c7
docs: drop CHANGELOG.md
beetlebugorg Jul 9, 2026
e87d912
docs(site): refresh getting-started + zig-api for bake+compose
beetlebugorg Jul 9, 2026
0f71162
docs(site): refresh architecture/rendering/limitations/tile-schema fo…
beetlebugorg Jul 9, 2026
53758bf
docs(schema): correct tile-schema.md to the tile57/2 six-layer schema
beetlebugorg Jul 9, 2026
d80c8c0
docs: accuracy fixes from the code audit
beetlebugorg Jul 9, 2026
2a01ecf
docs(zig): fix the curated-surface module comment
beetlebugorg Jul 9, 2026
ec2f5c2
feat(bake): parallel batch cell-bake (tile57_bake_cells)
beetlebugorg Jul 9, 2026
24ca126
fix(bake): warm up the portrayal globals before a per-cell bake
beetlebugorg Jul 9, 2026
0ffbacd
refactor(bake): make the cell the sole parallel unit (serial tile-gen)
beetlebugorg Jul 9, 2026
7f3a26e
feat(bake): tile57_bake_cells_to_files — parallel bake to app-named p…
beetlebugorg Jul 9, 2026
bb2c59d
feat(bake): tile57_bake_tree — parallel bake mirroring an ENC tree to…
beetlebugorg Jul 9, 2026
e523ee7
feat(bake): incremental reuse in bakeTree
beetlebugorg Jul 9, 2026
597e39d
fix(bake): reuse checks the whole update chain, not just the .000
beetlebugorg Jul 9, 2026
cd5f05d
feat(go): BakeTree binding for the parallel tree bake
beetlebugorg Jul 9, 2026
a6c252f
refactor(iso8211): promote to a standalone module
beetlebugorg Jul 9, 2026
fe06f61
docs(code): refresh stale module headers and drop specs/ refs
beetlebugorg Jul 9, 2026
624724d
refactor(style): rename the assets package to style
beetlebugorg Jul 9, 2026
8495d88
refactor(style): drop the redundant "style" prefix from member names
beetlebugorg Jul 9, 2026
c30a2f0
refactor(s101): rename the s100 package to s101 and spell out its mem…
beetlebugorg Jul 9, 2026
a910301
refactor(style): rename chartstyle to mariner; MarinerSettings to Set…
beetlebugorg Jul 9, 2026
45ac5f6
docs: propagate the module/member renames and show the S-101 adapter
beetlebugorg Jul 9, 2026
5020b52
refactor(geometry): rename the geo module to geometry; drop dead part…
beetlebugorg Jul 9, 2026
fc4a6f9
docs(architecture): clarify style/sprite as S-101 portrayal-asset gen…
beetlebugorg Jul 9, 2026
6d8e9e3
refactor(tiles): move the shared band + MVT-schema tethers into tiles
beetlebugorg Jul 9, 2026
f12acf2
refactor: promote coverage to a module; move ordersBeforeKeys to geom…
beetlebugorg Jul 9, 2026
eca50c3
refactor(compose): lift the runtime compositor into its own module
beetlebugorg Jul 9, 2026
9ba1c35
feat(tile57): complete the public root with the full pipeline
beetlebugorg Jul 9, 2026
09d8b89
refactor: delete dead code and trim the public surface
beetlebugorg Jul 9, 2026
971c285
docs: fix the Go README, drop hyperbole, document bake + compose
beetlebugorg Jul 9, 2026
ddf6fb0
refactor(scene): extract the SYMINS mini-parser into scene/symins.zig
beetlebugorg Jul 10, 2026
0cbf6c3
refactor(scene): extract light-sector reach; delete dead scanLightRea…
beetlebugorg Jul 10, 2026
8b60c81
refactor(scene): extract complex-linestyle tessellation into scene/li…
beetlebugorg Jul 10, 2026
2574b4c
refactor(scene): extract baked-tile replay into scene/replay.zig
beetlebugorg Jul 10, 2026
81c5bb4
refactor(linestyle): add drawPlainLine to pair with drawComplexLine
beetlebugorg Jul 10, 2026
0167e13
fix(capi): guard the scamin @intCast; correct the chart_open docs
beetlebugorg Jul 10, 2026
14303e8
docs(capi): fix the stale draw_sprite "LAST field" comment
beetlebugorg Jul 10, 2026
1a8752f
feat(capi): error model — tile57_status, tile57_error, taxonomy; conv…
beetlebugorg Jul 10, 2026
3706ce8
feat(chart): surface embedded coverage/cscl on PMTiles opens; bakeTre…
beetlebugorg Jul 10, 2026
5c8ad5d
feat(engine): mmap-backed PMTiles path-open; compose over borrowed ch…
beetlebugorg Jul 10, 2026
2b0bc76
feat(capi)!: rework the C ABI — bake/render/compose sections, tile57 …
beetlebugorg Jul 10, 2026
e880329
feat(go): track the reworked C ABI — PMTiles-only Source, handle-free…
beetlebugorg Jul 10, 2026
327fe5f
docs: C API page tracks the reworked ABI (bake/render/compose, tile57…
beetlebugorg Jul 10, 2026
cb7409a
refactor(capi): tile57_s57_* readers renamed tile57_enc_* — one ENC v…
beetlebugorg Jul 10, 2026
599f8ad
refactor(capi): tile57_compose_serve -> tile57_compose_tile; Go Compo…
beetlebugorg Jul 10, 2026
56f8399
docs: say "call that can fail", not "fallible call"
beetlebugorg Jul 10, 2026
2b8bdd9
refactor(capi): tile57_build_style -> tile57_style_build — completes …
beetlebugorg Jul 10, 2026
ff05d8d
refactor(compose): ComposeSource.serve -> tile; sync Zig package vers…
beetlebugorg Jul 10, 2026
8aae6ab
feat(capi): the compose handle mirrors the chart's full output set — …
beetlebugorg Jul 10, 2026
9895f23
docs: every page reviewed against the v0.2 surface; API style codifie…
beetlebugorg Jul 10, 2026
edccc5a
feat(capi): tile57_free takes just the pointer — ABI buffers are leng…
beetlebugorg Jul 10, 2026
798e97d
fix(compose): keep light-sector figures whole across ownership seams
beetlebugorg Jul 10, 2026
da6d488
refactor(zig): drop the multi-cell bakeArchive from the public surfac…
beetlebugorg Jul 10, 2026
df39f97
feat(style): render complex linestyles on the MapLibre path — per-lin…
beetlebugorg Jul 10, 2026
042dd5e
fix(style): linestyle symbols at the engine's SYMBOL_SCALE — they ren…
beetlebugorg Jul 10, 2026
c64de73
feat(bake): tessellate complex linestyles into tiles — phase-correct …
beetlebugorg Jul 10, 2026
7f2a6c0
fix(scene): honor S-52 §8.6.2 edge masking in the M_NSYS and dashed-b…
beetlebugorg Jul 10, 2026
046f312
feat(compose): fill-up quilting — finer cells serve coarser zooms whe…
beetlebugorg Jul 10, 2026
c706a5c
fix(scene): SCAMIN-less LIGHTS don't ride sub-band fill-up tiles
beetlebugorg Jul 10, 2026
42055d4
fix(scene): size light sector figures against the 512-CSS-px tile — t…
beetlebugorg Jul 10, 2026
3c46b23
feat(compose): reach ring — light sector figures survive into tiles t…
beetlebugorg Jul 10, 2026
bf030f1
feat(tools): tiledump --geom/--coords per-feature geometry mode + ins…
beetlebugorg Jul 10, 2026
d64634f
fix(geometry): cancel same-operand collinear overlaps — open-ring wed…
beetlebugorg Jul 10, 2026
82a7822
fix(geometry): order T-touches in the sweep status by the touching ed…
beetlebugorg Jul 10, 2026
600efea
docs: intro once-over — plain-prose opening, best-effort S-57→S-101 s…
beetlebugorg Jul 10, 2026
b9e0fee
docs(readme): tagline says vector tiles (MLT or MVT) — MLT is the def…
beetlebugorg Jul 10, 2026
b36ca8a
docs: the portrayal produces S-101 symbology; S-57→S-101 adapter is i…
beetlebugorg Jul 10, 2026
69d2753
docs(intro): goals become a compact :::note — one-liners, no section …
beetlebugorg Jul 10, 2026
7f22c1a
fix(geometry): self-healing result extraction — region-sampled retry,…
beetlebugorg Jul 10, 2026
7d1a50b
docs: one word for the unit — chart; cell survives only as spec/API v…
beetlebugorg Jul 10, 2026
5d71424
docs(cli): the tile57 CLI gets its own reference page
beetlebugorg Jul 10, 2026
12e4239
fix(scene): bake the masked M_NSYS boundary complement for the meta-b…
beetlebugorg Jul 10, 2026
3183655
feat(api)!: every symbol in a family — tile57_chart handle, chart voc…
beetlebugorg Jul 11, 2026
b27d386
feat(go)!: bindings track the v0.3.0 family rename
beetlebugorg Jul 11, 2026
b8e8499
docs: track the v0.3.0 family rename across every page
beetlebugorg Jul 11, 2026
aeac905
fix(capi): tile57_version reports 0.3.0 — the hardcoded string missed…
beetlebugorg Jul 11, 2026
3567528
perf(render): cache decoded tiles + palette + symbol store per chart …
beetlebugorg Jul 11, 2026
1d19871
feat(cli): --meta renders the meta-bounds inspection view (png/pdf/ex…
beetlebugorg Jul 11, 2026
20693b4
fix(compose): a meta-bounds boundary outline survives compositing whole
beetlebugorg Jul 11, 2026
5a67c79
feat(capi): tile57_chart_tile_surface — per-tile S-52 portrayal
beetlebugorg Jul 11, 2026
8f822d1
scene: show full aid names — drop the buoy/beacon designation shortening
beetlebugorg Jul 11, 2026
af6d965
build: add `zig build lib` step; port MLT decoder to 32-bit targets
beetlebugorg Jul 11, 2026
e0a68f3
tiles: cross-platform file mmap — fix Windows cross-compile
beetlebugorg Jul 11, 2026
5153b59
style: zig fmt the tree + enforce `zig fmt --check` in CI
beetlebugorg Jul 11, 2026
220ae52
ci: add vulnerability scanning (Trivy fs scan + Dependabot)
beetlebugorg Jul 11, 2026
4b6d73c
ci: drop Trivy security scan
beetlebugorg Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: 2
updates:
# Zig package deps (build.zig.zon) aren't supported by Dependabot; the submodules
# carry the vendored catalogues. So the actionable surface here is the CI actions —
# keep them patched.
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
uses: mlugg/setup-zig@v2
with:
version: 0.16.0
- name: Format check
run: zig fmt --check src/ tools/ build.zig
- name: Build (native)
run: zig build -Doptimize=ReleaseFast
- name: Test
Expand Down
375 changes: 0 additions & 375 deletions CHANGELOG.md

This file was deleted.

102 changes: 102 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# tile57 — engine conventions

## The API style

The public surface (include/tile57.h, src/tile57.zig, bindings/go) follows one
style. Hold every new or renamed symbol against it.

**The pipeline is the organizing principle.** Everything is bake, then compose
(or bake, then render): source charts bake once to per-chart archives; every
output is produced from baked archives. Sections, in order: Version, Errors,
Bake, Render (the chart), Compose, Style, Util. The header, the Zig public
root, and the docs all follow that same section order.

**The unit is a chart, never "cell" in prose.** An ENC cell IS one chart at one
compilation scale. "Cell" survives only as spec-facing vocabulary: one
first-use definition ("a *cell*, in the spec's vocabulary"), the `cell` tile
property and other wire-format names, `<cell.000>` CLI placeholders and the
`cell`/`cells` CLI commands, and the s57/geometry module internals (the domain
that parses cells).

**Everything is in a family, and the family token always leads.** The prefix
names what a call runs against; never bury the family token mid-name.

1. *Handles namespace under their noun and mirror each other.* `tile57_chart`
(one open baked archive) and `tile57_compose` (the runtime compositor) are
peers: `tile57_chart_open` / `_close` / `_get_info` / `_tile` / `_png` /
`_query` ↔ `tile57_compose_open` / `_close` / `_get_meta` / `_tile` /
`_png` / `_query`. A noun prefix means the first parameter is that handle —
the C spelling of a method.
2. *Handle-free families lead with their section token, then what the call
acts on or yields* — the token's part of speech follows the section, so the
name reads naturally after it. `tile57_bake_*` is a verb family
(`bake_chart_bytes`, `bake_tree` — "bake the X"); `tile57_enc_*` is a domain
family (the raw S-57 source readers — ENC is the ONE vocabulary for raw
source data, never s57_/source_/scan_ variants); `tile57_style_*` is a
product family (`style_build` / `style_diff` / `style_template`, never
`build_style` — "the style's build/diff/template"). Small POD/domain noun
families follow the same shape: `pmtiles_metadata`, `status_str`,
`mariner_defaults`, `colortables_default`, `assets_free`.
3. *Bare `tile57_` is the library itself* — process/library-level plumbing
only: `tile57_version`, `tile57_free`, `tile57_warmup`. No handle method is
ever bare.

In Zig the namespace is the family, with the same two consequences: a member
never repeats its namespace (`ComposeSource.openFiles`, never
`compose.openComposeSourceFiles`; `compose.tile`, never `compose.composeTile`),
and the two handles mirror (`Chart.openPmtilesPath` ↔ `ComposeSource.open` /
`.openFiles`).

**Outputs are named by what you get, never by how it's produced or served.**
`tile`, `png`, `pdf`, `canvas`, `surface`, `query` — not serve/render/build/
generate. The chart and the compositor offer the SAME output set, so the names
mirror exactly: `tile57_chart_png` ↔ `tile57_compose_png`, `tile57_chart_tile`
↔ `tile57_compose_tile`, and so on. The compositor is "many charts in, one
chart out"; keep that symmetry when adding an output — it goes on both handles
or has a stated reason not to.

**The status model is universal.** Every call that can fail returns
`tile57_status` (never a bare int, count, or bool) and takes an optional
caller-owned `tile57_error*` as its LAST parameter. Out-parameters come after
inputs and are ALWAYS defined on return — the result on TILE57_OK, NULL/0
otherwise. "Nothing produced" is NOT a failure: OK with a NULL/zero out. Every
pointer argument is BADARG-checked (no NULL derefs across the ABI). Counts and
flags come back through optional out-pointers (NULL to ignore), not return
values. Say "call that can fail" in docs, not "fallible".

**Memory has one rule.** Calls that return bytes allocate `*out`; the caller
releases with `tile57_free(ptr)` — buffers are length-prefixed at allocation,
so the pointer is all it needs. POD across the seam: no Zig errors, slices, or
optionals cross the ABI.

**Ownership and borrowing are explicit.** The compositor BORROWS its charts
(charts outlive it; close the compositor first). A path-opened chart mmaps its
file (the file stays in place). No handle is internally synchronized; document
lifetime + threading on every handle-producing call.

**The Zig public root is curated, not a module mirror.** src/tile57.zig shapes
the public namespace by the API sections; when module layering puts an
implementation elsewhere (e.g. composed view renders live beside Chart because
the `compose` module is a dependency leaf without the render path), the root
still surfaces it under the name it belongs to (`tile57.compose.renderView`).
Public surface trumps internal layering.

**Go bindings track shape, not spelling.** Idiomatic Go names over the same
structure: `Open`/`OpenBytes` on the archive, package-level `Charts`/`Features`/
`FeaturesBytes`/`CatalogEntries` for the enc readers, `ComposeSource.Tile`
mirroring `tile57_compose_tile`, errors as wrapped sentinels
(`errors.Is(err, tile57.ErrParse)`).

## Other repo rules

- Never reference specs/*.md in code comments (specs/ is never committed);
describe designs inline.
- Docs read standalone: present-tense capability statements, no prior-version
framing ("now supports", "anymore"), no "plain language", no "fallible", no
"seam" in user-facing docs.
- Never `git add -A` (vendor/ submodules; charts/ + test/ hold huge untracked
local data). Stage explicit paths. Commit after every change.
- Never hardcode machine-specific paths in code or tests; real-cell paths come
from args/env.
- Validate conversion/portrayal changes on real ENC_ROOT cells, not just unit
tests.
84 changes: 45 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<h1 align="center">tile57</h1>

<p align="center">
<b>⚓ A high-performance, low-memory S-57/S-101 chart engine: vector tiles, S-52 styles, PNG and PDF.</b><br>
tile57 turns IHO S-57 ENC cells into Mapbox Vector Tiles with a matching MapLibre S-52
style, or renders finished charts directly to PNG and PDF — one Zig library with a
<b>⚓ An S-57/S-101 chart engine: vector tiles, S-52 styles, PNG and PDF.</b><br>
tile57 turns IHO S-57 ENC cells into vector tiles (MLT or MVT) with a matching MapLibre
S-52 style, or renders finished charts directly to PNG and PDF — one Zig library with a
C ABI, compiled natively or to WASM.
</p>

Expand Down Expand Up @@ -48,11 +48,10 @@ entirely with AI assistance. A few specific goals shape its design:
- **Language-agnostic embedding.** A thin C ABI (`libtile57.a`) bridges the Zig core to any
language with C FFI. Go bindings ship in the repo; others are straightforward additions.

- **An engine anyone can build on.** The end goal is an easy-to-use S-57/S-100 chart
rendering engine that anyone can use to build their marine app ideas — without first
becoming an IHO spec expert. Open a chart, get tiles, PNGs, or PDFs; the S-52 rules,
portrayal catalogue, and mariner settings are the engine's problem. Ideas it should make
easy:
- **An engine to build on.** The goal is an S-57/S-100 chart engine you can use to build
a marine app without first becoming an IHO spec expert. Open a chart, get tiles, PNGs,
or PDFs; the S-52 rules, portrayal catalogue, and mariner settings are the engine's
problem. It aims to support:
- an **anchor alarm** that draws your swing circle over a real chart,
- a **Windy plugin** overlaying forecast weather on ENC charts,
- a **native cross-platform Qt6 C++ chartplotter**,
Expand All @@ -61,46 +60,49 @@ entirely with AI assistance. A few specific goals shape its design:
---

**tile57** decodes NOAA/IHO **S-57** ENC cells and generates **vector tiles** by
`(z, x, y)` — MapLibre Tiles (MLT, the default bake format; MapLibre GL JS ≥ 5.12
decodes them natively) or Mapbox Vector Tiles (`--format mvt`) — running the
`(z, x, y)` — MapLibre Tiles (MLT, the default; MapLibre GL JS ≥ 5.12
decodes them natively) or Mapbox Vector Tiles — running the
official IHO **S-101 Portrayal Catalogue** in embedded Lua to produce S-52
nautical portrayal. Alongside the tiles it emits a **MapLibre GL style** and the
portrayal **assets** it references — colour tables, line styles, and the sprite
+ area-fill pattern atlases — so a renderer like
[MapLibre](https://github.com/maplibre/maplibre-native) can draw a chart directly.

It is **high-performance and low-memory** by design:
It holds only its working set:

- **Lazy, per-cell work.** A multi-cell ENC_ROOT is indexed cheaply (band + bbox);
cells are parsed and portrayed only when a requested tile needs them, then held
under an LRU bound. A **streaming** open reads a cell's bytes on demand (and
frees them on eviction), so a host holds only the working set — not the whole
catalogue.
- **Band-streamed bakes.** Baking an ENC_ROOT to one PMTiles archive streams
band-by-band (finest → coarsest, best-band dedup), so peak memory tracks the
largest single band.
- **Per-cell bakes.** Each ENC cell bakes to its own PMTiles at its compilation
scale, so a bake holds one cell at a time; the runtime compositor stitches the
cells by `(z, x, y)` on demand.
- **Pure-Zig core.** The foundational format/encode packages have no libc; only
the Lua portrayal + sprite rasterizer pull in C.

## Pipeline

```
S-57 ENC cell (.000)
│ ISO 8211 decode src/s57/iso8211.zig
│ ISO 8211 decode src/iso8211/
S-57 feature + geometry model src/s57/
S-101 portrayal (embedded Lua) src/portray/ + src/s100/
adapt S-57 → S-101 features src/s101/ (adapter)
portrayal instruction stream
S-101 feature records
│ S-101 portrayal (embedded Lua) src/portray/ + rules
portrayal instruction stream src/s101/ (instructions)
│ scene generation src/scene/ (project + clip + draw calls)
render Surface ──► MVT / MLT tiles (src/tiles/) + MapLibre style.json + assets
└───► PNG raster / vector PDF / terminal text (src/render/)
```

The stages are separate Zig modules — `s57` (including its ISO 8211 decoder),
`s100`, `tiles`, `render`, `scene`, `assets` — pure Zig with no libc; only the
Lua portrayal (`portray`) and the sprite rasterizer (`sprite`) pull in C. See
The stages are separate Zig modules — `iso8211`, `s57`, `s101`, `tiles`,
`render`, `scene`, `style` — pure Zig with no libc; only the Lua portrayal
(`portray`) and the sprite rasterizer (`sprite`) pull in C. See
[the architecture docs](docs/docs/architecture.md).

## Use it from Zig
Expand All @@ -114,41 +116,45 @@ const tile57 = @import("tile57");
var chart = try tile57.Chart.openPath("ENC_ROOT/", null, true);
defer chart.deinit();

if (try chart.tile(z, x, y)) |mvt| { // decompressed MVT bytes, or null if empty
defer tile57.freeBytes(mvt);
// … hand to your renderer …
}
const bbox = chart.bounds(); // geographic extent [w, s, e, n], or null
// … render a view (chart.renderView), query features, or bake an archive …
```

`Chart.openPath` streams cells on demand (working-set only); `Chart.openBytes` opens a
single in-memory cell. See [the Zig API docs](docs/docs/zig-api.md).
`Chart` renders views, queries features, and reads metadata. The runtime tile
path — bake each cell, then compose by `(z, x, y)` on demand — is exposed through
the [C ABI](include/tile57.h). See [the Zig API docs](docs/docs/zig-api.md).

## Use it from C

The same engine behind a thin C ABI ([`include/tile57.h`](include/tile57.h)):
The same engine behind a thin C ABI ([`include/tile57.h`](include/tile57.h)).
Tiles are made one way — bake each cell to its own PMTiles, then compose on
demand — the structure `tile57 bake ENC_ROOT -o out/` writes:

```c
// Open an on-disk ENC_ROOT directory (or a single .000 file).
tile57_chart *chart = tile57_chart_open("ENC_ROOT/");
uint8_t *mvt; size_t n;
if (tile57_chart_tile(chart, z, x, y, &mvt, &n) == TILE57_TILE_OK) {
/* … render mvt … */
tile57_free(mvt, n);
// out/ holds tiles/<CELL>.pmtiles (one per cell) + partition.tpart
const char *paths[] = { "out/tiles/US5MD1MC.pmtiles" };
tile57_compose_source *src = tile57_compose_open(paths, 1, "out/partition.tpart");

uint8_t *tile; size_t n;
if (tile57_compose_serve(src, z, x, y, &tile, &n) == 1) { /* decompressed MLT */
/* … hand the tile to your renderer … */
tile57_free(tile, n);
}
tile57_chart_close(chart);
tile57_compose_close(src);
```

`libtile57.a` also exposes the ENC_ROOT bake, the MapLibre style builder, and the
asset/atlas generators. See [the C API docs](docs/docs/c-api.md).
A separate `tile57_chart` handle renders finished PNG/PDF views and answers
metadata + object queries; `libtile57.a` also exposes the MapLibre style builder
and the asset/atlas generators. See [the C API docs](docs/docs/c-api.md).

## The `tile57` CLI

The offline tool bakes charts and emits portrayal assets:

```sh
zig build # builds zig-out/bin/tile57
tile57 bake CELL.000 -o out/ # one cell -> bundle (tiles + style + assets + manifest)
tile57 bake ENC_ROOT -o out/ # whole catalogue, band-streamed -> same bundle
tile57 bake CELL.000 -o out/ # one cell -> out/tiles/<CELL>.pmtiles + partition.tpart
tile57 bake ENC_ROOT -o out/ # whole catalogue -> per-cell tiles/ + partition.tpart
tile57 assets -o assets/ # colortables + linestyles + sprite + patterns
tile57 png ENC_ROOT --view -76.48,38.974,15 --size 1600x1200 -o chart.png
tile57 pdf ENC_ROOT --view -76.48,38.974,15 --size 1600x1200 -o chart.pdf
Expand All @@ -172,7 +178,7 @@ Docs source lives in [`docs/`](docs/): [intro](docs/docs/intro.md),
[getting started](docs/docs/getting-started.md), the
[Zig API](docs/docs/zig-api.md), the [C API](docs/docs/c-api.md),
the [architecture](docs/docs/architecture.md), and the
[tile schema](docs/docs/tile-schema.md). See also [`CHANGELOG.md`](CHANGELOG.md).
[tile schema](docs/docs/tile-schema.md).

## License

Expand Down
40 changes: 27 additions & 13 deletions bindings/go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,39 @@ replace github.com/beetlebugorg/tile57/bindings/go => /path/to/chartplotter-nati
```go
import tile57 "github.com/beetlebugorg/tile57/bindings/go"

// Bake a single cell.000 or a whole ENC_ROOT dir into a self-contained bundle.
cells, bbox, err := tile57.BakeBundle("/enc/ENC_ROOT", "/out/bundle", "", "", "", 0, 16, nil)
// Bake an ENC_ROOT: each cell becomes its own PMTiles under <out>/tiles/, plus
// an ownership partition at <out>/partition.tpart.
n, err := tile57.BakeTree("/enc/ENC_ROOT", "/out", 4, nil)

// Or serve tiles live, and publish the SCAMIN manifest for the style.
src, _ := tile57.Open("/enc/ENC_ROOT")
// Open the compositor over the baked archives + partition, and serve tiles.
src, _ := tile57.OpenCompose([]string{"/out/tiles/US5MD1MC.pmtiles"}, "/out/partition.tpart")
defer src.Close()
mvt, _ := src.Tile(13, 2359, 3139)
manifest := src.Scamin() // []uint32, ascending
body, owned, _ := src.Tile(13, 2359, 3139) // owned=false, body=nil => open ocean

// Or open one baked archive as a chart (bounds, scale, coverage, SCAMIN).
chart, _ := tile57.Open("/out/tiles/US5MD1MC.pmtiles")
defer chart.Close()
info := chart.Info() // zoom range, bounds, embedded 1:N scale
scamin := chart.Scamin() // []uint32, ascending

// Raw S-57 reading is handle-free (cell inventory, feature extraction).
charts, _ := tile57.Charts("/enc/ENC_ROOT")
water, _ := tile57.Features("/enc/ENC_ROOT/US5MD1MC/US5MD1MC.000", "DEPARE", "DRGARE")
```

## Surface

- **Charts** — `Open` (path, streaming), `OpenChartBytes` (one in-memory cell),
`OpenPMTiles` (baked bundle); `Source.Tile`, `Info`, `Meta`, `Scamin`,
`ClearCache`, `Close`.
- **Bake** — `BakeCells` (→ one PMTiles archive in memory), `BakeBundle` (→ a full
on-disk bundle: tiles + assets + per-scheme styles + manifest).
- **Assets / style** — `ColortablesDefault`, `Colortables`, `Linestyles`,
`SpriteAtlas`, `PatternAtlas`, `StyleTemplate`, `BuildStyle`, `Style`,
- **Charts (a baked archive: metadata + query)** — `Open` (path, mmap'd),
`OpenBytes`; `Source.Info`, `Meta`, `Scamin`, `Coverage`, `Close`.
- **S-57 source readers (handle-free)** — `Charts` (per-chart metadata of a .000 or
ENC_ROOT), `Features` / `FeaturesBytes` (GeoJSON extraction), `CatalogEntries`
(CATALOG.031 decode).
- **Bake** — `BakeChart` (one chart → PMTiles bytes), `BakeTree` (an ENC_ROOT → per-chart
archives), `BakeAssets` (portrayal assets in memory).
- **Compose** — `OpenCompose` (paths; owns its charts) / `OpenComposeCharts`
(borrows yours); `ComposeSource.Serve` (a tile, with an ownership flag), `Meta`,
`SavePartition`, `Close`.
- **Style** — `ColortablesDefault`, `Style`, `BuildStyle`, `StyleDiff`,
`MarinerDefaults`.

`libtile57` is not internally synchronized; every `Source` method is mutex-guarded,
Expand Down
11 changes: 6 additions & 5 deletions bindings/go/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package tile57
import "C"

import (
"fmt"
"unsafe"
)

Expand All @@ -19,8 +18,9 @@ import (
func ColortablesDefault() ([]byte, error) {
var out *C.uint8_t
var n C.size_t
if C.tile57_colortables_default(&out, &n) != 1 {
return nil, fmt.Errorf("tile57: colortables_default failed")
var cerr C.tile57_error
if st := C.tile57_colortables_default(&out, &n, &cerr); st != C.TILE57_OK {
return nil, statusError(st, &cerr)
}
return tileBytes(out, n), nil
}
Expand All @@ -39,8 +39,9 @@ func BakeAssets(catalogDir string) (Assets, error) {
cdir, free := cStringOrNil(catalogDir)
defer free()
var ca C.tile57_assets
if C.tile57_bake_assets(cdir, &ca) != 1 {
return Assets{}, fmt.Errorf("tile57: bake_assets failed")
var cerr C.tile57_error
if st := C.tile57_bake_assets(cdir, &ca, &cerr); st != C.TILE57_OK {
return Assets{}, statusError(st, &cerr)
}
defer C.tile57_assets_free(&ca)
return Assets{
Expand Down
Loading
Loading