Measure Tool
CAD is now widely used across various fields and has greatly improved the efficiency of engineers. On the desktop, AutoCAD's measure tools are already very powerful; but on the web, how can we accurately and quickly measure CAD drawings?
Features
- Open AutoCAD drawings online in the web
- Measure distance
- Measure area
- Measure angle
- Coordinate annotation
- Snap to coordinates on web CAD graphics during measurement for improved accuracy
- Toggle snap on/off during measurement
- Enable orthogonal mode during measurement
- Customize right-click menu during measurement
- Continuous measurement
- Delete measurement results after completion
Demo
Measure Distance

Measure Area

Measure Angle

Coordinate Annotation

Other Features
During measurement: press Alt to toggle snap; press Ctrl to enable orthogonal mode; press Backspace to delete the last point; press ESC to cancel; press Enter to finish; press Right-click to open the context menu

Runs in a sandbox against the VJMAP public demo service
// --Distance measurement--
// Map service object
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// Open map
let res = await svc.openMap({
mapid: env.exampleMapId, // Map ID
mapopenway: vjmap.MapOpenWay.GeomRender, // Open with geometry render mode
style: vjmap.openMapDarkStyle() // When div has dark background, pass dark background style here
})
if (res.error) {
// If open fails
message.error(res.error)
}
// Get map bounds
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// Establish geometric projection coordinate system based on map bounds
let prj = new vjmap.GeoProjection(mapExtent);
// Map object
let map = new vjmap.Map({
container: 'map', // DIV container ID
style: svc.rasterStyle(), // Style, raster style here
center: prj.toLngLat(mapExtent.center()), // Set map center
zoom: 2, // Set map zoom level
renderWorldCopies: false, // Don't show multi-screen map
doubleClickZoom: false // Disable double-click zoom
});
// Associate service object and projection object
map.attach(svc, prj);
// Fit map bounds to full view
map.fitMapBounds();
const addStyle = cssStyle => {
let style = `<style>${cssStyle }</style>`;
let ele = document.createElement('div');
ele.innerHTML = style;
document.getElementsByTagName('head')[0].appendChild(ele.firstElementChild)
}
// Add custom popup style
addStyle(`
.custom-popup .vjmapgis-popup-content {
background-color: #0EFFC2;
opacity: 0.7;
}
.custom-popup .vjmapgis-popup-tip {
border-top-color: #0EFFC2;
opacity: 0.7;
}
`)
// Popup that follows the mouse
let popup = null;
const setPopupText = text => {
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 {
// If empty, remove popup
if (popup) {
popup.setLngLat([0,0]); // Cancel trackPointer
popup.remove();
popup = null;
}
}
}
let snapObj = {}; // Snap entities to use
// Measure distance
const measureDist = async ()=> {
let isDrawing = false;
let line = await vjmap.Draw.actionDrawLineSting(map, {
api: {
getSnapFeatures: snapObj //Snap data items assigned via features property later
},
updatecoordinate: (e) => {
if (!e.lnglat) return;
/*
// If you need to specify a start point in advance, use the following code
if (e.state.line.coordinates.length == 1) {
// Add the initial point as start when first point is placed
e.state.line.coordinates.unshift(map.toLngLat([
587662045.9814225, // cad coordinate x
3103973655.658176 // cad coordinate y
]))
e.state.currentVertexPosition = 1;
}*/
isDrawing = true;
const co = map.fromLngLat(e.feature.coordinates[e.feature.coordinates.length - 1]);
let html = `Current coordinate:<span style="color: #ff0000"> ${co.x.toFixed(2)}, ${co.y.toFixed(2)}</span>`;
if (e.feature.coordinates.length == 1) {
html += "<r/>Please specify the coordinate position for the first measurement point"
} else {
let len = e.feature.coordinates.length;
html += `<br/>Alt: snap; Ctrl: orthogonal; Backspace: delete last point`
html += `<br/>Distance from last point: <span style="color: #ff0000">${getDist([e.feature.coordinates[len - 2], e.feature.coordinates[len -1]])}</span>`
html += `; Total distance: <span style="color: #ff0000">${getDist(e.feature.coordinates)}</span>`
}
setPopupText(html)
},
contextMenu: (e) => {
new vjmap.ContextMenu({
event: e.event.originalEvent,
theme: "dark", //light
width: "250px",
items: [
{
label: 'Confirm',
onClick: () => {
// Fire Enter key to finish, simulate pressing Enter
map.fire("keyup", {keyCode:13})
setPopupText("");
}
},
{
label: 'Cancel',
onClick: () => {
// Fire ESC key to cancel, simulate pressing ESC
map.fire("keyup", {keyCode:27})
setPopupText("");
}
},{
label: 'Delete last point',
onClick: () => {
// Fire Backspace to delete last point, simulate pressing Backspace
map.fire("keyup", {keyCode:8})
}
},{
label: 'End distance measurement',
onClick: () => {
// Fire ESC key to cancel, simulate pressing ESC
map.fire("keyup", {keyCode:27})
isDrawing = false;
setPopupText("");
}
}
]
});
}
});
if (line.cancel) {
setPopupText("");
return {
cancel: true,
exit: isDrawing === false // If cancelled before drawing, end measurement
};// Cancel operation
}
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
};
}
// Distance measurement loop, continue until ESC to cancel, otherwise measure next line after finishing one
const measureDistLoop = async ()=> {
while(true) {
let res = await measureDist();
if (res.exit === true) break;
}
}
measureDistLoop();
const clearAll = (map)=> {
// Clear drawn layers; if worried about accidental deletion, record sourceId and marker after each draw, then delete them
// @ts-ignore
Object.keys(map.getStyle().sources).filter(e=>e.indexOf("custom_") == 0).forEach(e=> map.removeSourceEx(e));
// Remove all markers
map.removeMarkers();
}
// Add measurement result labels to each point on the line
const addMarkersToLine = (coordinates, color, sourceId) => {
let markerTexts = [];
for(let i = 1; i < coordinates.length; i++) {
let text = new vjmap.Text({
text: getDist(coordinates.slice(0, i + 1)),
anchor: "left",
offset: [3, 0], // x,y pixel offset
style:{ // Custom 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 => (15 - parseInt(c,16)).toString(16)).join("")}`,
}
});
text.setLngLat(coordinates[i]).addTo(map);
markerTexts.push(text);
}
// Add a delete marker at the first point
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); // Remove drawn line
markerTexts.forEach(m => m.remove());
markerTexts = [];
// Extra click, fire Backspace to delete last point, simulate pressing 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)
// Add coordinates to snap array
addSnapCoordinates(coordinates);
}
// Get distance value
const getDist = (coordinates) => {
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;
}
// Add snap points
const addSnapCoordinates = (coordinates) => {
snapObj.features.push({
type: "Feature",
geometry: {
type: "LineString",
coordinates: [...coordinates]
}
})
}
// Get snap points
if (map.hasVectorLayer()) {
// Vector style
// Vector style: get snap points
snapObj.features = map.queryRenderedFeatures({layers: ['vector-layer-lines']})
} else {
// Raster style: get coordinate data from server
// Raster style: get snap points
// Query all coordinate data, field meanings: https://vjmap.com/en/guide/svrStyleVar.html
let res = await svc.conditionQueryFeature({ fields:"s3", condition:"s3 != ''", limit: 100000})
res = res.result.map(e => e.s3.split(";"))
snapObj.features = []
for(let item of res) {
let coordinates = []
for(let pt of item) {
const p = pt.split(",")
if (p.length >= 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 > 1) {
snapObj.features.push({
type: "Feature",
geometry: {
type: "LineString",
coordinates: coordinates
}
})
}
}
}Code Implementation
The case code above is open source. Visit (Weijie Map Cloud Drawing Management Platform https://vjmap.com/app/cloud), and click Download this case source code.
import vjmap, { Map } from 'vjmap'
import { sleep } from '~/utils/ui';
import { getMapSnapPoints } from './snap';
let snapObj: any; // Snap entities to use
let curMeasureCmd: string; // Current measure command
export async function runMeasureCmd(map: Map, cmd: string) {
curMeasureCmd = cmd;
if (cmd != "measureCancel") {
// End current measurement first
await measureCancel(map);
if (!snapObj) {
// Get snap points on the map
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;
}
}
// End drawing
const measureCancel = async (map: Map)=> {
// Fire cancel key repeatedly: first cancels current draw, second exits measurement
map.fire("keyup", {keyCode:27});
await sleep(100);
map.fire("keyup", {keyCode:27});
await sleep(100);
map.setIsInteracting(false); // No longer interacting
}
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 {
// If empty, remove popup
if (popup) {
popup.setLngLat([0,0]); // Cancel trackPointer
popup.remove();
popup = null;
}
}
}
// Distance measurement loop, continue until ESC to cancel, otherwise measure next line after finishing one
const measureDistLoop = async (map: Map, snapObj: any)=> {
while(true) {
let res = await measureDist(map, snapObj);
if (res.exit === true) break;
if (curMeasureCmd != "measureDist") break;
}
}
// Measure distance
const measureDist = async (map: Map, snapObj: any)=> {
let isDrawing = false;
let line = await vjmap.Draw.actionDrawLineSting(map, {
api: {
getSnapFeatures: snapObj //Snap data items assigned via features property later
},
updatecoordinate: (e: any) => {
if (!e.lnglat) return;
isDrawing = true;
const co = map.fromLngLat(e.feature.coordinates[e.feature.coordinates.length - 1]);
let html = `【Measure Distance】Current coordinate:<span style="color: #ff0000"> ${co.x.toFixed(2)}, ${co.y.toFixed(2)}</span>`;
if (e.feature.coordinates.length == 1) {
html += "<br/>Please specify the coordinate position for the first measurement point"
} else {
let len = e.feature.coordinates.length;
html += `<br/>Alt: snap; Ctrl: orthogonal; Backspace: delete last point`
html += `<br/>Distance from last point: <span style="color: #ff0000">${getDist(map, [e.feature.coordinates[len - 2], e.feature.coordinates[len -1]])}</span>`
html += `; Total distance: <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: 'Confirm',
onClick: () => {
// Fire Enter key to finish, simulate pressing Enter
map.fire("keyup", {keyCode:13})
setPopupText("", map);
}
},
{
label: 'Cancel',
onClick: () => {
// Fire ESC key to cancel, simulate pressing ESC
map.fire("keyup", {keyCode:27})
setPopupText("", map);
}
},{
label: 'Delete last point',
onClick: () => {
// Fire Backspace to delete last point, simulate pressing Backspace
map.fire("keyup", {keyCode:8})
}
},{
label: 'End distance measurement',
onClick: () => {
// Fire ESC key to cancel, simulate pressing ESC
map.fire("keyup", {keyCode:27})
isDrawing = false;
setPopupText("", map);
}
}
]
});
}
});
if (line.cancel) {
setPopupText("", map);
return {
cancel: true,
exit: isDrawing === false // If cancelled before drawing, end measurement
};// Cancel operation
}
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
};
}
// Add measurement result labels to each point on the line
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 pixel offset
style:{ // Custom 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);
}
// Add a delete marker at the first point
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); // Remove drawn line
markerTexts.forEach((m: any) => m.remove());
markerTexts = [];
// Extra click, fire Backspace to delete last point, simulate pressing 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)
// Add coordinates to snap array
addSnapCoordinates(snapObj, coordinates);
}
// Get distance value
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;
}
// Add snap points
const addSnapCoordinates = (snapObj: any, coordinates: Array<[number, number]>) => {
snapObj.features.push({
type: "Feature",
geometry: {
type: "LineString",
coordinates: [...coordinates]
}
})
}
// Measure area
const measureArea = async (map: Map, snapObj: any)=> {
let isDrawing = false;
let poly = await vjmap.Draw.actionDrawPolygon(map, {
api: {
getSnapFeatures: snapObj //Snap data items assigned via features property later
},
updatecoordinate: (e: any) => {
if (!e.lnglat) return;
isDrawing = true;
const co = map.fromLngLat(e.feature.coordinates[0][e.feature.coordinates.length - 1]);
let html = `【Measure Area】Current coordinate:<span style="color: #ff0000"> ${co.x.toFixed(2)}, ${co.y.toFixed(2)}</span>`;
if (e.feature.coordinates[0].length == 1) {
html += "<br/>Please specify the coordinate position for the first measurement point"
} else {
html += `<br/>Alt: snap; Ctrl: orthogonal; Backspace: delete last point`
html += `<br/>Current area: <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: 'Confirm',
onClick: () => {
// Fire Enter key to finish, simulate pressing Enter
map.fire("keyup", {keyCode:13})
setPopupText("", map);
}
},
{
label: 'Cancel',
onClick: () => {
// Fire ESC key to cancel, simulate pressing ESC
map.fire("keyup", {keyCode:27})
setPopupText("", map);
}
},{
label: 'Delete last point',
onClick: () => {
// Fire Backspace to delete last point, simulate pressing Backspace
map.fire("keyup", {keyCode:8})
}
},{
label: 'End area measurement',
onClick: () => {
// Fire ESC key to cancel, simulate pressing ESC
map.fire("keyup", {keyCode:27})
isDrawing = false;
setPopupText("", map);
}
}
]
});
}
});
if (poly.cancel) {
debugger
setPopupText("", map);
return {
cancel: true,
exit: isDrawing === false // If cancelled before drawing, end measurement
};// Cancel operation
}
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
};
}
// Area measurement loop, continue until ESC to cancel, otherwise measure next polygon after finishing one
const measureAreaLoop = async (map: Map, snapObj: any)=> {
while(true) {
let res = await measureArea(map, snapObj);
if (res.exit === true) break;
if (curMeasureCmd != "measureArea") break;
}
}
// Add measurement result labels
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 pixel offset
style:{ // Custom 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);
// Add a delete marker at the first point
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); // Remove drawn line
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)
// Add coordinates to snap array
addSnapCoordinates(snapObj, coordinates);
}
// Get area value
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;
}
// Measure angle
const measureAngle = async (map: Map, snapObj: any)=> {
let isDrawing = false;
let line = await vjmap.Draw.actionDrawLineSting(map, {
pointCount: 3,// Only three points needed, auto-finish after drawing three points
api: {
getSnapFeatures: snapObj //Snap data items assigned via features property later
},
updatecoordinate: (e: any) => {
if (!e.lnglat) return;
isDrawing = true;
const co = map.fromLngLat(e.feature.coordinates[e.feature.coordinates.length - 1]);
let html = `【Measure Angle】Current coordinate:<span style="color: #ff0000"> ${co.x.toFixed(2)}, ${co.y.toFixed(2)}</span>`;
if (e.feature.coordinates.length == 1) {
html += "<br/>Please specify the coordinate position for the first measurement point"
} else {
let len = e.feature.coordinates.length;
html += `<br/>Alt: snap; Ctrl: orthogonal; Backspace: delete last point`
html += `<br/>Current angle: <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: 'Confirm',
onClick: () => {
// Fire Enter key to finish, simulate pressing Enter
map.fire("keyup", {keyCode:13})
setPopupText("", map);
}
},
{
label: 'Cancel',
onClick: () => {
// Fire ESC key to cancel, simulate pressing ESC
map.fire("keyup", {keyCode:27})
setPopupText("", map);
}
},{
label: 'Delete last point',
onClick: () => {
// Fire Backspace to delete last point, simulate pressing Backspace
map.fire("keyup", {keyCode:8})
}
},{
label: 'End angle measurement',
onClick: () => {
// Fire ESC key to cancel, simulate pressing ESC
map.fire("keyup", {keyCode:27})
isDrawing = false;
setPopupText("", map);
}
}
]
});
}
});
if (line.cancel) {
setPopupText("", map);
return {
cancel: true,
exit: isDrawing === false // If cancelled before drawing, end measurement
};// Cancel operation
}
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
};
}
// Angle measurement loop, continue until ESC to cancel, otherwise measure next angle after finishing one
const measureAngleLoop = async (map: Map, snapObj: any)=> {
while(true) {
let res = await measureAngle(map, snapObj);
if (res.exit === true) break;
if (curMeasureCmd != "measureAngle") break;
}
}
// Add measurement result labels
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);
// Draw annotation arc
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)];// Get midpoint
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 pixel offset
style:{ // Custom 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);
// Add a delete marker at the first point
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); // Remove drawn line
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)
// Add coordinates to snap array
addSnapCoordinates(snapObj, coordinates);
}
// Get angle value
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);// Radians to degrees
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
}
}
// Measure coordinate
const measureCoordinate = async (map: Map, snapObj: any)=> {
let isDrawing = false;
let point = await vjmap.Draw.actionDrawPoint(map, {
api: {
getSnapFeatures: snapObj //Snap data items assigned via features property later
},
updatecoordinate: (e: any) => {
if (!e.lnglat) return;
isDrawing = true;
const co = map.fromLngLat(e.lnglat);
let html = `【Measure Coordinate】Current coordinate:<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: 'Confirm',
onClick: () => {
// Fire Enter key to finish, simulate pressing Enter
map.fire("keyup", {keyCode:13})
setPopupText("", map);
}
},
{
label: 'Cancel',
onClick: () => {
// Fire ESC key to cancel, simulate pressing ESC
map.fire("keyup", {keyCode:27})
setPopupText("", map);
}
},
{
label: 'End coordinate measurement',
onClick: () => {
// Fire ESC key to cancel, simulate pressing ESC
map.fire("keyup", {keyCode:27})
isDrawing = false;
setPopupText("", map);
}
}
]
});
}
});
if (point.cancel) {
setPopupText("", map);
return {
cancel: true,
exit: isDrawing === false
};// Cancel operation
}
addMarkersToCoord(map, point.features[0].geometry.coordinates);
return {
point
};
}
// Coordinate measurement loop, continue until ESC to cancel
const measureCoordinateLoop = async (map: Map, snapObj: any)=> {
while(true) {
let res = await measureCoordinate(map, snapObj);
if (res.exit === true) break;
if (curMeasureCmd != "measureCoordinate") break;
}
}
// Add measurement result labels to point
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);
// Add a delete marker at the first point
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)
}
// Leader marker
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
}
Was this page helpful?Thanks for the feedback!