Skip to content

Commit fb7282a

Browse files
authored
refactor: Use upstream deck.gl-raster RasterTileLayer (#1183)
- Use upstream `RasterTileLayer` to deduplicate a bunch of our work here. - Bumps to deck.gl-raster 0.6.1
1 parent 66c50b9 commit fb7282a

6 files changed

Lines changed: 187 additions & 111 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
dev-docs/superpowers/plans
12
.vscode
23
*.mov
34
.mypy_cache
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Migrate `RasterModel` TMS path to `RasterTileLayer`
2+
3+
## Goal
4+
5+
Simplify [src/model/layer/raster.ts](../../../src/model/layer/raster.ts) by
6+
replacing its hand-wired `TileLayer` + `RasterTileset2D` + per-tile `RasterLayer`
7+
plumbing with the single composite `RasterTileLayer` that
8+
`@developmentseed/deck.gl-raster@^0.6.0-beta.1` now exports.
9+
10+
This is a prototype: validate that `RasterTileLayer` is a drop-in replacement
11+
for the current TMS code path, with no user-facing API or behavior change.
12+
13+
## Scope
14+
15+
- **In scope:** the TMS path (`renderTileMatrixSet`) in
16+
[src/model/layer/raster.ts](../../../src/model/layer/raster.ts).
17+
- **Out of scope:**
18+
- The non-TMS fallback in `render()` (the `TileLayer` + `BitmapLayer` Web
19+
Mercator path stays unchanged).
20+
- The Python widget side ([lonboard/layer/_raster.py](../../../lonboard/layer/_raster.py))
21+
— the `raster-get-tile-data` message protocol does not change.
22+
- Any user-facing trait, method, or constructor signature on
23+
`lonboard.RasterLayer`.
24+
25+
## Current state
26+
27+
`renderTileMatrixSet` builds three things by hand:
28+
29+
1. A `TileMatrixSetAdaptor` and a `RasterTileset2DFactory` subclass that
30+
captures the descriptor in its constructor.
31+
2. A `TileLayer` whose `getTileData` fetches encoded bytes from Python via
32+
`invoke()`, decodes to an `ImageBitmap`, and computes per-tile forward /
33+
inverse affine transforms by calling `morecantile.tileTransform` and
34+
`@developmentseed/affine`'s `invert`/`apply`. Returns
35+
`{ image, forwardTransform, inverseTransform }`.
36+
3. A `renderSubLayers` (`renderRasterSubLayer`) that constructs the inner
37+
`RasterLayer` from `@developmentseed/deck.gl-raster` and threads a full
38+
`ReprojectionFns` object through it (the per-tile transforms plus
39+
`proj4`-derived converters between source CRS ↔ EPSG:4326).
40+
41+
## Target state
42+
43+
`renderTileMatrixSet` constructs one `RasterTileLayer` directly:
44+
45+
```ts
46+
return new RasterTileLayer<TileData>({
47+
...this.baseLayerProps(),
48+
...this.layerProps(),
49+
tilesetDescriptor,
50+
getTileData: this.getTileData,
51+
renderTile: (data) => ({ image: data.image }),
52+
});
53+
```
54+
55+
Where:
56+
57+
- `tilesetDescriptor` is a `TileMatrixSetAdaptor` constructed with all four
58+
projection functions required by the 0.6 API:
59+
`projectTo4326`, `projectFrom4326`, `projectTo3857`, `projectFrom3857`. The
60+
existing `proj4.Converter` instances on `this.converters` already supply
61+
both directions; expose `.forward()` and `.inverse()` for each.
62+
- `TileData` is `MinimalTileData & { image: TextureSource }` — i.e.
63+
`{ image, width, height }`. No `forwardTransform`/`inverseTransform` fields;
64+
the per-tile transform is now resolved internally by `RasterTileLayer` via
65+
`tilesetDescriptor.levels[z].tileTransform(col, row)`.
66+
- `getTileData(tile, options)` continues to call `invoke()` to fetch encoded
67+
bytes from Python, decodes to `ImageBitmap`, and returns
68+
`{ image, width: image.width, height: image.height }`. The abort signal
69+
comes from `options.signal` (the composed layer + per-tile signal) rather
70+
than `tile.signal`.
71+
- `renderTile(data)` is a one-liner that returns `{ image: data.image }`.
72+
73+
## Code that gets removed
74+
75+
From [src/model/layer/raster.ts](../../../src/model/layer/raster.ts):
76+
77+
- Imports: `RasterLayer` and `RasterTileset2D` from
78+
`@developmentseed/deck.gl-raster`; `tileTransform` from
79+
`@developmentseed/morecantile`; `apply` and `invert` from
80+
`@developmentseed/affine`; `ReprojectionFns` from
81+
`@developmentseed/raster-reproject`.
82+
- The `RasterTileset2DFactory` subclass inside `renderTileMatrixSet`.
83+
- The `renderRasterSubLayer` method.
84+
- The `forwardTransform` / `inverseTransform` fields on `TileData`.
85+
86+
New imports: `RasterTileLayer` from `@developmentseed/deck.gl-raster`.
87+
88+
## Code that stays
89+
90+
- The non-TMS path in `render()` — the `TileLayer` + `BitmapLayer` Web Mercator
91+
fallback is untouched.
92+
- All `BaseLayerModel` plumbing and the synced trait fields (`tileSize`,
93+
`zoomOffset`, `maxZoom`, `minZoom`, `extent`, `maxCacheSize`,
94+
`debounceTime`).
95+
- The `accessConverters` helper (still used by the fallback path; harmless to
96+
keep).
97+
- The `dataViewToImageBitmap` decoder.
98+
- The `MSG_KIND` constant and the `TileResponse` shape.
99+
100+
## Type compatibility
101+
102+
`RasterTileLayerProps` extends `CompositeLayerProps` and re-picks a subset of
103+
`TileLayerProps` (`tileSize`, `zoomOffset`, `maxZoom`, `minZoom`, `extent`,
104+
`debounceTime`, `maxCacheSize`, `maxCacheByteSize`, `maxRequests`,
105+
`refinementStrategy`). The current `layerProps()` emits only fields in that
106+
subset, and `baseLayerProps()` emits standard `Layer` props (`pickable`,
107+
`opacity`, `visible`, …) that are valid on `CompositeLayerProps`. Spreading
108+
both is type-safe.
109+
110+
## Verification
111+
112+
1. `npm run build` and `npm run check` succeed with no new errors.
113+
2. Manually load a PMTiles raster via `RasterLayer.from_pmtiles` and a COG via
114+
`RasterLayer.from_geotiff` (existing notebooks like `raster.ipynb` and
115+
`raster-pmtiles.ipynb`) and confirm tiles render with the same visual
116+
result as before the migration.
117+
3. The Web Mercator (no-TMS) fallback continues to work — quick smoke test
118+
with a layer constructed without a `_tile_matrix_set`.
119+
120+
## Risks
121+
122+
- The 0.6 `TileMatrixSetAdaptor` constructor signature now requires
123+
`projectFrom4326` and `projectFrom3857` in addition to the forward
124+
directions. Easy to satisfy from the existing `proj4` converters.
125+
- `RasterTileLayer` may surface different default values for shared props
126+
(`tileSize`, `maxError`, etc.) than the manually-built `TileLayer` did.
127+
Address by inspecting visible behavior on the verification step and, if
128+
needed, passing the previous values explicitly.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
"@deck.gl/mapbox": "^9.3.1",
99
"@deck.gl/mesh-layers": "^9.3.1",
1010
"@deck.gl/react": "^9.3.1",
11-
"@developmentseed/affine": "^0.5.0",
12-
"@developmentseed/deck.gl-raster": "^0.5.0",
13-
"@developmentseed/morecantile": "^0.5.0",
14-
"@developmentseed/raster-reproject": "^0.5.0",
11+
"@developmentseed/affine": "^0.6.1",
12+
"@developmentseed/deck.gl-raster": "^0.6.1",
13+
"@developmentseed/morecantile": "^0.6.1",
14+
"@developmentseed/raster-reproject": "^0.6.1",
1515
"@geoarrow/deck.gl-layers": "^0.4.0-beta.6",
1616
"@geoarrow/geoarrow-js": "^0.3.2",
1717
"@maplibre/maplibre-gl-geocoder": "^1.9.4",

pnpm-lock.yaml

Lines changed: 29 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/model/layer/raster.ts

Lines changed: 20 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
import type { TextureSource } from "@deck.gl/core";
2-
import type {
3-
TileLayerProps,
4-
_Tileset2DProps as Tileset2DProps,
5-
} from "@deck.gl/geo-layers";
2+
import type { TileLayerProps } from "@deck.gl/geo-layers";
63
import { TileLayer } from "@deck.gl/geo-layers";
74
import { BitmapLayer } from "@deck.gl/layers";
8-
import { apply, invert } from "@developmentseed/affine";
5+
import type { MinimalTileData } from "@developmentseed/deck.gl-raster";
96
import {
10-
RasterLayer,
11-
RasterTileset2D,
7+
RasterTileLayer,
128
TileMatrixSetAdaptor,
139
} from "@developmentseed/deck.gl-raster";
1410
import type { TileMatrixSet } from "@developmentseed/morecantile";
15-
import { tileTransform } from "@developmentseed/morecantile";
16-
import type { ReprojectionFns } from "@developmentseed/raster-reproject";
1711
import type { WidgetModel } from "@jupyter-widgets/base";
1812
import proj4 from "proj4";
1913
import type { PROJJSONDefinition } from "proj4/dist/lib/core.js";
@@ -29,10 +23,8 @@ type TileResponse =
2923
| { type: "encoded-image"; media_type: string }
3024
| { error: string };
3125

32-
type TileData = {
26+
type TileData = MinimalTileData & {
3327
image: TextureSource;
34-
forwardTransform: ReprojectionFns["forwardTransform"];
35-
inverseTransform: ReprojectionFns["inverseTransform"];
3628
};
3729

3830
export class RasterModel extends BaseLayerModel {
@@ -115,9 +107,9 @@ export class RasterModel extends BaseLayerModel {
115107
};
116108
}
117109

118-
getTileData: TileLayerProps<TileData | null>["getTileData"] = async (
119-
tile,
120-
) => {
110+
getTileData = async (
111+
tile: Parameters<NonNullable<TileLayerProps["getTileData"]>>[0],
112+
): Promise<TileData | null> => {
121113
const { index } = tile;
122114
const { signal } = tile;
123115
const { x, y, z } = index;
@@ -150,77 +142,42 @@ export class RasterModel extends BaseLayerModel {
150142

151143
const image = await dataViewToImageBitmap(buffers[0], message.media_type);
152144

153-
// Compute per-tile affine transforms once; cached by TileLayer
154-
const tileMatrix = this.tileMatrixSet?.tileMatrices[z];
155-
if (!tileMatrix) {
156-
return null;
157-
}
158-
const tileAffine = tileTransform(tileMatrix, { col: x, row: y });
159-
const invAffine = invert(tileAffine);
160-
const forwardTransform = (u: number, v: number): [number, number] =>
161-
apply(tileAffine, u, v);
162-
const inverseTransform = (px: number, py: number): [number, number] =>
163-
apply(invAffine, px, py);
164-
165-
return { image, forwardTransform, inverseTransform };
145+
return { image, width: image.width, height: image.height };
166146
};
167147

168-
readonly renderRasterSubLayer: TileLayerProps<TileData | null>["renderSubLayers"] =
169-
(props) => {
170-
if (!props.data) {
171-
return null;
172-
}
173-
174-
const { image, forwardTransform, inverseTransform } = props.data;
175-
const { inverseFrom4326, forwardTo4326 } = this.accessConverters();
176-
177-
return new RasterLayer({
178-
id: `${props.id}-raster`,
179-
width: image.width,
180-
height: image.height,
181-
image,
182-
reprojectionFns: {
183-
forwardTransform,
184-
inverseTransform,
185-
forwardReproject: forwardTo4326,
186-
inverseReproject: inverseFrom4326,
187-
},
188-
});
189-
};
190-
191148
renderTileMatrixSet(
192149
tileMatrixSet: TileMatrixSet,
193150
projectTo4326: (x: number, y: number) => [number, number],
151+
projectFrom4326: (x: number, y: number) => [number, number],
194152
projectTo3857: (x: number, y: number) => [number, number],
195-
): TileLayer {
196-
const descriptor = new TileMatrixSetAdaptor(tileMatrixSet, {
153+
projectFrom3857: (x: number, y: number) => [number, number],
154+
): RasterTileLayer<TileData | null> {
155+
const tilesetDescriptor = new TileMatrixSetAdaptor(tileMatrixSet, {
197156
projectTo4326,
157+
projectFrom4326,
198158
projectTo3857,
159+
projectFrom3857,
199160
});
200161

201-
class RasterTileset2DFactory extends RasterTileset2D {
202-
constructor(opts: Tileset2DProps) {
203-
super(opts, descriptor, { projectTo4326 });
204-
}
205-
}
206-
207-
return new TileLayer<TileData | null>({
162+
return new RasterTileLayer<TileData | null>({
208163
...this.baseLayerProps(),
209164
...this.layerProps(),
165+
tilesetDescriptor,
210166
getTileData: this.getTileData,
211-
TilesetClass: RasterTileset2DFactory,
212-
renderSubLayers: this.renderRasterSubLayer,
167+
renderTile: (data) => (data ? { image: data.image } : null),
213168
});
214169
}
215170

216-
render(): TileLayer {
171+
render(): TileLayer | RasterTileLayer<TileData | null> {
217172
const tileMatrixSet = this.tileMatrixSet;
218173
const converters = this.converters;
219174
if (tileMatrixSet && converters) {
220175
return this.renderTileMatrixSet(
221176
tileMatrixSet,
222177
(x, y) => converters[4326].forward([x, y]),
178+
(x, y) => converters[4326].inverse([x, y]),
223179
(x, y) => converters[3857].forward([x, y]),
180+
(x, y) => converters[3857].inverse([x, y]),
224181
);
225182
}
226183

0 commit comments

Comments
 (0)