(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? (module.exports = factory()) : typeof define === 'function' && define.amd ? define(factory) : ((global = typeof globalThis !== 'undefined' ? globalThis : global || self), (global.mapboxgl1.MeatureTool = factory(global.mapboxgl1))); })(window, function (mapboxgl) { const css = ` /* Override default control style */ .mapbox-gl-draw_ctrl-bottom-left, .mapbox-gl-draw_ctrl-top-left { margin-left:0; border-radius:0 4px 4px 0; } .mapbox-gl-draw_ctrl-top-right, .mapbox-gl-draw_ctrl-bottom-right { margin-right:0; border-radius:4px 0 0 4px; } .mapbox-gl-draw_ctrl-draw-btn { border-color:rgba(0,0,0,0.9); color:rgba(255,255,255,0.5); width:30px; height:30px; } .mapbox-gl-draw_ctrl-draw-btn.active, .mapbox-gl-draw_ctrl-draw-btn.active:hover { background-color:rgb(0 0 0/5%); } .mapbox-gl-draw_ctrl-draw-btn { background-repeat: no-repeat; background-position: center; } .mapbox-gl-draw_point { background-image: url('data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="20" height="20">%3Cpath d="m10 2c-3.3 0-6 2.7-6 6s6 9 6 9 6-5.7 6-9-2.7-6-6-6zm0 2c2.1 0 3.8 1.7 3.8 3.8 0 1.5-1.8 3.9-2.9 5.2h-1.7c-1.1-1.4-2.9-3.8-2.9-5.2-.1-2.1 1.6-3.8 3.7-3.8z"/>%3C/svg>'); } .mapbox-gl-draw_polygon { background-image: url('data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="20" height="20">%3Cpath d="m15 12.3v-4.6c.6-.3 1-1 1-1.7 0-1.1-.9-2-2-2-.7 0-1.4.4-1.7 1h-4.6c-.3-.6-1-1-1.7-1-1.1 0-2 .9-2 2 0 .7.4 1.4 1 1.7v4.6c-.6.3-1 1-1 1.7 0 1.1.9 2 2 2 .7 0 1.4-.4 1.7-1h4.6c.3.6 1 1 1.7 1 1.1 0 2-.9 2-2 0-.7-.4-1.4-1-1.7zm-8-.3v-4l1-1h4l1 1v4l-1 1h-4z"/>%3C/svg>'); } .mapbox-gl-draw_line { background-image: url('data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="20" height="20">%3Cpath d="m13.5 3.5c-1.4 0-2.5 1.1-2.5 2.5 0 .3 0 .6.2.9l-3.8 3.8c-.3-.1-.6-.2-.9-.2-1.4 0-2.5 1.1-2.5 2.5s1.1 2.5 2.5 2.5 2.5-1.1 2.5-2.5c0-.3 0-.6-.2-.9l3.8-3.8c.3.1.6.2.9.2 1.4 0 2.5-1.1 2.5-2.5s-1.1-2.5-2.5-2.5z"/>%3C/svg>'); } .mapbox-gl-draw_trash { background-image: url('data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="20" height="20">%3Cpath d="M10,3.4 c-0.8,0-1.5,0.5-1.8,1.2H5l-1,1v1h12v-1l-1-1h-3.2C11.5,3.9,10.8,3.4,10,3.4z M5,8v7c0,1,1,2,2,2h6c1,0,2-1,2-2V8h-2v5.5h-1.5V8h-3 v5.5H7V8H5z"/>%3C/svg>'); } .mapbox-gl-draw_uncombine { background-image: url('data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="20" height="20">%3Cpath d="m12 2c-.3 0-.5.1-.7.3l-1 1c-.4.4-.4 1 0 1.4l1 1c.4.4 1 .4 1.4 0l1-1c.4-.4.4-1 0-1.4l-1-1c-.2-.2-.4-.3-.7-.3zm4 4c-.3 0-.5.1-.7.3l-1 1c-.4.4-.4 1 0 1.4l1 1c.4.4 1 .4 1.4 0l1-1c.4-.4.4-1 0-1.4l-1-1c-.2-.2-.4-.3-.7-.3zm-7 1c-1 0-1 1-.5 1.5.3.3 1 1 1 1l-1 1s-.5.5 0 1 1 0 1 0l1-1 1 1c.5.5 1.5.5 1.5-.5v-4zm-5 3c-.3 0-.5.1-.7.3l-1 1c-.4.4-.4 1 0 1.4l4.9 4.9c.4.4 1 .4 1.4 0l1-1c.4-.4.4-1 0-1.4l-4.9-4.9c-.1-.2-.4-.3-.7-.3z"/>%3C/svg>'); } .mapbox-gl-draw_combine { background-image: url('data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="20" height="20">%3Cpath d="M12.1,2c-0.3,0-0.5,0.1-0.7,0.3l-1,1c-0.4,0.4-0.4,1,0,1.4l4.9,4.9c0.4,0.4,1,0.4,1.4,0l1-1 c0.4-0.4,0.4-1,0-1.4l-4.9-4.9C12.6,2.1,12.3,2,12.1,2z M8,8C7,8,7,9,7.5,9.5c0.3,0.3,1,1,1,1l-1,1c0,0-0.5,0.5,0,1s1,0,1,0l1-1l1,1 C11,13,12,13,12,12V8H8z M4,10c-0.3,0-0.5,0.1-0.7,0.3l-1,1c-0.4,0.4-0.4,1,0,1.4l1,1c0.4,0.4,1,0.4,1.4,0l1-1c0.4-0.4,0.4-1,0-1.4 l-1-1C4.5,10.1,4.3,10,4,10z M8,14c-0.3,0-0.5,0.1-0.7,0.3l-1,1c-0.4,0.4-0.4,1,0,1.4l1,1c0.4,0.4,1,0.4,1.4,0l1-1 c0.4-0.4,0.4-1,0-1.4l-1-1C8.5,14.1,8.3,14,8,14z"/>%3C/svg>'); } .mapboxgl-map.mouse-pointer .mapboxgl-canvas-container.mapboxgl-interactive { cursor: pointer; } .mapboxgl-map.mouse-move .mapboxgl-canvas-container.mapboxgl-interactive { cursor: move; } .mapboxgl-map.mouse-add .mapboxgl-canvas-container.mapboxgl-interactive { cursor: crosshair; } .mapboxgl-map.mouse-move.mode-direct_select .mapboxgl-canvas-container.mapboxgl-interactive { cursor: grab; cursor: -moz-grab; cursor: -webkit-grab; } .mapboxgl-map.mode-direct_select.feature-vertex.mouse-move .mapboxgl-canvas-container.mapboxgl-interactive { cursor: move; } .mapboxgl-map.mode-direct_select.feature-midpoint.mouse-pointer .mapboxgl-canvas-container.mapboxgl-interactive { cursor: cell; } .mapboxgl-map.mode-direct_select.feature-feature.mouse-move .mapboxgl-canvas-container.mapboxgl-interactive { cursor: move; } .mapboxgl-map.mode-static.mouse-pointer .mapboxgl-canvas-container.mapboxgl-interactive { cursor: grab; cursor: -moz-grab; cursor: -webkit-grab; } .mapbox-gl-draw_boxselect { pointer-events: none; position: absolute; top: 0; left: 0; width: 0; height: 0; background: rgba(0,0,0,.1); border: 2px dotted #fff; opacity: 0.5; } .measure-move { border-radius: 3px; height: 16px; line-height: 16px; padding: 0 3px; font-size: 12px; box-shadow: 0 0 0 1px #ccc; float: right; cursor: default; } .measure-result { border-radius: 3px; height: 16px; line-height: 16px; padding: 0 3px; font-size: 12px; box-shadow: 0 0 0 1px #ccc; float: right; cursor: default; z-index: 10; } .close { width: 3px; height: 14px; text-align: center; border-radius: 3px; padding-top: 0px; padding-right: 10px; box-shadow: 0 0 0 0px #ccc; cursor: pointer; background: url(../measureicon/close.png) no-repeat center; border: 1px solid rgba(100, 100, 100, 0) } .clear { width: 3px; height: 14px; text-align: center; border-radius: 3px; padding-right: 10px; box-shadow: 0 0 0 0px #ccc; cursor: pointer; float: right; background: url(../measureicon/delete.png) no-repeat center; border: 1px solid rgba(100, 100, 100, 0) } .edit { width: 3px; height: 14px; text-align: center; border-radius: 3px; padding-right: 10px; box-shadow: 0 0 0 0px #ccc; cursor: pointer; float: right; background: url(../measureicon/edit.png) no-repeat center; border: 1px solid rgba(100, 100, 100, 0) } .close:hover { border: 1px solid rgba(52, 98, 152, 1); } .clear:hover { border: 1px solid rgba(52, 98, 152, 1); } .edit:hover { border: 1px solid rgba(52, 98, 152, 1); } `; document.write(`<style type='text/css'>${css}</style>`); return class { constructor(map) { this.map = map; } layerDistanceList = []; layerAreaList = []; layerPointList = []; setDistance = () => {}; setArea = () => {}; /** * 测量距离 * @param {*} layerId */ measureDistance(layerId, isP = true, callback = () => {}) { this.layerDistanceList.push(layerId); var isMeasure = true; const map = this.map; map.doubleClickZoom.disable(); let catchMark = null; let isEdit = false; let Geolist = []; let dragPointOrder = 0; let pointOnLine = [0, 0]; const jsonPoint = { type: 'FeatureCollection', features: [], }; const jsonLine = { type: 'FeatureCollection', features: [], }; // 添加测量结果弹窗 const ele = document.createElement('div'); ele.setAttribute('class', 'measure-move'); const option = { element: ele, anchor: 'left', offset: [8, 0], }; const tooltip = new mapboxgl.Marker(option).setLngLat([0, 0]); if (isP) tooltip.addTo(map); // 添加测量图层 map.addSource('points' + layerId, { type: 'geojson', data: jsonPoint, }); map.addSource('point-move' + layerId, { type: 'geojson', data: jsonPoint, }); map.addSource('line' + layerId, { type: 'geojson', data: jsonLine, }); map.addSource('line-move' + layerId, { type: 'geojson', data: jsonLine, }); map.addSource('point-follow' + layerId, { type: 'geojson', data: jsonPoint, }); map.addLayer({ id: 'line' + layerId, type: 'line', source: 'line' + layerId, paint: { 'line-color': '#ff0000', 'line-width': 2, 'line-opacity': 0.65, }, }); map.addLayer({ id: 'line-move' + layerId, type: 'line', source: 'line-move' + layerId, paint: { 'line-color': '#ff0000', 'line-width': 2, 'line-opacity': 0.65, 'line-dasharray': [5, 2], }, }); map.addLayer({ id: 'points' + layerId, type: 'circle', source: 'points' + layerId, paint: { 'circle-color': '#ffffff', 'circle-radius': 3.5, 'circle-stroke-width': 1.5, 'circle-stroke-color': '#ff0000', }, }); map.addLayer({ id: 'point-move' + layerId, type: 'circle', source: 'point-move' + layerId, paint: { 'circle-color': '#ffffff', 'circle-radius': 3.5, 'circle-stroke-width': 1.5, 'circle-stroke-color': '#ff0000', }, }); // 活动点可以选择用图层,也可以选择用Mark map.addLayer({ id: 'point-follow' + layerId, type: 'circle', source: 'point-follow' + layerId, paint: { 'circle-color': '#199afc', 'circle-radius': 5.5, 'circle-stroke-width': 1.5, 'circle-stroke-color': '#ffffff', }, }); // 清除面积测量 this.setArea = () => { isMeasure = false; if (map.getLayer('point-move' + layerId)) map.removeLayer('point-move' + layerId); if (map.getLayer('line-move' + layerId)) map.removeLayer('line-move' + layerId); return isMeasure; }; /** * 添加点 * @param {*} _e */ function addPointforJSON(_e) { if (isMeasure) { const point = { type: 'Feature', geometry: { type: 'Point', coordinates: [_e.lngLat.lng, _e.lngLat.lat], }, properties: { id: String(new Date().getTime()), }, }; jsonPoint.features.push(point); map.getSource('points' + layerId).setData(jsonPoint); drawLine(jsonPoint); addMeasureRes(jsonPoint); } } /** * 绘制线 * @param {*} jsonPoint */ function drawLine(jsonPoint) { if (jsonPoint.features.length > 1) { jsonLine.features = []; for (let i = 0; i < jsonPoint.features.length - 1; i++) { const coords = jsonPoint.features[i].geometry.coordinates; const next_coords = jsonPoint.features[i + 1].geometry.coordinates; jsonLine.features.push({ type: 'Feature', geometry: { type: 'LineString', coordinates: [coords, next_coords], }, }); } map.getSource('line' + layerId).setData(jsonLine); } } /** * 添加dom * @param {*} jsonPoint 点集 */ function addMeasureRes(jsonPoint) { if (isP && jsonPoint.features.length > 0) { removedom(); const pointList = []; for (let i = 0; i < jsonPoint.features.length; i++) { const coords = jsonPoint.features[i].geometry.coordinates; pointList.push(coords); const close = document.createElement('div'); close.setAttribute('class', `measure-result ${layerId} close`); close.onclick = __e => { // 移除点 __e.stopPropagation(); removePoint(coords); map.off('mousemove', onMouseMove); map.off('mousedown', onmousedown); if (catchMark) { catchMark.remove(); } }; const clear = document.createElement('div'); clear.setAttribute('class', `measure-result ${layerId} clear`); clear.onclick = __e => { // 全部删除 __e.stopPropagation(); removeLayer(); map.off('mousemove', onMouseMove); map.off('mousedown', onmousedown); if (catchMark) { catchMark.remove(); } }; const edit = document.createElement('div'); edit.setAttribute('class', `measure-result ${layerId} edit`); edit.onclick = __e => { // 编辑线 __e.stopPropagation(); map.off('mousemove', onMouseMove); map.off('mousedown', onmousedown); if (catchMark) { catchMark.remove(); } editLine(); }; const element = document.createElement('div'); element.setAttribute('class', 'measure-result ' + layerId); const option = { element: element, anchor: 'left', offset: [8, 0], }; element.innerHTML = i === 0 ? '起点' : getLength(pointList); if ((jsonPoint.features.length === i + 1) & !isMeasure) { element.appendChild(edit); element.appendChild(clear); } element.appendChild(close); new mapboxgl.Marker(option).setLngLat(coords).addTo(map); } } } /** * 移除点 * @param {*} coords 点坐标 */ function removePoint(coords) { if (jsonPoint.features.length > 0) { if (jsonPoint.features.length === 2) { jsonPoint.features = []; jsonLine.features = []; map.getSource('points' + layerId).setData(jsonPoint); map.getSource('line' + layerId).setData(jsonLine); removedom(); } else { for (let i = 0; i < jsonPoint.features.length; i++) { if ( (jsonPoint.features[i].geometry.coordinates[0] === coords[0]) & (jsonPoint.features[i].geometry.coordinates[1] === coords[1]) ) { jsonPoint.features.splice(i, 1); } } drawLine(jsonPoint); addMeasureRes(jsonPoint); map.getSource('points' + layerId).setData(jsonPoint); } } } /** * 计算长度 * @param {*} pointList * @returns */ function getLength(pointList) { const line = turf.lineString(pointList); let len = turf.length(line); if (len < 1) { len = Math.round(len * 1000) + '米'; } else { len = len.toFixed(2) + '公里'; } return len; } /** * 移除dom */ function removedom() { const dom = document.getElementsByClassName('measure-result ' + layerId); const len = dom.length; if (len) { for (let i = len - 1; i >= 0; i--) { if (dom[i]) dom[i].remove(); } } } /** * 移除图层 */ function removeLayer() { jsonPoint.features = []; jsonLine.features = []; if (map.getLayer('points' + layerId)) map.removeLayer('points' + layerId); if (map.getLayer('line' + layerId)) map.removeLayer('line' + layerId); removedom(); } /** * 鼠标move事件 * @param {} _e */ function mouseMove(_e) { if (isMeasure) { map.getCanvas().style.cursor = 'default'; var coords = [_e.lngLat.lng, _e.lngLat.lat]; const jsonp = { type: 'Feature', geometry: { type: 'Point', coordinates: coords, }, }; map.getSource('point-move' + layerId).setData(jsonp); if (jsonPoint.features.length > 0) { const pointList = []; for (let i = 0; i < jsonPoint.features.length; i++) { const coord = jsonPoint.features[i].geometry.coordinates; pointList.push(coord); } pointList.push(coords); const prev = jsonPoint.features[jsonPoint.features.length - 1]; const jsonl = { type: 'Feature', geometry: { type: 'LineString', coordinates: [prev.geometry.coordinates, coords], }, }; map.getSource('line-move' + layerId).setData(jsonl); ele.innerHTML = getLength(pointList); tooltip.setLngLat(coords); } } } /** * 绘制完成 * @param {*} _e */ function finish(_e) { if (isMeasure) { isMeasure = false; var coords = [_e.lngLat.lng, _e.lngLat.lat]; removePoint(coords); if (map.getLayer('point-move' + layerId)) map.removeLayer('point-move' + layerId); if (map.getLayer('line-move' + layerId)) map.removeLayer('line-move' + layerId); map.getCanvas().style.cursor = 'default'; tooltip.remove(); let features = map.getSource('line' + layerId)._data.features; let coordinates = features.map(i => turf.coordAll(i)[0]); if (coordinates.length == 1) coordinates = turf.coordAll(features[0]); callback(turf.lineString(coordinates)); } } map.on('click', function (_e) { addPointforJSON(_e); }); map.on('mousemove', function (_e) { mouseMove(_e); }); map.on('dblclick', function (_e) { finish(_e); }); /** * 编辑测量线 */ function editLine() { catchMark = createMarker(); UpdataGeolist(); map.on('mousemove', onMouseMove); map.on('mousedown', onmousedown); } function onMouseMove(e) { const moveCoord = [e.lngLat.lng, e.lngLat.lat]; if (jsonPoint.features.length > 1) { // 计算当前指针与线段最近的点 pointOnLine = getNearestPointOnLine(Geolist, moveCoord); // 自己计算 const screenOnLine = Object.values(map.project(pointOnLine)); // 线上屏幕坐标 const screenP = [e.point.x, e.point.y]; const screenDist = screenDistance(screenOnLine, screenP); // 距离 if (screenDist < 15) { isEdit = true; catchMark.setLngLat(pointOnLine).addTo(map); catchMark.getElement().style.display = 'block'; } else { isEdit = false; catchMark.getElement().style.display = 'none'; } } else { isEdit = false; catchMark.getElement().style.display = 'none'; map.dragPan.enable(); } } function onmousedown(e) { if (isEdit) { map.dragPan.disable(); let isExist = false; // 首先判断编辑点是否是存在(存在修改原来点,不存在新加点) for (let i = 0; i < jsonPoint.features.length; i++) { const coord = jsonPoint.features[i].geometry.coordinates; if (coord[0] === pointOnLine[0] && coord[1] === pointOnLine[1]) { isExist = true; } } // 获取编辑点在列表中的位置 dragPointOrder = getDragCoords(pointOnLine, Geolist); if (!isExist) { // 添加编辑点 const point = { type: 'Feature', geometry: { type: 'Point', coordinates: pointOnLine, }, properties: { id: String(new Date().getTime()), }, }; jsonPoint.features.splice(dragPointOrder, 0, point); // 更新绘制要素 updataFeature(); } map.on('mousemove', onDrag); map.on('mouseup', onMouseup); } } function onDrag(e) { // 开始计时 // var start = new Date().getTime() const movePoint = [e.lngLat.lng, e.lngLat.lat]; // 点跟随鼠标移动 jsonPoint.features[dragPointOrder].geometry.coordinates = movePoint; // 更新绘制要素 updataFeature(); // 计时结束 // var end1 = new Date().getTime() // console.log('渲染时间:', end1 - start + 'ms') } // 更新绘制要素 function updataFeature() { UpdataGeolist(); map.getSource('points' + layerId).setData(jsonPoint); drawLine(jsonPoint); addMeasureRes(jsonPoint); } function onMouseup(e) { map.off('mousemove', onDrag); map.dragPan.enable(); } // 创建Marker function createMarker() { const markerParam = { map: this.map, lngLat: [0, 0], height: 13, width: 13, size: 13, isDrag: false, cursor: 'default', }; return new mapboxgl.Marker().setLngLat(markerParam.lngLat).setDraggable(false).addTo(markerParam.map); } // 更新点集 function UpdataGeolist() { Geolist = []; for (let i = 0; i < jsonPoint.features.length; i++) { const coord = jsonPoint.features[i].geometry.coordinates; Geolist.push(coord); } } // 计算点到直线最近距离的点 function getNearestPointOnLine(list, moveCoord) { var dis, point1, point2; for (let i = 0; i < list.length - 1; i++) { const distance = getNearestDistance(moveCoord, list[i], list[i + 1]); if (i === 0) { dis = distance; point1 = list[i]; point2 = list[i + 1]; } else { if (distance < dis) { dis = distance; point1 = list[i]; point2 = list[i + 1]; } } } const Point = getNearestPoint(moveCoord, point1, point2); return Point; } // 计算点point到线段point1, point2最近距离 function getNearestDistance(point, point1, point2) { const P = {}; const A = {}; const B = {}; P.x = point[0]; P.y = point[1]; A.x = point1[0]; A.y = point1[1]; B.x = point2[0]; B.y = point2[1]; // 计算向量AP和向量AB的点积 const dotProduct = (P.x - A.x) * (B.x - A.x) + (P.y - A.y) * (B.y - A.y); // 计算向量AB的长度的平方 const lengthSquare = (B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y); // 计算点P到线段AB的投影点C const t = dotProduct / lengthSquare; const C = { x: A.x + t * (B.x - A.x), y: A.y + t * (B.y - A.y) }; // 如果点C在线段AB内,则点P到线段AB的最近距离为PC的长度;否则,点P到线段AB的最近距离为PA或PB中的较小值。 const isInside = dotProduct >= 0 && dotProduct <= lengthSquare; if (isInside) { return Math.sqrt((P.x - C.x) * (P.x - C.x) + (P.y - C.y) * (P.y - C.y)); } else { return Math.min( Math.sqrt((P.x - A.x) * (P.x - A.x) + (P.y - A.y) * (P.y - A.y)), Math.sqrt((P.x - B.x) * (P.x - B.x) + (P.y - B.y) * (P.y - B.y)) ); } } // 计算点到直线最近的点 point点坐标,point1, point2直线两个端点 function getNearestPoint(point, point1, point2) { var x, y, x0, y0, x1, y1, x2, y2; x0 = point[0]; y0 = point[1]; x1 = point1[0]; y1 = point1[1]; x2 = point2[0]; y2 = point2[1]; if (x1 !== x2 && y1 !== y2) { const a = (y2 - y1) / (x2 - x1); const b = y1 - a * x1; const k2 = -1 / a; const b2 = y0 - k2 * x0; x = (b2 - b) / (a - k2); y = a * x + b; } else if (x1 === x2) { x = x1; y = y0; } else if (y1 === y2) { x = x0; y = y1; } // 点不能超出线段 if (x1 < x2) { if (x2 < x) { x = x2; } else if (x < x1) { x = x1; } } else { if (x1 < x) { x = x1; } else if (x < x2) { x = x2; } } if (y1 < y2) { if (y2 < y) { y = y2; } else if (y < y1) { y = y1; } } else { if (y1 < y) { y = y1; } else if (y < y2) { y = y2; } } // 点吸附端点 const screenX0 = Object.values(map.project([x0, y0])); // 屏幕坐标 const screenX1 = Object.values(map.project([x1, y1])); // 屏幕坐标 const screenX2 = Object.values(map.project([x2, y2])); // 屏幕坐标 const screenDistX1 = screenDistance(screenX0, screenX1); // 距离 const screenDistX2 = screenDistance(screenX0, screenX2); // 距离 if (screenDistX1 < 10) { x = x1; y = y1; } if (screenDistX2 < 10) { x = x2; y = y2; } return [x, y]; } // 屏幕距离 function screenDistance(point1, point2) { const x2 = Math.pow(point1[0] - point2[0], 2); const y2 = Math.pow(point1[1] - point2[1], 2); const dist = Math.sqrt(x2 + y2); return dist; } // 计算编辑点在线段上的添加位置 function getDragCoords(coords, list) { var x, y, x1, y1, x2, y2; let index = 0; x = coords[0]; y = coords[1]; for (let i = 0; i < list.length - 1; i++) { x1 = list[i][0]; y1 = list[i][1]; x2 = list[i + 1][0]; y2 = list[i + 1][1]; if (x === x1 && y === y1) { index = i; break; } else { // 计算线段的长度 const length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); // 计算点到线段起点的距离 const distance1 = Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2)); // 计算点到线段终点的距离 const distance2 = Math.sqrt(Math.pow(x - x2, 2) + Math.pow(y - y2, 2)); // 如果点到线段两个端点的距离之和等于线段的长度,则点在线段上 if (Math.abs(length - distance1 - distance2) < 0.00001) { index = i + 1; break; } } } return index; } } /** * 测量面积 * @param {*} layerId */ measureArea(layerId, isP = true, callback = () => {}) { this.layerAreaList.push(layerId); var isMeasure = true; const map = this.map; map.doubleClickZoom.disable(); let catchMark = null; let isEdit = false; let Geolist = []; let dragPointOrder = 0; let pointOnLine = [0, 0]; const jsonPoint = { type: 'FeatureCollection', features: [], }; const jsonLine = { type: 'FeatureCollection', features: [], }; const ele = document.createElement('div'); ele.setAttribute('class', 'measure-move'); const option = { element: ele, anchor: 'left', offset: [8, 0], }; const tooltip = new mapboxgl.Marker(option).setLngLat([0, 0]); if (isP) tooltip.addTo(map); map.addSource('points-area' + layerId, { type: 'geojson', data: jsonPoint, }); map.addSource('point-move' + layerId, { type: 'geojson', data: jsonPoint, }); map.addSource('line-area' + layerId, { type: 'geojson', data: jsonLine, }); map.addSource('line-move' + layerId, { type: 'geojson', data: jsonLine, }); map.addLayer({ id: 'line-move' + layerId, type: 'line', source: 'line-move' + layerId, paint: { 'line-color': '#ff0000', 'line-width': 2, 'line-opacity': 0.65, 'line-dasharray': [5, 2], }, }); map.addLayer({ id: 'line-area' + layerId, type: 'fill', source: 'line-area' + layerId, paint: { 'fill-color': '#ff0000', 'fill-opacity': 0.1, }, }); map.addLayer({ id: 'line-area-stroke' + layerId, type: 'line', source: 'line-area' + layerId, paint: { 'line-color': '#ff0000', 'line-width': 2, 'line-opacity': 0.65, }, }); map.addLayer({ id: 'points-area' + layerId, type: 'circle', source: 'points-area' + layerId, paint: { 'circle-color': '#ffffff', 'circle-radius': 3.5, 'circle-stroke-width': 1.5, 'circle-stroke-color': '#ff0000', }, }); map.addLayer({ id: 'point-move' + layerId, type: 'circle', source: 'point-move' + layerId, paint: { 'circle-color': '#ffffff', 'circle-radius': 3.5, 'circle-stroke-width': 1.5, 'circle-stroke-color': '#ff0000', }, }); this.setDistance = () => { isMeasure = false; if (map.getLayer('point-move' + layerId)) map.removeLayer('point-move' + layerId); if (map.getLayer('line-move' + layerId)) map.removeLayer('line-move' + layerId); return isMeasure; }; /** * 添加点 * @param {*} _e */ function addPointforJSON(_e) { if (isMeasure) { const point = { type: 'Feature', geometry: { type: 'Point', coordinates: [_e.lngLat.lng, _e.lngLat.lat], }, properties: { id: String(new Date().getTime()), }, }; jsonPoint.features.push(point); map.getSource('points-area' + layerId).setData(jsonPoint); addMeasureRes(jsonPoint); } } /** * 添加dom * @param {*} jsonPoint 点集 */ function addMeasureRes(jsonPoint) { if (isP && jsonPoint.features.length > 0) { removedom(); const pointList = []; for (let i = 0; i < jsonPoint.features.length; i++) { const coords = jsonPoint.features[i].geometry.coordinates; pointList.push(coords); const close = document.createElement('div'); close.setAttribute('class', `measure-result ${layerId} close`); close.onclick = __e => { // 移除点 __e.stopPropagation(); removePoint(coords); map.off('mousemove', onMouseMove); map.off('mousedown', onmousedown); if (catchMark) { catchMark.remove(); } }; if ((jsonPoint.features.length === i + 1) & !isMeasure) { const clear = document.createElement('div'); clear.setAttribute('class', `measure-result ${layerId} clear`); clear.onclick = __e => { // 全部移除 __e.stopPropagation(); removeLayer(); map.off('mousemove', onMouseMove); map.off('mousedown', onmousedown); if (catchMark) { catchMark.remove(); } }; const edit = document.createElement('div'); edit.setAttribute('class', `measure-result ${layerId} edit`); edit.onclick = __e => { // 编辑 __e.stopPropagation(); map.off('mousemove', onMouseMove); map.off('mousedown', onmousedown); if (catchMark) { catchMark.remove(); } editArea(); }; const element = document.createElement('div'); element.setAttribute('class', 'measure-result ' + layerId); const option = { element: element, anchor: 'left', offset: [0, 0], }; element.innerHTML = getArea(pointList); element.appendChild(edit); element.appendChild(clear); element.appendChild(close); new mapboxgl.Marker(option).setLngLat(coords).addTo(map); } else { const option = { element: close, anchor: 'left', offset: [5, -15], }; new mapboxgl.Marker(option).setLngLat(coords).addTo(map); } } } } /** * 计算面积 * @param {*} pointList * @returns */ function getArea(pointList) { pointList.push(pointList[0]); const polygon = turf.polygon([pointList]); let area = turf.area(polygon); if (area < 10000) { area = Math.round(area) + '平方米'; } else { area = (area / 1000000).toFixed(5) + '平方公里'; } return area; } /** * 移除点 * @param {*} coords 点坐标 */ function removePoint(coords) { if (jsonPoint.features.length > 0) { if (jsonPoint.features.length === 3) { jsonPoint.features = []; jsonLine.features = []; map.getSource('points-area' + layerId).setData(jsonPoint); map.getSource('line-area' + layerId).setData(jsonLine); removedom(); } else { for (let i = 0; i < jsonPoint.features.length; i++) { if ( (jsonPoint.features[i].geometry.coordinates[0] === coords[0]) & (jsonPoint.features[i].geometry.coordinates[1] === coords[1]) ) { jsonPoint.features.splice(i, 1); } } addMeasureRes(jsonPoint); map.getSource('points-area' + layerId).setData(jsonPoint); const pointList = []; for (let i = 0; i < jsonPoint.features.length; i++) { const coord = jsonPoint.features[i].geometry.coordinates; pointList.push(coord); } const pts = pointList.concat([pointList[0]]); const jsona = { type: 'Feature', geometry: { type: 'Polygon', coordinates: [pts], }, }; map.getSource('line-area' + layerId).setData(jsona); } } } /** * 移除dom */ function removedom() { const dom = document.getElementsByClassName('measure-result ' + layerId); const len = dom.length; if (len) { for (let i = len - 1; i >= 0; i--) { if (dom[i]) dom[i].remove(); } } } /** * 移除图层 */ function removeLayer() { jsonPoint.features = []; jsonLine.features = []; if (map.getLayer('points-area' + layerId)) map.removeLayer('points-area' + layerId); if (map.getLayer('line-area' + layerId)) map.removeLayer('line-area' + layerId); if (map.getLayer('line-area-stroke' + layerId)) map.removeLayer('line-area-stroke' + layerId); removedom(); } /** * 鼠标move事件 * @param {} _e */ function mouseMove(_e) { if (isMeasure) { map.getCanvas().style.cursor = 'default'; const coords = [_e.lngLat.lng, _e.lngLat.lat]; const jsonp = { type: 'Feature', geometry: { type: 'Point', coordinates: coords, }, }; map.getSource('point-move' + layerId).setData(jsonp); if (jsonPoint.features.length > 0) { if (jsonPoint.features.length === 1) { const prev = jsonPoint.features[jsonPoint.features.length - 1]; const jsonl = { type: 'Feature', geometry: { type: 'LineString', coordinates: [prev.geometry.coordinates, coords], }, }; map.getSource('line-move' + layerId).setData(jsonl); } else { const json = { type: 'FeatureCollection', features: [], }; map.getSource('line-move' + layerId).setData(json); const pointList = []; for (let i = 0; i < jsonPoint.features.length; i++) { const coord = jsonPoint.features[i].geometry.coordinates; pointList.push(coord); } pointList.push(coords); const pts = pointList.concat([pointList[0]]); const jsona = { type: 'Feature', geometry: { type: 'Polygon', coordinates: [pts], }, }; map.getSource('line-area' + layerId).setData(jsona); ele.innerHTML = getArea(pointList); tooltip.setLngLat(coords); } } } } /** * 绘制完成 * @param {*} _e */ function finish(_e) { if (isMeasure) { isMeasure = false; const coords = [_e.lngLat.lng, _e.lngLat.lat]; removePoint(coords); if (map.getLayer('point-move' + layerId)) map.removeLayer('point-move' + layerId); if (map.getLayer('line-move' + layerId)) map.removeLayer('line-move' + layerId); map.getCanvas().style.cursor = 'default'; tooltip.remove(); callback(map.getSource('line-area' + layerId)._data); } } map.on('click', function (_e) { addPointforJSON(_e); }); map.on('dblclick', function (_e) { finish(_e); }); map.on('mousemove', function (_e) { mouseMove(_e); }); /** * 编辑测量面 */ function editArea() { catchMark = createMarker(); UpdataGeolist(); map.on('mousemove', onMouseMove); map.on('mousedown', onmousedown); } function onMouseMove(e) { const moveCoord = [e.lngLat.lng, e.lngLat.lat]; if (jsonPoint.features.length > 1) { // 计算当前指针与线段的距离 pointOnLine = getNearestPointOnLine(Geolist, moveCoord); // 线上实际坐标 const screenOnLine = Object.values(map.project(pointOnLine)); // 线上屏幕坐标 const screenP = [e.point.x, e.point.y]; const screenDist = screenDistance(screenOnLine, screenP); // 距离 if (screenDist < 15) { isEdit = true; catchMark.setLngLat(pointOnLine).addTo(map); catchMark.getElement().style.display = 'block'; } else { isEdit = false; catchMark.getElement().style.display = 'none'; } } else { isEdit = false; catchMark.getElement().style.display = 'none'; map.dragPan.enable(); } } function onmousedown(e) { if (isEdit) { map.dragPan.disable(); let isExist = false; // 首先判断编辑点是否是存在(存在修改原来点,不存在新加点) for (let i = 0; i < jsonPoint.features.length; i++) { const coord = jsonPoint.features[i].geometry.coordinates; if (coord[0] === pointOnLine[0] && coord[1] === pointOnLine[1]) { isExist = true; } } // 获取编辑点在列表中的位置 dragPointOrder = getDragCoords(pointOnLine, Geolist); if (!isExist) { // 添加编辑点 const point = { type: 'Feature', geometry: { type: 'Point', coordinates: pointOnLine, }, properties: { id: String(new Date().getTime()), }, }; jsonPoint.features.splice(dragPointOrder, 0, point); // 更新绘制要素 updataFeature(); } map.on('mousemove', onDrag); map.on('mouseup', onMouseup); } } function onDrag(e) { const movePoint = [e.lngLat.lng, e.lngLat.lat]; // 点跟随鼠标移动 jsonPoint.features[dragPointOrder].geometry.coordinates = movePoint; // 更新绘制要素 updataFeature(); } // 更新绘制要素 function updataFeature() { UpdataGeolist(); const pts = Geolist; const jsona = { type: 'Feature', geometry: { type: 'Polygon', coordinates: [pts], }, }; map.getSource('line-area' + layerId).setData(jsona); map.getSource('points-area' + layerId).setData(jsonPoint); addMeasureRes(jsonPoint); } function onMouseup(e) { map.off('mousemove', onDrag); map.dragPan.enable(); } // 创建Marker function createMarker() { const markerParam = { map: this.map, lngLat: [0, 0], height: 13, width: 13, size: 13, isDrag: false, cursor: 'default', }; return new mapboxgl.Marker().setLngLat(markerParam.lngLat).setDraggable(false).addTo(markerParam.map); } // 更新点集 function UpdataGeolist() { Geolist = []; for (let i = 0; i < jsonPoint.features.length; i++) { const coord = jsonPoint.features[i].geometry.coordinates; Geolist.push(coord); } Geolist.push(Geolist[0]); } // 计算点到直线最近距离的点 function getNearestPointOnLine(list, moveCoord) { var dis, point1, point2; for (let i = 0; i < list.length - 1; i++) { const distance = getNearestDistance(moveCoord, list[i], list[i + 1]); if (i === 0) { dis = distance; point1 = list[i]; point2 = list[i + 1]; } else { if (distance < dis) { dis = distance; point1 = list[i]; point2 = list[i + 1]; } } } const Point = getNearestPoint(moveCoord, point1, point2); return Point; } // 计算点point到线段point1, point2最近距离 function getNearestDistance(point, point1, point2) { const P = {}; const A = {}; const B = {}; P.x = point[0]; P.y = point[1]; A.x = point1[0]; A.y = point1[1]; B.x = point2[0]; B.y = point2[1]; // 计算向量AP和向量AB的点积 const dotProduct = (P.x - A.x) * (B.x - A.x) + (P.y - A.y) * (B.y - A.y); // 计算向量AB的长度的平方 const lengthSquare = (B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y); // 计算点P到线段AB的投影点C const t = dotProduct / lengthSquare; const C = { x: A.x + t * (B.x - A.x), y: A.y + t * (B.y - A.y) }; // 如果点C在线段AB内,则点P到线段AB的最近距离为PC的长度;否则,点P到线段AB的最近距离为PA或PB中的较小值。 const isInside = dotProduct >= 0 && dotProduct <= lengthSquare; if (isInside) { return Math.sqrt((P.x - C.x) * (P.x - C.x) + (P.y - C.y) * (P.y - C.y)); } else { return Math.min( Math.sqrt((P.x - A.x) * (P.x - A.x) + (P.y - A.y) * (P.y - A.y)), Math.sqrt((P.x - B.x) * (P.x - B.x) + (P.y - B.y) * (P.y - B.y)) ); } } // 计算点到直线最近的点 point点坐标,point1, point2直线两个端点 function getNearestPoint(point, point1, point2) { var x, y, x0, y0, x1, y1, x2, y2; x0 = point[0]; y0 = point[1]; x1 = point1[0]; y1 = point1[1]; x2 = point2[0]; y2 = point2[1]; if (x1 !== x2 && y1 !== y2) { const a = (y2 - y1) / (x2 - x1); const b = y1 - a * x1; const k2 = -1 / a; const b2 = y0 - k2 * x0; x = (b2 - b) / (a - k2); y = a * x + b; } else if (x1 === x2) { x = x1; y = y0; } else if (y1 === y2) { x = x0; y = y1; } // 点不能超出线段 if (x1 < x2) { if (x2 < x) { x = x2; } else if (x < x1) { x = x1; } } else { if (x1 < x) { x = x1; } else if (x < x2) { x = x2; } } if (y1 < y2) { if (y2 < y) { y = y2; } else if (y < y1) { y = y1; } } else { if (y1 < y) { y = y1; } else if (y < y2) { y = y2; } } // 点吸附端点 const screenX0 = Object.values(map.project([x0, y0])); // 屏幕坐标 const screenX1 = Object.values(map.project([x1, y1])); // 屏幕坐标 const screenX2 = Object.values(map.project([x2, y2])); // 屏幕坐标 const screenDistX1 = screenDistance(screenX0, screenX1); // 距离 const screenDistX2 = screenDistance(screenX0, screenX2); // 距离 if (screenDistX1 < 10) { x = x1; y = y1; } if (screenDistX2 < 10) { x = x2; y = y2; } return [x, y]; } // 屏幕距离 function screenDistance(point1, point2) { const x2 = Math.pow(point1[0] - point2[0], 2); const y2 = Math.pow(point1[1] - point2[1], 2); const dist = Math.sqrt(x2 + y2); return dist; } // 计算编辑点在线段上的添加位置 function getDragCoords(coords, list) { var x, y, x1, y1, x2, y2; let index = 0; x = coords[0]; y = coords[1]; for (let i = 0; i < list.length - 1; i++) { x1 = list[i][0]; y1 = list[i][1]; x2 = list[i + 1][0]; y2 = list[i + 1][1]; if (x === x1 && y === y1) { index = i; break; } else { // 计算线段的长度 const length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); // 计算点到线段起点的距离 const distance1 = Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2)); // 计算点到线段终点的距离 const distance2 = Math.sqrt(Math.pow(x - x2, 2) + Math.pow(y - y2, 2)); // 如果点到线段两个端点的距离之和等于线段的长度,则点在线段上 if (Math.abs(length - distance1 - distance2) < 0.00001) { index = i + 1; break; } } } return index; } } drawPoint(layerId, callback = () => {}) { this.layerPointList.push(layerId); const map = this.map; map.doubleClickZoom.disable(); const jsonPoint = { type: 'FeatureCollection', features: [], }; map.addSource('points' + layerId, { type: 'geojson', data: jsonPoint, }); map.addLayer({ id: 'points' + layerId, type: 'circle', source: 'points' + layerId, paint: { 'circle-color': '#ffffff', 'circle-radius': 3.5, 'circle-stroke-width': 1.5, 'circle-stroke-color': '#ff0000', }, }); /** * 添加点 * @param {*} _e */ function addPointforJSON(_e) { const point = { type: 'Feature', geometry: { type: 'Point', coordinates: [_e.lngLat.lng, _e.lngLat.lat], }, properties: { id: String(new Date().getTime()), }, }; jsonPoint.features.push(point); map.getSource('points' + layerId).setData(jsonPoint); map.off('click', addPointforJSON); callback(point); } map.on('click', addPointforJSON); } drawRectangle(layerId, callback = () => {}) { this.layerPointList.push(layerId); const map = this.map; map.doubleClickZoom.disable(); const jsonPoint = { type: 'FeatureCollection', features: [], }; const jsonArea = { type: 'FeatureCollection', features: [], }; map.addSource('points' + layerId, { type: 'geojson', data: jsonPoint, }); map.addSource('line-area' + layerId, { type: 'geojson', data: jsonArea, }); map.addLayer({ id: 'line-area' + layerId, type: 'fill', source: 'line-area' + layerId, paint: { 'fill-color': '#ff0000', 'fill-opacity': 0.1, }, }); map.addLayer({ id: 'points' + layerId, type: 'circle', source: 'points' + layerId, paint: { 'circle-color': '#ffffff', 'circle-radius': 3.5, 'circle-stroke-width': 1.5, 'circle-stroke-color': '#ff0000', }, }); /** * 添加点 * @param {*} _e */ function addPointforJSON(_e) { const point = { type: 'Feature', geometry: { type: 'Point', coordinates: [_e.lngLat.lng, _e.lngLat.lat], }, properties: { id: String(new Date().getTime()), }, }; jsonPoint.features.push(point); if (jsonPoint.features.length == 2) { map.off('click', addPointforJSON); map.off('mousemove', mouseRectangle); callback(jsonArea.features[0]); } map.getSource('points' + layerId).setData(jsonPoint); } function mouseRectangle(_e) { if (!jsonPoint.features.length) { const point = { type: 'Feature', geometry: { type: 'Point', coordinates: [_e.lngLat.lng, _e.lngLat.lat], }, properties: { id: String(new Date().getTime()), }, }; return map.getSource('points' + layerId).setData({ type: 'FeatureCollection', features: [point] }); } let coordinates = jsonPoint.features[0].geometry.coordinates; let bbox = coordinates.concat([_e.lngLat.lng, _e.lngLat.lat]).sort((a, b) => a - b); let polygon = turf.bboxPolygon([bbox[2], bbox[0], bbox[3], bbox[1]]); jsonArea.features = [polygon]; map.getSource('line-area' + layerId).setData(jsonArea); } map.on('click', addPointforJSON); map.on('mousemove', mouseRectangle); } /** * 清除所有测量要素 */ clearMeasureAll() { const dom = document.getElementsByClassName('measure-result'); const len = dom.length; if (len) { for (let i = len - 1; i >= 0; i--) { if (dom[i]) dom[i].remove(); } } if (this.layerDistanceList) { for (let i = 0; i < this.layerDistanceList.length; i++) { const layerid = this.layerDistanceList[i]; try { if (this.map.getLayer('points' + layerid)) this.map.removeLayer('points' + layerid); if (this.map.getLayer('line' + layerid)) this.map.removeLayer('line' + layerid); if (this.map.getLayer('point-move' + layerid)) this.map.removeLayer('point-move' + layerid); if (this.map.getLayer('line-move' + layerid)) this.map.removeLayer('line-move' + layerid); } catch (error) { console.log(error); } } } this.layerDistanceList = []; if (this.layerAreaList) { for (let i = 0; i < this.layerAreaList.length; i++) { const layerid = this.layerAreaList[i]; try { if (this.map.getLayer('points-area' + layerid)) this.map.removeLayer('points-area' + layerid); if (this.map.getLayer('line-area' + layerid)) this.map.removeLayer('line-area' + layerid); if (this.map.getLayer('line-area-stroke' + layerid)) this.map.removeLayer('line-area-stroke' + layerid); if (this.map.getLayer('point-move' + layerid)) this.map.removeLayer('point-move' + layerid); if (this.map.getLayer('line-move' + layerid)) this.map.removeLayer('line-move' + layerid); } catch (error) { console.log(error); } } } this.layerAreaList = []; if (this.layerPointList) { for (let i = 0; i < this.layerPointList.length; i++) { const layerid = this.layerPointList[i]; try { if (this.map.getLayer('points' + layerid)) this.map.removeLayer('points' + layerid); if (this.map.getLayer('line-area' + layerid)) this.map.removeLayer('line-area' + layerid); } catch (error) { console.log(error); } } } this.layerPointList = []; this.map.doubleClickZoom.enable(); } }; });