(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,features=[],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: 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', }, }) if(features.length) return; // 清除面积测量 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(2) + '平方公里' } 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,features=[],callback=()=>{}){ this.layerPointList.push(layerId); const map = this.map; map.doubleClickZoom.disable(); const jsonPoint = { type: 'FeatureCollection', features: 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', }, }) if(features.length) return; /** * 添加点 * @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() } clearById (layerid){ 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) } } })));