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

@softwarity/sigmet-draw

v2.1.0

Published

Draw SIGMET/AIRMET geometries on any map (grafts onto MapLibre, OpenLayers or Leaflet, Terra-Draw-style) and (de)serialize them to/from ICAO TAC.

Readme

@softwarity/sigmet-draw

npm version unpacked size

Works with  (via @softwarity/draw-adapter)

Draw SIGMET/AIRMET geometries on a map and (de)serialize them to/from ICAO/WMO TAC (the coded text form, WMO-No.49 Vol II, App. 6).

It is a headless library that grafts onto your map — like Terra Draw. You bring a MapLibre, OpenLayers or Leaflet map (basemap, controls, projection are yours); the library adds the SIGMET drawing on top through a thin adapter, exposes imperative tools (circle(), meridian(), …) and a change event, and never depends on a concrete map engine.

The user picks a tool, a default geometry is dropped on the map, and they just drag the handles — there is no freehand drawing. Each shape is clipped to the FIR and its TAC is produced live.

The 12 geometry templates

Each form is parametric (a few parameters + handles); the displayed area is derived and clipped to the FIR, and the TAC is serialized from the parameters.

| # | Kind | TAC form | Handles | |---|------|----------|---------| | 1 | meridian | E OF W012 | vertical line, pick side (E/W) | | 2 | parallel | N OF N54 | horizontal line, pick side (N/S) | | 3 | lineSide | SW OF LINE p1 - p2 [- p3 - p4] | oblique line (2–4 pts, ends snap to the FIR border), pick side | | 4 | latBand | N OF S50 AND S OF N54 | two horizontal lines | | 5 | lonBand | E OF W012 AND W OF E005 | two vertical lines | | 6 | corridor | … OF LINE … AND … OF LINE … | two oblique lines, area between | | 7 | quadrant | N OF N54 AND E OF W012 | a corner, click to pick the quadrant | | 8 | polygon | WI p1 - p2 - … - pn | polygon vertices | | 9 | circle | WI 250NM OF PSN N2706 W07306 | centre + radius handle | | 9b | tropicalCyclone(center) | WI 250NM OF TC CENTRE | fixed centre (caller-provided) + radius handle | | 10 | wideLine | APRX 50KM WID LINE BTN p1 - p2 [- p3 - p4] | polyline (2–4 pts) + width handle | | 11 | entireFir | ENTIRE FIR | — | | 12 | point | N48 E010 | single marker (constrained to the FIR) |

Lines/polygons drop their interior points collinear with their neighbours from the result (and grey them out); line endpoints snap to the FIR boundary; dragging a vertex near collinearity snaps it onto the line.

Rigid line drag — dragging the body of a lineSide / corridor line normally lets it flex: each endpoint snaps to the nearest border point, which bends the end segments. Hold Ctrl (⌘ on Mac) to keep the line's direction frozen instead — the interior stays rigid and each endpoint keeps following the FIR border by stretching its end segment (only the length adapts, not the angle).

Radius/width units follow the 2-digit/3-digit TAC rule: KM up to the cap, then NM (physically larger), capped — circle/wide-line 0…99, tropical cyclone 0…999. Pass nauticalMilesOnly: true to always emit NM. The tropical-cyclone centre is not part of the TAC (it lives in the TC SIGMET's PSN element), so fromTAC("WI 250NM OF TC CENTRE") returns the geometry with a placeholder centre that SigmetDraw resolves to the FIR centroid for display.

Usage

Headless graft (the primary API)

import { Map } from "maplibre-gl";
import { SigmetDraw, MapLibreAdapter } from "@softwarity/sigmet-draw";

const map = new Map({ container: "map", style, center, zoom }); // YOUR map
const sigmet = new SigmetDraw({
  adapter: new MapLibreAdapter({ map }),
  fir,                                   // GeoJSON Polygon/MultiPolygon — required
  label: (r) => r.tac,                   // optional on-shape text (omit for none)
  tooltip: (r) => r.tac,                 // optional floating tooltip on hover
  nauticalMilesOnly: false,              // optional — never emit KM when true
  toolbar: { position: "top-left" },     // optional turnkey toolbar (see below)
  style: { area: { fill: "#e11d48", opacity: 0.3 } }, // optional, see below
});

sigmet.on("tac", (tac) => console.log(tac));                    // just the TAC — common case
sigmet.on("change", ({ geometry, tac, area }) => { /* full result */ });

circleBtn.onclick   = () => sigmet.circle();   // wire YOUR buttons
meridianBtn.onclick = () => sigmet.meridian();
clearBtn.onclick    = () => sigmet.clear();

OpenLayers is identical with new OpenLayersAdapter({ map }) (an ol/Map), and Leaflet with new LeafletAdapter({ map }) (an L.Map, from @softwarity/sigmet-draw/leaflet). The consumer loads the engine's stylesheet (maplibre-gl/dist/maplibre-gl.css, ol/ol.css, or leaflet/dist/leaflet.css). Each engine is an optional peerDependency — install only the one(s) you use. Engine capabilities differ: globe is MapLibre-only (OpenLayers & Leaflet are 2D).

SigmetDraw methods: circle(), tropicalCyclone(center), meridian(), parallel(), latBand(), lonBand(), quadrant(), lineSide(), corridor(), polygon(), wideLine(), point(), entireFir(), clear(), plus on/off("change"|"tac", cb), load(geometry), setFir(fir), firBounds(), firCenter(), setStyle(…), setLabel(fn), setTooltip(fn), setReadonly(bool)/isReadonly, setSelected(bool)/isSelected, snapshot(opts?), ready(), destroy(), and the toolbar controller (when the toolbar option is enabled). See the full reference below.

Turnkey toolbar (batteries-included)

Instead of wiring your own buttons, enable the built-in toolbar in the options (native control group, built-in icons, every tool wired):

const sigmet = new SigmetDraw({
  adapter, fir,
  toolbar: {                                       // presence renders the toolbar
    position: "top-left",                          // see the 12 positions below
    padding: { top: "12px", left: "8px" },         // CSS length, or per-side
    gap: "2px",                                     // spacing between buttons (flow is derived from position)
    className: "my-toolbar",                        // extra class for your CSS
    tools: ["circle", "tropicalCyclone", "polygon"],// pick/order (default: all)
    clear: true,                                    // include the clear button
    snapshot: { quality: "native", onClick: "download", shutter: true }, // 📷 button. quality: native|low|medium|high;
                                                    // onClick: download|clipboard (⌘/Ctrl-click does the other); shutter:false → no capture flash.
                                                    // "none"/false hides it; disabled on Leaflet.
    lock: true,                                     // 🔒 "lock map" button (freezes pan/zoom while drawing); false hides it
    tcCenter: null,                                 // see below
  },
});

Positions — the first token is the anchored edge (which also sets the bar's flow: top/bottom ⇒ horizontal, left/right ⇒ vertical); a bare edge is centred along it:

top         top-left     top-right
bottom      bottom-left  bottom-right
left        left-top     left-bottom
right       right-top    right-bottom

Padding is a CSS length applied to the anchored edge(s), or a per-side object { top?, right?, bottom?, left? }.

The 🔒 lock-map button (lock, default on) and the 📷 snapshot button sit at the end of the bar; set lock: false to hide the lock button.

Then tweak it live through sigmet.toolbar:

sigmet.toolbar.tcCenter = { lat, lon };          // enable the TC button (real TC position)
sigmet.toolbar.tcCenter = null;                  // grey it out again
sigmet.toolbar.position = "right";               // re-place on the fly (vertical bar)
sigmet.toolbar.padding  = { top: "20px", left: "12px" };

The tropical-cyclone button is disabled until tcCenter is set — it needs a centre, and there is no sensible default (a real host has the TC position; the demo uses the FIR centroid). The buttons live inside the engine's native control group (maplibregl-ctrl-group / ol-control), so they automatically inherit the host engine's native button look; the library only ensures the SVG icons are centred and visible. Override anything via the .sigmet-toolbar class (or your className). Prefer full control? Build your own ToolbarItem[] and call adapter.addToolbar(items, options) directly — DEFAULT_TOOLS and TOOL_ICONS are exported.

Styling & dynamic label

One engine-agnostic SigmetStyle (baked into the data by decorate, rendered identically on MapLibre / OpenLayers / Leaflet). Override only what you want, at construction (style) or live (setStyle). A dynamic label prints text on the shape; a tooltip shows a floating box on hover over it — both are (result) => string.

sigmet.setStyle({
  area: { fill: "#e11d48", opacity: 0.3, stroke: "#e11d48", width: 2 },
  lineHandle: { stroke: "#22d3ee", width: 3 },        // draggable meridian/parallel lines
  iconHandle: { fill: "#fff", stroke: "#22d3ee" },    // all dot handles + glyphs (glyph colour = stroke)
  label: { color: "#fff", halo: "#0b1622", size: 13, width: 180 }, // width = max px → wraps
  tooltip: { color: "#e6edf3", background: "#0b1622", size: 12 },
});
sigmet.setLabel((r) => r.geometry.kind);   // on-shape text (null to hide)
sigmet.setTooltip((r) => r.tac);           // hover tooltip (null to hide)

Tokens: area, iconHandle (dots: vertices + move/resize/transform/radius), lineHandle (the draggable meridian/parallel lines), label, tooltip. Collinear (TAC-redundant) vertices are always greyed and not configurable. See DEFAULT_STYLE / mergeStyle.

Core only (no map)

import { toTAC, fromTAC, toArea } from "@softwarity/sigmet-draw/core";

const g = fromTAC("WI 250NM OF PSN N2706 W07306");
toTAC(g);                          // "WI 250NM OF PSN N2706 W07306"
toTAC(g, { minutes: "always" });   // pad whole-degree minutes (N5400)
toArea(g, { fir });                // GeoJSON Feature, clipped to the FIR

Reference

new SigmetDraw(options)

| Option | Type | Default | Description | |--------|------|---------|-------------| | adapter | MapAdapter | — | Wraps your map (new MapLibreAdapter({ map }) / new OpenLayersAdapter({ map })). Required. | | fir | Feature \| Polygon \| MultiPolygon | — | FIR polygon for clipping/constraints/snapping. Required. | | label | (r: SigmetResult) => string | — | On-shape text (return "" to hide). | | tooltip | (r: SigmetResult) => string | — | Floating text on hover over the geometry. | | nauticalMilesOnly | boolean | false | Force NM for radii/widths (never emit KM). | | toolbar | boolean \| ToolbarConfig | — | Render the turnkey toolbar (see below). | | style | SigmetStyleInput | — | Partial style override (merged onto DEFAULT_STYLE). |

Events — on(event, cb) / off(event, cb)

| Event | Callback | Fires | |-------|----------|-------| | "tac" | (tac: string) => void | on every placement/edit — the common case | | "change" | (r: SigmetResult) => void | same, with the full result { geometry, tac, area } |

Methods

circle(), tropicalCyclone(center), meridian(), parallel(), latBand(), lonBand(), quadrant(), lineSide(), corridor(), polygon(), wideLine(), point(), entireFir() · clear() · load(geometry) · setFir(fir) · firBounds()[minLon,minLat,maxLon,maxLat] · firCenter()LatLng · setStyle(partial) · setLabel(fn|null) · setTooltip(fn|null) · setReadonly(bool) / isReadonly (read-only mode — hides handles + toolbar, freezes editing; area + label stay) · setSelected(bool) / isSelected (deselect to hide the editing handles/guides for a clean view — area + label only, e.g. before a snapshot — while staying editable: clicking the shape re-selects it, clicking the empty map deselects it) · snapshot(opts?)Promise<Blob> (PNG of the current map — basemap + drawing; the editing chrome (handles/guides) is hidden for the capture. opts.scale = output pixel-ratio, default the screen's; opts.target = download/clipboard/blob; opts.hideOverlays overrides what's hidden. MapLibre & OpenLayers only; rejects on Leaflet) · ready(): Promise · destroy() · toolbar (the controller, see below).

SigmetStyle tokens

| Token | Fields | |-------|--------| | area | fill, stroke, width, opacity | | iconHandle | fill, stroke, width, radius | | lineHandle | stroke, width | | label | color, halo, size, width (width = max px before wrapping; halo thickness derived from size) | | tooltip | color, background, size |

All grab handles (vertices, move / resize / transform / radius) share iconHandle; the move/resize dot is rendered smaller and the chevron/rotate glyphs are coloured from iconHandle.stroke (recoloured live on setStyle). Collinear (TAC-redundant) vertices are always shown as a smaller, stroke-less grey dot — that state isn't styleable.

Helpers: DEFAULT_STYLE, mergeStyle(base, partial), rgba(hex, opacity).

ToolbarConfig / sigmet.toolbar

Config (construction): position (12 values, see above — the bar's flow is derived from it), padding (string | { top?, right?, bottom?, left? }), gap, className, tools (ToolName[]), clear (boolean), snapshot, lock (boolean, the 🔒 "lock map" button — default on), tcCenter (LatLng | null). Live via sigmet.toolbar: tcCenter (set to enable the TC button / null to grey it out), position, padding, gap. Build a fully custom toolbar with adapter.addToolbar(items, options) + the exported DEFAULT_TOOLS / TOOL_ICONS.

Architecture

One repo, one npm package (built with tsc), plus a standalone Angular demo in demo/ (deployed to GitHub Pages). The map engine is confined to the adapters; everything above is agnostic.

The per-engine map adapters live in the shared @softwarity/draw-adapter package (generic, data-driven, reused across the @softwarity drawing libs). The adapters here are thin wrappers that pre-bind the SIGMET layer manifest; styling is carried by the feature data (style-features.ts/decorate), so the adapter never sees a domain type.

src/
  core/                pure logic — 12 templates, TAC ↔ params, area (turf) — no map dep
  map/
    adapter.ts         back-compat shim → @softwarity/draw-adapter types
    maplibre-adapter.ts   MapLibreAdapter({ map })   + createMapLibreMap   (thin wrapper)
    openlayers-adapter.ts OpenLayersAdapter({ map }) + createOpenLayersMap (thin wrapper)
    leaflet-adapter.ts    LeafletAdapter({ map })    + createLeafletMap    (thin wrapper)
    style-features.ts  SIGMET_LAYERS / SIGMET_HIT + decorate() (bakes SigmetStyle into data)
    sigmet-draw.ts     SigmetDraw — the engine-agnostic drawing logic
    style.ts           SigmetStyle spec + DEFAULT_STYLE / mergeStyle
    tools.ts           DEFAULT_TOOLS + TOOL_ICONS (turnkey toolbar)
    geometry.ts        pure helpers (collapse, snap, radius/width units)
test/                  vitest (core + geometry/style helpers + controller harness)
demo/                  Angular demo: graft on all three engines, FIR selector grouped
                       by geometry (antimeridian, poles, equator, huge, …), live
                       style editor, turnkey toolbar

Package exports: . (all), ./core (pure logic), ./maplibre, ./openlayers, ./leaflet (per-engine adapters, so a consumer pulls only the engine it uses). MapLibre, OpenLayers and Leaflet are optional peerDependencies; @softwarity/draw-adapter is a regular dependency (the shared adapter core).

Notable details

  • FIR is required — used for clipping, the centre/point-inside-FIR constraint and line-endpoint snapping. The host draws the FIR outline on its own map; the library only consumes the polygon.
  • Antimeridian — a FIR straddling 180° is detected and handled in an unwrapped longitude frame; TAC is re-normalised to ±180.
  • Geometry via Turf 7 (circle, buffer, intersect, booleanPointInPolygon).

Development

npm install
npm run build            # tsc → dist
npm test                 # vitest (core + geometry/style helpers)

# demo (separate Angular app that consumes ../dist):
cd demo && npm install
npm run build            # lib first, then…
cd demo && npx ng serve  # dev server

References

  • WMO-No.49 Vol II, Appendix 6 — SIGMET/AIRMET templates & examples (A6-1/2/3).
  • ICAO Annex 3 / PANS-ABC (Doc 8400) for abbreviations.