Mapbox中移动、拖拽多边形

实现功能如下:

  1. 拖拽顶点:修改形状

  2. 拖拽内部:移动多边形

  3. 按住Shift拖拽任意顶点:围绕中心点旋转

  4. 拖拽过程中按Esc取消

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Mapbox GL Draw 多边形编辑(中心点旋转)</title> <script src='https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js'></script> <link href='https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.css' rel='stylesheet' /> <script src='https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.4.1/mapbox-gl-draw.js'></script> <link href='https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.4.1/mapbox-gl-draw.css' rel='stylesheet' /> <style> body { margin: 0; padding: 0; } #map { position: absolute; top: 0; bottom: 0; width: 100%; } .control-panel { position: absolute; top: 10px; right: 10px; background: white; padding: 15px; border-radius: 5px; box-shadow: 0 1px 3px rgba(0,0,0,0.3); max-width: 300px; max-height: 80vh; overflow-y: auto; } button { margin: 5px; padding: 8px 15px; background: #4264fb; color: white; border: none; border-radius: 3px; cursor: pointer; } button:hover { background: #3254eb; } .hint { position: absolute; bottom: 10px; left: 10px; background: white; padding: 10px; border-radius: 5px; box-shadow: 0 1px 3px rgba(0,0,0,0.3); font-size: 12px; } </style> </head> <body>

    多边形编辑

    <button id="loadPolygon">加载多边形</button> <button id="editMode">编辑模式</button> <button id="viewMode">查看模式</button>
    复制代码
     <script>
         // 设置Mapbox访问令牌
         mapboxgl.accessToken = '';
         
         // 初始化地图
         const map = new mapboxgl.Map({
             container: 'map',
             style: 'mapbox://styles/mapbox/streets-v12',
             center: [116.4074, 39.9042],
             zoom: 12
         });
         
         // 初始化Draw插件
         const draw = new MapboxDraw({
             displayControlsDefault: false,
             controls: {
                 polygon: true,
                 trash: true
             },
             styles: [
                 {
                     'id': 'gl-draw-polygon-fill',
                     'type': 'fill',
                     'filter': ['all', ['==', '$type', 'Polygon']],
                     'paint': {
                         'fill-color': '#fbb03b',
                         'fill-opacity': 0.3
                     }
                 },
                 {
                     'id': 'gl-draw-polygon-stroke',
                     'type': 'line',
                     'filter': ['all', ['==', '$type', 'Polygon']],
                     'paint': {
                         'line-color': '#fbb03b',
                         'line-width': 2
                     }
                 },
                 {
                     'id': 'gl-draw-polygon-and-line-vertex-halo-active',
                     'type': 'circle',
                     'filter': ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point'], ['!=', 'mode', 'static']],
                     'paint': {
                         'circle-radius': 8,
                         'circle-color': '#fff'
                     }
                 },
                 {
                     'id': 'gl-draw-polygon-and-line-vertex-active',
                     'type': 'circle',
                     'filter': ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point'], ['!=', 'mode', 'static']],
                     'paint': {
                         'circle-radius': 6,
                         'circle-color': '#fbb03b'
                     }
                 }
             ]
         });
         
         // 添加Draw控件到地图
         map.addControl(draw);
         
         // 添加导航控件
         map.addControl(new mapboxgl.NavigationControl());
         
         // 存储当前多边形的坐标
         let currentPolygonCoordinates = null;
         let rotationData = {
             isRotating: false,
             startPoint: null,
             originalFeature: null,
             centerPoint: null,
             startAngle: null
         };
         
         // 示例多边形坐标(不规则多边形)
         const samplePolygon = {
             type: 'Feature',
             geometry: {
                 type: 'Polygon',
                 coordinates: [[
                     [116.3906, 39.9186],
                     [116.4031, 39.9150],
                     [116.4050, 39.9074],
                     [116.3950, 39.9050],
                     [116.3906, 39.9186]
                 ]]
             },
             properties: {}
         };
         
         // 加载多边形
         document.getElementById('loadPolygon').addEventListener('click', () => {
             draw.deleteAll();
             draw.add(samplePolygon);
             currentPolygonCoordinates = samplePolygon.geometry.coordinates[0];
             updateCoordinatesDisplay();
         });
         
         // 进入编辑模式
         document.getElementById('editMode').addEventListener('click', () => {
             const features = draw.getAll();
             if (features.features.length > 0) {
                 const featureIds = features.features.map(f => f.id);
                 draw.changeMode('simple_select', { featureIds: featureIds });
             } else {
                 alert('请先加载多边形!');
             }
         });
         
         // 进入查看模式
         document.getElementById('viewMode').addEventListener('click', () => {
             draw.changeMode('simple_select');
         });
         
         // 更新坐标显示
         function updateCoordinatesDisplay() {
             if (currentPolygonCoordinates) {
                 const formattedCoords = currentPolygonCoordinates.map(coord => 
                     `[${coord[0].toFixed(6)}, ${coord[1].toFixed(6)}]`
                 ).join('<br>');
                 document.getElementById('coordinates').innerHTML = `
                     <h4>当前坐标:</h4>
                     <div style="font-size: 12px; max-height: 200px; overflow-y: auto;">
                         ${formattedCoords}
                     </div>
                     <p>顶点数:${currentPolygonCoordinates.length}</p>
                 `;
             }
         }
         
         // 计算多边形中心点
         function calculatePolygonCenter(coords) {
             let sumLng = 0;
             let sumLat = 0;
             const count = coords.length - 1; // 排除闭合点
             
             for (let i = 0; i < count; i++) {
                 sumLng += coords[i][0];
                 sumLat += coords[i][1];
             }
             
             return {
                 lng: sumLng / count,
                 lat: sumLat / count
             };
         }
         
         // 旋转多边形(围绕中心点)
         function rotatePolygonAroundCenter(coords, center, angle) {
             return coords.map(coord => {
                 // 将坐标转换为相对于中心点的向量
                 const dx = coord[0] - center.lng;
                 const dy = coord[1] - center.lat;
                 
                 // 应用旋转矩阵(弧度)
                 const cosA = Math.cos(angle);
                 const sinA = Math.sin(angle);
                 
                 // 旋转后的坐标
                 const rotatedX = dx * cosA - dy * sinA;
                 const rotatedY = dx * sinA + dy * cosA;
                 
                 // 转换回绝对坐标
                 return [
                     center.lng + rotatedX,
                     center.lat + rotatedY
                 ];
             });
         }
         
         // 计算两点之间的角度(相对于水平方向)
         function calculateAngle(point1, point2, center) {
             const dx1 = point1.lng - center.lng;
             const dy1 = point1.lat - center.lat;
             const dx2 = point2.lng - center.lng;
             const dy2 = point2.lat - center.lat;
             
             const angle1 = Math.atan2(dy1, dx1);
             const angle2 = Math.atan2(dy2, dx2);
             
             return angle2 - angle1;
         }
         
         // 监听地图加载完成
         map.on('load', () => {
             // 监听Draw的更新事件
             map.on('draw.update', (e) => {
                 const features = e.features;
                 if (features.length > 0) {
                     const feature = features[0];
                     if (feature.geometry.type === 'Polygon') {
                         currentPolygonCoordinates = feature.geometry.coordinates[0];
                         updateCoordinatesDisplay();
                     }
                 }
             });
             
             // 监听Draw的创建事件
             map.on('draw.create', (e) => {
                 const features = e.features;
                 if (features.length > 0) {
                     const feature = features[0];
                     if (feature.geometry.type === 'Polygon') {
                         currentPolygonCoordinates = feature.geometry.coordinates[0];
                         updateCoordinatesDisplay();
                     }
                 }
             });
             
             // 监听Draw的删除事件
             map.on('draw.delete', (e) => {
                 currentPolygonCoordinates = null;
                 document.getElementById('coordinates').innerHTML = '';
             });
             
             // 监听Draw的选择变化事件
             map.on('draw.selectionchange', (e) => {
                 if (e.features.length > 0) {
                     console.log('进入编辑模式,可以拖拽顶点或按住Shift旋转');
                 }
             });
             
             // 鼠标按下事件 - 开始旋转
             map.on('mousedown', (e) => {
                 const features = draw.getSelected().features;
                 if (features.length > 0 && e.originalEvent.shiftKey) {
                     const feature = features[0];
                     if (feature.geometry.type === 'Polygon') {
                         rotationData.isRotating = true;
                         rotationData.startPoint = e.lngLat;
                         rotationData.originalFeature = JSON.parse(JSON.stringify(feature));
                         
                         // 计算多边形中心点
                         const coords = feature.geometry.coordinates[0];
                         rotationData.centerPoint = calculatePolygonCenter(coords);
                         
                         // 计算起始角度(从中心点到鼠标点的角度)
                         rotationData.startAngle = Math.atan2(
                             e.lngLat.lat - rotationData.centerPoint.lat,
                             e.lngLat.lng - rotationData.centerPoint.lng
                         );
                         
                         e.preventDefault();
                     }
                 }
             });
             
             // 鼠标移动事件 - 执行旋转
             map.on('mousemove', (e) => {
                 if (rotationData.isRotating && 
                     rotationData.startPoint && 
                     rotationData.originalFeature && 
                     rotationData.centerPoint) {
                     
                     const currentPoint = e.lngLat;
                     
                     // 计算当前角度
                     const currentAngle = Math.atan2(
                         currentPoint.lat - rotationData.centerPoint.lat,
                         currentPoint.lng - rotationData.centerPoint.lng
                     );
                     
                     // 计算旋转角度
                     const rotationAngle = currentAngle - rotationData.startAngle;
                     
                     // 应用旋转
                     const rotatedCoords = rotatePolygonAroundCenter(
                         rotationData.originalFeature.geometry.coordinates[0],
                         rotationData.centerPoint,
                         rotationAngle
                     );
                     
                     // 更新图形
                     const newFeature = {
                         ...rotationData.originalFeature,
                         geometry: {
                             type: 'Polygon',
                             coordinates: [rotatedCoords]
                         }
                     };
                     
                     // 删除旧图形并添加新图形
                     const selectedIds = draw.getSelectedIds();
                     draw.delete(rotationData.originalFeature.id);
                     const addedFeature = draw.add(newFeature);
                     
                     // 保持选中状态
                     draw.changeMode('simple_select', { featureIds: [addedFeature.id] });
                     
                     e.preventDefault();
                 }
             });
             
             // 鼠标松开事件 - 结束旋转
             map.on('mouseup', (e) => {
                 if (rotationData.isRotating) {
                     rotationData.isRotating = false;
                     
                     // 获取最终坐标并更新显示
                     const features = draw.getSelected().features;
                     if (features.length > 0) {
                         const feature = features[0];
                         if (feature.geometry.type === 'Polygon') {
                             currentPolygonCoordinates = feature.geometry.coordinates[0];
                             updateCoordinatesDisplay();
                         }
                     }
                     
                     // 重置旋转数据
                     rotationData = {
                         isRotating: false,
                         startPoint: null,
                         originalFeature: null,
                         centerPoint: null,
                         startAngle: null
                     };
                 }
             });
             
             // 添加旋转中心点标记(可选,用于调试)
             function addCenterMarker(center) {
                 const marker = new mapboxgl.Marker({
                     color: '#ff0000',
                     draggable: false
                 })
                 .setLngLat([center.lng, center.lat])
                 .addTo(map);
                 
                 setTimeout(() => marker.remove(), 1000);
             }
             
             // 监听键盘事件,显示旋转中心点
             map.on('keydown', (e) => {
                 if (e.key === 'Shift') {
                     const features = draw.getSelected().features;
                     if (features.length > 0) {
                         const feature = features[0];
                         if (feature.geometry.type === 'Polygon') {
                             const coords = feature.geometry.coordinates[0];
                             const center = calculatePolygonCenter(coords);
                             addCenterMarker(center);
                         }
                     }
                 }
             });
             
             // 添加提示信息
             const hint = document.createElement('div');
             hint.className = 'hint';
             hint.innerHTML = `
                 <strong>操作指南:</strong><br>
                 1. 拖拽顶点:修改形状<br>
                 2. 拖拽内部:移动多边形<br>
                 3. <strong>按住Shift拖拽任意顶点:围绕中心点旋转</strong><br>
                 4. 拖拽过程中按Esc取消
             `;
             document.getElementById('map').appendChild(hint);
             
             // 添加旋转角度显示
             const angleDisplay = document.createElement('div');
             angleDisplay.className = 'hint';
             angleDisplay.style.bottom = '60px';
             angleDisplay.style.fontSize = '11px';
             angleDisplay.innerHTML = '旋转角度: 0°';
             document.getElementById('map').appendChild(angleDisplay);
             
             // 监听鼠标移动,更新角度显示
             map.on('mousemove', (e) => {
                 if (rotationData.isRotating && rotationData.startAngle !== null) {
                     const currentPoint = e.lngLat;
                     const currentAngle = Math.atan2(
                         currentPoint.lat - rotationData.centerPoint.lat,
                         currentPoint.lng - rotationData.centerPoint.lng
                     );
                     const rotationAngle = currentAngle - rotationData.startAngle;
                     const rotationDegrees = (rotationAngle * 180 / Math.PI).toFixed(1);
                     angleDisplay.innerHTML = `旋转角度: ${rotationDegrees}°`;
                     angleDisplay.style.display = 'block';
                 } else {
                     angleDisplay.style.display = 'none';
                 }
             });
         });
         
         // 防止Shift键被浏览器默认行为占用
         window.addEventListener('keydown', (e) => {
             if (e.key === 'Shift') {
                 e.preventDefault();
             }
         });
         
         window.addEventListener('keyup', (e) => {
             if (e.key === 'Shift') {
                 e.preventDefault();
             }
         });
     </script>
    </body> </html>
相关推荐
WebGIS开发8 小时前
WebGIS开发实战|智慧城市济南地图可视化开发系统
智慧城市·mapbox·webgis
WebGIS开发1 天前
WebGIS开发实战|广州市智慧城市监测平台
智慧城市·mapbox·gis开发·webgis
GDAL1 天前
深入解析 @mapbox/mbtiles:Node.js 玩转 MBTiles 瓦片格式
mapbox
GISHUB17 天前
地图矢量切片常用的几种开源方案
开源·mapbox
GDAL23 天前
Mapbox GL JS 核心表达式:`==` 相等判断完全教程
javascript·mapbox
GDAL24 天前
Mapbox GL JS 核心表达式:`all` 多条件且判断完全教程
mapbox·表达式·all
duansamve24 天前
MapBox从入门到精通
mapbox
map_3d_vis1 个月前
JSAPIThree 加载 Mapbox 数据学习笔记:使用 Mapbox 矢量瓦片地图
学习笔记·mvt·mapbox·矢量瓦片·初学者·mapvthree·jsapithree·mapboxvectortileprovider
新中地GIS开发老师5 个月前
2025Mapbox零基础入门教程(14)定位功能
前端·javascript·arcgis·gis·mapbox·gis开发·地理信息科学