Adding a component to take care of translations and scalings around `<TileSet>` components (not used yeti but should simplify `<LayerStack>`).

This commit is contained in:
Eric van der Vlist 2022-10-30 21:35:32 +01:00
parent 7322e5f5c4
commit ef35ffca32
2 changed files with 250 additions and 0 deletions

View File

@ -0,0 +1,165 @@
import { renderHook, act, render, screen } from '@testing-library/react';
import { useAtom } from 'jotai';
import LayerStack from './LayerStack';
import { coordinateSystemAtom, relativeCoordinateSystemAtom } from './Map';
import TiledLayer from './TiledLayer';
describe('The TiledLayer component', () => {
beforeEach(() => {
globalThis.cacheForTileSet = new Map();
});
test('generates an empty layer if inactive', () => {
// const { result } = renderHook(() => useAtom(tiledLayersAtom));
render(
<svg data-testid='tiled-layer'>
<TiledLayer
keyObject={{ provider: 'fake', zoomLevel: 5, x: 2, y: 3 }}
shift={{ x: 0.5, y: 0.25 }}
zoom={4}
/>
</svg>
);
const svg = screen.getByTestId('tiled-layer');
expect(svg).toMatchInlineSnapshot(`
<svg
data-testid="tiled-layer"
>
<g
transform="scale(4) translate(0.5, 0.25)"
/>
</svg>
`);
});
test('generates a populated layer if active', () => {
// const { result } = renderHook(() => useAtom(tiledLayersAtom));
render(
<svg data-testid='tiled-layer'>
<TiledLayer
keyObject={{ provider: 'fake', zoomLevel: 5, x: 2, y: 3 }}
shift={{ x: 0, y: 0 }}
zoom={1}
/>
</svg>
);
const svg = screen.getByTestId('tiled-layer');
expect(svg).toMatchInlineSnapshot(`
<svg
data-testid="tiled-layer"
>
<g
transform="scale(1) translate(0, 0)"
>
<g
transform="translate(2, 3)"
>
<image
height="1"
href="https://fakeurl/5/2/3.png"
width="1"
/>
</g>
<g
transform="translate(3, 3)"
>
<image
height="1"
href="https://fakeurl/5/3/3.png"
width="1"
/>
</g>
<g
transform="translate(4, 3)"
>
<image
height="1"
href="https://fakeurl/5/4/3.png"
width="1"
/>
</g>
<g
transform="translate(5, 3)"
>
<image
height="1"
href="https://fakeurl/5/5/3.png"
width="1"
/>
</g>
<g
transform="translate(2, 4)"
>
<image
height="1"
href="https://fakeurl/5/2/4.png"
width="1"
/>
</g>
<g
transform="translate(3, 4)"
>
<image
height="1"
href="https://fakeurl/5/3/4.png"
width="1"
/>
</g>
<g
transform="translate(4, 4)"
>
<image
height="1"
href="https://fakeurl/5/4/4.png"
width="1"
/>
</g>
<g
transform="translate(5, 4)"
>
<image
height="1"
href="https://fakeurl/5/5/4.png"
width="1"
/>
</g>
<g
transform="translate(2, 5)"
>
<image
height="1"
href="https://fakeurl/5/2/5.png"
width="1"
/>
</g>
<g
transform="translate(3, 5)"
>
<image
height="1"
href="https://fakeurl/5/3/5.png"
width="1"
/>
</g>
<g
transform="translate(4, 5)"
>
<image
height="1"
href="https://fakeurl/5/4/5.png"
width="1"
/>
</g>
<g
transform="translate(5, 5)"
>
<image
height="1"
href="https://fakeurl/5/5/5.png"
width="1"
/>
</g>
</g>
</svg>
`);
});
});

View File

@ -0,0 +1,85 @@
import { useAtom } from 'jotai';
import react from 'react';
import TileSet from './TileSet';
import { Point, TileKeyObject } from './types';
import { coordinateSystemAtom } from './Map';
export interface TiledLayerProperties {
/**
* A key identifying the top left tile
*/
keyObject: TileKeyObject;
/**
* A flag to indicate if the layer is active and should add tiles which are in its viewport or if it is "only" a backup.
*/
shift: Point;
/**
* The zoom to apply. If equal to 1, the layer is considered active and should add tiles which are in its viewport
*/
zoom: number;
}
/**
*
* @param props
* @returns A layer of tiles.
* This component wraps a `<TileSet>` in an SVG `<g>` element taking care of the scale and translation.
*
*/
export const TiledLayer: react.FC<TiledLayerProperties> = (
props: TiledLayerProperties
) => {
const [coordinateSystem] = useAtom(coordinateSystemAtom);
const viewPort =
props.zoom === 1
? {
topLeft: {
x:
props.keyObject.x +
Math.floor(
-coordinateSystem.shift.x / coordinateSystem.zoom / 256
),
y:
props.keyObject.y +
Math.floor(
-coordinateSystem.shift.y / coordinateSystem.zoom / 256
),
},
bottomRight: {
x:
props.keyObject.x +
Math.ceil(
(-coordinateSystem.shift.x + window.innerWidth) /
coordinateSystem.zoom /
256
) -
1,
y:
props.keyObject.y +
Math.ceil(
(-coordinateSystem.shift.y + window.innerHeight) /
coordinateSystem.zoom /
256
) -
1,
},
}
: undefined;
return (
<g
transform={`scale(${props.zoom}) translate(${props.shift.x}, ${props.shift.y})`}
>
<TileSet
keyObject={{
provider: props.keyObject.provider,
zoomLevel: props.keyObject.zoomLevel,
x: 0,
y: 0,
}}
viewPort={viewPort}
/>
</g>
);
};
export default TiledLayer;