@deepgis/dem-dynamic-terrain
Version:
使用 GDAL 制作地形瓦片,支持 mapbox 和 terrarium 两种编码输出格式
165 lines (164 loc) • 6.61 kB
JavaScript
import node_path from "node:path";
import gdal_async from "gdal-async";
function mapboxEncode(height) {
const value = Math.floor((height + 10000) * 10);
const r = value >> 16;
const g = value >> 8 & 0x0000FF;
const b = 0x0000FF & value;
return [
r,
g,
b
];
}
function terrariumEncode(height) {
height += 32768;
const r = Math.floor(height / 256.0);
const g = Math.floor(height % 256);
const b = Math.floor((height - Math.floor(height)) * 256.0);
return [
r,
g,
b
];
}
function getDriverByName(driverName) {
const length = gdal_async.drivers.count();
const nameNormal = driverName.toUpperCase();
for(let i = 0; i < length; i++){
const driver = gdal_async.drivers.get(i);
if (driver.description === nameNormal) return driver;
}
throw new Error(`\u{5F53}\u{524D}gdal\u{4E2D}\u{4E0D}\u{5B58}\u{5728}\u{8F93}\u{5165}\u{7684}\u{9A71}\u{52A8}\u{540D}\u{79F0}${nameNormal}`);
}
let dataset;
let noData;
let memDriver;
let pngDriver;
let outTileSize1;
const invalidColor = [
1,
134,
160
];
function forEachHeightBuffer(heightBuffer, encode) {
const channelLength = heightBuffer.length;
const rBuffer = new Uint8Array(channelLength);
const gBuffer = new Uint8Array(channelLength);
const bBuffer = new Uint8Array(channelLength);
const aBuffer = new Uint8Array(channelLength);
for(let i = 0; i < channelLength; i++){
const heightVal = heightBuffer[i];
let color;
color = heightVal === noData ? invalidColor : encode(heightVal);
rBuffer[i] = color[0];
gBuffer[i] = color[1];
bBuffer[i] = color[2];
aBuffer[i] = 255;
}
return [
rBuffer,
gBuffer,
bBuffer,
aBuffer
];
}
function writeTerrainTile(overviewInfo, readinfo, writeinfo, encoding) {
let readband;
readband = void 0 === overviewInfo.index ? readinfo.ds.bands.get(1) : readinfo.ds.bands.get(1).overviews.get(overviewInfo.index);
let dataType = readband.dataType;
let heightBuffer;
if (dataType == gdal_async.GDT_Byte) heightBuffer = new Uint8Array(writeinfo.wxsize * writeinfo.wysize);
else if (dataType === gdal_async.GDT_Int16) heightBuffer = new Int16Array(writeinfo.wxsize * writeinfo.wysize);
else if (dataType === gdal_async.GDT_Float32) heightBuffer = new Float32Array(writeinfo.wxsize * writeinfo.wysize);
else if ('Int8' === dataType) {
heightBuffer = new Int16Array(writeinfo.wxsize * writeinfo.wysize);
dataType = gdal_async.GDT_Int16;
}
readband.pixels.read(readinfo.rx, readinfo.ry, readinfo.rxsize, readinfo.rysize, heightBuffer, {
buffer_width: writeinfo.wxsize,
buffer_height: writeinfo.wysize,
data_type: dataType
});
let encodeBuffers;
if ('mapbox' === encoding) encodeBuffers = forEachHeightBuffer(heightBuffer, mapboxEncode);
else if ('terrarium' === encoding) encodeBuffers = forEachHeightBuffer(heightBuffer, terrariumEncode);
const _length = outTileSize1 * outTileSize1;
const r = new Uint8Array(_length);
const g = new Uint8Array(_length);
const b = new Uint8Array(_length);
const a = new Uint8Array(_length);
for(let i = 0; i < _length; i++){
r[i] = invalidColor[0];
g[i] = invalidColor[1];
b[i] = invalidColor[2];
a[i] = 255;
}
const defaultBuffers = [
r,
g,
b,
a
];
[
1,
2,
3,
4
].forEach((index)=>{
const writeband = writeinfo.ds.bands.get(index);
writeband.pixels.write(0, 0, outTileSize1, outTileSize1, defaultBuffers[index - 1]);
writeband.pixels.write(writeinfo.wx, writeinfo.wy, writeinfo.wxsize, writeinfo.wysize, encodeBuffers[index - 1]);
});
}
function writeRgbTile(overviewInfo, readinfo, writeinfo) {
const bands = readinfo.ds.bands.count();
if (3 !== bands) throw new Error("\u8F93\u5165\u7684\u6805\u683C\u6587\u4EF6\u6CE2\u6BB5\u6570\u4E0D\u4E3A3");
for(let i = 0; i < bands; i++){
const bundIndex = i + 1;
let readband;
readband = void 0 === overviewInfo.index ? readinfo.ds.bands.get(bundIndex) : readinfo.ds.bands.get(bundIndex).overviews.get(overviewInfo.index);
const dataType = readband.dataType;
let colorBuffer;
if (dataType === gdal_async.GDT_Int16) colorBuffer = new Int16Array(writeinfo.wxsize * writeinfo.wysize);
else if (dataType === gdal_async.GDT_Float32) colorBuffer = new Float32Array(writeinfo.wxsize * writeinfo.wysize);
else if (dataType === gdal_async.GDT_Byte) colorBuffer = new Uint8Array(writeinfo.wxsize * writeinfo.wysize);
readband.pixels.read(readinfo.rx, readinfo.ry, readinfo.rxsize, readinfo.rysize, colorBuffer, {
buffer_width: writeinfo.wxsize,
buffer_height: writeinfo.wysize,
data_type: dataType
});
const _length = outTileSize1 * outTileSize1;
const blank = new Uint8Array(_length);
for(let i = 0; i < _length; i++)blank[i] = 0;
const writeband = writeinfo.ds.bands.get(bundIndex);
writeband.pixels.write(0, 0, outTileSize1, outTileSize1, blank);
writeband.pixels.write(writeinfo.wx, writeinfo.wy, writeinfo.wxsize, writeinfo.wysize, colorBuffer);
}
const alphaBand = writeinfo.ds.bands.get(4);
const alphaBuffer = new Uint8Array(outTileSize1 * outTileSize1);
for(let i = 0; i < outTileSize1 * outTileSize1; i++)alphaBuffer[i] = 0;
alphaBand.pixels.write(writeinfo.wx, writeinfo.wy, writeinfo.wxsize, writeinfo.wysize, alphaBuffer);
}
function createTile(createInfo) {
const { outTileSize, overviewInfo, rb, wb, encoding, dsPath, x, y, z, outputTile } = createInfo;
outTileSize1 = outTileSize;
if (!dataset) {
dataset = gdal_async.open(dsPath, 'r');
noData = dataset.bands.get(1).noDataValue;
}
if (!memDriver) memDriver = getDriverByName('mem');
const msmDS = memDriver.create('', outTileSize, outTileSize, 4);
rb.ds = dataset;
wb.ds = msmDS;
const type = createInfo.type;
'terrain' === type ? writeTerrainTile(overviewInfo, rb, wb, encoding) : writeRgbTile(overviewInfo, rb, wb);
const pngPath = node_path.join(outputTile, z.toString(), x.toString(), `${y}.png`);
if (!pngDriver) pngDriver = getDriverByName('png');
const pngDs = pngDriver.createCopy(pngPath, msmDS);
msmDS.flush();
msmDS.close();
pngDs.close();
}
const create_tile = createTile;
export { create_tile as default };