TypeScript parser for S-57 and S-101 marine nautical charts with S-52 symbology rendering
See the codeLive Demo — parse a real NOAA chart in the browser. No server, no GDAL.
Pure TypeScript parser for S-57 and S-101 marine navigational charts with S-52 symbology rendering in the browser.
Zero runtime dependencies. Works in Node.js, Bun, and browsers. 127 tests.
| Package | Description |
|---|---|
@s57-parser/iso8211 | ISO 8211 binary format parser |
@s57-parser/s57 | S-57 ENC data model, topology, GeoJSON conversion |
@s57-parser/s101 | S-101 ENC parser (S-100 framework), auto-detection |
@s57-parser/s52-render | S-52 Canvas2D renderer (IHO symbology, 3 palettes) |
@s57-parser/leaflet | Leaflet plugin with S-52 canvas overlay |
@s57-parser/maplibre | MapLibre GL JS plugin (GeoJSON source + canvas layer) |
@s57-parser/cli | CLI tool: parse and inspect S-57/S-101 files |
npm install @s57-parser/s57 @s57-parser/s52-render
import { parseS57, toGeoJSON } from '@s57-parser/s57';
const response = await fetch('/chart/US5MA19M.000');
const buffer = await response.arrayBuffer();
const dataset = parseS57(buffer);
const geojson = toGeoJSON(dataset);
console.log(`${dataset.features.length} features`);
console.log(`${dataset.spatialRecords.size} spatial records`);
import { parseS57, toGeoJSON } from '@s57-parser/s57';
import { renderChart } from '@s57-parser/s52-render';
const dataset = parseS57(buffer);
const geojson = toGeoJSON(dataset);
// Attach attributes for conditional symbology (depth-based coloring, etc.)
for (const f of geojson.features) {
const feat = dataset.features.find(d => d.rcid === f.properties.RCID);
if (feat) f.properties._attributes = feat.attributes;
}
const canvas = document.getElementById('chart') as HTMLCanvasElement;
const ctx = canvas.getContext('2d')!;
renderChart(ctx, geojson, {
toPixelX: lon => /* your lon-to-pixel transform */,
toPixelY: lat => /* your lat-to-pixel transform */,
}, canvas.width, canvas.height, { mode: 'DAY_BRIGHT' });
import { parseS101, isS101, toGeoJSON } from '@s57-parser/s101';
// Auto-detect format
if (isS101(buffer)) {
const dataset = parseS101(buffer);
const geojson = toGeoJSON(dataset);
// GeoJSON properties include OBJL mapped to S-57 codes
// for compatibility with the S-52 renderer
}
import L from 'leaflet';
import { S57Layer } from '@s57-parser/leaflet';
const map = L.map('map').setView([42.35, -70.88], 12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
const response = await fetch('/chart/US5MA19M.000');
const buffer = await response.arrayBuffer();
const layer = new S57Layer(buffer, { mode: 'DAY_BRIGHT' });
layer.addTo(map);
// Cycle display modes
layer.setMode('DUSK');
layer.setMode('NIGHT');
import maplibregl from 'maplibre-gl';
import { addChartSource } from '@s57-parser/maplibre';
const map = new maplibregl.Map({ container: 'map', style: '...' });
const response = await fetch('/chart/US5MA19M.000');
const buffer = await response.arrayBuffer();
// Option 1: Native vector rendering with S-52-inspired styles
addChartSource(map, buffer, { sourceId: 'enc' });
// Option 2: Full S-52 canvas overlay
import { S57CanvasLayer } from '@s57-parser/maplibre';
const layer = new S57CanvasLayer('enc-overlay', buffer);
map.addLayer(layer);
import { parseS57, typedFeatures, filterByClass } from '@s57-parser/s57';
import type { Light, DepthArea } from '@s57-parser/s57';
const dataset = parseS57(buffer);
const typed = typedFeatures(dataset.features);
// Filter by object class with full type narrowing
const lights = filterByClass(typed, 'LIGHTS');
for (const light of lights) {
// light is Light — litchr, sigper, sectr1, sectr2 are typed
console.log(`${light.name}: ${light.litchr} period=${light.sigper}s`);
}
const depths = filterByClass(typed, 'DEPARE');
for (const area of depths) {
// area is DepthArea — drval1, drval2 are typed numbers
console.log(`Depth: ${area.drval1}–${area.drval2}m`);
}
15 object classes supported: DEPARE, DEPCNT, SOUNDG, COALNE, LNDARE,
LIGHTS, BCNCAR, BCNLAT, BOYCAR, BOYLAT, BOYSAW, BOYSPP,
OBSTRN, WRECKS, UWTROC, RESARE, BRIDGE, LNDMRK, ACHARE.
import { parseS57 } from '@s57-parser/s57';
import { applyUpdate } from '@s57-parser/s57';
const base = parseS57(baseBuffer); // .000 file
applyUpdate(base, update001Buffer); // .001 file (mutates dataset)
applyUpdate(base, update002Buffer); // .002 file
ISO 8211 Parser
b15 (suffix) and B(40) (parenthesized) binary field notationsS-57 Parser
DepthArea, Light, Buoy, etc.)S-101 Parser
S-52 Renderer
| Mode | Use case |
|---|---|
DAY_BRIGHT | Full daylight, highest contrast |
DUSK | Twilight, reduced brightness |
NIGHT | Night vision, dark red-tinted |
@s57-parser/iso8211 Pure ISO 8211 binary parser
|
+---------+
| |
@s57-parser/s57 @s57-parser/s101
| |
+---------+
|
@s57-parser/s52-render Canvas2D S-52 renderer
|
+---------+
| |
@s57-parser/leaflet @s57-parser/maplibre
Download free NOAA ENC charts from charts.noaa.gov (S-57 .000 files, ~782 MB total).
Place files in the test-data/ directory for integration tests.
# Install dependencies
bun install
# Run all tests (127 tests across 8 packages)
bun test
# Build all packages
bun run build
# Run demo viewer
bun run demo/serve.ts
Contributions welcome. Areas that need help:
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
24 commits
TypeScript
100.0%
TypeScript parser for S-57 and S-101 marine nautical charts with S-52 symbology rendering
See the codeLive Demo — parse a real NOAA chart in the browser. No server, no GDAL.
Pure TypeScript parser for S-57 and S-101 marine navigational charts with S-52 symbology rendering in the browser.
Zero runtime dependencies. Works in Node.js, Bun, and browsers. 127 tests.
| Package | Description |
|---|---|
@s57-parser/iso8211 | ISO 8211 binary format parser |
@s57-parser/s57 | S-57 ENC data model, topology, GeoJSON conversion |
@s57-parser/s101 | S-101 ENC parser (S-100 framework), auto-detection |
@s57-parser/s52-render | S-52 Canvas2D renderer (IHO symbology, 3 palettes) |
@s57-parser/leaflet | Leaflet plugin with S-52 canvas overlay |
@s57-parser/maplibre | MapLibre GL JS plugin (GeoJSON source + canvas layer) |
@s57-parser/cli | CLI tool: parse and inspect S-57/S-101 files |
npm install @s57-parser/s57 @s57-parser/s52-render
import { parseS57, toGeoJSON } from '@s57-parser/s57';
const response = await fetch('/chart/US5MA19M.000');
const buffer = await response.arrayBuffer();
const dataset = parseS57(buffer);
const geojson = toGeoJSON(dataset);
console.log(`${dataset.features.length} features`);
console.log(`${dataset.spatialRecords.size} spatial records`);
import { parseS57, toGeoJSON } from '@s57-parser/s57';
import { renderChart } from '@s57-parser/s52-render';
const dataset = parseS57(buffer);
const geojson = toGeoJSON(dataset);
// Attach attributes for conditional symbology (depth-based coloring, etc.)
for (const f of geojson.features) {
const feat = dataset.features.find(d => d.rcid === f.properties.RCID);
if (feat) f.properties._attributes = feat.attributes;
}
const canvas = document.getElementById('chart') as HTMLCanvasElement;
const ctx = canvas.getContext('2d')!;
renderChart(ctx, geojson, {
toPixelX: lon => /* your lon-to-pixel transform */,
toPixelY: lat => /* your lat-to-pixel transform */,
}, canvas.width, canvas.height, { mode: 'DAY_BRIGHT' });
import { parseS101, isS101, toGeoJSON } from '@s57-parser/s101';
// Auto-detect format
if (isS101(buffer)) {
const dataset = parseS101(buffer);
const geojson = toGeoJSON(dataset);
// GeoJSON properties include OBJL mapped to S-57 codes
// for compatibility with the S-52 renderer
}
import L from 'leaflet';
import { S57Layer } from '@s57-parser/leaflet';
const map = L.map('map').setView([42.35, -70.88], 12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
const response = await fetch('/chart/US5MA19M.000');
const buffer = await response.arrayBuffer();
const layer = new S57Layer(buffer, { mode: 'DAY_BRIGHT' });
layer.addTo(map);
// Cycle display modes
layer.setMode('DUSK');
layer.setMode('NIGHT');
import maplibregl from 'maplibre-gl';
import { addChartSource } from '@s57-parser/maplibre';
const map = new maplibregl.Map({ container: 'map', style: '...' });
const response = await fetch('/chart/US5MA19M.000');
const buffer = await response.arrayBuffer();
// Option 1: Native vector rendering with S-52-inspired styles
addChartSource(map, buffer, { sourceId: 'enc' });
// Option 2: Full S-52 canvas overlay
import { S57CanvasLayer } from '@s57-parser/maplibre';
const layer = new S57CanvasLayer('enc-overlay', buffer);
map.addLayer(layer);
import { parseS57, typedFeatures, filterByClass } from '@s57-parser/s57';
import type { Light, DepthArea } from '@s57-parser/s57';
const dataset = parseS57(buffer);
const typed = typedFeatures(dataset.features);
// Filter by object class with full type narrowing
const lights = filterByClass(typed, 'LIGHTS');
for (const light of lights) {
// light is Light — litchr, sigper, sectr1, sectr2 are typed
console.log(`${light.name}: ${light.litchr} period=${light.sigper}s`);
}
const depths = filterByClass(typed, 'DEPARE');
for (const area of depths) {
// area is DepthArea — drval1, drval2 are typed numbers
console.log(`Depth: ${area.drval1}–${area.drval2}m`);
}
15 object classes supported: DEPARE, DEPCNT, SOUNDG, COALNE, LNDARE,
LIGHTS, BCNCAR, BCNLAT, BOYCAR, BOYLAT, BOYSAW, BOYSPP,
OBSTRN, WRECKS, UWTROC, RESARE, BRIDGE, LNDMRK, ACHARE.
import { parseS57 } from '@s57-parser/s57';
import { applyUpdate } from '@s57-parser/s57';
const base = parseS57(baseBuffer); // .000 file
applyUpdate(base, update001Buffer); // .001 file (mutates dataset)
applyUpdate(base, update002Buffer); // .002 file
ISO 8211 Parser
b15 (suffix) and B(40) (parenthesized) binary field notationsS-57 Parser
DepthArea, Light, Buoy, etc.)S-101 Parser
S-52 Renderer
| Mode | Use case |
|---|---|
DAY_BRIGHT | Full daylight, highest contrast |
DUSK | Twilight, reduced brightness |
NIGHT | Night vision, dark red-tinted |
@s57-parser/iso8211 Pure ISO 8211 binary parser
|
+---------+
| |
@s57-parser/s57 @s57-parser/s101
| |
+---------+
|
@s57-parser/s52-render Canvas2D S-52 renderer
|
+---------+
| |
@s57-parser/leaflet @s57-parser/maplibre
Download free NOAA ENC charts from charts.noaa.gov (S-57 .000 files, ~782 MB total).
Place files in the test-data/ directory for integration tests.
# Install dependencies
bun install
# Run all tests (127 tests across 8 packages)
bun test
# Build all packages
bun run build
# Run demo viewer
bun run demo/serve.ts
Contributions welcome. Areas that need help:
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
24 commits
TypeScript
100.0%