Switch the mouse mode to distance measurement mode, then measure the distance between clicked points. The measurement results are returned as events and visualized using POI and Line.
Interface Functionality
When you click on the point you want to measure, the distance value between that point and the previous one is displayed. Double-clicking ends the distance measurement. Clicking on another point after ending a measurement will display a new set of measurement results.
The result values display both the intermediate distance values and the cumulative distance values.
Here is the complete code that implements the above functionality.
Following are the detailed steps of the code.
Implementing the Functionality
Global Variables
Before implementing the functionality, declare variables that will be used globally.
These variables help manage the count of POI objects and measurement objects after ending a measurement.
step 1. Creating a Layer
A layer is created to visualize the distance measurement icons and values. Since the distance values will be displayed as POI, the layer type is set to ELT_3DPOINT.
For more information on layer types, please refer here.
step 2 - 1. Setting Callback Functions
Callback functions are registered to receive the calculated distance from the engine. Callback functions are registered through JSOption.
step 2 - 2. Distance Measurement Callback Function Creation
This is the callback function executed on left mouse click. It visualizes the calculated distance.
step 2 - 3. Distance Measurement End Callback Function Creation
This is the callback function executed on double-click. It ends the distance measurement.
step 3. Changing Mouse Mode
The mouse mode is changed for distance measurement.
For more information on mouse modes, please refer here.
step 4 - 1. Creating Distance Icon
An icon is created to render the returned distance value. The icon image created in this step will be applied to the POI object that will display the measurement value.
There are two types of images created in this step:
Middle distance display icon image.
drawRoundRect function (step 4-3. Create distance rectangle icon) and setText([step 4-4. Create distance measurement result value icon](tutorial _distance.md#step-4-4.-icon)) Combine the functions to create a round text box image.
Cumulative distance display icon image
drawBalloon function (step 4-2. Create distance speech bubble icon) and setText([step 4-4. Create distance measurement result value icon](tutorial _distance.md#step-4-4.-icon)) Combine the functions to create a round text box image.
step 4 - 2. Creating Distance Balloon Icon
A balloon image is drawn to display the cumulative distance values.
step 4 - 3. Creating Distance Rectangle Icon
A rounded rectangle balloon image is drawn to display the intermediate distance values.
step 4 - 4. Creating Distance Measurement Result Value Icon
The calculated distance value is drawn as text on the drawn balloon.
step 4 - 5. Converting Distance Measurement Units (m/km)
The returned distance value is optionally converted to m/km text.
step 5. Creating Distance Object
A POI object is created using the icon and added to the layer.
The image is applied to the newly created POI object by storing the image drawn on the canvas in JSSymbol.
JSSymbol
XDWorld has a JSSymbol class for storing and managing textures.
The images created in step 4 are stored in JSSymbol and will be linked to the newly created POI object.
You can also simply register image data without using JSSymbol.
This process is described in the Creating POI tutorial.
step 6. Resetting Distance Measurement
The distance measurement results and objects are reset.
Result Screen
After completing all the steps, the distance measurement functionality is completed, displaying distance information for clicked points.
If you want to see the live code for the distance measurement process, please click here.
function init() {
Module.Start(window.innerWidth, window.innerHeight);
// Set camera location
Module.getViewCamera().setLocation(new Module.JSVector3D(129.128265, 35.171834, 1000.0));
// Create a layer for displaying analysis output POI
let layerList = new Module.JSLayerList(true);
let layer = layerList.createLayer("MEASURE_POI", Module.ELT_3DPOINT);
layer.setMaxDistance(20000.0);
layer.setSelectable(false);
// Set rendering options for distance measurement line
Module.getOption().SetDistanceMeasureLineDepthBuffer(false); // Set WEBGL GL_DEPTH_TEST
// Set callback functions for continuous use
Module.getOption().callBackAddPoint(addPoint); // Callback on mouse input, returns success on success or failure error on failure
Module.getOption().callBackCompletePoint(endPoint); // Callback on measurement end (double-click), returns success on success or failure error on failure
}
/* Change mouse state */
function setMouseState(_type) {
if (_type == "move") {
// Change to map movement mouse mode
Module.XDSetMouseState(Module.MML_MOVE_GRAB);
} else if (_type == "measure") {
// Change to distance measurement mouse mode
Module.XDSetMouseState(Module.MML_ANALYS_DISTANCE_STRAIGHT);
}
}
let m_mercount = 0; // Measurement object count
let m_objcount = 0; // Count of POI displaying measurement objects
/* Function specified for callBackAddPoint [Event occurs on left mouse click]*/
function addPoint(e) {
// e components
// dMidLon, dMidLat, dMidAlt : Midpoint (longitude, latitude, altitude) between the previous and current points
// dLon, dLat, dAlt : Current point (longitude, latitude, altitude)
// dDistance : Length between the current and previous points
// dTotalDistance : Length between all points
let partDistance = e.dDistance,
totalDistance = e.dTotalDistance;
if (partDistance == 0 && totalDistance == 0) {
m_objcount = 0; // Reset POI count
createPOI(new Module.JSVector3D(e.dLon, e.dLat, e.dAlt), "rgba(255, 204, 198, 0.8)", "Start", true);
} else {
if (e.dDistance > 0.01) {
createPOI(new Module.JSVector3D(e.dMidLon, e.dMidLat, e.dMidAlt), "rgba(255, 255, 0, 0.8)", e.dDistance, false);
}
createPOI(new Module.JSVector3D(e.dLon, e.dLat, e.dAlt), "rgba(255, 204, 198, 0.8)", e.dTotalDistance, true);
}
}
/* Function specified for callBackCompletePoint [Event occurs on double-click]*/
function endPoint(e) {
viewListOBjKey(e);
m_mercount++;
}
// =============================================== POI Creation Process
/* Information display POI */
function createPOI(_position, _color, _value, _balloonType) {
// Parameters
// _position : POI creation location
// _color : Color configuration for drawIcon
// _value : Text displayed on drawIcon
// _balloonType : Corner option for drawIcon (true: sharp corners, false: rounded corners)
// Create a canvas to draw the POI icon image
var drawCanvas = document.createElement("canvas");
// Canvas size (image size)
drawCanvas.width = 100;
drawCanvas.height = 100;
// Return icon image data
let imageData = drawIcon(drawCanvas, _color, _value, _balloonType);
let Symbol = Module.getSymbol();
let layerList = new Module.JSLayerList(true);
let layer = layerList.nameAtLayer("MEASURE_POI");
poi = Module.createPoint(m_mercount + "_POI_" + m_objcount);
poi.setPosition(_position); // Set location
poi.setImage(imageData, drawCanvas.width, drawCanvas.height); // Set icon
layer.addObject(poi, 0); // Register POI in layer
m_objcount++;
}
/* Return icon image data */
function drawIcon(_canvas, _color, _value, _balloonType) {
// Return context and clear background
var ctx = _canvas.getContext("2d"),
width = _canvas.width,
height = _canvas.height;
ctx.clearRect(0, 0, width, height);
// Set Draw Path for background then draw text
if (_balloonType) {
drawBalloon(ctx, height * 0.5, width, height, 5, height * 0.25, _color);
setText(ctx, width * 0.5, height * 0.2, _value);
} else {
drawRoundRect(ctx, 0, height * 0.3, width, height * 0.25, 5, _color);
setText(ctx, width * 0.5, height * 0.5, _value);
}
return ctx.getImageData(0, 0, _canvas.width, _canvas.height).data;
}
/* Draw balloon background */
function drawBalloon(ctx, marginBottom, width, height, barWidth, barHeight, color) {
var wCenter = width * 0.5,
hCenter = height * 0.5;
// Set Draw Path for balloon shape
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, height - barHeight - marginBottom);
ctx.lineTo(wCenter - barWidth, height - barHeight - marginBottom);
ctx.lineTo(wCenter, height - marginBottom);
ctx.lineTo(wCenter + barWidth, height - barHeight - marginBottom);
ctx.lineTo(
width,
height - barHeight - marginBottom
);
ctx.lineTo(width, 0);
ctx.closePath();
// Draw balloon
ctx.fillStyle = color;
ctx.fill();
}
/* Draw rounded rectangle background */
function drawRoundRect(ctx, x, y, width, height, radius, color) {
if (width < 2 * radius) radius = width * 0.5;
if (height < 2 * radius) radius = height * 0.5;
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.arcTo(x + width, y, x + width, y + height, radius);
ctx.arcTo(x + width, y + height, x, y + height, radius);
ctx.arcTo(x, y + height, x, y, radius);
ctx.arcTo(x, y, x + width, y, radius);
ctx.closePath();
// Draw rectangle
ctx.fillStyle = color;
ctx.fill();
return ctx;
}
/* Draw text */
function setText(_ctx, _posX, _posY, _value) {
var strText = "";
// Set text string
if (typeof _value == "number") {
strText = setKilloUnit(_value, 0.001, 0);
} else {
strText = _value;
}
// Set text style
_ctx.font = "bold 16px sans-serif";
_ctx.textAlign = "center";
_ctx.fillStyle = "rgb(0, 0, 0)";
// Draw text
_ctx.fillText(strText, _posX, _posY);
}
/* Convert m/km text */
function setKilloUnit(_text, _meterToKilloRate, _decimalSize) {
if (_decimalSize < 0) _decimalSize = 0;
if (typeof _text == "number") {
if (_text < 1.0 / (_meterToKilloRate * Math.pow(10, _decimalSize))) {
_text = _text.toFixed(1).toString() + "m";
} else {
_text = (_text * _meterToKilloRate).toFixed(2).toString() + "km";
}
}
return _text;
}
//=============================================== Measurement list and deletion related
function viewListOBjKey(_key) {
let cell = document.getElementById("objList");
let li = document.createElement("li");
// Add measurement object list (ui)
li.id = _key;
li.innerHTML = "<a href='#' onclick=\"deleteObject('" + _key + "');\">" + _key + "</a>";
cell.appendChild(li);
}
function deleteObject(_key) {
Module.XDClearDistanceObject(_key);
let li = document.getElementById(_key);
li.remove(); // Delete selected <a> controller
// Delete object
let layerList = new Module.JSLayerList(true);
let layer = layerList.nameAtLayer("MEASURE_POI");
let list = layer.getObjectKeyList();
let key = _key.replace(/[^0-9]/g, "") + "_POI_"; // Objects are created in the form of [creation order]_POI_
let strlist = list.split(",");
strlist.forEach((item, index) => {
if (item.indexOf(key) !== -1) {
layer.removeAtKey(item); // Delete object entered in layer by key
}
});
// Refresh screen
Module.XDRenderData();
}
/* Reset analysis content */
function clearAnalysis() {
// Reset ongoing analysis content
Module.XDClearDistanceMeasurement();
GLOBAL.m_mercount = 0;
// Delete layer
let layerList = new Module.JSLayerList(true);
let layer = layerList.nameAtLayer("MEASURE_POI");
layer.removeAll();
}
var GLOBAL = {
m_objcount: 0, // Count of POI displaying measurement objects
m_mercount: 0, // Measurement object count
};
let layerList = new Module.JSLayerList(true);
let layer = layerList.createLayer("MEASURE_POI", Module.ELT_3DPOINT);
layer.setMaxDistance(20000.0);
layer.setSelectable(false);
Module.getOption().callBackAddPoint(addPoint); // Callback on mouse input, returns success on success or failure error on failure
Module.getOption().callBackCompletePoint(endPoint); // Callback on measurement end (double-click), returns success on success or failure error on failure
function addPoint(e) {
// e components
// dMidLon, dMidLat, dMidAlt : Midpoint (longitude, latitude, altitude) between the previous and current points
// dLon, dLat, dAlt : Current point (longitude, latitude, altitude)
// dDistance : Length between the current and previous points
// dTotalDistance : Length between all points
let partDistance = e.dDistance,
totalDistance = e.dTotalDistance;
if (partDistance == 0 && totalDistance == 0) {
GLOBAL.m_objcount = 0; // Reset POI count
createPOI(new Module.JSVector3D(e.dLon, e.dLat, e.dAlt), "rgba(255, 204, 198, 0.8)", "Start", true);
} else {
if (e.dDistance > 0.01) {
createPOI(new Module.JSVector3D(e.dMidLon, e.dMidLat, e.dMidAlt), "rgba(255, 255, 0, 0.8)", e.dDistance, false);
}
createPOI(new Module.JSVector3D(e.dLon, e.dLat, e.dAlt), "rgba(255, 204, 198, 0.8)", e.dTotalDistance, true);
}
}
function endPoint(e) {
viewListOBjKey(e);
GLOBAL.m_mercount++;
}
function createPOI(_position, _color, _value, _balloonType) {
// Parameters
// _position : POI creation location
// _color : Color configuration for drawIcon
// _value : Text displayed on drawIcon
// _balloonType : Corner option for drawIcon (true: sharp corners, false: rounded corners)
// Create a canvas to draw the POI icon image
var drawCanvas = document.createElement("canvas");
// Canvas size (image size)
drawCanvas.width = 100;
drawCanvas.height = 100;
// Return icon image data
let imageData = drawIcon(drawCanvas, _color, _value, _balloonType);
let Symbol = Module.getSymbol();
let layerList = new Module.JSLayerList(true);
let layer = layerList.nameAtLayer("MEASURE_POI");
poi = Module.createPoint(GLOBAL.m_mercount + "_POI_" + GLOBAL.m_objcount);
poi.setPosition(_position); // Set location
poi.setImage(imageData, drawCanvas.width, drawCanvas.height); // Set icon
layer.addObject(poi, 0); // Register POI in layer
GLOBAL.m_objcount++;
}
function clearAnalysis() {
// Reset ongoing analysis content
Module.XDClearDistanceMeasurement();
GLOBAL.m_mercount = 0;
// Delete layer
let layerList = new Module.JSLayerList(true);
let layer = layerList.nameAtLayer("MEASURE_POI");
layer.removeAll();
}