# Modern NetCDF

A modern JavaScript implementation for reading and working with NetCDF files. This library provides an efficient, async/await-based API for handling NetCDF v3.x files with support for partial reads, slicing, and memory-efficient operations.

## Features

- 📖 Full NetCDF-3 classic & 64-bit offset support
- 🔄 Promise-based, await-friendly API
- 🔪 Hyperslab slicing with range + stride
- 📡 **Lazy** HTTP-Range streaming for remote files
- ⚙️ Zero-copy views & memory-efficient decoding
- 🧵 WebWorker shim auto-selects Node vs Browser
- 📊 Smart formatting for large arrays and strings
- 🛠️ Command-line `netcdf-dump` utility
- 💪 TypeScript declarations included

## Installation

```bash
npm install modern-netcdf
```

## Usage

### Basic Usage

```javascript
const { NetCDFReader } = require('modern-netcdf');

// From a file
const reader = await NetCDFReader.open('path/to/file.nc');

// From an ArrayBuffer
const buffer = fs.readFileSync('path/to/file.nc');
const reader = await NetCDFReader.open(buffer);

// From a URL (download header only; streams data on-demand)
const reader = await NetCDFReader.open('https://example.com/data.nc', { lazy: true });
// The first call to getData() will issue HTTP Range requests automatically.

// Access metadata
console.log(reader.dimensions);     // { time: 72, y: 1040, x: 1077 }
console.log(reader.globalAttributes); // Array of global attributes
console.log(reader.variables);      // Object of variable metadata

// Read entire variable
const data = await reader.getData('temperature');

// Clean up when done
reader.close();
```

### Advanced Data Slicing

The library supports sophisticated data slicing with start, end, and stride parameters:

```javascript
// Read a specific time index
const timeSlice = await reader.getData('temperature', { 
  time: 0  // Select first time step
});

// Read a range of latitudes and longitudes
const spatialSlice = await reader.getData('temperature', {
  lat: [100, 200],    // Select indices 100-199
  lon: [400, 600]     // Select indices 400-599
});

// Use stride to read every other point
const strided = await reader.getData('temperature', {
  lat: [0, 100, 2],   // Start: 0, End: 100, Stride: 2
  lon: [0, 200, 2]    // Start: 0, End: 200, Stride: 2
});
```

### Working with Variables

```javascript
// Get a specific variable
const tempVar = reader.getVariable('temperature');

// Access variable metadata
console.log(tempVar.name);        // Variable name
console.log(tempVar.type);        // Data type (float, double, etc.)
console.log(tempVar.dimensions);   // Array of dimension names
console.log(tempVar.attributes);   // Array of variable attributes
```

## Command-line Tool

The package includes a `netcdf-dump` utility for inspecting NetCDF files:

```bash
# Install globally
npm install -g modern-netcdf

# Use the command-line tool
netcdf-dump path/to/file.nc

# Or use with npx
npx netcdf-dump path/to/file.nc
```

The output format is similar to the standard `ncdump` utility:

```
netcdf input {
dimensions:
        time = 72 ;
        y = 1040 ;
        x = 1077 ;
variables:
        float temperature(time, y, x) ;
                units = "K" ;
                long_name = "Temperature" ;
        ...
}
```

## Error Handling

The library provides detailed error messages for common issues:

```javascript
try {
  const reader = await NetCDFReader.open('file.nc');
  const data = await reader.getData('nonexistent');
} catch (error) {
  if (error.message.includes('Variable not found')) {
    console.error('The requested variable does not exist');
  }
}
```

## Memory Considerations

For large datasets, the library implements smart memory management:

- Only loads requested data portions into memory
- Automatically summarizes large arrays in output
- Provides clean-up method via `reader.close()`

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

ISC 

### Using in a WebWorker (browser)

```javascript
// worker.js
importScripts('modern-netcdf/dist/worker.js');

// main thread (app.js)
const worker = new Worker('worker.js');
worker.postMessage({ id: 1, cmd: 'open', payload: { source: 'https://example.com/data.nc', options: { lazy: true } } });

worker.onmessage = (e) => {
  const { id, result, error } = e.data;
  if (error) console.error(error);
  else console.log('Worker response', result);
};
```

The same worker entry automatically routes to `worker_threads` when bundled for Node.js, so you can reuse the identical message protocol in server-side environments. 
