CAD如今在各个领域均得到了普遍的应用并大大提高了工程技术人员的工作效率。在桌面端,AutoCAD测量工具已经非常强大;然后在Web端,如何准确、快速的对CAD图在Web进行测量呢?
功能- 能Web在线打开AutoCAD图形
- 测量距离
- 测量面积
- 测量角度
- 坐标标注
- 测量时能捕捉Web端CAD图形上面的坐标,提高准确度
- 测量时能对捕捉进行开关启用
- 测量时能启用正交模式
- 测量时能自定义右键菜单功能
- 能进行连续测量
- 测量结束后,能删除已测量的结果
如果在Web网页端展示CAD图形(唯杰地图云端图纸管理平台 https://vjmap.com/app/cloud),这个在前面的博文中已讲过,这里不再重复,有需要的朋友可下载工程源代码研究下。
测量距离测量面积
测量角度
坐标标注
其他功能
在测量过程中,按Alt键可开启关闭捕捉;按Ctrl键可启用正交模式;按退格键可删除上一个点;按ESC键取消测量;按Enter键结束测量; 按右键弹出上下文菜单
代码实现
有需要的朋友可以在线体验下。上面的案例代码已开源。访问 (唯杰地图云端图纸管理平台 https://vjmap.com/app/cloud) ,点击下载此案例源码即可。
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,
}
});
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
}