|
| 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. |
0 commit comments