Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
"assist": {
"actions": {
"source": {
Expand Down
13 changes: 13 additions & 0 deletions packages/geotiff/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ Until you try to load an image compressed with, say, [LERC], you don't pay for t

[LERC]: https://github.com/Esri/lerc

You can also override the built-in decoders with your own by using `registry`. For example, to use a custom zstd decoder:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉


```ts
import { Compression } from "@cogeotiff/core";
import { registry } from "@developmentseed/geotiff";

registry.set(Compression.Zstd, () =>
import("your-zstd-module").then((m) => m.decode),
);
```

A decoder is a function that takes an `ArrayBuffer` and a `DecoderMetadata` object and returns a `Promise<ArrayBuffer>`. See [decode.ts](./src/decode.ts) for the full type definitions.

### Full user control over caching and chunking

There are a lot of great utilities in [`chunkd`](https://github.com/blacha/chunkd) that work out of the box here.
Expand Down
1 change: 1 addition & 0 deletions packages/geotiff/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@developmentseed/affine": "workspace:^",
"@developmentseed/lzw-tiff-decoder": "^0.2.2",
"@developmentseed/morecantile": "workspace:^",
"fzstd": "^0.1.1",
"uuid": "^13.0.0"
}
}
6 changes: 6 additions & 0 deletions packages/geotiff/src/codecs/zstd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { decompress } from "fzstd";

export async function decode(bytes: ArrayBuffer): Promise<ArrayBuffer> {
const result = decompress(new Uint8Array(bytes));
return result.buffer as ArrayBuffer;
}
6 changes: 3 additions & 3 deletions packages/geotiff/src/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ registry.set(Compression.DeflateOther, () =>
registry.set(Compression.Lzw, () =>
import("./codecs/lzw.js").then((m) => m.decode),
);
// registry.set(Compression.Zstd, () =>
// import("../codecs/zstd.js").then((m) => m.decode),
// );
registry.set(Compression.Zstd, () =>
import("./codecs/zstd.js").then((m) => m.decode),
);
// registry.set(Compression.Lzma, () =>
// import("../codecs/lzma.js").then((m) => m.decode),
// );
Expand Down
37 changes: 37 additions & 0 deletions packages/geotiff/tests/decode.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PlanarConfiguration } from "@cogeotiff/core";
import { describe, expect, it } from "vitest";
import { decode } from "../src/decode.js";
import { loadGeoTIFF } from "./helpers.js";
Expand Down Expand Up @@ -38,6 +39,42 @@ describe("decode", () => {
}
});

it("can decompress zstd-compressed tile data", async () => {
const tiff = await loadGeoTIFF("int8_3band_zstd_block64", "rasterio");
const image = tiff.tiff.images[0]!;
const tile = await image.getTile(0, 0);
expect(tile).not.toBeNull();

const { bitsPerSample, sampleFormat, predictor, planarConfiguration } =
tiff.cachedTags;
const { width, height } = image.tileSize;

// This fixture is band-separate, so each raw tile contains a single band.
const tileSamplesPerPixel =
planarConfiguration === PlanarConfiguration.Separate
? 1
: tiff.cachedTags.samplesPerPixel;

const result = await decode(tile!.bytes, tile!.compression, {
sampleFormat: sampleFormat[0]!,
bitsPerSample: bitsPerSample[0]!,
samplesPerPixel: tileSamplesPerPixel,
width,
height,
predictor,
planarConfiguration,
});

const bytesPerSample = bitsPerSample[0]! / 8;
const expectedBytes = width * height * tileSamplesPerPixel * bytesPerSample;

expect(result.layout).toBe("pixel-interleaved");
if (result.layout === "pixel-interleaved") {
expect(result.data).toBeInstanceOf(Int8Array);
expect(result.data.byteLength).toBe(expectedBytes);
}
});

it("can decompress lerc-compressed tile data", async () => {
const tiff = await loadGeoTIFF("float32_1band_lerc_block32", "rasterio");
const image = tiff.tiff.images[0]!;
Expand Down
9 changes: 5 additions & 4 deletions packages/geotiff/tests/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* JavaScript, so we use it as a ground truth for pixel values, dimensions, and
* georeferencing.
*
* Fixtures that require unsupported codecs (WebP, JPEG, LZW, LZMA, JXL,
* zstd) are intentionally omitted here.
* Fixtures that require unsupported codecs (WebP, JPEG, LZW, LZMA, JXL)
* are intentionally omitted here.
*/

import type { GeoTIFFImage, GeoTIFF as GeotiffJs } from "geotiff";
Expand Down Expand Up @@ -99,8 +99,9 @@ describe("integration vs geotiff.js", () => {
const tile = await ours.fetchTile(0, 0);
const oursBandSep = toBandSeparate(tile.array);

// readRasters returns band-separate by default
const refData = await refImage.readRasters({ window: [0, 0, tw, th] });
const refData = await refImage.readRasters({
window: [0, 0, tw, th],
});

expect(oursBandSep.bands.length).toBe(ours.count);
expect(refData.length).toBe(ours.count);
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.