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
44 changes: 41 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ on:
paths:
- "docs/**"
- ".github/workflows/docs.yml"
# The live demo is built from the app + bake pipeline and deployed under the
# docs site, so redeploy when any of those change too.
- "web/**"
- "cmd/**"
- "internal/**"
- "scripts/fetch-demo-cells.sh"
- "Makefile"
- "go.mod"
- "go.sum"
workflow_dispatch:

permissions:
Expand All @@ -21,20 +30,49 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: docs
steps:
- uses: actions/checkout@v7

- uses: actions/setup-go@v6
with:
go-version: "1.26"
cache: true

# Cache the curated NOAA demo-cell downloads (one per band over Annapolis),
# keyed on the cell list / fetch script so a change re-downloads. Lets the
# bake reuse the same cells across runs.
- name: Cache demo ENC cells
uses: actions/cache@v4
with:
path: ${{ runner.temp }}/demo-cells
key: demo-cells-${{ hashFiles('scripts/fetch-demo-cells.sh', 'Makefile') }}

# Build the read-only widget demo bundle into docs/static/demo so Docusaurus
# copies it into the published site at /chartplotter/demo/. Needs the S-101
# catalogue (IHO material kept out of the repo): clone it and let `make build`
# (a dep of `make demo`) embed it. The bundle is pure static files — no backend.
- name: Build live demo bundle (docs/static/demo)
run: |
git clone --depth 1 https://github.com/iho-ohi/S-101_Portrayal-Catalogue.git "$RUNNER_TEMP/s101-pc"
git clone --depth 1 https://github.com/iho-ohi/S-101-Documentation-and-FC.git "$RUNNER_TEMP/s101-fc"
make demo \
S101_PC="$RUNNER_TEMP/s101-pc/PortrayalCatalog" \
S101_FC="$RUNNER_TEMP/s101-fc/S-101FC/FeatureCatalogue.xml" \
DEMO_CACHE="$RUNNER_TEMP/demo-cells" \
DEMO_OUT="docs/static/demo"

- uses: actions/setup-node@v6
with:
node-version: "20"
cache: npm
cache-dependency-path: docs/package-lock.json
- name: Install
working-directory: docs
run: npm ci
- name: Build
working-directory: docs
run: npm run build

- uses: actions/upload-pages-artifact@v3
with:
path: docs/build
Expand Down
50 changes: 42 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ S101_PC ?= $(HOME)/Projects/s101-portrayal-catalogue/PortrayalCatalog
S101_FC ?= $(HOME)/Projects/s101-feature-catalogue/S-101FC/FeatureCatalogue.xml
S101_CACHE ?= $(CACHE)/s101

.PHONY: build xbuild test vet fmt fmt-check tidy clean clear-cache serve docs docs-shots bake-ienc bake-noaa serve-prod
.PHONY: build xbuild test vet fmt fmt-check tidy clean clear-cache serve docs docs-shots bake-ienc bake-noaa serve-widget demo serve-demo

# Prebaked prod test set (US Inland ENC bundle + the NOAA world archive).
# NB: keep these as bare values with NO inline `#` comments — Make folds any
Expand Down Expand Up @@ -146,10 +146,11 @@ noaa-d%.stamp: $(NOAA_CACHE)/%CGD_ENCs.zip | $(BIN)
@touch "$@"

# Serve the per-district NOAA archives + the baked IENC archive TOGETHER,
# prebaked, in production mode on 0.0.0.0:8080. Every .pmtiles lives at the project
# root; they're symlinked into web/ (the served asset dir) and listed in a combined
# charts-index.json manifest the prod app loads via ?catalog=. Open the printed URL.
serve-prod: build bake-noaa ## Serve per-district per-band NOAA + IENC prebaked pmtiles together, prod mode, on 0.0.0.0:8080
# prebaked, in read-only widget mode on 0.0.0.0:8080. Every .pmtiles lives at the
# project root; they're symlinked into web/ (the served asset dir) and listed in a
# combined charts-index.json manifest the widget app loads via ?catalog=. Open the
# printed URL.
serve-widget: build bake-noaa ## Serve per-district per-band NOAA + IENC prebaked pmtiles together, read-only widget mode, on 0.0.0.0:8080
@ln -sf "$(abspath $(IENC_PMTILES))" web/ienc.pmtiles
@for d in $(DISTRICTS); do for s in $(NOAA_BANDS); do \
f="noaa-d$$d-$$s.pmtiles"; [ -f "$$f" ] && ln -sf "$(abspath .)/$$f" "web/$$f" || true; \
Expand All @@ -162,12 +163,45 @@ serve-prod: build bake-noaa ## Serve per-district per-band NOAA + IENC prebaked
printf ' { "file": "ienc.pmtiles", "band": "all" }\n ]\n}\n'; \
} > web/charts-index.json
@echo
@echo " Prebaked prod test server — open:"
@echo " http://localhost:8080/?prod&catalog=/charts-index.json"
@echo " (binds 0.0.0.0 — reachable from the LAN at http://<this-host>:8080/?prod&catalog=/charts-index.json)"
@echo " Prebaked widget test server — open:"
@echo " http://localhost:8080/?widget&catalog=/charts-index.json"
@echo " (binds 0.0.0.0 — reachable from the LAN at http://<this-host>:8080/?widget&catalog=/charts-index.json)"
@echo
$(BIN) serve --host 0.0.0.0 --port 8080 --assets web

# ---- read-only demo bundle (the `widget` mode, packaged for static hosting) ----
# A self-contained, no-backend chart viewer over ONE location (Annapolis) with all
# the bands NOAA publishes there, so a visitor can zoom from the whole bay down to
# the docks on a few MB of tiles. `make demo` assembles dist/demo/ (override with
# DEMO_OUT, e.g. DEMO_OUT=docs/static/demo in CI): the per-band .pmtiles + manifest,
# the generated S-101 client assets, and the committed static frontend (demo.html
# as index.html). Serve it from ANY static host / CDN — no server logic required.
DEMO_CELLS ?= US2EC03M US3EC08M US4MD1DC US5MD1MC
DEMO_CACHE ?= $(CACHE)/demo
DEMO_OUT ?= dist/demo
DEMO_MAXZOOM ?= 16

demo: build ## Assemble the read-only Annapolis widget demo bundle into $(DEMO_OUT)
DEMO_CACHE="$(DEMO_CACHE)" DEMO_CELLS="$(DEMO_CELLS)" NOAA_URL_BASE="$(NOAA_URL_BASE)" scripts/fetch-demo-cells.sh
@mkdir -p "$(DEMO_OUT)"
$(BIN) bake "$(DEMO_CACHE)" -o "$(DEMO_OUT)/demo.pmtiles" --bands --max-zoom $(DEMO_MAXZOOM) --manifest "$(DEMO_OUT)/charts-index.json"
$(BIN) emit-assets "$(DEMO_OUT)" $(if $(wildcard $(S101_PC)),--s101 "$(S101_PC)")
@echo "assembling static frontend → $(DEMO_OUT)"
@cp web/demo.html "$(DEMO_OUT)/index.html"
@cp web/manifest.webmanifest web/catalog.json web/icon-192.png web/icon-512.png web/apple-touch-icon.png "$(DEMO_OUT)/"
@cp -R web/src web/vendor web/glyphs web/basemap "$(DEMO_OUT)/"
@echo " demo bundle ready: $(DEMO_OUT)/ — host it on any static server / CDN"

# LOCAL PREVIEW ONLY. The bundle is pure static files — deploy it to ANY
# range-capable static host (GitHub Pages, S3/CloudFront, nginx, `npx serve`); it
# needs no backend. PMTiles are read with HTTP Range, which python's http.server
# does NOT support, so we preview with the chartplotter binary acting purely as a
# range-capable static file server (the widget page makes no /api calls).
serve-demo: demo ## Preview the static demo bundle locally (range-capable static serve; HOST/PORT overridable)
@echo " Read-only widget demo — open: http://$(HOST):$(PORT)/"
$(BIN) serve --host $(HOST) --port $(PORT) --assets "$(DEMO_OUT)" \
$(if $(wildcard $(S101_PC)),--s101 "$(S101_PC)" --s101-fc "$(S101_FC)" --cache "$(S101_CACHE)")

docs: ## Run the documentation site dev server (Docusaurus; DOCS_HOST/DOCS_PORT overridable)
cd docs && { [ -d node_modules ] || npm install; } && npm start -- --host $(DOCS_HOST) --port $(DOCS_PORT)

Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@
</p>

<p align="center">
▶ <b><a href="https://beetlebugorg.github.io/chartplotter/demo/">Try the live demo</a></b>
&nbsp;·&nbsp;
📚 <b><a href="https://beetlebugorg.github.io/chartplotter/">Read the docs →</a></b>
</p>

<p align="center">
<img src="docs/static/img/ui/cape-lookout.png" alt="chartplotter rendering the North Carolina coast off Cape Lookout" width="800">
<br><sub>The North Carolina coast off Cape Lookout — NOAA ENC data, S-101 portrayal, ~1:700,000.</sub>
<a href="https://beetlebugorg.github.io/chartplotter/demo/" title="Open the live, interactive chart viewer">
<img src="docs/static/img/ui/annapolis.png" alt="chartplotter rendering NOAA charts of Annapolis — click to open the live demo" width="800">
</a>
<br><sub>▶ <b><a href="https://beetlebugorg.github.io/chartplotter/demo/">Open the live, interactive demo</a></b> — official NOAA charts of Annapolis, rendered in your browser. No install, no server.</sub>
</p>

---
Expand Down
9 changes: 8 additions & 1 deletion cmd/chartplotter/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func (c serveCmd) Run() error {
// on its own (baker.applyPortrayer); here we emit the matching client assets
// (colortables/sprite/patterns/linestyles) into a temp dir and serve them.
var catalogFS fs.FS
var s101AssetDir string // freshly-emitted S-101 client assets (temp dir), or ""
switch {
case c.S101 != "":
if c.S101FC == "" {
Expand Down Expand Up @@ -63,7 +64,12 @@ func (c serveCmd) Run() error {
if _, err := assets.EmitS101FS(catalogFS, "daySvgStyle.css", assetDir); err != nil {
return fmt.Errorf("emit S-101 assets: %w", err)
}
c.Assets = assetDir // override colortables/linestyles/sprite; rest falls back to embedded
// The emitted S-101 client assets (colortables/linestyles/sprite/patterns)
// are a FALLBACK, not a replacement: an explicit --assets dir stays primary
// (so a prebaked widget bundle serves its own index.html / charts-index.json /
// .pmtiles), this temp dir fills in the generated S-101 files it lacks, and the
// embedded bundle backs the rest. Registered on the Server below.
s101AssetDir = assetDir
}

cacheDir := c.Cache
Expand All @@ -87,6 +93,7 @@ func (c serveCmd) Run() error {
// 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.SetAssetFallback(s101AssetDir) // emitted S-101 assets, searched after --assets, before embedded
srv.Version = version
srv.ReportStaleCache() // loud warning if any served pack predates this binary

Expand Down
4 changes: 4 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
.docusaurus
.cache-loader

# Live demo bundle — generated by `make demo DEMO_OUT=docs/static/demo` in CI
# (the read-only widget app + baked Annapolis .pmtiles), never committed.
/static/demo/

# Misc
.DS_Store
.env.local
Expand Down
2 changes: 2 additions & 0 deletions docs/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ restrictions, light characteristics, depths, source dates, and any attached text

## Where to go next

- Put a chart on your own page — see [Widget mode](./widget.md) for the read-only,
no-server build and how to package and deploy it.
- Learn how a cell becomes tiles in [Architecture](./architecture.md).
- See the exact tile layers and fields in the [Tile Schema](./tile-schema.md).
- Look up the commands in the [CLI Reference](./cli.md) if you want to bake
Expand Down
9 changes: 8 additions & 1 deletion docs/docs/intro.md → docs/docs/intro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ slug: /
sidebar_position: 1
---

import LiveChart from '@site/src/components/LiveChart';

# chartplotter

:::warning Not for navigation
Expand All @@ -19,7 +21,12 @@ real-world navigation.**
**chartplotter** is a marine chart engine written in Go. It turns official NOAA
nautical charts into fast, offline map tiles that you can view in a web browser.

![A chartplotter chart of Chesapeake Bay](/img/ui/chart-day.png)
The chart below is **live** — official NOAA charts of Annapolis, Maryland, running
right here in your browser. Pan, zoom from the whole Chesapeake to the docks, and
switch Day / Dusk / Night. It is the read-only [**widget**](./widget.md) build:
static, embeddable, no server.

<LiveChart />

It reads **S-57** electronic navigational chart (ENC) cells, draws them with the
**S-101 Portrayal Catalogue** — the modern IHO standard for how charts look — and
Expand Down
108 changes: 108 additions & 0 deletions docs/docs/widget.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
id: widget
title: Widget
sidebar_position: 4
---

# Widget mode

**Widget mode** is a static, embeddable, read-only build of `<chart-plotter>` — the
same viewer you see [on the home page](./intro.mdx). It has **no backend and does no
in-browser baking**: the chart is a handful of prebaked
[PMTiles](./architecture.md) archives streamed straight from a static host over HTTP
range requests. It is how you put a chartplotter chart on a web page or ship a
self-contained demo.

Widget mode strips the management chrome down to a pure viewer:

- **No chart library, import, or downloads** — the charts are fixed.
- **No share link, no connections (NMEA/AIS), no Advanced / developer tools.**
- **Only the client-side display settings** — General, Text, Units, and Depths —
applied live and stored locally in the browser. Nothing is sent anywhere.

:::warning Not for navigation
A widget renders a fixed set of cached cells. **Do not use it for real-world
navigation.** See [Known limitations](./limitations.md).
:::

## Enable it

Widget mode is turned on by the `widget` attribute (or the `?widget` query param).
Point the viewer at a prebaked archive — either a per-band set discovered from a
`charts-index.json` next to the page, or a single archive via `pmtiles="…"`:

```html
<script type="module" src="/demo/src/chartplotter.mjs"></script>
<chart-plotter widget assets="/demo/" center="-76.482,38.978" zoom="13"></chart-plotter>
```

The `assets` attribute is the key: the widget resolves **everything** relative to it —
`vendor/maplibre-gl.js`, the S-101 client assets, `glyphs/`, `basemap/`,
`catalog.json`, and `charts-index.json`. Put the whole bundle in one directory and
point `assets` at it. (That is exactly how the [home page](./intro.mdx) embeds the
live chart, via the `<LiveChart/>` React component in `docs/src/components/`.)

## Package it

One make target assembles a complete bundle — it downloads the curated cells, bakes
the per-band archives, emits the S-101 client assets, and copies in the static
frontend:

```sh
make demo # → dist/demo/
make demo DEMO_OUT=path/to/out # → a directory of your choosing
make serve-demo # build it, then preview at http://127.0.0.1:8080
```

The result is a self-contained static site. `dist/demo/` contains:

| What | Files |
| --- | --- |
| Per-band tiles + manifest | `demo-<band>.pmtiles`, `charts-index.json` |
| External text/picture files | `demo-aux.zip` |
| S-101 client assets | `colortables.json`, `linestyles.json`, `sprite.{json,png}`, `patterns.{json,png}` |
| Frontend | `index.html`, `src/`, `vendor/`, `glyphs/`, `basemap/`, `catalog.json` |

To bake a different location, override the cells (and re-centre the page):

```sh
make demo DEMO_CELLS="US2EC03M US3EC08M US4MD1DC US5MD1MC"
```

You can also assemble a bundle by hand from the CLI:

```sh
chartplotter bake CELLS… -o out/demo.pmtiles --bands --manifest out/charts-index.json
chartplotter emit-assets out/ # the S-101 client assets
# then copy web/{index.html,src,vendor,glyphs,basemap,catalog.json} into out/
```

## Deploy it

The bundle is **pure static files** — copy the directory to any static host. The only
hard requirement is **HTTP range request support**, because the PMTiles archives are
read with byte ranges. Every real static host has it:

- **GitHub Pages** — what this project uses; CI builds the bundle into
`docs/static/demo/` so it deploys at `/<baseUrl>/demo/`.
- **Amazon S3 + CloudFront** — serve `.pmtiles` as `application/octet-stream` and
allow long-lived immutable caching (see `web/customHttp.yml` for the header set).
- **nginx / Caddy / Netlify / Cloudflare Pages** — work out of the box.

What does **not** work: `python3 -m http.server` — it ignores `Range` and returns the
whole archive for every tile fetch. For a quick local preview without deploying,
`make serve-demo` runs the `chartplotter` binary purely as a range-capable static file
server (the widget page itself makes no API calls).

A few small things hosts occasionally need:

- Serve `.pmtiles` with `Content-Type: application/octet-stream` and
`Accept-Ranges: bytes`.
- Keep relative paths intact — the bundle uses only relative URLs, so it works under
any sub-path (e.g. `/chartplotter/demo/`) without configuration.
- **Cross-origin embedding:** if the page and the bundle are on *different* origins
(e.g. your blog embeds a chart hosted on GitHub Pages), the bundle's host must send
`Access-Control-Allow-Origin` — the assets are fetched with `fetch()` and the sprite
image with `crossOrigin="anonymous"`. GitHub Pages, S3/CloudFront, and the
`chartplotter` server already send `*`. A **same-origin** embed — like this docs
site, where the page and the bundle share a host — needs nothing.
5 changes: 5 additions & 0 deletions docs/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ const config = {
navbar: {
title: 'chartplotter',
items: [
{
href: 'https://beetlebugorg.github.io/chartplotter/demo/',
label: 'Live demo',
position: 'right',
},
{
href: 'https://github.com/beetlebugorg/chartplotter',
label: 'GitHub',
Expand Down
1 change: 1 addition & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const sidebars = {
'intro',
'installation',
'getting-started',
'widget',
'cli',
'architecture',
'tile-schema',
Expand Down
Loading