mirror of
https://github.com/muerwre/orchidmap-front.git
synced 2025-04-25 02:56:41 +07:00
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:
commit
d42586d9e0
25 changed files with 556 additions and 971 deletions
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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' : ''}`);
|
||||
|
|
|
@ -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 })}
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
65
src/modules/Arrows.ts
Normal 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;
|
|
@ -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 {
|
||||
|
|
|
@ -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
156
src/modules/KmMarks.ts
Normal 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;
|
|
@ -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'];
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
|
@ -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)
|
||||
);
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
|
@ -142,6 +142,7 @@ module.exports = () => {
|
|||
devServer: {
|
||||
historyApiFallback: true,
|
||||
port: 8000,
|
||||
// host: '192.168.88.40',
|
||||
contentBase: 'dist',
|
||||
publicPath: '/',
|
||||
hot: true,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue