dyomedea/src/components/map/Map.tsx

130 lines
3.5 KiB
TypeScript
Raw Normal View History

import react from 'react';
import { atom } from 'jotai';
import { Point, MapScope } from './types';
import LayerStack from './LayerStack';
import { tileProviders } from './tile-providers';
import { lon2tile, lat2tile } from '../../lib/geo';
2022-10-17 08:37:26 +00:00
export interface MapProperties {
scope: MapScope;
numberOfTiledLayers: number;
}
2022-10-17 08:37:26 +00:00
2022-10-19 11:55:44 +00:00
/**
* Definition of a coordinate system
*
* The coordinate system is shifted and zoomed (from the viewport origin)
*
*/
export interface CoordinateSystem {
/** Zoom relative to the origin */
zoom: number;
/** Origin's shift (in pixels) */
shift: Point;
}
const initialCoordinateSystem: CoordinateSystem = {
zoom: 1,
shift: { x: 0, y: 0 },
};
2022-10-18 11:45:16 +00:00
2022-10-19 11:55:44 +00:00
/** An atom to store the map coordinates system */
export const coordinateSystemAtom = atom(initialCoordinateSystem);
2022-10-19 11:04:56 +00:00
/**
* Description of coordinates system transformation
*/
export interface Transformation {
2022-10-19 11:04:56 +00:00
/** New translation to apply */
deltaShift: Point | null;
2022-10-19 11:04:56 +00:00
/** Zoom factor to apply */
deltaZoom: number | null;
2022-10-19 11:04:56 +00:00
/** Center of the new zoom to apply */
zoomCenter: Point | null;
}
2022-10-17 20:37:07 +00:00
2022-10-19 11:04:56 +00:00
/**
* A write only atom to translate and zoom the coordinate system
*/
export const relativeCoordinateSystemAtom = atom(
null,
(get, set, t: Transformation) => {
const actualDeltaShift =
t.deltaShift === null ? { x: 0, y: 0 } : t.deltaShift;
const actualDeltaZoom = t.deltaZoom === null ? 1 : t.deltaZoom;
const actualZoomCenter =
t.zoomCenter === null ? { x: 0, y: 0 } : t.zoomCenter;
const coordinateSystem = get(coordinateSystemAtom);
2022-10-18 11:45:16 +00:00
var newCoordinateSystem = {
shift: {
x:
coordinateSystem.shift.x +
actualDeltaShift.x +
(coordinateSystem.shift.x - actualZoomCenter.x) *
(actualDeltaZoom - 1),
y:
coordinateSystem.shift.y +
actualDeltaShift.y +
(coordinateSystem.shift.y - actualZoomCenter.y) *
(actualDeltaZoom - 1),
},
zoom: coordinateSystem.zoom * actualDeltaZoom,
};
set(coordinateSystemAtom, newCoordinateSystem);
}
);
/**
*
* @returns A `<Map>` component
*
*/
export const Map: react.FC<MapProperties> = (props: MapProperties) => {
const centerPX = {
x: window.innerWidth / 2,
y: window.innerHeight / 2,
};
const tileProvider = tileProviders[props.scope.tileProvider];
const tilesZoom = Math.min(
Math.max(Math.round(props.scope.zoom), tileProvider.minZoom),
tileProvider.maxZoom
);
const tilesCenter: Point = {
x: lon2tile(props.scope.center.lon, tilesZoom),
y: lat2tile(props.scope.center.lat, tilesZoom),
};
const softZoom = props.scope.zoom - tilesZoom;
const relativeScale = 2 ** softZoom;
const visibleTileSize = tileProvider.tileSize * relativeScale;
const nbTilesLeft = window.innerWidth / 2 / visibleTileSize;
const nbTilesTop = window.innerHeight / 2 / visibleTileSize;
const firstTileLeft = Math.floor(tilesCenter.x - nbTilesLeft);
const firstTileTop = Math.floor(tilesCenter.y - nbTilesTop);
2022-10-17 20:37:07 +00:00
return (
2022-10-18 11:45:16 +00:00
<>
<LayerStack
numberOfTiledLayers={props.numberOfTiledLayers}
keyObject={{
provider: props.scope.tileProvider,
zoomLevel: tilesZoom,
x: firstTileLeft,
y: firstTileTop,
}}
coordinateSystem={{
shift: {
x: -((tilesCenter.x - nbTilesLeft) % 1) * visibleTileSize,
y: -((tilesCenter.y - nbTilesTop) % 1) * visibleTileSize,
},
zoom: relativeScale,
}}
/>
2022-10-18 11:45:16 +00:00
</>
2022-10-17 20:37:07 +00:00
);
2022-10-17 08:37:26 +00:00
};
export default Map;