Source: getTileData.js

/**
 * Convert values to Float32Array
 * @private
 * @param {Array|TypedArray} values - Input values
 * @returns {Float32Array} Converted array
 */
const toFloat32Array = (values) => {
    if (values instanceof Float32Array) {
        return values;
    }

    const output = new Float32Array(values.length);
    if (ArrayBuffer.isView(values)) {
        output.set(values);
        return output;
    }

    for (let i = 0; i < values.length; i++) {
        const v = Number(values[i]);
        output[i] = Number.isFinite(v) ? v : 0;
    }
    return output;
};

/**
 * Convert raster data to band-major Float32 format
 * Handles various input layouts: band-separate, interleaved, nested arrays
 * 
 * @private
 * @param {Array|TypedArray} srcData - Source raster data
 * @param {number} planeSize - Width * height
 * @param {number} bandCount - Number of bands
 * @param {string} layout - Data layout: 'band-separate' or 'interleaved'
 * @returns {Float32Array} Band-major float32 array
 */
const toBandMajorFloat32 = (srcData, planeSize, bandCount, layout) => {
    if (
        srcData instanceof Float32Array &&
        srcData.length === planeSize * bandCount &&
        layout === 'band-separate'
    ) {
        return srcData;
    }

    if (
        ArrayBuffer.isView(srcData) &&
        srcData.length === planeSize * bandCount &&
        layout === 'band-separate'
    ) {
        return toFloat32Array(srcData);
    }

    if (Array.isArray(srcData)) {
        if (bandCount === 1 && srcData[0]) {
            return toFloat32Array(srcData[0]);
        }

        const dest = new Float32Array(planeSize * bandCount);
        for (let b = 0; b < bandCount; b++) {
            const band = srcData[b];
            if (!band) {
                continue;
            }

            const base = b * planeSize;
            if (band instanceof Float32Array) {
                dest.set(band, base);
            } else if (ArrayBuffer.isView(band)) {
                dest.set(band, base);
            } else {
                for (let i = 0; i < planeSize; i++) {
                    const v = Number(band[i]);
                    dest[base + i] = Number.isFinite(v) ? v : 0;
                }
            }
        }
        return dest;
    }

    if (bandCount === 1) {
        return toFloat32Array(srcData);
    }

    const interleaved = srcData;
    const dest = new Float32Array(planeSize * bandCount);
    const isTypedInterleaved = ArrayBuffer.isView(interleaved);

    for (let b = 0; b < bandCount; b++) {
        const destBase = b * planeSize;
        let srcIndex = b;
        for (let i = 0; i < planeSize; i++) {
            const v = interleaved[srcIndex];
            if (isTypedInterleaved) {
                dest[destBase + i] = v;
            } else {
                const n = Number(v);
                dest[destBase + i] = Number.isFinite(n) ? n : 0;
            }
            srcIndex += bandCount;
        }
    }
    return dest;
};

/**
 * Build band data structure from raster array
 * @private
 * @param {Object} array - Raster array with width, height, count, data, layout
 * @returns {Float32Array} Band-major float32 data
 * @throws {Error} If raster array is missing or invalid
 */
const buildBandDataFromRaster = (array) => {
    if (!array) {
        throw new Error('buildBandDataFromRaster: missing raster array');
    }
    const width = array.width;
    const height = array.height;
    const planeSize = width * height;

    let bandCount = Number(array.count) || 1;
    const srcData = array.data;
    const layout = array.layout;

    if (Array.isArray(srcData)) {
        bandCount = srcData.length || bandCount;
    }

    const concatFloat = toBandMajorFloat32(srcData, planeSize, bandCount, layout);

    return { data: concatFloat, width, height, bandCount };
};

/**
 * Fetch and process tile data from a GeoTIFF for GPU rendering
 * Converts raster data to band-major Float32Array format suitable for GPU.js
 * 
 * @param {Object} image - GeoTIFF image object with fetchTile method
 * @param {Object} [options={}] - Fetch options
 * @param {number} options.x - Tile X coordinate
 * @param {number} options.y - Tile Y coordinate
 * @param {AbortSignal} [options.signal] - Abort signal for fetch cancellation
 * @param {Object} options.device - WebGL device/context
 * @returns {Promise<Object>} Promise resolving to tile data
 * @property {Float32Array} data - Band-major float32 raster data
 * @property {number} width - Tile width in pixels
 * @property {number} height - Tile height in pixels
 * @property {number} bandCount - Number of bands
 * @property {Object} device - WebGL device
 * @throws {Error} If raster array is missing from fetched tile
 * @public
 */
const getTileData = async (image, options = {}) => {
    const { x, y, signal, device } = options;
    const tile = await image.fetchTile(x, y, { signal, boundless: false });
    const { array } = tile;

    if (!array) {
        throw new Error('getTileData: missing raster array from fetched tile');
    }

    const bandData = buildBandDataFromRaster(array);
    bandData.device = device;
    return bandData;
};

export { getTileData };