66 */
77
88import type { Viewport } from "@deck.gl/core" ;
9- import type { _Tileset2DProps as Tileset2DProps } from "@deck.gl/geo-layers" ;
9+ import type {
10+ GeoBoundingBox ,
11+ _Tileset2DProps as Tileset2DProps ,
12+ } from "@deck.gl/geo-layers" ;
1013import { _Tileset2D as Tileset2D } from "@deck.gl/geo-layers" ;
1114import * as affine from "@developmentseed/affine" ;
12- import type { BoundingBox , TileMatrixSet } from "@developmentseed/morecantile" ;
15+ import type {
16+ BoundingBox ,
17+ TileMatrix ,
18+ TileMatrixSet ,
19+ } from "@developmentseed/morecantile" ;
1320import { tileTransform } from "@developmentseed/morecantile" ;
21+ import { transformBounds } from "@developmentseed/proj" ;
1422import type { Matrix4 } from "@math.gl/core" ;
15-
1623import { getTileIndices } from "./raster-tile-traversal" ;
1724import type {
1825 Bounds ,
1926 CornerBounds ,
2027 Point ,
28+ ProjectedBoundingBox ,
2129 ProjectionFunction ,
2230 TileIndex ,
2331 ZRange ,
2432} from "./types" ;
2533
34+ /** Type returned by `getTileMetadata` */
35+ export type TileMetadata = {
36+ /**
37+ * **Axis-aligned** bounding box of the tile in **WGS84 coordinates**.
38+ */
39+ bbox : GeoBoundingBox ;
40+
41+ /**
42+ * **Axis-aligned** bounding box of the tile in **projected coordinates**.
43+ */
44+ projectedBbox : ProjectedBoundingBox ;
45+
46+ /**
47+ * "Rotated" bounding box of the tile in **projected coordinates**,
48+ * represented as four corners.
49+ *
50+ * This preserves rotation/skew information that would be lost in the
51+ * axis-aligned bbox.
52+ */
53+ projectedCorners : {
54+ topLeft : Point ;
55+ topRight : Point ;
56+ bottomLeft : Point ;
57+ bottomRight : Point ;
58+ } ;
59+
60+ /**
61+ * Tile width in pixels.
62+ *
63+ * Note this may differ between levels in some TileMatrixSets.
64+ */
65+ tileWidth : number ;
66+
67+ /**
68+ * Tile height in pixels.
69+ *
70+ * Note this may differ between levels in some TileMatrixSets.
71+ */
72+ tileHeight : number ;
73+
74+ /**
75+ * A reference to the underlying TileMatrix.
76+ */
77+ tileMatrix : TileMatrix ;
78+ } ;
79+
2680/**
2781 * A generic tileset implementation organized according to the OGC
2882 * [TileMatrixSet](https://docs.ogc.org/is/17-083r4/17-083r4.html)
@@ -109,11 +163,25 @@ export class TileMatrixSetTileset extends Tileset2D {
109163 const currentOverview = this . tms . tileMatrices [ index . z ] ! ;
110164 const parentOverview = this . tms . tileMatrices [ index . z - 1 ] ! ;
111165
112- const decimation = currentOverview . cellSize / parentOverview . cellSize ;
166+ // Decimation is the number of child tiles that fit across one parent tile.
167+ // Must use tile footprint (cellSize × tileWidth/Height), not cellSize alone,
168+ // because tileWidth can change between levels (e.g. the last Sentinel-2
169+ // overview doubles tileWidth while halving cellSize, giving a 1:1 spatial
170+ // mapping where decimation = 1).
171+ const parentFootprintX = parentOverview . cellSize * parentOverview . tileWidth ;
172+ const parentFootprintY =
173+ parentOverview . cellSize * parentOverview . tileHeight ;
174+ const currentFootprintX =
175+ currentOverview . cellSize * currentOverview . tileWidth ;
176+ const currentFootprintY =
177+ currentOverview . cellSize * currentOverview . tileHeight ;
178+
179+ const decimationX = parentFootprintX / currentFootprintX ;
180+ const decimationY = parentFootprintY / currentFootprintY ;
113181
114182 return {
115- x : Math . floor ( index . x / decimation ) ,
116- y : Math . floor ( index . y / decimation ) ,
183+ x : Math . floor ( index . x / decimationX ) ,
184+ y : Math . floor ( index . y / decimationY ) ,
117185 z : index . z - 1 ,
118186 } ;
119187 }
@@ -122,7 +190,7 @@ export class TileMatrixSetTileset extends Tileset2D {
122190 return index . z ;
123191 }
124192
125- override getTileMetadata ( index : TileIndex ) : Record < string , unknown > {
193+ override getTileMetadata ( index : TileIndex ) : TileMetadata {
126194 const { x, y, z } = index ;
127195 const { tileMatrices } = this . tms ;
128196 const tileMatrix = tileMatrices [ z ] ! ;
@@ -138,24 +206,44 @@ export class TileMatrixSetTileset extends Tileset2D {
138206
139207 // Return the projected bounds as four corners
140208 // This preserves rotation/skew information
141- const projectedBounds = {
209+ const projectedCorners = {
142210 topLeft,
143211 topRight,
144212 bottomLeft,
145213 bottomRight,
146214 } ;
147215
148216 // Also compute axis-aligned bounding box for compatibility
149- const bounds : Bounds = [
217+ const projectedBounds : Bounds = [
150218 Math . min ( topLeft [ 0 ] , topRight [ 0 ] , bottomLeft [ 0 ] , bottomRight [ 0 ] ) ,
151219 Math . min ( topLeft [ 1 ] , topRight [ 1 ] , bottomLeft [ 1 ] , bottomRight [ 1 ] ) ,
152220 Math . max ( topLeft [ 0 ] , topRight [ 0 ] , bottomLeft [ 0 ] , bottomRight [ 0 ] ) ,
153221 Math . max ( topLeft [ 1 ] , topRight [ 1 ] , bottomLeft [ 1 ] , bottomRight [ 1 ] ) ,
154222 ] ;
155223
224+ // deck.gl's Tile2DHeader uses `bbox` (GeoBoundingBox) for screen-space
225+ // culling in filterSubLayer → isTileVisible. Without this, all tiles
226+ // would pass (or fail) the cull-rect test and the refinementStrategy
227+ // (best-available) would not show parent tiles correctly.
228+ const [ west , south , east , north ] = transformBounds (
229+ this . projectTo4326 ,
230+ ...projectedBounds ,
231+ ) ;
232+
156233 return {
157- bounds,
158- projectedBounds,
234+ bbox : {
235+ west,
236+ south,
237+ east,
238+ north,
239+ } ,
240+ projectedBbox : {
241+ left : projectedBounds [ 0 ] ,
242+ bottom : projectedBounds [ 1 ] ,
243+ right : projectedBounds [ 2 ] ,
244+ top : projectedBounds [ 3 ] ,
245+ } ,
246+ projectedCorners,
159247 tileWidth,
160248 tileHeight,
161249 tileMatrix,
0 commit comments