Skip to content

Pre-release documentation

This describes Beacon 2.0.0-rc3, a release candidate. Behavior documented here may still change before 2.0.0 ships, and some of it is not in any released build yet. For the current stable release, see the 1.8.0 documentation.

CF decoding

Scientific NetCDF files follow the CF conventions. CF stores some values in an encoded form: a time as a count since an epoch, a temperature as a packed integer, a gap as a sentinel number. Beacon decodes these on read.

You get physical values, not raw stored integers. This page says exactly which attributes Beacon acts on, and which it ignores.

Summary

AttributeBeacon does
units with sinceDecodes to a nanosecond timestamp
calendarSelects Gregorian or Julian. Other calendars are an error
scale_factorMultiplies. Result is f64
add_offsetAdds. Result is f64
_FillValueBecomes SQL NULL
missing_valueBeacon ignores it. The value stays as stored
valid_min, valid_maxBeacon ignores them. The value stays as stored
standard_name, long_nameBeacon keeps each as a column. It applies neither

Every attribute also appears as its own column, decoded or not. See Attribute columns.

Time

A variable whose units attribute contains since is a CF time. Beacon converts the count to an absolute instant and returns an Arrow nanosecond timestamp.

text
double time(time) ;
    time:units = "days since 1950-01-01" ;
sql
SELECT time FROM read_netcdf('argo/*.nc') LIMIT 3;
time
2024-01-01 00:00:00
2024-01-01 12:00:00
2024-01-02 00:00:00

The column is a real timestamp. Ordinary SQL date functions work on it:

sql
SELECT date_trunc('month', time) AS month, avg(temperature)
FROM read_netcdf('argo/**/*.nc')
GROUP BY month
ORDER BY month;

Supported units

nanoseconds, microseconds, milliseconds, seconds, minutes, hours, days, weeks. Each one takes the form <unit> since <date>.

Accepted reference dates:

text
seconds since 1970-01-01
days since 1950-01-01T00:00:00
days since -4713-01-01T00:00:00Z

Supported calendars

calendar valueResult
absentGregorian. This is the CF default
gregorian, standard, proleptic_gregorianGregorian
julianJulian
noleap, 365_day, 360_day, all_leap, 366_dayError

Names match case-insensitively.

Non-standard calendars are not supported

Climate model output often uses noleap or 360_day. Beacon rejects those files. It does not return a wrong instant. Convert the time axis before you query. You can also remove the units attribute. Beacon then reads the variable as a plain number.

This is a known gap. Track it on GitHub.

Packed values: scale_factor and add_offset

CF packs a float into a small integer to save space. Beacon unpacks it:

text
decoded = raw * scale_factor + add_offset
text
short sst(time, lat, lon) ;
    sst:scale_factor = 0.01 ;
    sst:add_offset = 273.15 ;
    sst:_FillValue = -32768s ;

A raw 1210 becomes 285.25. The decoded column is f64, whatever the stored type was.

Either attribute works alone. Beacon multiplies a variable that has only scale_factor. It shifts a variable that has only add_offset.

Missing data: _FillValue

A cell equal to _FillValue becomes SQL NULL.

sql
SELECT count(*) AS rows, count(sst) AS present
FROM read_netcdf('sst/2024-01.nc');
rowspresent
64800006103122

count(*) counts every grid cell. count(sst) skips the nulls, so the difference is the fill count.

Filter them out where they get in the way:

sql
SELECT avg(sst) FROM read_netcdf('sst/*.nc') WHERE sst IS NOT NULL;

Aggregates such as avg already skip nulls, so the WHERE clause above changes nothing. It matters for count(*) and for joins.

Fill and packing together

A variable can be both packed and filled. Beacon then decodes the fill value with the same arithmetic before it compares. A packed sentinel maps to the decoded fill exactly. Beacon sets it to NULL. You do not unpack the sentinel yourself.

Fill and time together

A time variable can carry a _FillValue too. Beacon decodes that fill with the same time arithmetic as the data, then compares. A fill cell becomes NULL, not a date far outside the record. A _FillValue = -32768 on days since 1970-01-01 is a null, not 1880-03-15.

missing_value is not applied

CF also allows an older missing_value attribute. Beacon does not treat it as a null. Those cells keep their stored number. Handle it in SQL:

sql
SELECT * FROM read_netcdf('old/*.nc') WHERE sst != -999.0;

The value is visible as the sst.missing_value column, so you can check what it is first.

Names and other attributes

Beacon keeps standard_name, long_name, valid_min and valid_max as columns. It changes nothing else. It does not rename a column to its standard_name. It does not drop rows outside valid_min and valid_max.

To find variables by standard name, query the attribute column:

sql
SELECT DISTINCT "temperature.standard_name"
FROM read_netcdf('argo/**/*.nc')
LIMIT 0;

Zarr and Atlas

Zarr and Atlas go through the same decoding path as NetCDF. The attribute names and the rules above apply unchanged.

Next

Released under the AGPL-3.0 License.