3

I am trying to add covid Data from a URL to a leaflet map. The issue is that my map is loading and drawing before the data has fully loaded. I have a file of JSON data which I call statesData. Then, I use the p5.js loadJSON() function to grab covid data from a URL and another function to format that data.

 // GET THE COVID DATA
    function setup(){
        loadJSON("https://disease.sh/v3/covid-19/states",gotData);
    }
    function gotData(data){
        covid = data;

        statesData.features[1].properties.casesPerOneMillion = covid[1].casesPerOneMillion;
        // add covid cases to states data
        for (let i = 0; i < statesData.features.length; i++) {
            for (let j = 0; j < statesData.features.length; j++) {
                if (statesData.features[i].properties.name === covid[j].state) {
                    statesData.features[i].properties.casesPerOneMillion = covid[j].casesPerOneMillion;
                }
            }
        }
    };

Here's an example of what the data looks like when I inspect the page and print it to the console: enter image description here

Here is how I'm adding the color layers:

    // GET CHLORO COLORS BASED ON CASES PER MIL NUM
    function getColor(d) {
        return d > 30000 ? '#800026' :
            d > 25000  ? '#BD0026' :
                d > 20000  ? '#E31A1C' :
                    d > 15000  ? '#FC4E2A' :
                        d > 5000   ? '#FD8D3C' :
                            d > 2000   ? '#FEB24C' :
                                d > 1000   ? '#FED976' :
                                    '#FFEDA0';
    }
    
    // CREATE FUNCTION TO STYLE AND APPLY GET COLOR
    function style(feature) {
        return {
            // apply get color
            fillColor: getColor(feature.properties.casesPerOneMillion),
            weight: 2,
            opacity: 1,
            color: 'white',
            dashArray: '3',
            fillOpacity: 0.7
        }
    }
    // doesn't work
    L.geoJson(statesData, {style: style}).addTo(map);

I know it isn't an issue with the code, because this (5000 being a random num I used just to be sure my style code was working) :

statesData.features[0].properties.casesPerOneMillion = 5000;

and it does work that way. The problem is that my map is loading before the data has finished downloading/has gone through the gotData() function that formats it. I thought the obvious solution would be to throw the L.geoJson(statesData, {style: style}).addTo(map); into an async function, but I guess leaflet doesn't allow you to do that? I always get the error "t.addLayer()" isn't a function when I try... Anyone know how to asynchronously add data to a leaflet map? I'm familiar with javaScript but am a novice when it comes back to async functions, callbacks, etc

3 Answers3

3

You don't need to use async/await for asynchronous code. The default is still to use the, now somewhat old-school, callback tree. So just put your L.geoJson call inside the gotData callback.

This should work. I've allowed myself to replace the getColor if-then-else monster into a lookup table. Just for style points :-)

const colorMap = {
  30000: '#800026',
  25000: '#BD0026',
  20000: '#E31A1C',
  15000: '#FC4E2A',
  5000: '#FD8D3C',
  2000: '#FEB24C',
  1000: '#FED976',
};

// GET CHLORO COLORS BASED ON CASES PER MIL NUM
const getColor = (d) => colorMap(d) || '#FFEDA0';

// CREATE FUNCTION TO STYLE AND APPLY GET COLOR
function style(feature) {
  return {
    // apply get color
    fillColor: getColor(feature.properties.casesPerOneMillion),
    weight: 2,
    opacity: 1,
    color: 'white',
    dashArray: '3',
    fillOpacity: 0.7
  }
}

// GET THE COVID DATA
function setup(){
  loadJSON("https://disease.sh/v3/covid-19/states", gotData);
}

function gotData(statesData){
  covid = statesData;
  
  statesData.features[1].properties.casesPerOneMillion = covid[1].casesPerOneMillion;
  // add covid cases to states data
  for (let i = 0; i < statesData.features.length; i++) {
    for (let j = 0; j < statesData.features.length; j++) {
      if (statesData.features[i].properties.name === covid[j].state) {
        statesData.features[i].properties.casesPerOneMillion = covid[j].casesPerOneMillion;
      }
    }
  }
  
  // now it should work
  L.geoJson(statesData, {style: style});
};

Update: As Falke Design points out, there was another bug in gotData: data needed to be statesData. I changed that.

Christian Fritz
  • 20,641
  • 3
  • 42
  • 71
  • Oops - I totally messed up my original question and left off the .addTo(map) part. What I'm trying to do is L.geoJson(data, {style: style}).addTo(map). You're totally right that that solution works without the part I forgot to mention - thanks for making me realize I accidentally pasted the wrong thing into my question and for the color table - looks much cleaner! If you have any ideas of how to do my revised question let me know! – SoftEngStudent Jan 10 '21 at 00:08
  • It should work the same way. Does it not? – Christian Fritz Jan 10 '21 at 00:24
  • You may need to give `map` to `setup` (not sure where you are calling that), and then hand it to `gotData`, as in `loadJson(..., (data) => gotData(data, map))`. You get the idea. – Christian Fritz Jan 10 '21 at 00:25
  • Doesn't work :/ I've tried moving it to the ```setup``` and ```gotData``` functions, neither work. I've even tried multiple ways of dong async functions and it won't let me add to map inside of any of them. The error is always ```"leaflet.js:5 Uncaught (in promise) TypeError: t.addLayer is not a function"``` – SoftEngStudent Jan 10 '21 at 00:37
  • Here's a link to the git repo https://github.com/isabellasims/covid-map if you ever wan't to take a look at the full covid.html file to get the full picture :) Thanks a lot for your help thus far! – SoftEngStudent Jan 10 '21 at 01:00
  • 1
    `L.geoJson(data, {style: style});` will not work, because `data` is no `geojson` feature – Falke Design Jan 10 '21 at 09:57
  • @Falke why does it work if I pre-download the data then? It only doesn't work when I fetch it from a URL... Example - did the same thing with density (which was pre-downloaded) and it worked perfectly fine. The only difference in the code is that it was ```feature.properties.density``` instead of ```casesPerMillion```. Here's the tutorial from leaflet here that I followed https://leafletjs.com/examples/choropleth/ . At this point just trying to understand why your solution works - but seriously thank you so much for finding it! – SoftEngStudent Jan 10 '21 at 23:14
1

Your function gotData will not work, because you never loop over data, you loop twice over statesData.features.length

You can do this in two ways.

1. define L.geoJSON after loading data

function gotData(data) {
    var covid = data;
    // add covid cases to states data
    for (let j = 0; j < data.length; j++) {
        for (let i = 0; i < statesData.features.length; i++) {
            if (statesData.features[i].properties.name === covid[j].state) {
                statesData.features[i].properties.casesPerOneMillion = covid[j].casesPerOneMillion;
                break; // break the loop and go to the next state (outer loop), because you have already found the correct state entry
            }
        }
    }

    L.geoJSON(statesData, {
        style: style
    }).addTo(map);
};

2. define L.geoJSON before and add only the loaded data

var geo = L.geoJSON(null, {
    style: style
}).addTo(map);

function gotData(data) {
    var covid = data;
    // add covid cases to states data
    for (let i = 0; i < data.length; i++) {
        for (let j = 0; j < statesData.features.length; j++) {
            if (statesData.features[i].properties.name === covid[j].state) {
                statesData.features[i].properties.casesPerOneMillion = covid[j].casesPerOneMillion;
                break; // break the loop and go to the next state (outer loop), because you have already found the correct state entry
            }
        }
    }

    geo.addData(statesData);
};
Falke Design
  • 10,635
  • 3
  • 15
  • 30
  • Thank you so much! The second solution works and I know what the problem was now! (just FYI - ```the gotData()``` function worked fine because the lengths of ```covid``` and ```statesData``` are the same - good call on the break statement though. The first solution doesn't work (I had already tried it - in post mentioned leaflet throws an error anytime you add to map inside an async function for whatever reason. Turns out, I just needed to be assigning the geoJSON to a variable this whole time (the null isn't necessary). – SoftEngStudent Jan 10 '21 at 23:33
0

With the help of answers above, I determined my original problem was that I was trying to define geo and add it to map at the same time. geo needed to be defined and added to the map outside of the gotData() function. Inside the gotData() function, the addData() function solved the problem.

 geo = L.geoJson(statesData, {
        style: style,
        onEachFeature: onEachFeature,

    });

// *** GET THE COVID DATA *** //
function setup(){
    loadJSON("https://disease.sh/v3/covid-19/states",gotData);
}

// *** FORMAT THE DATA *** //
// adds casesPerMillion from queried data to states data
function gotData(data){
    covid = data;
    statesData.features[1].properties.casesPerOneMillion = covid[1].casesPerOneMillion;
    // add covid cases to states data
    for (let i = 0; i < statesData.features.length; i++) {
        for (let j = 0; j < statesData.features.length; j++) {
            if (statesData.features[i].properties.name === covid[j].state) {
                // add cases per million to statesData
                statesData.features[i].properties.casesPerOneMillion = covid[j].casesPerOneMillion;
                // add tests per million to statesData
                statesData.features[i].properties.testsPerOneMillion = covid[j].testsPerOneMillion;
                break;
            }
        }
    }
    geo.addData(statesData); // another part of the solution - addData function

};

geo.addTo(map);