|
@@ -0,0 +1,950 @@
|
|
|
|
|
+// 闭包封装:内部所有变量和方法全部私有化!
|
|
|
|
|
+(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);
|