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
1 change: 1 addition & 0 deletions examples/cog-basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@developmentseed/deck.gl-geotiff": "workspace:^",
"@developmentseed/deck.gl-raster": "workspace:^",
"geotiff": "^2.1.3",
"geotiff-geokeys-to-proj4": "^2024.4.13",
"maplibre-gl": "^5.0.0",
"proj4": "^2.20.2",
"react": "^19.2.3",
Expand Down
100 changes: 48 additions & 52 deletions examples/cog-basic/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
import { useEffect, useState, useRef } from "react";
import { Map, useControl, type MapRef } from "react-map-gl/maplibre";
import { MapboxOverlay } from "@deck.gl/mapbox";
import type { DeckProps } from "@deck.gl/core";
import { fromUrl, Pool } from "geotiff";
import { MapboxOverlay } from "@deck.gl/mapbox";
import { COGLayer, proj } from "@developmentseed/deck.gl-geotiff";
import type { GeoTIFF } from "geotiff";
import { COGLayer, GeoTIFFLayer } from "@developmentseed/deck.gl-geotiff";
import proj4 from "proj4";
import { fromUrl, Pool } from "geotiff";
import { toProj4 } from "geotiff-geokeys-to-proj4";
import "maplibre-gl/dist/maplibre-gl.css";
import proj4 from "proj4";
import { useEffect, useRef, useState } from "react";
import { Map, useControl, type MapRef } from "react-map-gl/maplibre";

window.proj4 = proj4;

function DeckGLOverlay(props: DeckProps) {
const overlay = useControl<MapboxOverlay>(() => new MapboxOverlay(props));
overlay.setProps(props);
return null;
}

async function geoKeysParser(
geoKeys: Record<string, any>,
): Promise<proj.ProjectionInfo> {
const projDefinition = toProj4(geoKeys as any);
console.log("projDefinition", projDefinition);
(window as any).projDefinition = projDefinition;

return {
def: projDefinition.proj4,
parsed: proj.parseCrs(projDefinition.proj4),
coordinatesUnits: projDefinition.coordinatesUnits as proj.SupportedCrsUnit,
};
}

/**
* Calculate the WGS84 bounding box of a GeoTIFF image
*/
Expand All @@ -22,26 +39,10 @@ async function getCogBounds(
): Promise<[[number, number], [number, number]]> {
const image = await tiff.getImage();
const projectedBbox = image.getBoundingBox();
const geoKeys = image.getGeoKeys();

// Get the projection code
const projectionCode =
geoKeys.ProjectedCSTypeGeoKey || geoKeys.GeographicTypeGeoKey || null;

if (!projectionCode) {
throw new Error("Could not determine projection from GeoTIFF");
}

// Fetch projection definition
const url = `https://epsg.io/${projectionCode}.json`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch projection data from ${url}`);
}
const projDef = await response.json();
const projDefinition = await geoKeysParser(image.getGeoKeys());

// Reproject to WGS84 (EPSG:4326)
const converter = proj4(projDef, "EPSG:4326");
const converter = proj4(projDefinition.def, "EPSG:4326");

// Reproject all four corners to handle rotation/skew
const [minX, minY, maxX, maxY] = projectedBbox;
Expand Down Expand Up @@ -71,8 +72,12 @@ async function getCogBounds(
// const COG_URL =
// "https://nz-imagery.s3-ap-southeast-2.amazonaws.com/new-zealand/new-zealand_2024-2025_10m/rgb/2193/CC11.tiff";

// const COG_URL =
// "https://ds-wheels.s3.us-east-1.amazonaws.com/m_4007307_sw_18_060_20220803.tif";

// const COG_URL = "http://127.0.0.1:8080/Annual_NLCD_LndCov_2023_CU_C1V0.tif";
const COG_URL =
"https://ds-wheels.s3.us-east-1.amazonaws.com/m_4007307_sw_18_060_20220803.tif";
"https://ds-wheels.s3.us-east-1.amazonaws.com/Annual_NLCD_LndCov_2023_CU_C1V0.tif";

export default function App() {
const mapRef = useRef<MapRef>(null);
Expand Down Expand Up @@ -128,25 +133,16 @@ export default function App() {

const layers = geotiff
? [
renderAsTiled
? new COGLayer({
id: "cog-layer",
geotiff,
maxError: 0.125,
debug,
debugOpacity,
visible: renderAsTiled,
pool,
})
: new GeoTIFFLayer({
id: "geotiff-layer",
geotiff,
maxError: 0.125,
debug,
debugOpacity,
visible: !renderAsTiled,
pool,
}),
new COGLayer({
id: "cog-layer",
geotiff,
maxError: 0.125,
debug,
debugOpacity,
geoKeysParser,
pool,
beforeId: "aeroway-runway", // In interleaved mode render the layer under map labels. Replace with `slot: 'bottom'` if using Mapbox v3 Standard Style.
}),
]
: [];

Expand All @@ -161,9 +157,9 @@ export default function App() {
pitch: 0,
bearing: 0,
}}
mapStyle="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
mapStyle="https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json"
>
<DeckGLOverlay layers={layers} />
<DeckGLOverlay layers={layers} interleaved />
</Map>

{/* UI Overlay Container */}
Expand Down Expand Up @@ -231,9 +227,9 @@ export default function App() {
<h3 style={{ margin: "0 0 8px 0", fontSize: "16px" }}>
COGLayer Example
</h3>
<p style={{ margin: "0 0 12px 0", fontSize: "14px", color: "#666" }}>
{/* <p style={{ margin: "0 0 12px 0", fontSize: "14px", color: "#666" }}>
Displaying RGB imagery from New Zealand (NZTM2000 projection)
</p>
</p> */}

{/* Debug Controls */}
<div
Expand All @@ -243,7 +239,7 @@ export default function App() {
marginTop: "12px",
}}
>
<label
{/* <label
style={{
display: "flex",
alignItems: "center",
Expand All @@ -260,7 +256,7 @@ export default function App() {
style={{ cursor: "pointer" }}
/>
<span>Render as tiled</span>
</label>
</label> */}

<label
style={{
Expand Down Expand Up @@ -306,7 +302,7 @@ export default function App() {
)}
</div>

<div
{/* <div
style={{
marginTop: "12px",
paddingTop: "12px",
Expand All @@ -317,7 +313,7 @@ export default function App() {
>
<div>Max Error: 0.125 pixels</div>
<div>Source: LINZ</div>
</div>
</div> */}
</div>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"typecheck": "pnpm -r typecheck"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.49.0",
"publint": "^0.3.16",
"tsup": "^8.3.5",
"typescript": "^5.7.2"
Expand Down
27 changes: 19 additions & 8 deletions packages/deck.gl-geotiff/src/cog-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ import type { ReprojectionFns } from "@developmentseed/raster-reproject";
import type { GeoTIFF, GeoTIFFImage, Pool } from "geotiff";
import proj4 from "proj4";
import { parseCOGTileMatrixSet } from "./cog-tile-matrix-set.js";
import {
fromGeoTransform,
getGeoTIFFProjection,
} from "./geotiff-reprojection.js";
import { fromGeoTransform } from "./geotiff-reprojection.js";
import { defaultPool, loadRgbImage } from "./geotiff.js";
import type { GeoKeysParser } from "./proj.js";
import { epsgIoGeoKeyParser } from "./proj.js";

// Workaround until upstream exposes props
// https://github.com/visgl/deck.gl/pull/9917
Expand All @@ -30,6 +29,16 @@ const DEFAULT_MAX_ERROR = 0.125;
export interface COGLayerProps extends CompositeLayerProps {
geotiff: GeoTIFF;

/**
* A function callback for parsing GeoTIFF geo keys to a Proj4 compatible
* definition.
*
* By default, uses epsg.io to resolve EPSG codes found in the GeoTIFF.
* Alternatively, you may want to use `geotiff-geokeys-to-proj4`, which is
* more extensive but adds 1.5MB to your bundle size.
*/
geoKeysParser?: GeoKeysParser;

/**
* GeoTIFF.js Pool for decoding image chunks.
*
Expand Down Expand Up @@ -58,8 +67,9 @@ export interface COGLayerProps extends CompositeLayerProps {
debugOpacity?: number;
}

const defaultProps = {
const defaultProps: Partial<COGLayerProps> = {
maxError: DEFAULT_MAX_ERROR,
geoKeysParser: epsgIoGeoKeyParser,
};

/**
Expand Down Expand Up @@ -96,7 +106,8 @@ export class COGLayer extends CompositeLayer<COGLayerProps> {
async _parseGeoTIFF(): Promise<void> {
const { geotiff } = this.props;

const metadata = await parseCOGTileMatrixSet(geotiff);
const geoKeysParser = this.props.geoKeysParser!;
const metadata = await parseCOGTileMatrixSet(geotiff, geoKeysParser);

const image = await geotiff.getImage();
const imageCount = await geotiff.getImageCount();
Expand All @@ -105,14 +116,14 @@ export class COGLayer extends CompositeLayer<COGLayerProps> {
images.push(await geotiff.getImage(imageIdx));
}

const sourceProjection = await getGeoTIFFProjection(image);
const sourceProjection = await geoKeysParser(image.getGeoKeys());
if (!sourceProjection) {
throw new Error(
"Could not determine source projection from GeoTIFF geo keys",
);
}

const converter = proj4(sourceProjection, "EPSG:4326");
const converter = proj4(sourceProjection.def, "EPSG:4326");
const forwardReproject = (x: number, y: number) =>
converter.forward<[number, number]>([x, y], false);
const inverseReproject = (x: number, y: number) =>
Expand Down
Loading