140 lines
3.7 KiB
TypeScript
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;
|