An interactive web application for analyzing nighttime lights data from NASA/NOAA VIIRS satellite imagery. Track economic activity, urban development, and infrastructure changes through satellite data visualization.
- 🔍 City Search: Intelligent geocoding with OSM Nominatim API
- 📊 Interactive Visualization: Leaflet.js maps + D3.js timeline graphs
- 📈 Bias Correction: Implements Elvidge et al. (2021) methodology
- 🎬 Animation: Timeline playback of nightlights changes
- 📥 Data Export: Excel, CSV, and JSON export functionality
- 🌓 Dark Theme: Optimized for nightlights data visualization
- 📱 Responsive: Works on desktop and mobile devices
- Python 3.9+ (for backend)
- Modern web browser (Chrome, Firefox, Safari, Edge)
- 4GB RAM minimum (recommended: 8GB)
- Internet connection (for NOAA data downloads)
- Clone or download this repository
cd /Users/user/Documents/repo/satDataTest- Create a virtualenv (recommended)
python3 -m venv .venv
source .venv/bin/activate- Install Python dependencies
cd backend
pip install -r requirements.txt- Configure environment variables (recommended)
Copy .env.example to backend/.env and adjust paths if needed:
cp ../.env.example .envBy default the app runs in NOAA mode (VIIRS_SOURCE=noaa) and does not require any Google accounts.
If you want to run in Google Earth Engine mode (VIIRS_SOURCE=gee), follow docs/GEE_SETUP.md.
- Start the backend server
python main.pyThe API will start on http://localhost:8000
Note: This repository is real-data only. Any mock/synthetic data generation used during early testing has been removed.
- Generate the city hotlist (for autocomplete + clusters)
The UI uses a ~800-place hotlist for instant autocomplete and predefined clusters. Generate it once:
python3 scripts/build_hotlist.pyThis creates backend/data/hotlist.json locally (it is intentionally not committed to the repo).
- Open the frontend
Open frontend/index.html in your web browser, or use a simple HTTP server:
cd frontend
python -m http.server 8080Then navigate to http://localhost:8080
- Search for a city - Enter city name (e.g., "Mumbai") or coordinates (e.g., "19.07, 72.87")
- Select date range - Choose start/end months and years (2012-2025)
- Click "Analyze" - Wait for data to be fetched from NOAA
- Explore visualizations:
- Map: Geographic view with radiance intensity circles
- Graph: Timeline showing radiance trends
- Animation: Play timeline to see changes over time
- Export data - Download processed data as Excel, CSV, or JSON
Mumbai
Berlin, Germany
52.52, 13.40
New York
- Minimum: 2012-01 (VIIRS data start)
- Maximum: Current month - 2 months (processing lag)
- Granularity: Monthly composites
- Units: nW/cm²/sr (nanowatts per square centimeter per steradian)
- Typical Urban Values: 0.5 - 10 nW/cm²/sr
- Interpretation: Higher values = more nighttime lighting = more economic activity
- Calculation:
((current - baseline) / baseline) × 100 - Baseline: First year in selected range (same month)
- Color Coding:
- 🟢 Green (+40%+): Large increase vs baseline
- 🟡 Yellow (-40% to +40%): Stable (typical seasonality)
- 🔴 Red (-40%-): Large decrease (e.g. conflict / outage)
Some months have very low cloud-free coverage (e.g., monsoon-heavy periods). To avoid misleading “dips to zero”, this backend treats months with cloud_free_coverage < MIN_CF_CVG as missing:
radiance = nullradiance_corrected = null
You can tune the threshold using MIN_CF_CVG (see .env.example).
- Analysis radius is approximate: the dashed ring is a fixed-distance circle around the geocoded center, not an official city/district/port boundary.
- Not a statutory map: shapes are for orientation only and do not follow legal administrative borders.
- How the monthly point is computed: VIIRS pixels in a square bounding box (±radius) are averaged with equal weight, then bias-corrected (Elvidge et al., 2021).
- How “% change” is defined: for each month-of-year, the current value is compared to the same month in the first year of your selected range (the “baseline”), which helps reduce seasonality effects.
- Exports include raw and processed: exports include both
Radiance_RawandRadiance_BiasCorrected. - Exports include quality flags: exports include
Data_QualityandNull_Reasonso you can see when/why a month is missing.
http://localhost:8000
GET /viirs/latest-availableResponse:
{
"year": 2025,
"month": 9,
"date_string": "2025-09"
}POST /viirs/city
Content-Type: application/json
{
"city": "Mumbai",
"country": "India",
"start_month": 1,
"start_year": 2019,
"end_month": 12,
"end_year": 2024
}Response:
{
"status": "success",
"city_info": {
"city": "Mumbai",
"country": "India",
"lat": 19.0760,
"lon": 72.8777,
"radius_km": 10
},
"data": [
{
"date": "2019-01",
"city": "Mumbai",
"country": "India",
"latitude": 19.0760,
"longitude": 72.8777,
"radiance": 5.234,
"radiance_corrected": 5.678,
"cloud_free_coverage": 87.5
}
],
"metadata": {
"baseline_year": 2019,
"data_points": 72,
"data_source": "NOAA Earth Observation Group"
}
}GET /search?q=Mumb&limit=10POST /viirs/coordinates
Content-Type: application/json
{
"latitude": 19.07,
"longitude": 72.87,
"radius_km": 10,
"start_month": 1,
"start_year": 2019,
"end_month": 12,
"end_year": 2024
}NOAA Earth Observation Group (EOG)
https://eogdata.mines.edu/nighttime_light/monthly/v10
- Satellite: Suomi NPP VIIRS DNB
- Resolution: ~750m
- Temporal: Monthly composites (2012-present)
- Coverage: Global (-65° to 75°N)
Implements Elvidge et al. (2021) methodology:
corrected_radiance = radiance × (1 + α × (1 - cloud_free_coverage/100))
Where:
α ≈ 0.2(empirically determined)cloud_free_coverage= percentage of cloud-free observations
Reference:
Elvidge, C. D., Zhizhin, M., Ghosh, T., Hsu, F. C., & Taneja, J. (2021). "But clouds got in my way: Bias and bias correction of VIIRS nighttime lights data." Remote Sensing of Environment, 258, 112165. https://doi.org/10.1016/j.rse.2021.112165
This tool uses a simplified circular/square approximation for city boundaries:
How it works:
- Each city is assigned a fixed circular radius (e.g., Mumbai: 20 km, Tiruppur: 8 km)
- Data is extracted from a square bounding box (±radius in lat/lon)
- All pixels within the box are averaged with equal weight
Example: For a 10 km radius city:
- Square area: 20×20 km = 400 km²
- Pixel count: ~729 pixels (at 750m resolution)
- Averaging: Simple mean of all pixel radiance values
- Temporal trend analysis - Track changes in the same city over time
- City-to-city comparisons - Compare multiple cities consistently
- Economic activity monitoring - Detect growth, decline, or stability patterns
- Regional analysis - Understand broad urban development trends
- Policy impact assessment - Before/after analysis of infrastructure projects
- Precise urban boundaries - Does not follow actual city limits or administrative boundaries
- Irregular city shapes - Treats all cities as circles/squares regardless of actual shape
- Linear cities (coastal, along rivers) will include water/rural areas
- Cities with industrial corridors or satellite towns may be under/over-represented
- Fine spatial detail - Corner pixels (27% extra area) dilute the urban core signal
- Absolute radiance accuracy - Different cities averaged over varying pixel counts
- Dense urban cores - Suburban/rural areas at edges weighted equally to downtown
- Distance-based analysis - No weighting by distance from city center
| Component | Impact | Magnitude |
|---|---|---|
| Square vs Circle | Extra area in corners | +27% area |
| High latitude distortion | Longitude degree approximation | ±50% at 60° latitude |
| VIIRS pixel resolution | Spatial aggregation limit | ~750m at nadir |
| Variable pixel counts | Small vs large cities | 300-6,400 pixels |
| No distance weighting | Edge pixels = center pixels | No gradient |
For best results:
- Use for time-series analysis within the same city
- Compare cities of similar sizes for more meaningful comparisons
- Interpret absolute radiance values with caution
- Consider that coastal/linear cities may have diluted values
- Use percentage change metrics rather than absolute values when comparing different cities
Alternative approaches for precise analysis:
- Download actual city boundary shapefiles from OpenStreetMap
- Use GIS software (QGIS, ArcGIS) with VIIRS GeoTIFF files
- Implement distance-weighted averaging or circular masks
- Consider population-weighted metrics for accuracy
baseline = first_year_data[same_month]
percentage_change = ((current - baseline) / baseline) × 100- RAM: 4–8GB
- CPU: 2 cores
- Storage: 50GB+ (more if you persist caches or store large datasets)
- Network: stable outbound bandwidth (NOAA downloads can be large)
sudo apt update
sudo apt install python3-pip nginx
cd /path/to/satDataTest/backend
pip3 install -r requirements.txtsudo nano /etc/systemd/system/viirs-backend.service[Unit]
Description=VIIRS Nightlights Backend
After=network.target
[Service]
User=www-data
WorkingDirectory=/path/to/satDataTest/backend
ExecStart=/usr/bin/python3 -m uvicorn main:app --host 0.0.0.0 --port 8000
Restart=always
[Install]
WantedBy=multi-user.targetsudo systemctl enable viirs-backend
sudo systemctl start viirs-backendsudo nano /etc/nginx/sites-available/viirsserver {
listen 80;
server_name <your-domain>;
# Frontend
location / {
root /path/to/satDataTest/frontend;
index index.html;
try_files $uri $uri/ /index.html;
}
# Backend API
location /api/ {
proxy_pass http://localhost:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}sudo ln -s /etc/nginx/sites-available/viirs /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginxcrontab -eAdd:
0 2 5 * * /usr/bin/python3 /path/to/satDataTest/backend/update_preprocessed_cities.py >> /var/log/viirs_update.log 2>&1
The tool includes a pre-processed database of 140 major cities for instant results:
- 45 Indian cities (metros, SEZ, defense hubs, border cities)
- 25 American cities (major metros)
- 30 European capitals
- 20 Pakistani cities (cross-border analysis)
- 20 Conflict zone cities (Ukraine, Russia, Middle East)
Storage: ~70 MB (SQLite)
Response time: <100ms (vs 30-60s for new cities)
Error: "Module not found"
pip install -r backend/requirements.txtIf you set VIIRS_SOURCE=gee and see initialization/auth errors, follow the setup checklist in docs/GEE_SETUP.md.
Error: "Address already in use"
# Change port in main.py
uvicorn main:app --port 8000Error: "NOAA server timeout"
- Check internet connection
- NOAA servers may be temporarily down
- Try again in a few minutes
Map not loading
- Check browser console for errors
- Ensure backend is running (
http://localhost:8000) - Try different map style (OSM, Dark, Satellite)
Export not working
- Check browser allows downloads
- Ensure SheetJS library loaded (check console)
- NOAA EOG VIIRS Data
- Elvidge et al. (2021) Paper
- OpenStreetMap Nominatim
- Leaflet.js Documentation
- D3.js Documentation
Contributions welcome! Please:
- Fork the repository
- Create feature branch (
git checkout -b feature/AmazingFeature) - Commit changes (
git commit -m 'Add AmazingFeature') - Push to branch (
git push origin feature/AmazingFeature) - Open Pull Request
This project is open source for non-commercial use (personal, educational, and non-profit research). Commercial use is not permitted without separate written permission. See LICENSE and the methodology page for details.
Repository: https://github.com/dso6060/viirs-nightlights-analysis-tool
- Data: NOAA Earth Observation Group
- Methodology: Elvidge et al. (2021)
- Geocoding: OpenStreetMap / Nominatim
- Inspiration: ukRus VIIRS project
For issues or questions:
- Open a GitHub issue
- Check troubleshooting section
- Review API documentation
Built with 🛰️ satellite data and ❤️ for data visualization