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

@openmeteo/weather-map-layer

v0.2.1

Published

Weather Map Layer for MapLibre/Mapbox GL JS powered by Open-Meteo OMfiles

Readme

Open-Meteo Weather Map Layer

codecov Linting & Tests GitHub license npm version

⚠️ Notice This package is still under construction and is not yet fully production-ready. API changes may occur and some features might be incomplete.

Overview

This repository serves as a demonstration of the Open-Meteo File Protocol (om://) with Mapbox / MapLibre GL JS. The om:// scheme is a custom MapLibre protocol registered via addProtocol. The .om files are hosted on an S3 bucket and can be accessed directly through the protocol handler.

The core weather data generation and API is hosted in the open-meteo/open-meteo repository.

An interactive demo is available at maps.open-meteo.com.

Installation

Node

npm install @openmeteo/weather-map-layer
// ...
import { omProtocol } from '@openmeteo/weather-map-layer';

// Standard MapLibre GL JS setup
// ...

maplibregl.addProtocol('om', omProtocol);

const omUrl = `https://openmeteo.s3.amazonaws.com/data_spatial/dwd_icon/latest.json?variable=temperature_2m`;

map.on('load', () => {
	map.addSource('omFileSource', {
		url: 'om://' + omUrl,
		type: 'raster',
		maxzoom: 12 // tiles look pretty much the same below zoom-level 12, even on the high res models
	});

	map.addLayer({
		id: 'omFileLayer',
		type: 'raster',
		source: 'omFileSource',
		paint: {
			'raster-opacity': 0.75
		}
	});
});

Two files ship next to the module and are referenced with new URL(..., import.meta.url): the tile render worker (dist/worker.js) and the file reader's WebAssembly binary (dist/om_file_format.web.wasm). Bundlers that understand that pattern (Vite, webpack 5, Rollup, Parcel) copy them into their output as assets without configuration. The worker starts with the protocol; the binary is fetched on the first data read, not at import time.

HTML / UNPKG

The package ships as an ES module only, so load it from a <script type="module">. The render worker and the .wasm binary are fetched from the same directory; the worker is started through a same-origin blob when the module comes from another origin, as a worker script itself must be same-origin. For a standalone example, see examples/temperature.html.

<script type="module">
	import * as maplibregl from 'https://unpkg.com/[email protected]/dist/maplibre-gl.mjs';
	import * as OMWeatherMapLayer from 'https://unpkg.com/@openmeteo/[email protected]/dist/index.mjs'; // x-release-please-version

	// Standard MapLibre GL JS setup
	// ...

	maplibregl.addProtocol('om', OMWeatherMapLayer.omProtocol);

	const omUrl = `https://openmeteo.s3.amazonaws.com/data_spatial/dwd_icon/latest.json?variable=temperature_2m`;

	map.on('load', () => {
		map.addSource('omFileSource', {
			url: 'om://' + omUrl,
			type: 'raster',
			maxzoom: 12 // tiles look pretty much the same below zoom-level 12, even on the high res models
		});

		map.addLayer({
			id: 'omFileLayer',
			type: 'raster',
			source: 'omFileSource',
			paint: {
				'raster-opacity': 0.75
			}
		});
	});
</script>

Development

The examples can be served locally, providing direct access to the bundled assets and data files. To launch the development server, execute:

npm run serve

This command initiates a lightweight static server, enabling the interactive demos to be viewed in a browser while reflecting any code changes in real time.

Examples

Raster sources

The repository contains an examples directory with ready-to-run demos:

  • examples/temperature.html – shows temperature data from an OM file.
  • examples/precipitation.html – displays precipitation using a similar setup.
  • examples/wind.html – displays wind values, for arrows overlay see Vector sources.
  • examples/combined-variables.html – shows multiple data sources on the same map.
  • examples/partial-requests.html – demonstrates partial / incremental data requests.
  • examples/interpolation.html – switch the interpolation method live via the interpolation URL parameter.

Run the examples by opening the corresponding .html file in a browser.

Vector sources

For directional arrows / contouring / gridpoints, an additional source must be added, since these features use vector tiles instead of raster tiles.

...

map.on('load', () => {
	map.addSource('omFileVectorSource', {
		url: 'om://' + omUrl,
		type: 'vector'
	});

	map.addLayer({
		id: 'omFileVectorLayer',
		type: 'line',
		source: 'omFileVectorSource',
		'source-layer': 'contours',
		paint: {
			'line-color': 'black',
			'line-width': 4
		}
	});
});

For the vector source examples there is the examples/vector sub-directory with ready-to-run demos:

  • examples/vector/grid-points.html – displays all grid points for a model, with value data on each point.
  • examples/vector/temperature-anomaly.html – shows a seasonal forecast map with temperature anomalies.
  • examples/vector/temperature-labels.html – displays all grid points for a model, using value data to show temperature labels.
  • examples/vector/wind-arrows.html – displays wind map with directional arrows.

Framework Adapters

The core omProtocol handler is designed for MapLibre GL JS, but this package also ships adapters for Mapbox GL JS, Leaflet and OpenLayers. Each adapter provides addProtocol / removeProtocol plus factory methods for creating map-library-native source or layer objects. See examples/leaflet, examples/openlayers and examples/mapbox.

  • Leaflet – addLeafletProtocolSupport(L) gives createTileLayer and createVectorTileLayer, both plain L.GridLayers. They default to tileSize: 512, zoomOffset: -1: the protocol renders 512 px tiles and sizes its arrow/barb lattice for them, so this reproduces the MapLibre look 1:1. Pass tileSize: 256, zoomOffset: 0 for 256 px tiles.
  • OpenLayers – addOpenLayersProtocolSupport(ol) gives createRasterSource (a DataTile source for WebGLTile layers) and createVectorTileSource (an MVT VectorTile source).
  • Mapbox GL JS – addMapboxProtocolSupport() gives createRasterSource, a type: 'custom' raster source for map.addSource, and addVectorSource(map, sourceId, url), which keeps a GeoJSON source in sync with the vector tiles of the viewport (Mapbox custom sources carry raster data only). Style layers select their features with a filter on the layer property, e.g. ['==', ['get', 'layer'], 'wind-arrows'], instead of source-layer.

Cross-origin isolation

The protocol shares the decoded weather data with its render workers through SharedArrayBuffer, which browsers only enable on cross-origin isolated pages. Serve your page with

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

(require-corp is what the Open-Meteo maps app uses; every cross-origin resource then needs CORS or a Cross-Origin-Resource-Policy header. credentialless is a more lenient alternative where supported.) Without these headers the protocol still works, but every tile request has to copy the whole variable into a worker, which blocks the main thread noticeably while panning and zooming. npm run serve sends them (scripts/serve-examples.js); crossOriginIsolated in the console tells whether a page has them.

Maptiler SDK

The Maptiler SDK natively supports addProtocol, but unfortunately it mangles the param url, by removing the second :, this small snippet will fix that:

// MapTiler SDK mangles URLs like `om://https://...` into `om://https//...`
maptilersdk.addProtocol('om', (params, abortController) => {
	params.url = params.url.replace('https//', 'https://');
	return omProtocol(params, abortController, omProtocolSettings);
});

Contouring

  • examples/vector/contouring/contouring-pressure.html – shows how to use contouring with a pressure map.
  • examples/vector/contouring/contouring-on-colorscale.html – shows how to use contouring to follow the breakpoints in the colorscale.
  • examples/vector/contouring/custom-contouring-intervals.html – shows how to use contouring with a custom contouring interval.

Colors

If you’re rendering tiles on a dark base‑map or simply want to experiment with alternative color schemes, the documentation includes several example pages that illustrate all the available color‑scale options:

  • examples/colorscales/darkmode.html – demonstrates the dark=true URL parameter, which automatically switches to palettes fine‑tuned for dark backgrounds.
  • examples/colorscales/custom-rgba.html – shows how to build a linear gradient from a user‑defined array of RGBA values.
  • examples/colorscales/custom-breakpoints.html – demonstrates how to insert your own breakpoints into the scale definitions.

Callbacks

In scenarios where post‑loading transformations of weather data is required, the protocol provides a post‑read callback. This callback is invoked immediately after the data has been parsed by omFileReader, allowing transformations before the data is forwarded to the rendering pipeline. A typical usage pattern is illustrated below:

const omProtocolOptions = OMWeatherMapLayer.defaultOmProtocolSettings;
omProtocolOptions.postReadCallback = (omFileReader, data, state) => {
	if (data.values) {
		data.values = data.values?.map((value) => value / 100);
	}
};

maplibregl.addProtocol('om', (params, abortController) =>
	OMWeatherMapLayer.omProtocol(params, abortController, omProtocolOptions)
);

An example implementation with a useful case is available in the examples/callbacks sub-directory.

Clipping

To restrict weather data to a geometric boundary, the clipping parameters can be supplied during the instantiation of the omProtocol.

const omProtocolOptions = OMWeatherMapLayer.defaultOmProtocolSettings;
omProtocolOptions.clippingOptions = {
	geojson: geojson, // optionally clip weather data to geojson
	bounds: clipBbox // optionally limit tile generation to bbox bounds, automatically generated from geojson when left blank
};
...

The clipping examples require npm run serve, as they load GeoJSON files over HTTP.

  • examples/clipping/raster/clip-switzerland.html – Demonstrates temperature raster data clipped to the geographical contour of Switzerland.
  • examples/clipping/arrows/clip-italy.html – Shows wind velocity raster and vector arrow fields clipped to the contour of Italy.
  • examples/clipping/contours/clip-france.html – Illustrates temperature and isocontour overlays confined to the French boundary.
  • examples/clipping/bounds/clip-germany-bounds.html – Restricts tile generation to a bounding box around Germany.
  • examples/clipping/oceans/clip-oceans.html – Depicts the exclusion of oceanic regions from a global model, thereby hiding weather data on ocean surfaces.

Capture API

⚠️ Using the Capture API will add 0.5-1s delay for each request, because it must first fetch a metadata JSON file to resolve the latest model run before requesting the actual .om tile data.

Because the use of OM files on the S3 storage is often quite ambiguous, a Capture API is added, that will automatically produce the correct file paths for you.

Note on endpoints: https://openmeteo.s3.amazonaws.com/data_spatial is the raw AWS S3 endpoint, used throughout this README and the examples. The official maps app uses https://data-spatial.open-meteo.com/data_spatial, which only accepts requests with a localhost or *.open-meteo.com referer.

For each Weather Model, there will be a latest.json and in-progress.json metadata file, containing data like valid time steps, valid variables and reference times.

An example can be found here, for DWD Icon Global:

https://openmeteo.s3.amazonaws.com/data_spatial/dwd_icon/latest.json
{
	"completed": true,
	"last_modified_time": "2025-11-11T09:42:17Z",
	"reference_time": "2025-11-11T06:00:00Z",
	"valid_times": ["2025-11-11T06:00Z", "2025-11-11T07:00Z", "...+91"],
	"variables": ["cape", "cloud_cover", "cloud_cover_high", "...+120"]
}

Using the Capture API

If you don't want to select a particular model run, but instead always want to use the latest available run. Instead of using the model run in the URL you replace that part with latest.json

For example, with the link below replace the highlighted part:

With latest.json:

If you want to show the closest current time, or a pick a different valid time than the first one, you could use:

or the 5th index of the valid_times array

Time Step Modifiers

The modifier suffix on current_time_ controls the rounding granularity when snapping to the nearest available time step. For example, current_time_1H rounds to the nearest hour, while current_time_30M rounds to the nearest 30 minutes.

| modifier | Alteration | | -------- | ---------- | | M | Minutes | | H | Hours | | d | Days | | m | Months |

Seamless Composite Domains

Several domains support a seamless mode: a composite of a base model and one or more finer regional ones. At every point the finest model that is active at the current zoom level and has data there is shown, so the map switches to a finer model over its area and falls back to the coarser one elsewhere.

The protocol handler resolves the seamless domain on the client side: the latest.json metadata is fetched from the base model, every layer is requested at the base model's run and time step, and tiles are composited per pixel from the active sub-domains. Regional models with a shorter forecast horizon drop out past it (maxForecastHours), so a composite stays valid for the full range of its base model.

Available seamless domains

| Domain value | Constituent models (finest → base) | | ---------------------- | ---------------------------------------------------------------------------------------------------------- | | dwd_icon_seamless | dwd_icon_d2 (zoom 3+) → dwd_icon_eu (zoom 2+) → dwd_icon | | ncep_gfs_seamless | ncep_hrrr_conus (zoom 2+) → ncep_gfs025 | | meteofrance_seamless | meteofrance_arome_france0025 (zoom 3+) → meteofrance_arpege_europe (zoom 2+) → meteofrance_arpege_world025 | | cmc_gem_seamless | cmc_gem_hrdps_west (zoom 4+) → cmc_gem_hrdps (zoom 3+) → cmc_gem_rdps_10km (zoom 2+) → cmc_gem_gdps_15km | | jma_seamless | jma_msm (zoom 3+) → jma_gsm | | ukmo_seamless | ukmo_uk_deterministic_2km (zoom 3+) → ukmo_global_deterministic_10km | | knmi_seamless | knmi_harmonie_arome_netherlands (zoom 3+) → knmi_harmonie_arome_europe | | chmi_seamless | chmi_aladin_cz_1km (zoom 3+) → chmi_aladin_central_europe_2km |

A ready-to-run example is available at examples/seamless.html.

License

This project is licensed under the GNU General Public License v2.0.