DroneInfo

United States

NOAA MRMS PrecipRate

How the US Multi-Radar Multi-Sensor precipitation rate product encodes absence, why a one-time GeoTIFF conversion brings a point read down to 113 ms, and what the -3 and -1 sentinels actually mean.

At a glance

Product
Multi-Radar Multi-Sensor PrecipRate_00.00
Format
GRIB2, packing template 5.41 (grid_png)
Grid
7000 x 3500 cells at 1 km
Cadence
New file about every 2 minutes
Coverage
CONUS, Alaska, Hawaii, Puerto Rico and Guam, gated with generous padding rather than an exact boundary
Access
Public S3 bucket noaa-mrms-pds over plain HTTPS, no authentication
File size
About 500 KB gzipped
Units
mm/h directly per NOAA's table; -3 and -1 are sentinels, not part of the rate scale
Licence
US government work, public domain

How it signals an absence

Three different facts, and only one of them is a zero.

On the wire these three collapse onto one field. A measurement is radarStatus: "available"; no coverage is radarStatus: "out-of-coverage"; and no measurement, a failed or empty attempt, is radarStatus: "temporarily-unavailable". That field describes the outcome of the whole routing pass, not the one network this page is about: NowcastRouter walks the registered national adapters in order and returns as soon as one reports a measurement, so a point this adapter reports as out-of-coverage can still come back available if a different adapter further down the order answers instead, and can come back temporarily-unavailable rather than out-of-coverage if any other adapter in the pass failed instead of declining. radarCoverage sits beside it: "ok" appears only together with available, and "model-fallback", meaning Open-Meteo's model nowcast answered instead, can appear with either out-of-coverage or temporarily-unavailable.

SignalUpstream meaningWe reportWhy
clean exit (0), empty stdoutPoint falls outside the raster extentout-of-coverageEmpty output on a clean exit is the out-of-grid signal itself, checked before any value is parsed. Permanent for the point.
-3No Coverageout-of-coverageThe point is outside the radar domain. Permanent, so there is nothing to retry.
-1MissingunavailableInside the domain but no valid measurement for this cell. Often present again on the next file.
0Measured zeromeasurementThe radar looked and saw nothing falling. The only absence that may be shown as 0 mm/h.
non-zero exit codeDecoder or file error (missing binary, corrupt file, spawn failure)unavailableKept apart from empty-stdout-on-a-clean-exit deliberately: a broken read is not the same fact as a confirmed-uncovered point.

What the product is

NOAA's National Severe Storms Laboratory publishes PrecipRate_00.00 as part of its Multi-Radar Multi-Sensor system: a quality-controlled, instantaneous precipitation rate built from roughly 150 NEXRAD radar stations plus satellite and model input, not the raw return of any single radar.

The "multi" matters: individual NEXRAD returns are quality-controlled and merged with satellite and numerical-model data before a rate reaches the grid, rather than each station answering for its own patch of sky. The facts below cover the grid, the cadence, and how to reach the file; this section is about what the number in each cell actually represents.

Three ways to have no rate

A cell in this product can answer in three different ways, and only one of them is a rate. A value of -3, No Coverage, means the point sits outside the radar domain: there is no sensor reach here, and there will not be on the next file either, so this answer is permanent. A value of -1, Missing, means the point is inside the domain but this particular file has no valid reading for that cell, often because of masking, and the next file two minutes later may well have one. A value of 0 means the radar looked and measured nothing falling: a real reading, and the only one of the three that may be reported as 0 mm/h.

Treating -3 or -1 as zero is the specific bug this reference exists to warn against: it reports a place radar cannot see, or a momentary gap in one it can, as confirmed dry weather. The two are not interchangeable either. -3 is worth giving up on. -1 is worth asking again in two minutes.

Two more facts sit below the GRIB2 values themselves, at the level of the read rather than the cell. A point entirely outside the raster has no cell to read at all: gdallocationinfo exits cleanly with empty output, the out-of-grid signal, permanent for the point. And a non-zero exit code, from a missing binary, a corrupt file or a failed spawn, is a different fact again: the read did not complete, which is not the same claim as a confirmed-empty cell or a confirmed-uncovered point.

Why the raw file costs 1900 ms a pixel

PrecipRate_00.00 packs its 24.5 million cells, 7000 by 3500, using GRIB2 packing template 5.41, known as grid_png. That packing has no random access: reading a single pixel means decompressing the entire array, every time, regardless of which decoder does the work. Measured on production on 2026-08-01, with the file already downloaded and sitting on disk, one point read against the raw file cost roughly 1900 ms through eccodes and roughly 1270 ms through GDAL. Neither number is what a request pays today; the next section is.

Neither decoder is at fault, and switching between them does not fix anything on its own. The packing itself has no way to hand back one value without unpacking all of them.

Converting once instead of every time

Rather than pay that cost on every request, the file cache converts each downloaded GRIB2 once into a tiled, DEFLATE-compressed GeoTIFF, at a cost of roughly 2600 ms per file. After conversion, a point read against the GeoTIFF costs roughly 113 ms, because tiling hands the reader a small block instead of the whole raster.

The encoding was chosen by measurement rather than by default. An uncompressed GeoTIFF of the same raster runs to 196 MB; DEFLATE compression alone brings that to 2.2 MB; DEFLATE with tiling brings it to 1.4 MB and reads fastest of the three. Tiling, not compression, is what makes the read cheap: without it, a point read still has to unpack a full 7000-cell-wide strip to reach one value. MRMS publishes a new file roughly every two minutes, and every request in that window reuses the same converted file, so the 2600 ms conversion cost is paid once and amortised across all of them.

A decoder requirement that got retired

The decoder behind the numbers above matters as much as the numbers. eccodes' grib_get, used for the 1900 ms figure, needs a build with PNG support enabled, -DENABLE_PNG=ON, because grid_pngis PNG-compressed data inside the GRIB2 container. Alpine's eccodes package does not have that support built in; Debian's libeccodes-tools does.

That requirement no longer applies to this adapter. Once point reads moved to gdallocationinfo against the converted GeoTIFF, and the one-time conversion to gdal_translate, both from the standard gdal-bin package, MRMS was the last thing in the API image still calling eccodes. The dependency, and the PNG requirement with it, came out. One difference to carry over if you build this yourself: GDAL takes the point as lon lat, the opposite of eccodes' lat lon.

Reading one point yourself

gdal_translate + gdallocationinfobash
# One-time conversion, run once per upstream file (a new one lands
# roughly every two minutes):
gdal_translate -q -of GTiff -co COMPRESS=DEFLATE -co TILED=YES \
  MRMS_PrecipRate_00.00.grib2 MRMS_PrecipRate_00.00.tif

# Point read against the converted file. GDAL takes the point as lon
# then lat, the opposite of eccodes' lat,lon.
gdallocationinfo -wgs84 -valonly MRMS_PrecipRate_00.00.tif -104.9903 39.7392
  • Reading the raw GRIB2 directly still works, but pays the full decompression cost described above on every request. The conversion is the fix, not an alternative to it.
  • Before this conversion step existed, extraction ran through eccodes' grib_get against the raw GRIB2, which needs a PNG-enabled build (-DENABLE_PNG=ON) to decode grid_png. Alpine's eccodes did not have that support, Debian's libeccodes-tools did. Once every read moved to GDAL, MRMS was the last consumer of eccodes in the API image, and the dependency was retired.

Or call the API instead

The same reading, normalised, with the source that answered named in the response. No key and no signup for the demo endpoint.

curlbash
curl "https://api.droneinfo.app/v1/demo/weather/point?lat=39.7392&lon=-104.9903"

Run it for Denver in the browser