diff --git a/docs/api-reference/geo-layers/tile-layer.md b/docs/api-reference/geo-layers/tile-layer.md index ded3d7b01a9..f247985c2aa 100644 --- a/docs/api-reference/geo-layers/tile-layer.md +++ b/docs/api-reference/geo-layers/tile-layer.md @@ -261,9 +261,15 @@ The min zoom level of the layer's data. When underzoomed (i.e. `zoom < minZoom`) - Default: 0 +#### `overdraw` (boolean, optional) {#overdraw} + +Controls rendering behavior when viewport zoom is greater than maxZoom. When true (default), tiles at the clamped zoom level are shown. When false, no tiles are rendered. + +- Default: `true` + #### `extent` (number[4], optional) {#extent} -The bounding box of the layer's data, in the form of `[minX, minY, maxX, maxY]`. If provided, the layer will only load and render the tiles that are needed to fill this box. +The bounding box of the layer's data, in the form of `[minX, minY, maxX, maxY]`. If provided, the layer will only load and render the tiles that are needed to fill this box. - Default: `null` diff --git a/examples/website/map-tile/app.tsx b/examples/website/map-tile/app.tsx index 14424beef5d..544bd71c004 100644 --- a/examples/website/map-tile/app.tsx +++ b/examples/website/map-tile/app.tsx @@ -22,6 +22,9 @@ const INITIAL_VIEW_STATE: MapViewState = { bearing: 0 }; +// Approximate bounding box of France [west, south, east, north] +const FRANCE_EXTENT = [-5.14, 41.33, 9.56, 51.09]; + const COPYRIGHT_LICENSE_STYLE: React.CSSProperties = { position: 'absolute', right: 0, @@ -50,10 +53,18 @@ function getTooltip({tile}: TileLayerPickingInfo) { export default function App({ showBorder = false, - onTilesLoad + onTilesLoad, + minZoom = 4, + maxZoom = 7, + overdraw = false, + useExtent = false }: { showBorder?: boolean; onTilesLoad?: () => void; + minZoom?: number; + maxZoom?: number; + overdraw?: boolean; + useExtent?: boolean; }) { const tileLayer = new TileLayer({ // https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_servers @@ -68,10 +79,12 @@ export default function App({ autoHighlight: showBorder, highlightColor: [60, 60, 60, 40], // https://wiki.openstreetmap.org/wiki/Zoom_levels - minZoom: 0, - maxZoom: 19, + minZoom, + maxZoom, tileSize: 256, zoomOffset: devicePixelRatio === 1 ? -1 : 0, + overdraw, + extent: useExtent ? FRANCE_EXTENT : undefined, renderSubLayers: props => { const [[west, south], [east, north]] = props.tile.boundingBox; const {data, ...otherProps} = props; diff --git a/modules/geo-layers/src/tile-layer/tile-layer.ts b/modules/geo-layers/src/tile-layer/tile-layer.ts index e3defa432d9..cd8136d0596 100644 --- a/modules/geo-layers/src/tile-layer/tile-layer.ts +++ b/modules/geo-layers/src/tile-layer/tile-layer.ts @@ -50,7 +50,8 @@ const defaultProps: DefaultProps = { zRange: null, maxRequests: 6, debounceTime: 0, - zoomOffset: 0 + zoomOffset: 0, + overdraw: true }; /** All props supported by the TileLayer */ @@ -149,6 +150,16 @@ type _TileLayerProps = { * @default 0 */ zoomOffset?: number; + + /** + * Controls rendering behavior when viewport zoom is greater than maxZoom. + * + * When true (default), tiles at the clamped zoom level are shown. + * When false, no tiles are rendered. + * + * @default true + */ + overdraw?: boolean; }; export type TileLayerPickingInfo< @@ -243,7 +254,8 @@ export default class TileLayer extends minZoom, maxRequests, debounceTime, - zoomOffset + zoomOffset, + overdraw } = this.props; return { @@ -257,6 +269,7 @@ export default class TileLayer extends maxRequests, debounceTime, zoomOffset, + overdraw, getTileData: this.getTileData.bind(this), onTileLoad: this._onTileLoad.bind(this), diff --git a/modules/geo-layers/src/tileset-2d/tileset-2d.ts b/modules/geo-layers/src/tileset-2d/tileset-2d.ts index 2b54ec85bec..ba141a8be2b 100644 --- a/modules/geo-layers/src/tileset-2d/tileset-2d.ts +++ b/modules/geo-layers/src/tileset-2d/tileset-2d.ts @@ -81,6 +81,8 @@ export type Tileset2DProps = { debounceTime?: number; /** Changes the zoom level at which the tiles are fetched. Needs to be an integer. @default 0 */ zoomOffset?: number; + /** If false, tiles are not rendered when the viewport zoom is outside the [minZoom, maxZoom] range. @default true */ + overdraw?: boolean; /** Called when a tile successfully loads. */ onTileLoad?: (tile: Tile2DHeader) => void; @@ -109,6 +111,7 @@ export const DEFAULT_TILESET2D_PROPS: Omit, 'getTileDat maxRequests: 6, debounceTime: 0, zoomOffset: 0, + overdraw: true, // onTileLoad: (tile: Tile2DHeader) => void, // onTileUnload: (tile: Tile2DHeader) => void, // onTileError: (error: any, tile: Tile2DHeader) => void, /** Called when all tiles in the current viewport are loaded. */ // onViewportLoad: ((tiles: Tile2DHeader[]) => void) | null, @@ -204,6 +207,8 @@ export class Tileset2D { if (Number.isFinite(opts.minZoom)) { this._minZoom = Math.ceil(opts.minZoom as number); } + // Force re-evaluation of tile indices on next update + this._viewport = null; } // Clean up any outstanding tile requests. @@ -351,7 +356,7 @@ export class Tileset2D { modelMatrixInverse?: Matrix4; zoomOffset?: number; }): TileIndex[] { - const {tileSize, extent, zoomOffset} = this.opts; + const {tileSize, extent, zoomOffset, overdraw} = this.opts; return getTileIndices({ viewport, maxZoom, @@ -361,7 +366,8 @@ export class Tileset2D { extent: extent as Bounds | undefined, modelMatrix, modelMatrixInverse, - zoomOffset + zoomOffset, + overdraw }); } diff --git a/modules/geo-layers/src/tileset-2d/utils.ts b/modules/geo-layers/src/tileset-2d/utils.ts index c00e2719ac4..aedd3283d73 100644 --- a/modules/geo-layers/src/tileset-2d/utils.ts +++ b/modules/geo-layers/src/tileset-2d/utils.ts @@ -272,7 +272,8 @@ export function getTileIndices({ tileSize = TILE_SIZE, modelMatrix, modelMatrixInverse, - zoomOffset = 0 + zoomOffset = 0, + overdraw = true }: { viewport: Viewport; maxZoom?: number; @@ -283,6 +284,7 @@ export function getTileIndices({ modelMatrix?: Matrix4; modelMatrixInverse?: Matrix4; zoomOffset?: number; + overdraw?: boolean; }) { let z = viewport.isGeospatial ? Math.round(viewport.zoom + Math.log2(TILE_SIZE / tileSize)) + zoomOffset @@ -294,6 +296,9 @@ export function getTileIndices({ z = minZoom; } if (typeof maxZoom === 'number' && Number.isFinite(maxZoom) && z > maxZoom) { + if (!overdraw) { + return []; + } z = maxZoom; } let transformedExtent = extent; diff --git a/website/src/examples/tile-layer.js b/website/src/examples/tile-layer.js index f2b6ddf4bdb..31765d59d76 100644 --- a/website/src/examples/tile-layer.js +++ b/website/src/examples/tile-layer.js @@ -15,7 +15,11 @@ class MapTileDemo extends Component { static code = `${GITHUB_TREE}/examples/website/map-tile`; static parameters = { - showBorder: {displayName: 'Show tile borders', type: 'checkbox', value: false} + showBorder: {displayName: 'Show tile borders', type: 'checkbox', value: false}, + minZoom: {displayName: 'Min Zoom', type: 'range', value: 0, step: 1, min: 0, max: 19}, + maxZoom: {displayName: 'Max Zoom', type: 'range', value: 19, step: 1, min: 0, max: 19}, + overdraw: {displayName: 'Overdraw', type: 'checkbox', value: true}, + useExtent: {displayName: 'Extent (France)', type: 'checkbox', value: false} }; static renderInfo(meta) { @@ -44,7 +48,15 @@ class MapTileDemo extends Component { // eslint-disable-next-line no-unused-vars const {params, ...otherProps} = this.props; return ( - + ); } }