mapbox-gl-draw-snap-mode
Version:
Snapping mode for mapbox-gl-draw
199 lines (164 loc) • 5.8 kB
JavaScript
import MapboxDraw from "@mapbox/mapbox-gl-draw";
import {
addPointToVertices,
createSnapList,
getGuideFeature,
IDS,
shouldHideGuide,
snap,
} from "./../utils/index.js";
const { doubleClickZoom } = MapboxDraw.lib;
const { geojsonTypes, modes, cursors } = MapboxDraw.constants;
const DrawLine = MapboxDraw.modes.draw_line_string;
const SnapLineMode = { ...DrawLine };
SnapLineMode.onSetup = function (options) {
options = options || {};
const featureId = options.featureId;
var currentVertexPosition = 0;
var direction = 'forwards'
let line;
if (featureId) {
line = this.getFeature(featureId);
if (!line) {
throw new Error('Could not find a feature with the provided featureId');
}
let from = options.from;
if (from && from.type === 'Feature' && from.geometry && from.geometry.type === 'Point') {
from = from.geometry;
}
if (from && from.type === 'Point' && from.coordinates && from.coordinates.length === 2) {
from = from.coordinates;
}
if (!from || !Array.isArray(from)) {
throw new Error('Please use the `from` property to indicate which point to continue the line from');
}
const lastCoord = line.coordinates.length - 1;
if (line.coordinates[lastCoord][0] === from[0] && line.coordinates[lastCoord][1] === from[1]) {
currentVertexPosition = lastCoord + 1;
// add one new coordinate to continue from
line.addCoordinate(currentVertexPosition, ...line.coordinates[lastCoord]);
} else if (line.coordinates[0][0] === from[0] && line.coordinates[0][1] === from[1]) {
direction = 'backwards';
currentVertexPosition = 0;
// add one new coordinate to continue from
line.addCoordinate(currentVertexPosition, ...line.coordinates[0]);
} else {
throw new Error('`from` should match the point at either the start or the end of the provided LineString');
}
} else {
line = this.newFeature({
type: MapboxDraw.constants.geojsonTypes.FEATURE,
properties: {},
geometry: {
type: MapboxDraw.constants.geojsonTypes.LINE_STRING,
coordinates: []
}
});
}
const verticalGuide = this.newFeature(getGuideFeature(IDS.VERTICAL_GUIDE));
const horizontalGuide = this.newFeature(
getGuideFeature(IDS.HORIZONTAL_GUIDE)
);
this.addFeature(line);
this.addFeature(verticalGuide);
this.addFeature(horizontalGuide);
const selectedFeatures = this.getSelected();
this.clearSelectedFeatures();
doubleClickZoom.disable(this);
const [snapList, vertices] = createSnapList(
this.map,
this._ctx.api,
line,
this._ctx.options.snapOptions?.snapGetFeatures
);
const state = {
map: this.map,
line,
currentVertexPosition: currentVertexPosition,
vertices,
snapList,
selectedFeatures,
verticalGuide,
horizontalGuide,
direction: direction
};
state.options = this._ctx.options;
const moveendCallback = () => {
const [snapList, vertices] = createSnapList(
this.map,
this._ctx.api,
line,
this._ctx.options.snapOptions?.snapGetFeatures
);
state.vertices = vertices;
state.snapList = snapList;
};
// for removing listener later on close
state["moveendCallback"] = moveendCallback;
const optionsChangedCallback = (options) => {
state.options = options;
};
// for removing listener later on close
state["optionsChangedCallback"] = optionsChangedCallback;
this.map.on("moveend", moveendCallback);
this.map.on("draw.snap.options_changed", optionsChangedCallback);
return state;
};
SnapLineMode.onClick = function (state) {
// We save some processing by rounding on click, not mousemove
const lng = state.snappedLng;
const lat = state.snappedLat;
// End the drawing if this click is on the previous position
// Note: not bothering with 'direction'
if (state.currentVertexPosition > 0) {
const lastVertex = state.line.coordinates[state.currentVertexPosition - 1];
state.lastVertex = lastVertex;
if (lastVertex[0] === lng && lastVertex[1] === lat) {
return this.changeMode(modes.SIMPLE_SELECT, {
featureIds: [state.line.id],
});
}
}
// const point = state.map.project({ lng: lng, lat: lat });
addPointToVertices(state.map, state.vertices, { lng, lat });
state.line.updateCoordinate(state.currentVertexPosition, lng, lat);
state.currentVertexPosition++;
state.line.updateCoordinate(state.currentVertexPosition, lng, lat);
};
SnapLineMode.onMouseMove = function (state, e) {
const { lng, lat } = snap(state, e);
state.line.updateCoordinate(state.currentVertexPosition, lng, lat);
state.snappedLng = lng;
state.snappedLat = lat;
if (
state.lastVertex &&
state.lastVertex[0] === lng &&
state.lastVertex[1] === lat
) {
this.updateUIClasses({ mouse: cursors.POINTER });
// cursor options:
// ADD: "add"
// DRAG: "drag"
// MOVE: "move"
// NONE: "none"
// POINTER: "pointer"
} else {
this.updateUIClasses({ mouse: cursors.ADD });
}
};
// This is 'extending' DrawLine.toDisplayFeatures
SnapLineMode.toDisplayFeatures = function (state, geojson, display) {
if (shouldHideGuide(state, geojson)) return;
// This relies on the the state of SnapLineMode being similar to DrawLine
DrawLine.toDisplayFeatures(state, geojson, display);
};
// This is 'extending' DrawLine.onStop
SnapLineMode.onStop = function (state) {
this.deleteFeature(IDS.VERTICAL_GUIDE, { silent: true });
this.deleteFeature(IDS.HORIZONTAL_GUIDE, { silent: true });
// remove moveend callback
this.map.off("moveend", state.moveendCallback);
// This relies on the the state of SnapLineMode being similar to DrawLine
DrawLine.onStop.call(this, state);
};
export default SnapLineMode;