Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
16e0e6d
Compile the full engine to wasm32-wasi (zig build wasm-engine)
beetlebugorg Aug 24, 2026
5448843
smoke: compose two baked cells and serve from the composite
beetlebugorg Aug 24, 2026
750cef2
docs: the wasm engine build + wasi-libc sjlj runtime notice
beetlebugorg Aug 24, 2026
ebc4185
bindings: browser WASI shim, JS wrapper, and an in-page chartplotter …
beetlebugorg Aug 24, 2026
f788e2d
bindings: writable MemFS in the shim; bakeZip in the wrapper
beetlebugorg Aug 24, 2026
4f274ac
bindings: WebGPU renderer and a full chartplotter demo
beetlebugorg Aug 24, 2026
3803585
demo: run the engine in a Web Worker, with a real loader
beetlebugorg Aug 24, 2026
04a30a9
demo: name the insecure-context cause when WebGPU is hidden
beetlebugorg Aug 24, 2026
eff424c
docs: embed the wasm chartplotter as the site's live demo
beetlebugorg Aug 24, 2026
c34eeda
demo: wear the S-52 palette
beetlebugorg Aug 24, 2026
2f9a8ea
ci: format build.zig; keep the time(3) binding portable
beetlebugorg Aug 24, 2026
a4fd821
demo: bake cells in parallel across a pool of engine workers
beetlebugorg Aug 24, 2026
d91ea13
bindings: survive 2 GiB of linear memory; free batch files as bakes f…
beetlebugorg Aug 24, 2026
723f3af
wasm: emit the standardized exception-handling instructions
beetlebugorg Aug 24, 2026
02a5278
demo: persistent chart library, view-windowed residency, live zoom sc…
beetlebugorg Aug 24, 2026
1d561d7
demo: prefer general charts when the residency cap bites
beetlebugorg Aug 24, 2026
4e9a1e5
demo: show the stored library instantly, with its size; cap residency…
beetlebugorg Aug 24, 2026
758980e
demo: fit the view to the chart cluster, not the overview outliers
beetlebugorg Aug 24, 2026
3c97c2e
demo: fix wide-view wrap tearing and the storage readout
beetlebugorg Aug 24, 2026
4f9b908
demo: fill spare residency with out-of-window charts; remember the view
beetlebugorg Aug 24, 2026
b6b0ad8
demo: view rotation, pan flick, and prefetch margins
beetlebugorg Aug 24, 2026
3a0f9ed
README: lead with the live demo, in plain language
beetlebugorg Aug 24, 2026
b387bdc
bindings: one JavaScript package, named tile57, carrying the full engine
beetlebugorg Aug 24, 2026
9f8dd8b
demo: the chartplotter shell, mariner settings, and the cursor pick
beetlebugorg Aug 24, 2026
995f0a8
demo: recreational depth defaults
beetlebugorg Aug 24, 2026
82ba012
demo: depths display in feet by default
beetlebugorg Aug 24, 2026
428544e
demo: await the scheme swap before the rebuild
beetlebugorg Aug 24, 2026
232c324
demo: crosshair cursor over the chart
beetlebugorg Aug 24, 2026
964c05e
demo: drop the download-size note from the splash
beetlebugorg Aug 24, 2026
69919b3
demo: US5MD13M as the sample chart
beetlebugorg Aug 24, 2026
69374e5
demo: a real Annapolis sample, five charts deep
beetlebugorg Aug 24, 2026
731b574
render: gate the sector-leg variant in the resolver
beetlebugorg Aug 24, 2026
81a7427
demo: eight-cell Annapolis sample; README names quilting
beetlebugorg Aug 24, 2026
9f4709d
demo: two-column pick report; Esc closes surfaces
beetlebugorg Aug 24, 2026
173c2ef
demo: show TXTDSC files in the pick report; inline SVG icons
beetlebugorg Aug 24, 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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ jobs:
# Debug keeps the tests' safety checks. The matrix below covers ReleaseFast.
- name: Build and test
run: zig build install test
# The full-engine wasm reactor (docs.yml ships it as the live demo).
- name: Build wasm engine
run: zig build wasm-engine

cross-compile:
runs-on: ubuntu-latest
Expand Down
52 changes: 50 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ name: Docs
on:
push:
branches: [main]
# The site ships the live wasm chartplotter demo, so engine changes
# redeploy it too — the demo always runs the current engine.
paths:
- "docs/**"
- "bindings/js/**"
- "src/**"
- "build.zig"
- "build.zig.zon"
- ".github/workflows/docs.yml"
workflow_dispatch:

Expand All @@ -22,14 +28,56 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
# Recursive: the wasm engine embeds the portrayal catalogue submodule.
- uses: actions/checkout@v7
with:
submodules: recursive

- uses: actions/setup-node@v7
with:
node-version: "20"

# No committed lockfile, so `npm install` (not `npm ci`). The site is
# content-only — no submodules, no app build needed.
- name: Install Zig
uses: mlugg/setup-zig@v2
with:
version: 0.16.0

# The live demo: the full engine as wasm plus the demo page, staged
# under static/demo-app/ and embedded by src/pages/demo.jsx at /demo.
# GitHub Pages is https, so WebGPU is available to it. ReleaseSmall:
# one third the download, and this workload runs just as fast.
- name: Build wasm engine
run: zig build wasm-engine -Doptimize=ReleaseSmall

- name: Stage demo
run: |
mkdir -p docs/static/demo-app
cp bindings/js/demo.html docs/static/demo-app/index.html
cp bindings/js/wasi-shim.mjs bindings/js/tile57.mjs \
bindings/js/gpu-renderer.mjs bindings/js/engine-worker.mjs \
bindings/js/worker-rpc.mjs bindings/js/bake-pool.mjs \
bindings/js/chart-library.mjs \
docs/static/demo-app/
cp -r bindings/js/demo docs/static/demo-app/demo
cp zig-out/bin/tile57-engine.wasm docs/static/demo-app/

# A first visit with no charts offers a sample: Annapolis (public
# domain NOAA cells) - the harbor at 1:12,000, the Severn and bay
# approaches at 1:40,000, and the band-3/4 context so zooming out
# still shows chart. Best effort: without it the welcome card just
# keeps the download link alone.
- name: Fetch the sample charts
continue-on-error: true
run: |
tmp=$(mktemp -d)
for c in US5MD1MB US5MD1MC US5MD1MD US5MD1LC US5MD13M US5MD12M US4MD1DD US3EC08M; do
curl -fsSL -o "$tmp/$c.zip" "https://charts.noaa.gov/ENCs/$c.zip"
unzip -q -o "$tmp/$c.zip" -d "$tmp/enc"
done
(cd "$tmp/enc" && zip -qr "$tmp/sample.zip" .)
cp "$tmp/sample.zip" docs/static/demo-app/sample.zip

# No committed lockfile, so `npm install` (not `npm ci`).
- name: Install
working-directory: docs
run: npm install
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ zig-pkg/

# Local work in progress.
scratchpad/

# The full-engine wasm in the JS package is a build artifact (npm run build)
bindings/js/tile57-engine.wasm
271 changes: 62 additions & 209 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,237 +2,89 @@

<p align="center">
<b>⚓ Official nautical charts, ready to draw.</b><br>
tile57 reads IHO <b>S-101</b> and <b>S-57</b> charts and gives a renderer what it
needs: vector tiles with a matching MapLibre S-52 style, a draw-ready GPU scene,
pixel draw calls, or finished PNG and PDF. It also reads <b>raster charts</b> —
satellite photos and RNC sheets — and draws the official chart on top of them.
It reports the objects under a point, and the text and pictures a chart carries.
One Zig library with a C ABI.
tile57 reads the electronic charts hydrographic offices publish and draws
them the way the standard says they should look: in your app, on your
server, or right in the browser.
</p>

<p align="center">
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="License"></a>
<a href="https://beetlebugorg.github.io/tile57/demo"><b>🌊 Live demo</b></a>
&nbsp;·&nbsp;
<a href="https://beetlebugorg.github.io/tile57/"><b>📚 Docs</b></a>
&nbsp;·&nbsp;
📚 <b><a href="docs/docs/intro.md">Docs →</a></b>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="License"></a>
</p>

---

> [!WARNING]
> **Not for navigation.** This is not a certified navigation product. Do not use it
> to navigate. Refer to [Known limitations](docs/docs/limitations.md).
## Try it in your browser

---
<a href="https://beetlebugorg.github.io/tile57/demo">
<img src="docs/docs/img/wasm-chartplotter.webp" alt="Annapolis Harbor drawn by tile57" width="100%">
</a>

Hydrographic offices publish charts as S-101 and S-57 datasets. Those formats
carry the survey, not a picture of it. tile57 reads them and produces the picture,
in the form your renderer wants: vector tiles, a draw-ready GPU scene, pixel draw
calls, or a finished page. It also answers questions about the chart — which
objects sit under a point, what the chart states about them, and the notes and
diagrams it carries.

## Why it is different

- **The portrayal is official, not an imitation.** tile57 runs the IHO **S-101
Portrayal Catalogue** against the feature records in the chart. No symbol is a
look-alike. You get depth areas and contours, buoys and beacons with correct
symbols, lights with sector lines, soundings, and place names.
- **It reads the format the world is moving to.** A native S-101 chart feeds the
portrayal engine directly. tile57 converts an S-57 chart to the same S-101 model
first, so everything above the conversion sees one model.
- **It renders more than tiles.** The same engine writes PNG, PDF, and a callback
stream your own renderer can paint. It also answers cursor picks.
- **It feeds a GPU directly.** tile57 portrays a whole view into one draw-ready
scene: vertex and quad buffers, a uniform block, and ranges already sorted into
paint order. A host uploads it once, then walks the ranges. Geometry stays
north-up in world space and the host applies the view rotation, so a course-up
view that turns continuously never rebuilds its scene. The repo ships reference
shaders for Metal, Direct3D and Vulkan.
- **It combines raster charts and satellite photos with the official chart.**
Add your own satellite photos as MBTiles, or an RNC sheet as BSB/KAP. tile57
draws them below the official chart. The official chart then removes its solid
blue and yellow areas, so you can see the photo through them. All the depth
contours, buoys, lights and soundings stay on top. If your chart is old, you
see the place as it is today, and you read the official marks over it. On a
GPU this happens pixel by pixel, so the chart keeps its colors everywhere the
photo does not reach. tile57 also quilts many RNC sheets into one map. For
each area it uses the sheet with the correct scale for your zoom. Where two
sheets cover the same water at that scale, it uses the newer edition. This is
the rule it uses for official charts. See [Raster charts](docs/docs/raster-charts.md).
- **It embeds anywhere.** The core is pure Zig with a C ABI.
The **[live demo](https://beetlebugorg.github.io/tile57/demo)** is an
S-57/S-101 chart viewer in one page. Grab a free chart zip from
[NOAA](https://charts.noaa.gov/ENCs/ENCs.shtml), drop it on the map, and the
page does the rest: it bakes the charts, keeps them in your browser, and draws
them with WebGPU. Pan, zoom, and rotate a whole chart library. Nothing is
uploaded and there is no server.

> [!WARNING]
> **Not for navigation.** This is not a certified navigation product. Do not use
> it to navigate. Refer to [Known limitations](docs/docs/limitations.md).

## What you can do with it

- **Build a chartplotter** for desktop, mobile, embedded, or pure web. Point
tile57 at a folder of charts and it becomes one seamless, queryable map.
- **Quilt a whole library.** Charts at every scale stitch into one chart: the
most detailed chart wins each stretch of water, the general chart fills
around it, and a newer edition wins an overlap. Harbor to ocean is one
continuous map, the way an ECDIS quilts.
- **Serve charts to any map client.** Bake once and serve standard vector
tiles with a matching style; MapLibre draws them out of the box.
- **Draw at full speed.** tile57 hands your GPU a ready-to-draw scene. Pan,
zoom, and rotate without rebuilding anything.
- **Put charts on paper.** Finished PNG images and vector PDF pages, straight
from the source data.
- **See the world under the chart.** Satellite photos (MBTiles) and scanned
raster charts (BSB/KAP) draw beneath the official one, which opens up so you
can see through it. Every buoy, light, and depth stays on top.
- **Ask the chart questions.** What is under the cursor, what the chart says
about it, and the notes and diagrams it carries.

The portrayal is official, not a look-alike: tile57 runs the IHO's own
portrayal rules against the chart's own records. It reads today's charts
(S-57) and tomorrow's (S-101), and it embeds anywhere: one small library
with no runtime dependencies.

## Start here

```sh
brew install beetlebugorg/tap/tile57 # or grab a binary from Releases
brew install beetlebugorg/tap/tile57 # or grab a binary from Releases

tile57 bake ENC_ROOT -o out/ # every chart -> its own archive
tile57 bake ENC_ROOT -o out/ # every chart -> its own archive
tile57 png ENC_ROOT --view -76.48,38.974,15 --size 1600x1200 -o chart.png

tile57 raster info photos.mbtiles # what the file really contains
tile57 bake harbour.KAP -o out/ # an RNC sheet -> the same archive
tile57 png ENC_ROOT --over-image --view -76.48,38.974,15 -o over.png
```

`bake` turns a catalogue into per-chart archives. `png` draws a chart straight to
an image. `--over-image` removes the chart's solid areas, so you can draw it over
a photo.

Binaries for macOS, Linux and Windows are attached to every
[release](https://github.com/beetlebugorg/tile57/releases), alongside a `.deb`
and the static library. To build it yourself you need Zig 0.16 and the
submodules — see [Installation](docs/docs/installation.md).

## What you can get

| Output | Call | What it is |
|---|---|---|
| **Vector tiles** | `tile57_compose_tile` | MapLibre Tiles (MLT) or Mapbox Vector Tiles, by `(z, x, y)` |
| **Style + assets** | `tile57_style_build`, `tile57_bake_assets` | A MapLibre GL style, colour tables, line styles, sprite and pattern atlases |
| **PNG** | `tile57_chart_png` | A finished raster view |
| **PDF** | `tile57_chart_pdf` | A vector page, 1 px = 1 pt |
| **Draw calls** | `tile57_chart_canvas` | Pixel-space paint calls for your own rasterizer |
| **Tagged geometry** | `tile57_chart_surface` | World-space geometry, each call tagged with its S-57 class |
| **GPU scene** | `tile57_chart_gpu_scene` | Draw-ready vertex, quad and range buffers, plus the sprite and SDF atlases |
| **Pick** | `tile57_chart_query` | The objects under a point, with their attributes |
| **Notes and diagrams** | `tile57_aux_get` | The text and picture files a chart's features point at |
| **Raster charts** | `tile57_raster_chart_*` | Tiles from a satellite photo file or a BSB/KAP RNC sheet |
| **Quilted RNC** | `tile57_compose_rasters` | Many RNC sheets as one quilted map |

MLT is the default tile encoding. MapLibre GL JS 5.12 and later decode it natively.

---

# Technical reference

## How it works

tile57 reads two source formats. Both are `.000` files — a *cell*, in the spec's
vocabulary — and tile57 detects which one it holds from the file itself. Both
converge on the same S-101 feature records.

```
S-101 ENC (.000) S-57 ENC chart (.000)
│ ISO 8211 decode │ ISO 8211 decode src/iso8211/
▼ ▼
S-100 spatial + feature S-57 feature + geometry src/s57/ · src/s101/
records model
│ assemble to S-101 │ adapt S-57 → S-101 (native.zig / adapter.zig)
└──────────────┬──────────────┘
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/)
└───► draw-ready GPU scene (src/render/gpu.zig) + shaders/
```

tile57 reads a native S-101 chart straight into the S-101 model. That chart's
in-band code tables already carry the S-101 class and attribute names, so no
conversion runs. tile57 applies the chart's update files on load. tile57 reads an
S-57 chart into the S-57 model and adapts it. The adaptation follows S-65 and is
best-effort. Refer to [limitations](docs/docs/limitations.md).

Each stage is a separate Zig module: `iso8211`, `s57`, `s101`, `tiles`, `render`,
`scene`, and `style`. Those modules need no libc. Only the Lua portrayal
(`portray`) and the sprite rasterizer (`sprite`) use C. Refer to
[the architecture docs](docs/docs/architecture.md).

## How it holds memory

- **Work is lazy, and it is per chart.** tile57 indexes a multi-chart ENC_ROOT by
band and bounding box. It parses and portrays a chart only when a requested tile
needs it. An LRU bound caps how many it holds. A streaming open reads a chart's
bytes on demand and frees them on eviction.
- **Each chart bakes on its own.** Each chart bakes to its own PMTiles archive at
its compilation scale. A bake holds one chart at a time. The runtime compositor
stitches the archives by `(z, x, y)` on demand. There is no merged archive.
Binaries for macOS, Linux, and Windows are attached to every
[release](https://github.com/beetlebugorg/tile57/releases). Use the engine
from [C](https://beetlebugorg.github.io/tile57/c-api),
[Zig](https://beetlebugorg.github.io/tile57/zig-api),
[JavaScript](https://beetlebugorg.github.io/tile57/wasm), or
[Go](https://github.com/beetlebugorg/tile57/tree/main/bindings/go), or through
the [CLI](https://beetlebugorg.github.io/tile57/cli). The
[docs](https://beetlebugorg.github.io/tile57/) cover everything else.

## Use it from Zig

Add tile57 as a dependency, then import it:

```zig
const tile57 = @import("tile57");

// Open an ENC_ROOT directory, or a single .000 file, as a streaming chart.
var chart = try tile57.Chart.openPath("ENC_ROOT/", null, true);
defer chart.deinit();

const bbox = chart.bounds(); // geographic extent [w, s, e, n], or null
// … render a view (chart.renderView), query features, or bake an archive …
```

`Chart` renders views, queries features, and reads metadata. Refer to
[the Zig API docs](docs/docs/zig-api.md).

## Use it from C

The same engine sits behind a thin C ABI
([`include/tile57.h`](include/tile57.h)). `tile57 bake ENC_ROOT -o out/` writes one
directory per chart. `tile57_compose_tree` opens that whole tree in one call and
serves any tile on demand.

```c
// out/ holds <CHART>/<CHART>.pmtiles per chart, with the files that chart
// references, plus out/partition.tpart.
tile57_compose *c = NULL;
uint32_t charts = 0;
if (tile57_compose_tree("out/", &c, &charts, NULL) != TILE57_OK)
return 1;

uint8_t *tile = NULL;
size_t len = 0;
if (tile57_compose_tile(c, z, x, y, &tile, &len, NULL, NULL) == TILE57_OK && tile) {
/* … hand the decompressed MLT tile to your renderer … */
tile57_free(tile);
}
tile57_compose_close(c);
```

A `tile57_chart` handle renders PNG and PDF views and answers metadata and object
queries. `libtile57.a` also exposes the MapLibre style builder and the asset
generators. Refer to [the C API docs](docs/docs/c-api.md).

## The `tile57` CLI

The offline tool bakes charts and emits portrayal assets. Every command takes a
native S-101 or an S-57 `.000` file, and tile57 detects the format.

```sh
tile57 bake CELL.000 -o out/ # one chart -> out/<CELL>/<CELL>.pmtiles
tile57 bake ENC_ROOT -o out/ # a catalogue -> one directory per chart
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
tile57 ascii CELL.000 --view -76.48,38.974,13 --ansi --tui # the chart in your terminal
tile57 s101 CELL.000 # inspect a native S-101 dataset
```

## Build

The Zig engine and the CLI need Zig 0.16 only:
## Build from source

```sh
git submodule update --init --recursive # the vendored S-101 catalogue
zig build && zig build test
zig build && zig build test # needs Zig 0.16 only
```

Refer to [docs/installation](docs/docs/installation.md) for full instructions.

## Documentation

The docs source is 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).

## AI-First Development
## AI-first development

This project is built with AI assistance. Use AI tools freely. The most useful
contribution is a clear set of requirements, or a rough prototype of what you
Expand All @@ -242,5 +94,6 @@ want, rather than a patch. Refer to the
## License

tile57's own code is [MIT](LICENSE) © Jeremy Collins. It embeds the IHO S-101
Portrayal Catalogue (© IHO). It vendors nanosvg (zlib) and stb_image_write (public
domain). NOAA ENC charts are U.S. public domain and **not for navigation**.
Portrayal Catalogue (© IHO). It vendors nanosvg (zlib) and stb_image_write
(public domain). NOAA ENC charts are U.S. public domain and **not for
navigation**.
Loading
Loading