The Coronavirus (COVID-19) pandemic has swiftly changed how all of us interact day to day. How can we use available APIs to build a mapping app that shows the impact it has had on the world?
We’ll be putting together a mapping application that uses an API containing recent Coronavirus statistics and maps out the locations and impact each country is facing.
Coronavirus map dashboard demo
On the map, we’ll show a marker for each country with the number of confirmed cases. On top of that, we’ll include a little popup tooltip that shows more in depth information.
The map we’ll build will mostly look like the above, but will look a little simpler. We’ll utilize the OpenStreetMap public tileserver instead of using a custom Mapbox
To get started, we’re going to use this Leaflet Gatsby Starter I created to make the initial setup a little smoother. With our app bootstrapped, we’ll fetch our data and add markers to the map with our data.
Yup. If you haven’t played with maps before, don’t be discouraged! It’s not as bad as you probably think.
We’ll want to make sure we have the following set up:
yarn global add gatsby-cli
We’ll also want to set up a foundation for our map. We can do this by utilizing the Leaflet Gatsby Starter I put together that provides us a basic setup with Leaflet and React Leaflet.
gatsby new my-coronavirus-map https://github.com/colbyfayock/gatsby-starter-leaflet
Creating a new Leaflet Gatsby app in the terminal
After that’s finished running, you can navigate to the newly created project directory and start your local development server:
cd my-coronavirus-map
yarn develop
If all goes as planned, your server should start and you should now be able to see your basic mapping app in your browser!
The Gatsby Starter we’re using to spin up this app comes with some demo code that we don’t need here. We’ll want to make all of the changes below in the file src/pages/index.js
, which is the homepage of our app.
First, let’s remove everything from the mapEffect
function. This function is used to run code that fires when the map renders.
// In src/pages/index.js
async function mapEffect({ leafletElement } = {}) {
// Get rid of everything in here
}
We’ll also change the variable name of our leafletElement
simply for being able to more easily understand the code as we write it.
async function mapEffect({ leafletElement: map } = {}) {
}
Next, we don’t want a marker this time, so let’s remove the <Marker
component from our <Map
component:
<Map {…mapSettings} />
Now that we have those pieces cleared out, we can remove all of the following imports and variables from the top of our file:
After, our map should still work, but not do anything.
For our app, we’re going to use the NovelCOVID API. Particularly, we’re going to use the countries endpoint to fetch the list of our countries and the stats associated with them.
For making requests, I personally like to use axios as it has a nice to use API. If you want to use fetch or your own favorite request library, substitute that in for this step.
We’ll start by installing axios:
yarn add axios
Once that installs, remember to restart your server.
Import the axios package ta the top of our pages/index.js
file:
import axios from ‘axios’;
Next we’ll actually make our request. Inside our mapEffect
function, let’s try to make a request to the API endpoint:
async function mapEffect({ leafletElement: map } = {}) {
let response;
try {
response = await axios.get(‘https://corona.lmao.ninja/countries’);
} catch(e) {
console.log(`Failed to fetch countries: ${e.message}`, e);
return;
}
const { data = [] } = response;
}
In this snippet, we’re doing the following:
response
variable that will allow us to store the responsetry/catch
block that will catch any API errors if the request failsresponse
variabledata
from the response and set the default value to an empty array, as that will be the type of data we needAfter that’s set up, we can console log out the data
object and we’ll see our data successfully fetched!
Logging the Coronavirus location data to the browser console
Now that we have our data, we can transform it into a geographic data format, particularly GeoJSON, that will allow us to interface with Leaflet.
Let’s start by adding this block of code:
const { data = [] } = response;
const hasData = Array.isArray(data) && data.length > 0;
if ( !hasData ) return;
const geoJson = {
type: ‘FeatureCollection’,
features: data.map((country = {}) => {
const { countryInfo = {} } = country;
const { lat, long: lng } = countryInfo;
return {
type: ‘Feature’,
properties: {
…country,
},
geometry: {
type: ‘Point’,
coordinates: [ lng, lat ]
}
}
})
}
So what are we doing here?
hasData
that checks if our data
variable is an array and has datageoJson
object that will be our GeoJSON documentFeatureCollection
and as our features
we loop through our datasetlat
and lng
to create a point for our mapIf you console.log
this object our into your browser and copy the contents, you can paste this into geojson.io and see the location data show up correctly.
Previewing Coronavirus location data on geojson.io
With this GeoJSON document, we’ll now be able to add it to our map.
We have our GeoJSON document with our location data, so let’s add it to the map.
Let’s start with this code block. It’s a long one, but we’ll break it down piece by piece:
const geoJsonLayers = new L.GeoJSON(geoJson, {
pointToLayer: (feature = {}, latlng) => {
const { properties = {} } = feature;
let updatedFormatted;
let casesString;
const {
country,
updated,
cases,
deaths,
recovered
} = properties
casesString = `${cases}`;
if ( cases > 1000 ) {
casesString = `${casesString.slice(0, -3)}k+`
}
if ( updated ) {
updatedFormatted = new Date(updated).toLocaleString();
}
const html = `
<span class=“icon-marker”>
<span class=“icon-marker-tooltip”>
<h2>${country}</h2>
<ul>
<li><strong>Confirmed:</strong> ${cases}</li>
<li><strong>Deaths:</strong> ${deaths}</li>
<li><strong>Recovered:</strong> ${recovered}</li>
<li><strong>Last Update:</strong> ${updatedFormatted}</li>
</ul>
</span>
${ casesString }
</span>
`;
return L.marker( latlng, {
icon: L.divIcon({
className: ‘icon’,
html
}),
riseOnHover: true
});
}
});
So what are we doing here?
L.GeoJSON
which will transform our GeoJSON document into something Leaflet will understandpointToLayer
function. This allows us to customize the map layer Leaflet creates for our map1k+
instead of 1000
and a formatted date instead of the timestampL.marker
with our custom configuration that includes a class of icon
for the container and our custom HTML.riseOnHover
property so when hoving over a marker, it surfaces itself above over the other markers on the mapWe also want to add a bit of CSS here so that we can make sure our markers show up in the map and are usable. Let’s add this snippet to our assets/stylesheets/components/_map.scss
file:
.icon-marker {
display: flex;
position: relative;
justify-content: center;
align-items: center;
color: white;
width: 3.6em;
height: 3.6em;
font-size: .7em;
font-weight: bold;
background-color: $red-800;
border-radius: 100%;
box-shadow: 0 2px 5px rgba(black, .9);
&:hover {
.icon-marker-tooltip {
display: block;
}
}
}
.icon-marker-tooltip {
display: none;
position: absolute;
bottom: 100%;
width: 16em;
font-size: 1.4em;
padding: 1em;
background-color: $blue-grey-900;
border-radius: .4em;
margin-bottom: 1em;
box-shadow: 0 3px 5px rgba(black, .9);
&:before {
display: block;
position: absolute;
bottom: -.6em;
left: 50%;
content: ‘’;
width: 1.4em;
height: 1.4em;
background-color: $blue-grey-900;
transform: rotate(45deg);
margin-left: -.7em;
}
h2 {
font-size: 1.5em;
line-height: 1.2;
margin-bottom: .1em;
margin-top: 0;
}
h3 {
font-size: 1.2em;
margin: .1em 0;
font-weight: normal;
color: $blue-grey-100;
}
ul,
p {
font-weight: normal;
}
ul {
list-style: none;
padding: 0;
margin: .6em 0 0;
}
}
What we’re doing:
.icon-marker
class and set up our .icon-marker-tooltip
class to show up when hovered over.icon-marker-tooltip
class is hidden by default, as it’s our tooltip, but we position it absolutely to appear over top of our marker and formatted the way we want itAnd finally, once we have our geoJsonLayers
created with our styling added, we can add it to the map!
geoJsonLayers.addTo(map)
Map with Coronavirus location data
Now you might be wondering why it doesn’t appear to be centering properly. Go ahead and change the LOCATION
variable at the top of the index.js
file to:
const LOCATION = {
lat: 0,
lng: 0
};
Once that’s set, when the page reloads, the map should be centered in the middle of the world!
Map with Coronavirus location data centered with a tooltip
If you followed along, you now have created your own Coronavirus map dashboard that gives some quick stats about the cases around the world.
Take what you learned and run with it. You can apply this to any other type of data that you can imagine.
In my original demo, I set up a custom basemap using Mapbox that allows me to have a dark background making the markers easier to see.
Creating a new basemap in Mapbox Studio
Mapbox is great and has a nice free tier if you’re interested in getting started.
Once you have a Mapbox account, you can even copy the style I used and make it your own.
To learn how to integrate it, you can try to check out the source code of my original demo:
https://github.com/colbyfayock/coronavirus-map-dashboard
Dashboards with maps like the Johns Hopkins University app allows us to see more than a look on the map, but a glimpse at quick stats about the cases around the world.
The NovelCOVID API has more endpoints like /all
that provide a few global stats.
I want to reiterate that you should make sure you’re staying up to date using official sources for information, such as the Johns Hopkins University dashboard. Though the data should be reliable, it should also be considered a proof of concept for building a map and referencing, but shouldn’t be considered for any kind of statistical analysis.
Please take care of yourself during these times. We’re all in this together! ❤️
#reactjs #javascript #web-development #gatsby