This vignette’s output is
pre-rendered from live ISRO Bhuvan and NASA Earthdata data (see
vignettes/source-comparison.Rmd.orig), since NASA’s LAADS
DAAC can be too slow for a CI build. To refresh it, re-knit the
.orig file locally with EARTHDATA_TOKEN set
and copy the result over source-comparison.Rmd.
lightson exposes related but non-interchangeable
products. Choose a source based on the period, update frequency,
measurement units, and intended analysis. Do not append their raw values
into a single time series.
| Source/product | Coverage | Cadence | Approx. resolution | Values | Authentication |
|---|---|---|---|---|---|
| ISRO Bhuvan NTL | 2012–2024 | Annual | 500 m | RGB-derived BT.709 luminance | None |
| NASA VNP46A4 | 2012–2025 | Annual | 500 m | Radiance, nW/cm^2/sr | Earthdata token |
| NASA VNP46A3 | 2012–present | Monthly | 500 m | Radiance, nW/cm^2/sr | Earthdata token |
| NASA VNP46A2 | 2012–near-present | Daily | 500 m | Radiance, nW/cm^2/sr | Earthdata token |
Availability can lag acquisition. In particular, completed annual composites are normally published well into the following year. Check the provider when reproducibility depends on an exact latest date.
Bhuvan is convenient when an unauthenticated India-only workflow
matters. Its state statistics endpoint returns physical radiance
statistics, but its WMS serves rendered RGB imagery.
bhuvan_raster() therefore converts WMS colours to
dimensionless luminance; it cannot recover the original radiance.
The examples below use a handful of states over 2021-2022 and the bundled LGD state boundaries, keeping every fetch small.
library(lightson)
states <- get_india_admin("state")
sel <- c("Bihar", "West Bengal")
states_sel <- states[states$state_name %in% sel, ]
state_stats <- bhuvan_stats(years = 2021:2022)
bhuvan_maps <- bhuvan_raster(states_sel, years = 2021:2022)
#> Using cached: bhuvan_ec05c5bc4c11_2021.tif
#> Using cached: bhuvan_ec05c5bc4c11_2022.tifBhuvan uses VNP46A4 Collection 1 for 2012-2023 and Collection 2 from 2024. The processing change creates a discontinuity, so a 2023-2024 difference must not be interpreted directly as growth. For a consistent calibrated 2012-2025 annual panel, prefer NASA Collection 2 for every year.
All three NASA products use the same Earthdata bearer token; the
example below uses the annual product. Requests are scoped to
states_sel so only the (single) VIIRS tile covering those
two states downloads:
token <- earthdata_token()
annual <- ntl_download(
states_sel,
years = 2021:2022, token = token,
product = "annual"
)Daily downloads (product = "daily") default to
quality = "high", retaining mandatory QA flag zero. This
rejects cloud contamination and other poor retrievals but can leave
missing pixels. Use quality = "all" only when you intend to
inspect and apply the quality bands yourself. Daily data are
near-current rather than a true real-time feed.
Use annual data for long-run development panels, monthly data for seasonal or short-run economic change, and daily data for narrowly dated events such as power outages. Aggregating QA-filtered daily observations into a monthly statistic is not automatically equivalent to the official VNP46A3 composite.
Use the same polygons and aggregation statistic for both sources. Here the comparison uses the two states already downloaded above, over 2021-2022, so it does not cross Bhuvan’s 2024 collection break.
viirs_panel <- extract_panel(annual, states_sel, id_col = "state_name")
viirs_panel$source <- "NASA VNP46A4 C2"
viirs_panel$units <- "nW/cm^2/sr"
bhuvan_panel <- extract_panel(bhuvan_maps, states_sel, id_col = "state_name")
bhuvan_panel$source <- "ISRO Bhuvan WMS"
bhuvan_panel$units <- "BT.709 luminance"Raw levels cannot be compared because the units differ. A useful diagnostic is to express each state and source relative to its own 2021 value. An index of 120 then means that the source reports a 20% increase since 2021; it does not mean that the two sensors observed the same absolute brightness.
comparison <- rbind(viirs_panel, bhuvan_panel)
comparison <- ntl_index(
comparison,
baseline = 2021,
group_cols = c("source", "region_id"),
index_col = "index_2021"
)
library(ggplot2)
ggplot(comparison, aes(year, index_2021, colour = source)) +
geom_hline(yintercept = 100, colour = "grey60", linetype = 2) +
geom_line(linewidth = 0.8) +
geom_point(size = 2) +
facet_wrap(~region_id) +
labs(
x = NULL, y = "Index (2021 = 100)",
title = "Within-source nighttime-light change",
subtitle = "Index trajectories are comparable; raw levels are not",
colour = NULL
) +
theme_ntl() +
theme(legend.position = "bottom")Check whether conclusions depend on the source instead of relying only on the figure. The following produces one correlation per state across the two overlap years and ranks states with the weakest agreement:
agreement <- ntl_source_agreement(viirs_panel, bhuvan_panel)
head(agreement, 10)
#> region_id correlation observations
#> 2 West Bengal -1 2
#> 1 Bihar 1 2With only two overlap observations this correlation is a diagnostic, not a validation statistic.
For an outage study using daily data, use pre-event and post-event windows, compare affected states with unaffected controls, and record the exact QA rule. Do not fill cloudy daily observations with zero.
For most India economic panels, the preferred core series is VNP46A4 Collection 2 from 2012 onward. Bhuvan remains useful for unauthenticated India exploration and for its pre-aggregated state statistics.