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
39 changes: 22 additions & 17 deletions packages/deck.gl-geotiff/src/cog-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { PathLayer } from "@deck.gl/layers";
import type {
RasterLayerProps,
RasterModule,
TileMetadata,
} from "@developmentseed/deck.gl-raster";
import {
RasterLayer,
Expand Down Expand Up @@ -407,7 +408,12 @@ export class COGLayer<
inverseFrom3857: ReprojectionFns["inverseReproject"],
): Layer | LayersList | null {
const { maxError, debug, debugOpacity } = this.props;
const { tile } = props;

// Cast to include TileMetadata from raster-tileset's `getTileMetadata`
// method.
// TODO: implement generic handling of tile metadata upstream in TileLayer
const tile = props.tile as Tile2DHeader<GetTileDataResult<DataT>> &
TileMetadata;

if (!props.data) {
return null;
Expand Down Expand Up @@ -495,35 +501,34 @@ export class COGLayer<
}

if (debug) {
// Get projected bounds from tile data
// getTileMetadata returns data that includes projectedBounds
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const projectedBounds: {
topLeft: [number, number];
topRight: [number, number];
bottomLeft: [number, number];
bottomRight: [number, number];
} = (tile as any)?.projectedBounds;

if (!projectedBounds || !tms) {
const { projectedCorners } = tile;

if (!projectedCorners || !tms) {
return [];
}

// Project bounds from image CRS to WGS84
const { topLeft, topRight, bottomLeft, bottomRight } = projectedBounds;

// Create a closed path in WGS84 projection around the tile bounds
//
// The tile has a `bbox` field which is already the bounding box in WGS84,
// but that uses `transformBounds` and densifies edges. So the corners of
// the bounding boxes don't line up with each other.
//
// In this case in the debug mode, it looks better if we ignore the actual
// non-linearities of the edges and just draw a box connecting the
// reprojected corners. In any case, the _image itself_ will be densified
// on the edges as a feature of the mesh generation.
const { topLeft, topRight, bottomRight, bottomLeft } = projectedCorners;
const topLeftWgs84 = forwardTo4326(topLeft[0], topLeft[1]);
const topRightWgs84 = forwardTo4326(topRight[0], topRight[1]);
const bottomRightWgs84 = forwardTo4326(bottomRight[0], bottomRight[1]);
const bottomLeftWgs84 = forwardTo4326(bottomLeft[0], bottomLeft[1]);

// Create a closed path around the tile bounds
const path = [
topLeftWgs84,
topRightWgs84,
bottomRightWgs84,
bottomLeftWgs84,
topLeftWgs84, // Close the path
topLeftWgs84,
];

layers.push(
Expand Down
1 change: 1 addition & 0 deletions packages/deck.gl-raster/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"dependencies": {
"@developmentseed/affine": "workspace:^",
"@developmentseed/morecantile": "workspace:^",
"@developmentseed/proj": "workspace:^",
"@developmentseed/raster-reproject": "workspace:^",
"@math.gl/core": "^4.1.0",
"@math.gl/culling": "^4.1.0",
Expand Down
1 change: 1 addition & 0 deletions packages/deck.gl-raster/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type { RasterModule } from "./gpu-modules/types.js";
export type { RasterLayerProps } from "./raster-layer.js";
export { RasterLayer } from "./raster-layer.js";
export type { TileMetadata } from "./raster-tileset/index.js";
export { TileMatrixSetTileset } from "./raster-tileset/index.js";

import { __TEST_EXPORTS as traversalTestExports } from "./raster-tileset/raster-tile-traversal.js";
Expand Down
1 change: 1 addition & 0 deletions packages/deck.gl-raster/src/raster-tileset/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export type { TileMetadata } from "./raster-tileset-2d.js";
export { TileMatrixSetTileset } from "./raster-tileset-2d.js";
110 changes: 99 additions & 11 deletions packages/deck.gl-raster/src/raster-tileset/raster-tileset-2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,77 @@
*/

import type { Viewport } from "@deck.gl/core";
import type { _Tileset2DProps as Tileset2DProps } from "@deck.gl/geo-layers";
import type {
GeoBoundingBox,
_Tileset2DProps as Tileset2DProps,
} from "@deck.gl/geo-layers";
import { _Tileset2D as Tileset2D } from "@deck.gl/geo-layers";
import * as affine from "@developmentseed/affine";
import type { BoundingBox, TileMatrixSet } from "@developmentseed/morecantile";
import type {
BoundingBox,
TileMatrix,
TileMatrixSet,
} from "@developmentseed/morecantile";
import { tileTransform } from "@developmentseed/morecantile";
import { transformBounds } from "@developmentseed/proj";
import type { Matrix4 } from "@math.gl/core";

import { getTileIndices } from "./raster-tile-traversal";
import type {
Bounds,
CornerBounds,
Point,
ProjectedBoundingBox,
ProjectionFunction,
TileIndex,
ZRange,
} from "./types";

/** Type returned by `getTileMetadata` */
export type TileMetadata = {
/**
* **Axis-aligned** bounding box of the tile in **WGS84 coordinates**.
*/
bbox: GeoBoundingBox;

/**
* **Axis-aligned** bounding box of the tile in **projected coordinates**.
*/
projectedBbox: ProjectedBoundingBox;

/**
* "Rotated" bounding box of the tile in **projected coordinates**,
* represented as four corners.
*
* This preserves rotation/skew information that would be lost in the
* axis-aligned bbox.
*/
projectedCorners: {
topLeft: Point;
topRight: Point;
bottomLeft: Point;
bottomRight: Point;
};

/**
* Tile width in pixels.
*
* Note this may differ between levels in some TileMatrixSets.
*/
tileWidth: number;

/**
* Tile height in pixels.
*
* Note this may differ between levels in some TileMatrixSets.
*/
tileHeight: number;

/**
* A reference to the underlying TileMatrix.
*/
tileMatrix: TileMatrix;
};

/**
* A generic tileset implementation organized according to the OGC
* [TileMatrixSet](https://docs.ogc.org/is/17-083r4/17-083r4.html)
Expand Down Expand Up @@ -109,11 +163,25 @@ export class TileMatrixSetTileset extends Tileset2D {
const currentOverview = this.tms.tileMatrices[index.z]!;
const parentOverview = this.tms.tileMatrices[index.z - 1]!;

const decimation = currentOverview.cellSize / parentOverview.cellSize;
// Decimation is the number of child tiles that fit across one parent tile.
// Must use tile footprint (cellSize × tileWidth/Height), not cellSize alone,
// because tileWidth can change between levels (e.g. the last Sentinel-2
// overview doubles tileWidth while halving cellSize, giving a 1:1 spatial
// mapping where decimation = 1).
const parentFootprintX = parentOverview.cellSize * parentOverview.tileWidth;
const parentFootprintY =
parentOverview.cellSize * parentOverview.tileHeight;
const currentFootprintX =
currentOverview.cellSize * currentOverview.tileWidth;
const currentFootprintY =
currentOverview.cellSize * currentOverview.tileHeight;

const decimationX = parentFootprintX / currentFootprintX;
const decimationY = parentFootprintY / currentFootprintY;

return {
x: Math.floor(index.x / decimation),
y: Math.floor(index.y / decimation),
x: Math.floor(index.x / decimationX),
y: Math.floor(index.y / decimationY),
z: index.z - 1,
};
}
Expand All @@ -122,7 +190,7 @@ export class TileMatrixSetTileset extends Tileset2D {
return index.z;
}

override getTileMetadata(index: TileIndex): Record<string, unknown> {
override getTileMetadata(index: TileIndex): TileMetadata {
const { x, y, z } = index;
const { tileMatrices } = this.tms;
const tileMatrix = tileMatrices[z]!;
Expand All @@ -138,24 +206,44 @@ export class TileMatrixSetTileset extends Tileset2D {

// Return the projected bounds as four corners
// This preserves rotation/skew information
const projectedBounds = {
const projectedCorners = {
topLeft,
topRight,
bottomLeft,
bottomRight,
};

// Also compute axis-aligned bounding box for compatibility
const bounds: Bounds = [
const projectedBounds: Bounds = [
Math.min(topLeft[0], topRight[0], bottomLeft[0], bottomRight[0]),
Math.min(topLeft[1], topRight[1], bottomLeft[1], bottomRight[1]),
Math.max(topLeft[0], topRight[0], bottomLeft[0], bottomRight[0]),
Math.max(topLeft[1], topRight[1], bottomLeft[1], bottomRight[1]),
];

// deck.gl's Tile2DHeader uses `bbox` (GeoBoundingBox) for screen-space
// culling in filterSubLayer → isTileVisible. Without this, all tiles
// would pass (or fail) the cull-rect test and the refinementStrategy
// (best-available) would not show parent tiles correctly.
const [west, south, east, north] = transformBounds(
this.projectTo4326,
...projectedBounds,
);

return {
bounds,
projectedBounds,
bbox: {
west,
south,
east,
north,
},
projectedBbox: {
left: projectedBounds[0],
bottom: projectedBounds[1],
right: projectedBounds[2],
top: projectedBounds[3],
},
projectedCorners,
tileWidth,
tileHeight,
tileMatrix,
Expand Down
5 changes: 3 additions & 2 deletions packages/deck.gl-raster/src/raster-tileset/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ export type GeoBoundingBox = {
east: number;
south: number;
};
export type NonGeoBoundingBox = {

export type ProjectedBoundingBox = {
left: number;
top: number;
right: number;
bottom: number;
};

export type TileBoundingBox = NonGeoBoundingBox | GeoBoundingBox;
export type TileBoundingBox = ProjectedBoundingBox | GeoBoundingBox;

export type TileLoadProps = {
index: TileIndex;
Expand Down
Loading