Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
84e4b4c
feat(server): ownership-partition debug overlay per provider
beetlebugorg Jul 8, 2026
f5c1494
feat(web): partition-debug overlay controls in dev-tools (Advanced)
beetlebugorg Jul 8, 2026
dded2b1
feat(bake): per-cell composite bake replaces the in-bake combiner
beetlebugorg Jul 8, 2026
9c28bf6
fix(bake): composeProvider errors when all cells fail to bake (don't …
beetlebugorg Jul 8, 2026
45e296d
feat(bake): CLI `chartplotter bake` uses the per-cell composite too (…
beetlebugorg Jul 8, 2026
d02368a
feat(import): show an ETA on the composite bake progress
beetlebugorg Jul 9, 2026
77dff00
feat(import): live compose progress + server-owned status wording
beetlebugorg Jul 9, 2026
dd9d189
feat(import): name the chart being baked + pin the count/ETA so they …
beetlebugorg Jul 9, 2026
8ccb524
feat(tiles): live runtime-compositor tile source (tile57 ComposeSource)
beetlebugorg Jul 9, 2026
4f68330
feat(tiles): surface registry-only sets in /api/packs so they render …
beetlebugorg Jul 9, 2026
f064a60
feat(tiles): default imports to the live runtime compositor (no distr…
beetlebugorg Jul 9, 2026
ebce1cc
feat(tiles): ownership-aware tile caching for the live compositor
beetlebugorg Jul 9, 2026
b370696
refactor(bake): make live cell-bake + compose the only tile-productio…
beetlebugorg Jul 9, 2026
fb50894
refactor(live): rename the server's live per-cell dir cells-pm -> tiles
beetlebugorg Jul 9, 2026
f84fcea
feat(live): make live-compositor providers first-class in the pack ha…
beetlebugorg Jul 9, 2026
79a2a6d
fix(ui): brand a bare provider set as its display name (NOAA, not noaa)
beetlebugorg Jul 9, 2026
38568c3
refactor(discovery): scanPacks reads only the flat standalone-archive…
beetlebugorg Jul 9, 2026
e5779fb
feat(cache): content-addressed ?g token for live providers (sha-of-shas)
beetlebugorg Jul 9, 2026
762fbf0
feat(live): progressive per-batch re-key so an import fills in the ma…
beetlebugorg Jul 9, 2026
7fe8d1e
feat(bake): parallel tree-bake — the host bakes cells concurrently vi…
beetlebugorg Jul 9, 2026
1faa9ad
fix(cli): update `chartplotter bake` to the parallel BakeTree Prepare…
beetlebugorg Jul 9, 2026
e7ac8fa
feat(engine): track the tile57 v0.2.0 ABI — handle-free Cells/Feature…
beetlebugorg Jul 10, 2026
04770cb
chore(engine): tile57 @ 042dd5e — complex-linestyle style layers (+SY…
beetlebugorg Jul 10, 2026
244fd16
chore(engine): tile57 @ c64de73 — tessellated complex linestyles (re-…
beetlebugorg Jul 10, 2026
223bb93
fix(import): read chart metadata from the ENC tree — the 'Reading cha…
beetlebugorg Jul 10, 2026
10e4169
chore(engine): tile57 @ 7f2a6c0 — S-52 edge masking in boundary fallb…
beetlebugorg Jul 10, 2026
d49066a
chore(engine): tile57 @ 046f312 — fill-up quilting (finer cells serve…
beetlebugorg Jul 10, 2026
e3f94bd
fix(serve): don't re-serve kept live archives baked by another engine…
beetlebugorg Jul 10, 2026
a3944fe
fix(server): stamp the engine commit into the live cache-bust token
beetlebugorg Jul 10, 2026
5c5fac9
feat(engine): tile57 v0.3.0 family rename — Charts/BakeChart call sit…
beetlebugorg Jul 11, 2026
1798ffd
fix(server): engine commit reaches the boot staleness gate — New take…
beetlebugorg Jul 11, 2026
6c4c266
feat(server): engine re-bakes stage to tiles.next and swap LAST — old…
beetlebugorg Jul 11, 2026
43895cd
style: gofmt the tree + scope fmt to this repo (not the tile57 submod…
beetlebugorg Jul 11, 2026
b61982c
security: bump toolchain to go1.26.5 (fixes GO-2026-5856)
beetlebugorg Jul 11, 2026
abbc32f
chore(ci): use Go 1.26.5 so govulncheck picks up the crypto/tls fix (…
beetlebugorg Jul 11, 2026
ae9a0ed
build(deps): move tile57 submodule pin to the Windows-fix commit (4b6…
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
git -C chartplotter submodule update --init --recursive tile57
- uses: actions/setup-go@v6
with:
go-version: '1.26'
go-version: '1.26.5'
cache: true
cache-dependency-path: chartplotter/go.sum
- name: Install Zig
Expand Down Expand Up @@ -65,7 +65,7 @@ jobs:
git -C chartplotter submodule update --init --recursive tile57
- uses: actions/setup-go@v6
with:
go-version: '1.26'
go-version: '1.26.5'
cache: true
cache-dependency-path: chartplotter/go.sum
- name: Install Zig
Expand Down Expand Up @@ -95,7 +95,7 @@ jobs:
git -C chartplotter submodule update --init --recursive tile57
- uses: actions/setup-go@v6
with:
go-version: '1.26'
go-version: '1.26.5'
cache: true
cache-dependency-path: chartplotter/go.sum
- name: Install Zig
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:

- uses: actions/setup-go@v6
with:
go-version: "1.26"
go-version: "1.26.5"
cache: true
cache-dependency-path: chartplotter/go.sum
- name: Install Zig
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
git -C chartplotter submodule update --init --recursive tile57
- uses: actions/setup-go@v6
with:
go-version: "1.26"
go-version: "1.26.5"
cache: true
cache-dependency-path: chartplotter/go.sum
- name: Install Zig
Expand Down
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -305,16 +305,16 @@ vet:

# Format with the gofmt of the toolchain go.mod pins (Go 1.26), NOT whatever
# gofmt happens to be on PATH — gofmt's rules change between Go minor releases,
# so a stray 1.25 gofmt reintroduces drift that the 1.26 CI check rejects. Invoke
# gofmt over `.` (not `go fmt ./...`, which can skip build-tagged files) so the
# file set matches the CI `gofmt -l .` gate exactly.
# so a stray 1.25 gofmt reintroduces drift that the 1.26 CI check rejects. Scope
# to THIS repo's tracked *.go via `git ls-files` (not `.`, which walks into the
# ./tile57 engine submodule — chartplotter-go doesn't own its formatting).
fmt:
@"$$(go env GOROOT)/bin/gofmt" -w .
@"$$(go env GOROOT)/bin/gofmt" -w $$(git ls-files '*.go')

# Mirror the CI gofmt gate exactly, using the same toolchain gofmt as `fmt`.
fmt-check:
@GOFMT="$$(go env GOROOT)/bin/gofmt"; \
out="$$($$GOFMT -l .)"; \
out="$$($$GOFMT -l $$(git ls-files '*.go'))"; \
test -z "$$out" || { echo "needs gofmt:"; echo "$$out"; exit 1; }

tidy:
Expand Down
95 changes: 54 additions & 41 deletions cmd/chartplotter/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ package main

import (
"archive/zip"
"encoding/json"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"time"

"github.com/beetlebugorg/chartplotter/internal/engine/baker"
"github.com/beetlebugorg/chartplotter/internal/engine/tilesource"
)

// bakeCmd bakes S-57 ENC base cells into a PMTiles archive of MVT tiles, for
Expand All @@ -28,42 +31,67 @@ type bakeCmd struct {
}

func (c bakeCmd) Run() error {
// libtile57 is the sole bake engine. A *.pmtiles/-mbtiles -o writes ONE flat
// merged archive (+ optional --manifest / aux.zip) — the coverage-clipped
// composite resolves best-available inside the single archive, so there are
// no per-band archives any more (the retired --bands). Any other -o is a
// self-contained bundle directory (tiles/chart.pmtiles + per-scheme style +
// assets + manifest.json).
switch strings.ToLower(filepath.Ext(c.Out)) {
case ".pmtiles", ".mbtiles":
return c.runTile57Archive()
}
return c.runTile57Bundle()
}

// runTile57Bundle bakes the ENC inputs with the native libtile57 engine into a
// self-contained chart bundle (tiles/chart.pmtiles + per-scheme SCAMIN-bucketed
// style-*.json + assets + manifest.json) under the output directory. The engine
// reads the ENC from disk, so a lone directory or .000 is handed over directly and
// only zips / multiple inputs are staged into a temp directory of cells first.
func (c bakeCmd) runTile57Bundle() error {
// libtile57 makes tiles ONE way: bake each cell to its own native-scale PMTiles + build the
// ownership partition. This CLI writes that LIVE STRUCTURE — the exact inputs the runtime
// compositor consumes: <outDir>/tiles/<cell>.pmtiles + <outDir>/partition.tpart. There is no
// merged chart.pmtiles bundle; the style + assets are produced separately (the asset commands /
// the server's runtime style), not per bake.
input, cleanup, err := c.tile57Input()
if err != nil {
return err
}
defer cleanup()

outDir := bundleOutDir(c.Out)
if err := os.MkdirAll(outDir, 0o755); err != nil {
outDir := bundleOutDir(c.Out) // a *.pmtiles path → its stem dir; any other -o used verbatim
tilesDir := filepath.Join(outDir, "tiles")
if err := os.MkdirAll(tilesDir, 0o755); err != nil {
return err
}

// Bake each cell IN PARALLEL to <outDir>/tiles/<rel>.pmtiles, mirroring the input tree
// (incremental — reruns re-bake only changed cells). The engine writes + frees each archive.
start := time.Now()
if _, err := baker.PrepareLive(input, tilesDir, runtime.NumCPU(), func(done, total int) {
if done >= total {
fmt.Printf("\rbaked %d cell(s), building partition… ", total)
} else if done > 0 {
per := time.Since(start) / time.Duration(done)
fmt.Printf("\rbaking %d/%d · ~%s left ", done, total, (per * time.Duration(total-done)).Round(time.Second))
} else {
fmt.Printf("\rbaking %d/%d… ", done, total)
}
}); err != nil {
fmt.Println()
return err
}
// nil progress → the lib's built-in per-band console progress (good CLI output).
n, bbox, err := bakeTile57Bundle(input, outDir, c.MaxZoom, c.Format, nil)
fmt.Println()

// Collect the mirrored per-cell archives (baked + reused) for the compositor.
var paths []string
_ = filepath.WalkDir(tilesDir, func(p string, d fs.DirEntry, err error) error {
if err == nil && !d.IsDir() && strings.HasSuffix(p, ".pmtiles") {
paths = append(paths, p)
}
return nil
})
sort.Strings(paths)
if len(paths) == 0 {
return fmt.Errorf("no coverage: no valid S-57 cells under %s", input)
}

// Build + save the ownership-partition sidecar next to the tiles (the compositor loads it on
// open to skip the build).
comp, err := tilesource.NewComposer(paths, "")
if err != nil {
return err
}
fmt.Printf("baked %d cell(s) → %s/ via libtile57 — bundle: tiles/chart.pmtiles + assets/style-{day,dusk,night}.json + manifest.json (bbox %.4f,%.4f,%.4f,%.4f)\n",
n, outDir, bbox[0], bbox[1], bbox[2], bbox[3])
defer comp.Close()
if err := comp.SavePartition(filepath.Join(outDir, "partition.tpart")); err != nil {
return err
}
m := comp.Meta()
fmt.Printf("baked %d cell(s) → %s/tiles/*.pmtiles + partition.tpart (z%d..%d, bounds %.4f,%.4f,%.4f,%.4f)\n",
len(paths), outDir, m.MinZoom, m.MaxZoom, m.W, m.S, m.E, m.N)
return nil
}

Expand Down Expand Up @@ -120,21 +148,6 @@ func (c bakeCmd) tile57Input() (path string, cleanup func(), err error) {
return dir, func() { os.RemoveAll(dir) }, nil
}

// writeManifestJSON writes a charts-index.json manifest (indented) to path.
func writeManifestJSON(path string, man map[string]any) error {
mf, err := os.Create(path)
if err != nil {
return err
}
enc := json.NewEncoder(mf)
enc.SetIndent("", " ")
if err := enc.Encode(man); err != nil {
mf.Close()
return err
}
return mf.Close()
}

// bundleOutDir derives the bundle output DIRECTORY from the -o value: a *.pmtiles /
// *.mbtiles path becomes its stem (charts.pmtiles → charts/), otherwise -o is used
// as the directory verbatim.
Expand Down
5 changes: 2 additions & 3 deletions cmd/chartplotter/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,10 @@ func (c serveCmd) Run() error {
// Loopback bind → enforce the Host-header DNS-rebind check on /api. Any
// other bind means the operator opted into network exposure.
allowRemote := !(c.Host == "127.0.0.1" || c.Host == "localhost" || c.Host == "::1")
srv := server.New(c.Assets, cacheDir, dataDir, allowRemote)
srv := server.New(c.Assets, cacheDir, dataDir, allowRemote, engineCommit)
srv.SetAssetFallback(s101AssetDir) // emitted S-101 assets, searched after --assets, before embedded
srv.Version = version
srv.EngineCommit = engineCommit // stamped onto every bake
srv.ReportStaleCache() // loud warning if any served pack predates this binary
srv.ReportStaleCache() // loud warning if any served pack predates this binary

addr := net.JoinHostPort(c.Host, fmt.Sprintf("%d", c.Port))
remoteNote := ""
Expand Down
7 changes: 1 addition & 6 deletions cmd/chartplotter/simulate.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,7 @@ func loadWaterFeatures(p string) ([]tile57.Feature, error) {
if err != nil {
return nil, err
}
src, err := tile57.OpenChartBytes(data)
if err != nil {
return nil, err
}
defer src.Close()
return src.Features("DEPARE", "DRGARE")
return tile57.FeaturesBytes(data, "DEPARE", "DRGARE")
}

// readBaseCell returns the raw base-cell bytes from a .000 file or the first
Expand Down
118 changes: 0 additions & 118 deletions cmd/chartplotter/tile57_bake.go

This file was deleted.

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/beetlebugorg/chartplotter

go 1.26.0

toolchain go1.26.4
toolchain go1.26.5

require (
github.com/BertoldVdb/go-ais v0.4.0
Expand Down
41 changes: 41 additions & 0 deletions internal/engine/baker/compose.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package baker

import (
"io/fs"
"path/filepath"
"strings"

tile57 "github.com/beetlebugorg/tile57/bindings/go"
)

// PrepareLive walks the ENC data under `input` and bakes each S-57 base cell (*.000) IN PARALLEL to
// the SAME relative path under `cellsDir` with a .pmtiles extension (plus a .sha content sidecar),
// creating subdirs — the mirrored tree the runtime compositor mmaps directly (there is NO district
// compose pass; tiles compose on demand). A cell whose archive is already up to date (newer than its
// .000 and its update chain) is skipped, so re-baking a provider only bakes what changed. The engine
// writes and frees each archive as it goes, so this never holds N archives in memory (peak ~
// workers). `workers` bounds concurrency (a MEMORY bound). `onProgress(done, total)` fires per baked
// cell for the import UI (may be called concurrently). Returns the number of cells baked this pass.
func PrepareLive(input, cellsDir string, workers int, onProgress func(done, total int)) (int, error) {
return tile57.BakeTree(input, cellsDir, workers, onProgress)
}

// ListCells returns every base cell (.000) path under `root` (a single file or a directory),
// deduped by stem (a boundary cell shared by two districts appears once).
func ListCells(root string) ([]string, error) {
var out []string
seen := map[string]bool{}
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || !strings.HasSuffix(path, ".000") {
return nil
}
stem := strings.TrimSuffix(filepath.Base(path), ".000")
if seen[stem] {
return nil
}
seen[stem] = true
out = append(out, path)
return nil
})
return out, err
}
Loading
Loading