@map.ir/mapbox-gl-draw-geospatial-tools
Version:
Advanced tools for geospatial edit and analysis based on Mapbox Gl Draw
402 lines (377 loc) • 13.3 kB
JavaScript
import MapboxDraw from '@mapbox/mapbox-gl-draw';
import defaultDrawStyle from '@mapbox/mapbox-gl-draw/src/lib/theme';
import unionBy from 'lodash.unionby';
import SelectFeatureMode, { drawStyles as selectFeatureDrawStyles } from 'mapbox-gl-draw-select-mode';
import { SnapPolygonMode, SnapPointMode, SnapLineMode, SnapModeDrawStyles } from 'mapbox-gl-draw-snap-mode';
import mapboxGlDrawPinningMode from 'mapbox-gl-draw-pinning-mode';
import * as mapboxGlDrawPassingMode from 'mapbox-gl-draw-passing-mode';
import { SRMode, SRCenter, SRStyle } from 'mapbox-gl-draw-scale-rotate-mode';
import SplitPolygonMode, { drawStyles as splitPolygonDrawStyles } from 'mapbox-gl-draw-split-polygon-mode';
import CutPolygonMode, { drawStyles as cutPolygonDrawStyles } from 'mapbox-gl-draw-cut-polygon-mode';
import SplitLineMode from 'mapbox-gl-draw-split-line-mode';
import FreehandMode from 'mapbox-gl-draw-freehand-mode';
import DrawRectangle, { DrawStyles as RectRestrictStyles } from 'mapbox-gl-draw-rectangle-restrict-area';
import DrawRectangleAssisted from '@geostarters/mapbox-gl-draw-rectangle-assisted-mode';
import { additionalTools, measurement, addToolStyle } from 'mapbox-gl-draw-additional-tools';
// import MapboxCircle from 'mapbox-gl-circle';
const MapboxCircle = require('mapbox-gl-circle');
require('./index.css');
require('@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css');
class SnapOptionsToolbar {
constructor(opt) {
let ctrl = this;
ctrl.checkboxes = opt.checkboxes || [];
ctrl.onRemoveOrig = opt.draw.onRemove;
}
onAdd(map) {
let ctrl = this;
ctrl.map = map;
ctrl._container = document.createElement('div');
ctrl._container.className = 'mapboxgl-ctrl-group mapboxgl-ctrl';
ctrl.elContainer = ctrl._container;
ctrl.checkboxes.forEach((b) => {
ctrl.addCheckbox(b);
});
return ctrl._container;
}
onRemove(map) {
ctrl.checkboxes.forEach((b) => {
ctrl.removeButton(b);
});
ctrl.onRemoveOrig(map);
}
addCheckbox(opt) {
let ctrl = this;
var elCheckbox = document.createElement('input');
elCheckbox.setAttribute('type', 'checkbox');
elCheckbox.setAttribute('title', opt.title);
elCheckbox.checked = opt.initialState === 'checked';
elCheckbox.className = 'mapbox-gl-draw_ctrl-draw-btn';
if (opt.classes instanceof Array) {
opt.classes.forEach((c) => {
elCheckbox.classList.add(c);
});
}
elCheckbox.addEventListener(opt.on, opt.action);
ctrl.elContainer.appendChild(elCheckbox);
opt.elCheckbox = elCheckbox;
}
removeButton(opt) {
opt.elCheckbox.removeEventListener(opt.on, opt.action);
opt.elCheckbox.remove();
}
}
export default class MapboxDrawPro extends MapboxDraw {
constructor(options) {
options = options || {};
const { modes, styles, otherOtions } = options;
const customModes = {
// ...MapboxDraw.modes,
...CutPolygonMode(SplitPolygonMode(SelectFeatureMode(MapboxDraw.modes))),
draw_point: SnapPointMode,
draw_polygon: SnapPolygonMode,
draw_line_string: SnapLineMode,
pinning_mode: mapboxGlDrawPinningMode,
passing_mode_point: mapboxGlDrawPassingMode.passing_draw_point,
passing_mode_line_string: mapboxGlDrawPassingMode.passing_draw_line_string,
passing_mode_polygon: mapboxGlDrawPassingMode.passing_draw_polygon,
scaleRotateMode: SRMode,
splitLineMode: SplitLineMode,
freehandMode: FreehandMode,
draw_rectangle: DrawRectangle,
draw_rectangle_assisted: DrawRectangleAssisted,
};
const customOptions = {
bufferSize: 0.5,
bufferUnit: 'kilometers',
bufferSteps: 64,
snap: false,
// snapOptions: {
// snapPx: 15,
// snapToMidPoints: true,
// },
guides: false,
userProperties: true,
};
const _modes = { ...customModes, ...modes };
const __styles = [...cutPolygonDrawStyles(splitPolygonDrawStyles(selectFeatureDrawStyles(defaultDrawStyle)))];
const _styles = unionBy(__styles, styles, RectRestrictStyles, SnapModeDrawStyles, SRStyle, addToolStyle, 'id');
const _options = { modes: _modes, styles: _styles, ...customOptions, ...otherOtions };
super(_options);
this.buttons = [
{
on: 'click',
action: (map) => {
this.map.getCanvas().style.cursor = 'pointer';
this.map.once('click', (e) => {
this.map.getCanvas().style.cursor = '';
var myCircle = new MapboxCircle(e.lngLat, 250, {
editable: false,
fillColor: '#3bb2d0',
fillOpacity: 0.1,
strokeColor: '#3bb2d0',
strokeWeight: 2,
}).addTo(this.map);
myCircle.on('click', (mapMouseEvent) => {
myCircle.remove();
if (!myCircle.options.editable) {
myCircle.options.editable = true;
myCircle.options.fillColor = '#fbb03b';
myCircle.options.strokeColor = '#fbb03b';
} else {
myCircle.options.editable = false;
myCircle.options.fillColor = '#3bb2d0';
myCircle.options.strokeColor = '#3bb2d0';
}
myCircle._updateCircle(); // <-- this re-initializes internal values of the circle
myCircle.addTo(this.map);
});
myCircle.on('centerchanged', (circleObj) => {
myCircle.remove();
myCircle.options.editable = false;
myCircle.options.fillColor = '#3bb2d0';
myCircle.options.strokeColor = '#3bb2d0';
myCircle._updateCircle(); // <-- this re-initializes internal values of the circle
myCircle.addTo(this.map);
});
myCircle.on('radiuschanged', (circleObj) => {
myCircle.remove();
myCircle.options.editable = false;
myCircle.options.fillColor = '#3bb2d0';
myCircle.options.strokeColor = '#3bb2d0';
myCircle._updateCircle(); // <-- this re-initializes internal values of the circle
myCircle.addTo(this.map);
});
});
},
classes: ['draw-circle'],
title: 'Draw Circle tool',
},
{
on: 'click',
action: () => {
try {
draw.changeMode('freehandMode');
} catch (err) {
console.error(err);
}
},
classes: ['free-hand'],
title: 'Free-Hand Draw Mode tool',
},
{
on: 'click',
action: () => {
try {
draw.changeMode('draw_rectangle', {
areaLimit: parseInt(prompt('Max Area? (empty for no restriction)')), // 5 * 1_000_000, // 5 km2, optional
// escapeKeyStopsDrawing: true, // default true
// allowCreateExceeded: false, // default false
// exceedCallsOnEachMove: false, // default false
// exceedCallback: (area) => console.log('exceeded!', area), // optional
// areaChangedCallback: (area) => console.log('updated', area), // optional
});
} catch (err) {
console.error(err);
}
},
classes: ['draw-rectangle'],
title: 'Rectangle Draw Mode tool',
},
{
on: 'click',
action: () => {
try {
draw.changeMode('draw_rectangle_assisted');
} catch (err) {
console.error(err);
}
},
classes: ['draw-rectangle-assisted'],
title: 'Assisted Rectangle Draw Mode tool',
},
{
on: 'click',
action: () => {
try {
draw.changeMode('splitLineMode', {
spliter: prompt('Which Mode? (point, line_string, polygon)'),
});
} catch (err) {
alert(err.message);
console.error(err);
}
},
classes: ['split-line'],
title: 'Split Line Mode tool',
},
{
on: 'click',
action: () => {
const selectedFeatureIDs = draw.getSelectedIds();
console.log(
'🚀 ~ file: index.js ~ line 222 ~ MapboxDrawPro ~ constructor ~ selectedFeatureIDs',
selectedFeatureIDs
);
function goSplitMode(selectedFeatureIDs) {
try {
draw?.changeMode('split_polygon', {
featureIds: selectedFeatureIDs,
/** Default option vlaues: */
highlightColor: '#222',
// lineWidth: 0,
// lineWidthUnit: "kilometers",
});
} catch (err) {
console.error(err);
}
}
if (selectedFeatureIDs.length > 0) {
goSplitMode(selectedFeatureIDs);
} else {
draw.changeMode('select_feature', {
selectHighlightColor: 'yellow',
onSelect(selectedFeatureID) {
goSplitMode([selectedFeatureID]);
},
});
}
},
classes: ['split-polygon'],
title: 'Split Polygon Mode tool',
},
{
on: 'click',
action: () => {
try {
draw.changeMode('cut_polygon');
} catch (err) {
alert(err.message);
console.error(err);
}
},
classes: ['cut-polygon'],
title: 'Cut Polygon Mode tool',
},
{
on: 'click',
action: () => {
try {
draw.changeMode('scaleRotateMode', {
// required
canScale: true,
canRotate: true, // only rotation enabled
canTrash: false, // disable feature delete
rotatePivot: SRCenter.Center, // rotate around center
scaleCenter: SRCenter.Opposite, // scale around opposite vertex
singleRotationPoint: true, // only one rotation point
rotationPointRadius: 1.2, // offset rotation point
canSelectFeatures: true,
});
} catch (err) {
alert(err.message);
console.error(err);
}
},
classes: ['rotate-icon'],
title: 'Scale and Rotate Mode tool',
},
{
on: 'click',
action: () => {
draw.changeMode('pinning_mode');
},
classes: ['pinning_mode'],
title: 'Pinning Mode tool',
},
// {
// on: 'click',
// action: () => {
// draw.changeMode('passing_mode_point');
// },
// classes: ['passing_mode', 'point'],
// title: 'Passing-Point tool',
// },
// {
// on: 'click',
// action: () => {
// draw.changeMode('passing_mode_line_string', (info) => {
// console.log(info);
// });
// },
// classes: ['passing_mode', 'line'],
// title: 'Passing-LineString tool',
// },
// {
// on: 'click',
// action: () => {
// draw.changeMode('passing_mode_polygon');
// },
// classes: ['passing_mode', 'polygon'],
// title: 'Passing-Polygon tool',
// },
];
this.onAddOrig = this.onAdd;
this.onRemoveOrig = this.onRemove;
const addOtherControls = async (map, draw, placement) => {
const snapOptionsBar = new SnapOptionsToolbar({
draw,
checkboxes: [
{
on: 'change',
action: (e) => {
draw.options.snap = e.target.checked;
},
classes: ['snap_mode', 'snap'],
title: 'Snap when Draw',
initialState: 'checked',
},
{
on: 'change',
action: (e) => {
draw.options.guides = e.target.checked;
},
classes: ['snap_mode', 'grid'],
title: 'Show Guides',
},
],
});
setTimeout(() => {
map.addControl(additionalTools(draw), placement);
map.addControl(snapOptionsBar, placement);
}, 400);
};
this.onAdd = (map, placement) => {
this.map = map;
this.elContainer = this.onAddOrig(map, placement);
this.buttons.forEach((b) => {
this.addButton(b);
});
addOtherControls(map, this, placement);
return this.elContainer;
};
this.onRemove = (map) => {
this.buttons.forEach((b) => {
this.removeButton(b);
});
this.onRemoveOrig(map);
};
this.addButton = (opt) => {
var elButton = document.createElement('button');
elButton.className = 'mapbox-gl-draw_ctrl-draw-btn';
elButton.setAttribute('title', opt.title);
if (opt.classes instanceof Array) {
opt.classes.forEach((c) => {
elButton.classList.add(c);
});
}
elButton.addEventListener(opt.on, opt.action);
this.elContainer.appendChild(elButton);
opt.elButton = elButton;
};
this.removeButton = (opt) => {
opt.elButton.removeEventListener(opt.on, opt.action);
opt.elButton.remove();
};
}
}