UNPKG

mapbox-gl-draw-bezier-curve-mode

Version:
360 lines (316 loc) 12.6 kB
import MapboxDraw from '@mapbox/mapbox-gl-draw'; const CommonSelectors = MapboxDraw.lib.CommonSelectors; const Constants = MapboxDraw.constants; const createVertex = MapboxDraw.lib.createVertex; const doubleClickZoom = MapboxDraw.lib.doubleClickZoom; import BezierGroup from '../utils/BezierGroup'; import BezierCurve from '../utils/BezierCurve'; import BezierNode from '../utils/BezierNode'; import createBezierControlPoint from '../utils/createBezierPoint'; import createBezierHandle from '../utils/createBezierHandle'; import createBezierHandleLine from '../utils/createBezierHandleLine'; import {mirrorHandle} from '../utils/bezierUtils'; import {isAltDown} from '../utils/additional_selectors'; const DrawBezierCurve = {}; let draw=null; DrawBezierCurve.onSetup = function(opts) { opts = opts || {}; const featureId = opts.featureId; if(featureId){ console.log("option featureId is currently ignored on DrawBezierCurve"); } let line; let direction = 'forward'; const bezierGroup = new BezierGroup([new BezierCurve()]); if (opts.isPolygon === true) { bezierGroup.isPolygon = true; } line = this.newFeature(bezierGroup.geojson); this.addFeature(line); draw = this; this.clearSelectedFeatures(); doubleClickZoom.disable(this); this.updateUIClasses({ mouse: Constants.cursors.ADD }); this.activateUIButton(Constants.types.LINE); this.setActionableState({ trash: true }); const lastMouseOverVertexPath = -1; const state = { line, direction, lastMouseOverVertexPath, isPolygon: opts.isPolygon === true }; return state; }; DrawBezierCurve.clickAnywhere = function(state, e) { const bezierGroup = getBezierGroup(state); const bezierCurve = bezierGroup.bezierCurves[0]; this.updateUIClasses({ mouse: Constants.cursors.ADD }); const node1 = new BezierNode([e.lngLat.lng, e.lngLat.lat]); bezierCurve.nodes.push(node1); //if first node we prepare next node to match cursor position while its moving if(bezierCurve.nodes.length===1){ const node2 = new BezierNode([e.lngLat.lng, e.lngLat.lat]); bezierCurve.nodes.push(node2); } bezierGroup.refreshFeature(state.line); }; DrawBezierCurve.clickOnVertex = function(state, e) { const bezierGroup = getBezierGroup(state); const bezierCurve = bezierGroup.bezierCurves[0]; // In draw mode, if vertex is the first one, we want to close loop on it const isFirstVertex = (e.featureTarget.properties.coord_path === 0); if(isFirstVertex){ // Close loop bezier Curve bezierCurve.closed=true; } bezierGroup.refreshFeature(state.line); return this.changeMode(Constants.modes.SIMPLE_SELECT, { featureIds: [state.line.id] }); }; DrawBezierCurve.onMouseMove = function(state, e) { const bezierGroup = getBezierGroup(state); const bezierCurve = bezierGroup.bezierCurves[0]; // On mousemove that is not a drag, stop extended interactions. this.map.dragPan.enable(); //move next node at cursor position if(bezierCurve.nodes.length>0){ const lastNode = bezierCurve.nodes[bezierCurve.nodes.length-1]; lastNode.coords = [e.lngLat.lng, e.lngLat.lat]; bezierGroup.refreshFeature(state.line); } if (CommonSelectors.isVertex(e)) { this.updateUIClasses({ mouse: Constants.cursors.POINTER }); state.lastMouseOverVertexPath = e.featureTarget.properties.coord_path; } else { state.lastMouseOverVertexPath = -1; } }; DrawBezierCurve.onMouseDown = function(state, e) { if (isAltDown(e)) { this.map.dragPan.disable(); } }; DrawBezierCurve.onMouseUp = function(state, e) { if(state.dragging){ DrawBezierCurve.onEndDrag(state,e); } }; DrawBezierCurve.onStartDrag = function(state, e) { const bezierGroup = getBezierGroup(state); const bezierCurve = bezierGroup.bezierCurves[0]; // Check if we start dragging on the first node,that means we should close the curve. if(!bezierCurve.closed && isCurveClosable(bezierCurve) && state.lastMouseOverVertexPath === 0){ bezierCurve.closed=true; bezierCurve.nodes.pop();// delete last node as we will now edit the first node as we are closing the curve bezierGroup.refreshFeature(state.line); } state.dragging=true; //if no nodes we start a new bezier curve : so create a new node if(bezierCurve.nodes.length===0) { const lnglat = [e.lngLat.lng, e.lngLat.lat]; const node = new BezierNode(lnglat, lnglat); bezierCurve.nodes.push(node); bezierGroup.refreshFeature(state.line); } } DrawBezierCurve.onDrag = function(state, e) { if (isAltDown(e)) { if(!state.dragging){ DrawBezierCurve.onStartDrag(state,e); return; } const bezierGroup = getBezierGroup(state); const bezierCurve = bezierGroup.bezierCurves[0]; const lnglat = [e.lngLat.lng, e.lngLat.lat] if(bezierCurve.nodes.length>0){ if(!bezierCurve.closed) { const lastNode = bezierCurve.nodes[bezierCurve.nodes.length-1]; lastNode.handle = lnglat; } else { // if curve closed : we should edit the first node instead const firstNode = bezierCurve.nodes[0]; firstNode.handle = lnglat; } bezierGroup.refreshFeature(state.line); } } } DrawBezierCurve.onEndDrag = function(state,e) { state.dragging=false; const bezierGroup = getBezierGroup(state); const bezierCurve = bezierGroup.bezierCurves[0]; const lnglat = [e.lngLat.lng, e.lngLat.lat] if(!bezierCurve.closed) { // Create node at mouse pos const node = new BezierNode(lnglat); bezierCurve.nodes.push(node); bezierGroup.refreshFeature(state.line); } else { //if curve is closed we should return to simple select mode. // this mode will pop the last node so we need to add one. bezierCurve.nodes.push(new BezierNode(lnglat));// The node that will be immediately deleted by change mode > On Stop bezierGroup.refreshFeature(state.line); return draw.changeMode(Constants.modes.SIMPLE_SELECT, { featureIds: [state.line.id] }); } } DrawBezierCurve.removeLastNode = function(state,e) { const bezierGroup = getBezierGroup(state); const bezierCurve = bezierGroup.bezierCurves[0]; bezierCurve.nodes.pop(); bezierGroup.refreshFeature(state.line); } DrawBezierCurve.onTap = DrawBezierCurve.onClick = function(state, e) { // Right click or Mouse wheel click if(e.originalEvent.button===2 || e.originalEvent.button === 1) { DrawBezierCurve.removeLastNode(state,e); return; } if (CommonSelectors.isVertex(e)) return this.clickOnVertex(state, e); this.clickAnywhere(state, e); }; DrawBezierCurve.onKeyUp = function(state, e) { if (CommonSelectors.isEnterKey(e)) { this.changeMode(Constants.modes.SIMPLE_SELECT, { featureIds: [state.line.id] }); } else if (CommonSelectors.isEscapeKey(e)) { this.deleteFeature([state.line.id], { silent: true }); this.changeMode(Constants.modes.SIMPLE_SELECT); } }; DrawBezierCurve.onStop = function(state) { doubleClickZoom.enable(this); this.activateUIButton(); // check to see if we've deleted this feature if (this.getFeature(state.line.id) === undefined) return; //remove last added nodes const bezierGroup = getBezierGroup(state); const bezierCurve = bezierGroup.bezierCurves[0]; bezierCurve.removeLastNode(); // Auto-close at end of creation for polygon mode if not manually closed if (state.isPolygon === true && !bezierCurve.closed) { if (bezierCurve.nodes.length >= 3) { bezierCurve.closed = true; } } // If polygon mode and the curve is closed, convert to Polygon feature for fill styling if (state.isPolygon === true && bezierCurve.closed) { // Build ring from vertices const ring = bezierCurve.vertices.slice(); if (ring.length > 0) { const first = ring[0]; const last = ring[ring.length - 1]; if (first[0] !== last[0] || first[1] !== last[1]) { ring.push(first); } } // Persist bezierGroup on the new feature const polygonFeature = this.newFeature({ type: Constants.geojsonTypes.FEATURE, properties: { ...state.line.properties, bezierGroup: bezierGroup, user_bezierGroup: bezierGroup }, geometry: { type: Constants.geojsonTypes.POLYGON, coordinates: [ring] } }); this.addFeature(polygonFeature); // Do NOT select immediately to avoid Draw's Polygon selected-coords machinery this.setSelectedCoordinates([]); this.clearSelectedCoordinates(); this.clearSelectedFeatures && this.clearSelectedFeatures(); this.deleteFeature(state.line.id, { silent: true }); state.line = polygonFeature; } else { bezierGroup.refreshFeature(state.line); } if (state.line.isValid()) { this.map.fire(Constants.events.CREATE, { features: [state.line.toGeoJSON()] }); } else { this.deleteFeature([state.line.id], { silent: true }); this.changeMode(Constants.modes.SIMPLE_SELECT, {}, { silent: true }); } }; DrawBezierCurve.onTrash = function(state) { this.deleteFeature([state.line.id], { silent: true }); this.changeMode(Constants.modes.SIMPLE_SELECT); }; DrawBezierCurve.toDisplayFeatures = function(state, geojson, display) { const isActiveLine = geojson.properties.id === state.line.id; geojson.properties.active = (isActiveLine) ? Constants.activeStates.ACTIVE : Constants.activeStates.INACTIVE; if (!isActiveLine) return display(geojson); const bezierGroup = getBezierGroup(state); const bezierCurve = bezierGroup.bezierCurves[0]; if(state.dragging) { //if dragging : display current node & handles const node = bezierCurve.closed ? bezierCurve.nodes[0] : bezierCurve.nodes[bezierCurve.nodes.length-1]; // If curve is closed we should display the first node instead const path = bezierCurve.nodes.length-1; //display node: display(createBezierControlPoint(state.line.id, node.coords, path, false)); // Draw Handle lines if(node.handle){ if(node.handle2) { display(createBezierHandleLine(state.line.id, [node.handle, node.coords, node.handle2])); } else { display(createBezierHandleLine(state.line.id, [node.handle, mirrorHandle(node.coords, node.handle)])); } } // Draw Handles if(node.handle) { //display handle display(createBezierHandle(state.line.id, node.handle, path, false)); //display mirror handle const handle2 = mirrorHandle(node.coords, node.handle); display(createBezierHandle(state.line.id, handle2, path, false)); } } else { // Only render the line if it has at least one real coordinate if (bezierCurve.nodes.length < 2) return; const penultNode = bezierCurve.nodes[bezierCurve.nodes.length-2];//avant dernier node const path = bezierCurve.nodes.length-1; geojson.properties.meta = Constants.meta.FEATURE; display(createVertex( state.line.id, //parentId penultNode.coords,//coordinates `${path}`,//path false//selected )); } // Display first point to allow for finishing a curve into a closed loop if(!bezierCurve.closed && isCurveClosable(bezierCurve)) { const firstNode = bezierCurve.nodes[0]; const path = 0; display(createBezierControlPoint(state.line.id, firstNode.coords, path, false)); } display(geojson); }; function getBezierGroup(state) { //Ensure the state bezierGroup is also modified let bezierGroupFromProps = state.line.properties.bezierGroup; if(bezierGroupFromProps == null) return null; // recreate bezier group from itself to ensure it has the functions : Bezier Group from the props has no functions bezierGroupFromProps = new BezierGroup(bezierGroupFromProps.bezierCurves, bezierGroupFromProps.isPolygon); return bezierGroupFromProps; } function isCurveClosable(bezierCurve) { // Curve is closable if there is atleast 2 points with handles, or at least 3 points without handles // there is always 1 more node under mouse pos. so we have to count 1 more node if(bezierCurve.nodes.length<3){ return false; } if(bezierCurve.nodes.length===3){ const node1 = bezierCurve.nodes[0]; const node2 = bezierCurve.nodes[1]; if(!node1.handle && !node2.handle){ return false; } } return true; } export default DrawBezierCurve;