UNPKG

mapbox-gl-draw-bezier-curve-mode

Version:

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

233 lines (203 loc) 8.35 kB
import './App.css'; import React, { useRef, useEffect, useState } from 'react'; import mapboxgl from '!mapbox-gl'; // eslint-disable-line import/no-webpack-loader-syntax import MapboxDraw from "@mapbox/mapbox-gl-draw"; import * as turf from '@turf/turf'; import { SimpleSelectModeBezierOverride, DirectModeBezierOverride, DrawBezierCurve, rawDataToBezierGroup, customStyles, } from 'mapbox-gl-draw-bezier-curve-mode'; import {extendDrawBar} from './utils/extendDrawBar.js'; import {demoData} from './demoData.js'; import { feature } from '@turf/turf'; // Token for demo purpose only, please replace by your own mapboxgl.accessToken = 'pk.eyJ1IjoibWFwYm94LWdsLWRyYXctYmV6aWVyLWN1cnZlLWRlbW8iLCJhIjoiY2t0OXJyd2szMWV5MjJwbjlyNGtsOXVpdiJ9.Hom5aMPuxvSJUaiUynqIVA'; function App() { const mapContainer = useRef(null); const map = useRef(null); const [lng, setLng] = useState(2.2972); const [lat, setLat] = useState(48.7742); const [zoom, setZoom] = useState(19.92); const [drawMode, setDrawMode] = useState('simple_select'); const [selectionType, setSelectionType] = useState(''); useEffect(() => { if (map.current) return; // initialize map only once //////////////////////////////////////////////// // Initialize Map & Draw //////////////////////////////////////////////// map.current = new mapboxgl.Map({ container: mapContainer.current, style: 'mapbox://styles/mapbox/satellite-v9',//mapbox://styles/mapbox/streets-v11', center: [lng, lat], zoom: zoom }); var draw = new MapboxDraw({ displayControlsDefault: false, userProperties: true, modes: { ...MapboxDraw.modes, simple_select: SimpleSelectModeBezierOverride, direct_select: DirectModeBezierOverride, draw_bezier_curve: DrawBezierCurve, }, styles: customStyles }); //////////////////////////////////////////////// // Add DrawBar //////////////////////////////////////////////// // const drawLineString = {on: "click", action: () => {draw.changeMode("draw_line_string")}, classes: ["mapbox-gl-draw_line"], title:'Polygon tool'}; // const drawPolygon = {on: "click", action: () => {draw.changeMode("draw_polygon")}, classes: ["mapbox-gl-draw_polygon"], title:'LineString tool'}; const drawBezierBtn = {on: "click", action: () => {draw.changeMode("draw_bezier_curve")}, classes: ["bezier-curve-icon"], title:'Bezier tool'}; const drawPointBtn = {on: "click", action: () => {draw.changeMode("draw_point")}, classes: ["mapbox-gl-draw_point"], title:'Marker tool'}; const trashBtn = {on: "click", action: () => {draw.trash()}, classes: ["mapbox-gl-draw_trash"], title:'Delete'}; const combineBtn = {on: "click", action: () => {draw.combineFeatures()}, classes: ["mapbox-gl-draw_combine"], title:'Combine'}; const unCombineBtn = {on: "click", action: () => {draw.uncombineFeatures()}, classes: ["mapbox-gl-draw_uncombine"], title:'Uncombine'}; let drawBar = new extendDrawBar({ draw: draw, buttons: [ // drawLineString, // drawPolygon, drawBezierBtn, drawPointBtn, trashBtn, combineBtn, unCombineBtn], }); map.current.addControl(drawBar); // Fix to allow deletion with Del Key when default trash icon is not shown. See https://github.com/mapbox/mapbox-gl-draw/issues/989 draw.options.controls.trash=true; //////////////////////////////////////////////// // Map configuration & events //////////////////////////////////////////////// // Disable Rotation map.current.dragRotate.disable(); map.current.touchZoomRotate.disableRotation(); // Prevent context menu from appearing on right click window.addEventListener('contextmenu', function (e) { // do something here... eg : Show a context menu // console.log("show Context Menu"); e.preventDefault(); }, false); // Prevent firefox menu from appearing on Alt key up window.addEventListener('keyup', function (e) { if(e.key === "Alt") { e.preventDefault(); } }, false); map.current.on('move', () => { setLng(map.current.getCenter().lng.toFixed(4)); setLat(map.current.getCenter().lat.toFixed(4)); setZoom(map.current.getZoom().toFixed(2)); }); map.current.on('draw.modechange', (e) => { // console.log("Mode Change : " + e.mode); setDrawMode(e.mode); }); map.current.on('draw.selectionchange', (e) => { refreshSelectionType(e, setSelectionType); }); map.current.on('draw.update', (e) => { refreshSelectionType(e, setSelectionType); }); /////////////////////////////////////////////////// // Import some example bezier curves from demoData /////////////////////////////////////////////////// const bezierGroups = rawDataToBezierGroup(demoData); let featureId; bezierGroups.forEach(bezierGroup => { // Draw feature featureId = draw.add(bezierGroup.geojson)[0]; }); // Demo : Select last feature draw.changeMode('simple_select', { featureIds: [featureId] }) }); return ( <div> <div className="sidebar"> Longitude: {lng} | Latitude: {lat} | Zoom: {zoom}<br/> Draw mode: {drawMode}<br/> {(selectionType !== null && selectionType!=='') && <div> Selection type : {selectionType}<br/> </div> } </div> <div className="infobar"> <b>Controls</b><br/><br/> In draw mode : <br/> - <b>Alt + Drag with left click</b> to create nodes with bezier handles.<br/> - <b>Left click</b> to create nodes without handles.<br/><br/> In edit mode : <br/> - <b>Drag with Left click</b> on a handle to move it.<br/> - <b>Alt + Drag with left click</b> on a handle to break handle symetry.<br/><br/> More details : <a href="https://github.com/Jeff-Numix/mapbox-gl-draw-bezier-curve-mode">See github repo</a><br/> <br/> </div> <div ref={mapContainer} className="map-container" /> </div> ); } export default App; function refreshSelectionType(e, setSelectionType) { const features = e.features; if(features.length>0){ const feature1 = features[0]; const ftype = getMultiSelectionFeatureType(e.features); if(ftype === "Mixed") { setSelectionType(ftype); } else if(feature1.properties.bezierGroup){ const bezierGroup = feature1.properties.bezierGroup;// without functions let typeString = (bezierGroup.bezierCurves.length>1 ? `${ftype}(${bezierGroup.bezierCurves.length})` : `${ftype}`); typeString += " Length: " + getBezierLength(features); setSelectionType(typeString); } else if(feature.geometry){ setSelectionType(feature.geometry.type); } } else { setSelectionType(null); } } function getBezierLength(features){ let distKm=0 features.forEach(feature => { if(feature.geometry){ distKm += turf.length(feature.geometry, 'kilometers'); } }); if(distKm <0.001) return `${(distKm*100000).toLocaleString(undefined, { maximumFractionDigits: 2 })} cm`; if(distKm <1) return `${(distKm*1000).toLocaleString(undefined, { maximumFractionDigits: 2 })} m`; return `${(distKm).toLocaleString(undefined, { maximumFractionDigits: 2 })} km`; } function getFeatureType(feature){ if(feature.properties.bezierGroup){ const bezierGroup = feature.properties.bezierGroup; if(bezierGroup.bezierCurves.length>1){ return "MultiBezierCurve"; } else { return "BezierCurve"; } } else { return feature.geometry.type; } } function getMultiSelectionFeatureType(features) { if(features.length>0){ const feature1Type = getFeatureType(features[0]); for (let i = 1; i < features.length; i++) { const featureType = getFeatureType(features[i]); if(feature1Type!==featureType){return "Mixed"}; } return feature1Type; } else { return "None"; } }