Monitoring emissions during the Italian COVID-19 epidemic lock-down

We are living interesting and frightening times as the new COVID-19 pandemic takes over our countries. In recent days, we have faced similar situations when the SARS (2003) and MERS (2012) epidemics have spread around the world and killed dozens, however, COVID-19 happens to be on a different league.

Since the first cases started to appear in China, at the end of 2019, the number of cases increased dramatically and the disease spread like wildfire, sparing almost no country in its way. In Europe, the SARS-Cov-19 virus has arrived much later, but the situation quickly escalated. Of all countries, Italy has suffered the most, with over 15.000 confirmed cases to date, 1200 deaths and a 33% rate of daily increase. As Italian authorities struggle to contain the outbreak, a number of unprecedented measures are being taken by the government. What started with a few small cities in the northern provinces of Lombardia and Veneto after 20.02.2020 when the first case was confirmed, was rapidly to the extended to the entire northern area and then, the entire country.

Milan, Italy, during the COVID-19 outbreak (source: SIPA USA/IPA Images, .ie)

The case of Italy is not only interesting because of the fight against the new coronavirus, but also because these measures have deep socio-economic implications. A total lock-down hurts deeply the economy and the industrial area of Northern Italy took a first big blow. Lombardy, has been for decades the engine of the Italian economy, with much of the country’s industrial production being located there and in the neighboring regions. According to , Lombardy alone accounts for 1/5 of the total GDP of the Italian peninsula and while numbers can be crunched in different ways, one thing can definitely certify the scale of this colossus: the environmental signature.

Located between the Alps, the Apennines and the Adriatic Sea, the provinces of Lombardia, Piemonte, Emilia-Romagna and Veneto occupy much of the famous Po Valley (Pianura Padana). A large part of the Italian population lives in one famous cities of Milan, Parma, Bergamo, Bologna, Modena, Padua or Verona and in thousands of other smaller cities (commune). In a normal day, a myriad of people commute from one of these places to the nearest large center on one of the country’s autostrade (highways). The environmental impact is undeniable, with Milan being repeatedly and the region distinguishing itself on any pollution map. In fact, an episode back in December 2016, has left residents outraged, as clouds and smog were visible on satellite imagery.

The Po Valley under the clouds and smog on Dec. 13, 2016 (Source: MODIS, NASA)

But what happens during an epidemic?

The European Space Agency published recently a video of the evolution of nitrogen dioxide over Italy. The sequence of images, displaying data sensed by Sentinel-5P, a common EU/ESA/Copernicus satellite mission, clearly reveals how gaseous emissions drop quickly during the enforced quarantine days. is part of a larger family of satellites designed to monitor our planet, the Sentinel constellation, developed within the European Union’s .

S5P is a precursor, meaning that it paves the way for the future and atmospheric monitoring missions. On-board, its flagship instrument, , measures different gas concentrations in the atmospheric column using light spectroscopy and delivers data about the amount of nitrogen dioxide (NO2), ozone (O3), sulphur dioxide (SO2), carbon monoxide (CO), methane (CH4) and formaldehyde (HCHO) present in our atmosphere. Data is then processed and released openly to the public for further analysis (as stated in the Copernicus policy).

NO2 pollution over Europe in June 2018 (data: Sentinel-5P Tropomi, EU/ESA/Copernicus, source:

Since its launch in 2018, S5P has measured our air each day, giving us precise metrics on how natural and human activity is reflected into our atmosphere. Therefore, a wealth of data has been published from July 2018 until today, and also we, researchers and public, can play with this data and, unlike in a video, interact with it.

There are many ways to play with the data and as it is distributed in a NetCDF format, is easy to manipulate it using a programming language of choice (further tutorials on how to do this will follow on my blog ) . However, for the sake of accessibility and data visualization, an online platform was my first choice this time.

is a neat free cloud processing environment, where researchers, scientists and developers can have access to petabytes of satellite data and products and perform processing and analysis without the fuss of lacking resources. Sentinel-5P data is being released through the catalogue, both as a near real time (NRT) product, as well as an offline (OFFL) dataset, meaning we can test applications with this data as quick as they are ready after the initial processing.

There are many great things that you can do in GEE (even if you’re not a JavaScript wizard), but I find the possibility of sharing your analysis as an extremely useful. In particular in this case, since the scope was to give access to as many people as possible to interact with the data.

Sentinel-5P data representation in Google Earth Engine

Let’s see how to do this together!

First, you will need a Google account. That is easy and quite straightforward to do if you follow the steps explained . Next, while logged in, you will need to access the and navigate to Platform > Code Editor. If you have never interacted with the GEE platform before, take some time to familiarize with the environment described .

Once in the editor, we will notice the panel for writing our New Script. Well, GEE functionalities are based on writing some JavaScript code, but with a minimum scripting knowledge we can still navigate and build neat apps. There are many great resources online, many people publishing their code and a simple search will most likely answer our questions. A great place to start is the , the and the , where you can preview data in the public catalogue before making any choice. GEE allows a multitude of actions, such as importing data from personal repositories or using a Python wrapper to access the environment through its API. However, I won’t detail as it goes beyond the scope of the article.

Presuming you have pushed a couple of buttons and tried some of the examples and documentation, let’s proceed. First, we should import some images from the Image Collection. We are choosing the NO2 Near Real Time Level 3 data for keeping a daily update. Since the objective is to compare, we will choose the date of 20.02.2020 (the date of Italian first COVID-19 case) as a separator for the two periods of reference and equal intervals prior to and during the outbreak for averaging the data.

// Importing Sentinel 5P image collections for the weeks prior to the COVID-19 first case
// Reference date: first case 20.02.2020

var image_prior = ee.ImageCollection(‘COPERNICUS/S5P/NRTI/L3_NO2’)

// Importing Sentinel 5P image collections for the weeks prior to the COVID-19 first case

var image_during = ee.ImageCollection(‘COPERNICUS/S5P/NRTI/L3_NO2’)

Running just this part of the code should load the image collections into the memory of the console. However, nothing will be displayed to our map. We will move forward to building the visualization. A custom was chosen, to better reflect the ‘inferno’ of the outbreak and pleasantly scale the data. The opacity of the layer was set to a 75 % for allowing map labels to be displayed and layering with the satellite basemap. Any unnecessary map controls were ditched, leaving only the zooming functionality and the map was centered over the area of Northern Italy. In order to pick the minimum and maximum of value for the dataset, a quick check-up was done using Sinergise’s and the recommendations from . A threshold was set at 0.003 mol/m² in this case. The data was averaged (.mean()) over the reference period.

// Importing color palette for visualization

var palettes = require(‘users/gena/packages:palettes’);
var palette = palettes.matplotlib.magma[7];

// Setting first map controls and removing some unnecessary control panels while keeping zoom and scale

Map.addLayer(image_prior.mean(), {min: 0, max: 0.0003, palette: palette, opacity:0.75});
Map.setCenter(10.084656, 45.060917, 7);
Map.setControlVisibility({all: false, zoomControl: true, mapTypeControl: true});

The first map of the period before the outbreak is now added to the console.

In order to create a comparison, we will need to link the next map. The options will remain the same as for the initial map.

// Creating the linked map and adding it to the split widget through a linker

var linkedMap = ui.Map();
linkedMap.addLayer(image_during.mean(), {min: 0, max: 0.0003, palette: palette, opacity:0.75});
linkedMap.setCenter(10.084656, 45.060917, 7);
linkedMap.setControlVisibility({all: false, zoomControl: true, mapTypeControl: true})
var linker = ui.Map.Linker([ui.root.widgets().get(0), linkedMap]);

Since we now have two maps, we will need to distinguish which one is which, hence, we will create title labels. For the colors, we will use the .

// Add title labels to the maps

var title_prior= Map.add(ui.Label(
‘Mean NO2 emissions prior to epidemics’, {fontWeight: ‘bold’, fontSize: ‘10px’, position: ‘bottom-left’, color: ‘slateGrey’}));

var title_during= linkedMap.add(ui.Label(
‘Mean NO2 during the epidemics’, {fontWeight: ‘bold’, fontSize: ‘10px’, position: ‘bottom-right’, color: ‘slateGrey’}));

GEE offers a range of for creating interesting visualizations. A split panel was selected, as it offers the possibility to interact with the app and observe change.

// Creating the split panel comprising the two maps

var splitPanel = ui.SplitPanel({
firstPanel: linker.get(0),
secondPanel: linker.get(1),
orientation: ‘horizontal’,
wipe: true,
style: {stretch: ‘both’}


At this point, our comparison should already be visible. However, there are a couple of elements that need to be added in order to also offer an explanation for the visualization.

A side panel with more info is a good idea. We will add some text for the title and a brief explanation about the application and the data.

// Create side panel and add a header and text

var header = ui.Label(‘insert title here’, {fontSize: ‘15px’, color: ‘darkSlateGrey’});

var text_1 = ui.Label(
‘insert text here’,
{fontSize: ‘11px’});

var text_2 = ui.Label(
‘insert data source here’,
{fontSize: ‘11px’});

var toolPanel = ui.Panel([header, text_1, text_2], ‘flow’, {width: ‘300px’});

It won’t display yet if we hit Run, but once we add more element, we will have it. If you wish to stop here, just give the command:


Else, follow on.

Not everyone is familiar with satellite data and a brief explanation about the Sentinel-5P dataset and nitrogen dioxide is welcomed. Let’s add that as a link to the official source.

//Create external reference with link

var link = ui.Label(
‘insert link explanation here’, {},
var linkPanel = ui.Panel(
[ui.Label(‘For more information’, {fontWeight: ‘bold’}), link]);

Also, as a general rule, displaying data without offering a legend for interpretation is a bad idea. The legend itself is contained in a separate panel embedded in the master tool panel, hence, there are a number of elements to consider. Carefully read each line for understanding how we build this. Constructing one is a lengthy thing, but here we are:

// Create legend for the data

var viz = {min:0.0, max:300.0, palette:palette};

var legend = ui.Panel({
style: {
position: ‘bottom-left’,
padding: ‘8px 15px’

// Create legend title

var legendTitle = ui.Label({
value: ‘NO2 Concentration (μmol/m²)’,
style: {
fontWeight: ‘bold’,
fontSize: ‘8 px’,
margin: ‘0 0 4px 0’,
padding: ‘0’

// Add the title to the panel


// Create and and style the legend

var makeRow = function(color, name) {

// Create the colored box.

var colorBox = ui.Label({
style: {
backgroundColor: ‘#’ + color,

// Use padding to give the box height and width.

padding: ‘8px’,
margin: ‘0 0 4px 0’

// Create the label filled with the description text.

var description = ui.Label({
value: name,
style: {margin: ‘0 0 4px 6px’}

// Return the panel

return ui.Panel({
widgets: [colorBox, description],
layout: ui.Panel.Layout.Flow(‘horizontal’)

var lon = ee.Image.pixelLonLat().select(‘latitude’);
var gradient = lon.multiply((viz.max-viz.min)/100.0).add(viz.min);
var legendImage = gradient.visualize(viz);

// Create text label on top of legend

var panel_max = ui.Panel({
widgets: [


// Create thumbnail from the image

var thumbnail = ui.Thumbnail({
image: legendImage,
params: {bbox:’0,0,10,100', dimensions:’10x100'},
style: {padding: ‘1px’, position: ‘bottom-center’}

// Add the thumbnail to the legend


// Create text label on bottom of legend

var panel_min = ui.Panel({
widgets: [



Eventually, we came to the end. Now let’s just add the panel to the overall setup.


The final product should look similar to this:

I have optionally added a credits section, but you can skip it if unnecessary. The credits are just a text block and by now you should know how to build that :)

We are now ready to publish our map as an application. By choosing the option Apps in the panel, we can open the Apps Manager and explore our app gallery. Since this is our first app, it can look a little lonely. However, we will proceed on creating a Google Cloud profile, necessary for publishing apps and solve this issue. Be careful, this is not a Google Drive, you will have to register on the . Once registered, a $300 sum will be available to you for free on a period of 12 months. Publishing GEE apps is free, but publishing apps that use resources stored in your cloud storage is not and will eat through that money. Google offers this option as a trial and according to their rules, they won’t charge you after the trial expires, unless you are using resources that cost.

Assuming you have obtained your profile, you must create a new project. This is easy. In your dashboard, identify the My First Project area and open the project manager. Once there, select the option of creating your first project, name it and create it. You have 22 projects assigned to your quota. When finished, the project should appear in your dashboard.

Now, we can comeback to the GEE editor and publish our app. Using the Apps button in the code editor, open the Apps Manager and create a new app. Complete the information requested and assign the cloud project name to the previously created project.

Our app is ready and the link should appear in the Apps Manager.

is the link to the app I have created.

So, what do we learn from this app?

The map presents a comparison of the mean NO2 emissions in the troposphere (0–10 km above ground) in the period preceeding the COVID-19 outbreak and during the epidemics. The right side of the panel takes into account the period from the first case confirmation (20.02.2020) to present, while the left side of the panel accounts for an equal period before 20.02.2020.

We can clearly see that the three weeks period before the outbreak the pollution levels were high around the entire Po Valley, with a clear focus on Milan’s area, the string of cities and the connecting highways. NO2 is a nitrogen oxide (NO), part of the ‘noxes’ family (NOx = NO + NO2) and is emitted mainly by large industrial operators and transportation, being one of the gases released through combustion. Naturally, it is also released through microbiological activity in soil.

With natural barriers like the two mountain ranges, pollution accumulates in the lower area of the valley and since the air currents cannot transport the gases further, resulting in thick layers of smog. With reduced mobility and human activity due to forced quarantine, the emissions drop dramatically and just a handful of sources remain active around the area of Milan, where most of the industrial facilities are located. Cities are clearly visible as hotspots, though pollution is drastically reduced. However, home confinement is obviously reflected on the low highlight of the transport ways, with roads and highways emitting less NO2, as well as a reduction on the ship routes and port areas. The effects are not only visible in the northern part of the country, but the entire Italy looks dormant as the measures are affecting its whole territory.

It is certainly desolating and a sad looking picture and while I hope the situation will resume to normal soon and with less casualties, observing how much our activity pollutes the air we breathe and thinking on the long term consequences on our environment, present and future, and our health, we should not forget this sight and quickly turn back to solve our first and foremost crisis: climate change.


Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store