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

View file

@ -1,13 +1,13 @@
import React from 'react';
import { MainMap } from '~/constants/map';
import { Map as MapInterface } from 'leaflet';
import { createPortal } from 'react-dom';
import { selectMapProvider, selectMapRoute, selectMapStickers } from '~/redux/map/selectors';
import { connect } from 'react-redux';
import * as MAP_ACTIONS from '~/redux/map/actions';
import { Route } from '~/containers/map/Route';
import { Router } from '~/containers/map/Router';
import { TileLayer } from '~/containers/map/TileLayer';
import { Stickers } from '~/containers/map/Stickers';
@ -71,6 +71,7 @@ const MapUnconnected: React.FC<IProps> = ({
mapDropSticker={mapDropSticker}
is_editing={editing}
/>
<Router />
</div>,
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,
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_IS_ROUTING: `${P}-SET_IS_ROUTING`,
KEY_PRESSED: `${P}-KEY_PRESSED`,
SET_ROUTER: `${P}-SET_ROUTER`,
};

View file

@ -113,7 +113,10 @@ const resetSaveDialog = (state): IEditorState => ({
save_error: '',
});
const setDialog = (state, { dialog }: ReturnType<typeof ACTIONS.editorSetDialog>): IEditorState => ({
const setDialog = (
state,
{ dialog }: ReturnType<typeof ACTIONS.editorSetDialog>
): IEditorState => ({
...state,
dialog,
});
@ -126,7 +129,10 @@ const setDialogActive = (
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,
ready,
});
@ -169,6 +175,17 @@ const setIsRouting = (
is_routing,
});
const setRouter = (
state,
{ router }: ReturnType<typeof ACTIONS.editorSetRouter>
): IEditorState => ({
...state,
router: {
...state.router,
...router,
},
});
export const EDITOR_HANDLERS = {
[EDITOR_ACTIONS.SET_EDITING]: setEditing,
[EDITOR_ACTIONS.SET_CHANGED]: setChanged,
@ -197,4 +214,5 @@ export const EDITOR_HANDLERS = {
[EDITOR_ACTIONS.SET_FEATURE]: setFeature,
[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 { createReducer } from 'reduxsauce';
import { EDITOR_HANDLERS } from './handlers';
import { ILatLng } from '../map/types';
export interface IEditorState {
changed: boolean,
editing: boolean,
ready: boolean,
changed: boolean;
editing: boolean;
ready: boolean;
markers_shown: boolean;
mode: typeof MODES[keyof typeof MODES],
dialog: IDialogs[keyof IDialogs],
dialog_active: boolean,
router: {
points: ILatLng[];
waypoints: ILatLng[];
};
routerPoints: number,
distance: number,
estimated: number,
speed: number,
activeSticker: { set?: string, sticker?: string },
is_empty: boolean,
is_published: boolean,
is_routing: boolean,
save_error: string,
save_finished: boolean,
save_overwriting: boolean,
save_processing: boolean,
save_loading: boolean,
mode: typeof MODES[keyof typeof MODES];
dialog: IDialogs[keyof IDialogs];
dialog_active: boolean;
routerPoints: number;
distance: number;
estimated: number;
speed: number;
activeSticker: { set?: string; sticker?: string };
is_empty: boolean;
is_published: boolean;
is_routing: boolean;
save_error: string;
save_finished: boolean;
save_overwriting: boolean;
save_processing: boolean;
save_loading: boolean;
features: {
routing: boolean,
},
routing: boolean;
};
renderer: {
data: string,
width: number,
height: number
renderer_active: boolean,
info: string,
progress: number,
},
data: string;
width: number;
height: number;
renderer_active: boolean;
info: string;
progress: number;
};
}
const EDITOR_INITIAL_STATE = {
@ -57,7 +64,10 @@ const EDITOR_INITIAL_STATE = {
estimated: 0,
speed: 15,
activeSticker: { set: null, sticker: null },
router: {
waypoints: [],
points: [],
},
is_published: false,
is_empty: true,
is_routing: false,
@ -67,7 +77,7 @@ const EDITOR_INITIAL_STATE = {
save_overwriting: false,
save_processing: false,
save_loading: false,
features: {
routing: false,
},
@ -80,6 +90,6 @@ const EDITOR_INITIAL_STATE = {
info: '',
progress: 0,
},
}
};
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 { delay, SagaIterator } from 'redux-saga';
import { selectEditor } from '~/redux/editor/selectors';
import { simplify } from '~/utils/simplify';
import {
editorHideRenderer,
editorSetChanged,
@ -36,11 +36,15 @@ import {
imageFetcher,
downloadCanvas,
} from '~/utils/renderer';
import { selectMap } from '../map/selectors';
import { selectMap, selectMapRoute } from '../map/selectors';
import { selectUser } from '../user/selectors';
import { LOGOS } from '~/constants/logos';
import { loadMapSaga, replaceAddressIfItsBusy } from '../map/sagas';
import { mapSetAddressOrigin } from '../map/actions';
import { loadMapSaga } from '../map/sagas';
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 = () => {
document.getElementById('loader').style.opacity = String(0);
@ -230,10 +234,30 @@ function* getGPXTrackSaga(): SagaIterator {
}
function* routerCancel() {
yield put(editorSetMode(MODES.NONE))
yield put(editorSetMode(MODES.NONE));
// 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() {
yield takeEvery(EDITOR_ACTIONS.STOP_EDITING, stopEditingSaga);
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.GET_GPX_TRACK, getGPXTrackSaga);
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 selectEditorMode = (state: IState) => state.editor.mode;
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";
export const simplify = ({ map, latlngs }: { map: Map, latlngs: ILatLng[] }): ILatLng[] => {
const points = [];
const target = [];
const zoom = 12;
const mul = 0.7; // 0 - not simplifying, 1 - very rude.
// its better to estimate mul value by route length
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));
const points = latlngs.map(({ lat, lng }) => map.project({ lat, lng }, zoom));
return LineUtil.simplify(points, mul).map(item => map.unproject(item, zoom));
};