/**
* 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 };