diff --git a/examples/land-cover/src/App.tsx b/examples/land-cover/src/App.tsx index b33230b..d34b0bd 100644 --- a/examples/land-cover/src/App.tsx +++ b/examples/land-cover/src/App.tsx @@ -254,7 +254,8 @@ export default function App() { ? [ new COGLayer({ id: "cog-layer", - geotiff, + data: COG_URL, + // geotiff, maxError: 0.125, debug, debugOpacity, diff --git a/packages/deck.gl-geotiff/package.json b/packages/deck.gl-geotiff/package.json index da307f9..ce00b23 100644 --- a/packages/deck.gl-geotiff/package.json +++ b/packages/deck.gl-geotiff/package.json @@ -42,6 +42,8 @@ "url": "git+https://github.com/developmentseed/deck.gl-raster.git" }, "devDependencies": { + "@loaders.gl/core": "^4.3.4", + "@loaders.gl/loader-utils": "^4.3.4", "@types/node": "^25.0.1", "@typescript-eslint/eslint-plugin": "^8.16.0", "@typescript-eslint/parser": "^8.16.0", diff --git a/packages/deck.gl-geotiff/src/cog-layer.ts b/packages/deck.gl-geotiff/src/cog-layer.ts index 2ce58de..5d38f90 100644 --- a/packages/deck.gl-geotiff/src/cog-layer.ts +++ b/packages/deck.gl-geotiff/src/cog-layer.ts @@ -21,6 +21,7 @@ import { fromGeoTransform } from "./geotiff-reprojection.js"; import { defaultPool, loadRgbImage } from "./geotiff.js"; import type { GeoKeysParser } from "./proj.js"; import { epsgIoGeoKeyParser } from "./proj.js"; +import { GeoTIFFLoader } from "./loader.js"; // Workaround until upstream exposes props // https://github.com/visgl/deck.gl/pull/9917 @@ -29,7 +30,7 @@ type Tileset2DProps = any; const DEFAULT_MAX_ERROR = 0.125; export interface COGLayerProps extends CompositeLayerProps { - geotiff: GeoTIFF; + data: GeoTIFF | string; /** * A function callback for parsing GeoTIFF geo keys to a Proj4 compatible @@ -97,6 +98,7 @@ const defaultProps: Partial = { maxError: DEFAULT_MAX_ERROR, geoKeysParser: epsgIoGeoKeyParser, loadTexture: loadRgbImage, + loaders: [GeoTIFFLoader], }; /** @@ -114,6 +116,7 @@ export class COGLayer extends CompositeLayer { }; override initializeState(): void { + console.log("initialize props", this.props); this.setState({}); } @@ -123,7 +126,7 @@ export class COGLayer extends CompositeLayer { const { props, oldProps, changeFlags } = params; const needsUpdate = - Boolean(changeFlags.dataChanged) || props.geotiff !== oldProps.geotiff; + Boolean(changeFlags.dataChanged) || props.data !== oldProps.data; if (needsUpdate) { this._parseGeoTIFF(); @@ -131,7 +134,17 @@ export class COGLayer extends CompositeLayer { } async _parseGeoTIFF(): Promise { - const { geotiff } = this.props; + let geotiff: GeoTIFF; + + console.log(this.props.data, "data"); + + // If data is a string URL, create GeoTIFF from URL for lazy loading + if (typeof this.props.data === "string") { + const { fromUrl } = await import("geotiff"); + geotiff = await fromUrl(this.props.data); + } else { + geotiff = this.props.data; + } const geoKeysParser = this.props.geoKeysParser!; const metadata = await parseCOGTileMatrixSet(geotiff, geoKeysParser); @@ -170,6 +183,7 @@ export class COGLayer extends CompositeLayer { inverseReproject: ReprojectionFns["inverseReproject"], images: GeoTIFFImage[], ): TileLayer { + console.log(this.props, "props"); const { maxError, debug = false, debugOpacity = 0.5 } = this.props; // Create a factory class that wraps COGTileset2D with the metadata diff --git a/packages/deck.gl-geotiff/src/loader.ts b/packages/deck.gl-geotiff/src/loader.ts new file mode 100644 index 0000000..8674f0c --- /dev/null +++ b/packages/deck.gl-geotiff/src/loader.ts @@ -0,0 +1,71 @@ +/** + * Simple shim that implements the loaders.gl interface so that the user can + * pass a string into the data prop. + */ +import type { Loader, LoaderWithParser } from "@loaders.gl/core"; +import type { Source } from "@loaders.gl/loader-utils"; +import type { GeoTIFF } from "geotiff"; + +/* ASCII I */ +const I = 0x49; + +/* ASCII M */ +const M = 0x4d; + +export const GeoTIFFLoader: LoaderWithParser = { + id: "geotiff", + name: "GeoTIFF", + module: "geotiff", + version: "version", + worker: false, + extensions: ["tif", "tiff", "geotiff"], + mimeTypes: ["image/tiff", "image/geotiff"], + parse: async (arrayBuffer: ArrayBuffer): Promise => { + console.log("parsing geotiff"); + console.log(arrayBuffer); + }, + // binary: false, + // text: true, + // tests: [testTIFFMagic], + // options: { + // fetch: (input, info) => input, + // geotiff: { + // fetch: (input, init) => { + // return input; + // }, + // }, + // }, + // parseTextSync: (text: string) => text, +}; + +// function parseGeoTIFF(arrayBuffer: ArrayBuffer): Promise { + +// } + +/** + * Test for TIFF magic bytes + * + * Magic bytes are either `II` or `MM` indicating little or big endian. Then the + * following bytes should be 42 for TIFF or 43 for BigTIFF. + */ +function testTIFFMagic(arrayBuffer: ArrayBuffer): boolean { + const byteArray = new Uint8Array(arrayBuffer); + + const b0 = byteArray[0]; + const b1 = byteArray[1]; + + // "II" = little endian, "MM" = big endian + const isLittleEndian = b0 === I && b1 === I; + const isBigEndian = b0 === M && b1 === M; + + if (!isLittleEndian && !isBigEndian) { + return false; + } + + const dataView = new DataView(arrayBuffer); + + // 42 for classic TIFF, 43 for BigTIFF + const tiffVersion = dataView.getUint16(2, isLittleEndian); + + return tiffVersion === 42 || tiffVersion === 43; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 177d4d9..b0c963b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,6 +193,12 @@ importers: specifier: ^2.20.2 version: 2.20.2 devDependencies: + '@loaders.gl/core': + specifier: ^4.3.4 + version: 4.3.4 + '@loaders.gl/loader-utils': + specifier: ^4.3.4 + version: 4.3.4(@loaders.gl/core@4.3.4) '@types/node': specifier: ^25.0.1 version: 25.0.1