Skip to content
| Marketplace
Sign in
Visual Studio Code>Visualization>FieldglassNew to Visual Studio Code? Get it now.
Fieldglass

Fieldglass

Fieldglass

|
87 installs
| (0) | Free
View and render GRIB1, GRIB2, and NetCDF weather data files in VS Code
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Fieldglass

CI Coverage License: MIT OR Apache-2.0

Open a GRIB or NetCDF file in Visual Studio Code and see what is in it: every message listed with its parameter, level and time, and any field drawn as a map you can reproject, probe and contour.

Nothing else to install. No Python, no C libraries, no compiler — the extension ships a prebuilt binary for each platform and picks the right one. Every decoder is written from scratch in Rust, which is what makes that possible: every GRIB2 packing the WMO defines, decoded with no external libraries and no build flags, and spectral fields turned back into a map.

Fieldglass rendering a GFS temperature field reprojected onto an orthographic globe with coastlines

Latest release

Status

GRIB1 and GRIB2 — every message's metadata, and any decodable field drawn as a color image. Eight projections (the grid's own coordinates, equirectangular, Web Mercator, orthographic, polar stereographic, Mollweide, Robinson, Equal Earth), with coastlines, borders, lakes, rivers and a lat/lon grid over the top. GRIB2 decodes every packing the WMO has defined; see the GRIB2 packing modes table. Spectral fields are stored as coefficients rather than as a grid; those are transformed back into a map and drawn.

NetCDF — the full structure of both classic and NetCDF-4 / HDF5 files: dimensions, variables and attributes. Pick a variable and two axes to draw, then step through the rest (time, level). Grids that carry a normal latitude and longitude work, as do WRF and GOES files, and ocean or satellite files that give a position for every individual cell.

Not yet: editing a file's metadata from the viewer. The groundwork is there — see What it does, and where it stops for how far it goes.

The feature matrix below shows exactly what works today, and the roadmap shows what is planned.

Feature matrix

Some things work the same way for all three formats, so they are not repeated as three identical columns. Those are: detection from magic bytes, file-extension association (.grb / .grib* / .nc*), opening an unrecognized file through Reopen Editor With…, and header-section parsing. On the render side: the colormap picker (8 maps, reversible), linear and log10 color scaling, the point probe, and PNG export.

The table below covers the rest, where the three formats genuinely differ. Read a row as "how much of this feature does each format have": a bare tick means it works in full, and a tick followed by text means it works with the qualification given. Where a format has no entry, the feature does not apply to it.

Feature GRIB1 GRIB2 NetCDF
Per-message metadata (parameter, level, time, forecast period) ✅ ✅ (sections 0 to 4) ✅ (dims / vars / attrs)
Grid description (lat/lon, Gaussian, polar stereo, Lambert) ✅ ✅ lat/lon (3.0), rotated lat/lon (3.1), Mercator (3.10), transverse Mercator (3.12), polar stereo (3.20), Lambert (3.30), regular and reduced Gaussian (3.40), Lambert azimuthal equal-area (3.140), space view (3.90), spherical harmonic (3.50), bi-Fourier (3.61–3.63), HEALPix (3.150) ✅ regular lat/lon (CF coordinate vars), WRF (Lambert, polar stereo, Mercator, unrotated lat-lon), GOES geostationary, curvilinear (2-D lat(y,x)/lon(y,x))
WMO lookups (parameter, centre, level type) ✅ ON388 Table 2 + ECMWF local 128/129 · centres from the WMO Common Code Tables ✅ Code Table 4.2 complete (1,387 parameters across 60 discipline/category tables) + tables 0.0 / 1.2 / 1.3 / 1.4 / 3.1 / 3.2 / 4.1 / 4.3 / 4.4 / 4.5 / 4.6 / 4.10 · centres from the WMO Common Code Tables · local tables for ECMWF, DWD and NCEP n/a
Tabular metadata viewer ✅ ✅ (sections 0 to 4) ✅
Binary data section decoding (Rust API) ✅ ✅ packing — see GRIB2 packing modes ✅ classic (CDF-1/2/5) + NetCDF-4 / HDF5 (contiguous, compact, and chunked storage with deflate / shuffle / fletcher32 / zstd)
Metadata editing ❌ Not yet ❌ Not yet ❌ Not yet
2-D grid rendering with colormap ✅ ✅ any decodable message (see decoding row) ✅ regular lat/lon (classic + NetCDF-4 / HDF5), WRF, GOES geostationary, curvilinear (ocean tripolar, satellite swath)
Render-panel projection picker (8 targets) ✅ ✅ (any decodable message) ✅ regular lat/lon + WRF / GOES geostationary + curvilinear
Field compare (difference / sum / ratio / mean) ✅ two messages ✅ two messages ✅ two slices of a variable
Map overlays (coastlines, borders, lakes, rivers, lat/lon grid) ✅ ✅ (any decodable message) ✅ regular lat/lon + WRF / GOES geostationary + curvilinear
Contour lines ✅ lat/lon family + projected grids ✅ lat/lon family + projected grids ✅ regular lat/lon + WRF projections + curvilinear
Resampling picker (nearest / bilinear) ✅ ✅ ✅ regular lat/lon + WRF / GOES geostationary + curvilinear
Export decoded field as CSV ✅ matrix + lat,lon,value (lat/lon family + projected grids) ✅ matrix + lat,lon,value (lat/lon family + projected grids) ✅ matrix + lat,lon,value (georeferenced grids)

Legend: ✅ supported — any following text names the scope · 🚧 partial / in progress · ❌ not yet · n/a not applicable to the format.

Format-agnostic features:

  • Hex and ASCII fallback view for files whose contents are not a recognized format. ✅
  • Files without a recognized extension can still be opened through Reopen Editor With… → Fieldglass Viewer.

How the decoders are checked

Every packing below is decoded from the bytes up in Rust and checked value for value against an independent implementation of the same template — eccodes 2.34, which is the reference the meteorological community treats as authoritative. Where no encoder produces a variant, the check is against a hand-built fixture with the expected values computed by hand instead. The notes under each table say which applies and where a template needed something else.

GRIB1 packing modes

A GRIB1 file's BDS (Binary Data Section) flag bits select one of several packing schemes. Decoding (and therefore 2-D rendering) covers only some of them today; metadata and format detection do not depend on the packing mode and work on every file. For an unsupported variant, the decoder returns an error naming the eccodes-style packingType, so you can match it against eccodes' definitions.

BDS packing eccodes packingType Decode
Simple grid-point packing grid_simple ✅
Constant field (bits_per_value = 0) grid_simple ✅
Second-order, no SPD grid_second_order_no_SPD ✅
Second-order, SPD-1 grid_second_order_SPD1 ✅
Second-order, SPD-2 (ECMWF default) grid_second_order ✅
Second-order, SPD-3 grid_second_order_SPD3 ✅
Second-order, row-by-row grid_second_order_row_by_row ✅
Second-order, constant width grid_second_order_constant_width ✅
Second-order, general (legacy) grid_second_order_general_grib1 ✅
IEEE 754 raw floats grid_ieee ✅
Matrix-of-values (scalar form) grid_simple_matrix ✅
Matrix-of-values (true matrix) grid_simple_matrix ✅
Spherical-harmonic coefficients spectral_simple / spectral_complex ✅ Decodes + renders
JPEG 2000 / PNG grid_jpeg / grid_png ❌ n/a
Notes on individual templates

Simple grid-point packing. The bulk of CMC, NCEP non-operational data, and pygrib sample sets.

Constant field (bits_per_value = 0). Special case of simple packing.

Second-order, no SPD. Decodes via the shared general-extended path (order 0). Checked against a hand-built oracle fixture, since no encoder produces this variant.

Second-order, SPD-1. Decodes via the shared general-extended path (order 1). Checked against a hand-built oracle fixture.

Second-order, SPD-2 (ECMWF default). Most common in ECMWF MARS-derived files.

Second-order, SPD-3. Checked against an independent decode (re-encoded from the SPD-2 fixture), with and without boustrophedonic row ordering.

Second-order, row-by-row. Classic WMO layout: one group per row, per-row widths, no SPD. Checked against a hand-built oracle fixture.

Second-order, constant width. Classic WMO layout: an explicit secondary bitmap and a single shared width. Checked against a hand-built oracle fixture.

Second-order, general (legacy). Classic WMO layout: secondary-bitmap-delimited variable-length groups and per-group widths. Checked against a hand-built oracle fixture.

IEEE 754 raw floats. Values stored verbatim as big-endian floats; 32-bit (precision = 1) and 64-bit (precision = 2) both decode. The template also defines a 128-bit form (precision = 3), which does not.

Matrix-of-values (scalar form). matrixOfValues = 0: a simple-packed body behind the matrix sub-header (the simple-packed form of this template).

Matrix-of-values (true matrix). matrixOfValues = 1: an NR×NC matrix at every grid point, via secondary bitmaps. Decoded through Grib1Reader::decode_matrix_message (not a single 2-D field, so it bypasses the scalar path). No reference implementation encodes or decodes this variant, so it is validated against the published template definition and a hand-computed fixture rather than a decoded oracle.

Spherical-harmonic coefficients. IFS analyses. The spectral coefficients decode through Grib1Reader::decode_spectral_message (checked against an independent decode on a T63 field), and Grib1Reader::synthesize_spectral_message runs the inverse spherical-harmonic transform (the shared fieldglass-core::sht engine, validated against ECMWF's definitive spectral definition) to turn them back into a lat/lon grid, so the message renders in the viewer like any other field. Like the true matrixOfValues form, it has its own decode entry point and bypasses the scalar path.

JPEG 2000 / PNG. Not defined for GRIB1 edition 1; no encoder and no obtainable fixture. Common in GRIB2, tracked there.

GRIB2 packing modes

A GRIB2 message's Data Representation Section selects a packing template. Fieldglass decodes every registered template in Code Table 5.0 — in pure Rust, with no external libraries and no build flags. Decoding — and therefore 2-D rendering, reprojection, and overlays, all of which run on the decoded field, not the packing — works across the table below; metadata and format detection work on every message regardless.

DRS template eccodes packingType Decode
5.0 — simple grid-point grid_simple ✅
5.1 — matrix of values grid_simple_matrix ✅
5.4 — IEEE floating point grid_ieee ✅
5.2 — complex grid_complex ✅
5.3 — complex + spatial differencing grid_complex_spatial_differencing ✅
5.40 — JPEG 2000 grid_jpeg ✅
5.41 — PNG grid_png ✅
5.42 — CCSDS / AEC grid_ccsds ✅
5.200 — run-length grid_run_length ✅
5.61 — simple + log pre-processing grid_simple_log_preprocessing ✅
5.50002 — second-order grid_second_order ✅
5.50001 — second-order, no boustrophedonic grid_second_order_no_boustrophedonic ✅
5.50 — spectral (spherical harmonic) spectral_simple ✅ Decodes + renders
5.51 — spectral complex spectral_complex ✅ Decodes + renders
5.53 — bi-Fourier spectral bifourier_complex 🚧 Coefficients only
Notes on individual templates

Simple grid-point (5.0). The common case for NCEP / ECMWF fields. Constant fields (bits_per_value = 0) included.

Matrix of values (5.1). Experimental template for an NR×NC matrix at each grid point (e.g. 2-D wave spectra). With matrixBitmapsPresent = 0 the Section 7 holds one simple-packed value per grid point (NR/NC are descriptive metadata), decoded and rendered exactly like 5.0 — checked value for value against an independent decode. The true per-point matrix (matrixBitmapsPresent = 1, secondary bitmaps) decodes through Grib2Reader::decode_matrix_message following the GRIBEX interpretation. No reference implementation decodes that form, so it is validated against the independently-checked GRIB1 matrix decoder on the same hand-computed field.

IEEE floating point (5.4). Values stored verbatim as big-endian floats; 32-bit (precision = 1) and 64-bit (precision = 2). 128-bit (precision = 3) is unsupported.

Complex (5.2). Group-split packing, the GRIB2 analogue of GRIB1 second-order. Both splitting methods (general and row-by-row) and inline missing-value management (primary and primary + secondary substitutes) decode; substituted points come out masked, like bitmap points. Constant fields (zero groups) decode to the reference value.

Complex with spatial differencing (5.3). Complex packing with 1st- or 2nd-order spatial differencing (common in GFS; NBM adds inline missing values). Same envelope as 5.2, with the differencing recurrence skipping missing points. Constant fields (zero groups) decode to the reference value.

JPEG 2000 (5.40). The integer grid is wrapped in a JPEG 2000 codestream, decoded with the pure-Rust rust-j2k crate (no C / OpenJPEG binding, so the C-free cross-platform bundle is preserved — see codec strategy); the simple-packing R / E / D transform then applies.

PNG (5.41). The integer grid is wrapped in a PNG image (decoded with the pure-Rust png crate); the simple-packing R / E / D transform then applies.

CCSDS / AEC (5.42). The integer grid is wrapped in a CCSDS adaptive-entropy-coding (libaec-compatible) stream, decoded with the pure-Rust rust-aec crate; the simple-packing R / E / D transform then applies.

Run-length (5.200). Runs of quantised level indices resolved through a level → value table (JMA radar, rain-gauge analysis, and nowcasts). Level 0 marks missing; a run is a level code followed by base-range length digits. No R / E transform — only the decimal scale.

Simple with log pre-processing (5.61). Simple packing of the log-transformed field; decode is the simple-packing R / E / D value followed by Y = exp(X) − B, where B is the pre-processing parameter (B = 0 for a positive field). Experimental (WMO: bilateral tests only), no known operational producer — decoded for census completeness.

Second-order (5.50002). The GRIB1 general-extended second-order codec carried into GRIB2, sharing the spatial-predictor-differencing (SPD) inverse with the GRIB1 path. Section 5 holds the group-descriptor bit widths and SPD seeds; Section 7 holds the group widths, lengths, first-order references, and second-order offsets. Alternating-row (boustrophedonic) ordering is undone on decode. Checked against an independent decode, value for value.

Second-order, no boustrophedonic (5.50001). Same codec as 5.50002 without the secondOrderFlags octet, so ordering is never boustrophedonic. Checked against an independent decode, value for value.

Spectral (spherical harmonic) (5.50). The message carries spherical-harmonic coefficients (template 3.50), not values on a grid: the real (0,0) coefficient comes from Section 5 and the rest are simple-packed. They decode through Grib2Reader::decode_spectral_message (cross-validated against an independent decode on a T63 field, coefficient for coefficient), and the inverse spherical-harmonic transform (Grib2Reader::synthesize_spectral_message, validated against ECMWF's definitive spectral definition) turns them back into a lat/lon grid — an inverse spherical-harmonic transform — so the field renders in the viewer like any other, reprojecting and contouring through the normal pipeline.

Spectral complex (5.51). The ECMWF IFS form: coefficients up to a sub-truncation are stored as raw IEEE floats and the rest are simple-packed after Laplacian rescaling (n·(n+1))^P. Decodes through the same decode_spectral_message entry point (cross-validated against an independent decode, coefficient for coefficient on a T63 field), and renders through the same inverse transform as 5.50.

Bi-Fourier spectral (5.53). The ACCORD/ALADIN/AROME limited-area form (templates 3.61, 3.62 and 3.63): four coefficients per (i, j) wavenumber pair over a rectangle, ellipse, or diamond truncation. Coefficients inside the unpacked sub-truncation are raw IEEE floats (32- or 64-bit) and the rest are simple-packed after bi-Fourier Laplacian rescaling (i²+j²)^P. Decodes through Grib2Reader::decode_bifourier_message (cross-validated against an independent decode, coefficient for coefficient), with its own entry point like the spherical-harmonic forms; an inverse bi-Fourier transform to render it is still to come.

Two pre-standard local-use templates decode through the codecs above: 5.40000 (early NCEP JPEG 2000, identical to 5.40) and 5.40010 (early NCEP PNG, identical to 5.41). No reference implementation defines 5.40010, so there is no oracle to check it against directly: 5.40000 is checked against an independent decode, and 5.40010 against the 5.41 decode of the same codestream.

What it does, and where it stops

Each entry below describes something Fieldglass does and then says where it stops: which formats or grid types it covers, and what falls outside that. Read it as the detail behind the feature matrix rather than as a list of problems.

  • No metadata editing in the viewer. The Rust API has byte-level patching for the forecast period (P1) and the webview retains the full undo/redo wiring, but the editable affordance is hidden until general PDS-field editing lands. For now Fieldglass is a read-only viewer.
  • Reprojection targets. The projection picker offers eight targets: source (the grid in its native shape), equirectangular, Web Mercator, orthographic, polar stereographic, and three whole-world maps (Mollweide, Robinson, and Equal Earth), plus an optional coastline and lat/lon grid overlay. Most targets take a free-form center: orthographic recenters on any lon/lat, polar stereographic takes either pole with an arbitrary central meridian, and the three world maps take a central meridian to recenter the globe. GRIB1 lat/lon, Gaussian, rotated lat/lon, reduced (quasi-regular) lat/lon and Gaussian, Lambert, and polar stereographic grids, and the GRIB2 grids, listed here with the template number each one is defined under: rotated lat/lon (3.1), Mercator (3.10), transverse Mercator (3.12), polar stereographic (3.20), Lambert Conformal (3.30), regular and reduced Gaussian (3.40), Lambert azimuthal equal-area (3.140), and space-view perspective (3.90, geostationary), all reproject into the flat (lat/lon-box) targets.
  • Colormaps. The render panel picks from eight colormaps: five sequential (viridis, plasma, cividis, turbo, grayscale) and three diverging (red–blue, brown–teal, cool–warm), each of which can be reversed. Viridis is the default. Diverging maps suit anomaly and difference fields, where the midpoint is the value that matters. The colors are applied in Rust, and the legend strip is drawn from the same table that paints the grid. A Log₁₀ toggle switches from the default linear scaling to base-10 log, which spreads a field spanning orders of magnitude (precipitation, aerosol optical depth, chlorophyll) evenly across the ramp; non-positive values render as missing, and it needs a positive lower bound, so it is disabled until the range starts above zero.
  • Field compare (difference maps). The panel's Compare row picks a second field and an operation — A − B, B − A, A + B, mean(A, B), or A / B — and renders the combined field. The difference map is the classic way to read an anomaly, a bias, or the change between two time steps. For GRIB, Field B is another message in the file; for NetCDF, it is the same variable at a second slice (its own dimension steppers). Missing data in either field is missing in the result; the two fields must sit on the same grid. The combined field rides the normal pipeline, so projection, overlays, palette, and scaling all apply.
  • Point probe. Click anywhere on a rendered field to read the geographic point under the cursor and the decoded value and units there — ncview-style interrogation. The readout reproduces the render's own per-pixel map, so the number is exactly the one the color shows, on every projection (a click off the globe reports nothing). Works for GRIB and NetCDF.
  • Contour lines. A Contours toggle traces isolines of the field on the overlay canvas, following the map through every projection. Levels are chosen automatically (round 1/2/5 × 10ⁿ values) or by a manual interval, and break cleanly around missing data. A "contours only" mode hides the color raster so the lines read on a blank background. Isolines are extracted by marching squares in grid space and geolocated through the same forward map the reprojection uses, so they line up with the data. Available for the lat/lon family (regular lat/lon, Mercator, rotated lat/lon, regular and reduced Gaussian, reduced lat/lon) and the projected grids (Lambert conformal, polar stereographic, transverse Mercator, Lambert azimuthal equal-area); geostationary scan-angle grids say so rather than mis-drawing.
  • Map overlays. Five layers can be drawn over any rendered field: coastlines, country borders, lakes, rivers, and a latitude/longitude graticule. Each is toggled on its own and takes its own color and line weight, so a coastline and a border can be told apart at a glance. The vector layers are Natural Earth 1:110m data (public domain), bundled with the extension — nothing is fetched at run time. They are projected in Rust through the same forward map as the field, so an overlay can never drift from the image under it.
  • GRIB2: full parsing of all eight sections and value decode. .grb2 / .grib2 files enumerate messages and show edition, discipline, total length, originating centre, reference time, production status, data type, parameter name, level, forecast time, and grid geometry (templates 3.0 / 3.1 / 3.10 / 3.12 / 3.20 / 3.30 / 3.40 / 3.140 / 3.90 / 3.50 / 3.61–3.63 / 3.150: regular lat/lon, rotated lat/lon, Mercator, transverse Mercator, polar stereographic, Lambert Conformal, regular and reduced Gaussian, Lambert azimuthal equal-area, space-view perspective, spherical-harmonic coefficients, bi-Fourier spectral subdomains, and HEALPix). Value decoding is broken down per template in the GRIB2 packing modes table. Spherical-harmonic spectral fields additionally synthesize back to a lat/lon grid and render. HEALPix fields (template 3.150) — the equal-area sphere tiling ECMWF publishes its higher-resolution open data on — are resampled onto a lat/lon grid the same way, so they reproject, probe, contour and export like any other grid.
  • NetCDF-4 / HDF5: structure traversal and value decode (Rust API). Classic NetCDF (CDF-1 / CDF-2 / CDF-5) parses fully and renders dimensions, global attributes, and variables. For NetCDF-4 / HDF5 files, Fieldglass walks the HDF5 object-header tree — groups, datasets, dataspaces, datatypes, and attributes — resolves the dimension-scale convention into named dimensions and per-variable dimension lists, and surfaces the same dimensions / variables / global-attributes tables in the metadata viewer as the classic path. It also decodes a dataset's values through the Rust API: contiguous, compact, and chunked storage, and the deflate, shuffle, fletcher32, and zstd filters (fletcher32 is a checksum, verified on read; zstd is netcdf-c 4.9's recommended compressor for new climate archives, and decoding it in pure Rust exceeds a default netcdf-c install, which often has no working zstd plugin at runtime). Chunked storage covers the version-1 B-tree index and every version-4 index — single-chunk, fixed-array, extensible-array (the unlimited-dimension case), implicit, and v2-B-tree — each for both filtered and unfiltered chunks, along with the version-5 layout message that libhdf5 2.0 writes for a filtered variable. Decoded values are CF-unpacked: scale_factor / add_offset recover the real units, and _FillValue, missing_value, and valid_range mask the gaps. Variables on a regular lat/lon grid render a 2-D slice through the same picker and projection pipeline as the classic path, as do two projected layouts: WRF wrfout grids (Lambert, polar stereographic, Mercator, or unrotated lat-lon, from the MAP_PROJ global attributes) and GOES geostationary imagery (from a CF geostationary grid mapping). Variables in nested groups resolve too, with path-qualified names (/PRODUCT/qa_value) and dimensions looked up in ancestor groups, so grouped products such as Sentinel-5P and GPM IMERG list and render their variables. Curvilinear grids render as well: where a file gives a latitude and longitude for every cell instead of a projection — ocean model output on a tripolar mesh, satellite swaths geolocated per pixel — the cell positions themselves are the geometry, and reprojection, the point probe, contours and CSV export all work from them. Rotated WRF lat-lon domains are a follow-up.
  • Oblate-spheroid grids are projected on a sphere, except transverse Mercator. Lambert and polar-stereographic grids are placed on the Earth the message declares — GRIB1's earth-shape flag, GRIB2's shapeOfTheEarth, and WRF's own 6 370 000 m sphere — which matches an independent decode point for point for the spherical shapes that nearly all data uses. A grid that declares an oblate spheroid is projected on that spheroid's mean radius instead of the true ellipsoid, which is within about 0.1%. Transverse Mercator (template 3.12) and Lambert azimuthal equal-area (3.140) are the exceptions: both run on the declared spheroid, because the grids that use them declare one and the mean-radius approximation would place a UK-area field about 2.8 km out and a European one about 13.5 km out. True ellipsoidal projection for the other templates is a follow-up.
  • GRIB1 GDS coverage: Lat/Lon, Gaussian, polar stereographic, Lambert Conformal, rotated/oblique lat/lon (grid_type 10), and reduced (quasi-regular) lat/lon and Gaussian grids are parsed and reproject; GDS-absent messages resolve the common predefined global grids (ON388 Table B grids 2, 3, 4). Stretched grids, oblique Mercator, and other predefined-grid numbers are not yet supported and render as unsupported.
  • Parameter table coverage. GRIB2 ships the complete WMO Code Table 4.2 — 1,387 parameters across all 60 discipline/category tables — generated from a pinned wmo-im/GRIB2 release, with short names taken from the abbreviations NCEP publishes against the same triples. Codes above 191 are local, meaning whatever the originating centre says, so three centres' own tables ship alongside and resolve only for files from that centre: ECMWF (2,826 parameters, 13 of them gated on a declared local-table revision), DWD (213), and NCEP (479). GRIB1 carries WMO ON388 Table 2 (versions 1–3) plus ECMWF local tables 128 and 129 (centre 98), which cover the bulk of IFS / ERA5 fields; other centres' GRIB1 local tables still resolve as Unknown. Originating centres for both editions come from the WMO Common Code Tables — 309 for GRIB2, 239 for GRIB1 — with sub-centres resolved from the centre and sub-centre codes together.
  • Large files: the extension reads the whole file into memory via vscode.workspace.fs.readFile to keep remote/virtual workspaces working. Multi-GB GRIB archives are not the target use case yet.

Installation

The easiest way to install is from the VS Code Marketplace: search for "Fieldglass" in the Extensions view. To install a specific build by hand instead, download the .vsix from the releases page as described below.

Pre-built binaries for all supported platforms are bundled inside a single .vsix package. The extension selects the correct binary at runtime based on the host platform and architecture.

Supported platforms:

  • Linux x64 (glibc), Linux arm64 (glibc)
  • macOS x64, macOS arm64
  • Windows x64, Windows arm64

macOS

  1. Download the latest fieldglass-x.y.z.vsix from the releases page.
  2. Open VS Code, run "Extensions: Install from VSIX..." from the command palette, and select the downloaded file. Alternatively, from a terminal:
    code --install-extension fieldglass-x.y.z.vsix
    
  3. Reload the VS Code window.

Linux

  1. Download the latest fieldglass-x.y.z.vsix from the releases page.
  2. Open VS Code, run "Extensions: Install from VSIX..." from the command palette, and select the downloaded file. Alternatively, from a terminal:
    code --install-extension fieldglass-x.y.z.vsix
    
  3. Reload the VS Code window.

Windows

  1. Download the latest fieldglass-x.y.z.vsix from the releases page.
  2. Open VS Code, run "Extensions: Install from VSIX..." from the command palette, and select the downloaded file. Alternatively, from PowerShell or Command Prompt:
    code --install-extension fieldglass-x.y.z.vsix
    
  3. Reload the VS Code window.

Usage

Open any file with a supported extension. VS Code will use Fieldglass as the default editor and render a metadata table for each message in the file. To open an unrecognized file, right-click the file in the Explorer and choose "Open With...", then select "Fieldglass Viewer".

Development

Prerequisites

  • Rust (stable toolchain, edition 2024 — Rust 1.85 or newer)
  • Node.js 22 or newer
  • Python 3.10 or newer (only for the dev tooling — pre-commit, semgrep)
  • Visual Studio Code 1.85 or newer

Repository layout

Path Purpose
crates/fieldglass-core Format-agnostic traits and shared metadata types.
crates/fieldglass-grib1 GRIB1 parser, organized by section (is.rs, pds.rs, gds.rs, bds.rs) and WMO table lookups (tables.rs).
crates/fieldglass-grib2 GRIB2 reader — full §0–§7 parsing, message enumeration, and value decoding for the §5 packing templates listed in the GRIB2 packing modes table. Grid templates 3.0 / 3.1 / 3.10 / 3.12 / 3.20 / 3.30 / 3.40 / 3.140 / 3.90 / 3.50 / 3.61–3.63 / 3.150; product templates 4.0 / 4.8 / 4.11.
crates/fieldglass-netcdf NetCDF reader — full classic (CDF-1/2/5) header parser and value decode; HDF5 / NetCDF-4 object-tree traversal and dataset value decode (contiguous / compact / chunked, deflate + shuffle + fletcher32 + zstd).
crates/fieldglass-napi Node.js bindings exposed via napi-rs. The only crate that knows about Node.
extension/ TypeScript VS Code extension. Registers a custom read-only editor and renders a webview.

The four library crates (fieldglass-core, -grib1, -grib2, -netcdf) are published to crates.io on each stable release, so the readers can be used from Rust on their own, without the extension. fieldglass-napi is not published: it is a build artefact of the extension rather than a library to depend on.

Initial setup

git clone git@github.com:D0ubleD0uble/fieldglass.git
cd fieldglass
pipx install pre-commit              # or: pip install --user pre-commit
npm install                          # installs @napi-rs/cli and activates git hooks

The root npm install installs @napi-rs/cli (used to build the native module) and runs an npm prepare step that activates the repository's git hooks (see Pre-commit hooks below). If pre-commit isn't on PATH the prepare step prints a hint and continues; you can install it later and re-run npm install.

Building the native module

The compiled binary must be present in extension/bin/ for the extension to load. From the repository root:

cd crates/fieldglass-napi
npx napi build --platform --release --output-dir ../../extension/bin

This produces a file such as extension/bin/fieldglass.linux-x64-gnu.node along with extension/bin/index.d.ts. Repeat after changing any Rust code.

Building the extension

cd extension
npm install
npm run compile

For continuous compilation during development, run npm run watch instead.

Running the extension

Open the repository in VS Code and press F5. An Extension Development Host window will launch with Fieldglass loaded. Open any supported file in that window to test changes.

Tests

Run the full Rust test suite:

cargo test

Run tests for a single crate or a single test by name substring:

cargo test -p fieldglass-grib1
cargo test -p fieldglass-grib1 parse_pds

Extension integration tests

The TypeScript side has a small @vscode/test-electron suite that boots a real VS Code extension host and exercises the editor + render-panel paths end-to-end. The render-panel tests in particular round-trip a real gridReady payload through webview.postMessage so the wire format between the extension host and the webview is exercised under the same serializer as production. Coverage spans all three primary file formats (GRIB1 / GRIB2 render, NetCDF metadata) plus a regression test for the Buffer-vs-Uint8Array wire-format constraint.

cd extension
npm test

Tests require a display; CI runs them under xvfb-run -a npm test.

eccodes reference snapshots

fieldglass-grib2 cross-checks every bundled GRIB2 fixture against the eccodes reference implementation via JSON snapshots. The snapshots live next to each fixture as tests/fixtures/{name}.grib2.eccodes.ref.json and are checked into git, so the test (crates/fieldglass-grib2/tests/eccodes_reference.rs) has zero runtime dependencies — it just compares our parser output to the stored JSON, one curated WMO key at a time.

You only need eccodes when regenerating the snapshots, typically after adding a fixture or upgrading the reference. Install it via your package manager (Debian/Ubuntu: apt install libeccodes-tools; macOS: brew install eccodes), then run:

python3 tools/regenerate-eccodes-snapshots.py

The script invokes grib_dump -j per fixture, filters down to the curated subset of keys in CURATED_KEYS, and rewrites each snapshot. The diff is human-reviewable in PRs. To grow coverage to a new WMO key, add it to both CURATED_KEYS (in the script) and the dispatch match in tests/eccodes_reference.rs::assert_message_matches, then re-run the script.

Linting

cargo clippy --all-targets --workspace -- -D warnings
cargo fmt --all -- --check

The fieldglass-napi crate also enables #![deny(clippy::all)], so warnings there are hard errors regardless.

Pre-commit hooks

The repository uses the pre-commit framework. Its config is at .pre-commit-config.yaml. The framework auto-fetches and isolates the pinned versions of shellcheck, actionlint, gitleaks, and semgrep, so you only need pre-commit itself plus the Rust + Node toolchains.

One-time setup (per clone):

pipx install pre-commit                # preferred — works everywhere
# or, on systems without pipx:
pip install --user pre-commit          # may need --break-system-packages on PEP 668 distros
# or, fully isolated venv:
python3 -m venv ~/.venvs/fieldglass && ~/.venvs/fieldglass/bin/pip install pre-commit

cd /path/to/fieldglass
npm install                            # auto-runs `pre-commit install --hook-type pre-commit --hook-type pre-push`

Optional but recommended (the hooks gracefully report-and-fail if missing):

cargo install --locked cargo-deny      # advisory / license / source policy

What runs:

Stage Hook What it does
pre-commit cargo fmt --check, cargo clippy -- -D warnings, tsc --noEmit Fast lints — usually under 3 s on incremental builds.
pre-commit check-yaml, check-json, check-toml, end-of-file-fixer, trailing-whitespace, check-merge-conflict, check-added-large-files File-hygiene polish.
pre-commit shellcheck, actionlint, gitleaks Lint shell scripts, GitHub Actions YAML, and scan staged diff for secrets.
pre-push cargo test --workspace, cargo deny check, npm audit --omit=dev, semgrep scan Slower correctness + security checks.

Bypass with git commit --no-verify / git push --no-verify when you really must — CI (below) runs the same checks at full strength regardless.

Continuous integration

The pre-commit job in .github/workflows/ci.yml installs the same toolchain (Rust, Node, Python, cargo-deny) and runs pre-commit run --all-files for both the commit and push stages — so local hooks and CI run exactly the same checks, no drift. A second job builds the native module and compiles the extension as a smoke test. Tagged versions matching v* trigger a release build that compiles the native module for all six supported targets, packages the .vsix, and publishes it to GitHub Releases.

Security and static analysis

Three additional Tier-1 scanners run on every push and PR (results land in the repo's Security tab):

  • Semgrep SAST in .github/workflows/semgrep.yml — pattern-based security rules across Rust + TypeScript (p/default, p/security-audit, p/owasp-top-ten, p/rust, p/typescript, p/secrets). The same scan also runs locally on git push.
  • CodeQL semantic analysis in .github/workflows/codeql.yml — JavaScript/TypeScript and Rust, with the security-extended query suite.
  • Dependabot in .github/dependabot.yml — weekly version + security update PRs across cargo, npm (extension + root), and github-actions.

The Semgrep and CodeQL workflows are gated on github.event.repository.visibility == 'public' because GitHub Code Scanning's SARIF upload requires either a public repo or GitHub Advanced Security. They sit dormant on private repos and self-activate the moment the repo is flipped public. Dependabot runs regardless of visibility.

Adding a new GRIB metadata field

The same flow applies to either edition — only the source section module differs.

  1. Parse the field in the relevant section module:
    • GRIB1 → crates/fieldglass-grib1/src/{is,pds,gds,bds}.rs
    • GRIB2 → crates/fieldglass-grib2/src/{is,ids,lus,gds,pds,drs,bms,ds}.rs
  2. Expose the value on the section struct.
  3. Populate it on MessageMeta in crates/fieldglass-napi/src/lib.rs (both open_grib1 and open_grib2 produce the same MessageMeta shape, so a new field usually lands in both paths).
  4. Add the corresponding camelCase field to the MessageMeta interface in extension/src/provider.ts.
  5. Render it in the webview table in the same file.

WMO ON388 / FM 92 lookup tables live in crates/fieldglass-grib1/src/tables.rs (GRIB1) and crates/fieldglass-grib2/src/tables.rs (GRIB2) — extend the tables there rather than hardcoding strings at the napi or TypeScript layer. Several tables are generated instead, and should be regenerated rather than edited by hand: ECMWF GRIB1 local parameters (128/129) from eccodes into tables_ecmwf.rs by tools/gen_ecmwf_tables.py; the WMO GRIB2 master tables (Code Tables 4.2, 4.4, 4.5) into tables_wmo.rs by tools/gen_wmo_grib2_tables.py from a pinned wmo-im/GRIB2 release; the originating-centre and sub-centre tables into tables_cct.rs (both format crates) and cct_tables.rs (core) by tools/gen_wmo_cct_tables.py from a pinned wmo-im/CCT release; the ECMWF and DWD GRIB2 local parameter tables into tables_ecmf.rs and tables_edzw.rs by tools/gen_localconcepts_tables.py from eccodes' localConcepts; the NCEP GRIB2 local parameter table into tables_ncep.rs by tools/gen_ncep_tables.py from a pinned wgrib2 release; and the short names for the WMO GRIB2 master parameters into tables_wmo_short.rs by tools/gen_wmo_short_names.py, from the centre-0 rows of that same wgrib2 release, because WMO publishes no abbreviations of its own. The napi-rs bindings automatically convert snake_case Rust field names to camelCase TypeScript fields.

License

Fieldglass is dual-licensed under either of:

  • MIT License (LICENSE-MIT or https://opensource.org/licenses/MIT)
  • Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Fieldglass by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.

Third-party data

The bundled colormaps are third-party color specifications, redistributed under their own terms:

  • This product includes color specifications and designs developed by Cynthia Brewer (https://colorbrewer.org/). The red–blue and brown–teal diverging maps are from ColorBrewer, © Cynthia Brewer, Mark Harrower and The Pennsylvania State University, licensed under the Apache License, Version 2.0.
  • The turbo colormap is © 2019 Google LLC (Anton Mikhailov), licensed under the Apache License, Version 2.0.
  • Viridis, plasma and cividis are from matplotlib and are released into the public domain (CC0) by their authors: Nathaniel J. Smith, Stéfan van der Walt and Eric Firing (viridis, plasma), and Jamie R. Nuñez, Christopher R. Anderton and Ryan S. Renslow (cividis).
  • The cool–warm diverging map is by Kenneth Moreland and is released into the public domain (CC0).

The coastline overlay is Natural Earth 1:110m physical coastline data, which is in the public domain.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft