import react, { memo, useRef } from 'react'; import { range, isEqual } from 'lodash'; import { Rectangle, TileFactory, TileKeyObject } from './types'; import tileUri from './uris'; export interface TiledLayerProperties { /** The key of the first (ie top/left) tile */ keyObject: TileKeyObject; /** The current viewport expressed in tiles coordinates */ viewPort: Rectangle; /** The factory to create tiles */ tileFactory: TileFactory; } /** * A lazily loaded layer of tiles. * * This component is rather dumb and is mainly a sparse array of tiles. * * New tiles are added to the array when the viewport is updated and they stay in the array until * the component is destroyed or its number of tiles is updated. * * This component has no need to know the number nor the size of its tiles: tiles can be added when needed and * its unit is the tile size (the parent component needs to transform its enclosing SVG group to adapt its units) * * TODO: test tiles'X and Y boundaries. * */ export const TiledLayer: react.FC = memo( (props: TiledLayerProperties) => { console.log(`Rendering TiledLayer: ${JSON.stringify(props)}`); const tiles = useRef({}); const previousKeyObject = useRef(props.keyObject); if (!isEqual(props.keyObject, previousKeyObject.current)) { previousKeyObject.current = props.keyObject; tiles.current = {}; } range(props.viewPort.topLeft.y, props.viewPort.bottomRight.y + 1).map( (row) => { range(props.viewPort.topLeft.x, props.viewPort.bottomRight.x + 1).map( (col) => { const keyObject = { provider: props.keyObject.provider, zoomLevel: props.keyObject.zoomLevel, x: props.keyObject.x + col, y: props.keyObject.y + row, }; const key = tileUri(keyObject); if (!Object.hasOwn(tiles.current, key)) { tiles.current[key] = ( {props.tileFactory(keyObject)} ); } } ); } ); return <>{Object.values(tiles.current)}; }, isEqual ); export default TiledLayer;