From db0761efd1d9692e40936079885bf872a4a2a2a5 Mon Sep 17 00:00:00 2001 From: evlist Date: Tue, 13 Sep 2022 18:17:08 +0200 Subject: [PATCH] Refactoring (in progress) to put most of the logic in Redux reducers. --- src/App.tsx | 7 +- src/components/map/map.tsx | 87 ++++--------- src/components/map/tile.tsx | 1 - src/components/map/tiled-map.tsx | 37 ++++++ .../slippy/double-touch-handler.tsx | 10 +- src/components/slippy/layer.tsx | 5 +- src/components/slippy/mouse-handler.tsx | 13 +- .../slippy/single-touch-handler.tsx | 20 ++- src/components/slippy/slippy.tsx | 14 ++- src/components/slippy/wheel-handler.tsx | 4 +- src/lib/geo.ts | 12 +- src/store/index.ts | 3 +- src/store/map.ts | 118 ++++++++++++++---- src/store/slippy.ts | 45 ------- 14 files changed, 193 insertions(+), 183 deletions(-) create mode 100644 src/components/map/tiled-map.tsx delete mode 100644 src/store/slippy.ts diff --git a/src/App.tsx b/src/App.tsx index 9167ad8..40a7310 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,10 +21,9 @@ import '@ionic/react/css/display.css'; /* Theme variables */ import './theme/variables.css'; -import Map from './components/map/map'; +import TiledMap from './components/map/tiled-map'; import Slippy from './components/slippy/slippy'; import Layer from './components/slippy/layer'; -import { Fragment } from 'react'; setupIonicReact(); @@ -32,8 +31,8 @@ const App: React.FC = () => ( - - + + diff --git a/src/components/map/map.tsx b/src/components/map/map.tsx index 5391b9e..91df6c2 100644 --- a/src/components/map/map.tsx +++ b/src/components/map/map.tsx @@ -1,72 +1,35 @@ -import react, { useMemo, useEffect } from 'react'; +import react, { useMemo, useEffect, Fragment } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { mapActions } from '../../store/map'; import _ from 'lodash'; -import { MapState, mapActions } from '../../store/map'; -import { slippyActions } from '../../store/slippy'; -import { lat2tile, lon2tile } from '../../lib/geo'; -import Tile from './tile'; -import '../../theme/map.css'; - -export const tileSize = 256; +import Layer from '../slippy/layer'; +import Slippy from '../slippy/slippy'; +import TiledMap from './tiled-map'; const Map: react.FC<{}> = (props: {}) => { - const dispatch = useDispatch(); - - const mapState = useSelector((state: { map: MapState }) => state.map); - - console.log(`mapState: ${JSON.stringify(mapState)}`); - - const resizeHandler = () => { - dispatch(mapActions.resize()); - }; - const debouncedResizeHandler = useMemo( - () => _.debounce(resizeHandler, 500), - [] - ); - - useEffect(() => { - window.addEventListener('resize', debouncedResizeHandler); - }, []); - - const nbTilesY = _.ceil(mapState.viewport.height / tileSize) + 3; - const nbTilesX = _.ceil(mapState.viewport.width / tileSize) + 3; - 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; - - dispatch(slippyActions.set({ scale: 1, translation: { x: deltaX, y: deltaY } })); + const dispatch = useDispatch(); + + const resizeHandler = () => { + dispatch(mapActions.resize()); + }; + const debouncedResizeHandler = useMemo( + () => _.debounce(resizeHandler, 500), + [] + ); + + useEffect(() => { + window.addEventListener('resize', debouncedResizeHandler); + }, []); + return ( -
- {_.range(nbTilesY).map((iy) => ( -
- {_.range(nbTilesX).map((ix) => ( - - ))} -
- ))} -
+ + + + + + ); }; diff --git a/src/components/map/tile.tsx b/src/components/map/tile.tsx index bf7124d..75e0c57 100644 --- a/src/components/map/tile.tsx +++ b/src/components/map/tile.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { tileSize } from './map'; const tileProvider = (zoom: number, x: number, y: number) => 'https://tile.openstreetmap.org/' + zoom + '/' + x + '/' + y + '.png'; diff --git a/src/components/map/tiled-map.tsx b/src/components/map/tiled-map.tsx new file mode 100644 index 0000000..c0f1ad4 --- /dev/null +++ b/src/components/map/tiled-map.tsx @@ -0,0 +1,37 @@ +import react from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { MapState } from '../../store/map'; +import Tile from './tile'; + +import '../../theme/map.css'; +import _ from 'lodash'; + +export const tileSize = 256; + +const TiledMap: react.FC<{}> = (props: {}) => { + const dispatch = useDispatch(); + + const tilesState = useSelector((state: { map: MapState }) => state.map.tiles); + console.log(`tilesState: ${JSON.stringify(tilesState)}`); + + return ( +
+ {_.range(tilesState.nb.y).map((iy) => ( +
+ {_.range(tilesState.nb.y).map((ix) => ( + + ))} +
+ ))} +
+ ); +}; + +export default TiledMap; diff --git a/src/components/slippy/double-touch-handler.tsx b/src/components/slippy/double-touch-handler.tsx index 14c8766..4b8a6ac 100644 --- a/src/components/slippy/double-touch-handler.tsx +++ b/src/components/slippy/double-touch-handler.tsx @@ -1,9 +1,7 @@ -import react, { useCallback, useState } from 'react'; +import react, { useState } from 'react'; import { useDispatch } from 'react-redux'; -import _ from 'lodash'; - -import { slippyActions } from '../../store/slippy'; +import { mapActions } from '../../store/map'; interface DoubleTouchHandlerProps { children: any; @@ -100,13 +98,13 @@ const DoubleTouchHandler: react.FC = ( y: (event.touches[0].pageY + event.touches[1].pageY) / 2, }; dispatch( - slippyActions.scale({ + mapActions.scale({ factor: factor, center: currentCenter, }) ); dispatch( - slippyActions.translate({ + mapActions.shift({ x: currentCenter.x - previousCenter.x, y: currentCenter.y - previousCenter.y, }) diff --git a/src/components/slippy/layer.tsx b/src/components/slippy/layer.tsx index 5890f46..fee6385 100644 --- a/src/components/slippy/layer.tsx +++ b/src/components/slippy/layer.tsx @@ -1,7 +1,6 @@ import react from 'react'; import { useSelector } from 'react-redux'; - -import { SlippyState } from '../../store/slippy'; +import { MapState } from '../../store/map'; import '../../theme/layer.css'; @@ -9,7 +8,7 @@ const Layer: react.FC<{ children?: JSX.Element; }> = (props: { children?: JSX.Element }) => { const slippyState = useSelector( - (state: { slippy: SlippyState }) => state.slippy + (state: { map: MapState }) => state.map.slippy ); console.log( `--- Rendering layer, slippyState: ${JSON.stringify(slippyState)} ---` diff --git a/src/components/slippy/mouse-handler.tsx b/src/components/slippy/mouse-handler.tsx index 1a10f29..662a9b8 100644 --- a/src/components/slippy/mouse-handler.tsx +++ b/src/components/slippy/mouse-handler.tsx @@ -1,9 +1,8 @@ import react, { useState } from 'react'; import { useDispatch } from 'react-redux'; -import _ from 'lodash'; +import { mapActions } from '../../store/map'; -import { slippyActions } from '../../store/slippy'; interface MouseHandlerProps { children: any; @@ -14,12 +13,6 @@ const MouseHandler: react.FC = ( ) => { const dispatch = useDispatch(); - interface MouseState { - down: boolean; - starting: { x: number; y: number }; - timestamp: number; - } - const initialMouseState = { down: false, starting: { x: -1, y: -1 }, @@ -76,7 +69,7 @@ const MouseHandler: react.FC = ( })}` ); dispatch( - slippyActions.translate({ + mapActions.shift({ x: event.pageX - mouseState.starting.x, y: event.pageY - mouseState.starting.y, }) @@ -101,7 +94,7 @@ const MouseHandler: react.FC = ( const doubleClickHandler = (event: any) => { genericHandler(event); dispatch( - slippyActions.scale({ + mapActions.scale({ factor: 2, center: { x: event.pageX, y: event.pageY }, }) diff --git a/src/components/slippy/single-touch-handler.tsx b/src/components/slippy/single-touch-handler.tsx index 3665f04..585c71c 100644 --- a/src/components/slippy/single-touch-handler.tsx +++ b/src/components/slippy/single-touch-handler.tsx @@ -1,9 +1,7 @@ -import react, { useCallback, useState } from 'react'; +import react, { useState } from 'react'; import { useDispatch } from 'react-redux'; -import _ from 'lodash'; - -import { slippyActions } from '../../store/slippy'; +import { mapActions } from '../../store/map'; interface SingleTouchHandlerProps { children: any; @@ -39,7 +37,6 @@ const SingleTouchHandler: react.FC = ( const touchCancelHandler = (event: any) => { genericHandler(event); - throtteledTouchMoveHandler.cancel(); setTouchState(initialTouchState); }; @@ -59,16 +56,18 @@ const SingleTouchHandler: react.FC = ( genericHandler(event); // event.preventDefault(); setTouchState(initialTouchState); - throtteledTouchMoveHandler.cancel(); }; const touchMoveHandler = (event: any) => { // event.preventDefault(); - if (touchState.state === 'pointer' && (Date.now() - touchState.timestamp) > 50) { + if ( + touchState.state === 'pointer' && + Date.now() - touchState.timestamp > 50 + ) { if (event.touches.length === 1) { genericHandler(event); dispatch( - slippyActions.translate({ + mapActions.shift({ x: event.touches[0].pageX - touchState.touch.x, y: event.touches[0].pageY - touchState.touch.y, }) @@ -85,11 +84,6 @@ const SingleTouchHandler: react.FC = ( } }; - const throtteledTouchMoveHandler = useCallback( - _.throttle(touchMoveHandler, 100), - [touchState.state] - ); - return (
= () => { //console.log(`--- Rendering viewport, props: ${JSON.stringify(props)} ---`); - const viewport = useSelector( - (state: { map: MapState }) => state.map.viewport + const slippyState = useSelector( + (state: { map: MapState }) => state.map.slippy ); + console.log(`slippyState: ${JSON.stringify(slippyState)}`); + return (
@@ -26,8 +28,8 @@ const Slippy: react.FC<{}> = () => {
diff --git a/src/components/slippy/wheel-handler.tsx b/src/components/slippy/wheel-handler.tsx index e9ef1a7..f99467a 100644 --- a/src/components/slippy/wheel-handler.tsx +++ b/src/components/slippy/wheel-handler.tsx @@ -2,8 +2,8 @@ import react, { useState } from 'react'; import { useDispatch } from 'react-redux'; import _ from 'lodash'; +import { mapActions } from '../../store/map'; -import { slippyActions } from '../../store/slippy'; interface WheelHandlerProps { children: any; @@ -46,7 +46,7 @@ const WheelHandler: react.FC = ( Date.now() - wheelState.timestamp > 100 ) { dispatch( - slippyActions.scale({ + mapActions.scale({ factor: event.deltaY > 0 ? 2 : 0.5, center: { x: event.pageX, y: event.pageY }, }) diff --git a/src/lib/geo.ts b/src/lib/geo.ts index 12fad71..2c24d1d 100644 --- a/src/lib/geo.ts +++ b/src/lib/geo.ts @@ -1,4 +1,3 @@ - export interface Point { x: number; y: number; @@ -11,21 +10,18 @@ export interface geoPoint { // cf https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#ECMAScript_(JavaScript/ActionScript,_etc.) export const lon2tile = (lon: number, zoom: number) => { - const real = ((lon + 180) / 360) * Math.pow(2, zoom); - const floor = Math.floor(real); - return [floor, real - floor]; + return ((lon + 180) / 360) * Math.pow(2, zoom); }; export const lat2tile = (lat: number, zoom: number) => { - const real = + return ( ((1 - Math.log( Math.tan((lat * Math.PI) / 180) + 1 / Math.cos((lat * Math.PI) / 180) ) / Math.PI) / 2) * - Math.pow(2, zoom); - const floor = Math.floor(real); - return [floor, real - floor]; + Math.pow(2, zoom) + ); }; export function tile2long(x: number, z: number) { diff --git a/src/store/index.ts b/src/store/index.ts index d13a2b7..5e67564 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,10 +1,9 @@ import { configureStore } from '@reduxjs/toolkit'; -import slippyReducer from './slippy'; import mapReducer from './map'; const store = configureStore({ - reducer: { slippy: slippyReducer, map: mapReducer }, + reducer: { map: mapReducer }, }); export default store; diff --git a/src/store/map.ts b/src/store/map.ts index c67c0df..b787f59 100644 --- a/src/store/map.ts +++ b/src/store/map.ts @@ -1,36 +1,112 @@ import { createSlice } from '@reduxjs/toolkit'; -import { geoPoint } from '../lib/geo'; +import _ from 'lodash'; +import { tileSize } from '../components/map/tiled-map'; +import { geoPoint, Point, lon2tile, lat2tile } from '../lib/geo'; -export interface MapState { - viewport: { - width: number; - height: number; - }; - scope: { - center: geoPoint; - zoom: number; - }; +// Top level properties (the other properties can be derived from them) + +// The map itself +export interface MapScope { + center: geoPoint; + zoom: number; +} +const initialMapScope: MapScope = { + center: { lat: -37.8372, lon: 77.5513 }, + zoom: 13, +}; + +// Derived properties + +// Properties needed to render the tiled map +export interface TilesDescription { + nb: Point; + first: Point; + zoom: number; } -const initialMapState: MapState = { - viewport: { - width: window.innerWidth, - height: window.innerHeight, - }, - scope: { - center: { lat: -37.8372, lon: 77.5513 }, - zoom: 13, - }, +// Properties needed to render the slippy viewport +export interface SlippyState { + scale: number; + translation: Point; +} + +// Global state +export interface MapState { + scope: MapScope; + tiles: TilesDescription; + slippy: SlippyState; +} +/* + +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; + + +*/ + +const computeStateFromScope = (scope: MapScope) => { + const newScope = _.cloneDeep(scope); + let state: MapState = {} as MapState; + state.scope = newScope; + state.tiles = {} as TilesDescription; + state.slippy = {} as SlippyState; + state.tiles.nb = { + x: _.ceil(window.innerWidth / tileSize + 4), + y: _.ceil(window.innerHeight / tileSize + 4), + }; + state.tiles.zoom = _.round(state.scope.zoom); + 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), + 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) * tileSize, + y: (tilesCenter.y - state.tiles.first.y) * tileSize, + }; + state.slippy.translation = { + x: tilesCenterTargetLocation.x - tilesCenterActualLocation.x, + y: tilesCenterTargetLocation.y - tilesCenterActualLocation.y, + }; + state.slippy.scale = 1; + + return state; }; +const initialMapState: MapState = computeStateFromScope(initialMapScope); + const mapSlice = createSlice({ name: 'map', initialState: initialMapState, reducers: { resize: (state) => { - state.viewport.height = window.innerHeight; - state.viewport.width = window.innerWidth; + return computeStateFromScope(state.scope); }, + shift: (state, action) => {}, + scale: (state, action) => {}, }, }); diff --git a/src/store/slippy.ts b/src/store/slippy.ts deleted file mode 100644 index e4c6621..0000000 --- a/src/store/slippy.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { actionSheetController } from '@ionic/core'; -import { createSlice } from '@reduxjs/toolkit'; - -import { Point } from '../lib/geo'; - -export interface SlippyState { - scale: number; - translation: Point; -} - -const initialSlippyState: SlippyState = { - scale: 1, - translation: { x: 0, y: 0 }, -}; - -const slippySlice = createSlice({ - name: 'slippy', - initialState: initialSlippyState, - reducers: { - scale: (state, action) => { - console.log(`redux scale: ${JSON.stringify(action.payload)}`); - state.scale = state.scale * action.payload.factor; - state.translation.x = - state.translation.x + - (state.translation.x - action.payload.center.x) * - (action.payload.factor - 1); - state.translation.y = - state.translation.y + - (state.translation.y - action.payload.center.y) * - (action.payload.factor - 1); - }, - translate: (state, action) => { - console.log(`redux translate: action=${JSON.stringify(action)}`); - state.translation.x = state.translation.x + action.payload.x; - state.translation.y = state.translation.y + action.payload.y; - }, - set: (state, action) => { - return action.payload; - }, - }, -}); - -export const slippyActions = slippySlice.actions; - -export default slippySlice.reducer;