Live Demo — parse a real NOAA chart in the browser. No server, no GDAL.
Pure TypeScript parser for S-57 marine navigational charts (ENC), with experimental S-101 support and S-52-style rendering in the browser.
No runtime dependencies outside this repository. ESM packages for Node.js 18+, Bun and browsers.
- Boating and chartplotter apps: show official ENC charts in a web or hybrid app without a tile pipeline, GDAL or a commercial SDK.
- Open marine platforms (Signal K, OpenCPN tooling, OpenSeaMap): read S-57 cells directly in the browser or in Node.js.
- AIS, port and logistics dashboards: a nautical base layer under vessel tracks, built from free NOAA charts.
- Maritime education: browser-based ENC reading exercises for cadets, no simulator licence needed.
- GIS and hydrography work: S-57 to GeoJSON, ISO 8211 inspection, an early start on S-101.
Not for primary navigation: see Limitations. Need integration help or a feature? Open an issue or write to vlad@studyqa.com.
| Package | Description |
|---|---|
@s57-parser/iso8211 |
ISO 8211 binary format parser |
@s57-parser/s57 |
S-57 data model, topology, updates, typed features, GeoJSON |
@s57-parser/s101 |
S-101 parser (experimental), format detection |
@s57-parser/s52-render |
Canvas2D renderer with S-52 palettes and symbology |
@s57-parser/leaflet |
Leaflet layer |
@s57-parser/maplibre |
MapLibre GL JS source and custom layer |
@s57-parser/cli |
s57 command: info, GeoJSON export, ISO 8211 dump |
Each package has its own README with the full API.
npm install @s57-parser/s57import { readFileSync, writeFileSync } from 'node:fs';
import { parseS57, applyUpdate, toGeoJSON } from '@s57-parser/s57';
// Node buffers may be views into a shared pool: copy to a standalone ArrayBuffer.
const read = (path: string) => {
const b = readFileSync(path);
return b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength) as ArrayBuffer;
};
const dataset = parseS57(read('ENC_ROOT/US5MA19M/US5MA19M.000'));
applyUpdate(dataset, read('ENC_ROOT/US5MA19M/US5MA19M.001'));
console.log(dataset.name, dataset.cscl, dataset.features.length);
writeFileSync('chart.geojson', JSON.stringify(toGeoJSON(dataset)));Or with the CLI: npx @s57-parser/cli geojson US5MA19M.000 US5MA19M.001 -o chart.geojson.
import { parseS57, toGeoJSON } from '@s57-parser/s57';
import { renderChart } from '@s57-parser/s52-render';
const buffer = await (await fetch('/charts/US5MA12M.000')).arrayBuffer();
const dataset = parseS57(buffer);
const geojson = toGeoJSON(dataset);
// Conditional symbology (depth colours, light colours) reads raw attributes.
const attrs = new Map(dataset.features.map(f => [f.rcid, f.attributes]));
for (const f of geojson.features) f.properties._attributes = attrs.get(f.properties.RCID as number);
const canvas = document.querySelector('canvas')!;
const [west, south, east, north] = [-71.08, 42.21, -70.73, 42.34];
renderChart(canvas.getContext('2d')!, geojson, {
toPixelX: lon => ((lon - west) / (east - west)) * canvas.width,
toPixelY: lat => ((north - lat) / (north - south)) * canvas.height,
}, canvas.width, canvas.height, { mode: 'DAY_BRIGHT' });import { typedFeatures, filterByClass } from '@s57-parser/s57';
const typed = typedFeatures(dataset.features);
for (const light of filterByClass(typed, 'LIGHTS')) {
console.log(light.name, light.litchr, light.sigper, light.colour);
}import { S57Layer } from '@s57-parser/leaflet';
new S57Layer(buffer, { mode: 'DUSK' }).addTo(leafletMap);
import { addChartSource, S57CanvasLayer } from '@s57-parser/maplibre';
addChartSource(maplibreMap, buffer, { sourceId: 'enc' }); // vector layers
maplibreMap.addLayer(new S57CanvasLayer('enc-overlay', buffer)); // S-52 overlayISO 8211: DDR-driven decoding of A, I, R, b1n/b2n and B(n) subfields,
repeating groups, binary data containing terminator bytes; rejects malformed input.
S-57: DSID/DSPM metadata (COMF, SOMF, compilation scale), feature and vector records, chain-node topology, polygons with holes, data-limit outlines, incremental updates (RUIN, FSPC, VRPC, SGCC), 15 typed feature interfaces, the IHO attribute catalogue.
S-101 (experimental): feature catalogue, complex attributes, information records, associations, S-57 code mapping. Tested on synthetic data only, see the package README.
S-52 rendering: DAY_BRIGHT / DUSK / NIGHT palettes, rules for 36 object classes (a default style for the rest), conditional symbology for depth areas and lights, sector lights, sounding and light labels, pattern fills, label decluttering, viewport culling.
- Not for navigation. The renderer is a simplified S-52 presentation, not a type-approved ECDIS presentation library.
- S-63 encrypted cells are not supported.
- National text in UCS-2 (S-57 lexical level 2) is decoded as bytes, not converted.
- ESM only: use
import, orrequire()on Node.js 22.12+.
bun install
bun test # all packages
bun run test:coverage # with coverage thresholds (bunfig.toml)
bun run build # tsc -b: type-check and build every package
bun run pack:check # pack, install into a fresh project, import, run CLI, check typesTests run on the NOAA cell committed at demo/charts/US5MA12M.000. Update
files and S-101 datasets are generated in tests with a small ISO 8211 writer
(test-utils/). Tests for NOAA US5MA19M and its .001 update run when the
cell is in test-data/ (CI downloads it):
mkdir -p test-data/US5MA19M
curl -L https://charts.noaa.gov/ENCs/US5MA19M.zip -o /tmp/US5MA19M.zip
unzip -o /tmp/US5MA19M.zip -d test-data/US5MA19MDemo viewer:
bun demo/build.ts && bun run demo/serve.ts # http://localhost:3457bun demo/build.ts also writes demo/dist/s57-viewer.html, the whole viewer in one
self-contained HTML file that opens from disk (file://) and works offline.
Drop a .000 cell or a NOAA .zip exchange set onto it. A prebuilt copy:
https://devladpopov.github.io/s57-parser/s57-viewer.html
- Update versions in
packages/*/package.jsonand move theUnreleasedsection of CHANGELOG.md under the new version. bun run build && bun run pack:check- Push a
v*tag. release.yml publishes every package whose version is not on npm yet (npm Trusted Publishing, no token).
Contributions welcome. Areas that need help:
- WebGL renderer for large charts (millions of coordinates)
- S-63 encryption/decryption support
- Additional S-101 test data validation
- Performance optimization for mobile browsers
Built by Vladislav Popov — vladislavpopov.ru · GitHub @devladpopov
Questions, integration help, or commercial/consulting inquiries: open an issue or reach me at vlad@alumni.york.ac.uk.
MIT © Vladislav Popov