US nighttime lights data, 1994 (DMSP, composite from 29 orbits)
US nighttime lights data, 1995 (DMSP, composite from 236 orbits)
Nearly all production and consumption after sundown requires (and emits) light
Remote sensing of nighttime lights allows us to observe human activity from space
Emitting light
Economic activity, income, growth
Luminosity and GDP/capita
Urbanization
South Asia, 1994
South Asia, 2010
Emergency management, recovery
Before landfall
After landfall
Armed conflict
October 2021
October 2022
Technological change
Alles ist erleuchtet
Space-based sensors for night-lights (partial list)
| Sensor | Spatial resolution | Temporal resolution | Availability | Free? | On-board calibration |
|---|---|---|---|---|---|
| DMSP/OLS | 3km | Monthly | 1992-2013 | \(\checkmark\) | |
| Landsat 8 | 30m | Irregular | 2013- | \(\checkmark\) | |
| VIIRS/DNB | 740m | Daily | 2012- | \(\checkmark\) | \(\checkmark\) |
| EROS-B | <1m | Daily | 2013- | \(\checkmark\) | |
| Jilin-1 | <1m | Daily | 2017- | \(\checkmark\) |
Defense Meteorological Satellite Program (DMSP)
DMSP-5D2
Visible Infrared Imaging Radiometer Suite (VIIRS)
Suomi NPP
Not all orbits make good data
Requirements for nighttime light observation:
Swath path
Illustration of cloud cover contamination
Daily VIIRS data for Ukraine, 2021-2022
Overview of lab exercise
Vignette 1 / Korea
Vignette 2 / Hispaniola
Vignette 3 / Syria
We can obtain VIIRS nighttime luminosity (vnl) data from eogdata.mines.edu/products/vnl/
Scroll down to the “Annual VNL V2” section
Click on the “Go to Download V2.2” button for the most recent year’s data
Download file ending with .average_masked.dat.tif.gz for most recent year
Let’s also grab data for the “oldest” year available.
Navigate to the parent directory and find the annual data in the v21/ folder
There are two files ending in .average_masked.dat.tif.gz here.
Download the first one (April through December)
Let’s get some country and administrative boundaries from geoboundaries.org
Navigate to the “Individual Country Files” section
Download country-level (ADM0) data for South and North Korea (KOR, PRK)
When you unzip, the only file you need to extract is one ending in ADM0.geojson
Repeat this process for country-level (ADM0) data for Haiti (HTI)…
and country-level (ADM0) data for Dominican Republic (DOM)…
and district-level (ADM2) data for Syria (SYR)
We will be using the same event data on violence as in the last lab:
UCDP GED version 25.1, in csv format
Here is the full list of data sources and links:
| Category | Type | Format | Data source |
|---|---|---|---|
| Nighttime luminosity | Raster | .tif |
VIIRS |
| Administrative units | Vector (polygons) | .geojson |
geoBoundaries |
| Political violence | Table (non-geo) | .csv |
UCDP GED |
These are all in the Lab10WT02.zip file posted on Canvas.
Always save your progress!
Go to Project \(\to\) Save As...
Vignette 1. Load the 2024 VNL data (Layer \(\to\) Add Layer \(\to\) Add Raster Layer). VNL_npp_2024_....tif file in Data/VIIRS folder
The default color scheme is too dark. Let’s see if we can add some contrast
In the layer’s Properties, change Render type to Singleband pseudocolor and set Mode to Quantile. Click Classify and OK
This is probably too much contrast, but at least we can see the distribution
Load country boundaries for the two Koreas (Layer \(\to\) Add Layer \(\to\) Add Vector Layer). 2 files: geoBoundaries-PRK-ADM0.geojson and geoBoundaries-KOR-ADM0.geojson from Data/geoBoundaries folder.
Let’s merge the two Koreas into a single layer (Vector menu \(\to\) Data Management Tools \(\to\) Merge Vector Layers...)
Input layers \(=\) geoBoundaries-KOR-ADM0 and geoBoundaries-PRK-ADM0
Save the merged file as koreas.geojson
Change the symbology of the new layer, to make all but the borders transparent
Let’s extract the part of the global VNL raster that overlaps with the Koreas.
Go to Raster menu \(\to\) Extraction \(\to\) Clip Raster by Mask Layer...
Set parameters
Input layer \(=\) VNL_npp_2024...Mask layer \(=\) koreasMatch the extent of the clipped raster to the extent of the mask layerkoreas_nl2024_mask.tif
The clipped raster should look something like this
You can zoom in to see Seoul in greater detail
Change color scheme to Singleband pseudocolor, Quantile again for contrast
General tip: set Interpolation \(=\) Discrete, Mode \(=\) Equal interval and manually edit the cutpoints like this
This way, you can customize the appearance of the map for your needs
This map is ready to be exported (you know how to do this)
Now for Vignette 2, let’s repeat this process for the island of Hispaniola.
Load country boundaries for Haiti and the Dominican Republic (2 files: geoBoundaries-HTI-ADM0.geojson and geoBoundaries-DOM-ADM0.geojson from Data/geoBoundaries).
Once loaded, let’s merge the two countries into a single layer again (Vector menu \(\to\) Data Management Tools \(\to\) Merge Vector Layers...)
Input layers \(=\) geoBoundaries-DOM-ADM0 and geoBoundaries-HTI-ADM0
Save the merged file as hispaniola.geojson
Let’s extract the part of the global VNL raster that overlaps with the island.
Go to Raster menu \(\to\) Extraction \(\to\) Clip Raster by Mask Layer...
Set Input layer \(=\) VNL_npp_2024..., Mask layer \(=\) hispaniola. Save file as hispaniola_nl2024_mask.tif
The clipped raster should look something like this
Customize the color ramp and export the map (just like last time)
Vignette 3! Load administrative boundaries for Syria. File is geoBoundaries-SYR-ADM2.geojson from Data/geoBoundaries folder.
You may see a “Select Items to Add” screen after clicking Add. Click Add Layers
To compare current luminosity to a period earlier in the Syrian Civil War, load the 2012 VNL data (VNL_v21_npp_201204....tif file in Data/VIIRS folder)
Let’s extract the parts of both the 2024 and 2012 global VNL rasters that overlap with Syria. Raster menu \(\to\) Extraction \(\to\) Clip Raster by Mask Layer...
Set Input layer \(=\) VNL_npp_2024..., Mask layer \(=\) geoBoundaries-SYR-ADM2. Save file as syria_nl2024_mask.tif
Repeat with Input layer \(=\) VNL_v21_npp_2012..., Mask layer \(=\) geoBoundaries-SYR-ADM2. Save file as syria_nl2012_mask.tif
Let’s now calculate the difference between 2024 and 2012. Go to Raster menu \(\to\) Raster Calculator...
Set the expression to "syria_nl2024_mask@1" - "syria_nl2012_mask@1", save output layer as syria_nldiff.tif
Explore the distribution of this new raster by modifying the color scheme
(here, purple areas lost luminosity, orange areas gained luminosity)
We can now calculate average differences in luminosity per district, using Zonal statistics (in the Processing Toolbox)
Set the Input layer \(=\) geoBoundaries-SYR-ADM2, Raster layer \(=\) syria_nldiff, Output column prefix \(=\) nldiff_. Save the output as syria_nl_1.geojson
Adjust the symbology in the new syria_nl_1 layer to visualize the nldiff_mean variable with graduated colors, Equal Interval mode and Symmetric Classification around 0.00
The district-level luminosity differences should look something like this.
Now let’s see if places hardest-hit by violence saw the biggest declines…
Add the Syrian Civil War violence data to the project, using Add Delimited Text Layer.... Load the GEDEvent_v25_1.csv file in Data/GED folder. Set X field \(=\) longitude and Y field \(=\) latitude. Check box \(\checkmark\) Use spatial index
Highlight GED layer and go to Edit \(\to\) Select \(\to\) Select by Expression...
Expression: year>=2012 AND country='Syria' AND where_prec<4
Click Select Features
This procedure should have selected about 74.5 thousand events within Syria.
Now let’s calculate the number of violent events per district
Open the Count Points in Polygon tool
Select Polygons \(=\) syria_nl_1, Points \(=\) GEDEvent_v25_1. Make sure the box is checked next to Selected Features Only for the points. Name the count field all_violence, and save the output file as syria_nl_2.geojson. Click Run
The new layer syria_nl_2 should appear in your project window.
Now, let’s see if there is a relationship between violence and change in luminosity
Let’s run a simple regression model in R to see how violence impacted luminosity.
This code chunk imports the syria_nl_2.geojson file we created into an object called syr, and then lists the variable names:
syr = sf::read_sf("Output/syria_nl_2.geojson")
names(syr)
## [1] "shapeName" "shapeISO" "shapeID" "shapeGroup" "shapeType"
## [6] "nldiff_mean" "all_violence" "geometry"
This code chunk estimates an Ordinary Least Squares model that regresses dependent variable nldiff_mean on explanatory variable all_violence
mod = lm(nldiff_mean~all_violence,data=syr)
summary(mod)
##
## Call:
## lm(formula = nldiff_mean ~ all_violence, data = syr)
##
## Residuals:
## Min 1Q Median 3Q Max
## -18.0902 -0.1057 0.3550 0.8098 2.4571
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.3274582 0.4213761 -0.777 0.440
## all_violence -0.0004381 0.0002107 -2.079 0.042 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 2.61 on 59 degrees of freedom
## Multiple R-squared: 0.06824, Adjusted R-squared: 0.05245
## F-statistic: 4.321 on 1 and 59 DF, p-value: 0.04199
Let’s rescale the variable all_violence to make the coefficient more interpretable.
mod = lm(nldiff_mean~I(all_violence/100),data=syr)
summary(mod)
##
## Call:
## lm(formula = nldiff_mean ~ I(all_violence/100), data = syr)
##
## Residuals:
## Min 1Q Median 3Q Max
## -18.0902 -0.1057 0.3550 0.8098 2.4571
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.32746 0.42138 -0.777 0.440
## I(all_violence/100) -0.04381 0.02107 -2.079 0.042 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 2.61 on 59 degrees of freedom
## Multiple R-squared: 0.06824, Adjusted R-squared: 0.05245
## F-statistic: 4.321 on 1 and 59 DF, p-value: 0.04199
For every 100 violent events, the change in luminosity falls by 0.044.
You can perform all these steps in R
(see replication code wt02_demo.R in Lab10WT02.zip)
Vignette 1
Vignette 2
Vignette 3
… or in Python.
(see replication code wt02_demo.py in Lab10WT02.zip)
Vignette 1
Vignette 2
Vignette 3