---
title: 测量工具
source: https://vjmap.com/guide/measureTool.html
---

# 测量工具

 CAD如今在各个领域均得到了普遍的应用并大大提高了工程技术人员的工作效率。在桌面端，AutoCAD测量工具已经非常强大；然后在Web端，如何准确、快速的对CAD图在Web进行测量呢?

# 功能

- 能Web在线打开AutoCAD图形
- 测量距离
- 测量面积
- 测量角度
- 坐标标注
- 测量时能捕捉Web端CAD图形上面的坐标，提高准确度
- 测量时能对捕捉进行开关启用
- 测量时能启用正交模式
- 测量时能自定义右键菜单功能
- 能进行连续测量
- 测量结束后，能删除已测量的结果

# 效果


## 测量距离

![image-20220623192548087](https://vjmap.com/blogimages/image-20220623192548087.png)

## 测量面积

![image-20220623192725011](https://vjmap.com/blogimages/image-20220623192725011.png)

## 测量角度

![image-20220623193100555](https://vjmap.com/blogimages/image-20220623193100555.png)

## 坐标标注

![image-20220623193145638](https://vjmap.com/blogimages/image-20220623193145638.png)

## 其他功能

在测量过程中，按`Alt`键可开启关闭捕捉；按`Ctrl`键可启用正交模式；按`退格键`可删除上一个点；按`ESC`键取消测量；按`Enter`键结束测量; 按`右键`弹出上下文菜单 

![image-20220623194559185](https://vjmap.com/blogimages/image-20220623194559185.png)


<mapFrameEx>
<pre>
// --测距--
// 地图服务对象
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时，这里也传深色背景样式
})
if (res.error) {
    // 如果打开出错
    message.error(res.error)
}
// 获取地图范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 根据地图范围建立几何投影坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: svc.rasterStyle(), // 样式，这里是栅格样式
    center: prj.toLngLat(mapExtent.center()), // 设置地图中心点
    zoom: 2, // 设置地图缩放级别
    renderWorldCopies: false, // 不显示多屏地图
    doubleClickZoom: false // 禁用鼠标双击放大
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
map.fitMapBounds();
const addStyle = cssStyle =&gt; {
    let style = `&lt;style&gt;${cssStyle }&lt;/style&gt;`;
    let ele = document.createElement('div');
    ele.innerHTML = style;
    document.getElementsByTagName('head')[0].appendChild(ele.firstElementChild)
}
// 增加一个popup自定义的样式
addStyle(`
                .custom-popup .vjmapgis-popup-content {
                    background-color: #0EFFC2;
                    opacity: 0.7;
                }
                .custom-popup .vjmapgis-popup-tip {
                    border-top-color: #0EFFC2;
                    opacity: 0.7;
                }
            `)
// 跟随鼠标动的弹框
let popup = null;
const setPopupText = text =&gt; {
    if (text) {
        if (!popup) {
            popup = new vjmap.Popup({
                className: "custom-popup",
                closeOnClick: false,
                closeButton: false
            })
                .setHTML(text)
                .setMaxWidth("500px")
                .trackPointer()
                .addTo(map)
        }
        else {
            popup.setHTML(text);
        }
    } else {
        // 如果为空时，则删除popup
        if (popup) {
            popup.setLngLat([0,0]); // 取消trackPointer
            popup.remove();
            popup = null;
        }
    }
}
let snapObj = {}; // 设置的捕捉的实体
// 测量距离
const measureDist = async ()=&gt; {
    let isDrawing = false;
    let line = await vjmap.Draw.actionDrawLineSting(map, {
        api: {
            getSnapFeatures: snapObj //要捕捉的数据项在后面，通过属性features赋值
        },
        updatecoordinate: (e) =&gt; {
            if (!e.lnglat) return;
            /*
            // 如果需要指定事先指定起点，可用如下代码
            if (e.state.line.coordinates.length == 1) {
                // 第一个点时自动加最开始的点做为起点
                e.state.line.coordinates.unshift(map.toLngLat([
                    587662045.9814225, // cad坐标x
                    3103973655.658176 // cad坐标y
                ]))
                e.state.currentVertexPosition = 1;
            }*/
            isDrawing = true;
            const co = map.fromLngLat(e.feature.coordinates[e.feature.coordinates.length - 1]);
            let html = `当前坐标:&lt;span style="color: #ff0000"&gt; ${co.x.toFixed(2)}, ${co.y.toFixed(2)}&lt;/span&gt;`;
            if (e.feature.coordinates.length == 1) {
                html += "&lt;r/&gt;请指定要测量的第一点的坐标位置"
            } else {
                let len = e.feature.coordinates.length;
                html += `&lt;br/&gt;按Alt键取捕捉; Ctrl键启用正交; 退格键删除上一个点`
                html += `&lt;br/&gt;距上一点距离: &lt;span style="color: #ff0000"&gt;${getDist([e.feature.coordinates[len - 2], e.feature.coordinates[len -1]])}&lt;/span&gt;`
                html += `;  当前总的距离: &lt;span style="color: #ff0000"&gt;${getDist(e.feature.coordinates)}&lt;/span&gt;`
            }
            setPopupText(html)
        },
        contextMenu: (e) =&gt; {
            new vjmap.ContextMenu({
                event: e.event.originalEvent,
                theme: "dark", //light
                width: "250px",
                items: [
                    {
                        label: '确认',
                        onClick: () =&gt; {
                            // 给地图发送Enter键消息即可取消，模拟按Enter键
                            map.fire("keyup", {keyCode:13})
                            setPopupText("");
                        }
                    },
                    {
                        label: '取消',
                        onClick: () =&gt; {
                            // 给地图发送ESC键消息即可取消，模拟按ESC键
                            map.fire("keyup", {keyCode:27})
                            setPopupText("");
                        }
                    },{
                        label: '删除上一个点',
                        onClick: () =&gt; {
                            // 给地图发送退格键Backspace消息即可删除上一个点，模拟按Backspace键
                            map.fire("keyup", {keyCode:8})
                        }
                    },{
                        label: '结束测距',
                        onClick: () =&gt; {
                            // 给地图发送ESC键消息即可取消，模拟按ESC键
                            map.fire("keyup", {keyCode:27})
                            isDrawing = false;
                            setPopupText("");
                        }
                    }
                ]
            });
        }
    });
    if (line.cancel) {
        setPopupText("");
        return {
            cancel: true,
            exit: isDrawing === false // 如果还没有绘制，就取消的话，就结束测距
        };// 取消操作
    }
    let color = vjmap.randomColor();
    let polyline = new vjmap.Polyline({
        data: line.features[0].geometry.coordinates,
        lineColor: color,
        lineWidth: 2
    });
    polyline.addTo(map);
    addMarkersToLine(line.features[0].geometry.coordinates, color, polyline.sourceId);
    return {
        polyline
    };
}
// 测量距离循环，直至按ESC键取消，否则测量完一条后，继续测量下一条
const measureDistLoop = async ()=&gt; {
    while(true) {
        let res = await measureDist();
        if (res.exit === true) break;
    }
}
measureDistLoop();
const clearAll  = (map)=&gt; {
    // 清空绘制的图层，如果怕误删除，每次绘制完成，把相关的 sourceId和marker记录下来。然后依次删除
    // @ts-ignore
    Object.keys(map.getStyle().sources).filter(e=&gt;e.indexOf("custom_") == 0).forEach(e=&gt; map.removeSourceEx(e));
    // 清除所有marker
    map.removeMarkers();
}
// 给线的加个点加个测量的结果值
const addMarkersToLine = (coordinates, color, sourceId) =&gt; {
    let markerTexts = [];
    for(let i = 1; i &lt; coordinates.length; i++) {
        let text = new vjmap.Text({
            text: getDist(coordinates.slice(0, i + 1)),
            anchor: "left",
            offset: [3, 0], // x,y 方向像素偏移量
            style:{     // 自定义样式
                'cursor': 'pointer',
                'opacity': 0.8,
                'padding': '6px',
                'border-radius': '12px',
                'background-color': color,
                'border-width': 0,
                'box-shadow': '0px 2px 6px 0px rgba(97,113,166,0.2)',
                'text-align': 'center',
                'font-size': '14px',
                'color': `#${color.substr(1).split("").map(c =&gt; (15 - parseInt(c,16)).toString(16)).join("")}`,
            }
        });
        text.setLngLat(coordinates[i]).addTo(map);
        markerTexts.push(text);
    }
    // 给第一个点加一个marker用来删除
    const deletePng = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAbNJREFUWEftlrFOAkEQhv9Z4XgJOCrlTgp5ABOhMBoTCws7EzUaeAx5A0sDiYmFxpLGaAkU2KqFArHi4CU4cMccAQN6HHcIwZi7cnd25tt/5maHsOCPFhwfPoAnBarhQFIIccjAkV3qCMhKKct6q1tym1pPAHVVOWMg6+ScgKuYYR7PBaCmKmw5XgLiy4b5NhzkXVVWP4BXa00zTNcXc21oOR4AjAswaX9M2n4u1yLBIoiSbmV0Zcdc0pqd1HdbWwUWDuDqRjMy8lQDM4o54sYRoJcKq6qHclePKnuScQ0pd/RWt2ztVyOhLUFckIwTvWneDiLYnXdVA18O+r/dcNXXIsozCGsMzuhGJ98DUINpAuUAqmhGe33k/JjiG9g4K2ADUFdDOQan7QAIlI8Z7YwP4CvgK/C/FahFlQcwtm0bEeNFa5qJuSpgdT2AzkFyU290H3udMBzYgBD3gnAQa5iFuQJ4eZB6A8ovW/ETgARLmfIyaPZVSZIQRTBdas326Thwx7fAzRA6SREB2l0x2ndTAfTnwH0CrGk4PinY6D5VQHyhNcwbp3N/eyDxduPprH0FPgHEyH4wGIHUYwAAAABJRU5ErkJggg==";
    let el = document.createElement('div');
    el.className = 'marker';
    el.style.backgroundImage =
        `url(${deletePng})`;
    el.style.width = '20px';
    el.style.height = '20px';
    el.style.backgroundSize = '100%';
    el.style.cursor = "pointer";
    el.addEventListener('click', function (e) {
        map.removeSourceEx(sourceId); // 删除绘制的线
        markerTexts.forEach(m =&gt; m.remove());
        markerTexts = [];
        // 多点了下，给地图发送退格键Backspace消息即可删除上一个点，模拟按Backspace键
        map.fire("keyup", {keyCode:8})
    });
    // Add markers to the map.
    let deleteMarker = new vjmap.Marker({
        element: el,
        anchor: 'right'
    });
    deleteMarker.setLngLat(coordinates[0])
        .setOffset([-5, 0])
        .addTo(map);
    markerTexts.push(deleteMarker)
    // 把坐标加进捕捉数组中。
    addSnapCoordinates(coordinates);
}
// 得到距离值
const getDist = (coordinates) =&gt; {
    let result = vjmap.Math2D.lineDist(map.fromLngLat(coordinates));
    let unit = "m";
    if (result &gt;= 1000) {
        result /= 1000;
        unit = "km";
    } else if (result &lt; 0.01) {
        result *= 100;
        unit = "cm";
    }
    return result.toFixed(2) + " " + unit;
}
// 增加捕捉点
const addSnapCoordinates = (coordinates) =&gt; {
    snapObj.features.push({
        type: "Feature",
        geometry: {
            type: "LineString",
            coordinates: [...coordinates]
        }
    })
}
// 获取捕捉点
if (map.hasVectorLayer()) {
    // 矢量样式
    // 矢量样式获取捕捉点
    snapObj.features = map.queryRenderedFeatures({layers: ['vector-layer-lines']})
} else {
    //栅格样式去服务器获取坐标点数据
    // 栅格样式获取捕捉点
    // 查询所有坐标数据,字段含义可参考https://vjmap.com/guide/svrStyleVar.html
    let res = await svc.conditionQueryFeature({ fields:"s3", condition:"s3 != ''", limit: 100000})
    res = res.result.map(e =&gt; e.s3.split(";"))
    snapObj.features = []
    for(let item of res) {
        let coordinates = []
        for(let pt of item) {
            const p = pt.split(",")
            if (p.length &gt;= 2) {
                coordinates.push(map.toLngLat([+p[0], +p[1]]))
            }
        }
        if (coordinates.length == 1) {
            snapObj.features.push({
                type: "Feature",
                geometry: {
                    type: "Point",
                    coordinates: coordinates[0]
                }
            })
        }
        else if (coordinates.length &gt; 1) {
            snapObj.features.push({
                type: "Feature",
                geometry: {
                    type: "LineString",
                    coordinates: coordinates
                }
            })
        }
    }
}
</pre>
</mapFrameEx>
<br/>
<br/>


# 代码实现

上面的案例代码已开源。访问 ([唯杰地图云端图纸管理平台](https://vjmap.com/app/cloud)  https://vjmap.com/app/cloud) ，点击`下载此案例源码`即可。

```typescript
import vjmap, { Map } from 'vjmap'
import { sleep } from '~/utils/ui';
import { getMapSnapPoints } from './snap';
let snapObj: any; // 设置的捕捉的实体
let curMeasureCmd: string; // 当前测量命令
export async function runMeasureCmd(map: Map, cmd: string) {
  curMeasureCmd = cmd;
  if (cmd != "measureCancel") {
    // 先结束当前测量
    await measureCancel(map);
    if (!snapObj) {
        // 获取地图上的捕捉点
        snapObj = {};
        getMapSnapPoints(map, snapObj);
    }
  }
  switch (cmd) {
    case "measureDist":
      measureDistLoop(map, snapObj);
      break;
    case "measureArea":
      measureAreaLoop(map, snapObj);
      break;
    case "measureAngle":
      measureAngleLoop(map, snapObj);
      break;
    case "measureCoordinate":
      measureCoordinateLoop(map, snapObj);
      break;
    case "measureCancel":
      await measureCancel(map);
      break;
  }
}

// 结束绘制
const measureCancel = async (map: Map)=> {
    // 连续发送取消键，第一次取消当前绘制，第二次退出测量
    map.fire("keyup", {keyCode:27});
    await sleep(100);
    map.fire("keyup", {keyCode:27});
    await sleep(100);
    map.setIsInteracting(false); // 没有进行交互操作了
}

let popup: vjmap.Popup | null;
const setPopupText = (text: string, map: Map) => {
    if (text) {
        if (!popup) {
            popup = new vjmap.Popup({
                className: "my-custom-popup",
                closeOnClick: false,
                closeButton: false
            })
                .setHTML(text)
                .setMaxWidth("500px")
                .trackPointer()
                .addTo(map)
        }
        else {
            popup.setHTML(text);
        }
    } else {
        // 如果为空时，则删除popup
        if (popup) {
            popup.setLngLat([0,0]); // 取消trackPointer
            popup.remove();
            popup = null;
        }
    }

}
// 测量距离循环，直至按ESC键取消，否则测量完一条后，继续测量下一条
const measureDistLoop = async (map: Map, snapObj: any)=> {
    while(true) {
        let res = await measureDist(map, snapObj);
        if (res.exit === true) break;
        if (curMeasureCmd != "measureDist") break;
    }
}

// 测量距离
const measureDist = async (map: Map, snapObj: any)=> {
    let isDrawing = false;
    let line = await vjmap.Draw.actionDrawLineSting(map, {
        api: {
            getSnapFeatures: snapObj //要捕捉的数据项在后面，通过属性features赋值
        },
        updatecoordinate: (e: any) => {
            if (!e.lnglat) return;
            isDrawing = true;
            const co = map.fromLngLat(e.feature.coordinates[e.feature.coordinates.length - 1]);
            let html = `【测量距离】当前坐标:<span style="color: #ff0000"> ${co.x.toFixed(2)}, ${co.y.toFixed(2)}</span>`;
            if (e.feature.coordinates.length == 1) {
                html += "<br/>请指定要测量的第一点的坐标位置"
            } else {
                let len = e.feature.coordinates.length;
                html += `<br/>按Alt键取捕捉; Ctrl键启用正交; 退格键删除上一个点`
                html += `<br/>距上一点距离: <span style="color: #ff0000">${getDist(map, [e.feature.coordinates[len - 2], e.feature.coordinates[len -1]])}</span>`
                html += `;  当前总的距离: <span style="color: #ff0000">${getDist(map, e.feature.coordinates)}</span>`
            }
            setPopupText(html, map)
        },
        contextMenu: (e: any) => {
            new vjmap.ContextMenu({
                event: e.event.originalEvent,
                theme: "dark", //light
                width: "250px",
                items: [
                    {
                        label: '确认',
                        onClick: () => {
                            // 给地图发送Enter键消息即可取消，模拟按Enter键
                            map.fire("keyup", {keyCode:13})
                            setPopupText("", map);
                        }
                    },
                    {
                        label: '取消',
                        onClick: () => {
                            // 给地图发送ESC键消息即可取消，模拟按ESC键
                            map.fire("keyup", {keyCode:27})
                            setPopupText("", map);
                        }
                    },{
                        label: '删除上一个点',
                        onClick: () => {
                            // 给地图发送退格键Backspace消息即可删除上一个点，模拟按Backspace键
                            map.fire("keyup", {keyCode:8})
                        }
                    },{
                        label: '结束测距',
                        onClick: () => {
                            // 给地图发送ESC键消息即可取消，模拟按ESC键
                            map.fire("keyup", {keyCode:27})
                            isDrawing = false;
                            setPopupText("", map);
                        }
                    }
                ]
            });

        }
    });
    if (line.cancel) {
        setPopupText("", map);
        return {
            cancel: true,
            exit: isDrawing === false // 如果还没有绘制，就取消的话，就结束测距
        };// 取消操作
    }

    let color = vjmap.randomColor();
    let polyline = new vjmap.Polyline({
        data: line.features[0].geometry.coordinates,
        lineColor: color,
        lineWidth: 2
    });
    polyline.addTo(map);
    addMarkersToLine(map, line.features[0].geometry.coordinates, color, polyline.sourceId || "", snapObj);
    return {
        polyline
    };
}


// 给线的加个点加个测量的结果值
const addMarkersToLine = (map: Map, coordinates: Array<[number, number]>, color: string, sourceId: string, snapObj: any) => {
    let markerTexts: any = [];
    for(let i = 1; i < coordinates.length; i++) {
        let text = new vjmap.Text({
            text: getDist(map, coordinates.slice(0, i + 1)),
            anchor: "left",
            offset: [3, 0], // x,y 方向像素偏移量
            style:{     // 自定义样式
                'cursor': 'pointer',
                'opacity': 0.8,
                'padding': '6px',
                'border-radius': '12px',
                'background-color': color,
                'border-width': 0,
                'box-shadow': '0px 2px 6px 0px rgba(97,113,166,0.2)',
                'text-align': 'center',
                'font-size': '14px',
                'color': `#${color.substring(1).split("").map(c => (15 - parseInt(c,16)).toString(16)).join("")}`,
            }
        });
        text.setLngLat(coordinates[i]).addTo(map);
        markerTexts.push(text);
    }
    // 给第一个点加一个marker用来删除
    const deletePng = "delete.png";
    let el = document.createElement('div');
    el.className = 'marker';
    el.style.backgroundImage =
        `url(${deletePng})`;
    el.style.width = '20px';
    el.style.height = '20px';
    el.style.backgroundSize = '100%';
    el.style.cursor = "pointer";

    el.addEventListener('click', function (e) {
        map.removeSourceEx(sourceId); // 删除绘制的线
        markerTexts.forEach((m: any) => m.remove());
        markerTexts = [];

        // 多点了下，给地图发送退格键Backspace消息即可删除上一个点，模拟按Backspace键
        map.fire("keyup", {keyCode:8})
    });
    // Add markers to the map.
    let deleteMarker = new vjmap.Marker({
        element: el,
        anchor: 'right'
    });
    deleteMarker.setLngLat(coordinates[0])
        .setOffset([-5, 0])
        .addTo(map);
    markerTexts.push(deleteMarker)

    // 把坐标加进捕捉数组中。
    addSnapCoordinates(snapObj, coordinates);
}
// 得到距离值
const getDist = (map: Map, coordinates: Array<[number, number]>) => {
    let result = vjmap.Math2D.lineDist(map.fromLngLat(coordinates));
    let unit = "m";
    if (result >= 1000) {
        result /= 1000;
        unit = "km";
    } else if (result < 0.01) {
        result *= 100;
        unit = "cm";
    }
    return result.toFixed(2) + " " + unit;
}
// 增加捕捉点
const addSnapCoordinates = (snapObj: any, coordinates: Array<[number, number]>) => {
    snapObj.features.push({
        type: "Feature",
        geometry: {
            type: "LineString",
            coordinates: [...coordinates]
        }
    })
}



// 测量面积
const measureArea = async (map: Map, snapObj: any)=> {
    let isDrawing = false;
    let poly = await vjmap.Draw.actionDrawPolygon(map, {
        api: {
            getSnapFeatures: snapObj //要捕捉的数据项在后面，通过属性features赋值
        },
        updatecoordinate: (e: any) => {
            if (!e.lnglat) return;
            isDrawing = true;
            const co = map.fromLngLat(e.feature.coordinates[0][e.feature.coordinates.length - 1]);
            let html = `【测量面积】当前坐标:<span style="color: #ff0000"> ${co.x.toFixed(2)}, ${co.y.toFixed(2)}</span>`;
            if (e.feature.coordinates[0].length == 1) {
                html += "<br/>请指定要测量的第一点的坐标位置"
            } else {
                html += `<br/>按Alt键取捕捉; Ctrl键启用正交; 退格键删除上一个点`
                html += `<br/>当前面积: <span style="color: #ff0000">${getArea(map, e.feature.coordinates[0])}</span>`
            }
            setPopupText(html, map)
        },
        contextMenu: (e: any) => {
            new vjmap.ContextMenu({
                event: e.event.originalEvent,
                theme: "dark", //light
                width: "250px",
                items: [
                    {
                        label: '确认',
                        onClick: () => {
                            // 给地图发送Enter键消息即可取消，模拟按Enter键
                            map.fire("keyup", {keyCode:13})
                            setPopupText("", map);
                        }
                    },
                    {
                        label: '取消',
                        onClick: () => {
                            // 给地图发送ESC键消息即可取消，模拟按ESC键
                            map.fire("keyup", {keyCode:27})
                            setPopupText("", map);
                        }
                    },{
                        label: '删除上一个点',
                        onClick: () => {
                            // 给地图发送退格键Backspace消息即可删除上一个点，模拟按Backspace键
                            map.fire("keyup", {keyCode:8})
                        }
                    },{
                        label: '结束测面积',
                        onClick: () => {
                            // 给地图发送ESC键消息即可取消，模拟按ESC键
                            map.fire("keyup", {keyCode:27})
                            isDrawing = false;
                            setPopupText("", map);
                        }
                    }
                ]
            });

        }
    });
    if (poly.cancel) {
        debugger
        setPopupText("", map);
        return {
            cancel: true,
            exit: isDrawing === false // 如果还没有绘制，就取消的话，就结束测距
        };// 取消操作
    }

    let color = vjmap.randomColor();
    let polygon = new vjmap.Polygon({
        data: poly.features[0].geometry.coordinates[0],
        fillColor: color,
        fillOpacity: 0.4,
        fillOutlineColor: color,
    });
    polygon.addTo(map);
    addMarkersToPolygon(map, poly.features[0].geometry.coordinates[0], color, polygon.sourceId || "", snapObj);
    return {
        polygon
    };
}

// 测量面积循环，直至按ESC键取消，否则测量完一条后，继续测量下一条
const measureAreaLoop = async (map: Map, snapObj: any)=> {
    while(true) {
        let res = await measureArea(map, snapObj);
        if (res.exit === true) break;
        if (curMeasureCmd != "measureArea") break;
    }
}

// 给加个测量的结果值
const addMarkersToPolygon = (map: Map, coordinates: Array<[number, number]>, color: string, sourceId: string, snapObj: any) => {
    let markerTexts: any = [];
    const center = vjmap.polygonCentroid(map.fromLngLat(coordinates));
    let text = new vjmap.Text({
        text: getArea(map, coordinates),
        anchor: "center",
        offset: [0, 0], // x,y 方向像素偏移量
        style:{     // 自定义样式
            'cursor': 'pointer',
            'opacity': 0.8,
            'padding': '6px',
            'border-radius': '12px',
            'background-color': `#${color.substring(1).split("").map(c => (15 - parseInt(c,16)).toString(16)).join("")}`,
            'border-width': 0,
            'box-shadow': '0px 2px 6px 0px rgba(97,113,166,0.2)',
            'text-align': 'center',
            'font-size': '14px',
            'color': color,
        }
    });
    text.setLngLat(map.toLngLat(center)).addTo(map);
    markerTexts.push(text);
    // 给第一个点加一个marker用来删除
    const deletePng = = "delete.png";
    let el = document.createElement('div');
    el.className = 'marker';
    el.style.backgroundImage =
        `url(${deletePng})`;
    el.style.width = '20px';
    el.style.height = '20px';
    el.style.backgroundSize = '100%';
    el.style.cursor = "pointer";

    el.addEventListener('click', function (e) {
        map.removeSourceEx(sourceId); // 删除绘制的线
        markerTexts.forEach((m: any) => m.remove());
        markerTexts = [];
    });
    // Add markers to the map.
    let deleteMarker = new vjmap.Marker({
        element: el,
        anchor: 'right'
    });
    deleteMarker.setLngLat(coordinates[0])
        .setOffset([-5, 0])
        .addTo(map);
    markerTexts.push(deleteMarker)

    // 把坐标加进捕捉数组中。
    addSnapCoordinates(snapObj, coordinates);
}
// 得到面积值
const getArea = (map: Map, coordinates: Array<[number, number]>) => {
    let result = vjmap.calcPolygonArea(map.fromLngLat(coordinates));
    let unit = "m²";
    if (result >= 1e6) {
        result /= 1e6;
        unit = "km²";
    } else if (result < 1.0/1e4) {
        result *= 1e4;
        unit = "cm²";
    }
    return result.toFixed(2) + " " + unit;
}



// 测量角度
const measureAngle = async (map: Map, snapObj: any)=> {
    let isDrawing = false;
    let line = await vjmap.Draw.actionDrawLineSting(map, {
        pointCount: 3,// 只需三个点，绘制完三个点后，自动结束
        api: {
            getSnapFeatures: snapObj //要捕捉的数据项在后面，通过属性features赋值
        },
        updatecoordinate: (e: any) => {
            if (!e.lnglat) return;
            isDrawing = true;
            const co = map.fromLngLat(e.feature.coordinates[e.feature.coordinates.length - 1]);
            let html = `【测量角度】当前坐标:<span style="color: #ff0000"> ${co.x.toFixed(2)}, ${co.y.toFixed(2)}</span>`;
            if (e.feature.coordinates.length == 1) {
                html += "<br/>请指定要测量的第一点的坐标位置"
            } else {
                let len = e.feature.coordinates.length;
                html += `<br/>按Alt键取捕捉; Ctrl键启用正交; 退格键删除上一个点`
                html += `<br/>当前角度: <span style="color: #ff0000">${getAngle(map, e.feature.coordinates).angle}</span>`
            }
            setPopupText(html, map)
        },
        contextMenu: (e: any) => {
            new vjmap.ContextMenu({
                event: e.event.originalEvent,
                theme: "dark", //light
                width: "250px",
                items: [
                    {
                        label: '确认',
                        onClick: () => {
                            // 给地图发送Enter键消息即可取消，模拟按Enter键
                            map.fire("keyup", {keyCode:13})
                            setPopupText("", map);
                        }
                    },
                    {
                        label: '取消',
                        onClick: () => {
                            // 给地图发送ESC键消息即可取消，模拟按ESC键
                            map.fire("keyup", {keyCode:27})
                            setPopupText("", map);
                        }
                    },{
                        label: '删除上一个点',
                        onClick: () => {
                            // 给地图发送退格键Backspace消息即可删除上一个点，模拟按Backspace键
                            map.fire("keyup", {keyCode:8})
                        }
                    },{
                        label: '结束测角度',
                        onClick: () => {
                            // 给地图发送ESC键消息即可取消，模拟按ESC键
                            map.fire("keyup", {keyCode:27})
                            isDrawing = false;
                            setPopupText("", map);
                        }
                    }
                ]
            });

        }
    });
    if (line.cancel) {
        setPopupText("", map);
        return {
            cancel: true,
            exit: isDrawing === false // 如果还没有绘制，就取消的话，就结束测距
        };// 取消操作
    }

    let color = vjmap.randomColor();
    let polyline = new vjmap.Polyline({
        data: line.features[0].geometry.coordinates,
        lineColor: color,
        lineWidth: 2
    });
    polyline.addTo(map);
    addMarkersToAngle(map, line.features[0].geometry.coordinates, color, polyline.sourceId || "", snapObj);
    return {
        polyline
    };
}

// 测量角度循环，直至按ESC键取消，否则测量完一条后，继续测量下一条
const measureAngleLoop = async (map: Map, snapObj: any)=> {
    while(true) {
        let res = await measureAngle(map, snapObj);
        if (res.exit === true) break;
        if (curMeasureCmd != "measureAngle") break;
    }
}



// 给加个测量的结果值
const addMarkersToAngle = (map: Map, coordinates: Array<[number, number]>, color: string, sourceId: string, snapObj: any) => {
    if (coordinates.length < 3) return;
    let markerTexts: any = [];
    let points = map.fromLngLat(coordinates);
    let textPoint = coordinates[1];
    let ang = getAngle(map, coordinates);
    // 绘制注记圆弧
    const cirleArcPath = vjmap.getCirclePolygonCoordinates(
        points[1],
        points[1].distanceTo(points[0]) / 4.0, 36,
        ang.startAngle,  ang.endAngle, false);
    let path = new vjmap.Polyline({
        data: map.toLngLat(cirleArcPath),
        lineColor: color,
        lineWidth: 2
    });
    path.addTo(map);
    markerTexts.push(path)

    // @ts-ignore
    let arcPoints = path.getData().features[0].geometry.coordinates;
    let arcMid = arcPoints[Math.ceil(arcPoints.length / 2)];// 取中点
    let textAngle = vjmap.radiansToDegrees(-map.fromLngLat(arcMid).angleTo(points[1])) + 90;
    if (textAngle > 90) textAngle += 180;
    else if (textAngle > 270) textAngle -= 180;
    let text = new vjmap.Text({
        text: ang.angle as string,
        anchor: "center",
        rotation: textAngle,
        offset: [0, 0], // x,y 方向像素偏移量
        style:{     // 自定义样式
            'cursor': 'pointer',
            'opacity': 0.8,
            'padding': '6px',
            'border-radius': '12px',
            'background-color': color,
            'border-width': 0,
            'box-shadow': '0px 2px 6px 0px rgba(97,113,166,0.2)',
            'text-align': 'center',
            'font-size': '14px',
            'color': `#${color.substring(1).split("").map(c => (15 - parseInt(c,16)).toString(16)).join("")}`,
        }
    });
    text.setLngLat(arcMid).addTo(map);
    markerTexts.push(text);
    // 给第一个点加一个marker用来删除
    const deletePng = = "delete.png";
    let el = document.createElement('div');
    el.className = 'marker';
    el.style.backgroundImage =
        `url(${deletePng})`;
    el.style.width = '20px';
    el.style.height = '20px';
    el.style.backgroundSize = '100%';
    el.style.cursor = "pointer";

    el.addEventListener('click', function (e) {
        map.removeSourceEx(sourceId); // 删除绘制的线
        markerTexts.forEach((m: any) => m.remove());
        markerTexts = [];
    });
    // Add markers to the map.
    let deleteMarker = new vjmap.Marker({
        element: el,
        anchor: 'right'
    });
    deleteMarker.setLngLat(coordinates[1])
        .setOffset([-5, 0])
        .addTo(map);
    markerTexts.push(deleteMarker)

    // 把坐标加进捕捉数组中。
    addSnapCoordinates(snapObj, coordinates);
}
// 得到角度值
const getAngle = (map: Map, coordinates: Array<[number, number]>) => {
    let points = map.fromLngLat(coordinates);
    if (points.length < 3) return {  angle: 0.0 }
    let angle1 = points[0].angleTo(points[1]);
    let angle2 = points[2].angleTo(points[1]);
    let angle = angle1 - angle2;
    let deg = vjmap.radiansToDegrees(angle);//弧度转角度
    let dir = true;
    if (deg < 0) {
        deg = -deg;
        dir = !dir;
    }
    if (deg > 180) {
        deg = 360 - deg;
        dir = !dir;
    }
    let startAngle = !dir ? vjmap.radiansToDegrees(angle1) : vjmap.radiansToDegrees(angle2);
    let endAngle = dir ? vjmap.radiansToDegrees(angle1) : vjmap.radiansToDegrees(angle2);
    startAngle = startAngle < 0 ? 360 + startAngle : startAngle;
    endAngle = endAngle < 0 ? 360 + endAngle : endAngle;
    if (endAngle < startAngle) {
        endAngle += 360;
    }
    return {
        angle: deg.toFixed(2) + "°",
        dir,
        startAngle,
        endAngle
    }
}


// 测量坐标
const measureCoordinate = async (map: Map, snapObj: any)=> {
    let isDrawing = false;
    let point = await vjmap.Draw.actionDrawPoint(map, {
        api: {
            getSnapFeatures: snapObj //要捕捉的数据项在后面，通过属性features赋值
        },
        updatecoordinate: (e: any) => {
            if (!e.lnglat) return;
            isDrawing = true;
            const co = map.fromLngLat(e.lnglat);
            let html = `【测量坐标】当前坐标:<span style="color: #ff0000"> ${co.x.toFixed(2)}, ${co.y.toFixed(2)}</span>`;
            setPopupText(html, map)
        },
        contextMenu: (e: any) => {
            new vjmap.ContextMenu({
                event: e.event.originalEvent,
                theme: "dark", //light
                width: "250px",
                items: [
                    {
                        label: '确认',
                        onClick: () => {
                            // 给地图发送Enter键消息即可取消，模拟按Enter键
                            map.fire("keyup", {keyCode:13})
                            setPopupText("", map);
                        }
                    },
                    {
                        label: '取消',
                        onClick: () => {
                            // 给地图发送ESC键消息即可取消，模拟按ESC键
                            map.fire("keyup", {keyCode:27})
                            setPopupText("", map);
                        }
                    },
                    {
                        label: '结束测坐标',
                        onClick: () => {
                            // 给地图发送ESC键消息即可取消，模拟按ESC键
                            map.fire("keyup", {keyCode:27})
                            isDrawing = false;
                            setPopupText("", map);
                        }
                    }
                ]
            });

        }
    });
    if (point.cancel) {
        setPopupText("", map);
        return {
            cancel: true,
            exit: isDrawing === false
        };// 取消操作
    }

    addMarkersToCoord(map, point.features[0].geometry.coordinates);
    return {
        point
    };
}

// 测量坐标循环，直至按ESC键取消
const measureCoordinateLoop = async (map: Map, snapObj: any)=> {
    while(true) {
        let res = await measureCoordinate(map, snapObj);
        if (res.exit === true) break;
        if (curMeasureCmd != "measureCoordinate") break;
    }
}



// 给加个点加个测量的结果值
const addMarkersToCoord = (map: Map, coordinates: [number, number]) => {
    let markerTexts: any = [];
    let co = map.fromLngLat(coordinates);
    let content = `X: ${co.x.toFixed(2)}, Y: ${co.y.toFixed(2)}`
    let marker = createLeaderMarker(map, coordinates, content);
    markerTexts.push(marker);

    // 给第一个点加一个marker用来删除
    const deletePng = "delete.png";
    let el = document.createElement('div');
    el.className = 'marker';
    el.style.backgroundImage =
        `url(${deletePng})`;
    el.style.width = '20px';
    el.style.height = '20px';
    el.style.backgroundSize = '100%';
    el.style.cursor = "pointer";

    el.addEventListener('click', function (e) {
        markerTexts.forEach((m: any) => m.remove());
        markerTexts = [];

    });
    // Add markers to the map.
    let deleteMarker = new vjmap.Marker({
        element: el,
        anchor: 'right'
    });
    deleteMarker.setLngLat(coordinates)
        .setOffset([-5, 0])
        .addTo(map);
    markerTexts.push(deleteMarker)

}

// 引线标记
const createLeaderMarker = (map: Map, lnglat: [number, number], content: string) => {
    let el = document.createElement('div');
    el.className = 'marker';
    el.style.position = 'absolute'

    let img = document.createElement("div");
    img.style.backgroundImage = 'bk.png';
    img.style.backgroundRepeat = "no-repeat"
    img.style.height = '37px';
    img.style.width = '100px';
    img.style.position = 'absolute';
    img.style.left = '-3px';
    img.style.bottom = '-3px';
    img.style.right = "0px"
    el.appendChild(img);

    let panel = document.createElement("div");
    panel.style.height = '50px';
    panel.style.width = '350px';
    panel.style.position = 'absolute';
    panel.style.left = '97px';
    panel.style.top = '-60px';
    panel.style.border = "solid 1px #8E0EFF";
    panel.style.background = 'linear-gradient(#00ffff, #00ffff) left top,  linear-gradient(#00ffff, #00ffff) left top,     linear-gradient(#00ffff, #00ffff) right bottom,    linear-gradient(#00ffff, #00ffff) right bottom';
    panel.style.backgroundRepeat = 'no-repeat';
    panel.style.backgroundColor ='rgba(87,255,255, 0.3)'
    panel.style.backgroundSize = '1px 6px, 6px 1px';
    panel.style.fontSize = '18px';
    panel.style.color = '#ffffff';
    panel.innerHTML =  `<div style='margin: 15px 5px 15px 5px'>${content}</div>`;
    el.appendChild(panel);

    // Add markers to the map.
    let marker = new vjmap.Marker({
        element: el,
        anchor: "bottom-left"
    })
    marker.setLngLat(lnglat)
        .addTo(map);
    return marker
}


```

