@plymer/fast-barnes-ts
v0.4.0-rc0
Published
Fast Barnes interpolation for irregularly spaced 1D, 2D and 3D sample data in TypeScript.
Maintainers
Readme
fast-barnes-ts
Fast Barnes interpolation for irregularly spaced 1D/2D/3D samples, implemented in TypeScript for Node.js and browser bundles.
This package ports the fast convolution-based approach from MeteoSwiss fast-barnes-py into an npm-friendly TypeScript API.
It includes built-in support for reading GeoJSON FeatureCollection point data and generating contour outputs as GeoJSON FeatureCollection isolines or isobands.
This project was created with extensive help from GPT-5.3-Codex but ground-truthed by a professional operational meteorologist
Acknowledgements
This is a mostly vibe-coded port of Bruno Zürcher's incredible work building the fast-barnes-py package and would be impossible without him. This package was created to fill a need for a fast, browser-capable solution to interpolating weather data.
Features
- Fast
O(N + grid)interpolation withoptimized_convolution(default) - Alternative methods available:
convolution,naive - Supports 1D, 2D, and 3D interpolation domains
- Typed TypeScript API, published for Node.js and browser usage
- GeoJSON-first helpers for common weather and geospatial workflows
Install
npm install fast-barnes-tsQuick start
import { barnes, toNestedArray } from "fast-barnes-ts";
const points = [
[-3.73, 56.33],
[2.64, 47.05],
[-8.4, 47.5],
[2.94, 54.33],
];
const values = [995.1, 1012.5, 1011.3, 1006.0];
const resolution = 32;
const step = 1 / resolution;
const x0 = [-9, 47];
const size = [Math.floor(12 / step), Math.floor(12 / step)];
const result = barnes(points, values, 1.0, x0, step, size, {
method: "optimized_convolution",
numIter: 4,
maxDist: 3.5,
});
const grid = toNestedArray(result); // grid[y][x]GeoJSON workflow
Read station samples from a GeoJSON FeatureCollection<Point> and generate contour outputs in one call.
import { geoJSONtoGeoJSON } from "@plymer/fast-barnes-ts";
import type { FeatureCollection, Point } from "geojson";
type PressureProps = { pressure: number; stationId: string };
declare const stations: FeatureCollection<Point, PressureProps>;
const isolines = geoJSONtoGeoJSON(stations, "pressure", "isoline", {
contourOptions: { spacing: 4, base: 1024 },
extrema: {
minProminence: 1.5,
minSeparation: 3,
},
});
const isobands = geoJSONtoGeoJSON(stations, "pressure", "isoband", {
contourOptions: { spacing: 4, base: 1024 },
});Or, convert a tuple array in the form of [x, y, value][] directly to contours.
import {
tupleArrayToGeoJSON,
type Tuple2DWithValue,
} from "@plymer/fast-barnes-ts";
const samples: Tuple2DWithValue[] = [
[0.2, 0.2, 1.0],
[1.2, 1.1, 2.0],
[2.5, 0.7, 0.5],
[0.4, 1.7, 1.4],
];
const isolines = tupleArrayToGeoJSON(samples, "isolines", {
resolution: 64,
contourOptions: { spacing: 0.25, base: 0 },
});
const isobands = tupleArrayToGeoJSON(samples, "isobands", {
resolution: 64,
contourOptions: { spacing: 0.25, base: 0 },
});Both isolines and isobands are returned as GeoJSON FeatureCollection objects.
Isoline output is clipped at the interpolation domain edge, so contours end at the boundary instead of tracing the rectangular grid frame. Interior no-data (NaN) void boundaries are also excluded from isoline output. Isobands keep their full polygon geometry.
Set extrema: true (or pass extrema options) to append local max/min Point
features to the same output collection.
Core API
barnes(pts, val, sigma, x0, step, size, options?)
pts:number[](1D) ornumber[][](NxM, M in{1,2,3})val: values for each sample pointsigma: scalar or per-axis vectorx0: grid origin (scalar or vector)step: grid spacing (scalar or vector)size: grid size (scalar or vector)options.method:'optimized_convolution' | 'convolution' | 'naive'options.numIter: iteration count (default4)options.maxDist: cutoff distance in sigma units (default3.5)
Alternative overload:
barnes(tupleArray, sigma, x0, step, size, options?)
tupleArray:[x, value][](1D),[x, y, value][](2D), or[x, y, z, value][](3D)- optimized for direct tuple ingestion without object-sample conversion
Return shape:
{
data: Float32Array;
shape: readonly number[];
dimension: 1 | 2 | 3;
}GeoJSON helpers
samplesFromGeoJSON(featureCollection, propertyKey)geoJSONtoGeoJSON(featureCollection, propertyKey, mode, options?)tupleArrayToGeoJSON(samples, mode, options?)gridToIsolinesGeoJSON(field, x0, step, contourOptions)gridToIsobandsGeoJSON(field, x0, step, contourOptions)
Additional utility helpers:
toNestedArray(result)findGridExtrema2D(grid, x0, step, options?)gridExtremaToGeoJSON(extrema)getBarnesParams(tupleData, options)normalizeResolution(resolution)buildSpacedThresholds(data, spacing, base)resolveThresholds(grid, contourOptions)getHalfKernelSize(...)getHalfKernelSizeOpt(...)getTailValue(...)getSigmaEffective(...)
getBarnesParams(tupleData, options)
Builds x0, step, and size for 2D interpolation directly from tuple samples.
Euclidean mode:
import { getBarnesParams, type Tuple2DWithValue } from "fast-barnes-ts";
const samples: Tuple2DWithValue[] = [
[0.2, 0.2, 1.0],
[1.2, 1.1, 2.0],
[2.5, 0.7, 0.5],
[0.4, 1.7, 1.4],
];
const { x0, step, size } = getBarnesParams(samples, {
mode: "euclidean",
resolution: 64,
padding: 0.05,
});Spherical mode ([longitude, latitude, value] input) returns projected-grid
params plus projection helpers:
import {
barnes,
getBarnesParams,
type LambertProjectionParams,
type Tuple2DWithValue,
} from "fast-barnes-ts";
const stations: Tuple2DWithValue[] = [
[-128.154, 52.181, 1022.9],
[-93.733, 49.664, 1014.1],
[-122.955, 50.129, 1016.8],
[-105.483, 49.05, 1016.0],
];
const params = getBarnesParams(stations, {
mode: "spherical",
resolution: 128,
sphericalOptions: {
lambertPadding: 0.05,
},
});
const projectedPoints = stations.map(([lon, lat]) => params.project(lon, lat));
const values = stations.map(([, , value]) => value);
const grid = barnes(
projectedPoints,
values,
Math.max(params.step[0], params.step[1]) * 2,
params.x0,
params.step,
params.size,
);
// Inverse-project a grid coordinate back to lon/lat.
const lonLat = params.unproject(params.x0[0], params.x0[1]);
// Optional explicit typing if needed downstream.
const projection: LambertProjectionParams = params.projection;Notes:
- Spherical coordinates must be in
[longitude, latitude]order. getBarnesParamsthrows for empty input.- Spherical mode validates latitude/longitude ranges and throws clear errors for swapped lat/lon input.
Finding High/Low Pressure Centres
After interpolation, you can detect local maxima/minima (for highs/lows) and export them as GeoJSON points.
import {
barnes,
findGridExtrema2D,
gridExtremaToGeoJSON,
type Tuple2DWithValue,
} from "fast-barnes-ts";
const samples: Tuple2DWithValue[] = [
[-4.0, 51.0, 1008.2],
[-1.8, 52.2, 1015.4],
[1.2, 50.5, 1002.7],
[2.8, 53.0, 1012.3],
];
const x0: [number, number] = [-6, 49];
const step: [number, number] = [0.1, 0.1];
const size: [number, number] = [160, 120];
const grid = barnes(samples, 0.5, x0, step, size, {
method: "optimized_convolution",
numIter: 4,
});
const extrema = findGridExtrema2D(grid, x0, step, {
radius: 1,
minSeparation: 3,
minProminence: 0.8,
});
const centresGeoJSON = gridExtremaToGeoJSON(extrema);findGridExtrema2D returns both maxima and minima, each with:
kind:"max"or"min"value: interpolated field value at that grid cellprominence: local strength relative to nearby cellsgridIndex,i,j: grid coordinatesx,y: map/data coordinates
Examples
Generate example contour files:
npm run example:geojsonThis writes:
examples/output/isobands.geojsonexamples/output/isolines.geojson
Run the CDN-based MapLibre viewer:
npm run example:maplibreOpen:
http://localhost:4173/examples/maplibre-viewer.html
Development
npm install
npm run test
npm run build
npm run benchmark