Source

ui_interactions.js

/**
 * UI Interections Functions definitions
 *  @module ui_interactions */

/**
 * Update Mqtt Variables and call MQTTConnect() function
 * @param {string} mqtt_hostname MQTT hostname connection
 * @param {string} mqtt_port MQTT port connection
 * @param {number} reconnect_timeout MQTT reconnect Time Out value
 * @param {string} clientUsername MQTT client username connection
 * @param {string} clientPassword MQTT client password connection
 * @param {boolean} ssl_flag MQTT connection SSL flag
 * @param {string} messageToSend MQTT message to send to the broker
 * @param {string} mqttTopicToReceivePredictions MQTT Topic To Receive Predictions
 * @param {string} mqttTopicToReceiveCalculations MQTT Topic To Receive Calculations
 * @param {FeatureCollection} geojson_asset_points Asset Points FeatureCollection
 * @param {FeatureCollection} geojson_antennas Antennas FeatureCollection
 * @param {FeatureCollection} geojson_anchors Anchors FeatureCollection
 * @param {number} geojson_precison_decimal_places Vaule of precison decimal places
 * @param {Array} pulsing_dots_layers_IDs Pulsing Dots IDs Array
 * @param {Array} pulsing_dots_layers_sources Pulsing Dots sources Array
 * @param {number} selectedMapIndex index of selected Map
 * @param {string} received_uuid MQTT received session UUID
 * @returns {string} Connection string value and backend read rate value;
 */
function updateMqttParameters(
    mqtt_hostname,
    mqtt_port,
    reconnect_timeout,
    clientUsername,
    clientPassword,
    ssl_flag,
    messageToSend,
    mqttTopicToReceivePredictions,
    mqttTopicToReceiveCalculations,
    geojson_asset_points,
    geojson_antennas,
    geojson_anchors,
    geojson_precison_decimal_places,
    pulsing_dots_layers_sources,
    pulsing_dots_layers_IDs,
    selectedMapIndex,
    received_uuid
) {
    session_uuid = document.getElementById("session-uuid").innerHTML;
    client_id = document.getElementById("clientjs-id").innerHTML;
    mqtt_hostname = document.getElementById("mqtt_hostname_input").value;
    mqtt_port = parseInt(document.getElementById("mqtt_port_input").value);
    reconnect_timeout = parseInt(document.getElementById("reconnect_timeout_input").value) * 1000;
    clientUsername = document.getElementById("mqtt_username_input").value;
    clientPassword = document.getElementById("mqtt_password_input").value;
    mqttTopicToPublish = document.getElementById("topic_publish_input").value;
    mqttTopicToReceivePredictions = document.getElementById("topic_receive_predictions_input").value;
    mqttTopicToReceiveCalculations = document.getElementById("topic_receive_calculations_input").value;
    mqttTimerFullUpdate = parseInt(document.getElementById("mqtt_timer_full_update").value) * 1000;
    mqttTimerPartialUpdate = parseInt(document.getElementById("mqtt_timer_partial_update").value) * 1000;
    mqttMessagesPerTimer = parseInt(document.getElementById("mqtt_messages_per_timer").value);
    backend_read_rate = parseInt(document.getElementById("backend_read_rate_input").value) * 1000;

    // update connection_string variable
    connection_string = `ws://${mqtt_hostname}:${mqtt_port}/mqtt`;

    if (selected_ml_agent_algorithm.length < 1) {
        printableMessage = "Please select at least one ML-agent to be used in positioning predictions.";
        // show window alert message
        alert(printableMessage);

        // call to toggleMlAgentsContainer()
        toggleMlAgentsContainer();

        return;
    }
    else {
        printableMessage = "Connect MQTT:" + "\r\n" + "The client is connecting to the broker...";

        // print to console and to console_debugger
        console.log(printableMessage);
        printOnConsoleDebugger(printableMessage);

        // show window alert message
        alert(printableMessage);

        // call MQTTconnect() function
        connectToMqttBroker(
            session_uuid,
            client_id,
            mqtt_hostname,
            mqtt_port,
            reconnect_timeout,
            clientUsername,
            clientPassword,
            ssl_flag,
            backend_read_rate,
            messageToSend,
            mqttTopicToReceivePredictions,
            mqttTopicToReceiveCalculations,
            geojson_asset_points,
            geojson_antennas,
            geojson_anchors,
            geojson_precison_decimal_places,
            pulsing_dots_layers_sources,
            pulsing_dots_layers_IDs,
            selectedMapIndex,
            received_uuid
        );
    }

    // close container painel
    mqtt_variables_container.style.display = '';
    mqttVariablesContainerFlag = 0;

    return connection_string, backend_read_rate;
}

/**
 * Update Selected ML Agent from Container
 * @param {boolean} connectedFlag MQTT connection state flag
 * @param {FeatureCollection} geojson_asset_points Asset Points FeatureCollection
 * @param {FeatureCollection} geojson_antennas Antennas FeatureCollection
 * @param {FeatureCollection} geojson_anchors Anchors FeatureCollection
 * @param {number} geojson_precison_decimal_places Vaule of precison decimal places 
 * @param {number} backend_read_rate Value of Backend read rate 
 * @param {number} selectedMapIndex Index of selected Map
 * @param {string} mqttTopicToPublish MQTT Topic To Publish Messages
 * @param {string} messageToSend MQTT message to send to broker
 * @returns {Array} Selected ML Agent Algorithm
 */
function updateSelectedMlAgents(
    connectedFlag,
    geojson_asset_points,
    geojson_antennas,
    geojson_anchors,
    geojson_precison_decimal_places,
    backend_read_rate,
    selectedMapIndex,
    mqttTopicToPublish,
    messageToSend
) {
    var checkboxes = document.querySelectorAll('#ml_agents_form input[type=checkbox]:checked')
    prev = selected_ml_agent_algorithm;
    selected_ml_agent_algorithm = [];

    // verify the checkboxes
    checkboxes.forEach(index => {
        selected_ml_agent_algorithm.push(`"${index.id.toLowerCase()}"`)
    });

    if (selected_ml_agent_algorithm.length < 1) {
        printableMessage = "Please select a ML-agent to be used in positioning predictions.";

        // update buttons colors
        updateUnselectedColorButtonSubmitAgents();
        updateUnselectedColorButtonStartConnection();
    }

    else {
        if (selected_ml_agent_algorithm.length > 1) {
            if (selected_ml_agent_algorithm == '"knn","svr","gbr","rf","dt"') {
                selected_ml_agent_algorithm = '"all"';
            }

            printableMessage = "The selected ML-agents to be used in positioning predictions are '" + selected_ml_agent_algorithm + "'.";
        }
        else if (selected_ml_agent_algorithm.length == 1) {
            printableMessage = "The selected ML-agent to be used in positioning predictions is '" + selected_ml_agent_algorithm + "'.";
        }

        // update buttons colors
        updateSelectedColorButtonSubmitAgents();
        updateSelectedColorButtonStartConnection();
    }

    // print to console and to console_debugger
    console.log(printableMessage);
    printOnConsoleDebugger(printableMessage);

    // Construct Json Message
    if (connectedFlag)
        if (selected_ml_agent_algorithm.length >= 1) {
            messageToSend = constructJsonMessage(
                "algs",
                geojson_asset_points,
                geojson_antennas,
                geojson_anchors,
                geojson_precison_decimal_places,
                backend_read_rate,
                selectedMapIndex,
                messageToSend
            );
            publishMqttMessage(mqttTopicToPublish, messageToSend);
        }
        else {
            return prev;
        }

    return selected_ml_agent_algorithm;
}

/**
 * Update RSSI Path-Loss Model parameters
 * @param {boolean} connectedFlag MQTT connection state flag
 * @param {FeatureCollection} geojson_asset_points Asset Points FeatureCollection
 * @param {FeatureCollection} geojson_antennas Antennas FeatureCollection
 * @param {FeatureCollection} geojson_anchors Anchors FeatureCollection
 * @param {number} geojson_precison_decimal_places Vaule of precison decimal places 
 * @param {number} backend_read_rate Value of Backend read rate 
 * @param {number} selectedMapIndex Index of selected Map
 * @param {string} mqttTopicToPublish MQTT Topic To Publish Messages
 * @param {string} messageToSend MQTT message to send to broker
 * @returns {JSON} RSSI parameters as JSON object
 */
function updateRssiPathLossModelParameters(
    connectedFlag,
    geojson_asset_points,
    geojson_antennas,
    geojson_anchors,
    geojson_precison_decimal_places,
    backend_read_rate,
    selectedMapIndex,
    mqttTopicToPublish,
    messageToSend
) {
    txPower = parseFloat(document.getElementById("transmission_power_parameter").value);
    constantFading = parseFloat(document.getElementById("gaussian_distribuition_parameter").value);
    pathLossExpoent = parseFloat(document.getElementById("path_loss_parameter").value);
    referenceDistance = parseFloat(document.getElementById("reference_distance_parameter").value);
    skewIndex = parseFloat(document.getElementById("skew_index_parameter").value);
    attenuationFactor = parseFloat(document.getElementById("attenuation_factor_parameter").value)

    // update button color
    updateRssiParametersColorButton();

    // Populate message to print
    printableMessage = "The RSSI Path-Loss Model parameters have been updated.";

    // print to console and to console_debugger
    console.log(printableMessage);
    printOnConsoleDebugger(printableMessage);

    // Construct Json Message
    if (connectedFlag) {
        messageToSend = constructJsonMessage(
            "rssiUpdateParams",
            geojson_asset_points,
            geojson_antennas,
            geojson_anchors,
            geojson_precison_decimal_places,
            backend_read_rate,
            selectedMapIndex,
            messageToSend
        );
        publishMqttMessage(mqttTopicToPublish, messageToSend);
    }
    return {
        "pathLossExpoent": pathLossExpoent,
        "txPower": txPower,
        "referenceDistance": referenceDistance,
        "constantFading": constantFading,
        "skewIndex": skewIndex,
        "attenuationFactor": attenuationFactor
    };
}

/**
 * Toggle Drag option inside popup
 * @param {string} source_obj Source of FeatureCollection 
 * @param {FeatureCollection} geojson_obj FeatureCollection data object
 * @param {number} currentFeatureId Index of FeatureCollection data object
 * @param {boolean} draggability Value of draggability option
 */
function toggleDrag(source_obj, geojson_obj, currentFeatureId, draggability) {
    // Update the Point feature in `places` coordinates
    var currentObj = geojson_obj.features.find(obj => {
        return obj.properties.id === currentFeatureId;
    })

    // verify draggable option
    if (draggability) {
        currentObj.properties.draggable = false;
        printableMessage = `Draggable option of '${currentObj.properties.title}' was deactivated!`;

    } else {
        currentObj.properties.draggable = true;
        printableMessage = `Draggable option of '${currentObj.properties.title}' was activated!`;
    }

    // print to console and to console_debugger
    console.log(printableMessage);
    printOnConsoleDebugger(printableMessage);

    // Call event to close all open popups
    map.fire('closeAllPopups');

    // Call setData to the source layer `point` on it.
    map.getSource(source_obj).setData(geojson_obj);
}

/**
 * Toggle Predictions option inside popup
 * @param {string} source_obj Source of FeatureCollection 
 * @param {FeatureCollection} geojson_obj FeatureCollection data object
 * @param {number} currentFeatureId Index of FeatureCollection data object
 * @param {boolean} show_predictions Value of show predictions option
 */
function togglePredictions(source_obj, geojson_obj, currentFeatureId, show_predictions) {
    // Update the Point feature in `places` coordinates
    var currentObj = geojson_obj.features.find(obj => {
        return obj.properties.id === currentFeatureId;
    })

    // verify show_predictions option
    if (show_predictions) {
        currentObj.properties.show_predictions = false;
        printableMessage = `Predictions option of '${currentObj.properties.title}' was deactivated!`;

    } else {
        currentObj.properties.show_predictions = true;
        printableMessage = `Predictions option of '${currentObj.properties.title}' was activated!`;
    }

    // print to console and to console_debugger
    console.log(printableMessage);
    printOnConsoleDebugger(printableMessage);

    // Call event to close all open popups
    map.fire('closeAllPopups');

    // Call setData to the source layer `point` on it.
    map.getSource(source_obj).setData(geojson_obj);
}

/**
 * Toggle Lines-of-Sight option inside popup
 * @param {string} source_obj Source of FeatureCollection 
 * @param {FeatureCollection} geojson_obj FeatureCollection data object
 * @param {number} currentFeatureId Index of FeatureCollection data object
 * @param {boolean} show_lines Value of show lines option
 */
function toggleLinesOfSight(source_obj, geojson_obj, currentFeatureId, show_lines) {
    // Update the Point feature in `places` coordinates
    var currentObj = geojson_obj.features.find(obj => {
        return obj.properties.id === currentFeatureId;
    })

    // verify show_lines option
    if (show_lines) {
        currentObj.properties.show_lines = false;
        printableMessage = `Lines-of-Sight option of '${currentObj.properties.title}' was deactivated!`;

    } else {
        currentObj.properties.show_lines = true;
        printableMessage = `Lines-of-Sight option of '${currentObj.properties.title}' was activated!`;
    }

    // print to console and to console_debugger
    console.log(printableMessage);
    printOnConsoleDebugger(printableMessage);

    // Call event to close all open popups
    map.fire('closeAllPopups');

    // Call setData to the source layer `point` on it.
    map.getSource(source_obj).setData(geojson_obj);
}

/**
 * Generate an Animated Image with rgb basecolor
 * @param {string} basecolor RGB color values
 * @returns Animated Pulsing Dot
 */
function generateAnimatedImage(basecolor) {
    // ========== Create Animated Pulsing Dot ==========
    // This implements `StyleImageInterface`
    // to draw a pulsing dot icon on the map.
    const size = 256; // The image will be 256 pixels square.
    const bytesPerPixel = 4; // Each pixel is represented by 4 bytes: red, green, blue, and alpha.
    const animatedPulsingDot = {
        width: size,
        height: size,
        data: new Uint8Array(size * size * bytesPerPixel),

        onAdd: function () {
            const canvas = document.createElement('canvas');
            canvas.width = this.width;
            canvas.height = this.height;
            this.context = canvas.getContext('2d');
            this.map = map;
        },

        render: function () {
            const duration = 1000;
            const t = (performance.now() % duration) / duration;

            const radius = (size / 2) * 0.3;
            const outerRadius = (size / 2) * 0.7 * t + radius;
            const context = this.context;

            // Draw the outer circle.
            context.clearRect(0, 0, this.width, this.height);
            context.beginPath();
            context.arc(
                this.width / 2,
                this.height / 2,
                outerRadius,
                0,
                Math.PI * 2
            );
            context.fillStyle = `rgba(${basecolor}, ${1 - t}})`;
            context.fill();

            // Draw the inner circle.
            context.beginPath();
            context.arc(
                this.width / 2,
                this.height / 2,
                radius,
                0,
                Math.PI * 2
            );
            context.fillStyle = `rgba(${basecolor}, 0.5)`;
            context.strokeStyle = 'white';
            context.lineWidth = 2 + 4 * (1 - t);
            context.fill();
            context.stroke();

            // Update this image's data with data from the canvas.
            this.data = context.getImageData(
                0,
                0,
                this.width,
                this.height
            ).data;

            // Continuously repaint the map, resulting
            // in the smooth animation of the dot.
            this.map.triggerRepaint();

            // Return `true` to let the map know that the image was updated.
            return true;
        }
    }
    return animatedPulsingDot;
}

/**
 * Auto scroll down element (Console debug content)
 * @param {HTMLElement} htmlElement HTML Element (Console debug)
 * @param {string} string Text to print on console
 */
function autoScrollDown(htmlElement, string) {
    // Allow 1px inaccuracy by adding 1
    var isScrolledToBottom = htmlElement.scrollHeight - htmlElement.clientHeight <= htmlElement.scrollTop + 1;
    var newElement = document.createElement("div");
    newElement.innerHTML = "> " + string + "</br>";
    htmlElement.appendChild(newElement);
    // scroll to bottom if isScrolledToBottom
    if (isScrolledToBottom)
        htmlElement.scrollTop = htmlElement.scrollHeight - htmlElement.clientHeight;
}

/**
 * Print string on console debugger innerHTML element and call autoScrollDown()
 * @param {string} string Text to print on console
 */
function printOnConsoleDebugger(string) {
    var element = document.getElementById('console_debugger');
    // auto scroll down
    autoScrollDown(element, string);
}

/**
 * Attach a message to a container innerHTML
 * @param {HTMLElement} container HTML element container
 * @param {string} message Text to print on container
 */
function updateContainerInnerHtml(container, message) {
    container.innerHTML = message;
}

/**
 * Check All Checkbox Options
 * @param {Array} checkboxes Checkboxes HMTL input type Array
 */
function checkAllCheckboxOptions(checkboxes) {
    checkboxes.forEach(index => {
        index.checked = true;
    });
}

/**
 * Uncheck All Checkbox Options
 * @param {Array} checkboxes Checkboxes HMTL input type Array
 */
function uncheckAllCheckboxOptions(checkboxes) {
    checkboxes.forEach(index => {
        index.checked = false;
    });
}

/**
 * Update Rssi Parameters button color
 */
async function updateRssiParametersColorButton() {
    document.getElementById("btn_update_rssi_params").style.background = '#34eba4';
    // await 1 seconds (sleep)
    await new Promise(resolve => setTimeout(resolve, 1000));
    document.getElementById("btn_update_rssi_params").style.background = '#00c8ff';
}

/**
 * Update button color on select ML agents
 */ 
function updateSelectedColorButtonSubmitAgents() {
    document.getElementById("submit_ml_agents").style.background = '#34eba4';
}

/**
 * Update button color on unselect ML agents
 */ 
function updateUnselectedColorButtonSubmitAgents() {
    document.getElementById("submit_ml_agents").style.background = '#00c8ff';
}

/**
 * Update Start Connection button color on selected ML agents
 */ 
function updateSelectedColorButtonStartConnection() {
    document.getElementById("btn_start_connection").style.background = '#34eba4';
}

/**
 * Update Start Connection button color on deselect ML agents
 */ 
function updateUnselectedColorButtonStartConnection() {
    document.getElementById("btn_start_connection").style.background = '#00c8ff';
}

/**
 * Update 'Start Animation' and 'Stop Animation' button colors on start
 */
function updateAnimationColorButtonsOnStart() {
    document.getElementById("btn_start_animations").style.background = '#999999';
    document.getElementById("btn_stop_animations").style.background = '#ff8080';
}

/**
 * Update 'Start Animation' and 'Stop Animation' button colors on stop
 */
function updateAnimationColorButtonsOnStop() {
    document.getElementById("btn_start_animations").style.background = '#00c8ff';
    document.getElementById("btn_stop_animations").style.background = '#999999';
}

/**
 * Update connect and disconnect button colors on connect successfully
 */
function updateConnectionButtonsColors() {
    document.getElementById("connectMqtt").style.background = '#0f0';
    document.getElementById("connectMqtt").style.color = '#000';
    document.getElementById("disconnectMqtt").style.background = '#000';
    document.getElementById("disconnectMqtt").style.color = '#f00';
}

/**
 * Update connect and disconnect button colors on disconnect successfully
 */
function updateDisconnectionButtonsColors() {
    document.getElementById("connectMqtt").style.background = '#000';
    document.getElementById("connectMqtt").style.color = '#0f0';
    document.getElementById("disconnectMqtt").style.background = '#f00';
    document.getElementById("disconnectMqtt").style.color = '#000';
}

/**
 * Set visibility of 'pulsing-dots' layers to visible
 * @param {Array} layers_pulsing_dots_IDs_to_enable Pulsing Dots Layer Ids Array to enable
 * @returns {Array} Pulsing Dots Layer Ids Array with visible elements
 */ 
function setPulsingDotsLayersToVisible(layers_pulsing_dots_IDs_to_enable) {
    var visibles = []
    layers_pulsing_dots_IDs_to_enable.forEach(index => {
        if (map.getLayer(index)) {
            map.setLayoutProperty(index, 'visibility', 'visible');
            visibles.push(index);
        }
    });
    return visibles;
}

/**
 * Set visibility of 'pulsing-dots' layers to none
 * @param {Array} layers_pulsing_dots_IDs_to_disable Pulsing Dots Layer Ids Array to disable
 * @returns {Array} Pulsing Dots Layer Ids Array with non-visible elements
 */
function setPulsingDotsLayersToNone(layers_pulsing_dots_IDs_to_disable) {
    var disables = []
    layers_pulsing_dots_IDs_to_disable.forEach(index => {
        if (map.getLayer(index)) {
            map.setLayoutProperty(index, 'visibility', 'none');
            disables.push(index);
        }
    });
    return disables;
}

/**
 * Update Asset Point Configs option inside popup
 * @param {string} source_obj Source of FeatureCollection 
 * @param {FeatureCollection} geojson_assetpoints Asset Points FeatureCollection 
 * @param {FeatureCollection} geojson_assetpoints_directions Asset Points Directions FeatureCollection 
 * @param {number} prev_large_lobe_angle_direction Previous value of large lobe angle direction of Asset Point
 * @param {number} prev_small_lobe_angle_direction Previous value of small lobe angle direction of Asset Point
 * @param {number} animation_item Asset Point Animation item index
 * @param {number} currentFeatureId Asset Point feature index
 */
function updateAssetPointConfigs(source_obj, geojson_assetpoints, geojson_assetpoints_directions, prev_large_lobe_angle_direction, prev_small_lobe_angle_direction, animation_item, currentFeatureId) {
    // Update the Point feature in `places` coordinates
    var currentObj = geojson_assetpoints.features.find(obj => {
        return obj.properties.id === currentFeatureId;
    })

    // save parameters
    var large_lobe_angle_direction = rescrictTo180Degrees(document.getElementById("ap_large_direction_parameter").value).toFixed(2);
    var small_lobe_angle_direction = rescrictTo180Degrees(document.getElementById("ap_small_direction_parameter").value).toFixed(2);
    var large_lobe_angle_opening = document.getElementById("ap_large_opening_parameter").value;
    var small_lobe_angle_opening = document.getElementById("ap_small_opening_parameter").value;

    // parse values
    if (prev_large_lobe_angle_direction != String(large_lobe_angle_direction)) {
        large_angle_direction_value = parseFloat(large_lobe_angle_direction);
        small_angle_direction_value = parseFloat(rescrictTo180Degrees(parseFloat(large_lobe_angle_direction) + 180));
    }
    else if (prev_small_lobe_angle_direction != String(small_lobe_angle_direction)) {
        large_angle_direction_value = parseFloat(rescrictTo180Degrees(parseFloat(small_lobe_angle_direction) + 180));
        small_angle_direction_value = parseFloat(small_lobe_angle_direction);
    }
    else {
        large_angle_direction_value = parseFloat(large_lobe_angle_direction);
        small_angle_direction_value = parseFloat(small_lobe_angle_direction);
    }

    large_angle_opening_value = parseFloat(large_lobe_angle_opening);
    small_angle_opening_value = parseFloat(small_lobe_angle_opening);

    // set values
    geojson_assetpoints.features[currentFeatureId].properties.angle_direction_large_lobe = large_angle_direction_value;
    geojson_assetpoints.features[currentFeatureId].properties.angle_direction_small_lobe = small_angle_direction_value;
    geojson_assetpoints.features[currentFeatureId].properties.angle_opening_large_lobe = large_angle_opening_value;
    geojson_assetpoints.features[currentFeatureId].properties.angle_opening_small_lobe = small_angle_opening_value;
    geojson_assetpoints.features[currentFeatureId].properties.animation_option_index = parseInt(animation_item.match(/(\d+)$/));

    // set printable message
    printableMessage = `AP '${currentObj.properties.title}' configs was updated!`;

    // print to console and to console_debugger
    console.log(printableMessage);
    printOnConsoleDebugger(printableMessage);

    // call event to close all open popups
    map.fire('closeAllPopups');

    // call setData to the source layer `point` on it.
    map.getSource(source_obj).setData(geojson_assetpoints);

    // remove (splice) the direction at currentFeatureId from 'geojson_assetpoints_directions' FeatureCollection (x2 for asset_points_directions)
    geojson_assetpoints_directions.features.splice(currentFeatureId * 2, 2);

    // create asetpoint Direction at currentFeatureId
    createAssetPointLobesDirection(geojson_assetpoints.features[currentFeatureId], geojson_assetpoints_directions)

    // Call setData to the source layer `places` on it.
    map.getSource('assetpoints_directions').setData(geojson_assetpoints_directions);
}

/**
 * Update Antenna Configs option inside popup
 * @param {string} source_obj Source of FeatureCollection 
 * @param {FeatureCollection} geojson_antennas Antennas FeatureCollection 
 * @param {FeatureCollection} geojson_antennas_directions Antennas Directions FeatureCollection 
 * @param {number} currentFeatureId Antenna feature index
 */
function updateAntennaConfigs(source_obj, geojson_antennas, geojson_antennas_directions, currentFeatureId) {
    // Update the Point feature in `places` coordinates
    var currentObj = geojson_antennas.features.find(obj => {
        return obj.properties.id === currentFeatureId;
    })

    // save parameters
    var angle_direction = document.getElementById("ant_angle_direction_parameter").value;
    var angle_opening = document.getElementById("ant_angle_opening_parameter").value;
    var tx_power = document.getElementById("ant_tx_power_parameter").value;

    // parse values
    angle_direction_value = parseFloat(angle_direction);
    angle_opening_value = parseFloat(angle_opening);
    tx_power_value = parseFloat(tx_power);

    // set values
    geojson_antennas.features[currentFeatureId].properties.angle_direction = angle_direction_value;
    geojson_antennas.features[currentFeatureId].properties.angle_opening = angle_opening_value;
    geojson_antennas.features[currentFeatureId].properties.tx_power = tx_power_value;

    printableMessage = `Antenna '${currentObj.properties.title}' configs was updated!`;

    // print to console and to console_debugger
    console.log(printableMessage);
    printOnConsoleDebugger(printableMessage);

    // Call event to close all open popups
    map.fire('closeAllPopups');

    // Call setData to the source layer `point` on it.
    map.getSource(source_obj).setData(geojson_antennas);

    // remove (splice) the direction at currentFeatureId from 'geojson_antennas_directions' FeatureCollection
    geojson_antennas_directions.features.splice(currentFeatureId, 1);

    // create antenna Direction at currentFeatureId
    createAntennaDirection(geojson_antennas.features[currentFeatureId], geojson_antennas_directions)

    // call setData to the source layer `places` on it.
    map.getSource('antennas_directions').setData(geojson_antennas_directions);

    // set flag needUpdateAntennas to true
    needUpdateAntennas = true;
}

/**
 * Create Asset Point Lobes Direction with provided a rotation angles
 * @param {number} index Asset point feature index
 * @param {FeatureCollection} geojson_assetpoints_directions Asset Point Directions FeatureCollection
 * @returns {FeatureCollection} Asset Points Directions FeatureCollection
 */
function createAssetPointLobesDirection(index, geojson_assetpoints_directions) {
    // first save antenna coordinates
    var assetpoint_coordinates = index.geometry.coordinates;
    // value of rotation of large lobe in decimal degrees, anti-clockwise
    var angle_direction_large = -index.properties.angle_direction_large_lobe;
    // value of rotation of small lobe in decimal degrees, anti-clockwise
    var angle_direction_small = -index.properties.angle_direction_small_lobe;

    // use turf.js - Sector
    // Creates a circular sector of a circle of given radius and center Point, between (clockwise) bearing1 and bearing2; 
    // 0 bearing is North of center point, positive clockwise.
    var center = turf.point([assetpoint_coordinates[0], assetpoint_coordinates[1]]);
    var radiusFactor = 1000;
    var radius1 = 1 / radiusFactor * 0.75;
    var radius2 = 1 / radiusFactor * 0.55;

    var bearingLarge1 = 0 - index.properties.angle_opening_large_lobe / 2;
    var bearingLarge2 = bearingLarge1 + index.properties.angle_opening_large_lobe;

    var bearingSmall1 = 0 - index.properties.angle_opening_small_lobe / 2;;
    var bearingSmall2 = bearingSmall1 + index.properties.angle_opening_small_lobe;

    var sectorLobeLarge = turf.sector(center, radius1, bearingLarge1, bearingLarge2);
    var sectorLobeSmall = turf.sector(center, radius2, bearingSmall1, bearingSmall2);

    // apply rotation
    var options = { pivot: [assetpoint_coordinates[0], assetpoint_coordinates[1]] };
    var rotatedSectorLobeLarge = turf.transformRotate(sectorLobeLarge, angle_direction_large, options);
    var rotatedSectorLobeSmall = turf.transformRotate(sectorLobeSmall, angle_direction_small, options);

    // push (splice) the 'sector' object in 'index.properties.id', deleting 0 elements (x2 for asset_points_directions)
    geojson_assetpoints_directions.features.splice(index.properties.id * 2, 0, rotatedSectorLobeLarge, rotatedSectorLobeSmall);

    // copy properties (x2 for asset_points_directions)
    geojson_assetpoints_directions.features[index.properties.id * 2].properties = index.properties;
    geojson_assetpoints_directions.features[index.properties.id * 2 + 1].properties = index.properties;

    return geojson_assetpoints_directions;
}

/**
 * Create Antenna Direction with provided a rotation angle
 * @param {number} index Antenna feature index
 * @param {*} geojson_antennas_directions Antennas Directions FeatureCollection
 * @returns {FeatureCollection} Antennas Directions FeatureCollection
 */
function createAntennaDirection(index, geojson_antennas_directions) {
    // first save antenna coordinates
    var antenna_coordinates = index.geometry.coordinates;

    // value of rotation in decimal degrees, anti-clockwise
    var angle_direction = -index.properties.angle_direction;

    // use turf.js - Sector
    // Creates a circular sector of a circle of given radius and center Point, between (clockwise) bearing1 and bearing2; 
    // 0 bearing is North of center point, positive clockwise.
    var center = turf.point([antenna_coordinates[0], antenna_coordinates[1]]);
    var radiusFactor = 1000;
    var radius = 1 / radiusFactor;

    var bearing1 = 0 - index.properties.angle_opening / 2;
    var bearing2 = bearing1 + index.properties.angle_opening;
    var sector = turf.sector(center, radius, bearing1, bearing2);

    // apply rotation
    var options = { pivot: [antenna_coordinates[0], antenna_coordinates[1]] };
    var rotatedSector = turf.transformRotate(sector, angle_direction, options);

    // push (splice) the 'sector' object in 'index.properties.id', deleting 0 elements
    geojson_antennas_directions.features.splice(index.properties.id, 0, rotatedSector)

    // copy antennas properties
    geojson_antennas_directions.features[index.properties.id].properties = index.properties;

    return geojson_antennas_directions;
}

/**
 * Catch the Lines Of Sight FeatureCollection
 * @param {FeatureCollection} geojson_objs Points (Asset Points or Anchors) FeatureCollection data
 * @param {FeatureCollection} geojson_antennas Antennas FeatureCollection
 * @returns {Array} Lines Of Sight Array data
 */
function catchLinesOfSight(geojson_objs, geojson_antennas) {
    // set empty array data
    var lines = [];

    // Iterate by all the 'geojson_antennas.features', using array.forEach(index => { // do something here });
    geojson_antennas.features.forEach(antenna_index => {
        // Iterate by all the 'geojson_antennas.features', using array.forEach(index => { // do something here });
        geojson_objs.forEach(objs_index => {
            if (objs_index.properties.show_lines) {
                // create a line between asset_point and all of 'geojson_antennas'
                var line = turf.lineString([objs_index.geometry.coordinates, antenna_index.geometry.coordinates]);
                // populate array of lines_of_sight 
                lines.push(line);
            }
        });
    });

    return lines;
}

/**
 * Update Lines Of Sight Coordinates
 * @param {FeatureCollection} geojson_asset_points Asset Points FeatureCollection data
 * @param {FeatureCollection} geojson_anchors Anchors FeatureCollection data
 * @param {FeatureCollection} geojson_antennas Antennas FeatureCollection data
 * @param {FeatureCollection} geojson_lines_of_sight Lines of Sight FeatureCollection data
 * @returns {Array} Lines Of Sight Array data
 */
function updateLinesOfSightCoordinates(geojson_asset_points, geojson_anchors, geojson_antennas, geojson_lines_of_sight) {
    // set empty array data
    var data = [];

    // concat asset_points and anchors features 
    var geojson_objs = geojson_asset_points.features.concat(geojson_anchors.features)

    // catch the lines of sight layer
    geojson_lines_of_sight.features = catchLinesOfSight(geojson_objs, geojson_antennas)

    // Iterate by all the 'geojson_lines_of_sight.features', using array.forEach(index => { // do something here });
    geojson_lines_of_sight.features.forEach(index => {
        data.push({
            'type': 'Feature',
            "properties": {
                "id": index
            },
            "geometry": {
                "type": "LineString",
                "coordinates": index.geometry.coordinates
            }
        });
        // Update the point feature coordinates in `lines_of_sight` 
        index = data[index];
    });

    return geojson_lines_of_sight;
}

/**
 * Animate Lines Of Sight function 
 * @param {FeatureCollection} geojson_asset_points Asset Points FeatureCollection data
 * @param {FeatureCollection} geojson_anchors Anchors FeatureCollection data
 * @param {FeatureCollection} geojson_antennas Antennas FeatureCollection data
 * @param {FeatureCollection} geojson_lines_of_sight Lines of Sight FeatureCollection data
 */
function animateLinesOfSight(geojson_asset_points, geojson_anchors, geojson_antennas, geojson_lines_of_sight) {
    // Update the data to a new position based on the animation timestamp. The
    // divisor in the expression `timestamp / 1000` controls the animation speed.
    map.getSource('lines-of-sight').setData(updateLinesOfSightCoordinates(geojson_asset_points, geojson_anchors, geojson_antennas, geojson_lines_of_sight));
}

/**
 * Catch the Wall Intersections in Lines Of Sight 
 * @param {FeatureCollection} lines_of_sight Lines of Sight FeatureCollection data
 * @param {FeatureCollection} walls Map Features Walls FeatureCollection data
 * @returns {Array} Lines-of-Sight Walls Intersection Points Array
 */
function catchWallIntersections(lines_of_sight, walls) {
    // set empty array data
    var wall_intersections = [];

    // Iterate by all the 'lines_of_sight.features', using array.forEach(index => { // do something here });
    lines_of_sight.features.forEach(index => {
        // verify if line-of-sight have walls intersections
        var intersection = turf.lineIntersect(index, walls);
        // populate array of wall_intersections 
        wall_intersections.push(intersection.features);
    });

    return wall_intersections;
}

/**
 * Update Wall Intersection Points Coordinates
 * @param {FeatureCollection} wall_intersections_points Lines-of-Sight Walls Intersection Points Array
 * @param {FeatureCollection} geojson_lines_of_sight Lines-of-Sight FeatureCollection
 * @param {FeatureCollection} geojson_walls Map Features Walls FeatureCollection data
 * @returns {Array} Lines-of-Sight Walls Intersection Points Array
 */
function updateWallIntersectionPointsCoordinates(wall_intersections_points, geojson_lines_of_sight, geojson_walls) {
    // set empty array data
    var data = [];

    // catch line of sight walls intersections
    wall_intersections_points.features = catchWallIntersections(geojson_lines_of_sight, geojson_walls);

    // Iterate by all the 'lines_of_sight.features', using array.forEach(index => { // do something here });
    wall_intersections_points.features.forEach(index_i => {
        index_i.forEach(index_j => {
            data.push({
                'type': 'Feature',
                "properties": {
                    "id": index_i * index_j + index_i + index_j
                },
                "geometry": {
                    "type": "Point",
                    "coordinates": index_j.geometry.coordinates
                }
            });
        });
    });
    // Update the wall_intersections_points.features with data array
    wall_intersections_points.features = data

    return wall_intersections_points;
}

/**
 * Animate Wall Intersection Points function
 * @param {FeatureCollection} wall_intersections_points Lines-of-Sight Walls Intersection Points Array
 * @param {FeatureCollection} geojson_lines_of_sight Lines-of-Sight FeatureCollection
 * @param {FeatureCollection} geojson_walls Map Features Walls FeatureCollection data
 */
function animateWallIntersectionPoints(wall_intersections_points, geojson_lines_of_sight, geojson_walls) {
    // Update the data to a new position based on the animation timestamp. The
    // divisor in the expression `timestamp / 1000` controls the animation speed.
    map.getSource('wall-intersections-points').setData(updateWallIntersectionPointsCoordinates(wall_intersections_points, geojson_lines_of_sight, geojson_walls));
}

/**
 * Update Pulsing Dot Point Coordinates
 * @param {FeatureCollection} geojson_pulsing_dots_points Pulsing Dot Points FeatureCollection
 * @param {number} length_source FeatureCollection size
 * @param {number} index_source Pulsing Dot Points FeatureCollection index
 * @returns {FeatureCollection} Pulsing Dot Points FeatureCollection Updated
 */
function updatePulsingDotPointsCoordinates(geojson_pulsing_dots_points, length_source, index_source) {
    // set empty array data
    var data = []

    // Iterate by all the 'geojson_pulsing_dots_points.features', using array.forEach(index => { // do something here });
    geojson_pulsing_dots_points.features.forEach(index => {
        const new_id = index.properties.id - (length_source * index_source)
        data.push({
            'type': 'Feature',
            'properties': {
                'id': index.properties.id,
                'title': `Pulsing-Dot Point ${(index.properties.id + 1)}`,
                'description': `Pulsing-Dot point ${(index.properties.id + 1)} description`,
                'algorithm': index.properties.algorithm.toUpperCase(),
                'basecolor': index.properties.basecolor,
                'draggable': false,
            },
            'geometry': {
                'type': 'Point',
                'coordinates': index.geometry.coordinates
            }
        });
        // Update the point feature coordinates in `geojson_pulsing_dots_points` 
        geojson_pulsing_dots_points.features[new_id] = data[new_id];
    });

    return geojson_pulsing_dots_points;
}


/**
 * Animate Pulsing Dot Points function 
 * @param {Array} pulsing_dots_layers_sources Pulsing Dots sources Array
 */
function animatePulsingDotPoints(pulsing_dots_layers_sources) {
    for (let i = 0, len_i = pulsing_dots_layers_sources.length; i < len_i; i++) { 
        // Update the data to a new position based on the animation timestamp. The
        // divisor in the expression `timestamp / 1000` controls the animation speed.
        map.getSource(`pulsing-dot-points-${i+1}`).setData(updatePulsingDotPointsCoordinates(pulsing_dots_layers_sources[i], pulsing_dots_layers_sources[i].features.length, i));
    }
}

/**
 * Catch the Antennas Direction Intersections in Lines Of Sight ((UNCALLED - done in calculate at backend module)
 * @param {FeatureCollection} lines_of_sight Lines of Sight FeatureCollection data
 * @param {FeatureCollection} geojson_antennas_directions Antennas Directions FeatureCollection data 
 * @returns {boolean} Result value of Asset Points with Antennas Directions
 */
function catchAntennaDirectionsIntersections(lines_of_sight, geojson_antennas_directions) {
    // set empty array data
    var isDirectionated = [];

    // Iterate by all the 'lines_of_sight.features', using array.forEach(index => { // do something here });
    lines_of_sight.features.forEach(index => {
        // verify if if line-of-sight have intersections with geojson_antennas_directions
        // boolean-disjoint returns (true) if the intersection of the two geometries is an empty set.
        value = !turf.booleanDisjoint(index, geojson_antennas_directions.features[index])
        // populate array isDirectionated
        isDirectionated.push(value)
    });

    return isDirectionated;
}

// =========== End of UI Interections Functions =========== //