external/gridded-data/grib2/grib2-integration.js

// GRIB2 Integration Layer
// Integrates Gerard Llorach's GRIB2 research implementation with HydroLang
// This file uses the researcher's grib2utils.js as a foundation

import HydroLangGRIB2Parser from './grib2-parser.js';

/**
 * Integrated GRIB2 processing for HydroLang
 * Uses research implementation for real GRIB2 parsing
 */
class GRIB2Integration {

    constructor() {
        this.parser = new HydroLangGRIB2Parser();
    }

    /**
     * Parse GRIB2 buffer and extract grid data
     * @param {ArrayBuffer} buffer - GRIB2 file buffer
     * @param {Object} options - Extraction options
     * @returns {Promise<Object>} Parsed grid data
     */
    async parseGRIB2Buffer(buffer, options = {}) {
        console.log('Starting GRIB2 integration parsing...');

        try {
            // Use the research-based parser
            const parsedData = await this.parser.parseBuffer(buffer, options);

            console.log('GRIB2 integration parsing completed successfully');
            console.log('Grid dimensions:', parsedData.data.shape);
            console.log('Sample values:', parsedData.data.values[0]?.slice(0, 5));

            return parsedData;

        } catch (error) {
            console.error('GRIB2 integration parsing failed:', error);
            throw new Error(`GRIB2 integration error: ${error.message}`);
        }
    }

    /**
     * Extract GRIB2 data with proper error handling
     * @param {Object} grib2Data - GRIB2 data object
     * @param {Object} options - Extraction options
     * @returns {Promise<Object>} Extracted data
     */
    async extractGRIB2Data(grib2Data, options = {}) {
        const { parameter, level, bbox, startDate, endDate } = options;

        console.log('Extracting GRIB2 data with integration layer...', { parameter, level, bbox });

        try {
            // Get the actual file buffer
            const buffer = grib2Data?.buffer;
            if (!buffer) {
                throw new Error('No GRIB2 file buffer available for parsing');
            }

            // Validate GRIB2 file format first
            const view = new DataView(buffer);
            const magic = String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3));
            if (magic !== 'GRIB') {
                throw new Error('Invalid file format - not a GRIB2 file');
            }

            // Parse the actual GRIB2 content using research implementation
            const parsedData = await this.parseGRIB2Buffer(buffer, options);

            // Apply spatial subsetting if needed
            if (bbox && bbox.length === 4) {
                parsedData.data = this.applySpatialSubsetting(parsedData.data, parsedData.metadata.gridDefinition, bbox);
                parsedData.bbox = bbox;
            }

            console.log('GRIB2 data extraction completed successfully');
            console.log('Final grid shape:', parsedData.data.shape);

            return parsedData;

        } catch (error) {
            console.error('GRIB2 data extraction failed:', error);
            throw new Error(`GRIB2 extraction failed: ${error.message}`);
        }
    }

    /**
     * Apply spatial subsetting to GRIB2 data
     * @param {Object} data - Grid data
     * @param {Object} gridDef - Grid definition
     * @param {Array} bbox - Bounding box [west, south, east, north]
     * @returns {Object} Subsetted data
     */
    applySpatialSubsetting(data, gridDef, bbox) {
        if (!bbox || bbox.length !== 4) {
            return data;
        }

        const [west, south, east, north] = bbox;
        const { values, shape } = data;
        const [numRows, numCols] = shape;

        // Calculate grid indices for bbox
        const startCol = Math.max(0, Math.floor((west - gridDef.lonStart) / gridDef.incI));
        const endCol = Math.min(numCols - 1, Math.ceil((east - gridDef.lonStart) / gridDef.incI));
        const startRow = Math.max(0, Math.floor((south - gridDef.latStart) / gridDef.incJ));
        const endRow = Math.min(numRows - 1, Math.ceil((north - gridDef.latStart) / gridDef.incJ));

        console.log('Spatial subset indices:', { startRow, endRow, startCol, endCol });

        // Extract subset
        const subsetValues = [];
        for (let row = startRow; row <= endRow; row++) {
            if (values[row]) {
                subsetValues.push(values[row].slice(startCol, endCol + 1));
            }
        }

        return {
            values: subsetValues,
            shape: [subsetValues.length, subsetValues[0]?.length || 0]
        };
    }
}

// Export the integration layer
export default GRIB2Integration;

// Helper function to create coordinates for subset
function generateSubsetCoordinates(gridDef, bbox, subsetShape) {
    const [west, south, east, north] = bbox;
    const [rows, cols] = subsetShape;

    const latitudes = [];
    const longitudes = [];

    const latStep = (north - south) / Math.max(1, rows - 1);
    const lonStep = (east - west) / Math.max(1, cols - 1);

    for (let i = 0; i < rows; i++) {
        latitudes.push(south + i * latStep);
    }

    for (let j = 0; j < cols; j++) {
        longitudes.push(west + j * lonStep);
    }

    return { latitudes, longitudes };
}