From c1b6a09a358224487f590684823299a20d1dc563 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Mon, 22 Dec 2025 16:13:16 -0500 Subject: [PATCH] feat: Allow user-defined texture loader in COGLayer --- packages/deck.gl-geotiff/src/cog-layer.ts | 46 ++++++++++++++++--- packages/deck.gl-geotiff/src/geotiff-layer.ts | 4 +- packages/deck.gl-geotiff/src/geotiff-types.ts | 4 +- packages/deck.gl-geotiff/src/geotiff.ts | 4 +- packages/deck.gl-geotiff/src/index.ts | 1 + packages/deck.gl-geotiff/src/texture.ts | 8 ++-- 6 files changed, 50 insertions(+), 17 deletions(-) diff --git a/packages/deck.gl-geotiff/src/cog-layer.ts b/packages/deck.gl-geotiff/src/cog-layer.ts index 15852a9..8d13af7 100644 --- a/packages/deck.gl-geotiff/src/cog-layer.ts +++ b/packages/deck.gl-geotiff/src/cog-layer.ts @@ -6,12 +6,14 @@ import type { import { CompositeLayer } from "@deck.gl/core"; import { TileLayer } from "@deck.gl/geo-layers"; import { PathLayer } from "@deck.gl/layers"; -import { RasterLayer, RasterTileset2D } from "@developmentseed/deck.gl-raster"; import type { + RasterLayerProps, TileMatrix, TileMatrixSet, } from "@developmentseed/deck.gl-raster"; +import { RasterLayer, RasterTileset2D } from "@developmentseed/deck.gl-raster"; import type { ReprojectionFns } from "@developmentseed/raster-reproject"; +import type { TextureProps } from "@luma.gl/core"; import type { GeoTIFF, GeoTIFFImage, Pool } from "geotiff"; import proj4 from "proj4"; import { parseCOGTileMatrixSet } from "./cog-tile-matrix-set.js"; @@ -54,6 +56,29 @@ export interface COGLayerProps extends CompositeLayerProps { */ maxError?: number; + /** + * User-defined method to load texture. + * + * The default implementation loads an RGBA image using geotiff.js's readRGB + * method, returning an ImageData object. + * + * For more customizability, you can also return a TextureProps object from + * luma.gl, along with optional custom shaders for the RasterLayer. + */ + loadTexture?: ( + image: GeoTIFFImage, + options: { + window: [number, number, number, number]; + signal?: AbortSignal; + pool: Pool; + }, + ) => Promise<{ + texture: ImageData | TextureProps; + shaders?: RasterLayerProps["shaders"]; + height: number; + width: number; + }>; + /** * Enable debug visualization showing the triangulation mesh * @default false @@ -70,6 +95,7 @@ export interface COGLayerProps extends CompositeLayerProps { const defaultProps: Partial = { maxError: DEFAULT_MAX_ERROR, geoKeysParser: epsgIoGeoKeyParser, + loadTexture: loadRgbImage, }; /** @@ -158,7 +184,8 @@ export class COGLayer extends CompositeLayer { getTileData: async ( tile, ): Promise<{ - image: ImageData; + texture: ImageData | TextureProps; + shaders?: RasterLayerProps["shaders"]; height: number; width: number; pixelToInputCRS: ReprojectionFns["pixelToInputCRS"]; @@ -186,16 +213,18 @@ export class COGLayer extends CompositeLayer { Math.min((y + 1) * tileHeight, imageHeight), ]; - const { imageData, height, width } = await loadRgbImage(geotiffImage, { + const { texture, height, width, shaders } = await this.props + .loadTexture!(geotiffImage, { window, signal, pool: this.props.pool || defaultPool(), }); return { - image: imageData, + texture, height, width, + shaders, pixelToInputCRS, inputCRSToPixel, }; @@ -207,13 +236,15 @@ export class COGLayer extends CompositeLayer { if (data) { const { - image, + texture, + shaders, height, width, pixelToInputCRS, inputCRSToPixel, }: { - image: ImageData; + texture: ImageData | TextureProps; + shaders?: RasterLayerProps["shaders"]; height: number; width: number; pixelToInputCRS: ReprojectionFns["pixelToInputCRS"]; @@ -224,7 +255,8 @@ export class COGLayer extends CompositeLayer { id: `${props.id}-raster`, width, height, - texture: image, + texture, + shaders, maxError, reprojectionFns: { pixelToInputCRS, diff --git a/packages/deck.gl-geotiff/src/geotiff-layer.ts b/packages/deck.gl-geotiff/src/geotiff-layer.ts index 6c8c75c..36007ad 100644 --- a/packages/deck.gl-geotiff/src/geotiff-layer.ts +++ b/packages/deck.gl-geotiff/src/geotiff-layer.ts @@ -101,13 +101,13 @@ export class GeoTIFFLayer extends CompositeLayer { this.props.geoKeysParser!, ); const image = await geotiff.getImage(); - const { imageData, height, width } = await loadRgbImage(image, { + const { texture, height, width } = await loadRgbImage(image, { pool: this.props.pool || defaultPool(), }); this.setState({ reprojectionFns, - imageData, + imageData: texture, height, width, }); diff --git a/packages/deck.gl-geotiff/src/geotiff-types.ts b/packages/deck.gl-geotiff/src/geotiff-types.ts index c6e625e..5020c15 100644 --- a/packages/deck.gl-geotiff/src/geotiff-types.ts +++ b/packages/deck.gl-geotiff/src/geotiff-types.ts @@ -4,10 +4,10 @@ import { globals } from "geotiff"; export type ImageFileDirectory = { ImageWidth: number; ImageLength: number; - BitsPerSample: number[]; + BitsPerSample: Uint16Array; Compression: number; PhotometricInterpretation: typeof globals.photometricInterpretations; SamplesPerPixel: number; - SampleFormat: number[]; + SampleFormat: Uint16Array; PlanarConfiguration: number; }; diff --git a/packages/deck.gl-geotiff/src/geotiff.ts b/packages/deck.gl-geotiff/src/geotiff.ts index 7de4c6d..fc2b450 100644 --- a/packages/deck.gl-geotiff/src/geotiff.ts +++ b/packages/deck.gl-geotiff/src/geotiff.ts @@ -45,7 +45,7 @@ export function defaultPool(): Pool { export async function loadRgbImage( image: GeoTIFFImage, options?: ReadRasterOptions, -): Promise<{ imageData: ImageData; height: number; width: number }> { +): Promise<{ texture: ImageData; height: number; width: number }> { const mergedOptions = { ...options, interleave: true, @@ -60,7 +60,7 @@ export async function loadRgbImage( const imageData = addAlphaChannel(rgbImage); return { - imageData, + texture: imageData, height: rgbImage.height, width: rgbImage.width, }; diff --git a/packages/deck.gl-geotiff/src/index.ts b/packages/deck.gl-geotiff/src/index.ts index bc211bb..0dc9ffd 100644 --- a/packages/deck.gl-geotiff/src/index.ts +++ b/packages/deck.gl-geotiff/src/index.ts @@ -10,3 +10,4 @@ export { export { loadRgbImage } from "./geotiff.js"; export * as proj from "./proj.js"; +export * as texture from "./texture.js"; diff --git a/packages/deck.gl-geotiff/src/texture.ts b/packages/deck.gl-geotiff/src/texture.ts index 195b814..8308323 100644 --- a/packages/deck.gl-geotiff/src/texture.ts +++ b/packages/deck.gl-geotiff/src/texture.ts @@ -32,8 +32,8 @@ export function createTextureProps( */ function inferTextureFormat( samplesPerPixel: number, - bitsPerSample: number[], - sampleFormat: number[], + bitsPerSample: Uint16Array, + sampleFormat: Uint16Array, ): TextureFormat { const channelCount = verifySamplesPerPixel(samplesPerPixel); const bitWidth = verifyIdenticalBitsPerSample(bitsPerSample); @@ -70,7 +70,7 @@ function verifySamplesPerPixel(samplesPerPixel: number): ChannelCount { ); } -function verifyIdenticalBitsPerSample(bitsPerSample: number[]): BitWidth { +function verifyIdenticalBitsPerSample(bitsPerSample: Uint16Array): BitWidth { // bitsPerSamples is non-empty const first = bitsPerSample[0]!; @@ -94,7 +94,7 @@ function verifyIdenticalBitsPerSample(bitsPerSample: number[]): BitWidth { /** * Map the geotiff tag SampleFormat to known kinds of scalars */ -function inferScalarKind(sampleFormat: number[]): ScalarKind { +function inferScalarKind(sampleFormat: Uint16Array): ScalarKind { // Only support identical SampleFormats for all samples const first = sampleFormat[0]!;