carray-netcdf 1.0.13 → 2.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +79 -0
- data/CONTRIBUTING.md +75 -0
- data/LICENSE +21 -0
- data/README.md +108 -2
- data/bin/ncheader +223 -0
- data/bin/ncvar +160 -0
- data/carray-netcdf.gemspec +37 -19
- data/docs/00_introduction.md +72 -0
- data/docs/01_getting_started.md +114 -0
- data/docs/02_reading.md +216 -0
- data/docs/03_writing.md +418 -0
- data/docs/04_codec.md +373 -0
- data/docs/05_groups.md +200 -0
- data/docs/06_layer1.md +160 -0
- data/docs/07_cookbook.md +345 -0
- data/docs/08_cheatsheet.md +326 -0
- data/docs/09_cli.md +79 -0
- data/lib/carray/netcdf/codec.rb +536 -0
- data/lib/carray/netcdf/dim.rb +67 -0
- data/lib/carray/netcdf/file.rb +354 -0
- data/lib/carray/netcdf/group.rb +517 -0
- data/lib/carray/netcdf/var.rb +539 -0
- data/lib/carray/netcdf/version.rb +5 -0
- data/lib/carray/netcdf.rb +287 -0
- data/lib/carray-netcdf.rb +3 -1
- metadata +80 -25
- data/API.md +0 -270
- data/Rakefile +0 -12
- data/bin/ncdef2yaml +0 -60
- data/examples/test.nc +0 -0
- data/examples/test.rb +0 -41
- data/examples/time.rb +0 -11
- data/ext/extconf.rb +0 -11
- data/ext/rb_netcdflib.c +0 -2674
- data/lib/io/netcdf.rb +0 -848
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# API Cheatsheet
|
|
2
|
+
|
|
3
|
+
Tables of everything this gem gives you to call. Read [getting started](01_getting_started.md) first if you want narrative; this file is pure lookup.
|
|
4
|
+
|
|
5
|
+
Conventions:
|
|
6
|
+
|
|
7
|
+
- **L0** = the [netcdf](https://github.com/himotoyoshi/netcdf) gem (`::NetCDF`), a thin libnetcdf binding.
|
|
8
|
+
- **L1** = `CA::NetCDF` module functions; the L0 API with the array I/O calls overridden to speak CArray. Everything else passes through because L1 `include`s L0.
|
|
9
|
+
- **L2** = `CA::NetCDF::{File, Group, Var, Dim, Codec}` classes.
|
|
10
|
+
|
|
11
|
+
In every table, `→` is the return value.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## L0 — the netcdf gem
|
|
16
|
+
|
|
17
|
+
Layer 0 is the [netcdf](https://github.com/himotoyoshi/netcdf) gem itself: `::NetCDF`, one module function per libnetcdf function, plus the `NC_*` constants. Its own reference lists all of them, and is the place to look them up.
|
|
18
|
+
|
|
19
|
+
Reaching for L0 from here is the exception, not the rule. Anything it can do that layer 2 cannot is a gap worth reporting; the layers below are there to be built on, not to be called around.
|
|
20
|
+
|
|
21
|
+
## L1 — `CA::NetCDF`
|
|
22
|
+
|
|
23
|
+
The whole `::NetCDF` module is `include`d into `CA::NetCDF`, so everything from L0 above is callable — as `CA::NetCDF.nc_open(…)` or via mixin (`include CA::NetCDF` and call `nc_open(…)` bare). Only the array I/O calls are overridden to speak CArray.
|
|
24
|
+
|
|
25
|
+
### Overridden functions
|
|
26
|
+
|
|
27
|
+
| Call | Behavior |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `nc_get_var(ncid, varid, buffer=nil)` | Without buffer: L1 allocates a CArray of the variable's type (`NC_CHAR` → `:uint8`) and reads into it. With CArray buffer: read into it. |
|
|
30
|
+
| `nc_get_vara(ncid, varid, start, count, buffer=nil)` | Same, hyperslab. |
|
|
31
|
+
| `nc_get_vars(ncid, varid, start, count, stride, buffer=nil)` | Same, strided. |
|
|
32
|
+
| `nc_get_varm(ncid, varid, start, count, stride, imap, buffer=nil)` | Same, mapped. |
|
|
33
|
+
| `nc_put_var(ncid, varid, ca)` | Contiguous CArray passes through. Non-contiguous view → `.copy`. Masked → `ArgumentError` (mask/fill is L2). |
|
|
34
|
+
| `nc_put_vara(ncid, varid, start, count, ca)` | Same. |
|
|
35
|
+
| `nc_put_vars(ncid, varid, start, count, stride, ca)` | Same. |
|
|
36
|
+
| `nc_put_varm(ncid, varid, start, count, stride, imap, ca)` | Same. |
|
|
37
|
+
|
|
38
|
+
Everything else on `::NetCDF` (see [L0](#l0--the-netcdf-gem) above) is a passthrough. All the `NC_*` constants are visible via the include:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
CA::NetCDF::NC_DOUBLE # => 6
|
|
42
|
+
CA::NetCDF::NC_CLOBBER # => 0
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Two idioms
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
# call style
|
|
49
|
+
CA::NetCDF.nc_get_vara(ncid, varid, [0, 0], [3, 4]) # => CArray
|
|
50
|
+
|
|
51
|
+
# mixin style
|
|
52
|
+
class Reader
|
|
53
|
+
include CA::NetCDF
|
|
54
|
+
def read(ncid, varid) = nc_get_var(ncid, varid) # bare -> CArray
|
|
55
|
+
def define(ncid, ...) = nc_def_dim(ncid, "time", 100) # passthrough
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## L2 — `CA::NetCDF::File / Group / Var / Dim / Codec`
|
|
62
|
+
|
|
63
|
+
### `CA::NetCDF::File` (class methods)
|
|
64
|
+
|
|
65
|
+
| Call | → |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `File.open(path, mode = :read, codec: :auto)` | File; `mode` is `:read` / `:write` / `:append` |
|
|
68
|
+
| `File.open(path, ...) { \|nc\| ... }` | value of block; auto-closes on exit / exception |
|
|
69
|
+
| `File.create(path, codec: :auto, clobber: true, netcdf4: false, atomic: false, template: nil, dims: true, atts: true, vars: [], **var_defaults)` | File in define mode. See details below. |
|
|
70
|
+
| `File.create(path, ...) { \|nc\| ... }` | value of block; auto-closes. Under `atomic: true`: commits on successful block exit, aborts (unlinks tmp) on exception. Under `clobber: :skip` with existing target: block is not called; returns `nil`. |
|
|
71
|
+
|
|
72
|
+
Kwargs on `File.create`:
|
|
73
|
+
|
|
74
|
+
| Kwarg | Meaning |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `codec:` | codec dispatch (`:auto` / `:identity` / `:cf` / `[:cf, opts]` / ...) — see [codec.md](04_codec.md) |
|
|
77
|
+
| `clobber: true` (default) | overwrite an existing target file |
|
|
78
|
+
| `clobber: false` | raise `Errno::EEXIST` if target exists |
|
|
79
|
+
| `clobber: :skip` | if target exists, don't call the block, return `nil` (restartable loops) |
|
|
80
|
+
| `netcdf4: true` | create as NetCDF-4 (auto-inherited from `template:` if not set) |
|
|
81
|
+
| `atomic: true` | write to same-dir tmp; rename on successful close; unlink tmp on exception |
|
|
82
|
+
| `template: file_or_group` | inherit skeleton (dims + atts + format) from another open File/Group |
|
|
83
|
+
| `dims: true / false / [names]` | which template dims to copy (default all) |
|
|
84
|
+
| `atts: true / false / {overrides}` | which template atts to copy; Hash merges overrides |
|
|
85
|
+
| `vars: [names]` | template vars to fully copy (definition + bit-identical data) |
|
|
86
|
+
| `chunks: / deflate: / shuffle: / fletcher32: / endian: / no_fill:` | file-wide var defaults, applied to every `def_var` (per-var kwarg wins) |
|
|
87
|
+
|
|
88
|
+
`path` may be a Zarr URL (`"file:///data/t.zarr#mode=zarr,file"`) for `open` and `create` alike; `atomic:` and `clobber: false / :skip` then raise `ArgumentError`. See [Zarr stores](03_writing.md#zarr-stores).
|
|
89
|
+
|
|
90
|
+
### `File` instance (adds these to what `Group` provides)
|
|
91
|
+
|
|
92
|
+
| Call | → |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `#filename` | on-disk path |
|
|
95
|
+
| `#format` | `nc_inq_format(ncid)` |
|
|
96
|
+
| `#conventions` | tokenized `Conventions` attribute (`["CF-1.7", "ACDD-1.3"]`); `[]` if absent |
|
|
97
|
+
| `#codec` | resolved codec instance (`Codec::Identity` / `::CF` / `::COARDS`) |
|
|
98
|
+
| `#close` | close the file (idempotent). For an atomic-write File, renames tmp → filename here. |
|
|
99
|
+
| `#abort` | atomic-write only: close without renaming, unlink the tmp. Idempotent. |
|
|
100
|
+
| `#closed?` | Boolean |
|
|
101
|
+
| `#atomic?` | Boolean — is this an atomic-write File that has not yet committed? |
|
|
102
|
+
| `#tmp_path` | the tmp path an atomic write is going to (nil for regular / after commit) |
|
|
103
|
+
| `#var_defaults` | file-wide var option defaults (frozen Hash) |
|
|
104
|
+
| `#ensure_define_mode` / `#ensure_data_mode` | manual mode transitions (rarely needed) |
|
|
105
|
+
| `#__transaction__(define: true) { ... }` | run block in define or data mode |
|
|
106
|
+
|
|
107
|
+
### `Group` (base for `File`; both act as the root group in a NetCDF-4 file)
|
|
108
|
+
|
|
109
|
+
**Identity**
|
|
110
|
+
|
|
111
|
+
| Call | → |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `#ncid` | raw group ncid |
|
|
114
|
+
| `#file` | root `File` |
|
|
115
|
+
| `#parent` | parent `Group`, `nil` for root |
|
|
116
|
+
| `#name` | `"foo"` (or `"/"` for root) |
|
|
117
|
+
| `#path` | `"/foo/bar"` — hierarchy path |
|
|
118
|
+
| `#root?` | Boolean |
|
|
119
|
+
|
|
120
|
+
**Introspection (this group only)**
|
|
121
|
+
|
|
122
|
+
| Call | → |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `#dims` | `{String => Dim}` |
|
|
125
|
+
| `#vars` | `{String => Var}` |
|
|
126
|
+
| `#groups` | `{String => Group}` |
|
|
127
|
+
| `#atts` | `{String => value}` (frozen), this group's globals |
|
|
128
|
+
| `#dim(name)` / `#var(name)` / `#group(name)` / `#att(name)` | one lookup or `nil` |
|
|
129
|
+
| `#dim?(name)` / `#var?(name)` / `#group?(name)` | Boolean |
|
|
130
|
+
| `#resolve_dim(name)` | walk up ancestors — raises if not found |
|
|
131
|
+
| `#codec` | delegates to `file.codec` |
|
|
132
|
+
|
|
133
|
+
**Access**
|
|
134
|
+
|
|
135
|
+
| Call | → |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `#[](name)` | Var — accepts `"temp"`, `"foo/temp"`, `"/foo/temp"` |
|
|
138
|
+
| `#[]=(name, ca)` | whole-variable write |
|
|
139
|
+
| `#[]=(name, *index, ca)` | hyperslab write (delegates to `Var#[]=`) |
|
|
140
|
+
|
|
141
|
+
**Definition**
|
|
142
|
+
|
|
143
|
+
| Call | → |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `#def_dim(name, length)` | Dim; `length: :unlimited` or an integer |
|
|
146
|
+
| `#def_var(name, type, dims=[], **atts_or_opts)` | Var; type is a Symbol (`:float`, `:short`, …). Reserved kwargs (netcdf-4): `chunks:` / `deflate:` / `shuffle:` / `fletcher32:` / `endian:` / `no_fill:`. Every other kwarg is a variable attribute. |
|
|
147
|
+
| `#def_var(name, template: src_var, **overrides)` | copy a definition (type, dims, attributes) from another Var; `**overrides` merges/replaces atts. `type` and `dims:` can also override. |
|
|
148
|
+
| `#def_att(name, value)` | value; writes to this group's `NC_GLOBAL` |
|
|
149
|
+
| `#def_group(name)` | Group; creates a subgroup |
|
|
150
|
+
| `#copy_dim(dim_or_name, from: nil, name: nil)` | copy a dimension (unlimited status preserved); optionally rename |
|
|
151
|
+
| `#copy_var(var_or_name, from: nil, name: nil, **overrides)` | copy a variable's definition **AND** its data bit-identically (raw + layer-1 write, both codec-bypassed); optionally rename / override attrs |
|
|
152
|
+
|
|
153
|
+
### `Var`
|
|
154
|
+
|
|
155
|
+
**Identity + metadata**
|
|
156
|
+
|
|
157
|
+
| Call | → |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `#name` | variable name |
|
|
160
|
+
| `#shape` | Array of Integer (refreshed on every `[]`/`[]=`, so unlimited dim growth is visible) |
|
|
161
|
+
| `#ndim` | rank |
|
|
162
|
+
| `#nc_type` | raw NC_* integer |
|
|
163
|
+
| `#data_type` | CArray Symbol (`:float32`, `:int16`, …) |
|
|
164
|
+
| `#dims` | Array of Dim (with dim inheritance resolved) |
|
|
165
|
+
| `#atts` | `{String => value}` (frozen) |
|
|
166
|
+
| `#att(name)` | one attribute or `nil` |
|
|
167
|
+
| `#file` / `#group` | enclosing File / Group |
|
|
168
|
+
| `#codec` | delegates to `file.codec` |
|
|
169
|
+
|
|
170
|
+
**Read (`[]` — lazy hyperslab + decode)**
|
|
171
|
+
|
|
172
|
+
| Call | → |
|
|
173
|
+
|---|---|
|
|
174
|
+
| `#to_ca` | whole-var, decoded CArray |
|
|
175
|
+
| `#[]` | ≡ `#to_ca` |
|
|
176
|
+
| `#[i, j, k]` (all integers) | single decoded scalar (Numeric / UNDEF / CATime::Element) |
|
|
177
|
+
| `#[i, nil, ...]` | slab (integer + `nil`) — hyperslab read |
|
|
178
|
+
| `#[a..b, ...]` | range → hyperslab |
|
|
179
|
+
| `#[(a..b).step(s), ...]` | strided hyperslab |
|
|
180
|
+
| `#[bool_or_fancy]` | reads whole var + does CArray indexing on the result |
|
|
181
|
+
| `#raw` / `#raw(*idx)` | same indexing as `#[]`, but **bypasses the codec**; `v[...] == v.codec.decode(v, v.raw(...))` |
|
|
182
|
+
| `#method_missing(id, *)` | forwards to `to_ca.__send__(id, *)` — `var.mean`, `var.sum`, …; in-place methods (`fill`, `*!`, `*=`) raise |
|
|
183
|
+
|
|
184
|
+
**Write (`[]=`)**
|
|
185
|
+
|
|
186
|
+
| Call | → |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `#[]= (value)` | whole-var write (encoded) |
|
|
189
|
+
| `#[i, j, k]= scalar` | one cell (encoded through codec) |
|
|
190
|
+
| `#[i, nil, ...]= slab` | hyperslab write |
|
|
191
|
+
| `#[a..b, ...]= slab` | range hyperslab |
|
|
192
|
+
| `#def_att(name, value)` | attach an attribute |
|
|
193
|
+
|
|
194
|
+
### `Dim`
|
|
195
|
+
|
|
196
|
+
| Call | → |
|
|
197
|
+
|---|---|
|
|
198
|
+
| `#name` | dimension name |
|
|
199
|
+
| `#length` | current length |
|
|
200
|
+
| `#unlimited?` | Boolean |
|
|
201
|
+
| `#to_i` | raw dimid |
|
|
202
|
+
| `#group` | enclosing group |
|
|
203
|
+
| `#var` | coordinate variable of the same name (in the same group) or `nil` |
|
|
204
|
+
| `#to_ca` | coordinate values (`var.to_ca`) or `nil` |
|
|
205
|
+
|
|
206
|
+
### `Codec` — value transforms
|
|
207
|
+
|
|
208
|
+
**Resolver**
|
|
209
|
+
|
|
210
|
+
| Value passed as `codec:` | Resolves to |
|
|
211
|
+
|---|---|
|
|
212
|
+
| `:auto` (default) | Match by `Conventions` attribute → `Identity` fallback |
|
|
213
|
+
| `:identity`, `false`, `nil` | `Identity` |
|
|
214
|
+
| `:cf` | `Codec::CF.new` |
|
|
215
|
+
| `:coards` | `Codec::COARDS.new` |
|
|
216
|
+
| `[:cf, opts]` | `Codec::CF.new(**opts)` |
|
|
217
|
+
| `[:coards, opts]` | `Codec::COARDS.new(**opts)` |
|
|
218
|
+
| a stateless codec `Module` (e.g. `Codec::Identity`) | itself |
|
|
219
|
+
| any object with `#decode(var, ca)` and `#encode(var, ca)` | itself |
|
|
220
|
+
|
|
221
|
+
**Interface (what every codec must implement)**
|
|
222
|
+
|
|
223
|
+
| Class method | Purpose |
|
|
224
|
+
|---|---|
|
|
225
|
+
| `self.claims?(conventions_string)` | Should this codec claim a file whose `Conventions` attribute equals this string? (Only the string; deliberately no file-inspection hook — that would push the codec toward CDM Layer 2 territory.) |
|
|
226
|
+
| Instance methods | |
|
|
227
|
+
| `#decode(var, ca)` | raw on-disk → user-facing value (var-local: read `var.atts` only, never walk to `var.file`) |
|
|
228
|
+
| `#encode(var, ca)` | user-facing → raw on-disk (same contract) |
|
|
229
|
+
|
|
230
|
+
**Registration**
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
CA::NetCDF::Codec.register(MyCodec) # later registrations win on `applies?`
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Built-in: `Codec::Identity`** — module, stateless; `decode`/`encode` pass values through unchanged.
|
|
237
|
+
|
|
238
|
+
**Built-in: `Codec::CF`** (class)
|
|
239
|
+
|
|
240
|
+
| Call | Purpose |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `CF.new(mask_valid_range: false)` | Options |
|
|
243
|
+
| `CF.claims?(s)` | `"CF-*"`, `"CF "`, `"CF"` |
|
|
244
|
+
| `#mask_valid_range` | Boolean reader |
|
|
245
|
+
| `#decode(var, ca)` | fill → mask, (opt-in) valid_range → mask, unpack scale/offset, CF time → CATime |
|
|
246
|
+
| `#encode(var, ca)` | reverse (CATime → count, pack, mask → fill) |
|
|
247
|
+
|
|
248
|
+
CF attributes recognized (decode order):
|
|
249
|
+
|
|
250
|
+
| Attribute | Effect (decode → encode) |
|
|
251
|
+
|---|---|
|
|
252
|
+
| `_Unsigned` | signed on-disk → unsigned user (bit-reinterpret); reverse on write. Runs FIRST so fill/valid/scale see numerically-correct values. |
|
|
253
|
+
| `_FillValue`, `missing_value` | mask ↔ sentinel (in unsigned space if `_Unsigned` was set) |
|
|
254
|
+
| `valid_range` / `valid_min` / `valid_max` | (opt-in) out-of-range → UNDEF, same space as fill; encode not defined by CF |
|
|
255
|
+
| `scale_factor`, `add_offset` | `v = packed*sf + ao` ↔ `packed = round((v-ao)/sf)` (round only when var is integer) |
|
|
256
|
+
| `units: "<u> since <ref>"` + `calendar` | CATime ↔ count-since-ref int |
|
|
257
|
+
| `units` = anything else | raw (no UDUNITS conversion) |
|
|
258
|
+
|
|
259
|
+
Supported calendars: `standard`, `gregorian`, `proleptic_gregorian`, missing → CATime (proleptic Gregorian, UTC). Everything else (`noleap`, `360_day`, `julian`, …) warns once and returns raw values on read; write raises.
|
|
260
|
+
|
|
261
|
+
CF time unit words → CATime base:
|
|
262
|
+
|
|
263
|
+
| Word (+ common plural/abbrev) | CATime base |
|
|
264
|
+
|---|---|
|
|
265
|
+
| `year`, `years`, `yr`, `yrs`, `y` | `:Y` |
|
|
266
|
+
| `month`, `months`, `mon`, `mons` | `:M` |
|
|
267
|
+
| `day`, `days`, `d` | `:D` |
|
|
268
|
+
| `hour`, `hours`, `hr`, `hrs`, `h` | `:h` |
|
|
269
|
+
| `minute`, `minutes`, `min`, `mins` | `:m` |
|
|
270
|
+
| `second`, `seconds`, `sec`, `secs`, `s` | `:s` |
|
|
271
|
+
| `millisecond` … `msec` … `ms` | `:ms` |
|
|
272
|
+
| `microsecond` … `usec` … `us` | `:us` |
|
|
273
|
+
| `nanosecond` … `nsec` … `ns` | `:ns` |
|
|
274
|
+
|
|
275
|
+
An `<count> <base>` form like `"5 days since 2024-06-15"` is stored as CATime at the base (`:D`) with the count (`5`) folded into the numeric values — see [codec.md](04_codec.md#n-of-a-unit-intervals-5-days-since-).
|
|
276
|
+
|
|
277
|
+
**Built-in: `Codec::COARDS < CF`** — same transforms; `claims?` matches `"COARDS"` and `"COARDS/*"`.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Type-name conversion tables
|
|
282
|
+
|
|
283
|
+
**Symbol / String → nc_type** (accepted by `def_var`)
|
|
284
|
+
|
|
285
|
+
| Ruby type name (Symbol or String) | nc_type constant |
|
|
286
|
+
|---|---|
|
|
287
|
+
| `:byte`, `:int8` | `NC_BYTE` |
|
|
288
|
+
| `:ubyte`, `:uint8` | `NC_UBYTE` |
|
|
289
|
+
| `:char` | `NC_CHAR` |
|
|
290
|
+
| `:short`, `:int16` | `NC_SHORT` |
|
|
291
|
+
| `:ushort`, `:uint16` | `NC_USHORT` |
|
|
292
|
+
| `:int`, `:int32` | `NC_INT` |
|
|
293
|
+
| `:uint`, `:uint32` | `NC_UINT` |
|
|
294
|
+
| `:int64` | `NC_INT64` |
|
|
295
|
+
| `:uint64` | `NC_UINT64` |
|
|
296
|
+
| `:float`, `:float32` | `NC_FLOAT` |
|
|
297
|
+
| `:double`, `:float64` | `NC_DOUBLE` |
|
|
298
|
+
|
|
299
|
+
**nc_type → CArray data_type** (returned by `Var#data_type`)
|
|
300
|
+
|
|
301
|
+
| nc_type | data_type Symbol |
|
|
302
|
+
|---|---|
|
|
303
|
+
| `NC_BYTE` | `:int8` |
|
|
304
|
+
| `NC_UBYTE`, `NC_CHAR` | `:uint8` |
|
|
305
|
+
| `NC_SHORT` | `:int16` |
|
|
306
|
+
| `NC_USHORT` | `:uint16` |
|
|
307
|
+
| `NC_INT` | `:int32` |
|
|
308
|
+
| `NC_UINT` | `:uint32` |
|
|
309
|
+
| `NC_INT64` | `:int64` |
|
|
310
|
+
| `NC_UINT64` | `:uint64` |
|
|
311
|
+
| `NC_FLOAT` | `:float32` |
|
|
312
|
+
| `NC_DOUBLE` | `:float64` |
|
|
313
|
+
|
|
314
|
+
## Attribute name → typed writer
|
|
315
|
+
|
|
316
|
+
L2's `def_var(..., **atts)` writes these attribute names via the typed `nc_put_att_<type>` so libnetcdf accepts them (CF §2.5.1 requires them to match the variable's on-disk type):
|
|
317
|
+
|
|
318
|
+
| Attribute name | Written as |
|
|
319
|
+
|---|---|
|
|
320
|
+
| `_FillValue` | variable's on-disk type |
|
|
321
|
+
| `missing_value` | " |
|
|
322
|
+
| `valid_range` | " |
|
|
323
|
+
| `valid_min` | " |
|
|
324
|
+
| `valid_max` | " |
|
|
325
|
+
|
|
326
|
+
Everything else goes through the generic `nc_put_att`, which infers the type from the Ruby value.
|
data/docs/09_cli.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Command-line tools
|
|
2
|
+
|
|
3
|
+
The gem installs two executables. Both work on any NetCDF file, whoever wrote it, and both are thin scripts — `ncheader` over the netcdf gem's `nc_inq_*` calls, `ncvar` over layer 1, which reads into a CArray — so read them (`gem contents carray-netcdf`) if you want a worked example of walking a file.
|
|
4
|
+
|
|
5
|
+
## ncheader — what is in this file
|
|
6
|
+
|
|
7
|
+
Prints a file's header: dimensions, variables, attributes, groups, and user-defined types. Header only; no data values.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
ncheader FILE.nc # CDL, ncdump -h style (default)
|
|
11
|
+
ncheader -f json FILE.nc # the same tree as JSON
|
|
12
|
+
ncheader -f yaml FILE.nc # the same tree as YAML
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`-f` is `--format`, and `cdl` may be named explicitly.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
$ ncheader temp.nc
|
|
19
|
+
netcdf temp {
|
|
20
|
+
dimensions:
|
|
21
|
+
y = 3 ;
|
|
22
|
+
x = 4 ;
|
|
23
|
+
variables:
|
|
24
|
+
double temperature(y, x) ;
|
|
25
|
+
temperature:units = "K" ;
|
|
26
|
+
temperature:valid_range = 200.0, 350.0 ;
|
|
27
|
+
|
|
28
|
+
// global attributes:
|
|
29
|
+
:title = "example field" ;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The JSON and YAML forms are there for scripting — a schema check in CI, a diff between two files' structure, a generator that reads the header and writes something else.
|
|
34
|
+
|
|
35
|
+
## ncvar — get the values out
|
|
36
|
+
|
|
37
|
+
Streams one variable's data to stdout. The default output is **raw binary**: the variable's own type, the host's byte order, no header, no separators. That is the point of the tool — the values land in whatever reads binary.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
ncvar FILE.nc VAR # raw native-endian bytes
|
|
41
|
+
ncvar -f text FILE.nc VAR # one value per line
|
|
42
|
+
ncvar -f json FILE.nc VAR # {name, type, shape, start, data}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Every short flag has a long spelling: `--format`, `--start`, `--count`, `--stride`.
|
|
46
|
+
|
|
47
|
+
A hyperslab, zero-based like libnetcdf, with `-s` start, `-c` count, `-S` stride:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
ncvar -s 0,0 -c 10,20 FILE.nc temperature # a 10x20 corner
|
|
51
|
+
ncvar -s 0,0 -c 10,10 -S 1,2 FILE.nc temperature # every second column of it
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Omitted, `-s` is all zeros and `-c` is the rest of each dimension. `-c` counts the elements actually read, not the span they cover, so a stride of 2 goes with half the count — asking for 20 with `-S 1,2` reaches past the end and raises `NetCDF: Start+count exceeds dimension bound`.
|
|
55
|
+
|
|
56
|
+
A variable inside a group is named by its path:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
ncvar FILE.nc /observation/temp
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Piping the raw form is what it is for:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
$ ncvar temp.nc temperature | od -F -N 32 # -F reads doubles, -f floats
|
|
66
|
+
$ ncvar temp.nc temperature | python3 -c "import numpy,sys; print(numpy.frombuffer(sys.stdin.buffer.read()))"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# Ruby on the other end of the pipe
|
|
71
|
+
bytes = IO.popen(["ncvar", "temp.nc", "temperature"], &:read)
|
|
72
|
+
bytes.unpack("d*")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Since the stream carries no shape or type, take those from `ncheader` (or `-f json`, which includes both) when the reader needs them.
|
|
76
|
+
|
|
77
|
+
### Character and string variables
|
|
78
|
+
|
|
79
|
+
`NC_CHAR` is bytes, and comes out as bytes: raw mode writes them unchanged, `-f text` writes them as one string, `-f json` puts that string in `data`. `NC_STRING` is a sequence of separate strings, so `-f text` prints one per line, `-f json` keeps the nesting of the variable's shape, and raw mode joins them with a NUL — the one case where the raw stream needs a separator to be readable at all.
|