238 lines
6.3 KiB
TypeScript
238 lines
6.3 KiB
TypeScript
import { createSlice } from '@reduxjs/toolkit';
|
|
import _ from 'lodash';
|
|
import { tileSize } from '../components/map/tiled-map';
|
|
import {
|
|
geoPoint,
|
|
Point,
|
|
lon2tile,
|
|
lat2tile,
|
|
tile2lat,
|
|
tile2long,
|
|
} from '../lib/geo';
|
|
|
|
// Top level properties (the other properties can be derived from them)
|
|
|
|
// The map itself
|
|
export interface MapScope {
|
|
center: geoPoint;
|
|
zoom: number;
|
|
}
|
|
var initialMapScope: MapScope = {
|
|
center: { lat: -37.8403508, lon: 77.5539501 },
|
|
zoom: 4,
|
|
};
|
|
|
|
// Derived properties
|
|
|
|
// Properties needed to render the tiled map
|
|
export interface TilesDescription {
|
|
nb: Point;
|
|
first: Point;
|
|
zoom: number;
|
|
}
|
|
|
|
// Properties needed to render the slippy viewport
|
|
export interface SlippyState {
|
|
scale: number;
|
|
translation: Point;
|
|
}
|
|
|
|
// Properties needed to render the SVG whiteboard
|
|
export interface WhiteboardState {
|
|
scale: number;
|
|
translation: Point;
|
|
}
|
|
|
|
// Global state
|
|
export interface MapState {
|
|
scope: MapScope;
|
|
tiles: TilesDescription;
|
|
slippy: SlippyState;
|
|
whiteboard: WhiteboardState;
|
|
}
|
|
/*
|
|
|
|
const nbTilesY = _.ceil(mapState.viewport.height / tileSize) + 4;
|
|
const nbTilesX = _.ceil(mapState.viewport.width / tileSize) + 4;
|
|
const [tileCenterY, reminderY] = lat2tile(
|
|
mapState.scope.center.lat,
|
|
mapState.scope.zoom
|
|
);
|
|
const [tileCenterX, reminderX] = lon2tile(
|
|
mapState.scope.center.lon,
|
|
mapState.scope.zoom
|
|
);
|
|
const firstTileY = tileCenterY - _.round(nbTilesY / 2);
|
|
const firstTileX = tileCenterX - _.round(nbTilesX / 2);
|
|
const locationY = (tileCenterY + reminderY - firstTileY) * tileSize;
|
|
const locationX = (tileCenterX + reminderX - firstTileX) * tileSize;
|
|
const targetLocationY = mapState.viewport.height / 2;
|
|
const targetLocationX = mapState.viewport.width / 2;
|
|
const deltaY = targetLocationY - locationY;
|
|
const deltaX = targetLocationX - locationX;
|
|
|
|
|
|
*/
|
|
|
|
export var initialMapState: MapState = {
|
|
scope: initialMapScope,
|
|
slippy: {
|
|
scale: 1,
|
|
translation: {
|
|
x: 0,
|
|
y: 0,
|
|
},
|
|
},
|
|
tiles: {
|
|
first: {
|
|
x: 0,
|
|
y: 0,
|
|
},
|
|
nb: {
|
|
x: 0,
|
|
y: 0,
|
|
},
|
|
zoom: 0,
|
|
},
|
|
whiteboard: {
|
|
scale: 0,
|
|
translation: {
|
|
x: 0,
|
|
y: 0,
|
|
},
|
|
},
|
|
};
|
|
|
|
const evaluateWhiteboardViewBox = (
|
|
state: MapState,
|
|
visibleTileSize: number
|
|
) => {
|
|
// Update the whiteboard SVG viewBox
|
|
|
|
const scaleFactor = 2 ** state.scope.zoom;
|
|
|
|
state.whiteboard.scale = scaleFactor * tileSize;
|
|
state.whiteboard.translation.x =
|
|
(-state.tiles.first.x * visibleTileSize + state.slippy.translation.x) /
|
|
state.whiteboard.scale;
|
|
state.whiteboard.translation.y =
|
|
(-state.tiles.first.y * visibleTileSize + state.slippy.translation.y) /
|
|
state.whiteboard.scale;
|
|
};
|
|
|
|
export const evaluateStateFromScope = (state: MapState) => {
|
|
console.log('<<<<<<<<<<<< evaluateStateFromScope');
|
|
state.tiles.zoom = _.round(state.scope.zoom);
|
|
const softZoom = state.scope.zoom - state.tiles.zoom;
|
|
|
|
state.slippy.scale = 2 ** softZoom;
|
|
|
|
const visibleTileSize = tileSize * state.slippy.scale;
|
|
|
|
state.tiles.nb.x = _.ceil(window.innerWidth / visibleTileSize + 4);
|
|
state.tiles.nb.y = _.ceil(window.innerHeight / visibleTileSize + 4);
|
|
|
|
const tilesCenter: Point = {
|
|
x: lon2tile(state.scope.center.lon, state.tiles.zoom),
|
|
y: lat2tile(state.scope.center.lat, state.tiles.zoom),
|
|
};
|
|
|
|
state.tiles.first.x = _.floor(tilesCenter.x - state.tiles.nb.x / 2);
|
|
state.tiles.first.y = _.floor(tilesCenter.y - state.tiles.nb.y / 2);
|
|
|
|
const tilesCenterTargetLocation: Point = {
|
|
x: window.innerWidth / 2,
|
|
y: window.innerHeight / 2,
|
|
};
|
|
const tilesCenterActualLocation: Point = {
|
|
x: (tilesCenter.x - state.tiles.first.x) * visibleTileSize,
|
|
y: (tilesCenter.y - state.tiles.first.y) * visibleTileSize,
|
|
};
|
|
|
|
state.slippy.translation.x =
|
|
tilesCenterTargetLocation.x - tilesCenterActualLocation.x;
|
|
state.slippy.translation.y =
|
|
tilesCenterTargetLocation.y - tilesCenterActualLocation.y;
|
|
|
|
evaluateWhiteboardViewBox(state, visibleTileSize);
|
|
};
|
|
|
|
evaluateStateFromScope(initialMapState);
|
|
|
|
export const reevaluateState = (state: MapState) => {
|
|
// Update the scope (center and zoom level)
|
|
const centerPX = {
|
|
x: window.innerWidth / 2,
|
|
y: window.innerHeight / 2,
|
|
};
|
|
const visibleTileSize = tileSize * state.slippy.scale;
|
|
const centerTiles = {
|
|
x:
|
|
state.tiles.first.x +
|
|
(centerPX.x - state.slippy.translation.x) / visibleTileSize,
|
|
y:
|
|
state.tiles.first.y +
|
|
(centerPX.y - state.slippy.translation.y) / visibleTileSize,
|
|
};
|
|
state.scope.center.lat = tile2lat(centerTiles.y, state.tiles.zoom);
|
|
state.scope.center.lon = tile2long(centerTiles.x, state.tiles.zoom);
|
|
|
|
state.scope.zoom = state.tiles.zoom + Math.log2(state.slippy.scale);
|
|
|
|
// Check if the state must be reevaluated
|
|
|
|
if (
|
|
-state.slippy.translation.x < visibleTileSize ||
|
|
-state.slippy.translation.y < visibleTileSize ||
|
|
-state.slippy.translation.x >
|
|
(state.tiles.nb.x - 1) * visibleTileSize - window.innerWidth ||
|
|
-state.slippy.translation.y >
|
|
(state.tiles.nb.y - 1) * visibleTileSize - window.innerHeight ||
|
|
state.slippy.scale > Math.SQRT2 ||
|
|
state.slippy.scale < Math.SQRT1_2
|
|
) {
|
|
evaluateStateFromScope(state);
|
|
} else {
|
|
evaluateWhiteboardViewBox(state, visibleTileSize);
|
|
}
|
|
};
|
|
|
|
const mapSlice = createSlice({
|
|
name: 'map',
|
|
initialState: initialMapState,
|
|
reducers: {
|
|
resize: (state) => {
|
|
evaluateStateFromScope(state);
|
|
},
|
|
setCenter: (state, action) => {
|
|
state.scope.center.lat = action.payload.lat;
|
|
state.scope.center.lon = action.payload.lon;
|
|
evaluateStateFromScope(state);
|
|
},
|
|
shift: (state, action) => {
|
|
state.slippy.translation.x =
|
|
state.slippy.translation.x + action.payload.x;
|
|
state.slippy.translation.y =
|
|
state.slippy.translation.y + action.payload.y;
|
|
reevaluateState(state);
|
|
},
|
|
scale: (state, action) => {
|
|
state.slippy.scale = state.slippy.scale * action.payload.factor;
|
|
state.slippy.translation.x =
|
|
state.slippy.translation.x +
|
|
(state.slippy.translation.x - action.payload.center.x) *
|
|
(action.payload.factor - 1);
|
|
state.slippy.translation.y =
|
|
state.slippy.translation.y +
|
|
(state.slippy.translation.y - action.payload.center.y) *
|
|
(action.payload.factor - 1);
|
|
|
|
reevaluateState(state);
|
|
},
|
|
},
|
|
});
|
|
|
|
export const mapActions = mapSlice.actions;
|
|
|
|
export default mapSlice.reducer;
|