tatry
Version:
Elevation API server.
112 lines (90 loc) • 3.56 kB
JavaScript
import { constants, open } from 'node:fs/promises';
import Debug from 'debug';
import { INVALID_ELEVATION } from '../interpolate.js';
import * as cache from './cache.js';
const debug = Debug('tatry:lame-tiff');
export default function lameTiff(filename, meta) {
const bytesPerPixel = 2;
const { tileWidth, tileHeight, tilesPerCol, tilesPerRow, littleEndian, offsets, byteCounts } = meta;
debug('%s - width: %d, height: %d', filename, meta.width, meta.height);
debug(
'tileWidth: %d, tileHeight: %d, tilesPerCol: %d, tilesPerRow: %d',
tileWidth,
tileHeight,
tilesPerCol,
tilesPerRow
);
const readInt16Fn = littleEndian ? Buffer.prototype.readInt16LE : Buffer.prototype.readInt16BE;
function readInt16(buffer, offset) {
return readInt16Fn.call(buffer, offset);
}
async function readRaster(windowX, windowY, windowWidth, windowHeight) {
const valueArray = new Int16Array(windowWidth * windowHeight);
valueArray.fill(INVALID_ELEVATION);
async function processTile(index, firstCol, firstLine) {
const buffer = await readTile(index);
for (let l = 0; l < windowHeight; l++) {
const line = l + windowY - firstLine;
if (line < 0 || line >= tileHeight) {
continue;
}
for (let c = 0; c < windowWidth; c++) {
const column = c + windowX - firstCol;
if (column < 0 || column >= tileWidth) {
continue;
}
const pixelOffset = column + line * tileWidth;
const offset = pixelOffset * bytesPerPixel;
if (debug.enabled) {
if (offset >= buffer.length) {
throw new RangeError(`Trying to read beyond buffer in ${filename} [${windowX}, ${windowY}]`);
}
}
valueArray[c + l * windowWidth] = readInt16(buffer, offset);
}
}
}
debug('Window %d %d %d %d', windowX, windowY, windowWidth, windowHeight);
const minXTile = Math.floor(windowX / tileWidth);
const minYTile = Math.floor(windowY / tileHeight);
const maxXTile = Math.floor((windowX + windowWidth - 1) / tileWidth);
const maxYTile = Math.floor((windowY + windowHeight - 1) / tileHeight);
debug('minXTile: %d, minYTile: %d, maxXTile: %d, maxYTile: %d', minXTile, minYTile, maxXTile, maxYTile);
const promises = [];
for (let yTile = minYTile; yTile <= maxYTile; ++yTile) {
const baseIndex = yTile * tilesPerRow;
const firstLine = yTile * tileHeight;
for (let xTile = minXTile; xTile <= maxXTile; ++xTile) {
const firstCol = xTile * tileWidth;
promises.push(processTile(baseIndex + xTile, firstCol, firstLine));
}
}
await Promise.all(promises);
return valueArray;
}
async function readTile(index) {
let item = cache.get(filename, index);
if (!item) {
debug('cache miss %s:%d', filename, index);
const offset = offsets[index];
const byteCount = byteCounts[index];
const promise = readFromFile(byteCount, offset);
item = { promise, byteCount };
cache.put(filename, index, item);
}
return item.promise;
}
async function readFromFile(length, position) {
meta.fh ??= open(filename, constants.O_RDONLY);
const b = Buffer.allocUnsafe(length);
const fh = await meta.fh;
const { buffer, bytesRead } = await fh.read(b, 0, length, position);
if (bytesRead !== length) {
throw new RangeError(`Requested ${length}, Read: ${bytesRead}`);
}
return buffer;
}
return {
readRaster
};
}