Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
<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 reports the objects under a point,
and the text and pictures a chart carries. One Zig library with a C ABI, compiled
natively or to WASM.
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.
</p>

<p align="center">
Expand Down Expand Up @@ -47,8 +48,18 @@ diagrams it carries.
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 embeds anywhere.** The core is pure Zig with a C ABI. It compiles natively
and to WASM.
- **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.

## Start here

Expand All @@ -58,9 +69,14 @@ zig build # writes zig-out/bin/tile57

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
```

The first command bakes a catalogue. The second draws a chart straight to a PNG.
The last one removes the chart's solid areas, so you can draw it over a photo.

## What you can get

Expand All @@ -75,6 +91,8 @@ The first command bakes a catalogue. The second draws a chart straight to a PNG.
| **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.

Expand Down
9 changes: 7 additions & 2 deletions THIRD_PARTY_LICENSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ the same change (see the polylabel entry as the worked example).
| Library | Version | Where | License |
| --- | --- | --- | --- |
| Lua | 5.4.7 | `vendor/lua/` | MIT (© 1994–2024 Lua.org, PUC-Rio) |
| SQLite | 3.50.4 | `vendor/sqlite/` | public domain |
| nanosvg | 2013–14 (Mikko Mononen) | `vendor/nanosvg/` | zlib |
| stb_image_write | v1.16 (Sean Barrett) | `vendor/stb/` | public domain (MIT alternative) |
| Noto Sans Regular | 2026.05.01 (Google) | `vendor/fonts/NotoSans-Regular.ttf` | SIL Open Font License 1.1 |
Expand All @@ -30,9 +31,13 @@ the same change (see the polylabel entry as the worked example).
are parsed by `src/render/font.zig`, a from-scratch TrueType outline reader — no
font library is vendored.
- **stb_image_write** writes the PNG sprite atlases.
- **SQLite** reads MBTiles raster charts (`src/raster/mbtiles.zig`) where the
mariner left them. Built read-only from the amalgamation; see `addSqlite` in
build.zig for the trimmed feature set. The authors have dedicated it to the
public domain — no attribution is required, and this entry is a courtesy.

`vendor/lua/LICENSE.html` carries Lua's full notice; the nanosvg and stb licenses
are in the headers themselves.
`vendor/lua/LICENSE.html` carries Lua's full notice; the nanosvg, stb and SQLite
licenses are in the headers themselves.

## Ported algorithms

Expand Down
63 changes: 63 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,35 @@ fn addSvgRaster(b: *std.Build, mod: *std.Build.Module) void {
mod.addCSourceFile(.{ .file = b.path("src/sprite/svgraster.c"), .flags = &.{ "-std=gnu99", "-O2", "-fno-sanitize=undefined" } });
}

// Attach the vendored SQLite amalgamation to a module. Used by the `raster`
// module, which reads a community raster chart where it sits — MBTiles is a
// SQLite database, and the library that reads every SQLite database reads every
// MBTiles. Read-only and trimmed: no extension loading, no shared cache, no
// deprecated surface. THREADSAFE=1 (serialized) because a host streams tiles
// from a worker while its UI thread reads metadata, and a per-call mutex is
// nothing beside a JPEG decode.
fn addSqlite(b: *std.Build, mod: *std.Build.Module) void {
addSysrootIncludes(b, mod);
mod.addIncludePath(b.path("vendor/sqlite"));
mod.addCSourceFile(.{
.file = b.path("vendor/sqlite/sqlite3.c"),
.flags = &.{
"-std=gnu99",
"-O2",
"-fno-sanitize=undefined",
"-DSQLITE_THREADSAFE=1",
"-DSQLITE_DQS=0",
"-DSQLITE_DEFAULT_MEMSTATUS=0",
"-DSQLITE_OMIT_LOAD_EXTENSION",
"-DSQLITE_OMIT_DEPRECATED",
"-DSQLITE_OMIT_SHARED_CACHE",
"-DSQLITE_OMIT_PROGRESS_CALLBACK",
"-DSQLITE_OMIT_AUTHORIZATION",
"-DSQLITE_OMIT_UTF16",
},
});
}

// Re-import the pure packages into a consumer module (engine, libtile57.a, the
// baker). One list keeps the edge set in sync across all three.
fn addPkgs(mod: *std.Build.Module, pkgs: []const std.Build.Module.Import) void {
Expand Down Expand Up @@ -432,6 +461,27 @@ pub fn build(b: *std.Build) void {
});
addSvgRaster(b, sprite_mod);

// Raster charts (vendored SQLite): a chart made of pictures, read in place.
// libc + pic like `portray`, so SQLite is encapsulated here rather than
// spread across the lib and the baker, and the same objects link into both
// the PIE C++ host (libtile57.a) and the static baker. Target-less so it
// inherits the consumer's. NOT in pure_pkgs — `zig build test` stays
// libc-free, and the `tiles` module stays pure std.
const raster_mod = b.addModule("raster", .{
.root_source_file = b.path("src/raster/raster.zig"),
.link_libc = true,
.pic = true,
// The RNC bake writes the same per-chart archive the ENC bake does:
// PMTiles + PNG tiles (tiles) carrying the chart's own coverage
// (coverage, over s57's integer lon/lat point).
.imports = &.{
.{ .name = "tiles", .module = tiles_mod },
.{ .name = "coverage", .module = coverage_mod },
.{ .name = "s57", .module = s57_mod },
},
});
addSqlite(b, raster_mod);

// All pure packages, imported by name into engine / libtile57.a / the baker.
// (portray is libc, wired separately into the lib + baker only.)
const pure_pkgs = [_]std.Build.Module.Import{
Expand Down Expand Up @@ -517,6 +567,7 @@ pub fn build(b: *std.Build) void {
tile57_mod.addImport("coverage", coverage_mod); // per-cell coverage sidecar
tile57_mod.addImport("compose", compose_mod); // the runtime compositor
tile57_mod.addImport("errors", errors_mod); // the error taxonomy
tile57_mod.addImport("raster", raster_mod); // raster charts (MBTiles today)

// Static library (libtile57.a): C ABI + embedded Lua. Its own root so
// the C sources / libc only land in the archive (linked by the C++ host),
Expand All @@ -539,6 +590,7 @@ pub fn build(b: *std.Build) void {
lib_mod.addImport("compose", compose_mod); // C ABI: tile57_compose_* (the runtime compositor)
lib_mod.addImport("coverage", coverage_mod); // the tile57 public root re-exports it
lib_mod.addImport("errors", errors_mod); // C ABI: error taxonomy -> tile57_status
lib_mod.addImport("raster", raster_mod); // C ABI: tile57_raster_chart_* (MBTiles)
// The full engine surface as a NAMED import (not a root.zig file-import), so the
// single root.zig file isn't claimed by both lib_mod and engine_full (which bundle
// pulls in) — Zig requires each file to belong to exactly one module per artifact.
Expand Down Expand Up @@ -657,6 +709,7 @@ pub fn build(b: *std.Build) void {
.{ .name = "geometry", .module = geometry_mod }, // compose-tile --scan reads the boolean diagnostics
.{ .name = "render", .module = render_mod }, // renderpng pixel path
.{ .name = "chart", .module = chart_mod }, // ENC_ROOT view renders
.{ .name = "raster", .module = raster_mod }, // `raster info`
},
}),
});
Expand Down Expand Up @@ -764,6 +817,16 @@ pub fn build(b: *std.Build) void {
const tiles_test = addPkgTest(b, test_step, "src/tiles/tiles.zig", target, optimize, &.{});
tiles_test.link_libc = true;
addMvtFixture(b, tiles_test); // pmtiles.zig's round-trip test embeds it
// The raster charts link the vendored SQLite. Their real-file tests skip
// unless TILE57_MBTILES points at a chart — community files are hundreds of
// megabytes and cannot live in the repo.
const raster_test = addPkgTest(b, test_step, "src/raster/raster.zig", target, optimize, &.{
.{ .name = "tiles", .module = tiles_mod },
.{ .name = "coverage", .module = coverage_mod },
.{ .name = "s57", .module = s57_mod },
});
raster_test.link_libc = true;
addSqlite(b, raster_test);
_ = addPkgTest(b, test_step, "src/scene/scene.zig", target, optimize, &.{
.{ .name = "s57", .module = s57_mod },
.{ .name = "s101", .module = s101_mod },
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ tile57 is built to hold only its working set:

## The live-composite bake

One `bake` command (`tile57 bake <cell.000 | ENC_ROOT> -o out/`) writes the
One `bake` command (`tile57 bake <cell.000 | ENC_ROOT | chart.KAP | BSB_ROOT> -o out/`) writes the
live-composite structure — per-chart tiles plus the ownership partition a runtime
compositor serves them from:

Expand Down
9 changes: 8 additions & 1 deletion docs/docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ automatically); `ENC_ROOT` is a whole catalogue directory.
### `bake`

```
tile57 bake <cell.000 | ENC_ROOT> -o <out-dir> [--rules DIR] [-j N]
tile57 bake <cell.000 | ENC_ROOT | chart.KAP | BSB_ROOT> -o <out-dir> [--rules DIR] [-j N]
```

Produces the **live-composite structure** every other output is served from:
Expand All @@ -33,6 +33,13 @@ There is no merged archive — a runtime compositor serves any tile on demand
and re-runs are incremental: an archive already newer than its whole input
(`.000` + update chain) is skipped.

A `.KAP` sheet or a `BSB_ROOT` of them bakes the same structure with PNG tiles,
warped to web mercator through the sheet's own control points and clipped to its
`PLY` border. Each archive carries the sheet's coverage, compilation scale and
edition date, so a folder of RNCs quilts the way a folder of cells does — see
[Raster charts](raster-charts). One bake writes one kind of library: a directory
holding both `.000` cells and `.KAP` sheets is refused.

`-j`/`--workers` sets the bake thread count (default `min(cores/2, 8)`). Each
worker holds a whole cell's parse + portray + raster working set, so this is a
memory bound rather than a core count.
Expand Down
Binary file added docs/docs/img/raster-over-chart.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/docs/img/social-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading