2022-11-01 16:58:57 +00:00
|
|
|
import react, { useEffect, useState } from 'react';
|
2022-11-02 14:14:34 +00:00
|
|
|
import useDimensions from 'react-cool-dimensions';
|
|
|
|
|
2022-10-31 17:05:58 +00:00
|
|
|
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 14:02:46 +00:00
|
|
|
|
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 14:02:46 +00:00
|
|
|
|
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 {
|
2022-11-01 16:58:57 +00:00
|
|
|
/** The initial map's scope */
|
2022-10-31 17:05:58 +00:00
|
|
|
scope: MapScope;
|
2022-11-01 16:58:57 +00:00
|
|
|
/** The number of tiled layers (default to 1) */
|
2022-10-31 17:05:58 +00:00
|
|
|
numberOfTiledLayers?: number;
|
2022-11-01 16:58:57 +00:00
|
|
|
/** If provided, a function to call when the scope is updated. */
|
|
|
|
setScope?: (scope: MapScope) => void;
|
2022-10-31 17:05:58 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 13:37:08 +00:00
|
|
|
/**
|
2022-11-01 16:58:57 +00:00
|
|
|
*
|
|
|
|
* @param props
|
2022-11-01 13:37:08 +00:00
|
|
|
* @returns A `<LiveMap>` component.
|
2022-11-01 16:58:57 +00:00
|
|
|
*
|
2022-11-01 13:37:08 +00:00
|
|
|
* 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.
|
2022-11-01 16:58:57 +00:00
|
|
|
*
|
2022-11-01 13:37:08 +00:00
|
|
|
* To do so, `<LiveMap>` embeds a `<Map>` component together with a {@link components/map/Handlers!Handlers} component which listens to user's event.
|
2022-11-01 16:58:57 +00:00
|
|
|
*
|
2022-11-01 13:37:08 +00:00
|
|
|
* 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 16:58:57 +00:00
|
|
|
useEffect(() => {
|
|
|
|
setScope(props.scope);
|
|
|
|
}, [props.scope]);
|
|
|
|
console.log(`LiveMap rendering: ${JSON.stringify(scope)}`);
|
2022-11-02 14:14:34 +00:00
|
|
|
const { observe, width, height } = useDimensions<HTMLDivElement>();
|
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 -
|
2022-11-02 14:14:34 +00:00
|
|
|
(width / 2 / visibleTileSize - actualZoomCenterTiles.x) *
|
2022-10-31 20:01:56 +00:00
|
|
|
(deltaZoom - 1),
|
2022-10-31 17:05:58 +00:00
|
|
|
y:
|
|
|
|
tilesCenter.y -
|
2022-10-31 20:01:56 +00:00
|
|
|
actualDeltaShiftTiles.y -
|
2022-11-02 14:14:34 +00:00
|
|
|
(height / 2 / visibleTileSize - actualZoomCenterTiles.y) *
|
2022-10-31 20:01:56 +00:00
|
|
|
(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);
|
2022-11-01 16:58:57 +00:00
|
|
|
if (props.setScope !== undefined) {
|
|
|
|
props.setScope(newScope);
|
|
|
|
}
|
2022-10-31 17:05:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
2022-11-02 14:14:34 +00:00
|
|
|
<div style={{ width: '100%', height: '100%' }} ref={observe}>
|
2022-10-31 17:05:58 +00:00
|
|
|
<Handlers transformMap={transform} />
|
|
|
|
<Map scope={scope} numberOfTiledLayers={props.numberOfTiledLayers} />
|
2022-11-02 14:14:34 +00:00
|
|
|
</div>
|
2022-10-31 17:05:58 +00:00
|
|
|
);
|
2022-10-31 14:02:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export default LiveMap;
|