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. * * 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. * TODO: housekeeping * TODO: remove tileFactory to facilitate memoisation. * */ export const TiledLayer: react.FC = memo( (props: TiledLayerProperties) => { console.log(`Rendering TiledLayer: ${JSON.stringify(props)}`); const tiles = useRef(new Map()); if (props.viewPort !== undefined) { range(props.viewPort.topLeft.y, props.viewPort.bottomRight.y + 1).forEach( (row) => { range( props.viewPort!.topLeft.x, props.viewPort!.bottomRight.x + 1 ).forEach((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 (!tiles.current.has(key)) { tiles.current.set( key, {props.tileFactory(keyObject)} ); } }); } ); } return <>{Array.from(tiles.current.values())}; }, (prevProps, nextProps) => isEqual( [prevProps.keyObject, prevProps.viewPort], [nextProps.keyObject, nextProps.viewPort] ) ); export default TiledLayer;