mirror of
https://github.com/muerwre/orchidmap-front.git
synced 2025-04-25 02:56:41 +07:00
fixed router
This commit is contained in:
parent
42dbfb0681
commit
2be073078f
11 changed files with 241 additions and 62 deletions
|
@ -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>
|
||||
);
|
||||
|
||||
|
|
|
@ -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')
|
||||
);
|
||||
|
|
61
src/containers/map/Router/index.tsx
Normal file
61
src/containers/map/Router/index.tsx
Normal 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 };
|
|
@ -118,3 +118,8 @@ export const editorKeyPressed = ({
|
|||
key,
|
||||
target: tagName,
|
||||
});
|
||||
|
||||
export const editorSetRouter = (router: Partial<IEditorState['router']>) => ({
|
||||
type: EDITOR_ACTIONS.SET_ROUTER,
|
||||
router,
|
||||
});
|
||||
|
|
|
@ -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`,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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],
|
||||
router: {
|
||||
points: ILatLng[];
|
||||
waypoints: ILatLng[];
|
||||
};
|
||||
|
||||
dialog: IDialogs[keyof IDialogs],
|
||||
dialog_active: boolean,
|
||||
mode: typeof MODES[keyof typeof MODES];
|
||||
|
||||
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,
|
||||
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,
|
||||
|
@ -80,6 +90,6 @@ const EDITOR_INITIAL_STATE = {
|
|||
info: '',
|
||||
progress: 0,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export const editor = createReducer(EDITOR_INITIAL_STATE, EDITOR_HANDLERS);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -5,3 +5,4 @@ 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 selectEditorRouter = (state: IState) => state.editor.router;
|
65
src/utils/osrm.ts
Normal file
65
src/utils/osrm.ts
Normal 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,
|
||||
});
|
|
@ -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));
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue