From 76d8ad1b9ba9a2be748fdd0b38cd2215be72a5fa Mon Sep 17 00:00:00 2001 From: Blayne Chard Date: Wed, 22 Jan 2025 12:14:54 +1300 Subject: [PATCH] feat(tiler-sharp): support uint32 and uint8 source datasets --- packages/lambda-tiler/src/cli/render.tile.ts | 4 +- .../src/pipeline/decompressor.lerc.ts | 38 +++++++++++++----- .../tiler-sharp/src/pipeline/decompressor.ts | 13 +++++- .../src/pipeline/pipeline.color.ramp.ts | 8 +++- .../src/pipeline/pipeline.resize.ts | 40 ++++++++++++++++--- 5 files changed, 84 insertions(+), 19 deletions(-) diff --git a/packages/lambda-tiler/src/cli/render.tile.ts b/packages/lambda-tiler/src/cli/render.tile.ts index cf45995ce..de9366750 100644 --- a/packages/lambda-tiler/src/cli/render.tile.ts +++ b/packages/lambda-tiler/src/cli/render.tile.ts @@ -9,8 +9,8 @@ import { extname } from 'path'; import { TileXyzRaster } from '../routes/tile.xyz.raster.js'; // Render configuration -const source = fsa.toUrl(`/home/blacha/data/elevation/christchurch_2020-2021/`); -const tile = fromPath('/14/7898/8615.webp'); +const source = fsa.toUrl(`/home/blacha/data/nz-elevation/hillshade`); +const tile = fromPath('/9/250/256.png'); const pipeline: string | null = 'color-ramp'; let tileMatrix: TileMatrixSet | null = null; diff --git a/packages/tiler-sharp/src/pipeline/decompressor.lerc.ts b/packages/tiler-sharp/src/pipeline/decompressor.lerc.ts index 0e09d8a0b..2ff33bd97 100644 --- a/packages/tiler-sharp/src/pipeline/decompressor.lerc.ts +++ b/packages/tiler-sharp/src/pipeline/decompressor.lerc.ts @@ -9,9 +9,6 @@ export const LercDecompressor: Decompressor = { await Lerc.load(); const bytes = Lerc.decode(tile); - if (bytes.pixelType !== 'F32') { - throw new Error(`Lerc: Invalid output pixelType:${bytes.pixelType} from:${source.source.url.href}`); - } if (bytes.depthCount !== 1) { throw new Error(`Lerc: Invalid output depthCount:${bytes.depthCount} from:${source.source.url.href}`); } @@ -19,13 +16,34 @@ export const LercDecompressor: Decompressor = { throw new Error(`Lerc: Invalid output bandCount:${bytes.pixels.length} from:${source.source.url.href}`); } - return { - pixels: bytes.pixels[0] as Float32Array, - width: bytes.width, - height: bytes.height, - channels: 1, - depth: 'float32', - }; + switch (bytes.pixelType) { + case 'F32': + return { + pixels: bytes.pixels[0] as Float32Array, + width: bytes.width, + height: bytes.height, + channels: 1, + depth: 'float32', + }; + case 'U32': + return { + pixels: bytes.pixels[0] as Uint32Array, + width: bytes.width, + height: bytes.height, + channels: 1, + depth: 'uint32', + }; + case 'U8': + return { + pixels: bytes.pixels[0] as Uint8Array, + width: bytes.width, + height: bytes.height, + channels: 1, + depth: 'uint8', + }; + } + + throw new Error(`Lerc: Invalid output pixelType:${bytes.pixelType} from:${source.source.url.href}`); }, }; diff --git a/packages/tiler-sharp/src/pipeline/decompressor.ts b/packages/tiler-sharp/src/pipeline/decompressor.ts index 6ca75fd2c..3627530b4 100644 --- a/packages/tiler-sharp/src/pipeline/decompressor.ts +++ b/packages/tiler-sharp/src/pipeline/decompressor.ts @@ -1,6 +1,14 @@ import { CompositionTiff } from '@basemaps/tiler'; import { Tiff } from '@cogeotiff/core'; +export interface DecompressedInterleavedUint32 { + pixels: Uint32Array; + depth: 'uint32'; + channels: number; + width: number; + height: number; +} + export interface DecompressedInterleavedFloat { pixels: Float32Array; depth: 'float32'; @@ -18,7 +26,10 @@ export interface DecompressedInterleavedUint8 { } // One buffer containing all bands -export type DecompressedInterleaved = DecompressedInterleavedFloat | DecompressedInterleavedUint8; +export type DecompressedInterleaved = + | DecompressedInterleavedFloat + | DecompressedInterleavedUint8 + | DecompressedInterleavedUint32; export interface Decompressor { type: 'image/webp' | 'application/lerc'; diff --git a/packages/tiler-sharp/src/pipeline/pipeline.color.ramp.ts b/packages/tiler-sharp/src/pipeline/pipeline.color.ramp.ts index 30e201e24..1095a6785 100644 --- a/packages/tiler-sharp/src/pipeline/pipeline.color.ramp.ts +++ b/packages/tiler-sharp/src/pipeline/pipeline.color.ramp.ts @@ -42,7 +42,11 @@ export class ColorRamp { } } -export const ramp = new ColorRamp(DefaultColorRamp); +export const Ramps: Record = { + float32: new ColorRamp(DefaultColorRamp), + uint8: new ColorRamp(`0 0 0 0 255\n255 255 255 255 255`), + uint32: new ColorRamp(`0 0 0 0 255\n${2 ** 32} 255 255 255 255`), +}; export const PipelineColorRamp: Pipeline = { type: 'color-ramp', @@ -56,6 +60,8 @@ export const PipelineColorRamp: Pipeline = { height: data.height, }; + const ramp = Ramps[data.depth]; + const size = data.width * data.height; const noData = comp.asset.images[0].noData; diff --git a/packages/tiler-sharp/src/pipeline/pipeline.resize.ts b/packages/tiler-sharp/src/pipeline/pipeline.resize.ts index ae8381f7b..4a08228fd 100644 --- a/packages/tiler-sharp/src/pipeline/pipeline.resize.ts +++ b/packages/tiler-sharp/src/pipeline/pipeline.resize.ts @@ -24,7 +24,6 @@ export function cropResize( // Currently very limited supported input parameters if (data.channels !== 1) throw new Error('Unable to crop-resize more than one channel got:' + data.channels); - if (data.depth !== 'float32') throw new Error('Unable to crop-resize other than float32 got:' + data.depth); // Area of the source data that needs to be resampled const source = { x: 0, y: 0, width: data.width, height: data.height }; @@ -93,7 +92,8 @@ function resizeNearest( const invScale = 1 / target.scale; // Resample the input tile into the output tile using a nearest neighbor approach - const outputBuffer = new Float32Array(target.width * target.height); + const ret = getOutputBuffer(data, target); + const outputBuffer = ret.pixels; for (let y = 0; y < target.height; y++) { let sourceY = Math.round((y + 0.5) * invScale + source.y); if (sourceY > maxHeight) sourceY = maxHeight; @@ -106,7 +106,36 @@ function resizeNearest( } } - return { pixels: outputBuffer, width: target.width, height: target.height, depth: 'float32', channels: 1 }; + return ret; +} + +function getOutputBuffer(source: DecompressedInterleaved, target: Size & { scale: number }): DecompressedInterleaved { + switch (source.depth) { + case 'uint8': + return { + pixels: new Uint8Array(target.width * target.height), + width: target.width, + height: target.height, + depth: source.depth, + channels: 1, + }; + case 'float32': + return { + pixels: new Float32Array(target.width * target.height), + width: target.width, + height: target.height, + depth: source.depth, + channels: 1, + }; + case 'uint32': + return { + pixels: new Uint32Array(target.width * target.height), + width: target.width, + height: target.height, + depth: source.depth, + channels: 1, + }; + } } function resizeBilinear( @@ -120,7 +149,8 @@ function resizeBilinear( const maxWidth = Math.min(comp.source.width, data.width) - 2; const maxHeight = Math.min(comp.source.height, data.height) - 2; - const outputBuffer = new Float32Array(target.width * target.height); + const ret = getOutputBuffer(data, target); + const outputBuffer = ret.pixels; for (let y = 0; y < target.height; y++) { const sourceY = Math.min((y + 0.5) * invScale + source.y, maxHeight); const minY = Math.floor(sourceY); @@ -167,5 +197,5 @@ function resizeBilinear( } } - return { pixels: outputBuffer, width: target.width, height: target.height, depth: 'float32', channels: 1 }; + return ret; }