Leaflet – Keeping Popup Text Open After Marker Refresh

eventsjavascriptleafletpopup

I have written a JavaScript code (I just got started) which queries the Opensky REST API every 10 seconds. After I collect the data, I plot it through Leaflet.js. For each item I add a popup text box which opens when I click on the tooltip.
So far the code works great!

Here is the code so you can play around:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=\, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="id=edge">
        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="crossorigin=""/>
        <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
            integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
            crossorigin="">
        </script>
        <style>
            #openskyMap { 
                height: 800px; 
                width:1200px;
            }
        </style>
        <title>
            "FETCH JSON from API and map lat lon"
        </title>
    </head>
    <body>

        <h1>Where are the flights</h1>
        <p>Last Update (UNIX time): <span id="ts"></span></p>

        <div id="openskyMap"></div>

        /*                           JAVASCRIPT CODE BEGINS HERE                             */ 
        <script>

            /* DEFINE OPENSKY API URL */
            var opensky_url = "https://opensky-network.org/api/states/all?time=TIME";

            /* INITIATE LEAFLET MAP */
            const mymap = L.map('openskyMap').setView([45, 0], 3);
            const attribution = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreatMap</a> contributors'
            const tileUrl = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
            const tiles = L.tileLayer(tileUrl, {attribution});
            var layerGroup = L.layerGroup().addTo(mymap);
            tiles.addTo(mymap);

            /* CHANGE ICON SIZE BASED ON ZOOM */
            mymap.on('zoomend', function(e) {
                var currentZoom = mymap.getZoom();
                console.log("Current Zoom" + " " + currentZoom);
                if (currentZoom >= 10) {
                damsRadius = 100;
                } else {
                damsRadius = 20000;
                }
            });

            /* FUNCTION TO GET THE ICAO24 CODE. I CANNOT GET IT OUT OF HERE THOUGH! */
            function getIcao24(e) {
                var popup = e.target.getPopup();
                var content = popup.getContent();
                console.log(content)
                return content
            }

            /* INITIATE QUERY FUNCTION */
            async function getOpensky() {

                /* CALCULATE UNIX TIME TO BE PASSED TO THE URL */
                var ts = Math.round((new Date()).getTime() / 1000);
                ts = ts.toString();

                /* MAKE QUERY TO OPENSKY URL */
                const  response = await fetch(opensky_url.replace('TIME',ts));
                const data = await response.json();

                /* PLOT ITEMS TO MAP USING LEAFLET:js */
                layerGroup.clearLayers();
                for (var key in data.states) {

                    /* GET SINGLE ITEM FROM JSON */
                    const item = data.states[key];

                    try{ 

                       /* GET GEOGRAPHICAL COORDINATES*/
                        const lon = item[5];
                        const lat = item[6];

                        /* DEFINE POPUP TEXT TO BE DISPLAYED*/
                        const popupText = "<p>"+item[0] + " - " + item[1] + "<br/>Lat: " + lat.toFixed(2)+" / Lon: "+lon.toFixed(2)+"<br/>Epoch: "+ts+"</p>";

                        /* ADD ITEM TO THE MAP. CALL getICAO24 FUNCTION BUT I DON'T KNOW HOW TO SAVE THE OUTPUT*/
                        L.circle(L.latLng(
                            parseFloat(item[6]), // lon
                            parseFloat(item[5])  // lat
                        ), damsRadius).addTo(layerGroup).bindPopup(popupText).on('click', getIcao24);

                        document.getElementById('ts').textContent = ts;
                    }
                    catch (e) {}
                }               

            }
            /* CALL FUNCTION FIRST TIME*/
            getOpensky();

            /* UPDATE FUNCTION EVERY 10 SECONDS */
            setInterval(getOpensky, 10000);

        </script>
    </body>
</html>

Every time I click on an aircraft (item), the popup window opens as expected. However, I would like to keep the selected popup text open even after the refresh has occurred (setInterval every 10 seconds). This is not happening due to the fact that when I click on the tooltip, the output is only displayed on the console but not store anywhere to be somehow recognized during the next query.

I though that one possible solution is to add a function called getIcao24 which should return the popup text and get the unique icao24 code (item[0]) from the string and then check when it appears in the next iteration and set the popup box as open. However I have no clue how to get such information out of the function.

Would you be able to suggest a smart and elegant way to achieve my goal, i.e. maintain the popup box of a clicked item open even after a refresh has occurred?

Best Answer

There are probably many ways to achieve what you want, below is described one of them I usually use.

To have handy some property when you process marker event, you can 'misuse' marker object and just define this property as a new maker property, to which you can give any name as long as it does not collide with standard properties. In the example below it's _myID, which gets the value of item[0].

Then when popup is opened (more pertinent popupopen event is used), _myID property is saved in global variable idPopup.

When markers are refreshed next time, item[0] values are compared to saved id and upon match popup is opened.

There is also popupclose event processing function to clear idPopup if popup is closed by user before refresh. Since popup is closed also at the time marker removal/referesh, there is refreshInProgress variable to take care of that. popupFound variable takes care of the case when marker with the popup is not displayed upon next refresh.

So the code could the look something like this:

var idPopup = '';            

function getIcao24(e) {
  idPopup = e.target._myID;
}

function processPopupClose(e) {
  if (!refreshInProgress) {
    idPopup = '';
  }
}

async function getOpensky() {
  var ts = Math.round((new Date()).getTime() / 1000);
  ts = ts.toString();
  const  response = await fetch(opensky_url.replace('TIME',ts));
  const data = await response.json();
  refreshInProgress = true;
  layerGroup.clearLayers();
  popupFound = false;
  for (var key in data.states) {
    const item = data.states[key];
    try { 
      const lon = item[5];
      const lat = item[6];
      const popupText = "<p>"+item[0] + " - " + item[1] + "<br/>Lat: " + lat.toFixed(2)+" / Lon: "+lon.toFixed(2)+"<br/>Epoch: "+ts+"</p>";
      var marker = L.circle(L.latLng(
        parseFloat(item[6]), // lon
        parseFloat(item[5])  // lat
      ), damsRadius).addTo(layerGroup).bindPopup(popupText)
      .on('popupopen', getIcao24)
      .on('popupclose', processPopupClose);
      marker._myID = item[0];
      if (item[0] == idPopup) {
        marker.openPopup();
        popupFound = true;
      }
      document.getElementById('ts').textContent = ts;
    }
    catch (e) {}
  }               
  if (!popupFound) idPopup = '';
  refreshInProgress = false;
}