Custom geometry: district-level NTL with custom boundaries

lightson accepts any sf polygon layer. The workflow below starts with the bundled LGD state boundaries, then swaps in district-level GeoJSONs from bharatviz.

State-level workflow (bundled boundaries)

library(sf)
#> Linking to GEOS 3.13.0, GDAL 3.8.5, PROJ 9.5.1; sf_use_s2() is TRUE

states <- get_india_admin("state")
nrow(states)
#> [1] 36
head(states[, c("state_name", "state_code")])
#> Simple feature collection with 6 features and 2 fields
#> Geometry type: GEOMETRY
#> Dimension:     XY
#> Bounding box:  xmin: 76.7052 ymin: 6.7528 xmax: 97.4129 ymax: 30.7941
#> Geodetic CRS:  WGS 84
#>          state_name state_code                       geometry
#> 1     A & N Islands         35 GEOMETRYCOLLECTION (POLYGON...
#> 2    Andhra Pradesh         37 MULTIPOLYGON (((80.7879 15....
#> 3 Arunachal Pradesh         12 POLYGON ((96.1765 29.3452, ...
#> 4             Assam         18 MULTIPOLYGON (((91.5352 25....
#> 5             Bihar         10 POLYGON ((84.1093 27.5208, ...
#> 6        Chandigarh         04 POLYGON ((76.7723 30.7941, ...

Download Bhuvan WMS rasters for a subset of years. bhuvan_raster() derives the bounding box from the geometry:

names(rasters)
#> [1] "2020" "2021" "2022"
rasters[["2022"]]
#> class       : SpatRaster
#> size        : 1024, 1024, 1  (nrow, ncol, nlyr)
#> resolution  : 0.0285501, 0.02962432  (x, y)
#> extent      : 68.1776, 97.4129, 6.7528, 37.0881  (xmin, xmax, ymin, ymax)
#> coord. ref. : lon/lat WGS 84 (EPSG:4326)
#> source      : bhuvan_v2_349e9f004c78_2022.tif
#> name        : lyr1
#> min value   :  2.5
#> max value   :  200

Bhuvan WMS returns a classed map with four colours (< 5, 5-25, 26-200 and > 200 nW/cm^2/sr). bhuvan_raster() converts each class to a representative radiance (2.5, 15, 112.5 and 200), so values are banded rather than continuous, and the brightest class is a floor.

panel <- extract_panel(rasters, states, id_col = "state_name")
#> Warning in st_cast.GEOMETRYCOLLECTION(X[[i]], ...): only first part of
#> geometrycollection is retained
head(panel)
#>         region_id year mean_radiance n_pixels
#> 1   A & N Islands 2020      2.698413       63
#> 37  A & N Islands 2021      2.500000       63
#> 73  A & N Islands 2022      2.698413       63
#> 2  Andhra Pradesh 2020     10.274082    16236
#> 38 Andhra Pradesh 2021     10.506898    16236
#> 74 Andhra Pradesh 2022     14.095067    16236
library(ggplot2)
sel <- c("Bihar", "Maharashtra", "Tamil Nadu", "Uttar Pradesh")
plot_ntl_trend(
  panel[panel$region_id %in% sel, ],
  region   = sel,
  caption  = "Source: ISRO Bhuvan NTL portal"
)

plot_ntl_map(
  rasters[["2022"]],
  polygons = states,
  title    = "India nighttime lights, 2022"
)

District-level workflow

BHARATVIZ <- "https://bharatviz.org"

districts <- sf::read_sf(
  file.path(BHARATVIZ, "India-bhuvan-districts.geojson")
)
nrow(districts)
rasters_dist <- bhuvan_raster(districts, years = 2018:2023)
names(rasters_dist)
panel_dist <- extract_panel(rasters_dist, districts, id_col = "district_name")
head(panel_dist)
plot_ntl_trend(panel_dist, region = c("Mumbai", "Delhi", "Chennai", "Bengaluru"))
plot_ntl_map(rasters_dist[["2022"]], polygons = districts)

LGD boundaries

lgd <- sf::read_sf(file.path(BHARATVIZ, "India_LGD_districts.geojson"))
panel_lgd <- extract_panel(rasters_dist, lgd, id_col = "district_name")
head(panel_lgd)

Historical census boundaries

dist_2011 <- sf::read_sf(file.path(BHARATVIZ, "India-2011-districts.geojson"))
panel_2011 <- extract_panel(rasters_dist, dist_2011)
head(panel_2011)

extract_panel() picks the first character column as region_id automatically, or pass id_col explicitly.


VIIRS path

To use NASA VIIRS rasters, supply an Earthdata token:

token <- earthdata_token()
rasters_viirs <- ntl_download(region = districts, years = 2018:2023, token = token)
if (length(rasters_viirs) > 0) {
  panel_viirs <- extract_panel(rasters_viirs, districts, id_col = "district_name")
  head(panel_viirs)
}