netcdf 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,410 @@
1
+ # Reference
2
+
3
+ Every function this gem defines, grouped by subsystem. [Getting started](01_getting_started.md) is the place to begin if this is your first look; buffers have [their own page](02_buffers.md).
4
+
5
+ ## Contents
6
+
7
+ | Section | |
8
+ |---|---|
9
+ | [Conventions](#conventions) | What is Ruby about this binding |
10
+ | [File](#file) | Create, open, mode, inquiry, [in-memory files](#in-memory-files), [Zarr stores](#zarr-stores) |
11
+ | [Dimension](#dimension) | Define and inquire dimensions |
12
+ | [Variable](#variable) | Define and inquire variables, [scalar](#variable-scalar-io) and [array](#variable-array-io) I/O, [NC_STRING](#nc_string-variables), [NetCDF-4 options](#netcdf-4-per-variable-options) |
13
+ | [Attribute](#attribute) | Inquiry, [smart](#smart-generic-putget) and [typed](#typed-putget) put/get |
14
+ | [User-defined types](#user-defined-types-netcdf-4-only) | [Opaque](#opaque-fixed-size-raw-bytes), [vlen](#vlen-variable-length-sequence), [enum](#enum-named-integer-constants) |
15
+ | [Group](#group-netcdf-4-only) | NetCDF-4 group hierarchy |
16
+ | [Constants](#constants) | Every `NC_*` the gem defines |
17
+ | [Buffers](#buffers) | Pointer to the buffer page |
18
+ | [Error handling](#error-handling) | `NetCDF::Error` and its status |
19
+ | [Not yet implemented](#not-yet-implemented) | What is deliberately absent |
20
+
21
+ ## Conventions
22
+
23
+ - Every function is a module function on `NetCDF`, named as libnetcdf names it. A status code becomes a `NetCDF::Error`, output parameters become return values, and a lookup that finds nothing returns `nil`.
24
+ - `ncid`, `dimid`, `varid`, `attnum`, `grpid` are the integers libnetcdf uses, passed to and from these calls unchanged.
25
+ - Functions that take an `Array` of indices (`start`, `count`, `stride`, etc.) expect a Ruby `Array` of `Integer` whose length equals the variable's rank.
26
+ - Smart-typing generics (`nc_put_att`, `nc_get_att`, `nc_put_var1`, `nc_get_var1`, `nc_put_var{,a,s,m}`, `nc_get_var{,a,s,m}`) dispatch on the Ruby value type or the variable's `nc_type`. There are no typed Ruby variants for variables — Ruby's type system already covers the relevant cases. Attributes do expose typed variants because the put-call itself determines the on-disk attribute type.
27
+ - Buffer arguments for array I/O are MemoryView producers. A raw Ruby `String` is *not* accepted there; see [Buffers and MemoryView](02_buffers.md).
28
+ - `get` calls read into the buffer passed and return it; the gem allocates none of its own, and omitting the buffer raises `ArgumentError`. `NC_STRING` variables are the exception: they take no buffer and return a nested Ruby `Array<String>` (`char**` does not map to MemoryView).
29
+ - Array I/O releases the GVL around the underlying libnetcdf call. Every call is serialized on one process-wide lock, because libnetcdf is not thread-safe; see [the GVL](02_buffers.md#the-gvl).
30
+
31
+ ## File
32
+
33
+ ```
34
+ ncid = NetCDF.nc_create(path[, mode = NetCDF::NC_CLOBBER])
35
+ ncid = NetCDF.nc_open(path[, mode = NetCDF::NC_NOWRITE])
36
+ NetCDF.nc_close(ncid)
37
+ NetCDF.nc_abort(ncid) # discard a freshly created file
38
+ NetCDF.nc_sync(ncid)
39
+ NetCDF.nc_redef(ncid)
40
+ NetCDF.nc_enddef(ncid)
41
+
42
+ ndims, nvars, natts, unlimdim = NetCDF.nc_inq(ncid)
43
+ NetCDF.nc_inq_ndims(ncid) # => Integer
44
+ NetCDF.nc_inq_nvars(ncid) # => Integer
45
+ NetCDF.nc_inq_natts(ncid) # => Integer
46
+ NetCDF.nc_inq_unlimdim(ncid) # => Integer | nil
47
+
48
+ oldmode = NetCDF.nc_set_fill(ncid, mode)
49
+
50
+ path = NetCDF.nc_inq_path(ncid) # libnetcdf >= 4.1.2, => String
51
+ fmt = NetCDF.nc_inq_format(ncid) # => NC_FORMAT_*
52
+ fmt, mode = NetCDF.nc_inq_format_extended(ncid) # libnetcdf >= 4.2
53
+ vers = NetCDF.nc_inq_libvers # => String
54
+ name, size = NetCDF.nc_inq_type(ncid, xtype)
55
+ ```
56
+
57
+ ### In-memory files
58
+
59
+ Create and read NetCDF data without touching the filesystem. Requires libnetcdf built with the in-memory APIs (4.4+ for `nc_open_mem`, 4.6.2+ for `nc_create_mem` / `nc_close_memio`); each function is only defined when the underlying symbol was detected at build time.
60
+
61
+ ```
62
+ ncid = NetCDF.nc_open_mem(name, omode, bytes)
63
+ # name : identifying string (need not be a real path)
64
+ # omode : NC_NOWRITE, NC_WRITE, etc.
65
+ # bytes : Ruby String holding a full netCDF file image.
66
+ # libnetcdf reads the buffer in place, so the gem hands
67
+ # it a private copy (freed by nc_close / nc_abort /
68
+ # nc_close_memio); the String can be freed/modified
69
+ # after the call returns.
70
+
71
+ ncid = NetCDF.nc_create_mem(name, cmode[, initialsize = 0])
72
+ # cmode : NC_NETCDF4 etc. NC_DISKLESS/NC_INMEMORY are
73
+ # applied internally by libnetcdf.
74
+ # initialsize : starting buffer size in bytes (0 = libnetcdf default).
75
+
76
+ bytes = NetCDF.nc_close_memio(ncid)
77
+ # Closes a handle obtained from nc_create_mem and returns the
78
+ # on-disk image as a binary String. Do NOT also call nc_close
79
+ # on that handle. Handles obtained from nc_open_mem are closed
80
+ # with nc_close as usual.
81
+ ```
82
+
83
+ Open modes can be combined with diskless flags when the underlying libnetcdf supports them: `NC_DISKLESS`, `NC_PERSIST`, `NC_MMAP`, `NC_INMEMORY` (each defined only when present in the linked libnetcdf).
84
+
85
+ ### Zarr stores
86
+
87
+ libnetcdf 4.8 and later can read and write Zarr. A Zarr store is a directory, not one file. Each chunk of a variable is a file of its own, and the metadata is small JSON files. libnetcdf calls this support NCZarr.
88
+
89
+ The gem needs no extra functions for it. Pass a URL in place of a path, and say the format in the `#mode=` part:
90
+
91
+ ```
92
+ url = "file:///data/t.zarr#mode=zarr,file"
93
+ ncid = NetCDF.nc_create(url, NetCDF::NC_NETCDF4)
94
+ ncid = NetCDF.nc_open(url)
95
+
96
+ fmt, mode = NetCDF.nc_inq_format_extended(ncid) # fmt == NetCDF::NC_FORMATX_ZARR
97
+ ```
98
+
99
+ - `mode=zarr` reads and writes plain Zarr, the form other tools such as xarray use.
100
+ - `mode=nczarr` adds netCDF's own metadata, such as shared dimensions, to the store.
101
+ - `file` stores the data in a local directory. `s3` stores it in Amazon S3, when libnetcdf was built with S3 support.
102
+
103
+ The `#mode=` part is required. A plain path that ends in `.zarr` creates an ordinary HDF5 file.
104
+
105
+ This works only when libnetcdf was built with NCZarr. `nc-config --has-nczarr` says whether it was. Groups, attributes, chunking, compression, `NC_STRING` and array I/O work as for a netCDF-4 file. User-defined types do not: `nc_def_opaque`, for one, raises `NetCDF::Error`.
106
+
107
+ ## Dimension
108
+
109
+ ```
110
+ dimid = NetCDF.nc_def_dim(ncid, name, len) # len may be NC_UNLIMITED
111
+ dimid = NetCDF.nc_inq_dimid(ncid, name) # => Integer | nil
112
+ name, len = NetCDF.nc_inq_dim(ncid, dimid)
113
+ NetCDF.nc_inq_dimname(ncid, dimid) # => String
114
+ NetCDF.nc_inq_dimlen(ncid, dimid) # => Integer
115
+ NetCDF.nc_rename_dim(ncid, dimid, newname)
116
+ ```
117
+
118
+ ## Variable
119
+
120
+ ```
121
+ varid = NetCDF.nc_def_var(ncid, name, xtype, dimids)
122
+ varid = NetCDF.nc_inq_varid(ncid, name) # => Integer | nil
123
+
124
+ name, xtype, ndims, dimids, natts = NetCDF.nc_inq_var(ncid, varid)
125
+ NetCDF.nc_inq_varname(ncid, varid)
126
+ NetCDF.nc_inq_vartype(ncid, varid)
127
+ NetCDF.nc_inq_varndims(ncid, varid)
128
+ NetCDF.nc_inq_vardimid(ncid, varid)
129
+ NetCDF.nc_inq_varnatts(ncid, varid)
130
+ NetCDF.nc_rename_var(ncid, varid, newname)
131
+ ```
132
+
133
+ ### Variable scalar I/O
134
+
135
+ ```
136
+ NetCDF.nc_put_var1(ncid, varid, index_array, value)
137
+ value = NetCDF.nc_get_var1(ncid, varid, index_array)
138
+ ```
139
+
140
+ `value` is `String` (for `NC_CHAR`), `Integer`, or `Float` depending on the variable's `nc_type`.
141
+
142
+ ### Variable array I/O
143
+
144
+ ```
145
+ NetCDF.nc_put_var (ncid, varid, buffer)
146
+ NetCDF.nc_put_vara(ncid, varid, start, count, buffer)
147
+ NetCDF.nc_put_vars(ncid, varid, start, count, stride, buffer)
148
+ NetCDF.nc_put_varm(ncid, varid, start, count, stride, imap, buffer)
149
+
150
+ NetCDF.nc_get_var (ncid, varid[, buffer])
151
+ NetCDF.nc_get_vara(ncid, varid, start, count[, buffer])
152
+ NetCDF.nc_get_vars(ncid, varid, start, count, stride[, buffer])
153
+ NetCDF.nc_get_varm(ncid, varid, start, count, stride, imap[, buffer])
154
+ ```
155
+
156
+ `buffer` is any object that implements the Ruby MemoryView protocol. It must be sized to hold the requested elements, must be writable on `get` calls, and must be row-major C-contiguous; [Buffers and MemoryView](02_buffers.md) has the rules and the errors they raise.
157
+
158
+ On `get`, the read fills `buffer` and returns it. The argument is written as optional only because `NC_STRING` variables take none and return a nested `Array<String>` instead (see below); for any other type, omitting it raises `ArgumentError: buffer is required`.
159
+
160
+ ### NC_STRING variables
161
+
162
+ `NC_STRING` is the one type whose data does not fit into a single `MemoryView`-backed block (each element is a separately allocated C string). `nc_get_var{,a,s,m}` therefore takes no buffer and returns a Ruby Array directly, and `nc_put_var{,a,s,m}` accepts a Ruby String (only when the requested element count is 1) or an Array of Strings. If the Array is nested, it is flattened internally and the flattened length must match the requested element count.
163
+
164
+ | variable rank | `nc_get_*` returns | `nc_put_*` accepts |
165
+ |---------------|-------------------------|-----------------------------------|
166
+ | 0 (scalar) | `String` | `String` |
167
+ | 1 | flat `Array<String>` | `Array<String>` of correct length |
168
+ | n ≥ 2 | nested `Array` (n deep) | any nested form with matching flat length |
169
+
170
+ After a `get`, libnetcdf-allocated strings are released automatically.
171
+
172
+ ### NetCDF-4 per-variable options
173
+
174
+ ```
175
+ NetCDF.nc_def_var_fill(ncid, varid, no_fill_bool, fill_value_or_nil)
176
+ no_fill, fill = NetCDF.nc_inq_var_fill(ncid, varid)
177
+
178
+ NetCDF.nc_def_var_deflate(ncid, varid, shuffle_bool, deflate_bool, level)
179
+ shuffle, deflate, level = NetCDF.nc_inq_var_deflate(ncid, varid)
180
+
181
+ NetCDF.nc_def_var_chunking(ncid, varid, storage, chunksizes_or_nil)
182
+ storage, chunksizes = NetCDF.nc_inq_var_chunking(ncid, varid)
183
+
184
+ NetCDF.nc_def_var_fletcher32(ncid, varid, on_bool)
185
+ NetCDF.nc_inq_var_fletcher32(ncid, varid) # => true | false
186
+
187
+ NetCDF.nc_def_var_endian(ncid, varid, endian) # NC_ENDIAN_*
188
+ NetCDF.nc_inq_var_endian(ncid, varid) # => Integer
189
+
190
+ NetCDF.nc_def_var_filter(ncid, varid, filter_id, params_array) # libnetcdf >= 4.7
191
+ NetCDF.nc_inq_var_filter_info(ncid, varid, filter_id) # libnetcdf >= 4.7, => Array<Integer> | nil
192
+ NetCDF.nc_inq_var_filter_ids(ncid, varid) # libnetcdf >= 4.7, => Array<Integer>
193
+ NetCDF.nc_inq_filter_avail(ncid, filter_id) # libnetcdf >= 4.7, => true | false
194
+
195
+ NetCDF.nc_def_var_quantize(ncid, varid, mode, nsd) # libnetcdf >= 4.9
196
+ NetCDF.nc_inq_var_quantize(ncid, varid) # libnetcdf >= 4.9, => [mode, nsd]
197
+
198
+ NetCDF.nc_set_var_chunk_cache(ncid, varid, size, nelems, preemption)
199
+ NetCDF.nc_get_var_chunk_cache(ncid, varid) # => [size, nelems, preemption]
200
+ ```
201
+
202
+ Global chunk cache (process-wide HDF5 cache parameters):
203
+
204
+ ```
205
+ NetCDF.nc_set_chunk_cache(size, nelems, preemption)
206
+ NetCDF.nc_get_chunk_cache # => [size, nelems, preemption]
207
+ ```
208
+
209
+ ## Attribute
210
+
211
+ `varid` may be `NetCDF::NC_GLOBAL` for file- or group-level attributes.
212
+
213
+ ### Inquiry
214
+
215
+ ```
216
+ NetCDF.nc_inq_attname(ncid, varid, attnum) # => String
217
+ NetCDF.nc_inq_attid(ncid, varid, name) # => Integer | nil
218
+ xtype, len = NetCDF.nc_inq_att(ncid, varid, name)
219
+ NetCDF.nc_inq_atttype(ncid, varid, name)
220
+ NetCDF.nc_inq_attlen(ncid, varid, name)
221
+ NetCDF.nc_rename_att(ncid, varid, name, newname)
222
+ NetCDF.nc_del_att(ncid, varid, name)
223
+ NetCDF.nc_copy_att(src_ncid, src_varid, name, dst_ncid, dst_varid)
224
+ ```
225
+
226
+ ### Smart generic put/get
227
+
228
+ The Ruby value's class determines the on-disk type:
229
+
230
+ | Ruby | NetCDF type |
231
+ |-------------------------------|------------------|
232
+ | `String` | `NC_CHAR` |
233
+ | `Integer` fitting int32 | `NC_INT` |
234
+ | larger `Integer` fitting int64 | `NC_INT64` |
235
+ | `Integer` past 2**63 - 1, up to 2**64 - 1 | `NC_UINT64` |
236
+ | `Float` | `NC_DOUBLE` |
237
+ | `Array` of integers | the narrowest of the above that holds every element; an `Array` mixing a value past 2**63 - 1 with a negative one raises `RangeError` |
238
+ | `Array` with at least one float | `NC_DOUBLE` |
239
+
240
+ ```
241
+ NetCDF.nc_put_att(ncid, varid, name, value)
242
+ value = NetCDF.nc_get_att(ncid, varid, name)
243
+ ```
244
+
245
+ `nc_get_att` returns a single `String` / `Integer` / `Float` when the attribute length is 1, and an `Array` otherwise.
246
+
247
+ netCDF has no scalar attribute: `5` is stored as a length-1 attribute, the same as `[5]`, so both read back as `5`, and the shape you wrote cannot be recovered. Code that needs an `Array` whatever the length uses the typed gets (`nc_get_att_int` and friends), which always return one.
248
+
249
+ ### Typed put/get
250
+
251
+ Use these to commit to a specific on-disk attribute type. As in C, the
252
+ numeric puts take `xtype` — the type stored on disk — while the suffix names
253
+ the type of the values in memory, and libnetcdf converts between the two:
254
+ `nc_put_att_double(ncid, varid, "_FillValue", NC_FLOAT, -999.0)` stores an
255
+ `NC_FLOAT`, as a float variable's `_FillValue` has to be. A value the
256
+ suffix's C type cannot hold raises `RangeError`.
257
+
258
+ ```
259
+ NetCDF.nc_put_att_text (ncid, varid, name, string)
260
+ NetCDF.nc_put_att_schar (ncid, varid, name, xtype, value_or_array)
261
+ NetCDF.nc_put_att_uchar (ncid, varid, name, xtype, value_or_array)
262
+ NetCDF.nc_put_att_short (ncid, varid, name, xtype, value_or_array)
263
+ NetCDF.nc_put_att_ushort (ncid, varid, name, xtype, value_or_array)
264
+ NetCDF.nc_put_att_int (ncid, varid, name, xtype, value_or_array)
265
+ NetCDF.nc_put_att_uint (ncid, varid, name, xtype, value_or_array)
266
+ NetCDF.nc_put_att_longlong (ncid, varid, name, xtype, value_or_array)
267
+ NetCDF.nc_put_att_ulonglong(ncid, varid, name, xtype, value_or_array)
268
+ NetCDF.nc_put_att_float (ncid, varid, name, xtype, value_or_array)
269
+ NetCDF.nc_put_att_double (ncid, varid, name, xtype, value_or_array)
270
+ NetCDF.nc_put_att_string (ncid, varid, name, string_or_array_of_strings)
271
+
272
+ NetCDF.nc_get_att_text(ncid, varid, name) # => String
273
+ NetCDF.nc_get_att_<numeric>(ncid, varid, name) # => Array<Numeric>
274
+ NetCDF.nc_get_att_string(ncid, varid, name) # => Array<String>
275
+ ```
276
+
277
+ The smart `nc_put_att` always maps a plain Ruby `String` to `NC_CHAR`, matching the convention used by most NetCDF tooling. Use the typed `nc_put_att_string` to commit to `NC_STRING` instead. The smart `nc_get_att` returns the stored value as a single `String` when an `NC_STRING` attribute has length 1.
278
+
279
+ Strings read back — `NC_CHAR` and `NC_STRING` attributes, and `NC_CHAR` and `NC_STRING` values from `nc_get_var1` and the `NC_STRING` array gets — are tagged UTF-8, the encoding netCDF defines for `NC_STRING`. So are the names of groups, dimensions, variables, attributes and types. Strings are written as their bytes, without transcoding.
280
+
281
+ ## User-defined types (NetCDF-4 only)
282
+
283
+ ### Generic inquiry
284
+
285
+ ```
286
+ name, size, base_type, nfields, klass = NetCDF.nc_inq_user_type(ncid, typeid)
287
+ typeid = NetCDF.nc_inq_typeid(ncid, name) # => Integer | nil
288
+ NetCDF.nc_inq_typeids(ncid) # => Array<Integer>
289
+ ```
290
+
291
+ `klass` is one of `NC_VLEN`, `NC_OPAQUE`, `NC_ENUM`, `NC_COMPOUND`.
292
+
293
+ ### Opaque (fixed-size raw bytes)
294
+
295
+ ```
296
+ typeid = NetCDF.nc_def_opaque(ncid, size, name)
297
+ name, size = NetCDF.nc_inq_opaque(ncid, typeid)
298
+ ```
299
+
300
+ Single-element I/O (`nc_put_var1` / `nc_get_var1`) uses a Ruby String of exactly `opaque_size` bytes.
301
+
302
+ Array I/O (`nc_put_var{,a,s,m}` / `nc_get_var{,a,s,m}`) uses a MemoryView buffer holding `opaque_size × element_count` bytes, passed through untouched — libnetcdf already knows the element size, so the buffer's own `format` and `item_size` are ignored; any buffer of the right byte size will do:
303
+
304
+ ```ruby
305
+ t = NetCDF.nc_def_opaque(ncid, 4, "tag_t")
306
+ v = NetCDF.nc_def_var(ncid, "tags", t, [dimid])
307
+ NetCDF.nc_enddef(ncid)
308
+
309
+ src = CArray.uint8(12).load_binary("ABCDEFGHIJKL") # 12 bytes
310
+ NetCDF.nc_put_vara(ncid, v, [0], [3], src)
311
+
312
+ out = CArray.uint8(12)
313
+ NetCDF.nc_get_vara(ncid, v, [0], [3], out)
314
+ out.to_s # => "ABCDEFGHIJKL"
315
+ ```
316
+
317
+ Raw Ruby Strings are not accepted on the array I/O surface, the same as for any other type.
318
+
319
+ ### Vlen (variable-length sequence)
320
+
321
+ ```
322
+ typeid = NetCDF.nc_def_vlen(ncid, name, base_type)
323
+ name, datum_size, base_type = NetCDF.nc_inq_vlen(ncid, typeid)
324
+ ```
325
+
326
+ Type definition only — data I/O for vlen variables is not yet exposed.
327
+
328
+ ### Enum (named integer constants)
329
+
330
+ ```
331
+ typeid = NetCDF.nc_def_enum(ncid, base_type, name) # base_type: any integer nc_type
332
+ NetCDF.nc_insert_enum(ncid, typeid, "RED", 0)
333
+ NetCDF.nc_insert_enum(ncid, typeid, "GREEN", 1)
334
+
335
+ name, base, base_size, nmembers = NetCDF.nc_inq_enum(ncid, typeid)
336
+ name, value = NetCDF.nc_inq_enum_member(ncid, typeid, idx)
337
+ NetCDF.nc_inq_enum_ident(ncid, typeid, value) # => String | nil
338
+ ```
339
+
340
+ Variables of enum type are read and written as plain Integers — internally the gem uses libnetcdf's untyped scalar/array I/O to satisfy the enum type check. That I/O moves elements of the base type with no conversion, so an array buffer for an enum variable holds the base type: a formatted buffer must name it (`"i"` for an `NC_INT` base), and one naming another type raises `ArgumentError`. A formatless buffer is read as the base type.
341
+
342
+ ## Group (NetCDF-4 only)
343
+
344
+ ```
345
+ grpid = NetCDF.nc_def_grp(parent_ncid, name)
346
+ NetCDF.nc_rename_grp(grpid, name)
347
+
348
+ NetCDF.nc_inq_grps(ncid) # => Array<Integer>
349
+ NetCDF.nc_inq_grpname(ncid) # => String (leaf name)
350
+ NetCDF.nc_inq_grpname_full(ncid) # => String ("/foo/bar")
351
+ NetCDF.nc_inq_grp_parent(ncid) # => Integer | nil (root)
352
+ NetCDF.nc_inq_ncid(parent_ncid, name) # => Integer | nil
353
+ NetCDF.nc_inq_grp_full_ncid(ncid, path) # => Integer | nil
354
+ NetCDF.nc_inq_dimids(ncid[, include_parents])
355
+ NetCDF.nc_inq_varids(ncid)
356
+ ```
357
+
358
+ A group's `ncid` is interchangeable with the file's `ncid` for almost all calls — dimensions and variables defined under a group are local to it, but attributes can be reached with `NC_GLOBAL`.
359
+
360
+ ## Constants
361
+
362
+ Open / create modes: `NC_NOWRITE`, `NC_WRITE`, `NC_CLOBBER`, `NC_NOCLOBBER`, `NC_SHARE`, `NC_NETCDF4`, `NC_CLASSIC_MODEL`, `NC_64BIT_OFFSET`.
363
+
364
+ Diskless / in-memory modes (defined only when the linked libnetcdf provides them): `NC_DISKLESS`, `NC_PERSIST`, `NC_MMAP`, `NC_INMEMORY`.
365
+
366
+ Fill: `NC_FILL`, `NC_NOFILL`.
367
+
368
+ Globals: `NC_GLOBAL`, `NC_UNLIMITED`.
369
+
370
+ Types: `NC_BYTE`, `NC_CHAR`, `NC_SHORT`, `NC_INT`, `NC_FLOAT`, `NC_DOUBLE`, `NC_UBYTE`, `NC_USHORT`, `NC_UINT`, `NC_INT64`, `NC_UINT64`, `NC_STRING`.
371
+
372
+ Format codes (returned by `nc_inq_format`): `NC_FORMAT_CLASSIC`, `NC_FORMAT_64BIT_OFFSET`, `NC_FORMAT_64BIT_DATA`, `NC_FORMAT_NETCDF4`, `NC_FORMAT_NETCDF4_CLASSIC`.
373
+
374
+ Extended format codes (returned by `nc_inq_format_extended`): `NC_FORMATX_NC3`, `NC_FORMATX_NC_HDF5`, `NC_FORMATX_NC_HDF4`, `NC_FORMATX_PNETCDF`, `NC_FORMATX_DAP2`, `NC_FORMATX_DAP4`, `NC_FORMATX_UDF0`, `NC_FORMATX_UDF1`, `NC_FORMATX_ZARR`, `NC_FORMATX_UNDEFINED`.
375
+
376
+ Storage / chunking: `NC_CONTIGUOUS`, `NC_CHUNKED`, `NC_COMPACT`.
377
+
378
+ Fletcher32: `NC_NOCHECKSUM`, `NC_FLETCHER32`.
379
+
380
+ Endian: `NC_ENDIAN_NATIVE`, `NC_ENDIAN_LITTLE`, `NC_ENDIAN_BIG`.
381
+
382
+ Quantize modes: `NC_NOQUANTIZE`, `NC_QUANTIZE_BITGROOM`, `NC_QUANTIZE_GRANULARBR`, `NC_QUANTIZE_BITROUND`.
383
+
384
+ Limits: `NC_MAX_NAME`, `NC_MAX_VAR_DIMS`.
385
+
386
+ ## Buffers
387
+
388
+ Array I/O reads into and writes from MemoryView buffers the caller provides. What a buffer has to satisfy, how format strings are read, and which array libraries plug in are covered in [Buffers and MemoryView](02_buffers.md).
389
+
390
+ ## Error handling
391
+
392
+ Every libnetcdf failure becomes a `NetCDF::Error`. The numeric status is available via `#status`.
393
+
394
+ ```ruby
395
+ begin
396
+ NetCDF.nc_open("missing.nc")
397
+ rescue NetCDF::Error => e
398
+ puts e.message # libnetcdf's nc_strerror text
399
+ puts e.status # the original NC_E* code
400
+ end
401
+ ```
402
+
403
+ ## Not yet implemented
404
+
405
+ A short list of `libnetcdf` features that this gem does not yet expose.
406
+
407
+ - Compound types (`nc_def_compound`, `nc_insert_compound`, `nc_inq_compound*`).
408
+ - Vlen variable I/O (`nc_def_vlen` / `nc_inq_vlen` work, but reading and writing vlen variables is not yet wired through the I/O dispatcher).
409
+ - Parallel I/O (`nc_create_par`, `nc_open_par`, `nc_var_par_access`).
410
+ - DAP / pNetCDF backend tuning, log control (`nc_set_log_level`), and other rarely used knobs.
@@ -0,0 +1,61 @@
1
+ require "mkmf"
2
+
3
+ # Prefer nc-config when available, fall back to pkg-config, then bare detection.
4
+ nc_config = find_executable("nc-config")
5
+ if nc_config
6
+ $CFLAGS << " " << `#{nc_config} --cflags`.strip
7
+ $LDFLAGS << " " << `#{nc_config} --libs`.strip
8
+ have_library("netcdf") or abort "libnetcdf not linkable via nc-config"
9
+ elsif pkg_config("netcdf")
10
+ # pkg_config already populated CFLAGS/LDFLAGS
11
+ else
12
+ dir_config("netcdf")
13
+ have_library("netcdf", "nc_open") or abort "libnetcdf not found (install libnetcdf and its headers first)"
14
+ end
15
+
16
+ have_header("netcdf.h") or abort "netcdf.h not found"
17
+ have_header("ruby/memory_view.h") or abort "ruby/memory_view.h not found (Ruby >= 3.1 required)"
18
+ have_header("ruby/thread.h")
19
+
20
+ # Minimum supported libnetcdf is 4.1 (the first NetCDF-4 release that
21
+ # carried the chunk-cache API and stabilized the user-defined type
22
+ # surface). Everything that arrived after 4.1 is feature-detected, so
23
+ # the gem builds against any libnetcdf from 4.1 on.
24
+ if nc_config
25
+ version = `#{nc_config} --version`.strip.split.last
26
+ if version =~ /\A(\d+)\.(\d+)/
27
+ major, minor = $1.to_i, $2.to_i
28
+ if major < 4 || (major == 4 && minor < 1)
29
+ abort "libnetcdf >= 4.1 required (found #{version})"
30
+ end
31
+ end
32
+ end
33
+
34
+ # Feature detection for APIs that arrived after 4.1. Each successful
35
+ # probe defines HAVE_<NAME> which the C sources gate optional code on.
36
+ # netcdf_filter.h relies on macros declared in netcdf.h (EXTERNL), so
37
+ # both headers must be passed together for have_func to compile.
38
+ have_func("nc_inq_path", "netcdf.h") # 4.1.2+
39
+ have_func("nc_inq_format_extended", "netcdf.h") # 4.2+
40
+ have_func("nc_def_var_quantize", "netcdf.h") # 4.9+
41
+ have_func("nc_inq_var_quantize", "netcdf.h") # 4.9+
42
+
43
+ filter_hdrs = ["netcdf.h", "netcdf_filter.h"]
44
+ have_func("nc_def_var_filter", filter_hdrs) # 4.6+
45
+ have_func("nc_inq_var_filter_info", filter_hdrs) # 4.6+
46
+ have_func("nc_inq_var_filter_ids", filter_hdrs) # 4.6+
47
+ have_func("nc_inq_filter_avail", filter_hdrs) # 4.7+
48
+
49
+ # In-memory file APIs. nc_open_mem arrived in 4.4; nc_create_mem and
50
+ # nc_close_memio (with NC_memio) arrived in 4.6.2. netcdf_mem.h needs
51
+ # netcdf.h included first (it relies on EXTERNL from netcdf.h), so probe
52
+ # both headers together — mirrors the netcdf_filter.h pattern above.
53
+ mem_hdrs = ["netcdf.h", "netcdf_mem.h"]
54
+ have_header("netcdf_mem.h", "netcdf.h")
55
+ have_func("nc_open_mem", mem_hdrs) # 4.4+
56
+ have_func("nc_create_mem", mem_hdrs) # 4.6.2+
57
+ have_func("nc_close_memio", mem_hdrs) # 4.6.2+
58
+
59
+ $CFLAGS << " -Wall -Wextra -Wno-unused-parameter"
60
+
61
+ create_makefile("netcdf/netcdf")
@@ -0,0 +1,64 @@
1
+ /*
2
+ * Internal declarations shared between the netcdf gem's C sources.
3
+ * Not installed; only used at gem build time.
4
+ */
5
+ #ifndef NETCDF_INTERNAL_H
6
+ #define NETCDF_INTERNAL_H
7
+
8
+ #include <ruby.h>
9
+ #include <ruby/encoding.h>
10
+ #include <ruby/memory_view.h>
11
+ #include <ruby/thread.h>
12
+ #include <netcdf.h>
13
+ /* netcdf_filter.h needs EXTERNL from netcdf.h, so include order matters.
14
+ We pull it in whenever any filter API was detected at configure time. */
15
+ #if defined(HAVE_NC_DEF_VAR_FILTER) || \
16
+ defined(HAVE_NC_INQ_VAR_FILTER_INFO) || \
17
+ defined(HAVE_NC_INQ_VAR_FILTER_IDS) || \
18
+ defined(HAVE_NC_INQ_FILTER_AVAIL)
19
+ # include <netcdf_filter.h>
20
+ #endif
21
+ #ifdef HAVE_NETCDF_MEM_H
22
+ # include <netcdf_mem.h>
23
+ #endif
24
+ #include <limits.h>
25
+ #include <stdbool.h>
26
+ #include <string.h>
27
+
28
+ extern VALUE mNetCDF;
29
+ extern VALUE eError;
30
+
31
+ void nc_raise(int status) __attribute__((noreturn));
32
+
33
+ #define NC_CHECK(call) do { \
34
+ int _st = (call); \
35
+ if ( _st != NC_NOERR ) { \
36
+ nc_raise(_st); \
37
+ } \
38
+ } while ( 0 )
39
+
40
+ /* Common helpers (defined in netcdf.c). */
41
+ void ary_to_sizet(VALUE ary, int ndims, const char *role, size_t *out);
42
+ bool ary_to_ptrdiff(VALUE ary, int ndims, const char *role, ptrdiff_t *out);
43
+ ssize_t nc_xtype_size(nc_type xt);
44
+ void indices_to_c(int ncid, int varid, VALUE index_ary, size_t *out);
45
+
46
+ /* Range-checked integer conversions (defined in netcdf.c); each raises
47
+ RangeError for a value its C type cannot hold. */
48
+ signed char num_to_schar(VALUE v);
49
+ unsigned char num_to_uchar(VALUE v);
50
+ short num_to_short(VALUE v);
51
+ unsigned short num_to_ushort(VALUE v);
52
+ unsigned int num_to_uint(VALUE v);
53
+ unsigned long long num_to_ulonglong(VALUE v);
54
+
55
+ /* Submodule init functions, called by Init_netcdf. */
56
+ void init_nc_file(void);
57
+ void init_nc_dim(void);
58
+ void init_nc_var(void);
59
+ void init_nc_att(void);
60
+ void init_nc_io(void);
61
+ void init_nc_group(void);
62
+ void init_nc_types(void);
63
+
64
+ #endif