Variable Class

The Variable class represents NetCDF variables, which are multidimensional arrays with associated metadata (attributes). Variables are the primary way to store and access data in NetCDF files.

Properties

Basic Properties

1
2
3
4
5
6
readonly name: string              // Variable name
readonly datatype: string          // Data type ('f4', 'f8', 'i4', etc.)
readonly dimensions: string[]      // Array of dimension names
readonly shape: number[]          // Array of dimension sizes
readonly size: number             // Total number of elements
readonly ndims: number            // Number of dimensions

Convenience Attribute Properties

Common NetCDF attributes are exposed as properties for convenience:

1
2
3
4
5
6
units: string                     // Units attribute
long_name: string                // Long name attribute  
standard_name: string            // Standard name attribute (CF conventions)
_FillValue: number               // Fill value for missing data
scale_factor: number             // Scale factor for packed data
add_offset: number               // Offset for packed data

Example

1
2
3
4
5
6
const temp = dataset.variables.temperature;
console.log(temp.name);          // "temperature"
console.log(temp.datatype);      // "f4"
console.log(temp.dimensions);    // ["time", "lat", "lon"]
console.log(temp.shape);         // [12, 180, 360]
console.log(temp.units);         // "K"

Methods

Data Access

getValue()

1
async getValue(): Promise<Float64Array>

Read all variable data. Returns data as Float64Array regardless of the underlying NetCDF data type.

1
2
3
const data = await variable.getValue();
console.log('Data length:', data.length);
console.log('First value:', data[0]);

Note: Future versions will support:

  • Indexing and slicing: getValue([start], [count], [stride])
  • Type-specific arrays: getValueTyped() returning the appropriate TypedArray

setValue()

1
async setValue(data: Float64Array): Promise<void>

Write data to the variable. Data should be provided as Float64Array with length matching the variable’s total size.

1
2
3
4
5
6
7
8
// Create data array
const data = new Float64Array(variable.size);
for (let i = 0; i < data.length; i++) {
    data[i] = Math.sin(i * 0.1); // Example data
}

// Write to variable
await variable.setValue(data);

Important:

  • Data must be provided in C-order (row-major) layout
  • Array length must match variable.size
  • Data is automatically converted to the variable’s NetCDF data type

Attribute Operations

setAttr()

1
setAttr(name: string, value: any): void

Set a variable attribute.

1
2
3
variable.setAttr('units', 'meters per second');
variable.setAttr('valid_range', [0, 100]);
variable.setAttr('_FillValue', -9999.0);

getAttr()

1
getAttr(name: string): any

Get a variable attribute value.

1
2
const units = variable.getAttr('units');
const fillValue = variable.getAttr('_FillValue');

attrs()

1
attrs(): string[]

Get list of all attribute names for this variable.

1
2
const attrs = variable.attrs();
console.log('Variable attributes:', attrs);

Utility Methods

len()

1
__len__(): number

Get the size of the first (slowest-varying) dimension. Mimics Python’s len() function.

1
2
// For variable with shape [12, 180, 360]
console.log(variable.__len__()); // 12

Data Types and Conversion

netcdf4-wasm automatically handles data type conversion between JavaScript and NetCDF:

NetCDF → JavaScript

NetCDF Type JavaScript Read Type Description
NC_BYTE ('i1') Float64Array 8-bit signed integer
NC_UBYTE ('u1') Float64Array 8-bit unsigned integer
NC_SHORT ('i2') Float64Array 16-bit signed integer
NC_USHORT ('u2') Float64Array 16-bit unsigned integer
NC_INT ('i4') Float64Array 32-bit signed integer
NC_UINT ('u4') Float64Array 32-bit unsigned integer
NC_FLOAT ('f4') Float64Array 32-bit floating point
NC_DOUBLE ('f8') Float64Array 64-bit floating point

JavaScript → NetCDF

When writing data with setValue(), the Float64Array is automatically converted to the variable’s NetCDF data type:

1
2
3
4
5
6
// Variable created as 'f4' (32-bit float)
const temp = await dataset.createVariable('temperature', 'f4', ['time', 'lat', 'lon']);

// Data written as Float64Array but stored as 32-bit floats
const data = new Float64Array([273.15, 274.12, 275.33]);
await temp.setValue(data); // Automatically converted to f4 precision

Usage Examples

Creating and Writing Variables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
async function createVariables() {
    const nc = await Dataset('output.nc', 'w');
    
    try {
        // Create dimensions
        await nc.createDimension('time', 12);
        await nc.createDimension('lat', 180);
        await nc.createDimension('lon', 360);
        
        // Create temperature variable
        const temp = await nc.createVariable('temperature', 'f4', ['time', 'lat', 'lon']);
        
        // Set attributes using properties
        temp.units = 'K';
        temp.long_name = 'Air Temperature';
        temp.standard_name = 'air_temperature';
        temp._FillValue = -9999.0;
        
        // Set custom attributes
        temp.setAttr('valid_range', [200.0, 320.0]);
        temp.setAttr('coordinates', 'time lat lon');
        temp.setAttr('grid_mapping', 'crs');
        
        // Generate and write data
        const data = new Float64Array(12 * 180 * 360);
        let idx = 0;
        for (let t = 0; t < 12; t++) {
            for (let lat = 0; lat < 180; lat++) {
                for (let lon = 0; lon < 360; lon++) {
                    // Simple temperature model
                    const latDeg = -89.5 + lat;
                    const seasonal = Math.cos(t * Math.PI / 6); // Seasonal variation
                    const latitudinal = 288.15 - Math.abs(latDeg) * 0.5; // Cooler at poles
                    data[idx++] = latitudinal + seasonal * 10;
                }
            }
        }
        
        await temp.setValue(data);
        
        // Create coordinate variables
        const timeVar = await nc.createVariable('time', 'f8', ['time']);
        timeVar.units = 'months since 2023-01-01';
        timeVar.calendar = 'gregorian';
        
        const timeData = new Float64Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
        await timeVar.setValue(timeData);
        
    } finally {
        await nc.close();
    }
}

Reading and Analyzing Variables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
async function analyzeVariable() {
    const nc = await Dataset('data.nc', 'r');
    
    try {
        const temp = nc.variables.temperature;
        
        // Print variable information
        console.log('Variable Info:');
        console.log(`  Name: ${temp.name}`);
        console.log(`  Type: ${temp.datatype}`);
        console.log(`  Dimensions: ${temp.dimensions.join(', ')}`);
        console.log(`  Shape: [${temp.shape.join(', ')}]`);
        console.log(`  Total elements: ${temp.size}`);
        
        // Print attributes
        console.log('\nAttributes:');
        const attrs = temp.attrs();
        attrs.forEach(attr => {
            const value = temp.getAttr(attr);
            console.log(`  ${attr}: ${value}`);
        });
        
        // Quick access to common attributes
        console.log(`\nUnits: ${temp.units}`);
        console.log(`Long name: ${temp.long_name}`);
        console.log(`Fill value: ${temp._FillValue}`);
        
        // Read and analyze data
        console.log('\nReading data...');
        const data = await temp.getValue();
        
        // Calculate statistics
        const validData = data.filter(val => val !== temp._FillValue);
        const min = Math.min(...validData);
        const max = Math.max(...validData);
        const mean = validData.reduce((sum, val) => sum + val, 0) / validData.length;
        
        console.log('Statistics:');
        console.log(`  Valid points: ${validData.length} / ${data.length}`);
        console.log(`  Min: ${min.toFixed(2)}`);
        console.log(`  Max: ${max.toFixed(2)}`);
        console.log(`  Mean: ${mean.toFixed(2)}`);
        
    } finally {
        await nc.close();
    }
}

Working with Time Series Data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
async function timeSeriesExample() {
    const nc = await Dataset('timeseries.nc', 'w');
    
    try {
        // Create unlimited time dimension
        await nc.createDimension('time', null);
        await nc.createDimension('station', 5);
        
        // Create time coordinate
        const timeVar = await nc.createVariable('time', 'f8', ['time']);
        timeVar.units = 'hours since 2023-01-01 00:00:00';
        timeVar.calendar = 'gregorian';
        timeVar.long_name = 'time';
        
        // Create station data
        const stationVar = await nc.createVariable('station_id', 'i4', ['station']);
        stationVar.long_name = 'station identifier';
        
        // Create measurement variable
        const tempVar = await nc.createVariable('air_temperature', 'f4', ['time', 'station']);
        tempVar.units = 'degrees_Celsius';
        tempVar.standard_name = 'air_temperature';
        tempVar.long_name = 'Air Temperature';
        tempVar._FillValue = -999.0;
        tempVar.coordinates = 'time station';
        
        // Write station IDs
        const stationIds = new Float64Array([101, 102, 103, 104, 105]);
        await stationVar.setValue(stationIds);
        
        // Write 24 hours of hourly data
        const hours = 24;
        const stations = 5;
        
        // Time data (hours since start)
        const timeData = new Float64Array(hours);
        for (let h = 0; h < hours; h++) {
            timeData[h] = h;
        }
        await timeVar.setValue(timeData);
        
        // Temperature data with diurnal cycle
        const tempData = new Float64Array(hours * stations);
        let idx = 0;
        for (let h = 0; h < hours; h++) {
            const hourOfDay = h % 24;
            const diurnalTemp = 15 + 10 * Math.sin((hourOfDay - 6) * Math.PI / 12);
            
            for (let s = 0; s < stations; s++) {
                // Add station-specific offset and some noise
                const stationOffset = (s - 2) * 2; // -4 to +4 degrees
                const noise = (Math.random() - 0.5) * 2; // ±1 degree noise
                tempData[idx++] = diurnalTemp + stationOffset + noise;
            }
        }
        await tempVar.setValue(tempData);
        
        console.log(`Created time series with ${hours} time steps and ${stations} stations`);
        
    } finally {
        await nc.close();
    }
}

Variable Metadata Best Practices

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
async function metadataExample() {
    const nc = await Dataset('metadata_example.nc', 'w');
    
    try {
        await nc.createDimension('time', 100);
        await nc.createDimension('level', 10);
        
        // Example: Atmospheric pressure variable with comprehensive metadata
        const pressure = await nc.createVariable('air_pressure', 'f4', ['time', 'level']);
        
        // CF convention attributes
        pressure.standard_name = 'air_pressure';
        pressure.long_name = 'Atmospheric Pressure';
        pressure.units = 'Pa';
        
        // Data quality attributes
        pressure._FillValue = -9999.0;
        pressure.setAttr('valid_min', 50000.0);
        pressure.setAttr('valid_max', 110000.0);
        pressure.setAttr('accuracy', 100.0); // ±100 Pa
        
        // Grid and coordinate attributes
        pressure.setAttr('coordinates', 'time level latitude longitude');
        pressure.setAttr('grid_mapping', 'crs');
        
        // Processing attributes
        pressure.setAttr('source', 'Numerical Weather Prediction Model');
        pressure.setAttr('processing_level', '2');
        pressure.setAttr('cell_methods', 'time: mean level: point');
        
        // Custom application attributes
        pressure.setAttr('sensor_type', 'pressure_sensor_v2.1');
        pressure.setAttr('calibration_date', '2023-01-15');
        pressure.setAttr('data_version', '1.2.3');
        
        console.log('Created variable with comprehensive metadata');
        console.log('Attributes:', pressure.attrs());
        
    } finally {
        await nc.close();
    }
}

Performance Considerations

Memory Usage

  • getValue() loads all variable data into memory
  • For large variables, consider the memory requirements
  • Future versions will support chunked reading for large datasets

Data Layout

NetCDF uses C-order (row-major) layout for multidimensional arrays:

1
2
3
4
5
6
7
8
9
10
// For a variable with dimensions [time, lat, lon] and shape [2, 3, 4]
// The data array index mapping is:
// data[t * (lat_size * lon_size) + lat * lon_size + lon]

const data = await variable.getValue();
const [timeSize, latSize, lonSize] = variable.shape;

// Access specific element [t=1, lat=2, lon=3]
const idx = 1 * (latSize * lonSize) + 2 * lonSize + 3;
const value = data[idx];

Type Conversion Performance

  • Reading always returns Float64Array for consistency
  • Writing automatically converts to the variable’s NetCDF type
  • Minimal performance impact for most use cases
  • Consider the precision requirements when choosing NetCDF data types