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'; export interface MapProperties { scope: MapScope; numberOfTiledLayers: number; } /** * 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 }, }; /** An atom to store the map coordinates system */ export const coordinateSystemAtom = atom(initialCoordinateSystem); /** * Description of coordinates system transformation */ export interface Transformation { /** New translation to apply */ deltaShift: Point | null; /** Zoom factor to apply */ deltaZoom: number | null; /** Center of the new zoom to apply */ zoomCenter: Point | null; } /** * 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); 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 `` component * */ export const Map: react.FC = (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); return ( <> ); }; export default Map;