@@ -26,7 +26,7 @@ import { COGLayer } from '@developmentseed/deck.gl-geotiff'
2626// the exports map). See deviation note in the file header.
2727// eslint-disable-next-line @typescript-eslint/ban-ts-comment
2828// @ts -ignore
29- import { Colormap , LinearRescale , FilterNoDataVal , createColormapTexture } from '@developmentseed/deck.gl-raster/gpu-modules'
29+ import { Colormap , LinearRescale , FilterNoDataVal , CreateTexture , createColormapTexture } from '@developmentseed/deck.gl-raster/gpu-modules'
3030import type { Texture } from '@luma.gl/core'
3131import { buildColormapLUT } from './colormapLUT'
3232
@@ -107,6 +107,64 @@ function lutToImageData(lut: Uint8ClampedArray): ImageData {
107107 return new ImageData ( lut , 256 , 1 )
108108}
109109
110+ /**
111+ * Custom tile loader for single-band COGs. Uploads the band as a single-channel
112+ * `r32float` GPU texture so the RAW pixel value lands in `color.r` for the
113+ * downstream LinearRescale + Colormap modules.
114+ *
115+ * This deliberately replaces `@developmentseed/deck.gl-geotiff`'s default
116+ * `inferRenderPipeline`, whose auto-inference currently only supports unsigned
117+ * integer COGs (it throws `non-unsigned integers not yet supported` for float,
118+ * SampleFormat 3 — the common case for scientific single-band rasters). By
119+ * supplying our own `getTileData` + `renderTile`, `COGLayer._parseGeoTIFF`
120+ * skips that inference entirely.
121+ *
122+ * Values of any numeric type are converted to Float32 and uploaded as
123+ * `r32float`, so a single rescale (in raw data units) is correct for float and
124+ * integer COGs alike. Sampled with nearest filtering (float-linear filtering is
125+ * not guaranteed in WebGL2).
126+ *
127+ * @param image A `GeoTIFF` (full-res) or `Overview`, selected per-zoom by
128+ * COGLayer's `_getTileDataCallback` wrapper.
129+ * @param options `{ device, x, y, signal, pool }` supplied by the wrapper.
130+ */
131+ async function cogGetTileData (
132+ image : any ,
133+ options : { device : any ; x : number ; y : number ; signal ?: AbortSignal ; pool ?: any }
134+ ) : Promise < { texture : any ; width : number ; height : number ; byteLength : number } | null > {
135+ const { device, x, y, signal, pool } = options
136+ const tile = await image . fetchTile ( x , y , { boundless : false , pool, signal } )
137+ const array = tile ?. array
138+ if ( ! array ) return null
139+ const width = array . width
140+ const height = array . height
141+ // Single-band: prefer flat `data`; fall back to first band for band-separate.
142+ let src = array . data
143+ if ( array . layout === 'band-separate' && array . bands && array . bands . length ) {
144+ src = array . bands [ 0 ]
145+ }
146+ const f32 = src instanceof Float32Array ? src : Float32Array . from ( src )
147+ const texture = device . createTexture ( {
148+ data : f32 ,
149+ format : 'r32float' ,
150+ width,
151+ height,
152+ mipmaps : false ,
153+ sampler : {
154+ minFilter : 'nearest' ,
155+ magFilter : 'nearest' ,
156+ addressModeU : 'clamp-to-edge' ,
157+ addressModeV : 'clamp-to-edge' ,
158+ } ,
159+ } )
160+ return { texture, width, height, byteLength : f32 . byteLength }
161+ }
162+
163+ /** Truthy placeholder so COGLayer._parseGeoTIFF skips the (float-unsupported)
164+ * default `inferRenderPipeline`. The real render pipeline is produced by the
165+ * overridden `_renderTileCallback()` below, so this is never invoked. */
166+ const RENDER_TILE_PLACEHOLDER = ( ) : null => null
167+
110168// ---------------------------------------------------------------------------
111169// ColormappedCOGLayer
112170// ---------------------------------------------------------------------------
@@ -170,12 +228,15 @@ export class ColormappedCOGLayer extends COGLayer<any> {
170228 // Return type is `any` to avoid TypeScript's return-type assignability
171229 // check against the parent signature (which uses the full RenderTileResult
172230 // union from @developmentseed /deck.gl-raster). The actual runtime type
173- // satisfies the union contract; this is validated by Task 8's render test.
231+ // satisfies the union contract.
232+ //
233+ // The pipeline is built from scratch — CreateTexture samples our r32float
234+ // tile texture into `color` (raw value in color.r), then composeColormapPipeline
235+ // appends [FilterNoDataVal?, LinearRescale, Colormap]. We do NOT call
236+ // super._renderTileCallback() because COGLayer's default renderTile pairs
237+ // with its default getTileData (inferRenderPipeline), which throws for float.
174238 // eslint-disable-next-line @typescript-eslint/no-explicit-any
175239 protected override _renderTileCallback ( ) : any {
176- const base = super . _renderTileCallback ( )
177- if ( ! base ) return undefined
178-
179240 const p = this . props as any
180241 const { rescaleMin, rescaleMax, nodata } = p
181242 // Capture colormapTexture at callback-construction time; the updateTriggers
@@ -185,10 +246,9 @@ export class ColormappedCOGLayer extends COGLayer<any> {
185246 const colormapTexture = ( this . state as any ) . colormapTexture as Texture | null
186247
187248 return ( data : any ) => {
188- const result = base ( data )
189- if ( ! result ) return null
249+ if ( ! data || ! data . texture ) return null
190250 return composeColormapPipeline (
191- result as { image ?: unknown ; renderPipeline ?: PipelineEntry [ ] } ,
251+ { renderPipeline : [ { module : CreateTexture , props : { textureName : data . texture } } ] } ,
192252 { rescaleMin, rescaleMax, colormapTexture, nodata }
193253 )
194254 }
@@ -229,6 +289,12 @@ export function buildDeckCOGLayer(
229289 opacity : options . opacity ?? 1 ,
230290 minZoom : options . minZoom ,
231291 maxZoom : options . maxZoom ,
292+ // Supplying getTileData + renderTile makes COGLayer._parseGeoTIFF skip
293+ // its default inferRenderPipeline (which throws for float COGs).
294+ // getTileData uploads the band as r32float; renderTile is overridden by
295+ // the subclass (this placeholder only satisfies the parse-time guard).
296+ getTileData : cogGetTileData ,
297+ renderTile : RENDER_TILE_PLACEHOLDER ,
232298 // Custom props consumed in updateState and _renderTileCallback:
233299 colormapName,
234300 rescaleMin,
0 commit comments