npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@openwaters/seamap

v0.1.0

Published

MapLibre GL style for Open Waters Seamap – base map, bathymetry, chart symbology

Downloads

79

Readme

@openwaters/seamap

The Open Waters nautical chart as a MapLibre GL style: a VersaTiles base map, Seascape bathymetry, and chart symbology (buoys, beacons, lights, topmarks, landmarks, restricted areas) with the sprite sheet that draws it.

Whole style

style() assembles everything; setup() registers the runtime images the style depends on (the generic-icon fallback and the "unsurveyed water" stipple):

import { style, setup, attribution } from "@openwaters/seamap";

const map = new maplibregl.Map({
  container: "map",
  style: await style({ spriteBase: new URL("sprites", document.baseURI).href }),
  attributionControl: { customAttribution: attribution },
});
setup(map);

style() is async: land hillshading comes from the VersaTiles elevation tiles, and the builder fetches their TileJSON. Options: tiles (seamark TileJSON URL), seascape, versatiles, language, spriteBase, hillshade (on by default; false to skip, or an object to tune the shading), and the seascape passthroughs — flavor (overrides merged over its day), unit, safety, shading, and the dem/vector/coverage source id overrides.

Composed

sources() + layers() hand over just the chart symbology for a style you assemble yourself, following the same split as seascape:

import { sources, layers, sprite, handleMissingImages, attribution } from "@openwaters/seamap";

const { areas, symbols } = layers();
const style = {
  version: 8,
  sources: { ...mySources, ...sources() },
  sprite: [mySprite, sprite(new URL("sprites", document.baseURI).href)],
  // areas go below land fills and labels; symbols on top of everything
  layers: [...myBaseLayers, ...areas, ...myLandLayers, ...symbols],
};

const map = new maplibregl.Map({ style /* ... */ });
handleMissingImages(map);
  • sources({ url? }) — the seamap vector source (defaults to https://tiles.openwaters.io/seamap/tiles.json).
  • layers({ font? }) — the chart layers, split into areas and symbols to preserve draw order around your land layers. font renames glyph fontstacks ("Noto Sans Regular") to match your glyph server.
  • sprite(base) — the style.sprite entry pointing at wherever you serve the sheet.
  • handleMissingImages(map) — falls back to each shape's generic icon when tags compose a colour combination the sheet doesn't carry. Without it, unusual marks render as nothing.
  • attribution — the sprite artwork credit; sprites aren't a MapLibre source, so pass it as customAttribution.

Sprites

The built sheet ships in the package at sprites/dist/ (freenauticalchart.{json,png}, @2x variants, LICENSE, PROVENANCE.md). MapLibre loads sprites from a URL prefix — it appends .json/.png/@2x itself — so the files must be served together under a stable path rather than imported through a bundler's asset pipeline. With Vite:

import { viteStaticCopy } from "vite-plugin-static-copy";

viteStaticCopy({
  targets: [{ src: "node_modules/@openwaters/seamap/sprites/dist/*", dest: "sprites" }],
});

Icon names are composed from tag values at render time, so the style layers and the sprite sheet must always move together — always serve the sheet from this same package version.

In this repo the sheet is generated, not committed: bin/sprites (here in style/) expands the vendored SVG sources in sprites/ into sprites/dist/. Needs spreet (pinned in the repo's mise.toml) and Python 3. npm publish runs it via prepublishOnly; consumers of the published package never need the toolchain.

The same run composes the poi-* badges via bin/poi-badges, pulling glyphs from the CC0 Maki and Temaki sets (the @iconify-json/* dev dependencies) and wrapping each in a halo and disc. Those packages have to be installed first, so npm install comes before bin/sprites, not after. To add an amenity symbol, map a sprite name to a glyph in sprites/poi-icons.json — there is nothing to draw. Both scripts write into sprites/icons/gen/, which is gitignored, so no generated artwork is ever committed.

Chart layers

The symbology lives in layers/, grouped by what it draws. Each module exports one function returning its layers in draw order; layers/index.ts concatenates them and makes the areas/symbols split.

| module | draws | | ------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | areas.ts | rocks, wrecks and obstructions, seabed quality, restricted and allowed areas | | routes.ts | traffic separation schemes, ferry routes, navigation lines and tracks, submarine cables and pipelines | | structures.ts | piers and breakwaters, piles and dolphins, platforms, cranes, shore stations, harbours, small craft facilities | | lights.ts | lit-mark flares, sector arcs and rays, major and minor lights, fog signals | | marks.ts | buoys and beacons, topmarks, radar reflectors | | labels.ts | landmarks and all name text, including the light characteristic |

The order of that concatenation is load-bearing twice. Paint order is the obvious half. The other is symbol collision: MapLibre places symbols in reverse draw order, so a layer listed later wins the anchor in a crowded harbour — which is why labels come last. index.test.ts asserts the full id order, so a reshuffle can't happen by accident.

Layer geometry comes from the seamark and light layers of the seamap tiles; the symbols come from the sprite sheet, and because icon names are composed from tag values the two must move together.

The symbology derives from styles/freenauticalchart.json in signalk-seamap-plugin (CC0), which in turn draws the sprite set from quantenschaum/mapping. Positioning conventions — tight-cropped icons anchored and offset in the style rather than padded in the sheet — follow that project's own vector/styles/s57.json.

Versioning

Semver tracks the consumer-facing contract: renaming or reordering layer ids, changing the icon naming scheme, or removing icons is major; new layers or icons are minor; visual tweaks are patch.

License

GPL-3.0. The sprite artwork is GPL-3.0 from quantenschaum/mapping (see sprites/PROVENANCE.md), which carries the package as a whole; the symbology it derives from is CC0.