// 闭包封装:内部所有变量和方法全部私有化! (function (window) { /** * MapDraw 独立封装类 * 引入后直接 new MapDraw(mapboxMap实例) 即可使用 */ class MapDraw { /** * 构造函数:new MapDraw() 时自动执行 * @param {mapboxgl.Map} map - 已经初始化好的 mapbox 实例 */ constructor(map, callbacks = {}) { const that = this; // 接收 map 实例 that.map = map; // 编辑完成后 → 调用这个回调把数据抛出去 that.onEditComplete = callbacks.onEditComplete || null; that.tempData = null; // 当前绘制的图形数据 that.tempSource = null; // 当前绘制的source that.tempPoints = []; // 存储绘制的点 that.drawType = ''; // 绘制类型 that.isDrawing = false; // 是否正在绘制 that.hoveredPolygonId = null; // 当前悬停的面ID that.tempLineSource = null; // 当前绘制的线的source that.itemId = ''; // 当前绘制的图形ID that.totalDistance = 0; // 总距离 that.drawMarkers = []; // 存储节点标签 that.moveMarkerTips = null; //移动节点显示提示标签 // 用一个变量防止双击触发单击 that.lastClickTime = 0; that.clickTimer = null; that.clickCount = 0; // 编辑相关状态 that.mouseDownPos = null; // 鼠标按下时的位置 that.isDragWhole = false; // 拖拽整个图形 that.lastLngLat = null; // 最后一次点击的经纬度 that.isEditMode = false; // 编辑模式 that.isEditFlag = false; // 是否正在编辑 that.selectedFeature = null; // 当前选中的图形 that.selectedFeatureId = ''; // 当前选中图形的ID that.layersIds = []; // 当前选中图形的图层ID that.editMarkers = []; // 存储编辑节点的标记 that.isDragging = false; // 是否正在拖拽 that.draggingPointIndex = -1; // 正在拖拽的节点索引 that.dragEventHandlers = null; // 拖拽事件处理器 that.dragEventDrawHandlers = null; // 拖拽事件处理器2 that.unit = 'kilometers'; // 单位 (kilometers/meters) that.unitLabel = 'km'; // 单位标签 (km/m) that.editLayerIds = []; // 当前选中图形的图层ID // 缓存事件句柄,用于精准解绑(核心防重复触发) that._mapClickHandle = null; that._mapDblClickHandle = null; that._mapMouseDownHandle = null; that._mapMouseMoveHandle = null; that._mapMouseUpHandle = null; // 加载地图绘制图层 that.loadDrawLayer(); // 绑定地图事件 that.bindMapEvents(); } // 编辑后回调返回的完整数据格式 getCompleteFeature(feature) { const that = this; // 复制一个全新的对象,和原 feature 无关 let feat = JSON.parse(JSON.stringify(feature)); delete feat.properties.drawType; delete feat.properties.isCircle; if (that.onEditComplete) { that.onEditComplete({ type: "Feature", geometry: feat.geometry, properties: feat.properties || {} }); } } // 绑定地图事件 bindMapEvents() { const that = this; // ========== 精准解绑所有旧鼠标事件,防止触发两次 ========== if(that._mapClickHandle) that.map.off('click', that._mapClickHandle); if(that._mapDblClickHandle) that.map.off('dblclick', that._mapDblClickHandle); if(that._mapMouseDownHandle) that.map.off('mousedown', that._mapMouseDownHandle); if(that._mapMouseMoveHandle) that.map.off('mousemove', that._mapMouseMoveHandle); if(that._mapMouseUpHandle) that.map.off('mouseup', that._mapMouseUpHandle); // 清空旧句柄 that._mapClickHandle = null; that._mapDblClickHandle = null; that._mapMouseDownHandle = null; that._mapMouseMoveHandle = null; that._mapMouseUpHandle = null; // 初始化编辑鼠标事件 that.initMapEditEvents(); // 单击事件【缓存句柄】 that._mapClickHandle = (e) => { that.handleFeatureClick(e); if (that.isEditMode) { const now = Date.now(); const DOUBLE_CLICK_THRESHOLD = 300; clearTimeout(that.clickTimer); if (now - that.lastClickTime > DOUBLE_CLICK_THRESHOLD) { that.clickCount = 1; that.lastClickTime = now; that.clickTimer = setTimeout(() => { if (that.clickCount === 1) { that.handleMapClick(e); } that.clickCount = 0; }, 10); } else { that.clickCount = 2; that.lastClickTime = 0; } } }; that.map.on('click', that._mapClickHandle); // 双击事件【缓存句柄】 that._mapDblClickHandle = (e) => { clearTimeout(that.clickTimer); that.clickCount = 0; that.lastClickTime = 0; that.handleMapDblClick(e); }; that.map.on('dblclick', that._mapDblClickHandle); // ========== 键盘 DEL 删除选中节点 ========== that.bindDeleteHotkey(); } initMapEditEvents() { const that = this; // 鼠标按下【缓存句柄】 that._mapMouseDownHandle = (evt) => { // console.log('[ _mapMouseDownHandle ] >', that.selectedFeature) if (!that.selectedFeature) return; if(that.selectedFeature.properties.drawType === 'distance' || that.selectedFeature.properties.drawType === 'area') return; // console.log('[ _mapMouseDownHandle ] >', evt) that.mouseDownPos = evt.point; that.lastLngLat = evt.lngLat; that.isDragging = false; evt.preventDefault(); that.map.getCanvas().style.cursor = 'grabbing'; }; that.map.on('mousedown', that._mapMouseDownHandle); // 鼠标移动【缓存句柄】 that._mapMouseMoveHandle = (evt) => { if (!that.selectedFeature) return; if ((that.isEditMode || that.isEditFlag) && that.isDragging) { that.map.getCanvas().style.cursor = 'move'; that.handleDrag(evt); } // console.log('[ _mapMouseMoveHandle ] >', that.selectedFeature) if (!that.mouseDownPos || !that.selectedFeature) return; if(that.selectedFeature.properties.drawType === 'distance' || that.selectedFeature.properties.drawType === 'area') return; that.draggingPointIndex = -1; const dx = Math.abs(evt.point.x - that.mouseDownPos.x); const dy = Math.abs(evt.point.y - that.mouseDownPos.y); if (dx > 1 || dy > 1) { that.isDragging = true; const curLngLat = evt.lngLat; const dLng = curLngLat.lng - that.lastLngLat.lng; const dLat = curLngLat.lat - that.lastLngLat.lat; that.clearEditMarkers(); that.moveWholeFeature(dLng, dLat); that.lastLngLat = curLngLat; } }; that.map.on('mousemove', that._mapMouseMoveHandle); // 鼠标抬起【缓存句柄】 that._mapMouseUpHandle = () => { if (!that.selectedFeature) return; that.mouseDownPos = null; if (that.isDragging) { that.getCompleteFeature(that.selectedFeature); if(that.selectedFeature.properties.drawType === 'distance' || that.selectedFeature.properties.drawType === 'area'){ that.clearEditMarkers(); }else{ that.showEditMarkers(); } }else{ that.clearEditMarkers(); } // console.log('[ _mapMouseUpHandle ] >', that.selectedFeature) that.isDragging = false; // that.draggingPointIndex = -1; that.tempLineSource.setData({ type: 'FeatureCollection', features: [] }); that.map.getCanvas().style.cursor = ''; // console.log('[ _mapMouseUpHandle==== ] >', that.selectedFeature) }; that.map.on('mouseup', that._mapMouseUpHandle); } // 剩余原有方法完全不变 loadDrawLayer(){ const that = this; if(!that.map.getSource('temp-line')){ that.map.addSource('temp-line', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }); that.map.addLayer({ id: 'temp-line-layer', type: 'line', source: 'temp-line', paint: { 'line-color': 'red', 'line-width': 2, 'line-dasharray': [3, 3] } }); } that.tempLineSource = that.map.getSource('temp-line'); that.map.on('mousemove', (e) => { if (!that.isDrawing) return if(that.moveMarkerTips){ that.moveMarkerTips.setLngLat(e.lngLat); }else{ let text = "鼠标双击完成"; if(that.drawType === 'circle' || that.drawType === 'rectangle'){ text = "鼠标双击/单击完成"; } that.addMoveMarkerTips(e.lngLat, text); } const end = [e.lngLat.lng, e.lngLat.lat]; if (that.drawType === 'rectangle'){ if (that.tempPoints.length < 1) return; const bbox = [ Math.min(that.tempPoints[0][0], end[0]), Math.min(that.tempPoints[0][1], end[1]), Math.max(that.tempPoints[0][0], end[0]), Math.max(that.tempPoints[0][1], end[1]) ]; that.addToMap(turf.bboxPolygon(bbox)) }else if (that.drawType === 'circle' && that.tempPoints.length >= 1){ let center = that.tempPoints[0]; let radius = turf.distance(turf.point(center), turf.point(end), { units: 'kilometers' }) that.addToMap(turf.circle(center, radius, 64)) } if (that.tempPoints.length < 1 || that.drawType === 'rectangle') return; const mousePt = [e.lngLat.lng, e.lngLat.lat]; const lastPt = that.tempPoints[that.tempPoints.length - 1]; const line = turf.lineString([lastPt, mousePt]); that.tempLineSource.setData(line); }); } startDraw(type, flag, unit) { const that = this; that.drawType = type that.isDrawing = true that.tempPoints = [] that.tempLineSource.setData({ type: 'FeatureCollection', features: [] }) that.itemId = 'draw-' + Date.now(); that.map.getCanvas().style.cursor = 'crosshair'; that.totalDistance = 0; that.unit = unit || 'kilometers'; that.unitLabel = that.unit === 'kilometers' ? 'km' : 'm'; flag = flag || true; that.isEditDrawTool(flag); } isEditDrawTool(flag) { const that = this; that.isEditMode = flag; if (!that.isEditMode) { that.clearEditMarkers(); that.selectedFeature = null; that.selectedFeatureId = ''; that.layersIds = []; } // console.log('[ isEditDrawTool ] >', that.selectedFeature) } isEdit(flag) { const that = this; that.isEditFlag = flag; if (!that.isEditFlag) { that.clearEditMarkers(); that.selectedFeature = null; that.selectedFeatureId = ''; that.layersIds = []; } // console.log('[ isEdit ] >', that.selectedFeature) } handleMapClick(e) { const that = this; if (!that.isDrawing) return const coord = [e.lngLat.lng, e.lngLat.lat] that.tempPoints.push(coord) if (that.drawType === 'point') { let feature = turf.point(coord) feature.properties.drawType = 'point' feature.properties.isCircle = false; that.addToMap(feature) that.finishDraw() } if (that.drawType === 'line' && that.tempPoints.length >= 2) { let feature = turf.lineString(that.tempPoints) feature.properties.drawType = 'line' feature.properties.isCircle = false; that.addToMap(feature) } if (that.drawType === 'polygon' && that.tempPoints.length >= 2) { if(that.tempPoints.length >= 3){ let feature = turf.polygon([[...that.tempPoints, that.tempPoints[0]]]) feature.properties.drawType = 'polygon' feature.properties.isCircle = false; that.addToMap(feature) }else{ let feature = turf.lineString(that.tempPoints) feature.properties.drawType = 'polygon' feature.properties.isCircle = false; that.addToMap(feature) } } if (that.drawType === 'circle' && that.tempPoints.length >= 2) { const center = that.tempPoints[0] const radius = turf.distance(turf.point(center), turf.point(that.tempPoints[1]), { units: that.unit }) let feature = turf.circle(center, radius, 64) feature.properties.drawType = 'circle' feature.properties.isCircle = true; that.addToMap(feature) that.finishDraw() } if (that.drawType === 'distance' && that.tempPoints.length >= 2) { let feature = turf.lineString(that.tempPoints) feature.properties.drawType = 'distance' feature.properties.isCircle = false; that.addToMap(feature) const distance = turf.distance(turf.point(that.tempPoints[0]), turf.point(that.tempPoints[1]), { units: that.unit }) that.totalDistance += distance; that.addMarker(e.lngLat, that.totalDistance.toFixed(2) + " " + that.unitLabel); } if (that.drawType === 'area' && that.tempPoints.length >= 2) { if(that.tempPoints.length >= 3){ let feature = turf.polygon([[...that.tempPoints, that.tempPoints[0]]]) feature.properties.drawType = 'area' feature.properties.isCircle = false; that.addToMap(feature) const area = turf.area(feature) that.totalDistance += area; }else{ let feature = turf.lineString(that.tempPoints) feature.properties.drawType = 'area' feature.properties.isCircle = false; that.addToMap(feature) } } if (that.drawType === 'rectangle' && that.tempPoints.length >= 2) { that.finishDraw() } that.updateTempLayer() } handleMapDblClick(e) { const that = this; if (!that.isDrawing || that.drawType === 'point') return if (that.drawType === 'area' && that.tempPoints.length >= 2) { if(that.tempPoints.length >= 3){ let feature = turf.polygon([[...that.tempPoints, that.tempPoints[0]]]) feature.properties.drawType = 'area' feature.properties.isCircle = false; that.addToMap(feature) const areaNumber = (turf.area(feature)/1000000).toFixed(2); that.addMarker(e.lngLat, areaNumber + ' 平方公里'); }else{ let feature = turf.lineString(that.tempPoints) feature.properties.drawType = 'area' feature.properties.isCircle = false; that.addToMap(feature) const distance = turf.distance(turf.point(that.tempPoints[0]), turf.point(that.tempPoints[1]), { units: that.unit }) that.totalDistance += distance; that.addMarker(e.lngLat, that.totalDistance.toFixed(2) + " " + that.unitLabel); } } that.finishDraw() } updateTempLayer() { const that = this; let feature if (that.drawType === 'point' && that.tempPoints.length) feature = turf.point(that.tempPoints[0]) else if (that.drawType === 'line' && that.tempPoints.length >= 2) feature = turf.lineString(that.tempPoints) else if (that.drawType === 'polygon' && that.tempPoints.length >= 2) feature = turf.lineString([...that.tempPoints, that.tempPoints[0]]) else if (that.drawType === 'circle' && that.tempPoints.length >= 1) feature = turf.point(that.tempPoints[0]) else if (that.drawType === 'distance' && that.tempPoints.length >= 2) feature = turf.lineString(that.tempPoints) else if (that.drawType === 'area' && that.tempPoints.length >= 2) feature = turf.lineString([...that.tempPoints, that.tempPoints[0]]) that.tempSource?.setData(feature || { type: 'FeatureCollection', features: [] }) } addMoveMarkerTips(pt, text) { const that = this; let el = document.createElement('div'); el.className = 'marker-label-tips'; el.innerText = text; that.moveMarkerTips = new mapboxgl.Marker(el, { anchor: 'center', offset: [0, -30] }) .setLngLat(pt) .addTo(that.map); } addMarker(pt, text) { const that = this; let el = document.createElement('div'); el.className = 'marker-label-tips'; el.innerText = text; let marker = new mapboxgl.Marker(el, { anchor: 'center' }) .setLngLat(pt) .addTo(that.map); that.drawMarkers.push(marker); } addEditMarker(coord, index) { const that = this; let el = document.createElement('div'); el.className = 'edit-marker'; el.style.width = '10px'; el.style.height = '10px'; el.style.borderRadius = '50%'; el.style.backgroundColor = 'red'; el.style.border = '2px solid white'; el.style.cursor = 'pointer'; el.addEventListener('mousedown', (e) => { e.stopPropagation(); that.isDragging = true; that.draggingPointIndex = index; that.map.getCanvas().style.cursor = 'grabbing'; }); let marker = new mapboxgl.Marker(el) .setLngLat(coord) .addTo(that.map); that.editMarkers.push(marker); if(!that.selectedFeature.properties.isCircle){ that.insertTwoMiddlePoints(marker, index); } } clearNodeMarkers() { const that = this; that.drawMarkers.forEach(m => m.remove()); that.drawMarkers = []; } clearEditMarkers() { const that = this; that.editMarkers.forEach(marker => marker.remove()); that.editMarkers = []; } showEditMarkers() { const that = this; if (!that.selectedFeature) return; const feature = that.selectedFeature; if (feature.type === 'Feature') { if (feature.geometry.type === 'Point') { that.addEditMarker(feature.geometry.coordinates, 0); } else if (feature.geometry.type === 'LineString') { feature.geometry.coordinates.forEach((coord, index) => { that.addEditMarker(coord, index); }); } else if (feature.geometry.type === 'Polygon') { feature.geometry.coordinates[0].forEach((coord, index) => { if(index == feature.geometry.coordinates[0].length - 1){ return; } that.addEditMarker(coord, index); }); } } } clearAll() { const that = this; that.clearNodeMarkers(); that.clearEditMarkers(); that.selectedFeature = null; that.selectedFeatureId = ''; that.layersIds = []; const layers = that.map.getStyle().layers; layers.sort((a, b) => a.type.localeCompare(b.type)); layers.forEach(layer => { if (layer.id.startsWith('draw-')) { if(layer.id.indexOf("-point") !== -1){ that.map.removeLayer(layer.id) } if(layer.id.indexOf("-line") !== -1){ that.map.removeLayer(layer.id) let ids = layer.id.replace("-line",''); that.map.removeSource(ids) } if(layer.id.indexOf("-polygon") !== -1){ that.map.removeLayer(layer.id) } } }) } addToMap(feature) { const that = this; let id = that.itemId; if (!that.map.getSource(id)) { that.map.addSource(id, { type: 'geojson', data: feature }) if(that.drawType === 'line'){ that.map.addLayer({ id: id + '-line', type: 'line', source: id, paint: { 'line-color': 'red', 'line-width': 2 } }) }else if(that.drawType === 'polygon'){ if(that.tempPoints.length >= 3){ that.map.addLayer({ id: id + '-polygon', type: 'fill', source: id, paint: { 'fill-color': 'red', 'fill-opacity': 0.3 }}) } that.map.addLayer({ id: id + '-line', type: 'line', source: id, paint: { 'line-color': 'red', 'line-width': 2 } }) }else if(that.drawType === 'circle'){ that.map.addLayer({ id: id + '-polygon', type: 'fill', source: id, paint: { 'fill-color': 'red', 'fill-opacity': 0.3 }}) that.map.addLayer({ id: id + '-line', type: 'line', source: id, paint: { 'line-color': 'red', 'line-width': 2 } }) }else if(that.drawType === 'distance'){ that.map.addLayer({ id: id + '-line', type: 'line', source: id, paint: { 'line-color': 'red', 'line-width': 2 } }) }else if(that.drawType === 'area'){ if(that.tempPoints.length >= 3){ that.map.addLayer({ id: id + '-polygon', type: 'fill', source: id, paint: { 'fill-color': 'red', 'fill-opacity': 0.3 }}) } that.map.addLayer({ id: id + '-line', type: 'line', source: id, paint: { 'line-color': 'red', 'line-width': 2 } }) }else if(that.drawType === 'rectangle'){ that.map.addLayer({ id: id + '-polygon', type: 'fill', source: id, paint: { 'fill-color': 'red', 'fill-opacity': 0.3 }}) that.map.addLayer({ id: id + '-line', type: 'line', source: id, paint: { 'line-color': 'red', 'line-width': 2 } }) }else{ that.map.addLayer({ id: id + '-point', type: 'circle', source: id, paint: { 'circle-radius': 5, 'circle-color': 'red' } }) } }else{ that.map.getSource(id).setData(feature) if(that.tempPoints.length >= 3 && (that.drawType === 'area' || that.drawType === 'polygon')){ if(!that.map.getLayer(id + '-polygon')){ that.map.addLayer({ id: id + '-polygon', type: 'fill', source: id, paint: { 'fill-color': 'red', 'fill-opacity': 0.3 }}) } } } that.tempData = feature; } finishDraw() { const that = this; that.getCompleteFeature(that.tempData); that.isDrawing = false that.drawType = '' that.tempPoints = [] that.tempSource?.setData({ type: 'FeatureCollection', features: [] }) that.tempLineSource.setData({ type: 'FeatureCollection', features: [] }) that.map.getCanvas().style.cursor = ''; if(that.moveMarkerTips){ that.moveMarkerTips.remove(); that.moveMarkerTips=null; } } handleFeatureClick(e) { const that = this; const drawLayers = that.map.getStyle().layers .filter(layer => layer.id.startsWith('draw-')) .map(layer => layer.id); const point = e.point; const buffer = 5; const box = [ [point.x - buffer, point.y - buffer], [point.x + buffer, point.y + buffer] ]; // 追加自定义绘制图层 that.editLayerIds.forEach(layerId => { drawLayers.push(layerId); }) const features = that.map.queryRenderedFeatures(box, { layers: drawLayers }); // console.log('[ =====handleFeatureClick===== ] >', that.selectedFeature) if (features.length > 0) { if(that.isEditFlag){// 编辑绘制 const feature = features[0]; const sourceId = feature.layer.source; if (!that.map.getSource(sourceId)) return; that.selectedFeatureId = sourceId; const sourceData = that.map.getSource(sourceId)._data; if(sourceData.geometry){ that.selectedFeature = JSON.parse(JSON.stringify(sourceData)); }else{ sourceData.features.forEach(item => { if(item.properties.dms_id === feature.properties.dms_id){ that.selectedFeature = JSON.parse(JSON.stringify(item)); return; } }) } // 深拷贝切断引用 // that.selectedFeature = JSON.parse(JSON.stringify(feature)); that.getCompleteFeature(that.selectedFeature); that.clearEditMarkers(); if(that.selectedFeature.properties.drawType === 'distance' || that.selectedFeature.properties.drawType === 'area') return; that.showEditMarkers(); }else{ // 编辑工具绘制 const feature = features[0]; const layerId = feature.layer.id; const featureId = layerId.split('-').slice(0, -1).join('-'); if (!that.map.getSource(featureId)) return; that.selectedFeatureId = featureId; const sourceData = that.map.getSource(featureId)._data; that.selectedFeature = JSON.parse(JSON.stringify(sourceData)); // console.log('[ handleFeatureClick ] >', that.selectedFeature) that.clearEditMarkers(); if(that.selectedFeature.properties.drawType === 'distance' || that.selectedFeature.properties.drawType === 'area') return; that.showEditMarkers(); } } else { that.clearEditMarkers(); that.selectedFeature = null; that.selectedFeatureId = ''; // console.log('[ handleFeatureClick======== ] >', that.selectedFeature) } } // 编辑绘制点击事件 弃用 已被 handleFeatureClick 替代 handleEditDrawClick(e) { const that = this; const point = e.point; const buffer = 5; const box = [ [point.x - buffer, point.y - buffer], [point.x + buffer, point.y + buffer] ]; const features = that.map.queryRenderedFeatures(box, { layers: that.editLayerIds }); // console.log('[ handleEditDrawClick ] >', features) if (features.length > 0) { const feature = features[0]; const sourceId = feature.layer.source; if (!that.map.getSource(sourceId)) return; that.selectedFeatureId = sourceId; const sourceData = that.map.getSource(sourceId)._data; if(sourceData.geometry){ that.selectedFeature = JSON.parse(JSON.stringify(sourceData)); }else{ sourceData.features.forEach(item => { if(item.properties.dms_id === feature.properties.dms_id){ that.selectedFeature = JSON.parse(JSON.stringify(item)); return; } }) } // 深拷贝切断引用 // that.selectedFeature = JSON.parse(JSON.stringify(feature)); that.getCompleteFeature(that.selectedFeature); that.clearEditMarkers(); that.showEditMarkers(); } else { that.selectedFeature = null; that.selectedFeatureId = ''; } } moveWholeFeature(dx, dy) { const that = this; if (!that.selectedFeature) return; const feature = that.selectedFeature; if (feature.geometry.type === 'Point') { feature.geometry.coordinates[0] += dx; feature.geometry.coordinates[1] += dy; } else if (feature.geometry.type === 'LineString') { feature.geometry.coordinates.forEach(coord => { coord[0] += dx; coord[1] += dy; }); } else if (feature.geometry.type === 'Polygon') { if(feature.properties.isCircle){ const centerPt = turf.centroid(feature); const center = centerPt.geometry.coordinates; const newCenter = [center[0] + dx, center[1] + dy]; const radiusMeters = turf.distance(center, feature.geometry.coordinates[0][0], { units: 'meters' }); const newCircle = turf.circle(newCenter, radiusMeters, { units: 'meters', steps: 64 }); newCircle.properties = feature.properties; that.selectedFeature = newCircle; } else { feature.geometry.coordinates.forEach(ring => { ring.forEach(coord => { coord[0] += dx; coord[1] += dy; }); }); } } that.map.getSource(that.selectedFeatureId).setData(that.selectedFeature); const sourceData = that.map.getSource(that.selectedFeatureId)._data; that.selectedFeature = JSON.parse(JSON.stringify(sourceData)); } editCircleVertex(newPoint) { const that = this; const feature = that.selectedFeature; const centerPt = turf.centroid(feature); const center = centerPt.geometry.coordinates; const radiusMeters = turf.distance(center, newPoint, { units: 'meters' }); if (radiusMeters < 5) return; const newCircle = turf.circle(center, radiusMeters, { units: 'meters', steps: 64 }); newCircle.properties = feature.properties; const line = turf.lineString([center, newPoint]); that.tempLineSource.setData(line); that.selectedFeature = newCircle; return newCircle; } insertTwoMiddlePoints(marker, index) { const that = this; marker.getElement().addEventListener('click', (e) => { e.stopPropagation(); if (!that.selectedFeature) return; const geom = that.selectedFeature.geometry; let coords = []; let isPolygon = false; if (geom.type === 'LineString') { coords = [...geom.coordinates]; } else if (geom.type === 'Polygon') { coords = [...geom.coordinates[0]]; isPolygon = true; } if (coords.length < 2) return; if (isPolygon) { // ====================== // 面对称加两点(左边1个 + 右边1个) // ====================== let pureCoords = coords.slice(0, -1); const total = pureCoords.length; let realIndex = index; if (index === coords.length - 1) { realIndex = 0; } const prevIdx = (realIndex - 1 + total) % total; const nextIdx = (realIndex + 1) % total; const prevPt = pureCoords[prevIdx]; const currPt = pureCoords[realIndex]; const nextPt = pureCoords[nextIdx]; const midPrev = [(prevPt[0] + currPt[0]) / 2, (prevPt[1] + currPt[1]) / 2]; const midNext = [(currPt[0] + nextPt[0]) / 2, (currPt[1] + nextPt[1]) / 2]; pureCoords.splice(realIndex, 0, midPrev); pureCoords.splice(realIndex + 2, 0, midNext); coords = [...pureCoords, pureCoords[0]]; geom.coordinates[0] = coords; } else { // ====================== // 线和面对称加两点(左边1个 + 右边1个) // ====================== if (index === 0) { // 起点 → 只在右侧加点(和面对齐) const nextPt = coords[index + 1]; const midNext = [(coords[index][0] + nextPt[0]) / 2, (coords[index][1] + nextPt[1]) / 2]; coords.splice(index + 1, 0, midNext); } else if (index === coords.length - 1) { // 终点 → 只在左侧加点 const prevPt = coords[index - 1]; const midPrev = [(prevPt[0] + coords[index][0]) / 2, (prevPt[1] + coords[index][1]) / 2]; coords.splice(index, 0, midPrev); } else { // 中间点 → 左右各加 1 点(和面完全一样!) const prevPt = coords[index - 1]; const nextPt = coords[index + 1]; const currPt = coords[index]; const midPrev = [(prevPt[0] + currPt[0]) / 2, (prevPt[1] + currPt[1]) / 2]; const midNext = [(currPt[0] + nextPt[0]) / 2, (currPt[1] + nextPt[1]) / 2]; coords.splice(index, 0, midPrev); coords.splice(index + 2, 0, midNext); } geom.coordinates = coords; } that.refreshFeature(); }); } // ========== 键盘 DEL 删除选中节点 / 图形 ========== bindDeleteHotkey() { const that = this; // 先清掉旧事件,防止多实例、上下文错乱 window.removeEventListener('keydown', that._deleteKeyHandler); // 保存成实例方法,保证永远指向最新实例 that._deleteKeyHandler = function (e) { // 🔥 这里用 that,永远是当前最新实例 if (e.key === 'Delete' && that.selectedFeature && (that.isEditMode || that.isEditFlag)) { e.preventDefault(); // console.log('DEL 触发 - 当前图形:', that.selectedFeature); // 如果正在编辑、有选中节点 → 删除当前节点 if (that.draggingPointIndex !== -1) { that.deleteVertex(that.draggingPointIndex); } // else { // that.deleteSelected(); // } } }; window.addEventListener('keydown', that._deleteKeyHandler); } // ========== 删除顶点(点/线/面 通用) ========== deleteVertex(index) { const that = this; if (!that.selectedFeature) return; const geom = that.selectedFeature.geometry; let coords; let isPolygon = false; // 先重置拖拽状态!防止索引错乱 that.draggingPointIndex = -1; that.isDragging = false; if (geom.type === 'LineString') { coords = geom.coordinates; if (coords.length <= 2) return; // 线至少保留2个点 } else if (geom.type === 'Polygon') { isPolygon = true; coords = geom.coordinates[0]; if (coords.length <= 4) return; // 面至少保留3个点 } // 删除顶点 coords.splice(index, 1); // 闭合面处理 if (isPolygon) { if (index === 0) { coords[coords.length - 1] = coords[0]; } } // 刷新 that.refreshFeature(); } refreshFeature() { const that = this; if (!that.selectedFeature || !that.selectedFeatureId) return; // ========== 必须重置拖拽状态!否则必乱 ========== that.draggingPointIndex = -1; that.isDragging = false; that.map.getSource(that.selectedFeatureId).setData(that.selectedFeature); that.selectedFeature = JSON.parse(JSON.stringify(that.map.getSource(that.selectedFeatureId)._data)); that.clearEditMarkers(); that.showEditMarkers(); } handleDrag(e) { const that = this; // console.log("handleDrag",that.draggingPointIndex); if (!that.selectedFeature || that.draggingPointIndex === -1) return; if(that.selectedFeature.properties.drawType === 'distance' || that.selectedFeature.properties.drawType === 'area') return; let feature = that.selectedFeature; let newCoord = [e.lngLat.lng, e.lngLat.lat]; if(that.selectedFeature.properties.isCircle){ that.clearEditMarkers(); feature = that.editCircleVertex(newCoord); }else{ if (feature.geometry.type === 'Point') { feature.geometry.coordinates = newCoord; } else if (feature.geometry.type === 'LineString') { feature.geometry.coordinates[that.draggingPointIndex] = newCoord; } else if (feature.geometry.type === 'Polygon') { feature.geometry.coordinates[0][that.draggingPointIndex] = newCoord; if (that.draggingPointIndex === 0) { feature.geometry.coordinates[0][feature.geometry.coordinates[0].length - 1] = newCoord; } } that.clearEditMarkers(); that.showEditMarkers(); } that.selectedFeature = JSON.parse(JSON.stringify(feature)); that.map.getSource(that.selectedFeatureId).setData(feature); that.getCompleteFeature(that.selectedFeature); // console.log("handleDrag selectedFeature",that.selectedFeature); } setEditLayerIds(arr){ const that = this; that.editLayerIds = arr; } deleteSelected() { let that = this; if (!that.selectedFeatureId) return; const layers = window.mapboxMap.getStyle().layers; layers.forEach(layer => { if (layer.id.startsWith(that.selectedFeatureId)) { that.map.removeLayer(layer.id); } }); that.map.removeSource(that.selectedFeatureId); that.clearEditMarkers(); that.selectedFeature = null; that.selectedFeatureId = ''; that.layersIds = []; } } function init(map, options) { const instance = new MapDraw(map, options); return { startDraw: (type, flag, unit) => instance.startDraw(type, flag, unit), isEdit: (flag) => instance.isEdit(flag), clearAll: () => instance.clearAll(), setEditLayerIds: (arr) => instance.setEditLayerIds(arr), }; } window.MapDraw = { init }; })(window);