UNPKG

mapbox-gl-draw-bezier-curve-mode

Version:

A custom mode for MapboxGL-Draw to draw and edit bezier curves.

398 lines (340 loc) 14.7 kB
import MapboxDraw from '@mapbox/mapbox-gl-draw'; import createSupplementaryPoints from '@mapbox/mapbox-gl-draw/src/lib/create_supplementary_points'; import moveFeatures from '@mapbox/mapbox-gl-draw/src/lib/move_features'; import * as Constants from '@mapbox/mapbox-gl-draw/src/constants'; import constrainFeatureMovement from '@mapbox/mapbox-gl-draw/src/lib/constrain_feature_movement'; import {isOfMetaType, isActiveFeature, isShiftDown} from '@mapbox/mapbox-gl-draw/src/lib/common_selectors'; import doubleClickZoom from '@mapbox/mapbox-gl-draw/src/lib/double_click_zoom'; // import {noTarget, , isActiveFeature, isInactiveFeature, isShiftDown} from '@mapbox/mapbox-gl-draw/src/lib/common_selectors'; import dragBezierPoints from '../utils/dragBezierPoints'; import {mirrorHandle} from '../utils/bezierUtils'; import createSupplementaryPointsForBezier from '../utils/createSupplementaryPointsForBezier'; import copyBezierGroupToClipboard from '../utils/copyBezierGroupToClipboard'; import {isAltDown, isCtrlCDown} from '../utils/additional_selectors'; import BezierGroup from '../utils/BezierGroup'; import BezierNode from '../utils/BezierNode'; const DirectModeBezierOverride = MapboxDraw.modes.direct_select; const isVertex = isOfMetaType(Constants.meta.VERTEX); const isMidpoint = isOfMetaType(Constants.meta.MIDPOINT); let draw=null; DirectModeBezierOverride.onSetup = function(opts) { const featureId = opts.featureId; const feature = this.getFeature(featureId); draw = this; if (!feature) { throw new Error('You must provide a featureId to enter direct_select mode'); } if (feature.type === Constants.geojsonTypes.POINT) { throw new TypeError('direct_select mode doesn\'t handle point features'); } const state = { featureId, feature, dragMoveLocation: opts.startPos || null, dragMoving: false, canDragMove: false, selectedCoordPaths: opts.coordPath ? [opts.coordPath] : [] }; this.setSelectedCoordinates(this.pathsToCoordinates(featureId, state.selectedCoordPaths)); this.setSelected(featureId); doubleClickZoom.disable(this); this.setActionableState({ trash: true }); return state; }; DirectModeBezierOverride.onVertex = function (state, e) { this.startDragging(state, e); const props = e.featureTarget.properties; state.handleSelected = 0; const coordPath = props.coord_path; // Bezier Point or Handle Management if(props.bezierPoint || props.bezierHandle){ const bezierGroup = getBezierGroup(state); const result = bezierGroup.getBezierCurveAndNodeFromCoordPath(coordPath); const node = result.node; if(props.bezierPoint){ if(isAltDown(e)){ if(node){ // If No Handles : Create new Handles (delayed in the drag event) if(!node.handle){ state.createNewHandles=true; } // If Handle : Delete Handles else if (node.handle || node.handle2) { node.handle = node.handle2 = null; bezierGroup.refreshFeature(state.feature); } } } // state.selectedCoordPaths = []; } if(props.bezierHandle){ state.handleSelected = (props.handleInverse ? -1 : 1); state.selectedCoordPaths = [props.coord_path]; // Break Handle Symetry with Alt if(isAltDown(e)){ state.breakHandleSymetry=true; } // Reenable handle Symetry with Shift else if (isShiftDown(e)){ if(node){ if(node.handle2){ // if handle 2 was selected, copy inverse to handle 1 if(state.handleSelected===-1){ node.handle = mirrorHandle(node.coords, node.handle2); } node.handle2=undefined; bezierGroup.refreshFeature(state.feature); } } } return; } } //Select Vertex const selectedIndex = state.selectedCoordPaths.indexOf(props.coord_path); if (!isShiftDown(e) && selectedIndex === -1) { state.selectedCoordPaths = [props.coord_path]; } else if (isShiftDown(e) && selectedIndex === -1) { state.selectedCoordPaths.push(props.coord_path); } const selectedCoordinates = this.pathsToCoordinates(state.featureId, state.selectedCoordPaths); this.setSelectedCoordinates(selectedCoordinates); }; DirectModeBezierOverride.onMidpoint = function(state, e) { const bezierGroup = getBezierGroup(state); if (bezierGroup) { this.startDragging(state, e); const props = e.featureTarget.properties; //get bezierCurve & previous node const result = bezierGroup.getBezierCurveAndNodeFromCoordPath(props.coord_path); const bezierCurve = result.bezierCurve; const bezierCurveIndex = result.bezierCurveIndex; const nodeIndex = result.nodeIndex; const newNode = new BezierNode([props.lng, props.lat]); const newCoordPath = bezierGroup.bezierCurves.length>1 ? (`${bezierCurveIndex}.${nodeIndex+1}`) : (`${nodeIndex+1}`); // insert node into nodes bezierCurve.nodes.splice(nodeIndex+1, 0, newNode); bezierGroup.refreshFeature(state.feature); this.fireUpdate(); state.selectedCoordPaths = [newCoordPath]; const selectedCoordinates = this.pathsToCoordinates(state.featureId, state.selectedCoordPaths); this.setSelectedCoordinates(selectedCoordinates); } }; DirectModeBezierOverride.dragFeature = function(state, e, delta) { moveFeatures(this.getSelected(), delta); // Move bezier control points & handles dragBezierPoints(this, delta); state.dragMoveLocation = e.lngLat; }; DirectModeBezierOverride.dragVertex = function(state, e, delta) { const bezierGroup = getBezierGroup(state); if (bezierGroup) { if(state.createNewHandles){ const coordPath = state.selectedCoordPaths[state.selectedCoordPaths.length-1] const node = bezierGroup.getBezierCurveAndNodeFromCoordPath(coordPath).node; if(node){ if(!node.handle && !node.handle2){ node.handle = [node.coords[0], node.coords[1]]; } state.createNewHandles=false; state.handleSelected=1; } } if(state.handleSelected===0) { // Move Bezier Point state.selectedCoordPaths.forEach(coordPath => { // Move Bezier Points const node = bezierGroup.getBezierCurveAndNodeFromCoordPath(coordPath).node; // Move main point only if no handle Selected (=0) node.coords[0] += delta.lng; node.coords[1] += delta.lat; if(node.handle){ node.handle[0] += delta.lng; node.handle[1] += delta.lat; } if(node.handle2){ node.handle2[0] += delta.lng; node.handle2[1] += delta.lat; } }); } else { // Move Bezier Handles (only last selected) const coordPath = state.selectedCoordPaths[state.selectedCoordPaths.length-1] const node = bezierGroup.getBezierCurveAndNodeFromCoordPath(coordPath).node; if(node){ // If createSecondaryHandle and handle2 was not defined, create the handle2 by mirroring handle 1 if(state.breakHandleSymetry && !node.handle2){ state.breakHandleSymetry = false; node.handle2 = mirrorHandle(node.coords, node.handle); } if(!node.handle2){ // If no handle2 it means that handles are linked if(node.handle){ node.handle[0] += delta.lng * state.handleSelected; node.handle[1] += delta.lat * state.handleSelected; } } else { // If handle 2 then they are unlinked, we should move them indepentely base on the handleSelected if(state.handleSelected===1){ node.handle[0] += delta.lng; node.handle[1] += delta.lat; } else if(state.handleSelected===-1){ node.handle2[0] += delta.lng; node.handle2[1] += delta.lat; } } } } bezierGroup.refreshFeature(state.feature); this.fireUpdate(); } // IF NOT A BEZIER GROUP : classic handling else { const selectedCoords = state.selectedCoordPaths.map(coord_path => state.feature.getCoordinate(coord_path)); const selectedCoordPoints = selectedCoords.map(coords => ({ type: Constants.geojsonTypes.FEATURE, properties: {}, geometry: { type: Constants.geojsonTypes.POINT, coordinates: coords } })); const constrainedDelta = constrainFeatureMovement(selectedCoordPoints, delta); for (let i = 0; i < selectedCoords.length; i++) { const coord = selectedCoords[i]; state.feature.updateCoordinate(state.selectedCoordPaths[i], coord[0] + constrainedDelta.lng, coord[1] + constrainedDelta.lat); } } }; DirectModeBezierOverride.onKeyDown = function(state, e) { if (isCtrlCDown(e)){ copyBezierGroupToClipboard(this.getSelected()); } } DirectModeBezierOverride.onTrash = function(state) { const bezierGroup = getBezierGroup(state); // Mark Nodes for deletion state.selectedCoordPaths.forEach(coordPath => { const result = bezierGroup.getBezierCurveAndNodeFromCoordPath(coordPath); const bezierCurve = result.bezierCurve; const node = result.node; bezierCurve.nodesToDelete.push(node); }); // Remove nodes bezierGroup.removeMarkedNodes(); bezierGroup.refreshFeature(state.feature); this.fireUpdate(); state.selectedCoordPaths = []; this.clearSelectedCoordinates(); this.fireActionable(state); if (state.feature.isValid() === false) { this.deleteFeature([state.featureId]); this.changeMode(Constants.modes.SIMPLE_SELECT, {}); } }; DirectModeBezierOverride.onMouseMove = function(state, e) { // On mousemove that is not a drag, stop vertex movement. const isFeature = isActiveFeature(e); const onVertex = isVertex(e); const onMidpoint = isMidpoint(e); const noCoords = state.selectedCoordPaths.length === 0; if (onMidpoint) this.updateUIClasses({ mouse: Constants.cursors.ADD }); else if (isFeature && noCoords) this.updateUIClasses({ mouse: Constants.cursors.MOVE }); else if (onVertex && !noCoords) this.updateUIClasses({ mouse: Constants.cursors.MOVE }); else this.updateUIClasses({ mouse: Constants.cursors.NONE }); this.stopDragging(state); // Skip render return true; }; DirectModeBezierOverride.onMouseOut = function(state) { // As soon as you mouse leaves the canvas, update the feature if (state.dragMoving) this.fireUpdate(); // Skip render return true; }; DirectModeBezierOverride.onCombineFeatures = function(state) { if(state.selectedCoordPaths.length===0) return; // Mark down nodes to merge for future processing const bezierGroup = getBezierGroup(state); state.selectedCoordPaths.forEach(coordPath => { const result = bezierGroup.getBezierCurveAndNodeFromCoordPath(coordPath); const bezierCurve = result.bezierCurve; const node = result.node; bezierCurve.nodesToMerge.push(node); }); // Merge nodes bezierGroup.mergeMarkedNodes(); const feature = bezierGroup.refreshFeature(state.feature, draw, true); // Force recreate feature as we may go from a multiline string to a single string state.featureId = feature.id; state.feature = feature; this.fireUpdate(); state.selectedCoordPaths = []; const selectedCoordinates = this.pathsToCoordinates(state.featureId, state.selectedCoordPaths); this.setSelectedCoordinates(selectedCoordinates); this.fireActionable(state); if (state.feature.isValid() === false) { this.deleteFeature([state.featureId]); this.changeMode(Constants.modes.SIMPLE_SELECT, {}); } this.doRender(state.featureId); } DirectModeBezierOverride.onUncombineFeatures = function(state) { if(state.selectedCoordPaths.length===0) return; // Mark down nodes to split for future processing const bezierGroup = getBezierGroup(state); state.selectedCoordPaths.forEach(coordPath => { const result = bezierGroup.getBezierCurveAndNodeFromCoordPath(coordPath); const bezierCurve = result.bezierCurve; const node = result.node; bezierCurve.nodesToSplit.push(node); }); // Split nodes bezierGroup.splitMarkedNodes(); const feature = bezierGroup.refreshFeature(state.feature, draw, true); // Force recreate feature as we may go from a multiline string to a single string state.featureId = feature.id; state.feature = feature; this.fireUpdate(); state.selectedCoordPaths = []; const selectedCoordinates = this.pathsToCoordinates(state.featureId, state.selectedCoordPaths); this.setSelectedCoordinates(selectedCoordinates); this.fireActionable(state); if (state.feature.isValid() === false) { this.deleteFeature([state.featureId]); this.changeMode(Constants.modes.SIMPLE_SELECT, {}); } this.doRender(state.featureId); } DirectModeBezierOverride.toDisplayFeatures = function (state, geojson, display) { if (state.featureId === geojson.properties.id) { geojson.properties.active = Constants.activeStates.ACTIVE; //If Bezier curve create supplementary points for bezier points instead const supplementaryPoints = geojson.properties.user_bezierGroup ? (createSupplementaryPointsForBezier(geojson, { featureId:state.featureId, midpoints: true, selectedPaths: state.selectedCoordPaths })) : createSupplementaryPoints(geojson, { map: this.map, midpoints: true, selectedPaths: state.selectedCoordPaths }); supplementaryPoints.forEach(display); display(geojson); } else { geojson.properties.active = Constants.activeStates.INACTIVE; display(geojson); } this.fireActionable(state); } function getBezierGroup(state) { //Ensure the state bezierGroup is also modified let bezierGroupFromProps = state.feature.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); return bezierGroupFromProps; } export default DirectModeBezierOverride;