| Title: | Download and Analyse Nighttime Lights Satellite Data |
|---|---|
| Description: | Downloads and processes nighttime lights (NTL) satellite data from NASA VIIRS/Black Marble (daily, monthly, and annual composites) and India's ISRO Bhuvan NTL portal. |
| Authors: | Saket Choudhary [aut, cre] (ORCID: <https://orcid.org/0000-0001-5202-7633>) |
| Maintainer: | Saket Choudhary <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.1 |
| Built: | 2026-09-21 11:12:05 UTC |
| Source: | https://github.com/saketlab/lightson |
Fetches annual nighttime lights GeoTIFF tiles from the Bhuvan WMS service for a given spatial extent and converts the RGB visualization output to a single-band luminance raster.
bhuvan_raster( region, years, force = FALSE, width = 1024, height = 1024, bbox_pad = c(0, 0, 0, 0) )bhuvan_raster( region, years, force = FALSE, width = 1024, height = 1024, bbox_pad = c(0, 0, 0, 0) )
region |
An |
years |
Integer vector of years to download (2012-2024). |
force |
Logical. If |
width, height
|
Pixel dimensions of the WMS request. Default 1024x1024. |
bbox_pad |
Numeric vector of length 1 or 4 (xmin, ymin, xmax, ymax
sides) in degrees, added to the region bounding box before the WMS
request. Useful when GADM boundaries clip disputed territories (e.g.
|
The Bhuvan WMS returns RGB visualization rasters. Luminance is computed
as lum = 0.2126*R + 0.7152*G + 0.0722*B
(ITU-R BT.709). Values are comparable across years for trend analysis but
differ in units from VIIRS nW/cm²/sr. Do not mix Bhuvan luminance and VIIRS
radiance in the same regression without normalising first.
A named list of single-band SpatRaster objects, keyed by year
(e.g., list("2020" = rast(...), "2021" = rast(...))). Errors if the WMS
fetch fails for any requested year.
Bhuvan NTL data from 2024 onwards is derived from VNP46A4 Collection 2.0, while 2012-2023 uses the legacy Collection 1.0. The two collections use different calibration and atmospheric correction algorithms, so luminance values are not directly comparable across this boundary.
## Not run: districts <- sf::read_sf("https://bharatviz.org/India-bhuvan-districts.geojson") rasters <- bhuvan_raster(districts, years = 2018:2023) panel <- extract_panel(rasters, districts) ## End(Not run)## Not run: districts <- sf::read_sf("https://bharatviz.org/India-bhuvan-districts.geojson") rasters <- bhuvan_raster(districts, years = 2018:2023) panel <- extract_panel(rasters, districts) ## End(Not run)
Wraps the ISRO Bhuvan NTL REST API to return mean radiance per Indian state across years.
bhuvan_stats(years = NULL, state = NULL)bhuvan_stats(years = NULL, state = NULL)
years |
Integer vector of years to return. |
state |
Character. State name as returned by the API (e.g., |
For district-level or custom-geometry statistics, use bhuvan_raster()
together with extract_panel().
A tidy data frame with columns state (character), year
(integer), and mean_radiance (numeric). State names are normalised to
title case (e.g., "Tamil Nadu" not "TAMILNADU").
Bhuvan NTL data from 2024 onwards is derived from VNP46A4 Collection 2.0, while 2012-2023 uses the legacy Collection 1.0. The two collections use different calibration and atmospheric correction algorithms, so radiance estimates are not directly comparable across this boundary. Requests spanning both collections trigger a warning.
## Not run: # All states, all years df <- bhuvan_stats() # Bihar only df <- bhuvan_stats(state = "BIHAR") # Multiple years df <- bhuvan_stats(years = 2015:2023) ## End(Not run)## Not run: # All states, all years df <- bhuvan_stats() # Bihar only df <- bhuvan_stats(state = "BIHAR") # Multiple years df <- bhuvan_stats(years = 2015:2023) ## End(Not run)
Removes cached raster files downloaded by ntl_download() or bhuvan_raster().
cache_clear(source = NULL)cache_clear(source = NULL)
source |
One of |
Invisibly returns the number of files removed.
Returns the bearer token used for VIIRS/Black Marble downloads from NASA LAADS DAAC.
earthdata_token(token = NULL)earthdata_token(token = NULL)
token |
Character string. If |
Getting a token (web):
Create an account at https://urs.earthdata.nasa.gov
Accept the MERIS and Sentinel EULAs under Profile > EULAs
Go to Profile > Generate Token, click "Generate Token", then "Show Token"
Copy the token (starts with EDL-) and store it
Supplying the token:
Set EARTHDATA_TOKEN environment variable (recommended: add to ~/.Renviron)
Or pass it directly as the token argument
Or set EARTHDATA_USERNAME + EARTHDATA_PASSWORD to fetch it automatically
Bhuvan NTL functions (bhuvan_stats(), bhuvan_raster()) do not require
any authentication.
The access token string, invisibly.
## Not run: # Option 1: token from Profile > Generate Token on urs.earthdata.nasa.gov Sys.setenv(EARTHDATA_TOKEN = "EDL-xxxxxxxxxxxxxxxxxxxx") token <- earthdata_token() # Option 2: username + password (fetches token automatically) Sys.setenv(EARTHDATA_USERNAME = "myuser", EARTHDATA_PASSWORD = "mypass") token <- earthdata_token() # Option 3: pass directly token <- earthdata_token("EDL-xxxxxxxxxxxxxxxxxxxx") ## End(Not run)## Not run: # Option 1: token from Profile > Generate Token on urs.earthdata.nasa.gov Sys.setenv(EARTHDATA_TOKEN = "EDL-xxxxxxxxxxxxxxxxxxxx") token <- earthdata_token() # Option 2: username + password (fetches token automatically) Sys.setenv(EARTHDATA_USERNAME = "myuser", EARTHDATA_PASSWORD = "mypass") token <- earthdata_token() # Option 3: pass directly token <- earthdata_token("EDL-xxxxxxxxxxxxxxxxxxxx") ## End(Not run)
Takes a named list of SpatRaster objects (one per time period, as returned by
ntl_download() or bhuvan_raster()) and an sf or SpatVector polygon
layer, and returns a tidy long-format data frame with one row per region and
time period. Annual names such as "2025" produce the original four-column
output. ISO-date names used by monthly and daily Black Marble additionally
produce a date column.
extract_panel(rasters, polygons, fun = "mean", id_col = NULL)extract_panel(rasters, polygons, fun = "mean", id_col = NULL)
rasters |
A named list of |
polygons |
An |
fun |
Aggregation function name passed to |
id_col |
Name of the column in |
The polygons argument accepts any sf object: GADM, bharatviz GeoJSONs,
LGD districts, or any shapefile loaded with sf::read_sf().
A data frame with columns:
Character. Region identifier from polygons.
Integer. Year, taken from the name of each raster.
Numeric. Aggregated radiance value.
Integer. Number of non-NA cells contributing to the stat.
## Not run: districts <- sf::read_sf("https://bharatviz.org/India-bhuvan-districts.geojson") rasters <- bhuvan_raster(districts, years = 2018:2023) panel <- extract_panel(rasters, districts) head(panel) ## End(Not run)## Not run: districts <- sf::read_sf("https://bharatviz.org/India-bhuvan-districts.geojson") rasters <- bhuvan_raster(districts, years = 2018:2023) panel <- extract_panel(rasters, districts) head(panel) ## End(Not run)
Returns Indian administrative boundaries as an sf object. Useful as the
polygons argument to extract_panel().
get_india_admin( level = c("country", "state", "district"), source = c("gadm", "bhuvan") )get_india_admin( level = c("country", "state", "district"), source = c("gadm", "bhuvan") )
level |
One of |
source |
One of |
State boundaries use a bundled LGD (Local Government Directory) dataset derived from the Survey of India.
For custom boundaries, load your own file with sf::read_sf() and pass it
directly to extract_panel().
An sf object.
## Not run: states <- get_india_admin("state") districts <- get_india_admin("district") # custom boundary: districts <- sf::read_sf("https://bharatviz.org/India-bhuvan-districts.geojson") ## End(Not run)## Not run: states <- get_india_admin("state") districts <- get_india_admin("district") # custom boundary: districts <- sf::read_sf("https://bharatviz.org/India-bhuvan-districts.geojson") ## End(Not run)
Downloads calibrated VIIRS Black Marble annual (VNP46A4), monthly
(VNP46A3), or daily (VNP46A2) rasters from NASA LAADS DAAC.
ntl_download( region, years = NULL, token, force = FALSE, product = c("annual", "monthly", "daily"), dates = NULL, quality = c("high", "all") )ntl_download( region, years = NULL, token, force = FALSE, product = c("annual", "monthly", "daily"), dates = NULL, quality = c("high", "all") )
region |
An |
years |
Integer years. Required for annual data; selects all twelve
months for monthly data when |
token |
NASA Earthdata bearer token from |
force |
Re-download cached output. |
product |
One of |
dates |
Date-like vector. Required for daily data. Monthly dates are normalised to the first day of their month. |
quality |
For daily data, |
Named list of cropped SpatRaster objects. Names are years for
annual data and ISO dates otherwise.
## Not run: token <- earthdata_token() annual <- ntl_download("IND", 2020:2025, token) monthly <- ntl_download("IND", 2025, token, product = "monthly") daily <- ntl_download("IND", token = token, product = "daily", dates = as.Date("2025-11-12") ) ## End(Not run)## Not run: token <- earthdata_token() annual <- ntl_download("IND", 2020:2025, token) monthly <- ntl_download("IND", 2025, token, product = "monthly") daily <- ntl_download("IND", token = token, product = "daily", dates = as.Date("2025-11-12") ) ## End(Not run)
Converts each group's values to an index where its baseline observation is 100. This is useful for comparing changes between sources with different units, but does not make their absolute levels comparable.
ntl_index( panel, baseline, group_cols = NULL, time_col = "year", value_col = "mean_radiance", index_col = "ntl_index" )ntl_index( panel, baseline, group_cols = NULL, time_col = "year", value_col = "mean_radiance", index_col = "ntl_index" )
panel |
Data frame containing nighttime-light observations. |
baseline |
Baseline value in |
group_cols |
Grouping columns. If omitted, uses |
time_col |
Time column name. Defaults to |
value_col |
Value column name. Defaults to |
index_col |
Name for the new index column. |
panel with an additional numeric index column.
Aligns observations by region and time, then calculates within-region correlation between two sources. Correlation compares co-movement, not absolute levels or units.
ntl_source_agreement( x, y, id_col = "region_id", time_col = "year", value_col = "mean_radiance", method = c("pearson", "spearman", "kendall") )ntl_source_agreement( x, y, id_col = "region_id", time_col = "year", value_col = "mean_radiance", method = c("pearson", "spearman", "kendall") )
x, y
|
Panel data frames, such as outputs from |
id_col |
Region identifier column. |
time_col |
Shared time column. |
value_col |
Measurement column in both panels. |
method |
Correlation method passed to |
Data frame with region, correlation, and paired observation count.
Renders a single SpatRaster as a map. Two palettes are available:
plot_ntl_map( raster, polygons = NULL, title = "Nighttime lights", caption = NULL, palette = c("glow", "bhuvan"), dark = TRUE, limits = NULL )plot_ntl_map( raster, polygons = NULL, title = "Nighttime lights", caption = NULL, palette = c("glow", "bhuvan"), dark = TRUE, limits = NULL )
raster |
A single-layer |
polygons |
Optional |
title |
Plot title. Default |
caption |
Optional caption string. |
palette |
One of |
dark |
Logical. If |
limits |
Numeric vector of length 2 for the continuous scale. Only used
when |
"glow" (default): continuous dark-olive-to-cream-white ramp modelled on
the NASA Black Marble composite. Suited to dark backgrounds and multi-panel
year comparisons.
"bhuvan": 4-class categorical legend matching the Bhuvan portal: black
(< 5 nW/cm2/sr), dark blue-grey (5-25), cream (26-200), red (> 200).
A ggplot2 object.
## Not run: rasters <- bhuvan_raster("IND", years = 2022, bbox_pad = c(0, 0, 0, 3.5)) plot_ntl_map(rasters[["2022"]], dark = TRUE) plot_ntl_map(rasters[["2022"]], palette = "bhuvan") ## End(Not run)## Not run: rasters <- bhuvan_raster("IND", years = 2022, bbox_pad = c(0, 0, 0, 3.5)) plot_ntl_map(rasters[["2022"]], dark = TRUE) plot_ntl_map(rasters[["2022"]], palette = "bhuvan") ## End(Not run)
Arranges one map per year into a patchwork grid with a shared title, subtitle, and caption. A shared luminance scale is computed from the 99.5th percentile across all rasters so panels are directly comparable.
plot_ntl_panel( rasters, polygons = NULL, title = "India at night", subtitle = NULL, caption = NULL, palette = c("glow", "bhuvan"), ncol = 3L, bg_colour = "#080c14", title_colour = "#CDD9E5", subtitle_colour = "#8B949E", year_colour = "#E8A87C" )plot_ntl_panel( rasters, polygons = NULL, title = "India at night", subtitle = NULL, caption = NULL, palette = c("glow", "bhuvan"), ncol = 3L, bg_colour = "#080c14", title_colour = "#CDD9E5", subtitle_colour = "#8B949E", year_colour = "#E8A87C" )
rasters |
Named list of |
polygons |
Optional |
title |
Overall plot title. Default |
subtitle |
Optional subtitle string. |
caption |
Optional caption string. |
palette |
One of |
ncol |
Number of columns in the grid. Default |
bg_colour |
Background colour of the assembled panel.
Default |
title_colour |
Title text colour. Default |
subtitle_colour |
Subtitle and caption text colour. Default |
year_colour |
Year label colour on each individual map.
Default |
Requires the patchwork package.
A patchwork object.
## Not run: rasters <- bhuvan_raster("IND", years = c(2012, 2015, 2018, 2021, 2024)) states <- get_india_admin("state") plot_ntl_panel(rasters, polygons = states, title = "India at night, 2012-2024", caption = "Source: ISRO Bhuvan NTL portal" ) ## End(Not run)## Not run: rasters <- bhuvan_raster("IND", years = c(2012, 2015, 2018, 2021, 2024)) states <- get_india_admin("state") plot_ntl_panel(rasters, polygons = states, title = "India at night, 2012-2024", caption = "Source: ISRO Bhuvan NTL portal" ) ## End(Not run)
Line chart of mean radiance over time for one or more regions. Accepts
output from extract_panel() or bhuvan_stats().
plot_ntl_trend( panel, region = NULL, title = "Nighttime lights radiance", subtitle = NULL, caption = NULL )plot_ntl_trend( panel, region = NULL, title = "Nighttime lights radiance", subtitle = NULL, caption = NULL )
panel |
A data frame as returned by |
region |
Character vector of region identifiers to plot. |
title |
Plot title. Default |
subtitle |
Optional subtitle string. |
caption |
Optional caption string. |
A ggplot2 object.
## Not run: panel <- extract_panel(rasters, districts) plot_ntl_trend(panel, region = "Lucknow") plot_ntl_trend(panel, region = c("Mumbai", "Delhi", "Chennai")) ## End(Not run)## Not run: panel <- extract_panel(rasters, districts) plot_ntl_trend(panel, region = "Lucknow") plot_ntl_trend(panel, region = c("Mumbai", "Delhi", "Chennai")) ## End(Not run)
A transparent-background theme_minimal() base tuned for NTL data: tight
grid, bold title, and optional overrides via .... Ink colour is a
single equal-contrast grey (#767676, ~4.5:1 against both pure black and
pure white) so the same rendered figure reads on light and dark pages
alike, since pkgdown bakes each figure to one static PNG at build time.
theme_ntl(base_size = 12, ...)theme_ntl(base_size = 12, ...)
base_size |
Base font size. Default |
... |
Additional |
A ggplot2 theme object.