external/gridded-data/netcdf/netcdf.js

import * as divisors from "../../../modules/visualize/divisors.js";

/**
 * NetCDF data format loader
 * Provides lazy loading for NetCDF format libraries and dependencies
 * @external NetCDFLoader
 */

let netcdfLibraries = null;
let isNetCDFLoaded = false;

/**
 * Loads NetCDF-related libraries and dependencies
 * @method load
 * @memberof NetCDFLoader
 * @param {Object} [options] - Loading options
 * @param {boolean} [options.includeJS=true] - Include JavaScript NetCDF library
 * @param {boolean} [options.includeGeo=true] - Include geospatial extensions
 * @returns {Promise<Object>} Promise resolving to loaded NetCDF libraries
 * @example
 * await netcdfLoader.load({ includeJS: true, includeGeo: true });
 */
async function load(options = {}) {
  const { includeJS = true, includeGeo = true } = options;

  if (isNetCDFLoaded) {
    return netcdfLibraries;
  }

  try {
    console.log('Loading NetCDF libraries...');

    let libraries = [];

    if (includeJS) {
      // Load NetCDF.js library
      const netcdfJS = divisors.createScript({
        params: {
          src: "https://cdn.jsdelivr.net/npm/netcdfjs@1.2.2/build/netcdfjs.min.js",
          name: "netcdfjs"
        }
      });
      libraries.push(netcdfJS);
    }

    if (includeGeo) {
      // Load additional geospatial libraries for NetCDF
      const geoLibs = [
        divisors.createScript({
          params: {
            src: "https://cdn.jsdelivr.net/npm/geotiff@2.1.3/dist/geotiff.min.js",
            name: "geotiff"
          }
        }),
        divisors.createScript({
          params: {
            src: "https://cdn.jsdelivr.net/npm/proj4@2.9.0/dist/proj4.min.js",
            name: "proj4"
          }
        })
      ];
      libraries.push(...geoLibs);
    }

    // Wait for all scripts to load
    await Promise.all(
      libraries.map(lib => new Promise((resolve) => {
        lib.onload = resolve;
        lib.onerror = () => resolve(); // Continue even if error
      }))
    );

    // Create library interface
    const globalObj = typeof window !== 'undefined' ? window : globalThis;
    netcdfLibraries = {
      netcdfjs: globalObj.netcdfjs || null,
      geotiff: globalObj.GeoTIFF || null,
      proj4: globalObj.proj4 || null,
      loadedAt: new Date()
    };

    isNetCDFLoaded = true;
    console.log('NetCDF libraries loaded successfully');

    return netcdfLibraries;

  } catch (error) {
    console.error('Failed to load NetCDF libraries:', error);
    throw new Error(`NetCDF library loading failed: ${error.message}`);
  }
}

/**
 * Checks if NetCDF libraries are loaded
 * @method isLoaded
 * @memberof NetCDFLoader
 * @returns {boolean} True if libraries are loaded
 * @example
 * netcdfLoader.isLoaded();
 */
function isLoaded() {
  return isNetCDFLoaded && netcdfLibraries !== null;
}

/**
 * Gets information about the NetCDF loader
 * @method getInfo
 * @memberof NetCDFLoader
 * @returns {Object} Information about supported features
 * @example
 * netcdfLoader.getInfo();
 */
function getInfo() {
  return {
    name: 'NetCDF',
    description: 'Network Common Data Form for gridded data',
    version: '4.0+',
    features: [
      'Multi-dimensional arrays',
      'Metadata storage',
      'Compression support',
      'Coordinate systems',
      'Time series data'
    ],
    dependencies: [
      'netcdfjs (JavaScript NetCDF parser)',
      'geotiff (GeoTIFF support)',
      'proj4 (Coordinate transformations)'
    ]
  };
}

/**
 * Loads a NetCDF file from ArrayBuffer
 * @method loadFile
 * @memberof NetCDFLoader
 * @param {ArrayBuffer} buffer - NetCDF file buffer
 * @returns {Promise} Promise resolving to parsed NetCDF data
 * @example
 * const data = await netcdfLoader.loadFile(buffer);
 */
async function loadFile(buffer) {
  if (!isLoaded()) {
    await load();
  }

  if (!netcdfLibraries.netcdfjs) {
    throw new Error('NetCDF.js library not available');
  }

  try {
    const netcdfData = new netcdfLibraries.netcdfjs(buffer);
    return netcdfData;
  } catch (error) {
    throw new Error(`Failed to load NetCDF file: ${error.message}`);
  }
}

/**
 * Loads a NetCDF file from URL
 * @method loadFromURL
 * @memberof NetCDFLoader
 * @param {string} url - URL to NetCDF file
 * @returns {Promise} Promise resolving to parsed NetCDF data
 * @example
 * const data = await netcdfLoader.loadFromURL('https://example.com/data.nc');
 */
async function loadFromURL(url) {
  if (!isLoaded()) {
    await load();
  }

  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const buffer = await response.arrayBuffer();
    return await loadFile(buffer);
  } catch (error) {
    throw new Error(`Failed to load NetCDF from URL: ${error.message}`);
  }
}

/**
 * Extracts variable data from NetCDF
 * @method getVariable
 * @memberof NetCDFLoader
 * @param {Object} netcdfData - Parsed NetCDF data
 * @param {string} variableName - Name of variable to extract
 * @returns {Object} Variable data and metadata
 * @example
 * const variable = netcdfLoader.getVariable(data, 'temperature');
 */
function getVariable(netcdfData, variableName) {
  try {
    const variable = netcdfData.getVariable(variableName);
    const dimensions = variable.dimensions;
    const attributes = variable.attributes;
    const data = variable.read();

    return {
      name: variableName,
      data: data,
      dimensions: dimensions,
      attributes: attributes,
      shape: data.shape || []
    };
  } catch (error) {
    throw new Error(`Failed to extract variable ${variableName}: ${error.message}`);
  }
}

export default {
  load,
  isLoaded,
  getInfo,
  loadFile,
  loadFromURL,
  getVariable
};