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
14 changes: 7 additions & 7 deletions packages/deck.gl-geotiff/src/mosaic-layer/mosaic-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ export type MosaicLayerProps<
* remove items.
*
* Tile cache reuse depends on stable tile IDs. By default, each source's
* tile ID is derived from its position in this array (see `MosaicSource`'s
* `x` / `y` / `z` for the exact derivation), so:
* tile ID is derived from its position in this array (see
* `MosaicSource.key`), so:
*
* - Appending items preserves all existing rendered tiles.
* - Reordering or removing items from the middle of the array invalidates
* the cache slots of shifted items, causing them to re-fetch.
*
* Supply explicit `x`, `y`, and `z` identifiers per source if you need
* cache stability across arbitrary mutations of `sources`.
* Supply an explicit `key` per source if you need cache stability across
* arbitrary mutations of `sources`.
*/
sources: MosaicT[];

Expand Down Expand Up @@ -108,9 +108,9 @@ export class MosaicLayer<
maxRequests,
getTileData: async (data) => {
// We hard-cast this because TilesetClass is not generic.
// TilesetClass returns MosaicT in `index`, but the known type is only
// `TileIndex`, which only defines x,y,z
const index = data.index as MosaicT;
// MosaicTileset2D returns MosaicT in `index`, but TileLayer's typing
// exposes only the plain `TileIndex` here.
const index = data.index as unknown as MosaicT;
const { signal } = data;
const userData =
this.props.getSource &&
Expand Down
68 changes: 48 additions & 20 deletions packages/deck.gl-geotiff/src/mosaic-layer/mosaic-tileset-2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,13 @@ export type TileIndex = { x: number; y: number; z: number };
*/
export type MosaicSource = {
/**
* Optional tile-cache identifier component. Together with `y` and `z`, forms
* the tile ID `${z}-${x}-${y}` used to key the inner Tileset2D cache.
* Defaults to the source's index in the `sources` array. Supply an explicit
* value when you need cache stability across reordering or removal of items.
* Optional stable identifier used as this source's tile-cache key in the
* inner Tileset2D. Defaults to the source's position in the `sources`
* array. Supply an explicit value when the sources list is reordered or
* spliced at runtime, so a given source keeps the same cache slot across
* updates.
*/
x?: number;
/** See `x`. Defaults to `0`. */
y?: number;
/** See `x`. Defaults to `0`. */
z?: number;
key?: string;
/**
* Geographic bounds (WGS84) of the source in [minX, minY, maxX, maxY] format
*/
Expand All @@ -43,10 +40,15 @@ export type MosaicSource = {
* closure returns a new array reference (compared by `===`); mutating the
* array in place will not be detected.
*/
/** Sources prepared for spatial querying: each source augmented with the
* `TileIndex` fields, paired with a Flatbush index over their bboxes. */
/** A source augmented with the `TileIndex` fields and a resolved `key`
* (defaulting to the array position) so deck.gl typing is satisfied and the
* cache identifier is always defined. */
type ResolvedSource<MosaicT> = TileIndex & MosaicT & { key: string };

/** Sources prepared for spatial querying, paired with a Flatbush index over
* their bboxes. */
type BuiltIndex<MosaicT> = {
sources: (TileIndex & MosaicT)[];
sources: ResolvedSource<MosaicT>[];
index: Flatbush;
};

Expand Down Expand Up @@ -86,14 +88,18 @@ export class MosaicTileset2D<MosaicT extends MosaicSource> extends Tileset2D {
return null;
}

// Add x,y,z to each source for TileIndex compatibility
// This is mostly just a hack to satisfy deck.gl typing requirements for
// getTileIndices
const sources: (TileIndex & MosaicT)[] = raw.map((source, i) => ({
x: source.x === undefined ? i : source.x,
y: source.y === undefined ? 0 : source.y,
z: source.z === undefined ? 0 : source.z,
// Augment each source with a resolved `key` (used by getTileId for
// cache keying) and dummy `x`/`y`/`z` fields to satisfy deck.gl's
// TileIndex typing. The x/y/z values are never read because getTileId is
// overridden below.
const sources: ResolvedSource<MosaicT>[] = raw.map((source, i) => ({
...source,
key: source.key ?? String(i),
// TODO: remove x, y, z fields when merged
// https://github.com/visgl/deck.gl/pull/10299
x: i,
y: 0,
z: 0,
}));

const index = new Flatbush(raw.length);
Expand All @@ -107,6 +113,28 @@ export class MosaicTileset2D<MosaicT extends MosaicSource> extends Tileset2D {
return this.cached;
}

/** The Tileset2D cache key for a source. */
override getTileId(tileIndex: TileIndex): string {
// `getTileIndices` always returns `ResolvedSource`s, so a `key` is
// present on every value deck.gl will pass back here.
return (tileIndex as ResolvedSource<MosaicT>).key;
}

/** Must override to provide a zoom level for the tile. */
override getTileZoom(_tileIndex: TileIndex): number {
return 0;
}

/** Must override because our tileIndex does not have x, y, z */
override getTileMetadata(tileIndex: TileIndex): Record<string, any> {
const { key, bbox } = tileIndex as unknown as ResolvedSource<MosaicT>;
return { key, bbox };
}

override getParentIndex(tileIndex: TileIndex): TileIndex {
return tileIndex;
}

override getTileIndices({
viewport,
maxZoom,
Expand All @@ -115,7 +143,7 @@ export class MosaicTileset2D<MosaicT extends MosaicSource> extends Tileset2D {
viewport: Viewport;
maxZoom?: number;
minZoom?: number;
}): (TileIndex & MosaicT)[] {
}): ResolvedSource<MosaicT>[] {
if (viewport.zoom < (minZoom ?? -Infinity)) {
return [];
}
Expand Down
17 changes: 8 additions & 9 deletions packages/deck.gl-geotiff/tests/mosaic-tileset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ describe("MosaicTileset2D dynamic sources", () => {
expect(result.map((s) => s.id).sort()).toEqual(["A", "B"]);
});

it("derives default tile-id components from array position", () => {
it("defaults each source's tile-cache key to its array position", () => {
const sourcesRef: { current: Item[] } = { current: [A, B, C] };
const tileset = new MosaicTileset2D<Item>(
() => sourcesRef.current,
Expand All @@ -109,18 +109,16 @@ describe("MosaicTileset2D dynamic sources", () => {
});

const byId = new Map(result.map((s) => [s.id, s] as const));
expect(byId.get("A")).toMatchObject({ x: 0, y: 0, z: 0 });
expect(byId.get("B")).toMatchObject({ x: 1, y: 0, z: 0 });
expect(byId.get("C")).toMatchObject({ x: 2, y: 0, z: 0 });
expect(tileset.getTileId(byId.get("A")!)).toBe("0");
expect(tileset.getTileId(byId.get("B")!)).toBe("1");
expect(tileset.getTileId(byId.get("C")!)).toBe("2");
});

it("respects explicit x/y/z on a source", () => {
it("respects an explicit `key` on a source", () => {
const explicit: MosaicSource & { id: string } = {
id: "explicit",
bbox: [0, 0, 10, 10],
x: 42,
y: 7,
z: 3,
key: "stable-id",
};
const tileset = new MosaicTileset2D<MosaicSource & { id: string }>(
() => [explicit],
Expand All @@ -131,7 +129,8 @@ describe("MosaicTileset2D dynamic sources", () => {
viewport: fakeViewport([-1, -1, 11, 11]),
});

expect(result[0]).toMatchObject({ id: "explicit", x: 42, y: 7, z: 3 });
expect(result[0]).toMatchObject({ id: "explicit", key: "stable-id" });
expect(tileset.getTileId(result[0]!)).toBe("stable-id");
});

it("returns no tiles when zoom is outside the [minZoom, maxZoom] range", () => {
Expand Down