fixed router

This commit is contained in:
Fedor Katurov 2020-01-09 16:55:41 +07:00
parent 42dbfb0681
commit 2be073078f
11 changed files with 241 additions and 62 deletions

View file

@ -4,6 +4,7 @@ import * as EDITOR_ACTIONS from '~/redux/editor/actions';
import classnames from 'classnames'; import classnames from 'classnames';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectEditor } from '~/redux/editor/selectors'; import { selectEditor } from '~/redux/editor/selectors';
import pick from 'ramda/es/pick';
const noPoints = ({ const noPoints = ({
editorRouterCancel, editorRouterCancel,
@ -80,7 +81,7 @@ const draggablePoints = ({
); );
const mapStateToProps = state => ({ const mapStateToProps = state => ({
editor: selectEditor(state), editor: pick(['router'], selectEditor(state)),
}); });
const mapDispatchToProps = { const mapDispatchToProps = {
@ -91,7 +92,10 @@ const mapDispatchToProps = {
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & { width?: number }; type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & { width?: number };
const RouterDialogUnconnected: FC<Props> = ({ const RouterDialogUnconnected: FC<Props> = ({
editor: { routerPoints, is_routing }, editor: {
router: { waypoints },
is_routing,
},
editorRouterCancel, editorRouterCancel,
editorRouterSubmit, editorRouterSubmit,
width, width,
@ -99,9 +103,9 @@ const RouterDialogUnconnected: FC<Props> = ({
<div className="control-dialog" style={{ width }}> <div className="control-dialog" style={{ width }}>
<div className={classnames('save-loader', { active: is_routing })} /> <div className={classnames('save-loader', { active: is_routing })} />
{!routerPoints && noPoints({ editorRouterCancel })} {!waypoints.length && noPoints({ editorRouterCancel })}
{routerPoints === 1 && firstPoint({ editorRouterCancel })} {waypoints.length === 1 && firstPoint({ editorRouterCancel })}
{routerPoints >= 2 && draggablePoints({ editorRouterCancel, editorRouterSubmit })} {waypoints.length >= 2 && draggablePoints({ editorRouterCancel, editorRouterSubmit })}
</div> </div>
); );

View file

@ -1,13 +1,13 @@
import React from 'react'; import React from 'react';
import { MainMap } from '~/constants/map'; import { MainMap } from '~/constants/map';
import { Map as MapInterface } from 'leaflet';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { selectMapProvider, selectMapRoute, selectMapStickers } from '~/redux/map/selectors'; import { selectMapProvider, selectMapRoute, selectMapStickers } from '~/redux/map/selectors';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import * as MAP_ACTIONS from '~/redux/map/actions'; import * as MAP_ACTIONS from '~/redux/map/actions';
import { Route } from '~/containers/map/Route'; import { Route } from '~/containers/map/Route';
import { Router } from '~/containers/map/Router';
import { TileLayer } from '~/containers/map/TileLayer'; import { TileLayer } from '~/containers/map/TileLayer';
import { Stickers } from '~/containers/map/Stickers'; import { Stickers } from '~/containers/map/Stickers';
@ -71,6 +71,7 @@ const MapUnconnected: React.FC<IProps> = ({
mapDropSticker={mapDropSticker} mapDropSticker={mapDropSticker}
is_editing={editing} is_editing={editing}
/> />
<Router />
</div>, </div>,
document.getElementById('canvas') document.getElementById('canvas')
); );

View file

@ -0,0 +1,61 @@
import React, { FC, useEffect, useMemo, useCallback, memo } from 'react';
import pick from 'ramda/es/pick';
import { OsrmRouter } from '~/utils/osrm';
import { connect } from 'react-redux';
import { selectMap } from '~/redux/map/selectors';
import { selectEditorRouter, selectEditorMode } from '~/redux/editor/selectors';
import { MainMap } from '~/constants/map';
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
import { MODES } from '~/constants/modes';
const mapStateToProps = state => ({
map: pick(['route'], selectMap(state)),
router: pick(['waypoints', 'points'], selectEditorRouter(state)),
mode: selectEditorMode(state),
});
const mapDispatchToProps = {
editorSetRouter: EDITOR_ACTIONS.editorSetRouter,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
const RouterUnconnected: FC<Props> = memo(
({ map: { route }, mode, router: { waypoints }, editorSetRouter }) => {
const updateWaypoints = useCallback(
({ waypoints }) => editorSetRouter({ waypoints: waypoints.filter(wp => !!wp.latLng) }),
[editorSetRouter]
);
useEffect(() => {
OsrmRouter.on('waypointschanged', updateWaypoints).addTo(MainMap);
return () => {
OsrmRouter.off('waypointschanged', updateWaypoints).setWaypoints([]);
};
}, [MainMap, updateWaypoints, mode]);
useEffect(() => {
if (mode !== MODES.ROUTER) return;
const wp = OsrmRouter.getWaypoints()
.filter(point => point.latLng)
.map(point => point.latLng);
if (
!route.length ||
!wp.length ||
(route[route.length - 1].lat === wp[0].lat && route[route.length - 1].lng === wp[0].lng)
)
return;
OsrmRouter.setWaypoints([route[route.length - 1], ...wp]);
}, [route, mode, waypoints]);
return null;
}
);
const Router = connect(mapStateToProps, mapDispatchToProps)(RouterUnconnected);
export { Router };

View file

@ -118,3 +118,8 @@ export const editorKeyPressed = ({
key, key,
target: tagName, target: tagName,
}); });
export const editorSetRouter = (router: Partial<IEditorState['router']>) => ({
type: EDITOR_ACTIONS.SET_ROUTER,
router,
});

View file

@ -44,4 +44,6 @@ export const EDITOR_ACTIONS = {
SET_FEATURE: `${P}-SET_FEATURE`, SET_FEATURE: `${P}-SET_FEATURE`,
SET_IS_ROUTING: `${P}-SET_IS_ROUTING`, SET_IS_ROUTING: `${P}-SET_IS_ROUTING`,
KEY_PRESSED: `${P}-KEY_PRESSED`, KEY_PRESSED: `${P}-KEY_PRESSED`,
SET_ROUTER: `${P}-SET_ROUTER`,
}; };

View file

@ -113,7 +113,10 @@ const resetSaveDialog = (state): IEditorState => ({
save_error: '', save_error: '',
}); });
const setDialog = (state, { dialog }: ReturnType<typeof ACTIONS.editorSetDialog>): IEditorState => ({ const setDialog = (
state,
{ dialog }: ReturnType<typeof ACTIONS.editorSetDialog>
): IEditorState => ({
...state, ...state,
dialog, dialog,
}); });
@ -126,7 +129,10 @@ const setDialogActive = (
dialog_active: dialog_active || !state.dialog_active, dialog_active: dialog_active || !state.dialog_active,
}); });
const setReady = (state, { ready = true }: ReturnType<typeof ACTIONS.editorSetReady>): IEditorState => ({ const setReady = (
state,
{ ready = true }: ReturnType<typeof ACTIONS.editorSetReady>
): IEditorState => ({
...state, ...state,
ready, ready,
}); });
@ -169,6 +175,17 @@ const setIsRouting = (
is_routing, is_routing,
}); });
const setRouter = (
state,
{ router }: ReturnType<typeof ACTIONS.editorSetRouter>
): IEditorState => ({
...state,
router: {
...state.router,
...router,
},
});
export const EDITOR_HANDLERS = { export const EDITOR_HANDLERS = {
[EDITOR_ACTIONS.SET_EDITING]: setEditing, [EDITOR_ACTIONS.SET_EDITING]: setEditing,
[EDITOR_ACTIONS.SET_CHANGED]: setChanged, [EDITOR_ACTIONS.SET_CHANGED]: setChanged,
@ -197,4 +214,5 @@ export const EDITOR_HANDLERS = {
[EDITOR_ACTIONS.SET_FEATURE]: setFeature, [EDITOR_ACTIONS.SET_FEATURE]: setFeature,
[EDITOR_ACTIONS.SET_IS_ROUTING]: setIsRouting, [EDITOR_ACTIONS.SET_IS_ROUTING]: setIsRouting,
[EDITOR_ACTIONS.SET_ROUTER]: setRouter,
}; };

View file

@ -2,44 +2,51 @@ import { IDialogs } from '~/constants/dialogs';
import { MODES } from '~/constants/modes'; import { MODES } from '~/constants/modes';
import { createReducer } from 'reduxsauce'; import { createReducer } from 'reduxsauce';
import { EDITOR_HANDLERS } from './handlers'; import { EDITOR_HANDLERS } from './handlers';
import { ILatLng } from '../map/types';
export interface IEditorState { export interface IEditorState {
changed: boolean, changed: boolean;
editing: boolean, editing: boolean;
ready: boolean, ready: boolean;
markers_shown: boolean; markers_shown: boolean;
mode: typeof MODES[keyof typeof MODES],
dialog: IDialogs[keyof IDialogs], router: {
dialog_active: boolean, points: ILatLng[];
waypoints: ILatLng[];
};
routerPoints: number, mode: typeof MODES[keyof typeof MODES];
distance: number,
estimated: number, dialog: IDialogs[keyof IDialogs];
speed: number, dialog_active: boolean;
activeSticker: { set?: string, sticker?: string },
is_empty: boolean, routerPoints: number;
is_published: boolean, distance: number;
is_routing: boolean, estimated: number;
save_error: string, speed: number;
save_finished: boolean, activeSticker: { set?: string; sticker?: string };
save_overwriting: boolean, is_empty: boolean;
save_processing: boolean, is_published: boolean;
save_loading: boolean, is_routing: boolean;
save_error: string;
save_finished: boolean;
save_overwriting: boolean;
save_processing: boolean;
save_loading: boolean;
features: { features: {
routing: boolean, routing: boolean;
}, };
renderer: { renderer: {
data: string, data: string;
width: number, width: number;
height: number height: number;
renderer_active: boolean, renderer_active: boolean;
info: string, info: string;
progress: number, progress: number;
}, };
} }
const EDITOR_INITIAL_STATE = { const EDITOR_INITIAL_STATE = {
@ -57,7 +64,10 @@ const EDITOR_INITIAL_STATE = {
estimated: 0, estimated: 0,
speed: 15, speed: 15,
activeSticker: { set: null, sticker: null }, activeSticker: { set: null, sticker: null },
router: {
waypoints: [],
points: [],
},
is_published: false, is_published: false,
is_empty: true, is_empty: true,
is_routing: false, is_routing: false,
@ -67,7 +77,7 @@ const EDITOR_INITIAL_STATE = {
save_overwriting: false, save_overwriting: false,
save_processing: false, save_processing: false,
save_loading: false, save_loading: false,
features: { features: {
routing: false, routing: false,
}, },
@ -80,6 +90,6 @@ const EDITOR_INITIAL_STATE = {
info: '', info: '',
progress: 0, progress: 0,
}, },
} };
export const editor = createReducer(EDITOR_INITIAL_STATE, EDITOR_HANDLERS); export const editor = createReducer(EDITOR_INITIAL_STATE, EDITOR_HANDLERS);

View file

@ -1,7 +1,7 @@
import { call, put, takeEvery, takeLatest, select, race } from 'redux-saga/effects'; import { call, put, takeEvery, takeLatest, select, race } from 'redux-saga/effects';
import { delay, SagaIterator } from 'redux-saga'; import { delay, SagaIterator } from 'redux-saga';
import { selectEditor } from '~/redux/editor/selectors'; import { selectEditor } from '~/redux/editor/selectors';
import { simplify } from '~/utils/simplify';
import { import {
editorHideRenderer, editorHideRenderer,
editorSetChanged, editorSetChanged,
@ -36,11 +36,15 @@ import {
imageFetcher, imageFetcher,
downloadCanvas, downloadCanvas,
} from '~/utils/renderer'; } from '~/utils/renderer';
import { selectMap } from '../map/selectors'; import { selectMap, selectMapRoute } from '../map/selectors';
import { selectUser } from '../user/selectors'; import { selectUser } from '../user/selectors';
import { LOGOS } from '~/constants/logos'; import { LOGOS } from '~/constants/logos';
import { loadMapSaga, replaceAddressIfItsBusy } from '../map/sagas'; import { loadMapSaga } from '../map/sagas';
import { mapSetAddressOrigin } from '../map/actions'; import { mapClicked, mapSetRoute } from '../map/actions';
import { MAP_ACTIONS } from '../map/constants';
import { OsrmRouter } from '~/utils/osrm';
import path from 'ramda/es/path';
import { MainMap } from '~/constants/map';
const hideLoader = () => { const hideLoader = () => {
document.getElementById('loader').style.opacity = String(0); document.getElementById('loader').style.opacity = String(0);
@ -230,10 +234,30 @@ function* getGPXTrackSaga(): SagaIterator {
} }
function* routerCancel() { function* routerCancel() {
yield put(editorSetMode(MODES.NONE)) yield put(editorSetMode(MODES.NONE));
// TODO: clear router // TODO: clear router
} }
function* mapClick({ latlng }: ReturnType<typeof mapClicked>) {
const { mode }: ReturnType<typeof selectEditor> = yield select(selectEditor);
if (mode === MODES.ROUTER) {
const wp = OsrmRouter.getWaypoints().filter(point => !!point.latLng);
OsrmRouter.setWaypoints([...wp, latlng]);
}
}
function* routerSubmit() {
const route: ReturnType<typeof selectMapRoute> = yield select(selectMapRoute);
const latlngs = path(['_routes', 0, 'coordinates'], OsrmRouter);
const coordinates = simplify({ map: MainMap, latlngs });
yield put(mapSetRoute([...route, ...coordinates]));
OsrmRouter.setWaypoints([]);
yield put(editorSetMode(MODES.NONE));
}
export function* editorSaga() { export function* editorSaga() {
yield takeEvery(EDITOR_ACTIONS.STOP_EDITING, stopEditingSaga); yield takeEvery(EDITOR_ACTIONS.STOP_EDITING, stopEditingSaga);
yield takeLatest(EDITOR_ACTIONS.TAKE_A_SHOT, takeAShotSaga); yield takeLatest(EDITOR_ACTIONS.TAKE_A_SHOT, takeAShotSaga);
@ -242,4 +266,6 @@ export function* editorSaga() {
yield takeLatest(EDITOR_ACTIONS.KEY_PRESSED, keyPressedSaga); yield takeLatest(EDITOR_ACTIONS.KEY_PRESSED, keyPressedSaga);
yield takeLatest(EDITOR_ACTIONS.GET_GPX_TRACK, getGPXTrackSaga); yield takeLatest(EDITOR_ACTIONS.GET_GPX_TRACK, getGPXTrackSaga);
yield takeLatest(EDITOR_ACTIONS.ROUTER_CANCEL, routerCancel); yield takeLatest(EDITOR_ACTIONS.ROUTER_CANCEL, routerCancel);
yield takeLatest(MAP_ACTIONS.MAP_CLICKED, mapClick);
yield takeLatest(EDITOR_ACTIONS.ROUTER_SUBMIT, routerSubmit);
} }

View file

@ -4,4 +4,5 @@ export const selectEditor = (state: IState) => state.editor;
export const selectEditorEditing = (state: IState) => state.editor.editing; export const selectEditorEditing = (state: IState) => state.editor.editing;
export const selectEditorMode = (state: IState) => state.editor.mode; export const selectEditorMode = (state: IState) => state.editor.mode;
export const selectEditorActiveSticker = (state: IState) => state.editor.activeSticker; export const selectEditorActiveSticker = (state: IState) => state.editor.activeSticker;
export const selectEditorRenderer = (state: IState) => state.editor.renderer; export const selectEditorRenderer = (state: IState) => state.editor.renderer;
export const selectEditorRouter = (state: IState) => state.editor.router;

65
src/utils/osrm.ts Normal file
View file

@ -0,0 +1,65 @@
import { Marker } from 'leaflet';
import * as Routing from 'leaflet-routing-machine/src/index';
import { CLIENT } from '~/config/frontend';
import { DomMarker } from '~/utils/DomMarker';
import { MainMap } from '~/constants/map';
const createWaypointMarker = (): DomMarker => {
const element = document.createElement('div');
// element.addEventListener('mousedown', this.lockPropagations);
// element.addEventListener('mouseup', this.unlockPropagations);
return new DomMarker({
element,
className: 'router-waypoint',
});
};
const routeLine = r =>
Routing.line(r, {
styles: [
{ color: 'white', opacity: 0.8, weight: 12 },
{ color: '#4597d0', opacity: 1, weight: 4, dashArray: '15,10' },
],
addWaypoints: true,
});
// .on('linetouched', this.lockPropagations);
export const OsrmRouter = Routing.control({
serviceUrl: CLIENT.OSRM_URL,
profile: CLIENT.OSRM_PROFILE,
fitSelectedRoutes: false,
showAlternatives: false,
routeLine,
altLineOptions: {
styles: [{ color: '#4597d0', opacity: 1, weight: 3 }],
},
show: false,
plan: Routing.plan([], {
createMarker: (i, wp) => {
const marker = new Marker(wp.latLng, {
draggable: true,
icon: createWaypointMarker(),
})
.on('dragstart', () => MainMap.disableClicks())
.on('dragend', () => MainMap.enableClicks())
.on('contextmenu', ({ latlng }: any) => {
OsrmRouter.setWaypoints(
OsrmRouter.getWaypoints().filter(
point =>
!point.latLng || (point.latLng.lat != latlng.lat && point.latLng.lng != latlng.lng)
)
);
});
return marker;
},
routeWhileDragging: false,
}),
routeWhileDragging: false,
routingOptions: {
geometryOnly: false,
},
useHints: false,
});

View file

@ -2,22 +2,8 @@ import { Map, LineUtil } from 'leaflet';
import { ILatLng } from "~/redux/map/types"; import { ILatLng } from "~/redux/map/types";
export const simplify = ({ map, latlngs }: { map: Map, latlngs: ILatLng[] }): ILatLng[] => { export const simplify = ({ map, latlngs }: { map: Map, latlngs: ILatLng[] }): ILatLng[] => {
const points = [];
const target = [];
const zoom = 12; const zoom = 12;
const mul = 0.7; // 0 - not simplifying, 1 - very rude. const mul = 0.7; // 0 - not simplifying, 1 - very rude.
// its better to estimate mul value by route length const points = latlngs.map(({ lat, lng }) => map.project({ lat, lng }, zoom));
return LineUtil.simplify(points, mul).map(item => map.unproject(item, zoom));
for (let i = 0; i < latlngs.length; i += 1) {
points.push(map.project({ lat: latlngs[i].lat, lng: latlngs[i].lng }, zoom));
}
const simplified = LineUtil.simplify(points, mul);
// for (let i = 0; i < simplified.length; i += 1) {
// target.push(map.unproject(simplified[i], zoom));
// }
//
// return target;
return simplified.map(item => map.unproject(item, zoom));
}; };