Task 268: .GRB File Format

Task 268: .GRB File Format

1. List of All Properties Intrinsic to the .GRB File Format

The .GRB file format refers to the GRIB (GRIdded Binary) format, a binary standard developed by the World Meteorological Organization (WMO) for efficiently storing and exchanging gridded meteorological and oceanographic data. It supports both Edition 1 (legacy) and Edition 2 (current standard, GRIB2), but modern .GRB files are typically GRIB2. The format is message-based: a file can contain multiple self-describing messages, each with a fixed structure divided into up to 8 sections, ending with a 4-byte "7777" (0x7777) terminator. The intrinsic properties are the fixed and variable fields within these sections, which define metadata (e.g., grid, time, parameters) and the data itself. These properties enable compact representation of 2D/3D gridded data without a separate header file.

Properties are organized by section below (lengths in octets; variable fields depend on templates referenced in WMO tables). All multi-octet values are big-endian unless noted.

Section 0: Indicator Section (Fixed 16 octets)

  • GRIB identifier (octets 1-4): ASCII "GRIB" (fixed string).
  • Reserved (octets 5-6): Always 0x00 0x00.
  • Discipline (octet 7): Code for data type (e.g., 0 = meteorological; Table 0.0).
  • Edition number (octet 8): 2 for GRIB2.
  • Length of GRIB message (octets 9-16): Total octets in the entire message (unsigned 64-bit integer).

Section 1: Identification Section (Minimum 21 octets, variable)

  • Length of section (octets 1-4): Total octets in section (unsigned 32-bit).
  • Section number (octet 5): Always 1.
  • Identification of originating/generating center (octets 6-7): Code (Table 0, e.g., 7 = NCEP).
  • Identification of originating/generating sub-center (octets 8-9): Code (Table C, e.g., 0 = none).
  • GRIB master tables version number (octet 10): e.g., 2 (Table 1.0).
  • Version number of GRIB local tables (octet 11): Augments master tables (Table 1.1).
  • Significance of reference time (octet 12): e.g., 0 = analysis (Table 1.2).
  • Reference year (octets 13-14): 4-digit year (unsigned 16-bit).
  • Reference month (octet 15): 1-12 (unsigned 8-bit).
  • Reference day (octet 16): 1-31 (unsigned 8-bit).
  • Reference hour (octet 17): 0-23 (unsigned 8-bit).
  • Reference minute (octet 18): 0-59 (unsigned 8-bit).
  • Reference second (octet 19): 0-60 (unsigned 8-bit).
  • Production status of processed data (octet 20): e.g., 0 = operational (Table 1.3).
  • Type of processed data (octet 21): e.g., 0 = analysis (Table 1.4).
  • Reserved (octets 22-end): Variable, all zeros.

Section 2: Local Use Section (Minimum 6 octets, variable; optional)

  • Length of section (octets 1-4): Total octets in section (unsigned 32-bit).
  • Section number (octet 5): Always 2.
  • Local use data (octets 6-end): Center-specific data (variable length).

Section 3: Grid Definition Section (Minimum 15 octets, variable)

  • Length of section (octets 1-4): Total octets in section (unsigned 32-bit).
  • Section number (octet 5): Always 3.
  • Source of grid definition (octet 6): e.g., 0 = specified in section (Table 3.0).
  • Number of data points (octets 7-10): Total grid points (unsigned 32-bit).
  • Number of octets for optional list of numbers (octet 11): e.g., 0 = none (unsigned 8-bit).
  • Interpretation of list of numbers (octet 12): e.g., 0 = row-by-row (Table 3.11).
  • Grid definition template number (octets 13-14): e.g., 0 = lat/lon (Table 3.0; unsigned 16-bit).
  • Grid definition template (octets 15-variable): Template-specific fields (e.g., Template 3.0: Ni/Nj grid dimensions, latitudes/longitudes, scanning mode).
  • Optional list of numbers (variable, if octet 11 > 0): Defines points per row/column for quasi-regular grids.

Section 4: Product Definition Section (Minimum 10 octets, variable)

  • Length of section (octets 1-4): Total octets in section (unsigned 32-bit).
  • Section number (octet 5): Always 4.
  • Number of coordinate values after template (octets 6-7): e.g., 0 = none (unsigned 16-bit).
  • Product definition template number (octets 8-9): e.g., 0 = analysis (Table 4.0; unsigned 16-bit).
  • Product definition template (octets 10-variable): Template-specific fields (e.g., Template 4.0: parameter category/discipline, time range, vertical level type).
  • Optional list of coordinate values (variable, if octets 6-7 > 0): Hybrid coordinate pairs (IEEE float32).

Section 5: Data Representation Section (Minimum 12 octets, variable)

  • Length of section (octets 1-4): Total octets in section (unsigned 32-bit).
  • Section number (octet 5): Always 5.
  • Number of data points (octets 6-9): Matches Section 3 or bitmap-filtered (unsigned 32-bit).
  • Data representation template number (octets 10-11): e.g., 0 = JPEG2000 (Table 5.0; unsigned 16-bit).
  • Data representation template (octets 12-variable): Template-specific fields (e.g., Template 5.0: reference value, binary scale, decimal scale, precision).

Section 6: Bit-Map Section (Minimum 6 octets, variable; optional)

  • Length of section (octets 1-4): Total octets in section (unsigned 32-bit).
  • Section number (octet 5): Always 6.
  • Bit-map indicator (octet 6): 0 = bitmap present (Table 6.0).
  • Bit-map (octets 7-variable, if octet 6=0): Packed bits indicating defined/undefined points (length = ceil(#points/8)).

Section 7: Data Section (Minimum 6 octets, variable)

  • Length of section (octets 1-4): Total octets in section (unsigned 32-bit).
  • Section number (octet 5): Always 7.
  • Data field (octets 6-end): Encoded data per Section 5 template (e.g., complex packing, PNG/JPEG).

File-Level Properties

  • Multiple messages: Concatenated GRIB messages, each starting with Section 0.
  • End of message: Always 4 octets "7777" (0x7777).
  • Overall file length: Sum of message lengths + terminators.
  • No built-in file header/footer; self-describing via sections.

These properties ensure portability, compression, and metadata richness for gridded data.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .GRB Property Dump

Embed this as a <div> with inline <script> in a Ghost blog post (uses File API and DataView for binary parsing; assumes basic GRIB2 structure with common templates like 3.0/4.0/5.0; prints to a <pre> element).

4. Python Class for .GRB Handling

Uses struct for binary parsing (assumes GRIB2 with common templates; read/write basic fields; write reconstructs simple message).

import struct
import json

class GRIB2File:
    def __init__(self, filename=None):
        self.data = None
        self.properties = {}
        if filename:
            self.read(filename)

    def read_uint32(self, pos):
        return struct.unpack('>I', self.data[pos:pos+4])[0]

    def read_uint16(self, pos):
        return struct.unpack('>H', self.data[pos:pos+2])[0]

    def read_uint8(self, pos):
        return struct.unpack('>B', self.data[pos:pos+1])[0]

    def read_int16(self, pos):
        return struct.unpack('>h', self.data[pos:pos+2])[0]

    def read_float32(self, pos):
        return struct.unpack('>f', self.data[pos:pos+4])[0]

    def read(self, filename):
        with open(filename, 'rb') as f:
            self.data = f.read()
        pos = 0
        # Section 0
        grib_id = self.data[pos:pos+4].decode('ascii')
        pos += 8  # Skip reserved + discipline/edition for simplicity
        msg_len = struct.unpack('>Q', self.data[pos:pos+8])[0]
        pos += 8
        self.properties['sec0'] = {'grib_id': grib_id, 'msg_len': msg_len}
        # Section 1
        sec1_len = self.read_uint32(pos); pos += 4
        sec1_num = self.read_uint8(pos); pos += 1
        center = self.read_uint16(pos); pos += 2
        subcenter = self.read_uint16(pos); pos += 2
        master_ver = self.read_uint8(pos); pos += 1
        local_ver = self.read_uint8(pos); pos += 1
        ref_sig = self.read_uint8(pos); pos += 1
        year = self.read_uint16(pos); pos += 2
        month = self.read_uint8(pos); pos += 1
        day = self.read_uint8(pos); pos += 1
        hour = self.read_uint8(pos); pos += 1
        minute = self.read_uint8(pos); pos += 1
        second = self.read_uint8(pos); pos += 1
        prod_status = self.read_uint8(pos); pos += 1
        data_type = self.read_uint8(pos); pos += 1
        pos += (sec1_len - 21)  # Skip reserved
        self.properties['sec1'] = {'len': sec1_len, 'center': center, 'subcenter': subcenter, 'master_ver': master_ver,
                                   'local_ver': local_ver, 'ref_sig': ref_sig, 'year': year, 'month': month, 'day': day,
                                   'hour': hour, 'minute': minute, 'second': second, 'prod_status': prod_status, 'data_type': data_type}
        # Section 3 (assume Template 3.0)
        sec3_len = self.read_uint32(pos); pos += 4
        sec3_num = self.read_uint8(pos); pos += 1
        source = self.read_uint8(pos); pos += 1
        ndp = self.read_uint32(pos); pos += 4
        oct_list = self.read_uint8(pos); pos += 1
        interp = self.read_uint8(pos); pos += 1
        temp_num = self.read_uint16(pos); pos += 2
        # Template 3.0
        shape = self.read_uint8(pos); pos += 1
        scale_lat = struct.unpack('>i', self.data[pos:pos+4])[0] / 2**8; pos += 4
        ni = self.read_uint32(pos); pos += 4
        nj = self.read_uint32(pos); pos += 4
        la1 = self.read_uint32(pos) / 10**7; pos += 4
        lo1 = self.read_uint32(pos) / 10**7; pos += 4
        res = self.read_uint8(pos); pos += 1
        dx = self.read_uint32(pos) / 10**6; pos += 4
        dy = self.read_uint32(pos) / 10**6; pos += 4
        scan = self.read_uint8(pos); pos += 1
        pos += (sec3_len - 40)  # Adjust for optional
        self.properties['sec3'] = {'len': sec3_len, 'source': source, 'ndp': ndp, 'temp_num': temp_num, 'shape': shape,
                                   'scale_lat': scale_lat, 'ni': ni, 'nj': nj, 'la1': la1, 'lo1': lo1, 'res': res,
                                   'dx': dx, 'dy': dy, 'scan': scan}
        # Section 4 (assume Template 4.0)
        sec4_len = self.read_uint32(pos); pos += 4
        sec4_num = self.read_uint8(pos); pos += 1
        ncoords = self.read_uint16(pos); pos += 2
        temp_num = self.read_uint16(pos); pos += 2
        param_cat = self.read_uint8(pos); pos += 1
        param_num = self.read_uint8(pos); pos += 1
        type_ens = self.read_uint8(pos); pos += 1
        pert = self.read_uint8(pos); pos += 1
        time_range = self.read_uint8(pos); pos += 1
        fcst_time = self.read_uint16(pos); pos += 2
        type_level = self.read_uint8(pos); pos += 1
        level1 = self.read_uint16(pos); pos += 2
        level2 = self.read_uint16(pos); pos += 2
        pos += (sec4_len - 18)  # Skip optional coords
        self.properties['sec4'] = {'len': sec4_len, 'ncoords': ncoords, 'temp_num': temp_num, 'param_cat': param_cat,
                                   'param_num': param_num, 'type_ens': type_ens, 'pert': pert, 'time_range': time_range,
                                   'fcst_time': fcst_time, 'type_level': type_level, 'level1': level1, 'level2': level2}
        # Section 5 (assume Template 5.0)
        sec5_len = self.read_uint32(pos); pos += 4
        sec5_num = self.read_uint8(pos); pos += 1
        ndp5 = self.read_uint32(pos); pos += 4
        temp_num = self.read_uint16(pos); pos += 2
        ref_val = self.read_float32(pos); pos += 4
        bin_scale = self.read_int16(pos); pos += 2
        dec_scale = self.read_int16(pos); pos += 2
        ndigits = self.read_uint8(pos); pos += 1
        data_flag = self.read_uint8(pos); pos += 1
        pos += (sec5_len - 18)
        self.properties['sec5'] = {'len': sec5_len, 'ndp': ndp5, 'temp_num': temp_num, 'ref_val': ref_val,
                                   'bin_scale': bin_scale, 'dec_scale': dec_scale, 'ndigits': ndigits, 'data_flag': data_flag}
        # Skip Sec6/7 and 7777 for simplicity

    def write(self, filename, properties=None):
        if properties is None:
            properties = self.properties
        with open(filename, 'wb') as f:
            # Section 0 (simplified)
            f.write(b'GRIB')
            f.write(b'\x00\x00')  # Reserved
            f.write(b'\x00')  # Discipline 0
            f.write(b'\x02')  # Edition 2
            # Dummy length, update later
            f.write(struct.pack('>Q', 1000))  # Placeholder
            # Section 1 (simplified)
            sec1_data = struct.pack('>I B H H B B B H B B B B B B B',
                                    21, 1, properties['sec1']['center'], properties['sec1']['subcenter'],
                                    properties['sec1']['master_ver'], properties['sec1']['local_ver'],
                                    properties['sec1']['ref_sig'], properties['sec1']['year'],
                                    properties['sec1']['month'], properties['sec1']['day'], properties['sec1']['hour'],
                                    properties['sec1']['minute'], properties['sec1']['second'],
                                    properties['sec1']['prod_status'], properties['sec1']['data_type'])
            sec1_len = len(sec1_data)
            f.write(struct.pack('>I', sec1_len))
            f.write(sec1_data)
            # Add other sections similarly (omitted for brevity; mirror read structure)
            # End
            f.write(b'\x77\x77\x77\x77')

    def print_properties(self):
        print(json.dumps(self.properties, indent=2))

# Example usage:
# g = GRIB2File('test.grb')
# g.print_properties()
# g.write('output.grb')

5. Java Class for .GRB Handling

Uses DataInputStream for reading/writing (assumes GRIB2 common templates; basic read/write).

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;

public class GRIB2File {
    private byte[] data;
    private Map<String, Object> properties = new HashMap<>();

    public GRIB2File(String filename) throws IOException {
        read(filename);
    }

    private int readUint32(int pos) {
        return ((data[pos] & 0xFF) << 24) | ((data[pos+1] & 0xFF) << 16) | ((data[pos+2] & 0xFF) << 8) | (data[pos+3] & 0xFF);
    }

    private short readUint16(int pos) {
        return (short) (((data[pos] & 0xFF) << 8) | (data[pos+1] & 0xFF));
    }

    private byte readUint8(int pos) {
        return data[pos];
    }

    private short readInt16(int pos) {
        return (short) (((data[pos] & 0xFF) << 8) | (data[pos+1] & 0xFF));
    }

    private float readFloat32(int pos) {
        ByteBuffer bb = ByteBuffer.allocate(4);
        bb.put(data, pos, 4);
        bb.position(0);
        bb.order(ByteOrder.BIG_ENDIAN);
        return bb.getFloat();
    }

    public void read(String filename) throws IOException {
        FileInputStream fis = new FileInputStream(filename);
        data = fis.readAllBytes();
        fis.close();
        int pos = 0;
        // Section 0
        String gribId = new String(data, pos, 4);
        pos += 8; // Skip
        long msgLen = (((long) readUint32(pos)) << 32) | (readUint32(pos + 4) & 0xFFFFFFFFL);
        pos += 8;
        Map<String, Object> sec0 = new HashMap<>();
        sec0.put("grib_id", gribId);
        sec0.put("msg_len", msgLen);
        properties.put("sec0", sec0);
        // Section 1
        int sec1Len = readUint32(pos); pos += 4;
        byte sec1Num = readUint8(pos); pos += 1;
        short center = readUint16(pos); pos += 2;
        short subcenter = readUint16(pos); pos += 2;
        byte masterVer = readUint8(pos); pos += 1;
        byte localVer = readUint8(pos); pos += 1;
        byte refSig = readUint8(pos); pos += 1;
        short year = readUint16(pos); pos += 2;
        byte month = readUint8(pos); pos += 1;
        byte day = readUint8(pos); pos += 1;
        byte hour = readUint8(pos); pos += 1;
        byte minute = readUint8(pos); pos += 1;
        byte second = readUint8(pos); pos += 1;
        byte prodStatus = readUint8(pos); pos += 1;
        byte dataType = readUint8(pos); pos += 1;
        pos += (sec1Len - 21);
        Map<String, Object> sec1 = new HashMap<>();
        sec1.put("len", sec1Len);
        sec1.put("center", center);
        sec1.put("subcenter", subcenter);
        sec1.put("master_ver", masterVer);
        sec1.put("local_ver", localVer);
        sec1.put("ref_sig", refSig);
        sec1.put("year", year);
        sec1.put("month", month);
        sec1.put("day", day);
        sec1.put("hour", hour);
        sec1.put("minute", minute);
        sec1.put("second", second);
        sec1.put("prod_status", prodStatus);
        sec1.put("data_type", dataType);
        properties.put("sec1", sec1);
        // Section 3 (Template 3.0)
        int sec3Len = readUint32(pos); pos += 4;
        byte sec3Num = readUint8(pos); pos += 1;
        byte source = readUint8(pos); pos += 1;
        int ndp = readUint32(pos); pos += 4;
        byte octList = readUint8(pos); pos += 1;
        byte interp = readUint8(pos); pos += 1;
        short tempNum = readUint16(pos); pos += 2;
        byte shape = readUint8(pos); pos += 1;
        int scaleLatRaw = ((data[pos] & 0xFF) << 24) | ((data[pos+1] & 0xFF) << 16) | ((data[pos+2] & 0xFF) << 8) | (data[pos+3] & 0xFF);
        double scaleLat = ((double) scaleLatRaw) / Math.pow(2, 8); pos += 4;
        int ni = readUint32(pos); pos += 4;
        int nj = readUint32(pos); pos += 4;
        double la1 = readUint32(pos) / 1e7; pos += 4;
        double lo1 = readUint32(pos) / 1e7; pos += 4;
        byte res = readUint8(pos); pos += 1;
        double dx = readUint32(pos) / 1e6; pos += 4;
        double dy = readUint32(pos) / 1e6; pos += 4;
        byte scan = readUint8(pos); pos += 1;
        pos += (sec3Len - 40);
        Map<String, Object> sec3 = new HashMap<>();
        sec3.put("len", sec3Len);
        sec3.put("source", source);
        sec3.put("ndp", ndp);
        sec3.put("temp_num", tempNum);
        sec3.put("shape", shape);
        sec3.put("scale_lat", scaleLat);
        sec3.put("ni", ni);
        sec3.put("nj", nj);
        sec3.put("la1", la1);
        sec3.put("lo1", lo1);
        sec3.put("res", res);
        sec3.put("dx", dx);
        sec3.put("dy", dy);
        sec3.put("scan", scan);
        properties.put("sec3", sec3);
        // Similar for Sec4 and Sec5 (omitted for brevity; follow pattern)
        // Sec4 Template 4.0
        // ... (implement similarly)
        // Sec5 Template 5.0
        // ... (implement similarly)
    }

    public void write(String filename, Map<String, Object> props) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(filename)) {
            // Section 0
            fos.write("GRIB".getBytes());
            fos.write(new byte[]{0, 0, 0, 2}); // Reserved + edition
            fos.write(ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putLong(1000L).array()); // Placeholder len
            // Section 1 (example)
            Map<String, Object> sec1 = (Map) props.get("sec1");
            byte[] sec1Bytes = new byte[21];
            // Pack using ByteBuffer (simplified; expand for full)
            fos.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(21).array());
            fos.write(sec1Bytes); // Populate from props
            // Add other sections and 7777 similarly
            fos.write(new byte[]{0x77, 0x77, 0x77, 0x77});
        }
    }

    public void printProperties() {
        System.out.println(properties);
    }

    // Main for testing
    public static void main(String[] args) throws IOException {
        GRIB2File g = new GRIB2File("test.grb");
        g.printProperties();
        // g.write("output.grb", g.properties);
    }
}

6. JavaScript Class for .GRB Handling

Node.js compatible (uses fs and Buffer; browser-adaptable with File API; basic read/write for common templates).

const fs = require('fs');

class GRIB2File {
  constructor(filename = null) {
    this.data = null;
    this.properties = {};
    if (filename) this.read(filename);
  }

  readUint32(pos) {
    return (this.data[pos] << 24) | (this.data[pos + 1] << 16) | (this.data[pos + 2] << 8) | this.data[pos + 3];
  }

  readUint16(pos) {
    return (this.data[pos] << 8) | this.data[pos + 1];
  }

  readUint8(pos) {
    return this.data[pos];
  }

  readInt16(pos) {
    const v = this.readUint16(pos);
    return v > 0x7FFF ? v - 0x10000 : v;
  }

  readFloat32(pos) {
    const buffer = Buffer.alloc(4);
    this.data.copy(buffer, 0, pos, pos + 4);
    return buffer.readFloatBE(0);
  }

  read(filename) {
    this.data = fs.readFileSync(filename);
    let pos = 0;
    // Section 0
    const gribId = this.data.toString('ascii', pos, pos + 4);
    pos += 8;
    const msgLen = (this.readUint32(pos) << 32) | this.readUint32(pos + 4);
    pos += 8;
    this.properties.sec0 = { grib_id: gribId, msg_len: msgLen };
    // Section 1
    let sec1Len = this.readUint32(pos); pos += 4;
    let sec1Num = this.readUint8(pos); pos += 1;
    let center = this.readUint16(pos); pos += 2;
    let subcenter = this.readUint16(pos); pos += 2;
    let masterVer = this.readUint8(pos); pos += 1;
    let localVer = this.readUint8(pos); pos += 1;
    let refSig = this.readUint8(pos); pos += 1;
    let year = this.readUint16(pos); pos += 2;
    let month = this.readUint8(pos); pos += 1;
    let day = this.readUint8(pos); pos += 1;
    let hour = this.readUint8(pos); pos += 1;
    let minute = this.readUint8(pos); pos += 1;
    let second = this.readUint8(pos); pos += 1;
    let prodStatus = this.readUint8(pos); pos += 1;
    let dataType = this.readUint8(pos); pos += 1;
    pos += (sec1Len - 21);
    this.properties.sec1 = { len: sec1Len, center, subcenter, masterVer, localVer, refSig, year, month, day, hour, minute, second, prodStatus, dataType };
    // Section 3 (Template 3.0)
    let sec3Len = this.readUint32(pos); pos += 4;
    let sec3Num = this.readUint8(pos); pos += 1;
    let source = this.readUint8(pos); pos += 1;
    let ndp = this.readUint32(pos); pos += 4;
    let octList = this.readUint8(pos); pos += 1;
    let interp = this.readUint8(pos); pos += 1;
    let tempNum = this.readUint16(pos); pos += 2;
    let shape = this.readUint8(pos); pos += 1;
    let scaleLatRaw = this.readUint32(pos); pos += 4;
    let scaleLat = scaleLatRaw / Math.pow(2, 8);
    let ni = this.readUint32(pos); pos += 4;
    let nj = this.readUint32(pos); pos += 4;
    let la1 = this.readUint32(pos) / 1e7; pos += 4;
    let lo1 = this.readUint32(pos) / 1e7; pos += 4;
    let res = this.readUint8(pos); pos += 1;
    let dx = this.readUint32(pos) / 1e6; pos += 4;
    let dy = this.readUint32(pos) / 1e6; pos += 4;
    let scan = this.readUint8(pos); pos += 1;
    pos += (sec3Len - 40);
    this.properties.sec3 = { len: sec3Len, source, ndp, tempNum, shape, scaleLat, ni, nj, la1, lo1, res, dx, dy, scan };
    // Section 4 (Template 4.0) - similar implementation
    // ... (omit for brevity; mirror Python)
    // Section 5 (Template 5.0) - similar
    // ...
  }

  write(filename, properties = null) {
    if (!properties) properties = this.properties;
    const buffer = Buffer.alloc(1000); // Placeholder
    let pos = 0;
    // Section 0
    buffer.write('GRIB', pos); pos += 4;
    buffer.writeUInt8(0, pos++); buffer.writeUInt8(0, pos++); // Reserved
    buffer.writeUInt8(0, pos++); // Discipline
    buffer.writeUInt8(2, pos++); // Edition
    buffer.writeBigUInt64BE(BigInt(1000), pos); pos += 8;
    // Section 1 - pack similarly
    // ...
    fs.writeFileSync(filename, buffer.slice(0, pos));
    // Append 7777
    fs.appendFileSync(filename, Buffer.from([0x77, 0x77, 0x77, 0x77]));
  }

  printProperties() {
    console.log(JSON.stringify(this.properties, null, 2));
  }
}

// Usage
// const g = new GRIB2File('test.grb');
// g.printProperties();
// g.write('output.grb');
module.exports = GRIB2File;

7. C Class (Struct with Functions) for .GRB Handling

Uses standard C (no classes; struct + functions; assumes GRIB2 common templates; read/write with fread/fwrite).

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct {
    uint64_t msg_len;
    char grib_id[5];
    // Sec1
    uint32_t sec1_len;
    uint16_t center;
    uint16_t subcenter;
    uint8_t master_ver;
    uint8_t local_ver;
    uint8_t ref_sig;
    uint16_t year;
    uint8_t month;
    uint8_t day;
    uint8_t hour;
    uint8_t minute;
    uint8_t second;
    uint8_t prod_status;
    uint8_t data_type;
    // Sec3 (Template 3.0)
    uint32_t sec3_len;
    uint8_t source;
    uint32_t ndp;
    uint16_t temp_num;
    uint8_t shape;
    double scale_lat;
    uint32_t ni;
    uint32_t nj;
    double la1;
    double lo1;
    uint8_t res;
    double dx;
    double dy;
    uint8_t scan;
    // Add Sec4/Sec5 similarly
} GRIBProperties;

typedef struct {
    uint8_t* data;
    size_t size;
    GRIBProperties props;
} GRIB2File;

uint32_t read_uint32(const uint8_t* data, size_t* pos) {
    uint32_t v = (data[*pos] << 24) | (data[(*pos)+1] << 16) | (data[(*pos)+2] << 8) | data[(*pos)+3];
    *pos += 4;
    return v;
}

uint16_t read_uint16(const uint8_t* data, size_t* pos) {
    uint16_t v = (data[*pos] << 8) | data[(*pos)+1];
    *pos += 2;
    return v;
}

uint8_t read_uint8(const uint8_t* data, size_t* pos) {
    uint8_t v = data[*pos];
    *pos += 1;
    return v;
}

int16_t read_int16(const uint8_t* data, size_t* pos) {
    int16_t v = (int16_t)read_uint16(data, pos);
    return v;
}

float read_float32(const uint8_t* data, size_t* pos) {
    // Simple IEEE unpack (big-endian)
    uint32_t bits = read_uint32(data, pos);
    float* pf = (float*)&bits;
    return *pf;
}

void read_grb(GRIB2File* g, const char* filename) {
    FILE* f = fopen(filename, "rb");
    fseek(f, 0, SEEK_END);
    g->size = ftell(f);
    fseek(f, 0, SEEK_SET);
    g->data = malloc(g->size);
    fread(g->data, 1, g->size, f);
    fclose(f);
    size_t pos = 0;
    // Section 0
    memcpy(g->props.grib_id, g->data, 4);
    g->props.grib_id[4] = '\0';
    pos += 8;
    uint32_t len_high = read_uint32(g->data, &pos);
    uint32_t len_low = read_uint32(g->data, &pos);
    g->props.msg_len = ((uint64_t)len_high << 32) | len_low;
    // Section 1
    g->props.sec1_len = read_uint32(g->data, &pos);
    read_uint8(g->data, &pos); // sec num
    g->props.center = read_uint16(g->data, &pos);
    g->props.subcenter = read_uint16(g->data, &pos);
    g->props.master_ver = read_uint8(g->data, &pos);
    g->props.local_ver = read_uint8(g->data, &pos);
    g->props.ref_sig = read_uint8(g->data, &pos);
    g->props.year = read_uint16(g->data, &pos);
    g->props.month = read_uint8(g->data, &pos);
    g->props.day = read_uint8(g->data, &pos);
    g->props.hour = read_uint8(g->data, &pos);
    g->props.minute = read_uint8(g->data, &pos);
    g->props.second = read_uint8(g->data, &pos);
    g->props.prod_status = read_uint8(g->data, &pos);
    g->props.data_type = read_uint8(g->data, &pos);
    pos += (g->props.sec1_len - 21);
    // Section 3 Template 3.0
    g->props.sec3_len = read_uint32(g->data, &pos);
    read_uint8(g->data, &pos); // sec num
    g->props.source = read_uint8(g->data, &pos);
    g->props.ndp = read_uint32(g->data, &pos);
    read_uint8(g->data, &pos); // oct_list
    read_uint8(g->data, &pos); // interp
    g->props.temp_num = read_uint16(g->data, &pos);
    g->props.shape = read_uint8(g->data, &pos);
    int32_t scale_lat_raw = (int32_t)read_uint32(g->data, &pos);
    g->props.scale_lat = (double)scale_lat_raw / (1 << 8);
    g->props.ni = read_uint32(g->data, &pos);
    g->props.nj = read_uint32(g->data, &pos);
    g->props.la1 = read_uint32(g->data, &pos) / 1e7;
    g->props.lo1 = read_uint32(g->data, &pos) / 1e7;
    g->props.res = read_uint8(g->data, &pos);
    g->props.dx = read_uint32(g->data, &pos) / 1e6;
    g->props.dy = read_uint32(g->data, &pos) / 1e6;
    g->props.scan = read_uint8(g->data, &pos);
    pos += (g->props.sec3_len - 40);
    // Similar for Sec4/Sec5
    // ...
}

void write_grb(const GRIB2File* g, const char* filename) {
    FILE* f = fopen(filename, "wb");
    // Section 0
    fwrite("GRIB", 1, 4, f);
    fwrite("\x00\x00\x00\x02", 1, 4, f); // Reserved + edition
    uint64_t dummy_len = 1000;
    uint32_t high = dummy_len >> 32;
    uint32_t low = dummy_len & 0xFFFFFFFF;
    fwrite(&high, 4, 1, f);
    fwrite(&low, 4, 1, f);
    // Section 1
    uint32_t sec1_len = 21;
    fwrite(&sec1_len, 4, 1, f);
    uint8_t sec_num = 1;
    fwrite(&sec_num, 1, 1, f);
    uint16_t center = g->props.center;
    fwrite(&center, 2, 1, f);
    // Continue packing other fields...
    // End
    uint32_t end = 0x77777777;
    fwrite(&end, 4, 1, f);
    fclose(f);
}

void print_properties(const GRIBProperties* p) {
    printf("GRIB ID: %s\n", p->grib_id);
    printf("Msg Len: %lu\n", p->msg_len);
    printf("Center: %u\n", p->center);
    printf("Year: %u\n", p->year);
    // Print all...
    printf("Scale Lat: %f\n", p->scale_lat);
    printf("NI: %u, NJ: %u\n", p->ni, p->nj);
    // ...
}

int main() {
    GRIB2File g = {0};
    read_grb(&g, "test.grb");
    print_properties(&g.props);
    write_grb(&g, "output.grb");
    free(g.data);
    return 0;
}