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
data/carray-netcdf.gemspec
CHANGED
|
@@ -1,23 +1,41 @@
|
|
|
1
|
+
Gem::Specification.new do |spec|
|
|
2
|
+
spec.name = "carray-netcdf"
|
|
3
|
+
spec.version = File.read(File.expand_path("lib/carray/netcdf/version.rb", __dir__))[/VERSION\s*=\s*"([^"]+)"/, 1]
|
|
4
|
+
spec.summary = "A NetCDF interface for CArray"
|
|
5
|
+
spec.description = <<~TEXT
|
|
6
|
+
A NetCDF interface for CArray, built on the netcdf gem. Data is
|
|
7
|
+
exchanged as CArray at every level, through Ruby's MemoryView protocol,
|
|
8
|
+
so libnetcdf works on the array's own memory.
|
|
1
9
|
|
|
2
|
-
|
|
3
|
-
|
|
10
|
+
Two layers do this. Layer 1 turns the netcdf gem's MemoryView buffers
|
|
11
|
+
into CArray, keeping the nc_* functions as they are. Layer 2 builds the
|
|
12
|
+
object-oriented interface on top, and returns data as the file's
|
|
13
|
+
conventions define it: _FillValue as a mask, scale_factor and add_offset
|
|
14
|
+
applied, CF time as CATime. Pure Ruby, on CArray 3.0.
|
|
15
|
+
TEXT
|
|
16
|
+
spec.authors = ["himotoyoshi"]
|
|
17
|
+
spec.email = ["himotoyoshi@users.noreply.github.com"]
|
|
18
|
+
spec.license = "MIT"
|
|
19
|
+
spec.homepage = "https://github.com/himotoyoshi/carray-netcdf"
|
|
4
20
|
|
|
5
|
-
|
|
6
|
-
Dir.glob("carray-*.gem"),
|
|
7
|
-
].flatten
|
|
21
|
+
spec.required_ruby_version = ">= 3.1.0"
|
|
8
22
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
spec.files = Dir[
|
|
24
|
+
"lib/**/*.rb",
|
|
25
|
+
"bin/*",
|
|
26
|
+
"README.md",
|
|
27
|
+
"CONTRIBUTING.md",
|
|
28
|
+
"CHANGELOG.md",
|
|
29
|
+
"docs/*.md",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"carray-netcdf.gemspec",
|
|
32
|
+
]
|
|
33
|
+
spec.require_paths = ["lib"]
|
|
34
|
+
spec.bindir = "bin"
|
|
35
|
+
spec.executables = %w[ncheader ncvar]
|
|
36
|
+
|
|
37
|
+
# A CArray is handed to libnetcdf as memory, so the ceiling is the next
|
|
38
|
+
# minor on both sides.
|
|
39
|
+
spec.add_dependency "carray", ">= 3.0.1"
|
|
40
|
+
spec.add_dependency "netcdf", ">= 0.1.0", "< 0.2"
|
|
23
41
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Introduction
|
|
2
|
+
|
|
3
|
+
carray-netcdf is a NetCDF interface for CArray, built on the [netcdf](https://github.com/himotoyoshi/netcdf) gem. This guide takes NetCDF and CArray as known, and covers what this gem adds between them.
|
|
4
|
+
|
|
5
|
+
## What is carray-netcdf
|
|
6
|
+
|
|
7
|
+
A NetCDF file holds arrays, and in Ruby the array is CArray. The purpose of this gem is to connect the two directly: open a file, get CArrays out of it, compute, and put CArrays back into a file that other NetCDF tools can read.
|
|
8
|
+
|
|
9
|
+
Two things stand between a file and a CArray you can compute on. The first is the values themselves. What a file stores is often not what you want to work with — a physical field packed into `short` with a `scale_factor`, missing values marked by a `_FillValue` sentinel, a time axis kept as numbers since an epoch. Reading those correctly means following the conventions the file declares, and the gem does it for you, so what comes back is a masked CArray of physical values and a CATime axis.
|
|
10
|
+
|
|
11
|
+
The second is size. Files are routinely larger than memory, so reading a variable has to mean reading a part of it. Here a variable is indexed the way a CArray is, and only the part you index is read.
|
|
12
|
+
|
|
13
|
+
## Two layers, one module
|
|
14
|
+
|
|
15
|
+
The netcdf gem is a literal translation of libnetcdf, and it passes data as MemoryView buffers. Two layers turn that into CArray, and both live in the module `CA::NetCDF`:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
netcdf (dependency) thin libnetcdf binding, MemoryView buffers
|
|
19
|
+
↑
|
|
20
|
+
CA::NetCDF
|
|
21
|
+
├─ layer 1 the nc_* functions, with CArray in and out
|
|
22
|
+
└─ layer 2 File / Group / Var / Dim, and the codecs
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Layer 1 keeps the `nc_*` functions as they are and only changes what they take and return: a `get` hands back a CArray, a `put` accepts one. Use it as module functions, or mix it into a class of your own when you want the C idiom with CArray at both ends.
|
|
26
|
+
|
|
27
|
+
Layer 2 is built on layer 1 and is the everyday surface: a file is an object, a variable is indexed like a CArray, and a codec decodes values on the way out.
|
|
28
|
+
|
|
29
|
+
## Convention in this guide
|
|
30
|
+
|
|
31
|
+
Every example assumes these two lines:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
require "carray-netcdf"
|
|
35
|
+
NC = CA::NetCDF
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requiring the gem loads CArray, the netcdf gem, and both layers.
|
|
39
|
+
|
|
40
|
+
## What the gem covers
|
|
41
|
+
|
|
42
|
+
Whole NetCDF files, read and written: dimensions including unlimited ones, variables of every type the netcdf gem binds, attributes on a variable or on the file, and NetCDF-4 groups. Values pass through a codec in both directions, so `_FillValue`, `scale_factor`, `add_offset` and CF time are handled on the way out and on the way in. A file can be created atomically, or with its schema copied from an existing file. A Zarr store is read and written through the same calls, given a URL in place of a path.
|
|
43
|
+
|
|
44
|
+
## Threads and Ractors
|
|
45
|
+
|
|
46
|
+
Array I/O releases the GVL while libnetcdf works, so a large read or write does not hold up other Ruby threads. Do not touch a CArray from another thread while it is being read into or written from.
|
|
47
|
+
|
|
48
|
+
libnetcdf itself is not thread-safe, so the netcdf gem lets one call at a time into it, across the whole process. Threads therefore do not make netCDF I/O faster, only the Ruby work around it. That lock covers single calls: a `File`, `Group` or `Var` keeps state of its own between calls, so share one between threads only under a lock of your own.
|
|
49
|
+
|
|
50
|
+
NetCDF works only from the main Ractor. Opening a file in any other Ractor raises. For netCDF I/O in parallel, use separate processes.
|
|
51
|
+
|
|
52
|
+
## Beyond this gem
|
|
53
|
+
|
|
54
|
+
Three things a CF file may carry are not turned into anything by this gem, and are left to the layer above it or to your own code.
|
|
55
|
+
|
|
56
|
+
- **The CF data model.** Coordinate systems, grid mappings, cell methods, standard names and ancillary variables are kept as plain attributes — `var.att("grid_mapping")` still works — but no coordinate objects are built from them. That belongs to a heavier layer, or to your own code.
|
|
57
|
+
- **Non-standard calendars** for CF time (`noleap`, `360_day`, `julian` and the rest). CATime is proleptic Gregorian, in UTC, so a file declaring another calendar warns once and its time coordinate comes back as the raw numbers.
|
|
58
|
+
- **Time finer than CATime's base tick.** A variable in `"hours since 2022-01-01"` decodes to whole hours. Declare a finer unit, such as `"seconds since ..."`, if you need sub-hour precision.
|
|
59
|
+
|
|
60
|
+
The [codec chapter](04_codec.md) states in full which CF attributes are honoured and which are passed through untouched.
|
|
61
|
+
|
|
62
|
+
## How this guide is arranged
|
|
63
|
+
|
|
64
|
+
- [Getting started](01_getting_started.md) — install, then read and write a first file
|
|
65
|
+
- [Reading files](02_reading.md) — opening a file, finding a variable, reading all or part of it
|
|
66
|
+
- [Writing files](03_writing.md) — declaring the schema, writing data, changing a file later
|
|
67
|
+
- [Codecs and CF conventions](04_codec.md) — what gets decoded, how a codec is chosen, writing your own
|
|
68
|
+
- [Groups](05_groups.md) — NetCDF-4 groups, and what they inherit
|
|
69
|
+
- [Layer 1](06_layer1.md) — the `nc_*` functions with CArray in and out
|
|
70
|
+
- [Cookbook](07_cookbook.md) — worked solutions to real tasks
|
|
71
|
+
- [Cheatsheet](08_cheatsheet.md) — every method and constant of the gem on one page
|
|
72
|
+
- [Command-line tools](09_cli.md) — `ncheader` and `ncvar`, which ship with the gem
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
libnetcdf and its headers have to be present first, because the [netcdf](https://github.com/himotoyoshi/netcdf) gem underneath is built against them:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
brew install netcdf # macOS
|
|
9
|
+
apt install libnetcdf-dev # Debian / Ubuntu
|
|
10
|
+
dnf install netcdf-devel # Fedora / RHEL
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
gem install carray-netcdf
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
That brings in CArray and the netcdf gem.
|
|
20
|
+
|
|
21
|
+
Requires:
|
|
22
|
+
|
|
23
|
+
- Ruby >= 3.1
|
|
24
|
+
- carray >= 3.0.1, < 3.1
|
|
25
|
+
- libnetcdf >= 4.1
|
|
26
|
+
|
|
27
|
+
## Loading
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
require "carray-netcdf"
|
|
31
|
+
|
|
32
|
+
NC = CA::NetCDF # optional short alias used throughout this guide
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Loading the gem pulls in carray, the netcdf gem, layer 1 (the `nc_*` functions), and layer 2 (`File`, `Group`, `Var`, `Dim`, `Codec`).
|
|
36
|
+
|
|
37
|
+
## Reading in three lines
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
NC::File.open("data.nc") do |nc|
|
|
41
|
+
data = nc["temperature"].to_ca # => CArray (already decoded via CF codec if applicable)
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- `NC::File.open` opens for reading by default. The block form auto-closes.
|
|
46
|
+
- `nc["temperature"]` returns a lazy `Var` handle — no I/O yet.
|
|
47
|
+
- `.to_ca` reads the whole variable and returns a `CArray`. If the file declares `Conventions: CF-*` and the variable has `_FillValue` / `scale_factor` / `add_offset` / CF time units, all of that is decoded automatically.
|
|
48
|
+
|
|
49
|
+
## Reading a slice
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
NC::File.open("data.nc") do |nc|
|
|
53
|
+
t = nc["temperature"]
|
|
54
|
+
t.shape # => e.g. [365, 73, 144]
|
|
55
|
+
t.dims.map(&:name) # => ["time", "lat", "lon"]
|
|
56
|
+
|
|
57
|
+
first_day = t[0, nil, nil] # 2-D CArray, shape [73, 144]
|
|
58
|
+
point = t[0, 0, 0] # scalar (Float, or CATime::Element for a time coord)
|
|
59
|
+
block = t[0..9, nil, nil] # first 10 days, 3-D
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The `[]` accepts anything `CArray.scan_index` accepts: integers, ranges, `nil` for a whole axis, arrays, boolean masks. Point-and-block indexing maps directly to `nc_get_var1` / `nc_get_vara` / `nc_get_vars` under the hood, so you only ever read what you ask for.
|
|
64
|
+
|
|
65
|
+
## Writing a file in ten lines
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
NC::File.create("out.nc") do |nc|
|
|
69
|
+
nc.def_dim("time", :unlimited)
|
|
70
|
+
nc.def_dim("lat", 73)
|
|
71
|
+
nc.def_dim("lon", 144)
|
|
72
|
+
|
|
73
|
+
nc.def_var("temp", :float, ["time", "lat", "lon"],
|
|
74
|
+
units: "K", _FillValue: -999.9)
|
|
75
|
+
|
|
76
|
+
nc.def_att("Conventions", "CF-1.7")
|
|
77
|
+
nc.def_att("title", "Demo file")
|
|
78
|
+
|
|
79
|
+
nc["temp"] = CArray.float32(1, 73, 144) { rand * 30 + 260 }
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- `File.create` opens the file in **define mode** and drops you into it.
|
|
84
|
+
- `def_dim` / `def_var` / `def_att` build the schema. Pass `:unlimited` (or the string `"unlimited"` in YAML-ish contexts) for an unlimited dimension. Types are Symbols: `:byte :short :int :int64 :float :double`, plus the CArray-style aliases (`:int32`, `:float32`, `:float64` …).
|
|
85
|
+
- Variable attributes go as keyword arguments to `def_var` (any Ruby-side Numeric / String / Array is fine). Attributes that CF requires to match the variable's type — `_FillValue`, `missing_value`, `valid_range`, `valid_min`, `valid_max` — are automatically written in the variable's type so libnetcdf doesn't reject them.
|
|
86
|
+
- `nc["temp"] = ca` is a whole-variable write. `nc["temp", 0, nil, nil] = ca` is a hyperslab write. `File` switches between define and data mode for you.
|
|
87
|
+
|
|
88
|
+
## The three access idioms
|
|
89
|
+
|
|
90
|
+
Everything below reads the same underlying data:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
NC::File.open("data.nc") do |nc|
|
|
94
|
+
# (1) full read, then work with a CArray
|
|
95
|
+
ca = nc["temp"].to_ca
|
|
96
|
+
ca.mean(0)
|
|
97
|
+
|
|
98
|
+
# (2) hyperslab read via CArray indexing on the Var
|
|
99
|
+
slab = nc["temp"][0..9, nil, nil]
|
|
100
|
+
|
|
101
|
+
# (3) treat the Var as if it were a CArray
|
|
102
|
+
# method_missing delegates to .to_ca — convenient for interactive work,
|
|
103
|
+
# but each call re-reads the whole variable, so cache if you loop.
|
|
104
|
+
nc["temp"].mean
|
|
105
|
+
end
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Where to go next
|
|
109
|
+
|
|
110
|
+
- [Reading files](02_reading.md) — the full `File` / `Var` / `Dim` surface, plus what codecs turn raw bytes into (masks, physical values, CATime).
|
|
111
|
+
- [Writing files](03_writing.md) — schema definition, hyperslab writes, packing, unlimited dimensions.
|
|
112
|
+
- [Codecs and CF conventions](04_codec.md) — what CF decoding actually does, how to opt in to `valid_range` masking, how to write custom codecs.
|
|
113
|
+
- [Groups](05_groups.md) — NetCDF-4 groups.
|
|
114
|
+
- [Cookbook](07_cookbook.md) — end-to-end scenarios pulled from real use cases.
|
data/docs/02_reading.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Reading files
|
|
2
|
+
|
|
3
|
+
A read goes through three steps, and only the last one touches the disk:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
nc = NC::File.open(path) # a file handle: its dimensions, variables and attributes
|
|
7
|
+
var = nc["temp"] # a variable handle — still no data read
|
|
8
|
+
ca = var[0, nil, nil] # this is the read: libnetcdf fills a CArray, the codec decodes it
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
A `Var` is a lazy handle. Holding one costs nothing, and the index you give it decides how much of the variable is read — a point, a slab, a strided subset, or the whole thing with `to_ca`. What comes back is a CArray, with the file's conventions already applied to it, so a packed variable arrives as physical values and its missing entries arrive as a mask.
|
|
12
|
+
|
|
13
|
+
Everything else on this page is detail around those three steps.
|
|
14
|
+
|
|
15
|
+
## Opening
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require "carray-netcdf"
|
|
19
|
+
NC = CA::NetCDF
|
|
20
|
+
|
|
21
|
+
# Block form (recommended — auto-closes on exit / exception)
|
|
22
|
+
NC::File.open("data.nc") do |nc|
|
|
23
|
+
# ...
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Explicit form
|
|
27
|
+
nc = NC::File.open("data.nc")
|
|
28
|
+
begin
|
|
29
|
+
# ...
|
|
30
|
+
ensure
|
|
31
|
+
nc.close
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`open` takes an optional mode:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
NC::File.open("data.nc") # :read (default)
|
|
39
|
+
NC::File.open("data.nc", :write) # open for modification (existing file)
|
|
40
|
+
NC::File.open("data.nc", :append) # alias of :write
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For creating a new file, see [Writing files](03_writing.md).
|
|
44
|
+
|
|
45
|
+
A Zarr store opens the same way, through a URL in place of the path: `NC::File.open("file:///data/t.zarr#mode=zarr,file")`. See [Zarr stores](03_writing.md#zarr-stores).
|
|
46
|
+
|
|
47
|
+
## Introspection
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
NC::File.open("data.nc") do |nc|
|
|
51
|
+
nc.filename # => "data.nc" (OS path)
|
|
52
|
+
nc.format # => "NC_FORMAT_CLASSIC" / "NC_FORMAT_NETCDF4" / ...
|
|
53
|
+
nc.path # => "/" (netcdf-4 group path — always "/" for the root)
|
|
54
|
+
|
|
55
|
+
nc.dims # => {"time" => Dim, "lat" => Dim, "lon" => Dim}
|
|
56
|
+
nc.vars # => {"temp" => Var, "time" => Var, "lat" => Var, "lon" => Var}
|
|
57
|
+
nc.atts # => {"Conventions" => "CF-1.7", "title" => "…", ...} (frozen)
|
|
58
|
+
|
|
59
|
+
nc.dim("time") # => Dim
|
|
60
|
+
nc.var("temp") # => Var
|
|
61
|
+
nc.att("title") # => "..." (or nil)
|
|
62
|
+
|
|
63
|
+
nc.dim?("time") # => true
|
|
64
|
+
nc.var?("bogus") # => false
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Names are Strings — always. `nc["temp"]` and `nc.var("temp")` work; Symbols are not coerced.
|
|
69
|
+
|
|
70
|
+
## The Var handle
|
|
71
|
+
|
|
72
|
+
`nc["temp"]` returns a `Var`. It is a **lazy handle**: no I/O happens until you index into it or call `to_ca`.
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
t = nc["temp"]
|
|
76
|
+
|
|
77
|
+
t.name # => "temp"
|
|
78
|
+
t.shape # => [365, 73, 144]
|
|
79
|
+
t.ndim # => 3
|
|
80
|
+
t.data_type # => :float32 (Symbol, matching CArray convention)
|
|
81
|
+
t.nc_type # => NetCDF::NC_FLOAT (the raw nc_type integer)
|
|
82
|
+
t.dims # => [Dim("time"), Dim("lat"), Dim("lon")]
|
|
83
|
+
t.atts # => {"units" => "K", "_FillValue" => -999.9, ...} (frozen)
|
|
84
|
+
t.att("units") # => "K"
|
|
85
|
+
|
|
86
|
+
t.file # => the enclosing File
|
|
87
|
+
t.group # => the enclosing Group (== File for a root-level variable)
|
|
88
|
+
t.codec # => the codec that will decode reads from this variable
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Whole-variable read
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
ca = nc["temp"].to_ca # equivalent to nc["temp"][]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Returns a CArray of shape `t.shape` and type `t.data_type` (or the codec's choice, e.g. Float64 after scale/offset, CATime after CF time decode). The codec has already run — `_FillValue` cells are `UNDEF`, physical values are unpacked, time coordinates are CATime.
|
|
98
|
+
|
|
99
|
+
## Hyperslab reads (`nc[var][i, j, k]`)
|
|
100
|
+
|
|
101
|
+
`Var#[]` accepts anything `CArray.scan_index` accepts:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
t = nc["temp"]
|
|
105
|
+
|
|
106
|
+
t[] # whole variable (same as t.to_ca)
|
|
107
|
+
t[0, nil, nil] # first time step (rank-2 CArray, shape [73, 144])
|
|
108
|
+
t[0] # first cell if 1-D, or a slab if the axis is size 1;
|
|
109
|
+
# for higher-rank vars, use explicit nil for the rest
|
|
110
|
+
t[0, 0, 0] # single element (scalar — Float / Integer / Element)
|
|
111
|
+
t[0..9, nil, nil] # first 10 time steps
|
|
112
|
+
t[(0..-1).step(2), nil, nil] # every other time step (netcdf stride under the hood)
|
|
113
|
+
t[[0, 5, 10], nil, nil] # not hyperslab-able (uses `nc_get_var` + CArray indexing)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- Point indices (`0, 0, 0`) go through `nc_get_var1` and return a **decoded scalar** — a `Float` after scale/offset, `UNDEF` for a fill cell, a `CATime::Element` for a CF time coordinate, or a plain Numeric if the variable has no codec transforms.
|
|
117
|
+
- Block indices with all-unit strides go through `nc_get_vara`; strided block indices go through `nc_get_vars`. `CArray.scan_index` treats a form like `[start, count, stride]` (an Array of three ints) as a strided block, so `t[[0, 5, 2], nil, nil]` is a *hyperslab*, not fancy indexing.
|
|
118
|
+
- Genuine fancy indexing — a boolean mask, a scatter/gather via an integer index array — falls back to reading the whole variable and indexing the resulting CArray. That's a correctness fallback, not a performance path; for large files, prefer hyperslab-shaped indices.
|
|
119
|
+
|
|
120
|
+
Scalar decoding examples:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
nc["temp"][0, 0, 0] # => 273.15 (Float, after scale/offset)
|
|
124
|
+
nc["temp"][0, 0, 0] # => UNDEF (if that cell held _FillValue)
|
|
125
|
+
nc["time"][0] # => #<CATime::Element 2022-01-01T00:00:00Z>
|
|
126
|
+
nc["time"][0].to_time # => 2022-01-01 00:00:00 UTC (Ruby Time)
|
|
127
|
+
nc["time"][0].to_date # => #<Date: 2022-01-01>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## `method_missing`: use a Var like a CArray
|
|
131
|
+
|
|
132
|
+
`Var` forwards unknown methods to `to_ca`, so anything you can call on a CArray, you can call on a Var directly:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
nc["temp"].mean # => the mean of the whole array
|
|
136
|
+
nc["temp"].max # => the max
|
|
137
|
+
nc["temp"].shape # (Var#shape is defined, so this is not method_missing)
|
|
138
|
+
nc["temp"].sum(0) # => reduction along axis 0
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
A method that changes its receiver — `fill`, `seq!`, `mask=`, anything ending in `!` or `=` — raises `NoMethodError` instead: it would change the fresh array `to_ca` returns, not the file. Write with `v[...] = ...`.
|
|
142
|
+
|
|
143
|
+
Each such call re-reads the whole variable. For repeated work, cache once:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
temp = nc["temp"].to_ca
|
|
147
|
+
temp.mean
|
|
148
|
+
temp.max
|
|
149
|
+
temp.sum(0)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Reading a subset into an existing buffer
|
|
153
|
+
|
|
154
|
+
For streaming big files a slab at a time, allocate one buffer once and reuse it. Layer 1's `nc_get_vara` accepts an explicit CArray as the last argument and reads into it — no allocation per slab:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
NC::File.open("big.nc") do |nc|
|
|
158
|
+
varid = ::NetCDF.nc_inq_varid(nc.ncid, "temp")
|
|
159
|
+
buf = CArray.float32(73, 144) # one time slice, reused
|
|
160
|
+
|
|
161
|
+
365.times do |k|
|
|
162
|
+
CA::NetCDF.nc_get_vara(nc.ncid, varid, [k, 0, 0], [1, 73, 144], buf)
|
|
163
|
+
# buf now holds day k; no allocation, no codec, raw physical bytes.
|
|
164
|
+
process(buf)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
This bypasses layer 2 (and the codec). Use it when you're staying at raw values on purpose and want zero-allocation streaming. See [layer1.md](06_layer1.md) for the full API.
|
|
170
|
+
|
|
171
|
+
## Dimensions
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
d = nc.dim("time")
|
|
175
|
+
|
|
176
|
+
d.name # => "time"
|
|
177
|
+
d.length # => 365
|
|
178
|
+
d.unlimited? # => true / false
|
|
179
|
+
d.to_i # => raw dimid (rarely needed)
|
|
180
|
+
|
|
181
|
+
# Bridge to the coordinate variable (a variable of the same name)
|
|
182
|
+
d.var # => Var, or nil if no matching variable exists
|
|
183
|
+
d.to_ca # => the coord values (nil if no coord variable)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Attributes
|
|
187
|
+
|
|
188
|
+
Attributes are surfaced as native Ruby values by the underlying netcdf gem:
|
|
189
|
+
|
|
190
|
+
- `NC_CHAR` / `NC_STRING` → `String`
|
|
191
|
+
- Numeric scalar (single-element) → `Integer` or `Float`
|
|
192
|
+
- Numeric multi-element → `CArray` (or Ruby `Array` for very short cases — the netcdf gem picks the natural shape)
|
|
193
|
+
|
|
194
|
+
Examples:
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
nc.att("Conventions") # => "CF-1.7"
|
|
198
|
+
nc.att("title") # => "Demo file"
|
|
199
|
+
nc["temp"].att("units") # => "K"
|
|
200
|
+
nc["temp"].att("_FillValue") # => -999.9 (Float; typed as the variable)
|
|
201
|
+
nc["temp"].att("valid_range") # => CArray([0.0, 400.0])
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
An unknown attribute returns `nil`. `nc.atts` returns the frozen hash of everything (global on `nc`, per-variable on `nc["temp"]`).
|
|
205
|
+
|
|
206
|
+
## Turning decoding off
|
|
207
|
+
|
|
208
|
+
If you want raw bytes — no `_FillValue` masking, no unpacking, no CATime — pass `codec: :identity`:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
NC::File.open("data.nc", codec: :identity) do |nc|
|
|
212
|
+
raw = nc["temp"].to_ca # packed short values, fill sentinel as-is, ...
|
|
213
|
+
end
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Codec selection is covered in detail in the [Codecs chapter](04_codec.md).
|