Task 442: .NCD File Format
Task 442: .NCD File Format
1. File Format Specifications for the .NCD File Format
The .NCD file format is the NetCDF (Network Common Data Form) classic format, a binary format for storing multidimensional scientific data. It is commonly used for array-oriented data like climate, oceanography, and atmospheric models. The format is self-describing, machine-independent, and supports dimensions, variables, and attributes. The specification is defined by UNIDATA and includes two variants: classic (32-bit offsets) and 64-bit offset. The full BNF grammar and field descriptions are as follows:
BNF Grammar
netcdf_file = header data
header = magic numrecs dim_list gatt_list var_list
magic = 'C' 'D' 'F' VERSION
VERSION = \x01 | \x02 // \x01 for classic, \x02 for 64-bit offset
numrecs = NON_NEG | STREAMING // record dimension length or streaming (\xFF \xFF \xFF \xFF)
dim_list = ABSENT | NC_DIMENSION nelems [dim ...]
gatt_list = att_list // global attributes
att_list = ABSENT | NC_ATTRIBUTE nelems [attr ...]
var_list = ABSENT | NC_VARIABLE nelems [var ...]
ABSENT = ZERO ZERO
ZERO = \x00 \x00 \x00 \x00
NC_DIMENSION = \x00 \x00 \x00 \x0A
NC_VARIABLE = \x00 \x00 \x00 \x0B
NC_ATTRIBUTE = \x00 \x00 \x00 \x0C
nelems = NON_NEG
dim = name dim_length
name = nelems namestring
namestring = ID1 [IDN ...] padding // padded to 4 bytes
// Name regex: ([a-zA-Z0-9_]|{MUTF8})([^\x00-\x1F/\x7F-\xFF]|{MUTF8})*
dim_length = NON_NEG // 0 for unlimited/record dimension
attr = name nc_type nelems [values ...]
nc_type = NC_BYTE (\x01) | NC_CHAR (\x02) | NC_SHORT (\x03) | NC_INT (\x04) | NC_FLOAT (\x05) | NC_DOUBLE (\x06)
var = name nelems [dimid ...] vatt_list nc_type vsize begin
dimid = NON_NEG
vatt_list = att_list
vsize = NON_NEG
begin = OFFSET // 32-bit for classic, 64-bit for offset format
data = non_recs recs
non_recs = [vardata ...]
vardata = [values ...] // row-major order
recs = [record ...]
record = [varslab ...]
varslab = [values ...]
values = bytes | chars | shorts | ints | floats | doubles
string = nelems [chars]
bytes = [BYTE ...] padding
chars = [CHAR ...] padding
shorts = [SHORT ...] padding
ints = [INT ...]
floats = [FLOAT ...]
doubles = [DOUBLE ...]
padding = <0-3 null bytes to 4-byte boundary>
NON_NEG = <32-bit non-negative int, big-endian>
OFFSET = <32-bit or 64-bit non-negative int, big-endian>
BYTE = <8-bit byte>
CHAR = <8-bit byte>
SHORT = <16-bit signed int, big-endian, two's complement>
INT = <32-bit signed int, big-endian, two's complement>
INT64 = <64-bit signed int, big-endian, two's complement>
FLOAT = <32-bit IEEE float, big-endian>
DOUBLE = <64-bit IEEE double, big-endian>
FILL_BYTE = \x81
FILL_CHAR = \x00
FILL_SHORT = \x80 \x01
FILL_INT = \x80 \x00 \x00 \x01
FILL_FLOAT = \x7C \xF0 \x00 \x00
FILL_DOUBLE = \x47 \x9E \x00 \x00 \x00 \x00 \x00 \x00
Field Descriptions
- magic: 'CDF' followed by VERSION (\x01 classic, \x02 64-bit offset).
- numrecs: Number of records along the unlimited dimension (NON_NEG) or streaming mode.
- dim_list: List of dimensions (name, length); unlimited dimension has length 0.
- gatt_list: Global attributes (name, type, count, values).
- var_list: Variables (name, rank, shape [dimids], attributes, type, size, offset).
- att_list: Attributes (name, type, count, values); types are byte, char, short, int, float, double.
- data: Non-record variable data first (contiguous, row-major), then interleaved record data.
- padding: Aligns to 4-byte boundaries with nulls (header) or fill values (data).
- fill values: Default padding for missing data, overridable by '_FillValue' attribute.
- names: UTF-8 NFC-normalized, with restrictions (no control chars, no '/').
- offsets: 32-bit in classic (file < 2GiB), 64-bit in offset format (larger files).
- Notes: No compression in classic; names max 2^32-4 bytes; vsize redundant but used for validation.
2. List of All Properties Intrinsic to This File Format
| Property | Description | Type/Structure |
|---|---|---|
| Magic | File identifier ('CDF' + version byte) | 4 bytes |
| Version | Format variant (1 for classic, 2 for 64-bit offset) | 1 byte |
| Numrecs | Number of records or streaming flag | 4 bytes |
| Dimensions | List of dimensions: count, then per dimension (name length, name string padded, length) | Variable length list |
| Global Attributes | List of attributes: count, then per attribute (name, type, value count, values padded) | Variable length list |
| Variables | List of variables: count, then per variable (name, rank, dimids, attributes, type, vsize, begin offset) | Variable length list |
| Data | Non-record data (contiguous per variable, row-major) + record data (interleaved per record) | Variable length, typed values with padding |
| Attributes (per variable/global) | Name, type (byte/char/short/int/float/double), count, values | Per attribute |
| Fill Values | Default missing data fillers, overridable by '_FillValue' | Type-specific constants |
3. Two Direct Download Links for .NCD Files
- https://thredds.socib.es/thredds/fileServer/operational_models/oceanographical/hydrodynamics/model_run_aggregation/wmop_surface/wmop_surface_fmrc.ncd
- https://iws.ismar.cnr.it/thredds/fileServer/tmes_sea_level_frmc/TMES_sea_level_collection_fmrc.ncd
4. Ghost Blog Embedded HTML JavaScript for Drag and Drop .NCD File Dump
5. Python Class for .NCD File
import struct
import sys
class NCDFile:
def __init__(self, filename):
self.filename = filename
self.magic = None
self.version = None
self.numrecs = None
self.dims = []
self.gattrs = []
self.vars = []
self.data = None # Not fully reading data for simplicity
def read(self):
with open(self.filename, 'rb') as f:
self.data = f.read()
dv = memoryview(self.data)
offset = [0]
def unpack(fmt):
size = struct.calcsize(fmt)
val = struct.unpack('>' + fmt, dv[offset[0]:offset[0]+size])[0]
offset[0] += size
return val
def read_string():
len_ = unpack('I')
str_ = dv[offset[0]:offset[0]+len_].tobytes().decode('utf-8')
offset[0] += len_ + (4 - len_ % 4) % 4
return str_
def read_list(tag, item_reader):
list_tag = unpack('I')
if list_tag == 0:
return []
if list_tag != tag:
raise ValueError('Invalid tag')
count = unpack('I')
items = []
for _ in range(count):
items.append(item_reader())
return items
def read_dim():
name = read_string()
length = unpack('I')
return {'name': name, 'length': length}
def read_attr():
name = read_string()
type_ = unpack('I')
count = unpack('I')
values = []
size = [1,1,2,4,4,8][type_-1]
fmt = ['b','c','h','i','f','d'][type_-1]
for _ in range(count):
values.append(unpack(fmt))
offset[0] += (4 - (count * size % 4)) % 4
return {'name': name, 'type': type_, 'values': values}
def read_var():
name = read_string()
rank = unpack('I')
dimids = [unpack('I') for _ in range(rank)]
attrs = read_list(12, read_attr)
type_ = unpack('I')
vsize = unpack('I')
begin = unpack('I') # Assume classic
return {'name': name, 'rank': rank, 'dimids': dimids, 'attrs': attrs, 'type': type_, 'vsize': vsize, 'begin': begin}
self.magic = dv[0:3].tobytes().decode('ascii')
offset[0] = 3
self.version = unpack('B')
self.numrecs = unpack('I')
self.dims = read_list(10, read_dim)
self.gattrs = read_list(12, read_attr)
self.vars = read_list(11, read_var)
def print_properties(self):
print(f"Magic: {self.magic}")
print(f"Version: {self.version}")
print(f"Numrecs: {self.numrecs if self.numrecs != 0xFFFFFFFF else 'Streaming'}")
print("Dimensions:")
for d in self.dims:
print(f" - {d['name']}: {d['length']}")
print("Global Attributes:")
for a in self.gattrs:
print(f" - {a['name']} (type {a['type']}): {a['values']}")
print("Variables:")
for v in self.vars:
print(f" - {v['name']} (type {v['type']}, rank {v['rank']}, dimids {v['dimids']}, vsize {v['vsize']}, begin {v['begin']})")
print(f" Attributes: { [a['name'] for a in v['attrs']] }")
def write(self, out_filename):
# Simple write: create minimal .ncd with one dim, one var, no data
with open(out_filename, 'wb') as f:
f.write(b'CDF\x01') # magic + version
f.write(struct.pack('>I', 0)) # numrecs
f.write(struct.pack('>I', 10)) # dim_list tag
f.write(struct.pack('>I', 1)) # one dim
name = b'time'
f.write(struct.pack('>I', len(name)))
f.write(name)
f.write(b'\x00' * (4 - len(name) % 4)) # pad
f.write(struct.pack('>I', 0)) # unlimited
f.write(struct.pack('>II', 12, 0)) # gatt_list absent
f.write(struct.pack('>I', 11)) # var_list tag
f.write(struct.pack('>I', 1)) # one var
name = b'temp'
f.write(struct.pack('>I', len(name)))
f.write(name)
f.write(b'\x00' * (4 - len(name) % 4))
f.write(struct.pack('>I', 1)) # rank
f.write(struct.pack('>I', 0)) # dimid 0
f.write(struct.pack('>II', 12, 0)) # vatt_list absent
f.write(struct.pack('>I', 5)) # float
f.write(struct.pack('>I', 0)) # vsize 0
f.write(struct.pack('>I', offset[0])) # begin (header size, but simplified)
# No data written
# Example usage
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python ncd.py <file.ncd>")
sys.exit(1)
ncd = NCDFile(sys.argv[1])
ncd.read()
ncd.print_properties()
ncd.write('output.ncd')
To arrive at the solution: The code parses the binary file using struct and memoryview for efficiency, reading each section sequentially according to the BNF. For write, it creates a minimal valid header without data.
6. Java Class for .NCD File
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
public class NCDFile {
private String filename;
private String magic;
private int version;
private long numrecs;
private java.util.List<java.util.Map<String, Object>> dims = new java.util.ArrayList<>();
private java.util.List<java.util.Map<String, Object>> gattrs = new java.util.ArrayList<>();
private java.util.List<java.util.Map<String, Object>> vars = new java.util.ArrayList<>();
public NCDFile(String filename) {
this.filename = filename;
}
public void read() throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(filename, "r")) {
FileChannel channel = raf.getChannel();
ByteBuffer bb = ByteBuffer.allocate((int) raf.length()).order(ByteOrder.BIG_ENDIAN);
channel.read(bb);
bb.flip();
int[] offset = {0};
String readString = () -> {
int len = bb.getInt(offset[0]); offset[0] += 4;
byte[] bytes = new byte[len];
bb.position(offset[0]);
bb.get(bytes);
offset[0] += len + (4 - len % 4) % 4;
return new String(bytes);
};
java.util.List<java.util.Map<String, Object>> readList = (int tag, java.util.function.Supplier<java.util.Map<String, Object>> itemReader) -> {
int listTag = bb.getInt(offset[0]); offset[0] += 4;
if (listTag == 0) return new java.util.ArrayList<>();
if (listTag != tag) throw new RuntimeException("Invalid tag");
int count = bb.getInt(offset[0]); offset[0] += 4;
java.util.List<java.util.Map<String, Object>> items = new java.util.ArrayList<>();
for (int i = 0; i < count; i++) items.add(itemReader.get());
return items;
};
java.util.Map<String, Object> readDim = () -> {
java.util.Map<String, Object> dim = new java.util.HashMap<>();
dim.put("name", readString());
dim.put("length", (long) bb.getInt(offset[0])); offset[0] += 4;
return dim;
};
java.util.Map<String, Object> readAttr = () -> {
java.util.Map<String, Object> attr = new java.util.HashMap<>();
attr.put("name", readString());
int type = bb.getInt(offset[0]); offset[0] += 4;
attr.put("type", type);
int count = bb.getInt(offset[0]); offset[0] += 4;
Object[] values = new Object[count];
int size = new int[]{1,1,2,4,4,8}[type-1];
for (int i = 0; i < count; i++) {
switch (type) {
case 1: values[i] = bb.get(offset[0]); break;
case 2: values[i] = (char) bb.get(offset[0]); break;
case 3: values[i] = bb.getShort(offset[0]); break;
case 4: values[i] = bb.getInt(offset[0]); break;
case 5: values[i] = bb.getFloat(offset[0]); break;
case 6: values[i] = bb.getDouble(offset[0]); break;
}
offset[0] += size;
}
offset[0] += (4 - (count * size % 4)) % 4;
attr.put("values", values);
return attr;
};
java.util.Map<String, Object> readVar = () -> {
java.util.Map<String, Object> var = new java.util.HashMap<>();
var.put("name", readString());
int rank = bb.getInt(offset[0]); offset[0] += 4;
var.put("rank", rank);
int[] dimids = new int[rank];
for (int i = 0; i < rank; i++) dimids[i] = bb.getInt(offset[0]); offset[0] += 4;
var.put("dimids", dimids);
var.put("attrs", readList.apply(12, readAttr));
var.put("type", bb.getInt(offset[0])); offset[0] += 4;
var.put("vsize", (long) bb.getInt(offset[0])); offset[0] += 4;
var.put("begin", (long) bb.getInt(offset[0])); offset[0] += 4; // Assume classic
return var;
};
byte[] mag = new byte[3];
bb.get(mag);
magic = new String(mag);
offset[0] = 3;
version = bb.get(offset[0]++) & 0xFF;
numrecs = bb.getInt(offset[0]); offset[0] += 4;
dims = readList.apply(10, readDim);
gattrs = readList.apply(12, readAttr);
vars = readList.apply(11, readVar);
}
}
public void printProperties() {
System.out.println("Magic: " + magic);
System.out.println("Version: " + version);
System.out.println("Numrecs: " + (numrecs == 0xFFFFFFFFL ? "Streaming" : numrecs));
System.out.println("Dimensions:");
for (java.util.Map<String, Object> d : dims) {
System.out.println(" - " + d.get("name") + ": " + d.get("length"));
}
System.out.println("Global Attributes:");
for (java.util.Map<String, Object> a : gattrs) {
System.out.println(" - " + a.get("name") + " (type " + a.get("type") + "): " + java.util.Arrays.toString((Object[]) a.get("values")));
}
System.out.println("Variables:");
for (java.util.Map<String, Object> v : vars) {
System.out.println(" - " + v.get("name") + " (type " + v.get("type") + ", rank " + v.get("rank") + ", dimids " + java.util.Arrays.toString((int[]) v.get("dimids")) + ", vsize " + v.get("vsize") + ", begin " + v.get("begin"));
System.out.println(" Attributes: " + ((java.util.List<java.util.Map<String, Object>>) v.get("attrs")).stream().map(m -> (String) m.get("name")).toArray());
}
}
public void write(String outFilename) throws IOException {
try (FileOutputStream fos = new FileOutputStream(outFilename)) {
ByteBuffer bb = ByteBuffer.allocate(1024).order(ByteOrder.BIG_ENDIAN); // Small buffer for minimal
bb.put("CDF".getBytes()).put((byte)1); // magic + version
bb.putInt(0); // numrecs
bb.putInt(10); // dim_list
bb.putInt(1); // one dim
String name = "time";
bb.putInt(name.length());
bb.put(name.getBytes());
bb.put(new byte[4 - name.length() % 4]); // pad
bb.putInt(0); // length
bb.putInt(12); bb.putInt(0); // gatt_list absent
bb.putInt(11); // var_list
bb.putInt(1); // one var
name = "temp";
bb.putInt(name.length());
bb.put(name.getBytes());
bb.put(new byte[4 - name.length() % 4]);
bb.putInt(1); // rank
bb.putInt(0); // dimid
bb.putInt(12); bb.putInt(0); // vatt_list absent
bb.putInt(5); // float
bb.putInt(0); // vsize
bb.putInt(bb.position()); // begin approximate
bb.flip();
fos.write(bb.array(), 0, bb.limit());
}
}
public static void main(String[] args) throws IOException {
if (args.length < 1) {
System.out.println("Usage: java NCDFile <file.ncd>");
System.exit(1);
}
NCDFile ncd = new NCDFile(args[0]);
ncd.read();
ncd.printProperties();
ncd.write("output.ncd");
}
}
To arrive at the solution: The code uses ByteBuffer for big-endian reading, parsing sections with lambdas for readability. Write creates a minimal file.
7. JavaScript Class for .NCD File
class NCDFile {
constructor(filename) {
this.filename = filename;
this.magic = null;
this.version = null;
this.numrecs = null;
this.dims = [];
this.gattrs = [];
this.vars = [];
this.data = null;
}
async read() {
const response = await fetch(this.filename);
this.data = await response.arrayBuffer();
const dv = new DataView(this.data);
let offset = 0;
const unpack = (fmt) => {
const sizes = { B: 1, I: 4 };
const val = fmt === 'B' ? dv.getUint8(offset) : dv.getUint32(offset);
offset += sizes[fmt];
return val;
};
const readString = () => {
const len = unpack('I');
let str = '';
for (let i = 0; i < len; i++) str += String.fromCharCode(dv.getUint8(offset + i));
offset += len + (4 - len % 4) % 4;
return str;
};
const readList = (tag, itemReader) => {
const listTag = unpack('I');
if (listTag === 0) return [];
if (listTag !== tag) throw new Error('Invalid tag');
const count = unpack('I');
const items = [];
for (let i = 0; i < count; i++) items.push(itemReader());
return items;
};
const readDim = () => ({ name: readString(), length: unpack('I') });
const readAttr = () => {
const name = readString();
const type = unpack('I');
const count = unpack('I');
const values = [];
const getFn = ['getInt8', 'getUint8', 'getInt16', 'getInt32', 'getFloat32', 'getFloat64'][type-1];
const size = [1,1,2,4,4,8][type-1];
for (let i = 0; i < count; i++) {
values.push(dv[getFn](offset));
offset += size;
}
offset += (4 - (count * size % 4)) % 4;
return { name, type, values };
};
const readVar = () => {
const name = readString();
const rank = unpack('I');
const dimids = [];
for (let i = 0; i < rank; i++) dimids.push(unpack('I'));
const attrs = readList(12, readAttr);
const type = unpack('I');
const vsize = unpack('I');
const begin = unpack('I'); // classic
return { name, rank, dimids, attrs, type, vsize, begin };
};
this.magic = new TextDecoder().decode(new Uint8Array(this.data, 0, 3));
offset = 3;
this.version = unpack('B');
this.numrecs = unpack('I');
this.dims = readList(10, readDim);
this.gattrs = readList(12, readAttr);
this.vars = readList(11, readVar);
}
printProperties() {
console.log(`Magic: ${this.magic}`);
console.log(`Version: ${this.version}`);
console.log(`Numrecs: ${this.numrecs === 0xFFFFFFFF ? 'Streaming' : this.numrecs}`);
console.log('Dimensions:');
this.dims.forEach(d => console.log(` - ${d.name}: ${d.length}`));
console.log('Global Attributes:');
this.gattrs.forEach(a => console.log(` - ${a.name} (type ${a.type}): ${a.values}`));
console.log('Variables:');
this.vars.forEach(v => {
console.log(` - ${v.name} (type ${v.type}, rank ${v.rank}, dimids [${v.dimids}], vsize ${v.vsize}, begin ${v.begin})`);
console.log(` Attributes: ${v.attrs.map(a => a.name).join(', ')}`);
});
}
write(outFilename) {
// Minimal write using Blob
const buf = new ArrayBuffer(100); // Small for minimal
const dv = new DataView(buf);
let o = 0;
'CDF'.split('').forEach(c => dv.setUint8(o++, c.charCodeAt(0)));
dv.setUint8(o++, 1); // version
dv.setUint32(o, 0); o += 4; // numrecs
dv.setUint32(o, 10); o += 4; // dim_list
dv.setUint32(o, 1); o += 4; // count
const name = 'time';
dv.setUint32(o, name.length); o += 4;
name.split('').forEach(c => dv.setUint8(o++, c.charCodeAt(0)));
o += 4 - name.length % 4;
dv.setUint32(o, 0); o += 4; // length
dv.setUint32(o, 12); o += 4; dv.setUint32(o, 0); o += 4; // gatt absent
dv.setUint32(o, 11); o += 4; // var_list
dv.setUint32(o, 1); o += 4; // count
const vname = 'temp';
dv.setUint32(o, vname.length); o += 4;
vname.split('').forEach(c => dv.setUint8(o++, c.charCodeAt(0)));
o += 4 - vname.length % 4;
dv.setUint32(o, 1); o += 4; // rank
dv.setUint32(o, 0); o += 4; // dimid
dv.setUint32(o, 12); o += 4; dv.setUint32(o, 0); o += 4; // vatt absent
dv.setUint32(o, 5); o += 4; // float
dv.setUint32(o, 0); o += 4; // vsize
dv.setUint32(o, o); o += 4; // begin
const blob = new Blob([buf.slice(0, o)]);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outFilename;
a.click();
}
}
// Example usage
// const ncd = new NCDFile('example.ncd');
// ncd.read().then(() => ncd.printProperties());
// ncd.write('output.ncd');
To arrive at the solution: Uses DataView for big-endian parsing. Write creates a Blob for download.
8. C Class for .NCD File
(Note: This is C++ for practicality, as pure C would require manual memory management.)
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstring>
#include <cstdint>
struct Dim {
std::string name;
uint32_t length;
};
struct Attr {
std::string name;
uint32_t type;
std::vector<double> values; // Simplify to double for print
};
struct Var {
std::string name;
uint32_t rank;
std::vector<uint32_t> dimids;
std::vector<Attr> attrs;
uint32_t type;
uint32_t vsize;
uint32_t begin;
};
class NCDFile {
public:
NCDFile(const std::string& fn) : filename(fn) {}
void read() {
std::ifstream f(filename, std::ios::binary | std::ios::ate);
auto size = f.tellg();
f.seekg(0);
data.resize(size);
f.read(data.data(), size);
offset = 0;
auto unpack32 = [&]() -> uint32_t {
uint32_t val;
std::memcpy(&val, data.data() + offset, 4);
offset += 4;
return __builtin_bswap32(val); // Big-endian
};
auto readString = [&]() -> std::string {
uint32_t len = unpack32();
std::string str(data.begin() + offset, data.begin() + offset + len);
offset += len + (4 - len % 4) % 4;
return str;
};
auto readList = [&](uint32_t tag, auto itemReader) -> std::vector<decltype(itemReader())> {
uint32_t listTag = unpack32();
if (listTag == 0) return {};
if (listTag != tag) throw std::runtime_error("Invalid tag");
uint32_t count = unpack32();
std::vector<decltype(itemReader())> items;
for (uint32_t i = 0; i < count; i++) items.push_back(itemReader());
return items;
};
auto readDim = [&]() {
Dim d;
d.name = readString();
d.length = unpack32();
return d;
};
auto readAttr = [&]() {
Attr a;
a.name = readString();
a.type = unpack32();
uint32_t count = unpack32();
a.values.resize(count);
int size = (int[]){1,1,2,4,4,8}[a.type-1];
for (uint32_t i = 0; i < count; i++) {
double val;
std::memcpy(&val, data.data() + offset, size);
a.values[i] = val; // Simplified
offset += size;
}
offset += (4 - (count * size % 4)) % 4;
return a;
};
auto readVar = [&]() {
Var v;
v.name = readString();
v.rank = unpack32();
v.dimids.resize(v.rank);
for (uint32_t i = 0; i < v.rank; i++) v.dimids[i] = unpack32();
v.attrs = readList(12, readAttr);
v.type = unpack32();
v.vsize = unpack32();
v.begin = unpack32(); // classic
return v;
};
magic = std::string(data.begin(), data.begin() + 3);
offset = 3;
version = data[offset++];
numrecs = unpack32();
dims = readList(10, readDim);
gattrs = readList(12, readAttr);
vars = readList(11, readVar);
}
void printProperties() {
std::cout << "Magic: " << magic << std::endl;
std::cout << "Version: " << version << std::endl;
std::cout << "Numrecs: " << (numrecs == 0xFFFFFFFF ? "Streaming" : std::to_string(numrecs)) << std::endl;
std::cout << "Dimensions:" << std::endl;
for (const auto& d : dims) std::cout << " - " << d.name << ": " << d.length << std::endl;
std::cout << "Global Attributes:" << std::endl;
for (const auto& a : gattrs) {
std::cout << " - " << a.name << " (type " << a.type << "): ";
for (double v : a.values) std::cout << v << " ";
std::cout << std::endl;
}
std::cout << "Variables:" << std::endl;
for (const auto& v : vars) {
std::cout << " - " << v.name << " (type " << v.type << ", rank " << v.rank << ", dimids [";
for (uint32_t id : v.dimids) std::cout << id << " ";
std::cout << "], vsize " << v.vsize << ", begin " << v.begin << ")" << std::endl;
std::cout << " Attributes: ";
for (const auto& a : v.attrs) std::cout << a.name << " ";
std::cout << std::endl;
}
}
void write(const std::string& outFilename) {
std::ofstream f(outFilename, std::ios::binary);
auto pack32 = [&](uint32_t val) {
val = __builtin_bswap32(val);
f.write(reinterpret_cast<char*>(&val), 4);
};
auto writeString = [&](const std::string& str) {
pack32(str.length());
f.write(str.data(), str.length());
int pad = (4 - str.length() % 4) % 4;
f.write("\0\0\0", pad);
};
f.write("CDF", 3);
f.put(1); // version
pack32(0); // numrecs
pack32(10); // dim_list
pack32(1); // count
writeString("time");
pack32(0); // length
pack32(12); pack32(0); // gatt absent
pack32(11); // var_list
pack32(1); // count
writeString("temp");
pack32(1); // rank
pack32(0); // dimid
pack32(12); pack32(0); // vatt absent
pack32(5); // float
pack32(0); // vsize
pack32(100); // begin dummy
// No data
}
private:
std::string filename;
std::string magic;
uint8_t version;
uint32_t numrecs;
std::vector<Dim> dims;
std::vector<Attr> gattrs;
std::vector<Var> vars;
std::vector<char> data;
size_t offset;
};
int main(int argc, char** argv) {
if (argc < 2) {
std::cout << "Usage: " << argv[0] << " <file.ncd>" << std::endl;
return 1;
}
NCDFile ncd(argv[1]);
ncd.read();
ncd.printProperties();
ncd.write("output.ncd");
return 0;
}
To arrive at the solution: Uses vector for buffer, __builtin_bswap32 for big-endian. Write uses ofstream. Simplified value storage as double for print.