dyomedea/src/components/map/LiveMap.tsx

128 lines
3.9 KiB
TypeScript
Raw Normal View History

2022-10-31 17:05:58 +00:00
import react, { useState } from 'react';
import { MapScope, Point } from './types';
import Map from './Map';
import Handlers from './Handlers';
import { tileProviders } from './tile-providers';
import { lon2tile, lat2tile, tile2lat, tile2long } from '../../lib/geo';
2022-10-31 17:05:58 +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;
}
2022-10-31 17:05:58 +00:00
/**
* 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;
}
export interface LiveMapProperties {
scope: MapScope;
numberOfTiledLayers?: number;
}
2022-11-01 13:37:08 +00:00
/**
*
* @param props
* @returns A `<LiveMap>` component.
*
* A `<LiveMap>` is a wrapper around a {@link components/map/Map!Map} component which updates the `<Map>`'s scope according to user's mouse, wheel and touch events.
*
* To do so, `<LiveMap>` embeds a `<Map>` component together with a {@link components/map/Handlers!Handlers} component which listens to user's event.
*
* The main task of `<LiveMap>` components is thus to translate {@link Transformation}s delivered by `<Handler>` in pixels into geographical coordinates.
*/
2022-10-31 17:05:58 +00:00
export const LiveMap: react.FC<LiveMapProperties> = (
props: LiveMapProperties
) => {
const [scope, setScope] = useState(props.scope);
2022-11-01 13:17:36 +00:00
// console.log(`LiveMap rendering: ${JSON.stringify(scope)}`);
2022-10-31 17:05:58 +00:00
const transform = (t: Transformation) => {
2022-10-31 20:01:56 +00:00
const deltaZoom = t.deltaZoom === null ? 1 : t.deltaZoom;
const deltaZoomLevel = Math.log2(deltaZoom);
2022-10-31 17:05:58 +00:00
const tileProvider = tileProviders[scope.tileProvider];
const tilesZoom = Math.min(
Math.max(Math.round(scope.zoom), tileProvider.minZoom),
tileProvider.maxZoom
);
const softZoom = scope.zoom - tilesZoom;
const relativeScale = 2 ** softZoom;
const visibleTileSize = tileProvider.tileSize * relativeScale;
2022-10-31 20:01:56 +00:00
// Values in pixels
const actualDeltaShift =
t.deltaShift === null ? { x: 0, y: 0 } : t.deltaShift;
const actualZoomCenter =
t.zoomCenter === null ? { x: 0, y: 0 } : t.zoomCenter;
// Values in tiles (for the current zoom level)
const actualDeltaShiftTiles = {
x: actualDeltaShift.x / visibleTileSize,
y: actualDeltaShift.y / visibleTileSize,
};
const actualZoomCenterTiles = {
x: actualZoomCenter.x / visibleTileSize,
y: actualZoomCenter.y / visibleTileSize,
};
2022-10-31 17:05:58 +00:00
const tilesCenter: Point = {
x: lon2tile(scope.center.lon, tilesZoom),
y: lat2tile(scope.center.lat, tilesZoom),
};
2022-10-31 20:01:56 +00:00
2022-10-31 17:05:58 +00:00
const newTilesCenter = {
x:
tilesCenter.x -
2022-10-31 20:01:56 +00:00
actualDeltaShiftTiles.x -
(window.innerWidth / 2 / visibleTileSize - actualZoomCenterTiles.x) *
(deltaZoom - 1),
2022-10-31 17:05:58 +00:00
y:
tilesCenter.y -
2022-10-31 20:01:56 +00:00
actualDeltaShiftTiles.y -
(window.innerHeight / 2 / visibleTileSize - actualZoomCenterTiles.y) *
(deltaZoom - 1),
2022-10-31 17:05:58 +00:00
};
const newScope: MapScope = {
center: {
lat: tile2lat(newTilesCenter.y, tilesZoom),
lon: tile2long(newTilesCenter.x, tilesZoom),
},
2022-10-31 20:01:56 +00:00
zoom: deltaZoomLevel + scope.zoom,
2022-10-31 17:05:58 +00:00
tileProvider: scope.tileProvider,
};
2022-10-31 20:40:10 +00:00
// console.log(
// `LiveMap transform: ${JSON.stringify(t)}, ${JSON.stringify(
// scope
// )} -> ${JSON.stringify(newScope)}, delta lat: ${
// newScope.center.lat - scope.center.lat
// }, delta lon: ${newScope.center.lon - scope.center.lon}`
// );
2022-10-31 17:05:58 +00:00
setScope(newScope);
};
return (
<>
<Handlers transformMap={transform} />
<Map scope={scope} numberOfTiledLayers={props.numberOfTiledLayers} />
</>
);
};
export default LiveMap;