Measuring Distance

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.
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();
}

Following are the detailed steps of the code.

Implementing the Functionality

Global Variables

Before implementing the functionality, declare variables that will be used globally.

var GLOBAL = {
    m_objcount: 0, // Count of POI displaying measurement objects
    m_mercount: 0, // Measurement object count
};

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.

let layerList = new Module.JSLayerList(true);
let layer = layerList.createLayer("MEASURE_POI", Module.ELT_3DPOINT);
layer.setMaxDistance(20000.0);
layer.setSelectable(false);

step 2 - 1. Setting Callback Functions

Callback functions are registered to receive the calculated distance from the engine. Callback functions are registered through JSOption.

(For detailed API descriptions, see callBackAddPoint and callBackCompletePoint).

Two types of callback functions are registered:

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

When the mouse mode is set to MML_ANALYS

_DISTANCE_STRAIGHT, the specified callback function is called upon clicking the measurement points.

For setting the mouse mode, refer to step 3. Changing Mouse Mode.

step 2 - 2. Distance Measurement Callback Function Creation

This is the callback function executed on left mouse click. It visualizes the calculated distance.

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);
    }
}

step 2 - 3. Distance Measurement End Callback Function Creation

This is the callback function executed on double-click. It ends the distance measurement.

function endPoint(e) {
    viewListOBjKey(e);
    GLOBAL.m_mercount++;
}

step 3. Changing Mouse Mode

The mouse mode is changed for distance measurement.

For more information on mouse modes, please refer here.

Module.XDSetMouseState(Module.MML_ANALYS_DISTANCE_STRAIGHT);

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.

The process of creating a POI object and applying the result image is described in step 5. Creating Distance Object.

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;
}

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.

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();
}

step 4 - 3. Creating Distance Rectangle Icon

A rounded rectangle balloon image is drawn to display the intermediate distance values.

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;
}

step 4 - 4. Creating Distance Measurement Result Value Icon

The calculated distance value is drawn as text on the drawn balloon.

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);
}

step 4 - 5. Converting Distance Measurement Units (m/km)

The returned distance value is optionally converted to 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;
}

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.

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++;
}

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.

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();
}

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.

Last updated