Merge branch 'master' into feature/dialog-editor

# Conflicts:
#	src/components/dialogs/MapListDialog.tsx
#	src/modules/Poly.ts
#	src/styles/dialogs.less
This commit is contained in:
muerwre 2019-03-06 14:08:16 +07:00
commit d42586d9e0
25 changed files with 556 additions and 971 deletions

View file

@ -1,11 +1,18 @@
import { DEFAULT_PROVIDER, PROVIDERS } from '$constants/providers';
import { LatLngLiteral } from 'leaflet';
const API_ADDR = 'https://HOSTNAME.org:3000';
const OSRM_URL = 'https://HOSTNAME.org:5001/route/v1';
const OSRM_PROFILE = 'bike';
const OSRM_TEST_URL = ([south_west, north_east]: [LatLngLiteral, LatLngLiteral]) => (
`${OSRM_URL}/${OSRM_PROFILE}/${Object.values(south_west).join(',')};${Object.values(north_east).join(',')}`
);
export const CLIENT = {
OSRM_URL,
API_ADDR,
OSRM_TEST_URL,
OSRM_PROFILE,
STROKE_WIDTH: 5,
};

View file

@ -78,6 +78,8 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
openRoute = (_id: string): void => {
if (isMobile()) this.props.setDialogActive(false);
// pushPath(`/${_id}/${this.props.editing ? 'edit' : ''}`);
pushPath(`/${_id}`);
this.stopEditing();
pushPath(`/${_id}/${this.props.editing ? 'edit' : ''}`);

View file

@ -4,10 +4,12 @@ import {
routerCancel as routerCancelAction,
routerSubmit as routerSubmitAction,
} from "$redux/user/actions";
import classnames from "classnames";
type Props = {
routerPoints: number,
width: number,
is_routing: boolean,
routerCancel: typeof routerCancelAction,
routerSubmit: typeof routerSubmitAction,
@ -81,9 +83,11 @@ const draggablePoints = ({
);
export const RouterDialog = ({
routerPoints, routerCancel, routerSubmit, width
routerPoints, routerCancel, routerSubmit, width, is_routing,
}: Props) => (
<div className="control-dialog" style={{ width }}>
<div className={classnames('save-loader', { active: is_routing })} />
{!routerPoints && noPoints({ routerCancel })}
{routerPoints === 1 && firstPoint({ routerCancel })}
{routerPoints >= 2 && draggablePoints({ routerCancel, routerSubmit })}

View file

@ -57,7 +57,7 @@ class Component extends React.PureComponent<Props, State> {
<span className="desktop-only">
<Icon icon="icon-cycle" size={32} />
</span>
<span className="desktop-only">{toHours(estimated)}</span>
<div className="desktop-only">{toHours(estimated)}</div>
</div>
{
dialogOpened &&

View file

@ -11,6 +11,7 @@ import { IRootState } from "$redux/user/reducer";
import { Tooltip } from "$components/panels/Tooltip";
interface Props extends IRootState {
routing: IRootState['features']['routing'],
setMode: typeof setMode,
startEditing: typeof startEditing,
stopEditing: typeof stopEditing,
@ -46,20 +47,25 @@ class Component extends React.PureComponent<Props, void> {
render() {
const {
mode, changed, editing,
mode, changed, editing, routing,
} = this.props;
return (
<div>
<div className={classnames('panel right', { active: editing })} ref={el => { this.panel = el; }}>
<div className="control-bar control-bar-padded">
<button
className={classnames({ active: mode === MODES.ROUTER })}
onClick={this.startRouterMode}
>
<Tooltip>Автоматический маршрут</Tooltip>
<Icon icon="icon-route-2" />
</button>
{
routing &&
<button
className={classnames({ active: mode === MODES.ROUTER })}
onClick={this.startRouterMode}
>
<Tooltip>Автоматический маршрут</Tooltip>
<Icon icon="icon-route-2" />
</button>
}
<button
className={classnames({ active: mode === MODES.POLY })}
onClick={this.startPolyMode}
@ -67,6 +73,7 @@ class Component extends React.PureComponent<Props, void> {
<Tooltip>Редактирование маршрута</Tooltip>
<Icon icon="icon-poly-3" />
</button>
<button
className={classnames({ active: (mode === MODES.STICKERS || mode === MODES.STICKERS_SELECT) })}
onClick={this.startStickerMode}
@ -133,26 +140,20 @@ class Component extends React.PureComponent<Props, void> {
function mapStateToProps(state) {
const {
user: {
user,
editing,
mode,
routerPoints,
activeSticker,
title,
address,
changed,
features: {
routing,
}
},
} = state;
return {
user,
editing,
mode,
routerPoints,
activeSticker,
title,
address,
changed,
routing,
};
}

View file

@ -13,6 +13,8 @@ export const APP_INFO = {
'Приложение для vk', // [11.12.18]
'Фильтр в диалоге поиска карт', // [13.12.18]
'Экспорт GPX', // [18.02.19]
'Улучшенный редактор ломанных', // [23.02.19]
'Отметки расстояний и стрелки', // [04.03.19]
],
[
'Первый коммит', // [15.08.18]

View file

@ -2,12 +2,16 @@
## BUGS
todo fix arrows (can't reproduce now :-( )
done adding route, applying it and adding again and deleting it makes ghost points on the map
## FEATURES
todo arrows on screenshot
todo make arrows and distance points
todo refactor reducer to use is_ prefix for editing and etc (mb move them to status object)
todo tower sticker
todo route description
todo polyline editing only in manual mode (or by click)
todo selecting logo on crop
done public maps
@ -18,13 +22,23 @@
done delayed notify (delay(2000).then(showLoadingMsg))
todo network error notifications
todo check canvas support at startup
todo check osrm is up
done check osrm is up
todo maybe: map preview on save
done maybe: stickers clusterization?
todo maybe: map preview on save (dont think so)
## DONE
done routing spinner
done maybe: stickers clusterization?
done moving out the screen makes stickers editable again
done check if osrm available
done selecting map on dialog in edit mode opens it at view mode
done make arrows and distance points
done fix arrows (can't reproduce now :-( )
done adding route, applying it and adding again and deleting it makes ghost points on the map
done adding/removing points doesn't change distance
done cancelling editing someone's else map return back to it's original address /razminochnyj/
done change title on route opening

65
src/modules/Arrows.ts Normal file
View file

@ -0,0 +1,65 @@
import { LatLngLiteral, LayerGroup, Map } from "leaflet";
import { arrowClusterIcon, createArrow } from "$utils/arrow";
import { MarkerClusterGroup } from 'leaflet.markercluster/dist/leaflet.markercluster-src.js';
import { angleBetweenPoints, dist2, middleCoord } from "$utils/geom";
class Component extends LayerGroup {
constructor(props){
super(props);
}
setLatLngs = (latlngs: LatLngLiteral[]): void => {
if (!this.map) return;
this.arrowLayer.clearLayers();
if (latlngs.length === 0) return;
const midpoints = latlngs.reduce((res, latlng, i) => (
latlngs[i + 1] && dist2(latlngs[i], latlngs[i + 1]) > 0.00005
? [
...res,
{
latlng: middleCoord(latlngs[i], latlngs[i + 1]),
angle: angleBetweenPoints(
this.map.latLngToContainerPoint(latlngs[i]),
this.map.latLngToContainerPoint(latlngs[i + 1])
),
}
]
: res
), []);
midpoints.forEach(({ latlng, angle }) => (
this.arrowLayer.addLayer(createArrow(latlng, angle))
));
};
map: Map;
arrowLayer: MarkerClusterGroup = new MarkerClusterGroup({
spiderfyOnMaxZoom: false,
showCoverageOnHover: false,
zoomToBoundsOnClick: false,
animate: false,
maxClusterRadius: 120,
iconCreateFunction: arrowClusterIcon,
});
}
Component.addInitHook(function () {
this.once('add', (event) => {
if (event.target instanceof Arrows) {
this.map = event.target._map;
this.arrowLayer.addTo(this.map);
}
});
this.once('remove', (event) => {
if (event.target instanceof Arrows) {
this.arrowLayer.removeFrom(this.map);
}
});
});
export const Arrows = Component;

View file

@ -14,7 +14,7 @@ import {
setAddress,
setChanged,
setDistance,
setIsEmpty,
setIsEmpty, setIsRouting,
setLogo,
setMarkersShown,
setMode,
@ -81,9 +81,19 @@ export class Editor {
map, routerMoveStart, lockMapClicks, setDistance: this.setDistance, triggerOnChange, editor: this,
});
this.stickers = new Stickers({ map, lockMapClicks, triggerOnChange, editor: this });
this.stickers = new Stickers({
map,
lockMapClicks,
triggerOnChange,
editor: this
});
this.router = new Router({
map, lockMapClicks, setRouterPoints: this.setRouterPoints, pushPolyPoints
map,
lockMapClicks,
pushPolyPoints,
setRouterPoints: this.setRouterPoints,
setIsRouting: this.setIsRouting,
});
this.switches = {
@ -176,6 +186,7 @@ export class Editor {
setAddress: typeof setAddress = value => store.dispatch(setAddress(value));
setPublic: typeof setPublic = value => store.dispatch(setPublic(value));
setIsEmpty: typeof setIsEmpty = value => store.dispatch(setIsEmpty(value));
setIsRouting: typeof setIsRouting = value => store.dispatch(setIsRouting(value));
setMarkersShown = (value: boolean): void => {
if (this.getState().markers_shown !== value) store.dispatch(setMarkersShown(value));
@ -397,7 +408,7 @@ export class Editor {
get isEmpty(): boolean {
const { route, stickers } = this.dumpData();
return (!route || route.length < 1) && (!stickers || stickers.length <= 0);
return (!route || route.length <= 1) && (!stickers || stickers.length <= 0);
}
get hasEmptyHistory(): boolean {

View file

@ -33,8 +33,8 @@ export class Component extends Polyline {
this.constraintsStyle = { ...this.constraintsStyle, ...options.constraintsStyle };
this.maxMarkers = options.maxMarkers || this.maxMarkers;
this.kmMarksEnabled = options.kmMarksEnabled || this.kmMarksEnabled;
this.kmMarksStep = options.kmMarksStep || this.kmMarksStep;
// this.kmMarksEnabled = options.kmMarksEnabled || this.kmMarksEnabled;
// this.kmMarksStep = options.kmMarksStep || this.kmMarksStep;
this.constrLine = new Polyline([], this.constraintsStyle);
@ -49,7 +49,7 @@ export class Component extends Polyline {
this.setLatLngs(latlngs);
this.recreateMarkers();
this.recalcDistance();
this.recalcKmMarks();
// this.recalcKmMarks();
this.touchHinter.setLatLngs(latlngs);
this.fire('latlngschange', { latlngs });
};
@ -405,6 +405,7 @@ export class Component extends Polyline {
this.setLatLngs(latlngs);
this.fire('latlngschange', { latlngs });
this.showVisibleMarkers();
this.startDrawing();
};
@ -483,35 +484,35 @@ export class Component extends Polyline {
this.fire('distancechange', { distance: this.distance });
};
//
// recalcKmMarks = () => {
// if (!this.kmMarksEnabled) return;
//
// const latlngs = this.getLatLngs() as LatLngLiteral[];
//
// this.kmMarks = { };
//
// let last_km_mark = 0;
//
// latlngs.reduce((dist, latlng, index) => {
// if (index >= latlngs.length - 1) return;
//
// const next = latlngs[index + 1];
// const sum = dist + distKm(latlng, next);
// const rounded = Math.floor(dist / this.kmMarksStep) * this.kmMarksStep;
//
// if (rounded > last_km_mark) {
// last_km_mark = rounded;
// this.kmMarks[rounded] = latlng;
// }
//
// return sum;
// }, 0);
// };
recalcKmMarks = () => {
if (!this.kmMarksEnabled) return;
const latlngs = this.getLatLngs() as LatLngLiteral[];
this.kmMarks = { };
let last_km_mark = 0;
latlngs.reduce((dist, latlng, index) => {
if (index >= latlngs.length - 1) return;
const next = latlngs[index + 1];
const sum = dist + distKm(latlng, next);
const rounded = Math.floor(dist / this.kmMarksStep) * this.kmMarksStep;
if (rounded > last_km_mark) {
last_km_mark = rounded;
this.kmMarks[rounded] = latlng;
}
return sum;
}, 0);
};
kmMarksEnabled?: InteractivePolylineOptions['kmMarksEnabled'] = true;
kmMarksStep?: InteractivePolylineOptions['kmMarksStep'] = 5;
kmMarks?: { [x: number]: LatLngLiteral };
// kmMarksEnabled?: InteractivePolylineOptions['kmMarksEnabled'] = true;
// kmMarksStep?: InteractivePolylineOptions['kmMarksStep'] = 5;
// kmMarks?: { [x: number]: LatLngLiteral };
// kmMarksLayer?: LayerGroup = new LayerGroup();
markers: Marker[] = [];

156
src/modules/KmMarks.ts Normal file
View file

@ -0,0 +1,156 @@
import { divIcon, LatLngLiteral, Layer, LayerGroup, Map, marker, Marker } from "leaflet";
import { arrowClusterIcon, createArrow } from "$utils/arrow";
import { MarkerClusterGroup } from 'leaflet.markercluster/dist/leaflet.markercluster-src.js';
import { allwaysPositiveAngleDeg, angleBetweenPoints, distKm } from "$utils/geom";
import classNames from 'classnames';
interface KmMarksOptions {
showMiddleMarkers: boolean,
showEndMarker: boolean,
kmMarksStep: number,
}
class Component extends LayerGroup {
constructor(latlngs?: LatLngLiteral[], options?: KmMarksOptions){
super();
this.options = {
showMiddleMarkers: true,
showEndMarker: true,
kmMarksStep: 10,
...(options || {}),
} as KmMarksOptions;
}
setLatLngs = (latlngs: LatLngLiteral[]): void => {
if (!this.map) return;
this.marksLayer.clearLayers();
this.endMarker.clearLayers();
this.distance = 0;
if (latlngs.length <= 1) return;
if (this.options.showMiddleMarkers) this.drawMiddleMarkers(latlngs);
if (this.options.showEndMarker) this.drawEndMarker(latlngs);
};
drawMiddleMarkers = (latlngs: LatLngLiteral[]) => {
const kmMarks = {};
let last_km_mark = 0;
this.distance = latlngs.reduce((dist, current, index) => {
if (index >= latlngs.length - 1) return dist;
const next = latlngs[index + 1];
const diff = distKm(current, next);
const sum = dist + diff;
const rounded = Math.floor(sum / this.options.kmMarksStep) * this.options.kmMarksStep;
const count = Math.floor((rounded - last_km_mark) / this.options.kmMarksStep);
if (rounded > last_km_mark) {
const angle = angleBetweenPoints(
this.map.latLngToContainerPoint(current),
this.map.latLngToContainerPoint(next),
);
for (let i = 1; i <= count; i += 1) {
const step = last_km_mark + (i * this.options.kmMarksStep);
const shift = (step - dist) / diff;
const coords = {
lat: current.lat - ((current.lat - next.lat) * shift),
lng: current.lng - ((current.lng - next.lng) * shift),
};
kmMarks[step] = { ...coords, angle };
this.marksLayer.addLayer(this.createMiddleMarker(coords, angle, step));
}
last_km_mark = rounded;
}
return sum;
}, 0);
};
createMiddleMarker = (latlng: LatLngLiteral, angle: number, distance: number): Marker => marker(latlng, {
draggable: false,
interactive: true,
icon: divIcon({
html: `
<div class="leaflet-km-dist" style="transform: translate(-50%, -50%) rotate(${allwaysPositiveAngleDeg(angle)}deg);">
${distance}
</div>
`,
className: 'leaflet-km-marker',
iconSize: [11, 11],
iconAnchor: [6, 6]
})
});
createEndMarker = (latlng: LatLngLiteral, angle: number, distance: number): Marker => marker(latlng, {
draggable: false,
interactive: true,
icon: divIcon({
html: `
<div class="leaflet-km-dist">
${parseFloat(distance.toFixed(1))}
</div>
`,
className: classNames('leaflet-km-marker end-marker', { right: (angle > -90 && angle < 90) }),
iconSize: [11, 11],
iconAnchor: [6, 6]
}),
zIndexOffset: -100,
});
drawEndMarker = (latlngs: LatLngLiteral[]): void => {
this.endMarker.clearLayers();
const current = latlngs[latlngs.length - 2];
const next = latlngs[latlngs.length - 1
];
const angle = angleBetweenPoints(
this.map.latLngToContainerPoint(current),
this.map.latLngToContainerPoint(next),
);
this.endMarker.addLayer(this.createEndMarker(next, angle, this.distance));
};
options: KmMarksOptions;
map: Map;
marksLayer: MarkerClusterGroup = new MarkerClusterGroup({
spiderfyOnMaxZoom: false,
showCoverageOnHover: false,
zoomToBoundsOnClick: false,
animate: false,
maxClusterRadius: 120,
iconCreateFunction: arrowClusterIcon,
});
endMarker: LayerGroup = new LayerGroup();
distance: number = 0;
}
Component.addInitHook(function () {
this.once('add', (event) => {
if (event.target instanceof KmMarks) {
this.map = event.target._map;
this.marksLayer.addTo(this.map);
this.endMarker.addTo(this.map);
}
});
this.once('remove', (event) => {
if (event.target instanceof KmMarks) {
this.marksLayer.removeFrom(this.map);
this.endMarker.removeFrom(this.map);
}
});
});
export const KmMarks = Component;

View file

@ -1,13 +1,12 @@
import { Map, LatLng } from 'leaflet';
import { EditablePolyline } from '$utils/EditablePolyline';
import { simplify } from '$utils/simplify';
import { CLIENT } from '$config/frontend';
import { editor, Editor } from "$modules/Editor";
import { ILatLng } from "$modules/Stickers";
import { InteractivePoly } from "$modules/InteractivePoly";
import { MarkerClusterGroup } from 'leaflet.markercluster/dist/leaflet.markercluster-src.js';
import { angleBetweenPoints, dist2, distToSegment, middleCoord } from "$utils/geom";
import { arrowClusterIcon, createArrow } from "$utils/arrow";
import { Arrows } from "$modules/Arrows";
import { KmMarks } from "$modules/KmMarks";
import { isMobile } from "$utils/window";
interface Props {
map: Map;
@ -25,14 +24,13 @@ export class Poly {
this.poly = new InteractivePoly([ ], {
color: 'url(#activePathGradient)',
weight: 6,
maxMarkers: 100,
maxMarkers: isMobile() ? 20 : 100,
smoothFactor: 3,
})
.on('distancechange', this.onDistanceUpdate)
.on('allvertexhide', this.onVertexHide)
.on('allvertexshow', this.onVertexShow)
.on('latlngschange', this.updateArrows)
.on('latlngschange', triggerOnChange);
.on('latlngschange', this.updateMarks)
this.poly.addTo(map);
this.editor = editor;
@ -44,7 +42,8 @@ export class Poly {
this.triggerOnChange = triggerOnChange;
this.lockMapClicks = lockMapClicks;
this.arrowLayer.addTo(map);
this.arrows = new Arrows({}).addTo(map);
this.kmMarks = new KmMarks().addTo(map);
}
onDistanceUpdate = (event) => {
@ -55,66 +54,15 @@ export class Poly {
onVertexHide = (): void => this.editor.setMarkersShown(false);
onVertexShow = (): void => this.editor.setMarkersShown(true);
updateArrows = event => {
this.editor.setChanged(true);
updateMarks = event => {
// this.editor.setChanged(true);
this.editor.triggerOnChange();
const { latlngs } = event;
this.arrowLayer.clearLayers();
if (latlngs.length === 0) return;
const midpoints = latlngs.reduce((res, latlng, i) => (
latlngs[i + 1] && dist2(latlngs[i], latlngs[i + 1]) > 0.00005
? [
...res,
{
latlng: middleCoord(latlngs[i], latlngs[i + 1]),
angle: angleBetweenPoints(
this.map.latLngToContainerPoint(latlngs[i]),
this.map.latLngToContainerPoint(latlngs[i + 1])
),
}
]
: res
), []);
midpoints.forEach(({ latlng, angle }) => (
this.arrowLayer.addLayer(createArrow(latlng, angle))
));
this.arrows.setLatLngs(latlngs);
this.kmMarks.setLatLngs(latlngs);
};
// setModeOnDrawing = (): void => {
// if (this.editor.getMode() !== MODES.POLY) this.editor.setMode(MODES.POLY);
// };
//
// drawArrows = () => {
// // todo: fix this
// this.arrows.clearLayers();
// const { latlngs } = this;
//
// if (!latlngs || latlngs.length <= 1) return;
//
// latlngs.forEach((latlng, i) => {
// if (i === 0) return;
//
// const mid = middleCoord(latlngs[i], latlngs[i - 1]);
// const dist = findDistance(latlngs[i - 1].lat, latlngs[i - 1].lng, latlngs[i].lat, latlngs[i].lng);
//
// if (dist <= 1) return;
//
// const slide = new Polyline(
// [
// latlngs[i - 1],
// [mid.lat, mid.lng]
// ],
// { color: 'none', weight: CLIENT.STROKE_WIDTH }
// ).addTo(this.arrows) as any;
//
// // todo: uncomment and fix this:
// slide._path.setAttribute('marker-end', 'url(#long-arrow)');
// });
// };
continue = (): void => {
this.poly.editor.continue();
};
@ -147,8 +95,6 @@ export class Poly {
this.poly.setPoints([]);
};
// clearArrows = (): LayerGroup<any> => this.arrows.clearLayers();
dumpData = (): Array<LatLng> => this.latlngs;
get latlngs(): Array<LatLng> {
@ -161,16 +107,9 @@ export class Poly {
return (!this.latlngs || Object.values(this.latlngs).length <= 0);
}
arrows;
poly;
arrowLayer: MarkerClusterGroup = new MarkerClusterGroup({
spiderfyOnMaxZoom: false,
showCoverageOnHover: false,
zoomToBoundsOnClick: false,
animate: false,
maxClusterRadius: 120,
// disableClusteringAtZoom: 13,
iconCreateFunction: arrowClusterIcon,
});
kmMarks;
editor: Props['editor'];
map: Props['map'];

View file

@ -13,6 +13,7 @@ interface IWaypoint {
}
interface Props {
setIsRouting: typeof editor.setIsRouting,
map: Map,
setRouterPoints: typeof editor.setRouterPoints,
pushPolyPoints: typeof editor.pushPolyPoints,
@ -21,12 +22,13 @@ interface Props {
export class Router {
constructor({
map, lockMapClicks, setRouterPoints, pushPolyPoints
map, lockMapClicks, setRouterPoints, pushPolyPoints, setIsRouting,
}: Props) {
this.waypoints = [];
this.lockMapClicks = lockMapClicks;
this.setRouterPoints = setRouterPoints;
this.pushPolyPoints = pushPolyPoints;
this.setIsRouting = setIsRouting;
const routeLine = r => Routing.line(r, {
styles: [
@ -38,8 +40,9 @@ export class Router {
this.router = Routing.control({
serviceUrl: CLIENT.OSRM_URL,
profile: 'bike',
profile: CLIENT.OSRM_PROFILE,
fitSelectedRoutes: false,
showAlternatives: false,
routeLine,
altLineOptions: {
styles: [{ color: '#4597d0', opacity: 1, weight: 3 }]
@ -50,14 +53,29 @@ export class Router {
draggable: true,
icon: this.createWaypointMarker(),
}),
routeWhileDragging: true,
routeWhileDragging: false,
}),
routeWhileDragging: true
}).on('waypointschanged', this.updateWaypointsCount);
routeWhileDragging: false,
routingOptions: {
geometryOnly: false,
},
useHints: false,
})
.on('routingstart', this.showSpinner)
.on('routesfound routingerror', this.hideSpinner)
.on('waypointschanged', this.updateWaypointsCount);
this.router.addTo(map);
}
showSpinner = () => {
this.setIsRouting(true);
};
hideSpinner = () => {
this.setIsRouting(false);
};
pushWaypointOnClick = ({ latlng: { lat, lng } }: { latlng: ILatLng }): void => {
const waypoints = this.router.getWaypoints().filter(({ latLng }) => !!latLng);
this.router.setWaypoints([...waypoints, { lat, lng }]);
@ -87,7 +105,7 @@ export class Router {
}
window.removeEventListener('mouseup', this.unlockPropagations);
setTimeout(() => this.lockMapClicks(false), 300);
setTimeout(() => this.lockMapClicks(false), 0);
};
startFrom = (latlngs: ILatLng): void => {
@ -148,6 +166,7 @@ export class Router {
};
waypoints: Array<IWaypoint> = [];
setIsRouting: Props['setIsRouting'];
lockMapClicks: Props['lockMapClicks'];
setRouterPoints: Props['setRouterPoints'];
pushPolyPoints: Props['pushPolyPoints'];

View file

@ -9,7 +9,7 @@ import classnames from 'classnames';
import { getLabelDirection } from '$utils/geom';
import { ILatLng } from "$modules/Stickers";
import { IRootState } from "$redux/user/reducer";
import { editor } from "$modules/Editor";
import { Editor, editor } from "$modules/Editor";
const getX = e => (
e.touches && e.touches.length > 0
@ -33,6 +33,7 @@ interface Props {
set: IRootState['activeSticker']['set'];
angle?: number;
text?: string;
editor: Editor,
deleteSticker: (sticker: this) => void;
@ -42,7 +43,7 @@ interface Props {
export class Sticker {
constructor({
latlng, deleteSticker, map, lockMapClicks, sticker, set, triggerOnChange, angle = 2.2, text = '',
latlng, deleteSticker, map, lockMapClicks, sticker, set, triggerOnChange, angle = 2.2, text = '', editor,
}: Props) {
this.text = text;
this.latlng = latlng;
@ -54,6 +55,7 @@ export class Sticker {
this.direction = getLabelDirection(this.angle);
this.deleteSticker = deleteSticker;
this.lockMapClicks = lockMapClicks;
this.editor = editor;
ReactDOM.render(
<React.Fragment>
@ -94,17 +96,27 @@ export class Sticker {
this.marker = marker(latlng, { icon: mark, draggable: true });
this.marker.on('add', this.updateModeOnAdd);
this.element.addEventListener('mouseup', this.onDragStop);
this.element.addEventListener('mouseup', this.preventPropagations);
this.element.addEventListener('touchend', this.onDragStop);
this.element.addEventListener('touchend', this.preventPropagations);
this.marker.addEventListener('dragend', this.triggerOnChange);
this.marker.on('dragend', this.triggerOnChange);
this.setAngle(this.angle);
}
updateModeOnAdd = () => {
if (this.editor.getEditing()) {
this.startEditing();
} else {
this.stopEditing();
}
};
setText = (text: Props['text']): void => {
this.text = text;
};
@ -122,11 +134,10 @@ export class Sticker {
this.lockMapClicks(true);
window.addEventListener('mousemove', this.onDrag);
window.addEventListener('mouseup', this.onDragStop);
window.addEventListener('touchmove', this.onDrag);
window.addEventListener('touchend', this.onDragStop);
// this.marker.disableEdit();
window.addEventListener('mouseup', this.onDragStop);
window.addEventListener('touchend', this.onDragStop);
};
preventPropagations = (e): void => {
@ -144,9 +155,9 @@ export class Sticker {
this.isDragging = false;
window.removeEventListener('mousemove', this.onDrag);
window.removeEventListener('mouseup', this.onDragStop);
window.removeEventListener('touchmove', this.onDrag);
window.removeEventListener('mouseup', this.onDragStop);
window.removeEventListener('touchend', this.onDragStop);
this.lockMapClicks(false);
@ -207,6 +218,7 @@ export class Sticker {
marker: Marker;
isDragging: boolean = false;
direction: string;
editor: Editor;
text: Props['text'];
latlng: Props['latlng'];

View file

@ -3,6 +3,7 @@ import { IStickerDump, Sticker } from '$modules/Sticker';
import { MarkerClusterGroup } from 'leaflet.markercluster/dist/leaflet.markercluster-src.js';
import { clusterIcon } from '$utils/clusterIcon';
import { editor, Editor } from "$modules/Editor";
import { STICKERS } from "$constants/stickers";
export interface ILatLng {
lat: number,
@ -25,7 +26,7 @@ export class Stickers {
this.clusterLayer.addTo(map);
this.clusterLayer.on('animationend', this.onSpiderify);
// this.clusterLayer.on('animationend', this.onSpiderify);
this.lockMapClicks = lockMapClicks;
this.stickers = [];
@ -36,6 +37,9 @@ export class Stickers {
createSticker = ({
latlng, sticker, angle = 2.2, text = '', set
}: IStickerDump): void => {
if (!STICKERS[set] || !STICKERS[set].layers || !STICKERS[set].layers[sticker]) return;
const marker = new Sticker({
latlng,
angle,
@ -46,6 +50,7 @@ export class Stickers {
set,
triggerOnChange: this.triggerOnChange,
text,
editor: this.editor,
});
this.stickers.push(marker);
@ -76,14 +81,15 @@ export class Stickers {
dumpData = (): Array<IStickerDump> => this.stickers.map(sticker => sticker.dumpData());
onSpiderify = (): void => {
// todo: it has markers passed as argument. Update only them.
if (this.editor.getEditing()) {
this.startEditing();
} else {
this.stopEditing();
}
};
// onSpiderify = (): void => {
// console.log('spider?');
// // todo: it has markers passed as argument. Update only them.
// if (this.editor.getEditing()) {
// this.startEditing();
// } else {
// this.stopEditing();
// }
// };
startEditing = (): void => {
this.stickers.map(sticker => sticker.startEditing());

View file

@ -68,3 +68,6 @@ export const setIsEmpty = is_empty => ({ type: ACTIONS.SET_IS_EMPTY, is_empty })
export const mapsLoadMore = () => ({ type: ACTIONS.MAPS_LOAD_MORE });
export const mapsSetShift = (shift: number) => ({ type: ACTIONS.MAPS_SET_SHIFT, shift });
export const setFeature = (features: { [x: string]: boolean }) => ({ type: ACTIONS.SET_FEATURE, features });
export const setIsRouting = (is_routing: boolean) => ({ type: ACTIONS.SET_IS_ROUTING, is_routing });

View file

@ -75,4 +75,7 @@ export const ACTIONS: IActions = {
MAPS_LOAD_MORE: 'MAPS_LOAD_MORE',
MAPS_SET_SHIFT: 'MAPS_SET_SHIFT',
SET_FEATURE: 'SET_FEATURE',
SET_IS_ROUTING: 'SET_IS_ROUTING',
};

View file

@ -33,9 +33,11 @@ export interface IRootReducer {
address_origin: string,
changed: boolean,
provider: keyof typeof PROVIDERS,
is_public: boolean,
markers_shown: boolean,
is_public: boolean,
is_empty: boolean,
is_routing: boolean,
save_error: string,
save_finished: boolean,
@ -46,6 +48,10 @@ export interface IRootReducer {
dialog: IDialogs[keyof IDialogs],
dialog_active: boolean,
features: {
routing: boolean,
},
renderer: {
data: string,
width: number,
@ -285,6 +291,19 @@ const mapsSetShift: ActionHandler<typeof ActionCreators.mapsSetShift> = (state,
}
});
const setFeature: ActionHandler<typeof ActionCreators.setFeature> = (state, { features }) => ({
...state,
features: {
...state.features,
...features,
}
});
const setIsRouting: ActionHandler<typeof ActionCreators.setIsRouting> = (state, { is_routing }) => ({
...state,
is_routing,
});
const HANDLERS = ({
[ACTIONS.SET_USER]: setUser,
[ACTIONS.SET_EDITING]: setEditing,
@ -327,12 +346,14 @@ const HANDLERS = ({
[ACTIONS.SET_IS_EMPTY]: setIsEmpty,
[ACTIONS.MAPS_SET_SHIFT]: mapsSetShift,
[ACTIONS.SET_FEATURE]: setFeature,
[ACTIONS.SET_IS_ROUTING]: setIsRouting,
});
export const INITIAL_STATE: IRootReducer = {
ready: false,
user: { ...DEFAULT_USER },
editing: false,
mode: MODES.NONE,
logo: DEFAULT_LOGO,
routerPoints: 0,
@ -343,11 +364,14 @@ export const INITIAL_STATE: IRootReducer = {
title: '',
address: '',
address_origin: '',
changed: false,
provider: DEFAULT_PROVIDER,
is_public: false,
markers_shown: true,
changed: false,
editing: false,
is_public: false,
is_empty: true,
is_routing: false,
save_error: '',
save_finished: false,
@ -358,6 +382,10 @@ export const INITIAL_STATE: IRootReducer = {
dialog: DIALOGS.NONE,
dialog_active: false,
features: {
routing: false,
},
renderer: {
data: '',
width: 0,

View file

@ -2,7 +2,7 @@ import { REHYDRATE } from 'redux-persist';
import { delay, SagaIterator } from 'redux-saga';
import { takeLatest, select, call, put, takeEvery, race, take } from 'redux-saga/effects';
import {
checkIframeToken,
checkIframeToken, checkOSRMService,
checkUserToken,
getGuestToken, getRouteList,
getStoredMap,
@ -32,7 +32,7 @@ import {
setProvider,
changeProvider,
setSaveLoading,
mapsSetShift, searchChangeDistance, clearAll,
mapsSetShift, searchChangeDistance, clearAll, setFeature,
} from '$redux/user/actions';
import { getUrlData, parseQuery, pushLoaderState, pushNetworkInitError, pushPath, replacePath } from '$utils/history';
import { editor } from '$modules/Editor';
@ -85,7 +85,7 @@ function* startEmptyEditorSaga() {
yield put(setChanged(false));
yield put(setEditing(true));
return hideLoader();
return yield call(setReadySaga);
}
function* startEditingSaga() {
@ -136,6 +136,21 @@ function* replaceAddressIfItsBusy(destination, original) {
pushPath(`/${destination}/edit`);
}
function* checkOSRMServiceSaga() {
const north_east = editor.map.map.getBounds().getNorthEast();
const south_west = editor.map.map.getBounds().getSouthWest();
const routing = yield call(checkOSRMService, [north_east, south_west]);
yield put(setFeature({ routing }));
}
function* setReadySaga() {
yield put(setReady(true));
hideLoader();
yield call(checkOSRMServiceSaga);
}
function* mapInitSaga() {
pushLoaderState(90);
@ -150,9 +165,7 @@ function* mapInitSaga() {
if (newUrl) {
yield pushPath(`/${newUrl}`);
yield put(setReady(true));
hideLoader();
return;
return yield call(setReadySaga);
}
}
@ -162,7 +175,7 @@ function* mapInitSaga() {
if (map) {
if (mode && mode === 'edit') {
if (map && map.owner && mode === 'edit' && map.owner.id !== id) {
hideLoader();
yield call(setReadySaga);
yield call(replaceAddressIfItsBusy, map.random_url, map.address);
} else {
yield put(setAddressOrigin(''));
@ -175,8 +188,7 @@ function* mapInitSaga() {
editor.stopEditing();
}
yield put(setReady(true));
hideLoader();
yield call(setReadySaga);
return true;
}
}

View file

@ -40,13 +40,31 @@
}
}
.leaflet-vertex-icon, .leaflet-middle-icon {
.vertex-icon-mixin(@left, @right) {
&::after {
content: ' ';
position:absolute;
top:4px;
left: @left;
right: @right;
width:8px;
height:8px;
background: white;
border-radius: 8px;
transform:scale(1);
transition:transform 150ms;
}
}
.leaflet-vertex-icon {
outline: none !important;
border-radius: 10px;
opacity: 1;
border: none;
width: 16px !important;
height: 16px !important;margin-left:-8px !important;margin-top:-8px !important;
height: 16px !important;
margin-left:-8px !important;
margin-top:-8px !important;
background: transparent;
position: absolute;
cursor: grab;
@ -72,19 +90,7 @@
}
}
&::after {
content: ' ';
position:absolute;
top:4px;
left:4px;
width:8px;
height:8px;
background: white;
box-shadow: @red_secondary 0 0 0 3px;
border-radius: 8px;
transform:scale(1);
transition:transform 150ms;
}
.vertex-icon-mixin(4px, auto);
&:hover {
opacity: 1;
@ -99,6 +105,54 @@
height: 48px;
}
.leaflet-km-marker, .leaflet-km-marker-2 {
position: absolute;
z-index: 0 !important;
.leaflet-km-dist {
background: @red_secondary;
color: white;
border-radius: 4px;
font-size: 12px;
text-align: center;
min-width: 20px;
height: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
top: 50%;
left: 50%;
font-weight: bold;
padding: 0 2px;
}
}
.end-marker {
.leaflet-km-dist {
left: auto;
right: -3px;
top: -3px;
position: absolute;
z-index: -10;
padding: 2px 16px 2px 4px;
}
&.right {
.leaflet-km-dist {
padding: 2px 4px 2px 16px;
left: -3px;
right: auto;
}
}
}
.leaflet-km-marker-2 {
.leaflet-km-dist {
background: green;
}
}
.touch-hinter-poly {
stroke: rgba(255, 50, 0, 0.1);
cursor: grab;

View file

@ -33,6 +33,9 @@
opacity: 0;
touch-action: none;
pointer-events: none;
text-transform: uppercase;
font-size: 1.2em;
color: fade(white, 70%);
svg {
fill: white;

View file

@ -1,782 +0,0 @@
import L from 'leaflet';
const EditablePolyline = L.Polyline.polylineEditor = L.Polyline.extend({
_prepareMapIfNeeded() {
const that = this;
that._changed = false;
if (this._map._editablePolylines != null) {
return;
}
// Container for all editable polylines on this map:
this._map._editablePolylines = [];
this._map._editablePolylinesEnabled = true;
// Click anywhere on map to add a new point-polyline:
if (this._options && this._options.newPolylines) {
that._map.on('dblclick', (event) => {
// console.log(`click, target=${event.target == that._map} type=${event.type}`);
if (that._map.isEditablePolylinesBusy()) { return; }
const latLng = event.latlng;
// if (that._options.newPolylineConfirmMessage) {
// if (!confirm(that._options.newPolylineConfirmMessage)) { return; }
// }
const contexts = [{ originalPolylineNo: null, originalPointNo: null }];
L.Polyline.PolylineEditor([latLng], that._options, contexts).addTo(that._map);
that._showBoundMarkers();
that._changed = true;
});
}
this.constrLineStyle = {
dashArray: '2,10',
weight: 4,
color: 'red',
opacity: 0.5,
};
/**
* Check if there is *any* busy editable polyline on this map.
*/
this._map.isEditablePolylinesBusy = () => this._editablePolylines.some(el => el._isBusy());
// {
// for (let i = 0; i < this._editablePolylines.length; i += 1) {
// if (this._editablePolylines[i]._isBusy()) { return true; }
// }
//
// return false;
// };
/**
* Enable/disable editing.
*/
this._map.setEditablePolylinesEnabled = enabled => {
// const map = this;
this._editablePolylinesEnabled = enabled;
for (let i = 0; i < this._map._editablePolylines.length; i += 1) {
const polyline = this._map._editablePolylines[i];
if (enabled) {
polyline._showBoundMarkers();
} else {
// polyline._hideAll();
this._hideAllMarkers();
}
}
};
this.editor = {
enable: () => {
this._map.setEditablePolylinesEnabled(true);
},
disable: () => {
this._map.setEditablePolylinesEnabled(false);
},
continueForward: () => {
if (this.getLatLngs().length === 0) {
return this._map.on('click', this._addFirstPoint);
}
if (!this._editablePolylinesEnabled) {
this._map.setEditablePolylinesEnabled(true);
}
this._prepareForNewPoint(
this._markers[this._markers.length - 1],
this._markers.length,
);
return;
},
stopDrawing: () => {
this._clearDragLines();
this._map.off('click', this._addPointForward);
this._map.off('click', this._addFirstPoint);
if (this._markers.length <= 1) this.editor.clear();
},
reset: () => {
const latlngs = this.getLatLngs();
this._markers = [];
for (let i = 0; i < latlngs.length; i += 1) {
this._addMarkers(i, latlngs[i]);
}
this._reloadPolyline();
},
clear: () => {
this.setPoints([]);
}
};
/*
* Utility method added to this map to retreive editable
* polylines.
*/
this._map.getEditablePolylines = () => this._editablePolylines;
this._map.fixAroundEditablePoint = marker => {
for (let i = 0; i < this._editablePolylines.length; i += 1) {
const polyline = this._editablePolylines[i];
polyline._reloadPolyline(marker);
}
};
},
constr: {},
/**
* Will add all needed methods to this polyline.
*/
_addMethods() {
const that = this;
this._init = (options, contexts) => {
this._prepareMapIfNeeded();
/**
* Since all point editing is done by marker events, markers
* will be the main holder of the polyline points locations.
* Every marker contains a reference to the newPointMarker
* *before* him (=> the first marker has newPointMarker=null).
*/
this._parseOptions(options);
this._markers = [];
const points = this.getLatLngs();
for (let i = 0; i < points.length; i += 1) {
const marker = this._addMarkers(i, points[i]);
if (!('context' in marker)) {
marker.context = {};
if (that._contexts != null) {
marker.context = contexts[i];
}
}
if (marker.context && !('originalPointNo' in marker.context)) { marker.context.originalPointNo = i; }
if (marker.context && !('originalPolylineNo' in marker.context)) { marker.context.originalPolylineNo = that._map._editablePolylines.length; }
}
// Map move => show different editable markers:
this._map.on('zoomend', this._showBoundMarkers);
this._map.on('moveend', this._showBoundMarkers);
if (this._desiredPolylineNo && this._desiredPolylineNo != null) {
this._map._editablePolylines.splice(this._desiredPolylineNo, 0, this);
} else {
this._map._editablePolylines.push(this);
}
};
// this.setLatLngs = latlngs => {
//
// };
/**
* Check if is busy adding/moving new nodes. Note, there may be
* *other* editable polylines on the same map which *are* busy.
*/
this._isBusy = function () {
return that._busy;
};
this._setBusy = function (busy) {
that._busy = busy;
};
/**
* Get markers for this polyline.
*/
this.getPoints = () => this._markers;
this.isChanged = () => this._changed;
this._parseOptions = (original = {}) => {
const options = { ...original };
// Do not show edit markers if more than maxMarkers would be shown:
if (!('maxMarkers' in original)) { options.maxMarkers = 100; }
if (!('newPolylines' in original)) { options.newPolylines = false; }
if (!('newPolylineConfirmMessage' in options)) { options.newPolylineConfirmMessage = ''; }
if (!('addFirstLastPointEvent' in options)) { options.addFirstLastPointEvent = 'click'; }
if (!('customPointListeners' in options)) { options.customPointListeners = {}; }
if (!('customNewPointListeners' in options)) { options.customNewPointListeners = {}; }
this._options = options;
// Icons:
if (!options.pointIcon) {
this._options.pointIcon = L.divIcon({
className: 'leaflet-vertex-icon',
iconSize: [11, 11],
iconAnchor: [6, 6]
});
}
// this._options.pointIcon = L.icon({
// iconUrl: '../static/img/editmarker.png', iconSize: [11, 11], iconAnchor: [6, 6]
// });
if (!options.newPointIcon) {
this._options.newPointIcon = L.divIcon({
className: 'leaflet-middle-icon',
iconSize: [11, 11],
iconAnchor: [6, 6]
});
}
// this._options.newPointIcon = L.icon({
// iconUrl: '../static/img/editmarker2.png', iconSize: [11, 11], iconAnchor: [6, 6]
// });
};
/**
* Show only markers in current map bounds *is* there are only a certain
* number of markers. This method is called on eventy that change map
* bounds.
*/
this._showBoundMarkers = () => {
if (!this._map) return;
this._setBusy(false);
if (!this._editablePolylinesEnabled) return;
const bounds = this._map.getBounds();
let found = 0;
// todo: optimise this FUCK THIS
for (let polylineNo in this._map._editablePolylines) {
const polyline = this._map._editablePolylines[polylineNo];
for (let markerNo in polyline._markers) {
const marker = polyline._markers[markerNo];
if (bounds.contains(marker.getLatLng())) { found += 1; }
}
}
if (found < that._options.maxMarkers) {
if (this._options.onMarkersShow) this._options.onMarkersShow();
} else {
if (this._options.onMarkersHide) this._options.onMarkersHide();
}
// todo: optimise this
for (const polylineNo in that._map._editablePolylines) {
const polyline = that._map._editablePolylines[polylineNo];
for (const markerNo in polyline._markers) {
const marker = polyline._markers[markerNo];
if (found < that._options.maxMarkers) {
that._setMarkerVisible(marker, bounds.contains(marker.getLatLng()));
that._setMarkerVisible(marker.newPointMarker, markerNo > 0 && bounds.contains(marker.getLatLng()));
} else {
that._setMarkerVisible(marker, false);
that._setMarkerVisible(marker.newPointMarker, false);
}
}
}
};
this._hideAllMarkers = () => {
this._markers.map(marker => {
if (marker.newPointMarker) {
this._map.removeLayer(marker.newPointMarker);
marker.newPointMarker._visible = false;
}
marker._visible = false;
this._map.removeLayer(marker);
});
};
/**
* Used when adding/moving points in order to disable the user to mess
* with other markers (+ easier to decide where to put the point
* without too many markers).
*/
this._hideAll = (except) => {
this._setBusy(true);
for (const polylineNo in that._map._editablePolylines) {
const polyline = that._map._editablePolylines[polylineNo];
for (const markerNo in polyline._markers) {
const marker = polyline._markers[markerNo];
if (except == null || except !== marker) {
polyline._setMarkerVisible(marker, false);
}
if (except == null || except !== marker.newPointMarker) {
polyline._setMarkerVisible(marker.newPointMarker, false);
}
}
}
};
/**
* Show/hide marker.
*/
this._setMarkerVisible = (marker, show) => {
if (!marker) { return; }
const map = this._map;
if (show) {
// if (!marker._visible) {
if (!marker._map) { // First show for this marker:
marker.addTo(map);
} else { // Marker was already shown and hidden:
map.addLayer(marker);
}
marker._map = map;
// }
marker._visible = true;
} else {
// if (marker._visible) {
map.removeLayer(marker);
// }
marker._visible = false;
}
};
this.setPoints = latlngs => {
this.setLatLngs(latlngs);
this._hideAllMarkers();
this._markers = [];
for (let i = 0; i < latlngs.length; i += 1) {
this._addMarkers(i, latlngs[i]);
}
this._reloadPolyline();
if (this._options.onPointsSet) this._options.onPointsSet(latlngs);
};
/**
* Reload polyline. If it is busy, then the bound markers will not be
* shown.
*/
this._reloadPolyline = fixAroundPointNo => {
this.setLatLngs(this._getMarkerLatLngs());
if (fixAroundPointNo != null) this._fixAround(fixAroundPointNo);
if (this._editablePolylinesEnabled) {
this._showBoundMarkers();
} else {
this._hideAllMarkers();
}
this._changed = true;
};
this._onMarkerDrag = (event) => {
// if (this.constr.is_drawing) {
// this._map.off('click', this._addFirstPoint);
// this._map.off('click', this._addPointForward);
// }
if (this._options.onMarkerDragStart) this._options.onMarkerDragStart(event);
if (this.constr.line1) that._map.removeLayer(this.constr.line1);
if (this.constr.line2) that._map.removeLayer(this.constr.line2);
const marker = event.target;
const point = that._getPointNo(event.target);
const previousPoint = point && point > 0 ? that._markers[point - 1].getLatLng() : null;
const nextPoint = point < that._markers.length - 1 ? that._markers[point + 1].getLatLng() : null;
this._setupDragLines(marker, previousPoint, nextPoint);
this._hideAll(marker);
};
this._onMarkerDrop = event => {
const point = that._getPointNo(event.target);
// setTimeout(() => {
this._reloadPolyline(point);
if (this._options.onMarkerDragEnd) this._options.onMarkerDragEnd(event);
//
// if (this.constr.is_drawing) {
// setTimeout(this._prepareForNewPoint.bind(this), 25);
// }
// }, 25);
};
/**
* Add two markers (a point marker and his newPointMarker) for a
* single point.
*
* Markers are not added on the map here, the marker.addTo(map) is called
* only later when needed first time because of performance issues.
*/
this._addMarkers = (pointNo, latLng, fixNeighbourPositions) => {
const points = this.getLatLngs();
const marker = L.marker(latLng, { draggable: true, icon: this._options.pointIcon });
marker.newPointMarker = null;
marker.on('dragstart', this._onMarkerDrag);
marker.on('dragend', this._onMarkerDrop);
marker.on('contextmenu', (event) => {
const _marker = event.target;
const _pointNo = this._getPointNo(event.target);
if (!this._markers || this._markers.length <= 2) return;
if (this.constr.is_drawing) return;
if (_marker.newPointMarker) {
this._map.removeLayer(_marker.newPointMarker);
}
this._map.removeLayer(_marker);
this._markers.splice(_pointNo, 1);
this._reloadPolyline(_pointNo);
if (this._options.onPointDropped) this._options.onPointDropped(event, 'dropped');
});
// marker.on(that._options.addFirstLastPointEvent, (event) => {
// console.log('click on marker');
// const point = that._getPointNo(event.target);
// console.log(`pointNo=${point} that._markers.length=${that._markers.length}`);
//
// if (pointNo === 0 || pointNo === that._markers.length - 1) {
// console.log('first or last');
// that._prepareForNewPoint(event.target, point === 0 ? 0 : point + 1);
// } else {
// console.log('not first or last');
// }
// });
// User-defined custom event listeners:
if (that._options.customPointListeners) {
for (let eventName in that._options.customPointListeners) {
marker.on(eventName, that._options.customPointListeners[eventName]);
}
}
if (that._options.customNewPointListeners) {
for (let eventName in that._options.customNewPointListeners) {
newPointMarker.on(eventName, that._options.customNewPointListeners[eventName]);
}
}
// exit if its first marker
if (!this._markers || this._markers.length === 0 || points.length === 0) {
this._markers.push(marker);
return marker;
}
const previousPoint = points[pointNo === 0 ? pointNo : pointNo - 1];
const newPointMarker = L.marker(
[
(latLng.lat + previousPoint.lat) / 2.0,
(latLng.lng + previousPoint.lng) / 2.0
],
{
draggable: true,
icon: this._options.newPointIcon
}
);
marker.newPointMarker = newPointMarker;
newPointMarker.on('dragstart', (event) => {
this._clearDragLines();
const point = that._getPointNo(event.target);
const previous = that._markers[point - 1].getLatLng();
const next = that._markers[point].getLatLng();
this._setupDragLines(marker.newPointMarker, previous, next);
this._hideAll(marker.newPointMarker);
});
newPointMarker.on('dragend', (event) => {
const marker = event.target;
const pointNo = that._getPointNo(event.target);
this._addMarkers(pointNo, marker.getLatLng(), true);
// setTimeout(() => {
this._reloadPolyline();
if (this._options.onPointAdded) this._options.onPointAdded(event, 'added');
// }, 25);
});
// newPointMarker.on('contextmenu', (event) => {
// // 1. Remove this polyline from map
// var marker = event.target;
// const pointNo = that._getPointNo(marker);
// const markers = that.getPoints();
// that._hideAll();
//
// const secondPartMarkers = that._markers.slice(pointNo, pointNo.length);
// that._markers.splice(pointNo, that._markers.length - pointNo);
//
// that._reloadPolyline();
//
// const points = [];
// const contexts = [];
// for (let i = 0; i < secondPartMarkers.length; i += 1) {
// const item = secondPartMarkers[i];
// points.push(item.getLatLng());
// contexts.push(item.context);
// }
//
// // Need to know the current polyline order numbers, because
// // the splitted one need to be inserted immediately after:
// const originalPolylineNo = that._map._editablePolylines.indexOf(that);
//
// L.Polyline.PolylineEditor(points, that._options, contexts, originalPolylineNo + 1)
// .addTo(that._map);
//
// that._showBoundMarkers();
// });
this._markers.splice(pointNo, 0, marker);
if (fixNeighbourPositions) {
this._fixAround(pointNo);
}
return marker;
};
//
// this._addNewPoint = pointNo => event => {
// // if (that._markers.length === 1) {
// // pointNo += 1;
// // }
// //
// that._addMarkers(pointNo, event.latlng, true);
// that._reloadPolyline();
//
// if (pointNo === 0) {
// this._prepareForNewPoint(this._markers[0], 0);
// } else {
// this._prepareForNewPoint(this._markers[this._markers.length - 1], (this._markers.length));
// }
// };
this._addFirstPoint = event => {
this._addMarkers(0, event.latlng, true);
this._map.setEditablePolylinesEnabled(true);
this._reloadPolyline();
this._map.off('click', this._addFirstPoint);
this._prepareForNewPoint(this._markers[0], 0);
};
this._addPointForward = event => {
if (event.sourceTarget && typeof event.sourceTarget._moved === 'boolean' && event.sourceTarget._moved) {
// prevent from adding markers after drag
this._map.off('click', this._addPointForward);
this._clearDragLines();
setTimeout(this._prepareForNewPoint.bind(this), 25);
return;
}
const pointNo = (this._markers && this._markers.length) || 0;
this._addMarkers(pointNo, event.latlng, true);
this._reloadPolyline();
this._map.off('click', this._addPointForward);
if (this._options.onPointAdded) this._options.onPointAdded(event);
setTimeout(this._prepareForNewPoint.bind(this), 25);
};
/**
* Event handlers for first and last point.
*/
this._prepareForNewPoint = target => {
if (this._options.onContinueDrawing) this._options.onContinueDrawing();
const marker = target || this._markers[this._markers.length - 1];
this._setupDragLines(marker, marker.getLatLng());
this._map.on('click', this._addPointForward);
};
/**
* Fix nearby new point markers when the new point is created.
*/
this._fixAround = pointNoOrMarker => {
const pointNo = (typeof pointNoOrMarker) === 'number'
? pointNoOrMarker
: this._markers.indexOf(pointNoOrMarker);
if (pointNo < 0) { return; }
const previousMarker = pointNo === 0 ? null : this._markers[pointNo - 1];
const marker = this._markers[pointNo];
const nextMarker = pointNo < that._markers.length - 1 ? this._markers[pointNo + 1] : null;
if (marker && previousMarker) {
marker.newPointMarker.setLatLng([
(previousMarker.getLatLng().lat + marker.getLatLng().lat) / 2.0,
(previousMarker.getLatLng().lng + marker.getLatLng().lng) / 2.0
]);
}
if (marker && nextMarker) {
nextMarker.newPointMarker.setLatLng([
(marker.getLatLng().lat + nextMarker.getLatLng().lat) / 2.0,
(marker.getLatLng().lng + nextMarker.getLatLng().lng) / 2.0
]);
}
};
/**
* Find the order number of the marker.
*/
this._getPointNo = (marker) => {
for (let i = 0; i < this._markers.length; i += 1) {
if (marker === this._markers[i] || marker === this._markers[i].newPointMarker) {
return i;
}
}
return -1;
};
/**
* Get polyline latLngs based on marker positions.
*/
this._getMarkerLatLngs = () => this._markers.map(marker => marker.getLatLng());
this._moveDragLines = event => {
if (this.constr.line1) { this.constr.line1.setLatLngs([event.latlng, this.constr.point1]); }
if (this.constr.line2) { this.constr.line2.setLatLngs([event.latlng, this.constr.point2]); }
};
this._clearDragLines = event => {
if (that._map && this.constr.is_drawing) {
if (this.constr.line1) that._map.removeLayer(this.constr.line1);
if (this.constr.line2) that._map.removeLayer(this.constr.line2);
that._map.off('mousemove', this._moveDragLines);
that._map.off('click', this._clearDragLines);
this.constr.marker.off('click', this._clearDragLines);
this.constr.marker.off('dragend', this._clearDragLines);
this.constr.is_drawing = false;
if (event && event.target !== that._map) {
that._map.fire('click', event);
}
}
};
this._setupDragLines = (marker, point1, point2) => {
this.constr.line1 = null;
this.constr.line2 = null;
this.constr.point1 = point1;
this.constr.point2 = point2;
this.constr.marker = marker;
this.constr.is_drawing = true;
if (point1) {
this.constr.line1 = L.polyline([marker.getLatLng(), this.constr.point1], this.constrLineStyle)
.addTo(that._map);
}
if (point2) {
this.constr.line2 = L.polyline([marker.getLatLng(), this.constr.point2], this.constrLineStyle)
.addTo(that._map);
}
that._map.on('mousemove', this._moveDragLines);
that._map.on('click', this._clearDragLines);
this.constr.marker.on('dragend', this._clearDragLines);
this.constr.marker.on('click', this._clearDragLines);
if (this.constr.line1) this.constr.line1.on('click', this._clearDragLines);
if (this.constr.line2) this.constr.line2.on('click', this._clearDragLines);
};
}
});
L.Polyline.polylineEditor.addInitHook(function () {
this.on('add', function (event) {
this._map = event.target._map;
this._addMethods();
/**
* When addint a new point we must disable the user to mess with other
* markers. One way is to check everywhere if the user is busy. The
* other is to just remove other markers when the user is doing
* somethinng.
*
* TODO: Decide the right way to do this and then leave only _busy or
* _hideAll().
*/
this._busy = false;
this._initialized = false;
this._init(this._options, this._contexts);
this._initialized = true;
return this;
});
this.on('remove', (event) => {
const polyline = event.target;
const map = polyline._map;
const polylines = map.getEditablePolylines();
const index = polylines.indexOf(polyline);
if (index > -1) {
polylines[index]._markers.forEach((marker) => {
map.removeLayer(marker);
if (marker.newPointMarker) { map.removeLayer(marker.newPointMarker); }
});
polylines.splice(index, 1);
}
});
});
/**
* Construct a new editable polyline.
*
* latlngs ... a list of points (or two-element tuples with coordinates)
* options ... polyline options
* contexts ... custom contexts for every point in the polyline. Must have the
* same number of elements as latlngs and this data will be
* preserved when new points are added or polylines splitted.
* polylineNo ... insert this polyline in a specific order (used when splitting).
*
* More about contexts:
* This is an array of objects that will be kept as "context" for every
* point. Marker will keep this value as marker.context. New markers will
* have context set to null.
*
* Contexts must be the same size as the polyline size!
*
* By default, even without calling this method -- every marker will have
* context with one value: marker.context.originalPointNo with the
* original order number of this point. The order may change if some
* markers before this one are delted or new added.
*/
L.Polyline.PolylineEditor = (latlngs, options, contexts, polylineNo) => {
// Since the app code may not be able to explicitly call the
// initialization of all editable polylines (if the user created a new
// one by splitting an existing), with this method you can control the
// options for new polylines:
if (options.prepareOptions) {
options.prepareOptions(options);
}
const result = new L.Polyline.polylineEditor(latlngs, options);
result._options = options;
result._contexts = contexts;
result._desiredPolylineNo = polylineNo;
return result;
};
export { EditablePolyline };

View file

@ -4,6 +4,8 @@ import { IRootState } from "$redux/user/reducer";
import { IUser } from "$constants/auth";
import { ILatLng } from "$modules/Stickers";
import { IStickerDump } from "$modules/Sticker";
import { CLIENT } from '$config/frontend';
import { LatLngLiteral } from "leaflet";
const arrayToObject = (array: any[], key: string): {} => array.reduce((obj, el) => ({ ...obj, [el[key]]: el }), {});
@ -95,3 +97,7 @@ export const getRouteList = ({
}
}).then(result => (result && result.data && result.data.success && result.data))
.catch(() => ({ list: [], min: 0, max: 0, limit: 0, step: 20, shift: 20 }));
export const checkOSRMService = (bounds: LatLngLiteral[]): Promise<boolean> => (
CLIENT && CLIENT.OSRM_URL && axios.get(CLIENT.OSRM_TEST_URL(bounds)).then(() => true).catch(() => false)
);

View file

@ -1,4 +1,4 @@
import { LatLng, LatLngLiteral, Point } from "leaflet";
import { LatLng, LatLngLiteral, Point, PointExpression } from "leaflet";
interface ILatLng {
lat: number,
@ -84,4 +84,22 @@ export const distToSegment = (A: LatLng, B: LatLng, C: LatLng): number => Math.s
// if C between A and B
export const pointBetweenPoints = (A: LatLng, B: LatLng, C: LatLng): boolean => (distToSegment(A, B, C) < 0.01);
export const angleBetweenPoints = (A: Point, B: Point): number => parseFloat(((Math.atan2(B.y - A.y, B.x - A.x))* 180 / Math.PI).toFixed(6));
export const angleBetweenPoints = (A: Point, B: Point): number => parseFloat(((Math.atan2(B.y - A.y, B.x - A.x))* 180 / Math.PI).toFixed());
export const angleBetweenPointsRad = (A: Point, B: Point): number => ((Math.atan2(B.x - A.x, B.y - A.y)));
// export const pointOnDistance = (A: Point, B: Point, shift: number): Point => {
// const c = Math.sqrt((((B.x - A.x) ** 2) + ((B.y - A.y) ** 2)));
// const angle = angleBetweenPointsRad(A, B);
//
// // console.log({ angle, c, shift });
// const x = Math.floor(B.x - c * Math.sin(angle) * shift);
// const y = Math.floor(B.y - c * Math.cos(angle) * shift);
//
// // console.log({ x, y });
//
// return new Point(x, y);
// };
export const allwaysPositiveAngleDeg = (angle: number): number => (
(angle >= -90 && angle <= 90) ? angle : (180 + angle)
);

View file

@ -142,6 +142,7 @@ module.exports = () => {
devServer: {
historyApiFallback: true,
port: 8000,
// host: '192.168.88.40',
contentBase: 'dist',
publicPath: '/',
hot: true,