From d970b288e4bc62951726dad4d541f6f21a9e14ac Mon Sep 17 00:00:00 2001 From: evlist Date: Thu, 20 Oct 2022 16:21:27 +0200 Subject: [PATCH] Using a global variable to cache s for s. --- src/components/map/LayerStack.test.tsx | 646 ++++++++++++++++++++++++- src/components/map/LayerStack.tsx | 29 +- src/components/map/Tile.tsx | 4 +- src/components/map/TiledLayer.test.tsx | 13 +- src/components/map/TiledLayer.tsx | 97 ++-- 5 files changed, 728 insertions(+), 61 deletions(-) diff --git a/src/components/map/LayerStack.test.tsx b/src/components/map/LayerStack.test.tsx index 4f6b477..2885d90 100644 --- a/src/components/map/LayerStack.test.tsx +++ b/src/components/map/LayerStack.test.tsx @@ -1,9 +1,10 @@ import { renderHook, act, render, screen } from '@testing-library/react'; import { useAtom } from 'jotai'; import LayerStack from './LayerStack'; +import { coordinateSystemAtom, relativeCoordinateSystemAtom } from './Map'; describe('The LayerStack component', () => { - test('generates something', () => { + test('generates four empty layers and a populated one', () => { // const { result } = renderHook(() => useAtom(tiledLayersAtom)); render( { transform="translate(0, 0) scale(1)" > { +`); + }); + + test('generates two empty layers and a populated one', () => { + // const { result } = renderHook(() => useAtom(tiledLayersAtom)); + render( + + ); + const svg = screen.getByTestId('layer-stack'); + expect(svg).toMatchInlineSnapshot(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`); + }); + + test('populates a new layer when zoomed in', () => { + // const { result } = renderHook(() => useAtom(tiledLayersAtom)); + render( + + ); + const { result } = renderHook(() => [ + useAtom(coordinateSystemAtom), + useAtom(relativeCoordinateSystemAtom), + ]); + act(() => { + result.current[0][1]({ + shift: { + x: 0, + y: 0, + }, + zoom: 1, + } as any); + result.current[1][1]({ + deltaShift: null, + zoomCenter: { x: 0, y: 0 }, + deltaZoom: 2, + } as any); + }); + const svg = screen.getByTestId('layer-stack'); + expect(svg).toMatchInlineSnapshot(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `); }); }); diff --git a/src/components/map/LayerStack.tsx b/src/components/map/LayerStack.tsx index 037e72c..498b571 100644 --- a/src/components/map/LayerStack.tsx +++ b/src/components/map/LayerStack.tsx @@ -47,8 +47,13 @@ export const LayerStack: react.FC = ( const numberOfTiledLayers = props.numberOfTiledLayers === undefined ? 1 : props.numberOfTiledLayers; - const [activeTiledLayer, setActiveTiledLayer] = useState( - Math.floor(numberOfTiledLayers / 2) + const activeTiledLayer = Math.min( + Math.max( + Math.round(Math.log2(coordinateSystem.zoom)) + + Math.floor(numberOfTiledLayers / 2), + 0 + ), + numberOfTiledLayers - 1 ); const viewPort = { @@ -71,7 +76,7 @@ export const LayerStack: react.FC = ( }; const getTiledLayer = (i: number) => { - const relativeZoomLevel = i - activeTiledLayer; + const relativeZoomLevel = i - Math.floor(numberOfTiledLayers / 2); const zoom = 2 ** relativeZoomLevel; const origin = { x: props.keyObject.x * zoom, @@ -87,15 +92,19 @@ export const LayerStack: react.FC = ( x: origin.x - floor(origin.x), y: origin.y - floor(origin.y), }; + const key = tileUri({ + provider: keyObject.provider, + zoomLevel: keyObject.zoomLevel, + }); + return ( = ( ); }; - const tileLayers = range(0, numberOfTiledLayers).map((i) => {}); - // console.log(`tiledLayers: ${JSON.stringify(tiledLayers)}`); return ( @@ -123,7 +130,7 @@ export const LayerStack: react.FC = ( } { // Tiled layers with more details - range(numberOfTiledLayers, activeTiledLayer + 1, -1).map( + range(numberOfTiledLayers - 1, activeTiledLayer, -1).map( getTiledLayer ) } diff --git a/src/components/map/Tile.tsx b/src/components/map/Tile.tsx index fb3f2fc..fc9e691 100644 --- a/src/components/map/Tile.tsx +++ b/src/components/map/Tile.tsx @@ -27,10 +27,10 @@ export const Tile: react.FC = memo((props: TileProperties) => { const timeout = (ms: number) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; - console.log(`Rendering tile: ${props.href}`); + // console.log(`Rendering tile: ${props.href}`); useEffect(() => { const loadImage = async () => { - console.log(`Pre loading: ${props.href}`); + // console.log(`Pre loading: ${props.href}`); const image = new Image(1, 1); image.loading = 'eager'; // @ts-ignore diff --git a/src/components/map/TiledLayer.test.tsx b/src/components/map/TiledLayer.test.tsx index b6fe5d0..6e8238e 100644 --- a/src/components/map/TiledLayer.test.tsx +++ b/src/components/map/TiledLayer.test.tsx @@ -7,6 +7,10 @@ const fakeTileFactory: TileFactory = (keyObject) => ( ); describe('The TiledLayer component ', () => { + beforeEach(() => { + globalThis.cacheForTiledLayer = new Map(); + }); + test('exposes the tiles needed per its viewport', () => { const { baseElement } = render( @@ -133,7 +137,7 @@ describe('The TiledLayer component ', () => { rerender( @@ -169,21 +173,21 @@ describe('The TiledLayer component ', () => { transform="translate(5, 0)" > - {"provider":"osm","zoomLevel":11,"x":10,"y":8} + {"provider":"osm","zoomLevel":10,"x":9,"y":10} - {"provider":"osm","zoomLevel":11,"x":10,"y":9} + {"provider":"osm","zoomLevel":10,"x":9,"y":11} - {"provider":"osm","zoomLevel":11,"x":10,"y":10} + {"provider":"osm","zoomLevel":10,"x":9,"y":12} @@ -267,5 +271,4 @@ describe('The TiledLayer component ', () => { `); }); - }); diff --git a/src/components/map/TiledLayer.tsx b/src/components/map/TiledLayer.tsx index 985638c..c4b55e0 100644 --- a/src/components/map/TiledLayer.tsx +++ b/src/components/map/TiledLayer.tsx @@ -4,6 +4,26 @@ import { range, isEqual } from 'lodash'; import { Rectangle, TileFactory, TileKeyObject } from './types'; import tileUri from './uris'; +export const thisIsAModule = true; + +/** + * 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 ``s. + * + * Idea stolen [on the web](https://dev.to/tiagof/react-re-mounting-vs-re-rendering-lnh) + * + * TODO: housekeeping + * + */ +declare global { + var cacheForTiledLayer: any; +} + +export {}; + +globalThis.cacheForTiledLayer = new Map(); + export interface TiledLayerProperties { /** The key of the first (ie top/left) tile */ keyObject: TileKeyObject; @@ -29,45 +49,46 @@ export interface TiledLayerProperties { * 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()); +export const TiledLayer: react.FC = ( + props: TiledLayerProperties +) => { + // console.log(`Rendering TiledLayer: ${JSON.stringify(props)}`); - 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)} - - ); - } - }); - } - ); - } + const key = tileUri({ + provider: props.keyObject.provider, + zoomLevel: props.keyObject.zoomLevel, + }); - return <>{Array.from(tiles.current.values())}; - }, - (prevProps, nextProps) => - isEqual( - [prevProps.keyObject, prevProps.viewPort], - [nextProps.keyObject, nextProps.viewPort] - ) -); + 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, + + {props.tileFactory(keyObject)} + + ); + } + }); + } + ); + globalThis.cacheForTiledLayer.set(key, tiles); + } + + return <>{Array.from(tiles.values())}; +}; export default TiledLayer;