Measuring Area

Change the mouse mode to area measurement mode, then measure the area of the selected region. The measurement result values are returned as events and visualized using JSPoint and JSPolygon.

Interface Features

When you click on the point you wish to measure, the area including that point is displayed as a polygon, and double-clicking ends the area measurement. After ending one measurement, clicking on another point starts a new set of measurement results.

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(126.92836647767662, 37.52439503321471, 1000.0));

    // Create layer for 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 render option for area measurement polygon
    Module.getOption().SetAreaMeasurePolygonDepthBuffer(false);	// Set WEBGL GL_DEPTH_TEST

    // Continuously use callback function settings
    Module.getOption().callBackAddPoint(addPoint);		// Callback on mouse input, returns success on success, failure error on failure
    Module.getOption().callBackCompletePoint(endPoint);	// Callback on measurement end (double-click), returns success on success, 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 area measurement mouse mode
        Module.XDSetMouseState(Module.MML_ANALYS_AREA_PLANE);
    }
}

let m_mercount = 0;	// Number of measurement objects

/* Function specified in callBackAddPoint [Event occurs on left mouse click]*/
function addPoint(e) {
    // e components
    // dLon, dLat, dAlt : Center coordinates of the area (longitude, latitude, altitude)
    // dArea			: Area size
    createPOI(new Module.JSVector3D(e.dLon, e.dLat, e.dAlt), "rgba(255, 204, 198, 0.8)", e.dArea, true);
}

/* Function specified in callBackCompletePoint [Event occurs on double mouse 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");

    // If POI exists, delete and then create
    let key = m_mercount + "_POI";
    layer.removeAtKey(key);

    // POI creation process
    poi = Module.createPoint(m_mercount + "_POI");
    poi.setPosition(_position);												// Set location
    poi.setImage(imageData, drawCanvas.width, drawCanvas.height);			// Set icon
    layer.addObject(poi, 0);												// Register POI in layer
}

/* 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
    var strText = setTextComma(_value.toFixed(2)) + '㎡';

    //

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

/* Unit expression */
function setTextComma(str) {
    str = String(str);
    return str.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, '$1,');
}

//=============================================== 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.XDClearAreaObject(_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";	// Create object 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.XDClearAreaMeasurement();
    GLOBAL.m_mercount = 0;

    // Delete layer
    let layerList = new Module.JSLayerList(true);
    let layer = layerList.nameAtLayer("MEASURE_POI");
    layer.removeAll();

    // Delete all nodes in <ui>
    let cell = document.getElementById("objList");
    while ( cell.hasChildNodes() ) {
        cell.removeChild( cell.firstChild );
    }
}

Following are the detailed steps of the code.

Feature Implementation

Global Variables

Before implementing the feature, global variables are declared.

var GLOBAL = {
    m_mercount: 0, // Number of measurement objects
};

This manages the number of measurement objects after measurement ends.

step 1. Creating a Layer

A layer is created to visualize the area measurement icon and area values.

Since the area measurement values need to be output as POI, the layer type is set to ELT_3DPOINT.

For more information on layer types, see 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 area from the engine.

The callback functions are registered through JSOption.

For detailed API descriptions, see the explanations of callBackAddPoint and callBackCompletePoint.

Module.getOption().callBackAddPoint(addPoint); // Callback on mouse input, returns success on success, failure error on failure
Module.getOption().callBackCompletePoint(endPoint); // Callback on measurement end (double-click), returns success on success, failure error on failure

Two callback functions are registered:

step 2 - 2. Creating Area Measurement Callback Function

This callback function is executed on left mouse click.

The calculated area is received and visualized.

function addPoint(e) {
    // e components
    // dLon, dLat, dAlt : Center coordinates of the area (longitude, latitude, altitude)
    // dArea			: Area size
    createPOI(new Module.JSVector3D(e.dLon, e.dLat, e.dAlt), "rgba(255, 204, 198, 0.8)", e.dArea);
}

When the mouse mode is set to MML_ANALYS_AREA_PLANE, the callback function set here is called when you click on the point you want to measure.

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

step 2 - 3. Creating Area Measurement End Callback Function

This callback function is executed on double mouse click. It ends the area measurement.

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

step 3. Changing Mouse Mode

The mouse mode is changed for area measurement.

For more information on mouse modes, see here.

Module.XDSetMouseState(Module.MML_ANALYS_AREA_PLANE);

step 4 - 1. Creating Area Icon

An icon is created to render the received area value.

The icon image created in this step will be applied to the POI object that will display the measurement value.

For the POI object creation and result image application step, refer to step 5. Creating Area Object.

function drawIcon(_canvas, _color, _value) {
    // 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
    drawBalloon(ctx, height * 0.5, width, height, 5, height * 0.25, _color);
    setText(ctx, width * 0.5, height * 0.2, _value);

    return ctx.getImageData(0, 0, _canvas.width, _canvas.height).data;
}

Executing the above function creates an icon image like the one shown below.

step 4 - 2. Drawing Area Balloon

A background balloon is drawn to display the accumulated area in text.

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. Drawing Text

The received area value is drawn as text on the balloon.

function setText(_ctx, _posX, _posY, _value) {
    var strText = "";

    // Set text string
    var strText = setTextComma(_value.toFixed(2)) + "㎡";

    // 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 - 4. Inserting Text Comma

A comma (,) is added as a unit separator in the text composed of numbers.

function setTextComma(str) {
    str = String(str);
    return str.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, "$1,");
}

step 5. Creating Area Object

An object is created with the created icon and added to the layer.

The JSSymbol class is used to store and manage textures.

For a brief description of JSSymbol, refer to the explanation of JSSymbol in the step 5. Creating Distance Object section of the distance measurement tutorial.

function createPOI(_position, _color, _value) {
    // Parameters
    // _position : POI creation location
    // _color : Color configuration for drawIcon
    // _value : Text displayed on drawIcon

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

    let Symbol = Module.getSymbol();

    let layerList = new Module.JSLayerList(true);
    let layer = layerList.nameAtLayer("MEASURE_POI");

    // If POI exists, delete and then create
    let key = GLOBAL.m_mercount + "_POI";
    layer.removeAtKey(key);

    // POI creation process
    poi = Module.createPoint(GLOBAL.m_mercount + "_POI");
    poi.setPosition(_position); // Set location
    poi.setImage(imageData, drawCanvas.width, drawCanvas.height); // Set icon

    layer.addObject(poi, 0); // Register POI in layer
}

It is also possible to simply register image data without using JSSymbol.

This process is described in the Creating POI tutorial.

step 6. Resetting Area Measurement

The area measurement results and objects are reset.

function clearAnalysis() {
    // Reset ongoing analysis content
    Module.XDClearAreaMeasurement();
    GLOBAL.m_mercount = 0;

    // Delete layer
    let layerList = new Module.JSLayerList(true);
    let layer = layerList.nameAtLayer("MEASURE_POI");
    layer.removeAll();

    // Delete all nodes in <ui>
    let cell = document.getElementById("objList");
    while (cell.hasChildNodes()) {
        cell.removeChild(cell.firstChild);
    }
}

Result Screen

After completing the above process, the area measurement feature that displays the area information of the specified region is completed.

If you want to see the live code for the area measurement process, click here.

Last updated