Initial Climate Mood Analysis project
This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
# County Climate Data Sources and Mapping
|
||||
|
||||
This project now supports county polygons and county-level filter records keyed by 5-digit county FIPS.
|
||||
|
||||
## Launching the app
|
||||
|
||||
The browser blocks `fetch("data/climate-data.csv")` when `index.html` is opened directly as a `file://` URL, so run the site through a local HTTP server:
|
||||
|
||||
```powershell
|
||||
.\serve.ps1
|
||||
```
|
||||
|
||||
Then open [http://localhost:8000/](http://localhost:8000/). This keeps the app CSV-only while allowing the map and filters to load normally.
|
||||
|
||||
## Source 1: Koppen-Geiger classes (`koppenZone`)
|
||||
|
||||
- Dataset: Beck et al. updated 1-km Koppen-Geiger climate classes (historical + future windows)
|
||||
- Landing page: [https://www.gloh2o.org/koppen/](https://www.gloh2o.org/koppen/)
|
||||
- Primary paper for updated release: [https://www.nature.com/articles/s41597-023-02549-6](https://www.nature.com/articles/s41597-023-02549-6)
|
||||
- Coverage: 1901-2099 (use historical 1991-2020 layer for this project to align with NOAA baselines)
|
||||
- License shown on dataset page: CC BY 4.0
|
||||
|
||||
## Source 2: NOAA 1991-2020 gridded normals (`avgTempF`, `annualPrecipIn`, `seasonalityIndex`, previous `extremeDays`)
|
||||
|
||||
- Main product page: [https://www.ncei.noaa.gov/products/land-based-station/us-climate-normals](https://www.ncei.noaa.gov/products/land-based-station/us-climate-normals)
|
||||
- Monthly gridded normals readme: [https://www.ncei.noaa.gov/sites/default/files/2022-04/Readme_Monthly_Gridded_Normals.pdf](https://www.ncei.noaa.gov/sites/default/files/2022-04/Readme_Monthly_Gridded_Normals.pdf)
|
||||
- Daily gridded normals readme: [https://www.ncei.noaa.gov/sites/default/files/2022-09/Readme%20for%20Daily%20Gridded%20Normals%201991-2020.pdf](https://www.ncei.noaa.gov/sites/default/files/2022-09/Readme%20for%20Daily%20Gridded%20Normals%201991-2020.pdf)
|
||||
- Daily gridded normals documentation (includes units in C/mm): [https://www.ncei.noaa.gov/sites/default/files/2022-09/Documentation_Daily_Gridded_Normals%20V1.0.pdf](https://www.ncei.noaa.gov/sites/default/files/2022-09/Documentation_Daily_Gridded_Normals%20V1.0.pdf)
|
||||
|
||||
The ETL script uses:
|
||||
|
||||
- Monthly `tavg` normals (C)
|
||||
- Monthly `prcp` normals (mm)
|
||||
- Daily `tmax` normals (C)
|
||||
- Daily `tmin` normals (C)
|
||||
|
||||
If you have monthly nClimGrid history files (for example `nclimgrid_tavg.nc`) rather than 12-slice normals files:
|
||||
|
||||
- The script now computes a 1991-2020 monthly climatology from the time series automatically.
|
||||
- `extremeDays` can run in `auto` mode, which uses a documented monthly proxy when daily grids are not provided.
|
||||
|
||||
## Source 3: County polygons / FIPS join geometry
|
||||
|
||||
- Current app geometry file: `data/geojson-counties-fips.json`
|
||||
- Original Plotly county geometry source: [https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json](https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json)
|
||||
- Official county geometry reference (Census TIGER/Line): [https://www.census.gov/geographies/mapping-files/time-series/geo/tiger-line-file.html](https://www.census.gov/geographies/mapping-files/time-series/geo/tiger-line-file.html)
|
||||
|
||||
## Source 4: Solar resource (`avgSolarGhiKwhM2Day`)
|
||||
|
||||
- Recommended dataset: NREL National Solar Radiation Database (NSRDB)
|
||||
- Data/API page: [https://developer.nrel.gov/docs/solar/nsrdb/](https://developer.nrel.gov/docs/solar/nsrdb/)
|
||||
- Maps/geospatial data page: [https://www.nrel.gov/gis/solar-resource-maps](https://www.nrel.gov/gis/solar-resource-maps)
|
||||
- Fallback point API option: NASA POWER `ALLSKY_SFC_SW_DWN` (surface shortwave downwelling radiation), [https://power.larc.nasa.gov/docs/tutorials/service-data-request/api/](https://power.larc.nasa.gov/docs/tutorials/service-data-request/api/)
|
||||
|
||||
The app metric is designed for annual average daily global horizontal irradiance (GHI), in `kWh/m2/day`.
|
||||
For county means, use a gridded annual GHI raster and pass it to the generator with `--solar-ghi-raster`.
|
||||
|
||||
### First test: NSRDB county representative points
|
||||
|
||||
For a lightweight first pass, sample each county at one interior representative point instead of requesting full county polygons.
|
||||
This creates a point CSV:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\build_county_representative_points.py
|
||||
```
|
||||
|
||||
Then fetch a small NSRDB GOES TMY test batch.
|
||||
If `--email` or `--api-key` are omitted, the script prompts you for them:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\fetch_nsrdb_representative_point_ghi.py --limit 10
|
||||
```
|
||||
|
||||
The fetch script uses GOES TMY first.
|
||||
For high-latitude points, it can automatically retry the NSRDB Polar TMY endpoint when GOES reports no data.
|
||||
If a county FIPS appears in `data/nrel/county_representative_point_ghi_error_log.csv` from an earlier run, that county tries the Polar endpoint first on the next run.
|
||||
Disable this behavior with `--no-polar-fallback`.
|
||||
|
||||
Outputs:
|
||||
|
||||
- `data/nrel/county_representative_points.csv`: county FIPS, name, state, latitude, and longitude.
|
||||
- `data/nrel/representative_point_csv/`: cached raw NSRDB CSV responses by county FIPS.
|
||||
- `data/nrel/county_representative_point_ghi_summary.csv`: summarized `avgSolarGhiKwhM2Day` values.
|
||||
|
||||
The calculation is:
|
||||
|
||||
```text
|
||||
avgSolarGhiKwhM2Day = sum(hourly GHI) / 1000 / 365
|
||||
```
|
||||
|
||||
If the raw cache is complete but the summary CSV only contains the last fetched batch, rebuild the summary from cached files without calling the API:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\rebuild_nsrdb_representative_point_ghi_summary.py
|
||||
```
|
||||
|
||||
This point-based workflow is easier to validate and resume than full polygon downloads, but it is an approximation of county sunlight rather than an area-weighted county mean.
|
||||
|
||||
### First cloud-cover pass: NSRDB representative points
|
||||
|
||||
For a fast cloud-cover input layer, fetch representative-point NSRDB CSVs with observed GHI, Clearsky GHI, and Cloud Type:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\fetch_nsrdb_representative_point_cloud_metrics.py --limit 10
|
||||
```
|
||||
|
||||
Once the first batch looks right, fetch every county:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\fetch_nsrdb_representative_point_cloud_metrics.py --all
|
||||
```
|
||||
|
||||
Outputs:
|
||||
|
||||
- `data/nrel/representative_point_cloud_csv/`: cached raw NSRDB CSV responses with `ghi,clearsky_ghi,cloud_type`.
|
||||
- `data/nrel/county_representative_point_cloud_summary.csv`: summarized cloud-cover proxy fields.
|
||||
- `data/nrel/county_representative_point_cloud_error_log.csv`: failed county requests with redacted API context.
|
||||
|
||||
The primary calculation uses daylight rows where Clearsky GHI is at least 50 W/m2:
|
||||
|
||||
```text
|
||||
cloudinessIndexPct = 1 - mean(clamped(GHI / Clearsky GHI, 0, 1))
|
||||
```
|
||||
|
||||
The same summary also stores daylight row counts, observed-to-clear-sky ratio, and broad Cloud Type frequency buckets. This is faster than the polygon archive workflow, but it remains a representative-point county approximation.
|
||||
|
||||
Apply the representative-point cloudiness metric to the browser app CSV:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\apply_nsrdb_cloud_metric_to_climate_data.py
|
||||
```
|
||||
|
||||
The current cloud summary covers the 3,143 county representative points and leaves Puerto Rico rows blank in `data/climate-data.csv`.
|
||||
|
||||
### County-average target: NSRDB polygon cloud archive requests
|
||||
|
||||
For a less noisy county-level cloudiness layer, submit county polygons to the NSRDB archive workflow with `ghi,clearsky_ghi,cloud_type`. This uses the same polygon tiling and pacing logic as the GHI archive workflow, but writes separate cloud manifests, response JSON files, archives, and summaries.
|
||||
|
||||
Start with a dry run:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\request_nsrdb_county_polygon_cloud_archives.py `
|
||||
--dry-run `
|
||||
--skip-site-count `
|
||||
--limit 5
|
||||
```
|
||||
|
||||
Then submit a small real batch:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\request_nsrdb_county_polygon_cloud_archives.py `
|
||||
--limit 5
|
||||
```
|
||||
|
||||
Continue in resumable chunks:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\request_nsrdb_county_polygon_cloud_archives.py `
|
||||
--start 5 `
|
||||
--limit 100
|
||||
```
|
||||
|
||||
Download completed archives:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\download_nsrdb_county_polygon_cloud_archives.py
|
||||
```
|
||||
|
||||
Summarize downloaded archives into area-weighted county cloudiness:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\summarize_nsrdb_county_polygon_cloud_archives.py --reuse-existing-output
|
||||
```
|
||||
|
||||
Outputs:
|
||||
|
||||
- `data/nrel/county_polygon_cloud_request_manifest.csv`: submitted cloud archive requests.
|
||||
- `data/nrel/county_polygon_cloud_error_log.csv`: cloud archive request errors.
|
||||
- `data/nrel/polygon_cloud_request_responses/`: raw NSRDB cloud acknowledgement JSON files.
|
||||
- `data/nrel/polygon_cloud_archives/`: downloaded cloud ZIP archives.
|
||||
- `data/nrel/county_polygon_cloud_summary.csv`: area-weighted county cloudiness summaries.
|
||||
|
||||
Then update the browser app CSV. Polygon area-weighted values are used first when `data/nrel/county_polygon_cloud_summary.csv` exists; representative-point values remain the fallback:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\apply_nsrdb_cloud_metric_to_climate_data.py
|
||||
```
|
||||
|
||||
### County-average target: NSRDB polygon archive requests
|
||||
|
||||
For the final county-average solar layer, submit county polygons to the NSRDB archive workflow instead of sampling one representative point.
|
||||
The polygon request returns a generated archive URL per county. Download those archives, then summarize them into a county polygon GHI CSV that can replace the representative-point solar values where available.
|
||||
|
||||
Start with a small dry run that does not touch the API:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\request_nsrdb_county_polygon_ghi_archives.py `
|
||||
--dry-run `
|
||||
--skip-site-count `
|
||||
--limit 5
|
||||
```
|
||||
|
||||
Then submit a small real batch:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\request_nsrdb_county_polygon_ghi_archives.py `
|
||||
--limit 5
|
||||
```
|
||||
|
||||
Once the first batch looks right, continue in resumable chunks:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\request_nsrdb_county_polygon_ghi_archives.py `
|
||||
--start 5 `
|
||||
--limit 100
|
||||
```
|
||||
|
||||
Or request every county from the current start point:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\request_nsrdb_county_polygon_ghi_archives.py `
|
||||
--all
|
||||
```
|
||||
|
||||
Outputs:
|
||||
|
||||
- `data/nrel/county_polygon_ghi_request_manifest.csv`: one row per submitted county, including NSRDB profile, site count, request weight, response JSON path, and `download_url`.
|
||||
- `data/nrel/county_polygon_ghi_error_log.csv`: counties that need retrying or tiling.
|
||||
- `data/nrel/polygon_request_responses/`: raw API acknowledgement JSON files.
|
||||
- `data/nrel/polygon_archives/`: downloaded county or tiled county ZIP archives.
|
||||
- `data/nrel/county_polygon_ghi_summary.csv`: summarized polygon archive GHI, including `avgSolarGhiKwhM2Day`.
|
||||
|
||||
Download completed GHI archives:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\download_nsrdb_county_polygon_ghi_archives.py
|
||||
```
|
||||
|
||||
The script uses POST requests because county polygon WKT can be long.
|
||||
It checks NSRDB site count by default and skips counties whose estimated request weight exceeds the API maximum.
|
||||
Default pacing is one request every 2.1 seconds, matching the NSRDB archive limit.
|
||||
Use `--include-puerto-rico` if you want to test Puerto Rico polygons too.
|
||||
|
||||
After the archives are downloaded, rebuild or resume the polygon summary:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\summarize_nsrdb_county_polygon_archives.py --reuse-existing-output
|
||||
```
|
||||
|
||||
Then update the app CSV. Polygon archive GHI is used first; representative-point GHI remains as a fallback for counties without polygon archive data:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\apply_locally_extreme_metric_to_climate_data.py
|
||||
```
|
||||
|
||||
## Metric definitions in generated output
|
||||
|
||||
- `koppenZone`: majority class within county polygon from Koppen raster.
|
||||
- `avgTempF`: mean of 12 monthly county mean temperatures, converted C -> F.
|
||||
- `annualPrecipIn`: sum of 12 monthly county mean precipitation totals, converted mm -> inches.
|
||||
- `seasonalityIndex`: coefficient of variation of monthly precipitation totals, scaled to 0-100.
|
||||
- `wettestPrecipMonth`: month with the highest 1991-2020 county mean precipitation total.
|
||||
- `driestPrecipMonth`: month with the lowest 1991-2020 county mean precipitation total.
|
||||
- previous `extremeDays` / `oldExtremeDays`: count of daily-normal or monthly-proxy days where county mean `tmax >= 95F` or `tmin <= 32F` (thresholds configurable in script). This is preserved only as an audit column after the NOAA nClimGrid-Daily metrics are applied.
|
||||
- `avgSolarGhiKwhM2Day`: county mean annual average daily GHI, in `kWh/m2/day`. The current app CSV uses NSRDB polygon archive area-weighted values where available, with representative-point values kept as fallback.
|
||||
- `cloudinessIndexPct`: representative-point NSRDB daylight cloudiness proxy derived from observed GHI divided by Clearsky GHI. Higher values mean observed irradiance is lower relative to modeled clear-sky irradiance. Despite the legacy field name, values are stored on a 0-1 scale.
|
||||
|
||||
## Run the generator
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```powershell
|
||||
pip install -r scripts/requirements_county_etl.txt
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/build_county_climate_data.py `
|
||||
--counties-geojson data/geojson-counties-fips.json `
|
||||
--koppen-raster data/koppen_geiger_tif/1991_2020/koppen_geiger_0p00833333.tif `
|
||||
--koppen-legend data/koppen_geiger_tif/legend.txt `
|
||||
--monthly-tavg-nc data/noaa/nclimgrid/nclimgrid_tavg.nc `
|
||||
--monthly-prcp-nc data/noaa/nclimgrid/nclimgrid_prcp.nc `
|
||||
--daily-tmax-nc data/noaa/nclimgrid/nclimgrid_tmax.nc `
|
||||
--daily-tmin-nc data/noaa/nclimgrid/nclimgrid_tmin.nc `
|
||||
--climatology-start-year 1991 `
|
||||
--climatology-end-year 2020 `
|
||||
--extreme-days-mode auto `
|
||||
--solar-ghi-csv data/nrel/county_polygon_ghi_summary.csv `
|
||||
--out data/climate-data.csv
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- This computes all counties in your geometry file, not just the sample records.
|
||||
- For counties outside CONUS coverage in NOAA gridded files, fallback values are applied by the script when no valid grid values intersect.
|
||||
- For physically-based daily `extremeDays`, provide true daily grids and set `--extreme-days-mode require-daily`.
|
||||
- If `--counties-geojson` does not exist locally, the script will try to download the county GeoJSON automatically from the Plotly URL above and cache it at that path.
|
||||
- `--solar-ghi-csv` is optional and can load county-keyed solar summaries into `avgSolarGhiKwhM2Day`.
|
||||
- `--solar-ghi-raster` is optional and takes precedence over `--solar-ghi-csv`. If you have a gridded annual GHI raster, add `--solar-ghi-raster path/to/annual_ghi_kwh_m2_day.tif` for a true county-area raster mean.
|
||||
|
||||
## Source 5: NOAA nClimGrid-Daily county area averages (`locallyExtremeDays`)
|
||||
|
||||
- Main product page: [https://www.ncei.noaa.gov/products/land-based-station/nclimgrid-daily](https://www.ncei.noaa.gov/products/land-based-station/nclimgrid-daily)
|
||||
- County/monthly area averages root: [https://www.ncei.noaa.gov/data/nclimgrid-daily/access/averages/](https://www.ncei.noaa.gov/data/nclimgrid-daily/access/averages/)
|
||||
- User guide: [https://www.ncei.noaa.gov/data/nclimgrid-daily/doc/nclimgrid-daily_v1-0-0_user-guide.pdf](https://www.ncei.noaa.gov/data/nclimgrid-daily/doc/nclimgrid-daily_v1-0-0_user-guide.pdf)
|
||||
- NCEI state-code to FIPS crosswalk: [https://www.ncei.noaa.gov/data/nclimgrid-daily/doc/us-state-codes_ncei-to-fips.csv](https://www.ncei.noaa.gov/data/nclimgrid-daily/doc/us-state-codes_ncei-to-fips.csv)
|
||||
- Census boundary change notes: [https://www.census.gov/programs-surveys/geography/technical-documentation/boundary-change-notes.html](https://www.census.gov/programs-surveys/geography/technical-documentation/boundary-change-notes.html)
|
||||
- Census county changes: [https://www.census.gov/programs-surveys/geography/technical-documentation/county-changes.html](https://www.census.gov/programs-surveys/geography/technical-documentation/county-changes.html)
|
||||
|
||||
The locally extreme metric uses county-specific 1991-2020 thresholds:
|
||||
|
||||
```text
|
||||
p95_tmax_c = 95th percentile of county-average daily Tmax
|
||||
p05_tmin_c = 5th percentile of county-average daily Tmin
|
||||
locallyExtremeDays = count(Tmax >= p95_tmax_c OR Tmin <= p05_tmin_c)
|
||||
```
|
||||
|
||||
The same daily county Tmax/Tmin records now also produce an absolute climate-bucket metric:
|
||||
|
||||
```text
|
||||
absoluteExtremeDays = count(Tmax >= 95F OR Tmin <= 0F)
|
||||
```
|
||||
|
||||
The heat threshold follows the EPA extreme-heat example of days at or above 95F.
|
||||
The cold threshold is intentionally stricter than the older 32F freeze bucket because NWS cold guidance treats freezing as a freeze-warning/crop threshold and describes human extreme cold as region-dependent and often wind-chill based. The current NOAA cache does not include wind, so `Tmin <= 0F` is used as a configurable air-temperature proxy.
|
||||
|
||||
Run from the local NOAA CSV cache:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\build_county_locally_extreme_data.py --skip-download
|
||||
```
|
||||
|
||||
Outputs:
|
||||
|
||||
- `data/noaa/county_locally_extreme_thresholds.csv`
|
||||
- `data/noaa/county_locally_extreme_days.csv`
|
||||
- `data/noaa/county_locally_extreme_days_comparison.csv`
|
||||
|
||||
Apply the locally extreme average to the browser app CSV:
|
||||
|
||||
```powershell
|
||||
.venv\Scripts\python.exe scripts\apply_locally_extreme_metric_to_climate_data.py
|
||||
```
|
||||
|
||||
This keeps `data/climate-data.csv` compatible with the existing app by writing the locally extreme average into the active `extremeDays` column, while preserving the earlier proxy metric in `oldExtremeDays`. It also adds detail/audit columns:
|
||||
|
||||
- `locallyExtremeDays`
|
||||
- `locallyExtremeHotDays`
|
||||
- `locallyExtremeColdDays`
|
||||
- `absoluteExtremeDays`
|
||||
- `oldExtremeDays`
|
||||
- `locallyExtremeAnalysisYears`
|
||||
- `locallyExtremeSourceFips`
|
||||
- `locallyExtremeFipsAdjustment`
|
||||
|
||||
FIPS/geography-vintage policy:
|
||||
|
||||
- Use current NOAA/Census county-equivalent FIPS for the locally extreme outputs.
|
||||
- Correct NOAA's District of Columbia county row from source region code `18511` to Census FIPS `11001`.
|
||||
- Apply only one-to-one old app comparison concordances, currently `46113 -> 46102` for Shannon/Oglala Lakota and `51515 -> 51019` for Bedford city/Bedford County.
|
||||
- Do not guess split or many-to-one geography changes without a Census relationship file; leave those rows flagged in the comparison output.
|
||||
Reference in New Issue
Block a user