dyomedea/src/components/map/LayerStack.tsx

140 lines
3.7 KiB
TypeScript

import react, { useCallback, useEffect, useRef, useState } from 'react';
import { atom, useAtom } from 'jotai';
import { TileFactory, TileKeyObject } from './types';
import { coordinateSystemAtom } from './Map';
import TiledLayer from './TiledLayer';
import Tile from './Tile';
import _, { floor, range } from 'lodash';
import tileUri from './uris';
export interface LayerStackProperties {
/**
* A key identifying the initial top left tile
*/
keyObject: TileKeyObject;
/**
* Number of {@link components/map/TiledLayer!TiledLayer}.
*/
numberOfTiledLayers?: number;
}
/**
*
* @param props
* @returns A stack of layers embedded in an SVG element
*
* This component does the conversion between the {@link components/map/Map!CoordinateSystem} stored
* in the {@link components/map/Map!coordinateSystemAtom} atom and the {@link components/map/TiledLayer!TiledLayer}
* components which units are in tiles.
*
*/
export const LayerStack: react.FC<LayerStackProperties> = (
props: LayerStackProperties
) => {
const [coordinateSystem] = useAtom(coordinateSystemAtom);
const simpleTileFactory: TileFactory = useCallback(
(keyObject) => (
<Tile
href={`https://tile.openstreetmap.org/${keyObject.zoomLevel}/${keyObject.x}/${keyObject.y}.png`}
/>
),
[]
);
const numberOfTiledLayers =
props.numberOfTiledLayers === undefined ? 1 : props.numberOfTiledLayers;
const [activeTiledLayer, setActiveTiledLayer] = useState(
Math.floor(numberOfTiledLayers / 2)
);
const viewPort = {
topLeft: {
x: Math.floor(-coordinateSystem.shift.x / coordinateSystem.zoom / 256),
y: Math.floor(-coordinateSystem.shift.y / coordinateSystem.zoom / 256),
},
bottomRight: {
x: Math.ceil(
(-coordinateSystem.shift.x + window.innerWidth) /
coordinateSystem.zoom /
256
),
y: Math.ceil(
(-coordinateSystem.shift.y + window.innerHeight) /
coordinateSystem.zoom /
256
),
},
};
const getTiledLayer = (i: number) => {
const relativeZoomLevel = i - activeTiledLayer;
const zoom = 2 ** relativeZoomLevel;
const origin = {
x: props.keyObject.x * zoom,
y: props.keyObject.y * zoom,
};
const keyObject = {
provider: props.keyObject.provider,
zoomLevel: props.keyObject.zoomLevel + relativeZoomLevel,
x: Math.floor(origin.x),
y: Math.floor(origin.y),
};
const shift = {
x: origin.x - floor(origin.x),
y: origin.y - floor(origin.y),
};
return (
<g
transform={`scale(${256 * zoom}) translate(${shift.x}, ${shift.y})`}
key={tileUri({
provider: keyObject.provider,
zoomLevel: keyObject.zoomLevel,
})}
>
<TiledLayer
keyObject={keyObject}
viewPort={i === activeTiledLayer ? viewPort : undefined}
tileFactory={simpleTileFactory}
/>
</g>
);
};
const tileLayers = range(0, numberOfTiledLayers).map((i) => {});
// console.log(`tiledLayers: ${JSON.stringify(tiledLayers)}`);
return (
<svg
width={window.innerWidth}
height={window.innerHeight}
data-testid='layer-stack'
>
<g
transform={`translate(${coordinateSystem.shift.x}, ${coordinateSystem.shift.y}) scale(${coordinateSystem.zoom})`}
>
{
// Tiled layers with less detail
range(0, activeTiledLayer).map(getTiledLayer)
}
{
// Tiled layers with more details
range(numberOfTiledLayers, activeTiledLayer + 1, -1).map(
getTiledLayer
)
}
{
// And the active one
getTiledLayer(activeTiledLayer)
}
</g>
</svg>
);
};
export default LayerStack;