dyomedea/src/components/map/TiledLayer.tsx

100 lines
2.8 KiB
TypeScript

import react, { memo, useRef } from 'react';
import { range, isEqual } from 'lodash';
import { Rectangle, TileFactory, TileKeyObject } from './types';
import tileUri from './uris';
import tileFactory from './tile-factory';
/**
* @hidden
*/
export const thisIsAModule = true;
/**
*
*/
declare global {
var cacheForTiledLayer: any;
}
//export {};
globalThis.cacheForTiledLayer = new Map();
export interface TiledLayerProperties {
/** The key of the first (ie top/left) tile */
keyObject: TileKeyObject;
/** The current viewport expressed in tiles coordinates */
viewPort?: Rectangle;
}
/**
* 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)
*
* The `globalThis.cacheForTiledLayer` global variable is used as a cache to store tiles without being subject
* to re-initialization when components are unmounted/remounted.
*
* This cache is a map of map, the first key identifying the `<TiledLayer` and the second one for `<Tile>`s.
*
* Idea stolen [on the web](https://dev.to/tiagof/react-re-mounting-vs-re-rendering-lnh)
*
* TODO: cache housekeeping
*
* TODO: test tiles'X and Y boundaries.
*
* TODO: remove tileFactory to facilitate memoisation.
*
*/
export const TiledLayer: react.FC<TiledLayerProperties> = (
props: TiledLayerProperties
) => {
// console.log(`Rendering TiledLayer: ${JSON.stringify(props)}`);
const key = tileUri({
provider: props.keyObject.provider,
zoomLevel: props.keyObject.zoomLevel,
});
const tiles: any = globalThis.cacheForTiledLayer.get(key) ?? 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.has(key)) {
tiles.set(
key,
<g key={key} transform={`translate(${col}, ${row})`}>
{tileFactory(keyObject)}
</g>
);
}
});
}
);
globalThis.cacheForTiledLayer.set(key, tiles);
}
return <>{Array.from(tiles.values())}</>;
};
export default TiledLayer;