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

@zwaarcontrast/ol-graticule

v2.1.2

Published

OpenLayers graticule layer with pluggable grid systems, polygon clipping, and a cursor-position control.

Downloads

383

Readme

@zwaarcontrast/ol-graticule

Flexible graticule layer for OpenLayers with a pluggable GridSystem strategy.

Ships with two built-in grid systems (pixel and geographic lat/lon) and a cursor position control. Other CRSs and historical grids are published as add-on packages, see the add-ons section below.

Geographic lat/lon graticule rendered over a world map

Live demo: https://zwaarcontrast.nl/ol-graticule/ol-graticule/

Install

npm install @zwaarcontrast/ol-graticule ol

Peer: ol ^10. No other runtime dependencies.

Usage

Lat/lon graticule on a web map

import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import {
  UniversalGraticule,
  GeographicGridSystem,
  CursorPositionControl,
} from '@zwaarcontrast/ol-graticule';

const gridSystem = new GeographicGridSystem();

new Map({
  target: 'map',
  layers: [
    new TileLayer({ source: new OSM() }),
    new UniversalGraticule({ gridSystem, style: { edgeLabel: true } }),
  ],
  controls: [new CursorPositionControl({ gridSystem })],
  view: new View({ center: [0, 0], zoom: 2 }),
});

Pixel ruler on a canvas / IIIF view

import Map from 'ol/Map';
import View from 'ol/View';
import { UniversalGraticule, PixelGridSystem } from '@zwaarcontrast/ol-graticule';

const rulers = new UniversalGraticule({
  gridSystem: new PixelGridSystem({ yInverted: true }),
  style: { edgeLabel: true },
});

new Map({
  target: 'map',
  layers: [/* your IIIF / image layer */, rulers],
  view: new View({ center: [0, 0], zoom: 0 }),
});

yInverted: true is for image coordinate systems (IIIF zDirection: -1) where the Y axis goes down in image space but OL's Y axis goes up.

Switching or disabling the grid

Both UniversalGraticule and CursorPositionControl treat their grid-system slot as the single source of truth for what (and whether) they render. Pass a new grid to switch, pass null to deactivate, pass a grid again to reactivate, no separate visibility toggle required.

const graticule = new UniversalGraticule({ gridSystem: new GeographicGridSystem() });
const cursor = new CursorPositionControl({ gridSystem: new GeographicGridSystem() });

// Switch to a different grid, both update in place.
graticule.setGridSystem(new PixelGridSystem());
cursor.setGridSystem(new PixelGridSystem());

// Turn them off without removing them from the map.
graticule.setGridSystem(null);
cursor.setGridSystem(null);

// Turn them back on.
graticule.setGridSystem(new GeographicGridSystem());
cursor.setGridSystem(new GeographicGridSystem());

Both constructors accept null (or an omitted gridSystem) so you can wire the layer and control into your map up front and activate them later.

UniversalGraticule options

| Option | Type | Default | What it does | |---|---|---|---| | gridSystem | GridSystem \| null | null | The grid to draw. null = inactive layer. | | style | GraticuleStyle | library defaults | Line / edge-label / cell-label config, see Styling. | | xLabelPosition | 'top' \| 'bottom' | 'top' | Which edge gets x-axis (lon/easting) labels. | | yLabelPosition | 'left' \| 'right' | 'left' | Which edge gets y-axis (lat/northing) labels. | | xLabelOffset | number (px) | 2 | Inset for x-axis labels from the top/bottom edge. | | yLabelOffset | number (px) | 2 | Inset for y-axis labels from the left/right edge. | | maxLines | number | 100 | Safety cap on lines per axis per frame. |

Plus every VectorLayer option except source and style (they're managed internally).

Styling

All styling flows through one GraticuleStyle shape:

import Stroke from 'ol/style/Stroke';
import Text from 'ol/style/Text';
import Fill from 'ol/style/Fill';

new UniversalGraticule({
  gridSystem,
  style: {
    line: {
      major: new Stroke({ color: 'rgba(0,0,0,0.4)', width: 1 }),
      minor: new Stroke({ color: 'rgba(0,0,0,0.15)', width: 0.5 }),
    },
    edgeLabel: new Text({
      font: '600 11px system-ui',
      fill: new Fill({ color: 'white' }),
      stroke: new Stroke({ color: 'black', width: 3 }),
    }),
    // cellLabel: false    // suppress cell codes on MBS / Kriegsmarine grids
    // cellLabel: createDefaultCellLabelHandler({ fontFamily: 'Inter' })
  },
});
  • line, one Stroke (same for major + minor), a { major, minor? } pair, or a custom OL StyleLike that inspects each feature's gridLineType property.
  • edgeLabel, omit to hide; true for library defaults; a Text for a styled template (cloned per label); or a full EdgeLabelStyleHandler for pooled custom rendering.
  • cellLabel, omit for library defaults (used by MBS, Kriegsmarine); false to suppress; or a CellLabelStyleHandler for custom rendering. Tweak the defaults with createDefaultCellLabelHandler({ fontFamily, fadeStops, … }).

The CursorPositionControl has its own small style shape:

new CursorPositionControl({
  gridSystem,
  style: {
    color: 'rgba(249, 115, 22, 0.9)',
    labelCss: 'font: 600 10px system-ui; color: white;',
  },
});

Add-ons

These packages plug their own GridSystem into UniversalGraticule:

| Package | What it draws | |---|---| | @zwaarcontrast/ol-graticule-projected | Any proj4 CRS (UTM, state plane, national grids). | | @zwaarcontrast/ol-graticule-rd | Dutch RD Amersfoort (EPSG:28992 / 28991) with bundled RDNAPTRANS 2018 datum-shift grid. | | @zwaarcontrast/ol-graticule-mgrs | Military Grid Reference System (MGRS / NATO grid) over UTM, with Norway/Svalbard exceptions. | | @zwaarcontrast/ol-graticule-modified-british-system | Modified British System letter-cell artillery grids for ten WWII theatres (Nord de Guerre, French Lambert I/II/III, British/Irish Cassini, War Office Cassini, Scandinavian Zone 3, Italian Northern/Southern, Iberian Peninsula). | | @zwaarcontrast/ol-graticule-marinequadratkarte | WWII Kriegsmarine naval grid (not yet published, see repo). |

Reverse: parse a label back to a coordinate

Every built-in grid system also implements the optional parseCoordinate method, which turns a typed label back into view-projection coordinates, useful for "go to" search inputs.

import { ParseError } from '@zwaarcontrast/ol-graticule';

const gridSystem = new GeographicGridSystem();

try {
  const [x, y] = gridSystem.parseCoordinate('50°51′N 4°21′E', map.getView().getProjection());
  map.getView().animate({ center: [x, y], duration: 400 });
} catch (err) {
  if (err instanceof ParseError) console.warn(err.reason);
}

Parsing is lenient: hemisphere markers route axes for GeographicGridSystem (50°N 4°E and 4°E 50°N both work); plain pairs default to "x y" order (lon-lat for geographic, easting-northing for linear). Compound formats (MBS letter-cells, Kriegsmarine references) round-trip via the formatter's own parseCoordinate. Spatial validity is intentionally not checked, call isValidCoordinate on the result if you need it.

The underlying single-axis parse lives on the formatter:

new DegreeFormatter().parse("50°37'02\"N", 'y');   // 50.6172…
new MetricFormatter().parse('1.2345 km');           // 1234.5
new PixelFormatter().parse('123 px');               // 123

API reference

  • UniversalGraticule(options), VectorLayer subclass. setGridSystem(grid | null) to swap/disable.
  • CursorPositionControl(options), OL Control. Renders edge labels (axis grids) or a floating compound label (MBS/Kriegsmarine) depending on the grid system.
  • GridSystem, interface with getFeatures, getLabels, formatCoordinate, optional parseCoordinate, getCellLabels, isValidCoordinate. Implement it to draw any grid describable in code.
  • LabelFormatter, format / optional formatCoordinate / optional parse / optional parseCoordinate / optional formatCellLabel.
  • ParseError, thrown by parse* methods on unparseable input. Has text and reason fields.
  • Built-in grids: PixelGridSystem, GeographicGridSystem, PolygonClippedGridSystem.
  • Built-in formatters: PixelFormatter, DegreeFormatter (DMS / DD / DDM), MetricFormatter.
  • Built-in intervals: PixelIntervals, DegreeIntervals, MetricIntervals.
  • Style helpers: createDefaultEdgeLabelHandler, createDefaultCellLabelHandler, resolveLineStyle, DEFAULT_LINE_STROKE, DEFAULT_CURSOR_COLOR.

See src/types.ts and src/style.ts for full type signatures.

License

MIT.