diff --git a/.gitignore b/.gitignore index 484f99f..29de853 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ logs/ ## slurm ---- **/slurm* +## data files ---- +data/ + ## Python ---- # Byte-compiled / optimized / DLL files diff --git a/Dockerfile b/Dockerfile index c871f80..d69b3fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,8 +13,8 @@ RUN mamba env update -n base -f requirements.yaml #&& mamba clean -a # Create paths to data placeholders -RUN python utils/create_dir_paths.py datapaths.input.satellite_pm25.annual=null datapaths.input.satellite_pm25.monthly=null +RUN python utils/create_dir_paths.py datapaths.input.satellite_pm25.yearly=null datapaths.input.satellite_pm25.monthly=null -# snakemake --configfile conf/config.yaml --cores 4 -C temporal_freq=annual +# snakemake --configfile conf/config.yaml --cores 4 -C temporal_freq=yearly ENTRYPOINT ["snakemake", "--configfile", "conf/config.yaml"] -CMD ["--cores", "4", "-C", "polygon_name=county", "temporal_freq=annual"] +CMD ["--cores", "4", "-C", "polygon_name=county", "temporal_freq=yearly"] diff --git a/README.md b/README.md index cb3a85f..27a05aa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# pm25_washu_raster2polygon +# pm25_randall_raster2polygon Code to produce spatial aggregations of pm25 estimates as generated by the [Atmospheric Composition Analysis Group](https://sites.wustl.edu/acag/datasets/surface-pm2-5/). The spatial aggregation are performed for satellite pm25 from grid/raster (NetCDF) to polygons (shp). @@ -10,7 +10,7 @@ The [Atmospheric Composition Analysis Group](https://sites.wustl.edu/acag/datase The version [V5.GL.04](https://sites.wustl.edu/acag/datasets/surface-pm2-5/#V5.GL.04) consists of mean PM2.5 (ug/m3) available at: -* Temporal frequency: Annual and monthly +* Temporal frequency: yearly and monthly * Grid resolutions: (0.1° × 0.1°) and (0.01° × 0.01°) * Geographic regions: North America, Europe, Asia, and Global @@ -29,26 +29,89 @@ Aaron van Donkelaar, Melanie S. Hammer, Liam Bindle, Michael Brauer, Jeffery R. # Codebook -## Dataset Columns: +## Dataset Output Structure -* county aggregations: +The pipeline produces parquet files with PM2.5 aggregations at the polygon level. -* zcta aggregations: +### Yearly aggregations (`temporal_freq=yearly`): + +Output path: `data/V5GL/output/{polygon_name}_yearly/pm25__randall__{polygon_name}_yearly__{year}.parquet` + +Columns: +* `{polygon_id}` (index): County FIPS code, ZCTA code, or Census Tract GEOID depending on polygon type +* `pm25` (float64): Mean PM2.5 concentration (µg/m³) for the year +* `year` (int64): Year of observation + +### Monthly aggregations (`temporal_freq=monthly`): + +Output path: `data/V5GL/output/{polygon_name}_monthly/pm25__randall__{polygon_name}_monthly__{year}.parquet` + +Columns: +* `{polygon_id}` (index): County FIPS code, ZCTA code, or Census Tract GEOID depending on polygon type +* `pm25` (float64): Mean PM2.5 concentration (µg/m³) for the month +* `year` (int64): Year of observation +* `month` (object/int): Month of observation + +### Polygon ID Variables + +The index column name varies by polygon type and is defined in `conf/shapefiles/shapefiles.yaml`: +* **County**: `county` (5-digit FIPS code, e.g., "01001" for Autauga County, Alabama) +* **ZCTA**: `zcta` (5-digit ZIP Code Tabulation Area, e.g., "00601") +* **Census Tract**: `GEOID` (11-digit census tract identifier) + +### Intermediate Files (Monthly only) + +During monthly processing, intermediate files are created: +* Path: `data/V5GL/intermediate/{polygon_name}_monthly/pm25__randall__{polygon_name}_monthly__{year}_{month}.parquet` +* These are concatenated into yearly files in the output directory --- # Configuration files -The configuration structure withing the `/conf` folder allow you to modify the input parameters for the following steps: +The configuration structure within the `/conf` folder allows you to modify the input parameters for the following steps: -* create directory paths: `utils/create_dir_paths.py` +* create directory paths: `src/create_datapaths.py` * download pm25: `src/download_pm25.py` * download shapefiles: `src/download_shapefile.py` * aggregate pm25: `src/aggregate_pm25.py` +* concatenate monthly files: `src/concat_monthly.py` The key parameters are: -* `temporal_freq` which determines whether the original annual or monthly pm25 files will be aggregated. The options are: `annual` and `monthly`. -* `polygon_name` which determines into which polygons the pm25 grid will the aggregated. The options are: `zcta` and `county`. +* `temporal_freq` which determines whether the original yearly or monthly pm25 files will be aggregated. The options are: `yearly` and `monthly`. +* `polygon_name` which determines into which polygons the pm25 grid will be aggregated. The options are: `zcta`, `county`, and `census_tract`. + +## Configuration Structure + +The configuration system uses Hydra with the following structure: + +* `/conf/config.yaml` - Main configuration file with default settings +* `/conf/datapaths/` - Data path configurations for different environments: + * `cannon_v5gl.yaml` - Paths for V5GL data on Cannon cluster + * `cannon_v6gl.yaml` - Paths for V6GL data on Cannon cluster + * `datapaths.yaml` - Template configuration +* `/conf/shapefiles/shapefiles.yaml` - Shapefile metadata including: + * Available years for each polygon type + * ID column names (`idvar`) + * File naming prefixes + * Download URLs (optional, via `url_map`) +* `/conf/satellite_pm25/` - PM2.5 dataset configurations for different versions +* `/conf/snakemake.yaml` - Default parameters for Snakemake workflow + +## Shapefile Configuration + +Shapefiles can be obtained in two ways: + +1. **Symlinks to existing Lab shapefiles** (recommended for Cannon cluster): + ```bash + python src/create_datapaths.py + ``` + This creates symbolic links from the project's `data/` directory to the Lab's existing shapefile repository at `/n/dominici_lab/lab/lego/geoboundaries/`. + +2. **Direct download** from Census Bureau (optional): + Shapefiles can be downloaded automatically if URLs are configured in `conf/shapefiles/shapefiles.yaml`. The download script will use the `url_map` for the specified year. + +The pipeline uses backward compatibility for shapefiles - if PM2.5 data is from a year without an exact shapefile match, it automatically selects the most recent prior shapefile year available. --- @@ -75,17 +138,37 @@ mamba activate ## Input and output paths -Run +The pipeline requires setting up directory paths and symbolic links to data sources. Run: + +```bash +python src/create_datapaths.py +``` + +This script: +* Creates the base directory structure under `data/V5GL/` (or `data/V6GL/` depending on configuration) +* Creates symbolic links to: + * PM2.5 raw data at `/n/dominici_lab/lab/lego/environmental/pm25__randall/` + * Shapefiles at `/n/dominici_lab/lab/lego/geoboundaries/us_geoboundaries__census/us_shapefile__census/` + * Output directories for aggregated results + +To use a different configuration, specify the datapaths config file: ```bash -python utils/create_dir_paths.py +python src/create_datapaths.py datapaths=cannon_v6gl ``` ## Pipeline -You can run the pipeline steps manually or run the snakemake pipeline described in the Snakefile. +The pipeline consists of four main steps: + +1. **Download/link shapefiles**: Obtain or link to US Census shapefiles (counties, ZCTAs, or census tracts) +2. **Download PM2.5 data**: Download satellite PM2.5 NetCDF files from Washington University +3. **Aggregate PM2.5**: Perform spatial aggregation from raster grid to polygons +4. **Concatenate monthly files** (monthly frequency only): Combine monthly parquet files into yearly files + +You can run the pipeline steps manually or use the Snakemake workflow. -**run pipeline steps manually** +### Run pipeline steps manually ```bash python src/download_shapefile.py @@ -98,7 +181,7 @@ python src/aggregate_pm25.py or run the pipeline: ```bash -snakemake --cores 4 -C polygon_name=county temporal_freq=annual +snakemake --cores 4 -C polygon_name=county temporal_freq=yearly ``` Modify `cores`, `polygon_name` and `temporal_freq` as you find convenient. @@ -115,7 +198,7 @@ mkdir /satellite_pm25_raster2polygon ```bash docker pull nsaph/satellite_pm25_raster2polygon -docker run -v :/app/data/input/satellite_pm25/annual /satellite_pm25_raster2polygon/:/app/data/output/satellite_pm25_raster2polygon nsaph/satellite_pm25_raster2polygon +docker run -v :/app/data/input/satellite_pm25/yearly /satellite_pm25_raster2polygon/:/app/data/output/satellite_pm25_raster2polygon nsaph/satellite_pm25_raster2polygon ``` If you are interested in storing the input raw and intermediate data run diff --git a/Snakefile b/Snakefile index df4aa44..f5ac6d6 100644 --- a/Snakefile +++ b/Snakefile @@ -17,37 +17,32 @@ temporal_freq = config['temporal_freq'] polygon_name = config['polygon_name'] with initialize(version_base=None, config_path="conf"): - hydra_cfg = compose(config_name="config", overrides=[f"temporal_freq={temporal_freq}", f"polygon_name={polygon_name}"]) + cfg = compose(config_name="config", overrides=[f"temporal_freq={temporal_freq}", f"polygon_name={polygon_name}"]) -satellite_pm25_cfg = hydra_cfg.satellite_pm25 -shapefiles_cfg = hydra_cfg.shapefiles +satellite_pm25_cfg = cfg.satellite_pm25 +shapefiles_cfg = cfg.shapefiles -shapefile_years_list = list(shapefiles_cfg[polygon_name].keys()) +shapefile_years_list = shapefiles_cfg[polygon_name].years months_list = "01" if temporal_freq == 'yearly' else [str(i).zfill(2) for i in range(1, 12 + 1)] -years_list = list(range(1998, 2022 + 1)) +years_list = list(range(1998, 2023 + 1)) # == Define rules == rule all: input: expand( - f"data/output/pm25__washu/{polygon_name}_{temporal_freq}/pm25__washu__{polygon_name}_{temporal_freq}__" + - ("{year}.parquet" if temporal_freq == 'yearly' else "{year}_{month}.parquet"), - year=years_list, - month=months_list + f"{cfg.datapaths.base_path}/output/{polygon_name}_{temporal_freq}/pm25__randall__{polygon_name}_{temporal_freq}__" + + "{year}.parquet", + year=years_list + ) if temporal_freq == 'yearly' else expand( + f"{cfg.datapaths.base_path}/output/{polygon_name}_monthly/pm25__randall__{polygon_name}_monthly__" + "{year}.parquet", + year=years_list ) -# remove and use symlink to the us census geoboundaries -rule download_shapefiles: - output: - f"data/input/shapefiles/shapefile_{polygon_name}_" + "{shapefile_year}/shapefile.shp" - shell: - f"python src/download_shapefile.py polygon_name={polygon_name} " + "shapefile_year={wildcards.shapefile_year}" - rule download_satellite_pm25: output: expand( - f"data/input/pm25__washu__raw/{temporal_freq}/{satellite_pm25_cfg[temporal_freq]['file_prefix']}." + + f"{cfg.datapaths.base_path}/input/raw/{temporal_freq}/{satellite_pm25_cfg[temporal_freq]['file_prefix']}." + ("{year}01-{year}12.nc" if temporal_freq == 'yearly' else "{year}{month}-{year}{month}.nc"), year=years_list, month=months_list) @@ -58,23 +53,24 @@ rule download_satellite_pm25: def get_shapefile_input(wildcards): shapefile_year = available_shapefile_year(int(wildcards.year), shapefile_years_list) - return f"data/input/shapefiles/shapefile_{polygon_name}_{shapefile_year}/shapefile.shp" + shapefile_prefix = shapefiles_cfg[polygon_name].prefix + shapefile_name = f"{shapefile_prefix}{shapefile_year}" + return f"{cfg.datapaths.base_path}/input/shapefiles/{polygon_name}_yearly/{shapefile_name}/{shapefile_name}.shp" rule aggregate_pm25: input: get_shapefile_input, expand( - f"data/input/pm25__washu__raw/{temporal_freq}/{satellite_pm25_cfg[temporal_freq]['file_prefix']}." + + f"{cfg.datapaths.base_path}/input/raw/{temporal_freq}/{satellite_pm25_cfg[temporal_freq]['file_prefix']}." + ("{{year}}01-{{year}}12.nc" if temporal_freq == 'yearly' else "{{year}}{month}-{{year}}{month}.nc"), month=months_list ) output: - expand( - f"data/output/pm25__washu/{polygon_name}_{temporal_freq}/pm25__washu__{polygon_name}_{temporal_freq}__" + - ("{{year}}.parquet" if temporal_freq == 'yearly' else "{{year}}_{month}.parquet"), - month=months_list # we only want to expand months_list and keep year as wildcard - ) + [f"{cfg.datapaths.base_path}/output/{polygon_name}_{temporal_freq}/pm25__randall__{polygon_name}_{temporal_freq}__{{year}}.parquet"] if temporal_freq == 'yearly' else [ + f"{cfg.datapaths.base_path}/intermediate/{polygon_name}_{temporal_freq}/pm25__randall__{polygon_name}_{temporal_freq}__{{year}}_{month}.parquet" + for month in months_list + ] log: f"logs/satellite_pm25_{polygon_name}_{{year}}.log" shell: @@ -83,3 +79,22 @@ rule aggregate_pm25: ("year={wildcards.year}" if temporal_freq == 'yearly' else "year={wildcards.year}") + " &> {log}" ) + +rule concat_monthly: + # This rule is only needed when temporal_freq is 'monthly' to create yearly files + # Combines monthly parquet files from intermediate directory into a single yearly parquet file + input: + lambda wildcards: expand( + f"{cfg.datapaths.base_path}/intermediate/{polygon_name}_monthly/pm25__randall__{polygon_name}_monthly__" + wildcards.year + "_{month}.parquet", + month=[str(i).zfill(2) for i in range(1, 12 + 1)] + ) + output: + yearly_file=f"{cfg.datapaths.base_path}/output/{polygon_name}_monthly/pm25__randall__{polygon_name}_monthly__" + "{year}.parquet" + log: + f"logs/concat_monthly_{polygon_name}_" + "{year}.log" + shell: + f"PYTHONPATH=. python src/concat_monthly.py polygon_name={polygon_name} " + "year={wildcards.year} &> {log}" + + + + diff --git a/conf/config.yaml b/conf/config.yaml index c3c4483..3c502cd 100644 --- a/conf/config.yaml +++ b/conf/config.yaml @@ -1,15 +1,15 @@ defaults: - _self_ - - datapaths: cannon_datapaths + - datapaths: cannon_v5gl - shapefiles: shapefiles - - satellite_pm25: us_pm25 + - satellite_pm25: V5GL0502.HybridPM25c_0p05.NorthAmerica # == aggregation args temporal_freq: yearly # yearly, monthly to be matched with cfg.satellite_pm25 year: 2020 # == shapefile download args -polygon_name: zcta # zcta, county to be matched with cfg.shapefiles +polygon_name: county # zcta, county to be matched with cfg.shapefiles shapefile_year: 2020 #to be matched with cfg.shapefiles show_progress: false diff --git a/conf/datapaths/cannon_datapaths.yaml b/conf/datapaths/cannon_datapaths.yaml deleted file mode 100644 index c41a3fe..0000000 --- a/conf/datapaths/cannon_datapaths.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# if files are stored within the local copy of the repository, then use null: -input: - pm25__washu__raw: - yearly: /n/netscratch/dominici_lab/Lab/pm25__washu__raw/yearly/ #/n/dominici_lab/lab/lego/environmnetal/pm25__washu/raw/annual - monthly: /n/netscratch/dominici_lab/Lab/pm25__washu__raw/monthly/ #/n/dominici_lab/lab/lego/environmnetal/pm25__washu/raw/monthly - shapefiles: null - -output: - pm25__washu: - zcta_yearly: /n/dominici_lab/lab/lego/environmental/pm25__washu/zcta_yearly - zcta_monthly: /n/dominici_lab/lab/lego/environmental/pm25__washu/zcta_monthly - county_yearly: /n/dominici_lab/lab/lego/environmental/pm25__washu/county_yearly - county_monthly: /n/dominici_lab/lab/lego/environmental/pm25__washu/county_monthly diff --git a/conf/datapaths/cannon_v5gl.yaml b/conf/datapaths/cannon_v5gl.yaml new file mode 100644 index 0000000..3df4114 --- /dev/null +++ b/conf/datapaths/cannon_v5gl.yaml @@ -0,0 +1,20 @@ +base_path: data/V5GL + +dirs: + input: + raw: + yearly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V5GL/raw/yearly #/n/netscratch/dominici_lab/Lab/pm25__randall__raw/yearly + monthly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V5GL/raw/monthly #/n/netscratch/dominici_lab/Lab/pm25__randall__raw/monthly + shapefiles: + county_yearly: /n/dominici_lab/lab/lego/geoboundaries/us_geoboundaries__census/us_shapefile__census/county_yearly + zcta_yearly: /n/dominici_lab/lab/lego/geoboundaries/us_geoboundaries__census/us_shapefile__census/zcta_yearly + + intermediate: + zcta_monthly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V5GL/intermediate/zcta_monthly + county_monthly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V5GL/intermediate/county_monthly + + output: + zcta_yearly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V5GL/zcta_yearly + zcta_monthly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V5GL/zcta_monthly + county_yearly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V5GL/county_yearly + county_monthly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V5GL/county_monthly diff --git a/conf/datapaths/cannon_v6gl.yaml b/conf/datapaths/cannon_v6gl.yaml new file mode 100644 index 0000000..313913c --- /dev/null +++ b/conf/datapaths/cannon_v6gl.yaml @@ -0,0 +1,18 @@ +base_path: data/V6GL + +dirs: + input: + raw: + yearly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V6GL/raw/yearly #/n/netscratch/dominici_lab/Lab/pm25__randall__raw/yearly + monthly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V6GL/raw/monthly #/n/netscratch/dominici_lab/Lab/pm25__randall__raw/monthly + shapefiles: + county_yearly: /n/dominici_lab/lab/lego/geoboundaries/us_geoboundaries__census/us_shapefile__census/county_yearly + zcta_yearly: /n/dominici_lab/lab/lego/geoboundaries/us_geoboundaries__census/us_shapefile__census/zcta_yearly + intermediate: + zcta_monthly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V6GL/intermediate/zcta_monthly + county_monthly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V6GL/intermediate/county_monthly + output: + zcta_yearly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V6GL/zcta_yearly + zcta_monthly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V6GL/zcta_monthly + county_yearly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V6GL/county_yearly + county_monthly: /n/dominici_lab/lab/lego/environmental/pm25__randall/V6GL/county_monthly \ No newline at end of file diff --git a/conf/datapaths/datapaths.yaml b/conf/datapaths/datapaths.yaml index e9d1be7..b732073 100644 --- a/conf/datapaths/datapaths.yaml +++ b/conf/datapaths/datapaths.yaml @@ -1,12 +1,16 @@ -# if files are stored within the local copy of the repository, then use null: -input: - pm25__washu__raw: - yearly: null - monthly: null - shapefiles: null +base_path: data/V6GL -output: - pm25__washu: +dirs: + input: + raw: + yearly: null + monthly: null + shapefiles: null + + intermediate: + zcta_monthly: null + county_monthly: null + output: zcta_yearly: null zcta_monthly: null county_yearly: null diff --git a/conf/satellite_pm25/us_pm25.yaml b/conf/satellite_pm25/V5GL04.HybridPM25c_0p10.NorthAmerica.yaml similarity index 100% rename from conf/satellite_pm25/us_pm25.yaml rename to conf/satellite_pm25/V5GL04.HybridPM25c_0p10.NorthAmerica.yaml diff --git a/conf/satellite_pm25/V5GL0502.HybridPM25c_0p05.NorthAmerica.yaml b/conf/satellite_pm25/V5GL0502.HybridPM25c_0p05.NorthAmerica.yaml new file mode 100644 index 0000000..94ab872 --- /dev/null +++ b/conf/satellite_pm25/V5GL0502.HybridPM25c_0p05.NorthAmerica.yaml @@ -0,0 +1,19 @@ +yearly: + url: https://wustl.app.box.com/v/ACAG-V5GL0502-GWRPM25c0p05/folder/293383209520 + + zipname: Annual + + file_prefix: "V5GL0502.HybridPM25c_0p05.NorthAmerica" + #file name convention is V5GL0502.HybridPM25c_0p05.NorthAmerica.yyyymm-yyyymm.nc + +monthly: + url: https://wustl.app.box.com/v/ACAG-V5GL0502-GWRPM25c0p05/folder/293385030318 + + zipname: Monthly + + file_prefix: "V5GL0502.HybridPM25c_0p05.NorthAmerica" + #file name convention is V5GL0502.HybridPM25c_0p05.NorthAmerica.yyyymm-yyyymm.nc + +layer: "GWRPM25" #geographic weighted regression PM2.5 +latitude_layer: "lat" +longitude_layer: "lon" diff --git a/conf/satellite_pm25/V6GL02.04.CNNPM25.0p10.NA.yaml b/conf/satellite_pm25/V6GL02.04.CNNPM25.0p10.NA.yaml new file mode 100644 index 0000000..926f845 --- /dev/null +++ b/conf/satellite_pm25/V6GL02.04.CNNPM25.0p10.NA.yaml @@ -0,0 +1,18 @@ +yearly: + url: https://wustl.app.box.com/s/s7eiaxytjr9w1z7glat45cesitcemprv/folder/327763225614 + + zipname: Annual + + file_prefix: "V6GL02.04.CNNPM25.0p10.NA" + #file name convention is V6GL02.04.CNNPM25.0p10.NA.yyyymm-yyyymm.nc + +monthly: + url: https://wustl.app.box.com/s/s7eiaxytjr9w1z7glat45cesitcemprv/folder/327764742544 + zipname: Monthly + + file_prefix: "V6GL02.04.CNNPM25.0p10.NA" + #file name convention is V6GL02.04.CNNPM25.0p10.NA.yyyymm-yyyymm.nc + +layer: "PM25" #CNN PM2.5 +latitude_layer: "lat" +longitude_layer: "lon" diff --git a/conf/satellite_pm25/global_pm25.yaml b/conf/satellite_pm25/global_pm25.yaml deleted file mode 100644 index 617e89a..0000000 --- a/conf/satellite_pm25/global_pm25.yaml +++ /dev/null @@ -1,14 +0,0 @@ -yearly: #ACAG-V5GL04-GWRPM25 has lower resolution, ACAG-V5GL04-GWRPM25c0p10 has higher resolution - url: https://wustl.app.box.com/v/ACAG-V5GL04-GWRPM25c0p10/folder/237172480358 - - zipname: Annual - - file_prefix: "V5GL04.HybridPM25c_0p10.Global" - #file name convention is V5GL04.HybridPM25.NorthAmerica.yyyymm-yyyymm.nc - # e.g. V5GL04.HybridPM25.NorthAmerica.201801-201812.nc is the file for 2018 and NorthAmerica - # "V5GL04.HybridPM25.NorthAmerica" #lower resolution - # "V5GL04.HybridPM25c_0p10.NorthAmerica" #higher resolution - -layer: "GWRPM25" #geographic weighted regression PM2.5 -latitude_layer: "lat" -longitude_layer: "lon" \ No newline at end of file diff --git a/conf/shapefiles/shapefiles.yaml b/conf/shapefiles/shapefiles.yaml index 420e8b4..42bcf4f 100644 --- a/conf/shapefiles/shapefiles.yaml +++ b/conf/shapefiles/shapefiles.yaml @@ -1,66 +1,38 @@ -# cfg.shapefile_year and cfg.polygon_name are used to match the shapefile to dowload +# Shapefile metadata configuration +# Each polygon type contains: +# - years: list of available shapefile years +# - idvar: ID column name in the shapefile +# - prefix: shapefile directory/file naming prefix census_tract: - 2020: - url: https://www2.census.gov/geo/tiger/GENZ2020/shp/cb_2020_us_tract_500k.zip - idvar: GEOID - 2021: - url: https://www2.census.gov/geo/tiger/GENZ2021/shp/cb_2021_us_tract_500k.zip - idvar: GEOID - 2022: - url: https://www2.census.gov/geo/tiger/GENZ2022/shp/cb_2022_us_tract_500k.zip - idvar: GEOID - + years: + - 2020 + - 2021 + - 2022 + idvar: "GEOID" + prefix: "cb_us_tract_" + county: # County shapefiles (cartographic boundaries) https://www.census.gov/programs-surveys/geography/guidance/tiger-data-products-guide.html - 2013: - url: https://www2.census.gov/geo/tiger/GENZ2013/cb_2013_us_county_500k.zip # readme https://www2.census.gov/geo/tiger/GENZ2013/2013_file_name_def.pdf - idvar: GEOID - 2014: - url: https://www2.census.gov/geo/tiger/GENZ2014/shp/cb_2014_us_county_500k.zip - idvar: GEOID - 2015: - url: https://www2.census.gov/geo/tiger/GENZ2015/shp/cb_2015_us_county_500k.zip - idvar: GEOID - 2016: - url: https://www2.census.gov/geo/tiger/GENZ2016/shp/cb_2016_us_county_500k.zip - idvar: GEOID - 2017: - url: https://www2.census.gov/geo/tiger/GENZ2017/shp/cb_2017_us_county_500k.zip - idvar: GEOID - 2018: - url: https://www2.census.gov/geo/tiger/GENZ2018/shp/cb_2018_us_county_500k.zip - idvar: GEOID - 2019: - url: https://www2.census.gov/geo/tiger/GENZ2019/shp/cb_2019_us_county_500k.zip - idvar: GEOID - 2020: - url: https://www2.census.gov/geo/tiger/GENZ2020/shp/cb_2020_us_county_500k.zip - idvar: GEOID - 2021: - url: https://www2.census.gov/geo/tiger/GENZ2021/shp/cb_2021_us_county_500k.zip - idvar: GEOID - 2022: - url: https://www2.census.gov/geo/tiger/GENZ2022/shp/cb_2022_us_county_500k.zip - idvar: GEOID - -zcta: # ZCTA shapefiles (cartographic boudaries) https://www.census.gov/programs-surveys/geography/guidance/tiger-data-products-guide.html - 2000: - url: https://www2.census.gov/geo/tiger/GENZ2010/gz_2010_us_860_00_500k.zip # readme https://www2.census.gov/geo/tiger/GENZ2010/ReadMe.pdf - idvar: ZCTA5 - 2010: - url: https://www2.census.gov/geo/tiger/GENZ2019/shp/cb_2019_us_zcta510_500k.zip # latest ZCTA5CE10 available - idvar: ZCTA5CE10 - 2020: - url: https://www2.census.gov/geo/tiger/GENZ2020/shp/cb_2020_us_zcta520_500k.zip # latest ZCTA5CE20 available (to date) - idvar: ZCTA5CE20 - -# zcta: # ZCTA shapefiles (tiger line) https://www.census.gov/programs-surveys/geography/guidance/tiger-data-products-guide.html -# 2000: -# url: https://www2.census.gov/geo/tiger/TIGER2010/ZCTA5/2000/tl_2010_us_zcta500.zip # latest ZCTA5CE00 available -# idvar: ZCTA5CE00 -# 2010: -# url: https://www2.census.gov/geo/tiger/TIGER2020/ZCTA5/tl_2020_us_zcta510.zip # latest ZCTA5CE10 available -# idvar: ZCTA5CE10 -# 2020: -# url: https://www2.census.gov/geo/tiger/TIGER2022/ZCTA520/tl_2022_us_zcta520.zip # latest ZCTA5CE20 available (to date) -# idvar: ZCTA5CE20 + years: + - 2013 + - 2014 + - 2015 + - 2016 + - 2017 + - 2018 + - 2019 + - 2020 + - 2021 + - 2022 + - 2023 + idvar: "county" + prefix: "us_shapefile__census__county_yearly__" + +zcta: # ZCTA shapefiles (cartographic boundaries) https://www.census.gov/programs-surveys/geography/guidance/tiger-data-products-guide.html + years: + - 2000 + - 2010 + - 2020 + idvar: "zcta" + prefix: "us_shapefile__census__zcta_yearly__" + \ No newline at end of file diff --git a/requirements.yaml b/environment.yaml similarity index 78% rename from requirements.yaml rename to environment.yaml index 6f0ee4d..596e80a 100644 --- a/requirements.yaml +++ b/environment.yaml @@ -1,4 +1,4 @@ -name: satellite_pm25_raster2polygon +name: pm25_randall channels: - conda-forge - defaults @@ -19,6 +19,3 @@ dependencies: - selenium==4.29.0 - chromedriver-binary==135.0.7030.0.0 - tqdm==4.67.1 - - torch==2.6.0 - - torchaudio==2.6.0 - - torchvision==0.21.0 diff --git a/fasrc_jobs/county_monthly.sbatch b/fasrc_jobs/county_monthly.sbatch deleted file mode 100644 index 2f47486..0000000 --- a/fasrc_jobs/county_monthly.sbatch +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -# -#SBATCH -p serial_requeue # partition (queue) -#SBATCH -c 16 # number of cores -#SBATCH --mem 96GB # memory -#SBATCH -t 0-02:00 # time (D-HH:MM) - -singularity exec $HOME/singularity_images/satellite_pm25_raster2polygon_latest.sif snakemake --cores 16 -C polygon_name=county temporal_freq=monthly diff --git a/fasrc_jobs/zcta_monthly.sbatch b/fasrc_jobs/zcta_monthly.sbatch deleted file mode 100644 index 38c04a4..0000000 --- a/fasrc_jobs/zcta_monthly.sbatch +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -# -#SBATCH -p shared # partition (queue) -#SBATCH -c 32 # number of cores -#SBATCH --mem 96GB # memory -#SBATCH -t 0-01:00 # time (D-HH:MM) - -singularity exec $HOME/singularity_images/satellite_pm25_raster2polygon_latest.sif snakemake --cores 16 -C polygon_name=zcta temporal_freq=monthly diff --git a/fasrc_jobs/README.md b/jobs/README.md similarity index 100% rename from fasrc_jobs/README.md rename to jobs/README.md diff --git a/jobs/county_monthly.sbatch b/jobs/county_monthly.sbatch new file mode 100644 index 0000000..4e28318 --- /dev/null +++ b/jobs/county_monthly.sbatch @@ -0,0 +1,10 @@ +#!/bin/bash +# +#SBATCH -p serial_requeue # partition (queue) +#SBATCH -c 16 # number of cores +#SBATCH --mem 96GB # memory +#SBATCH -t 0-02:00 # time (D-HH:MM) + +#singularity exec $HOME/singularity_images/satellite_pm25_raster2polygon_latest.sif snakemake --cores 16 -C polygon_name=county temporal_freq=monthly + +snakemake --cores 16 -C polygon_name=county temporal_freq=monthly diff --git a/jobs/v5gl.sbatch b/jobs/v5gl.sbatch new file mode 100644 index 0000000..8bb89cc --- /dev/null +++ b/jobs/v5gl.sbatch @@ -0,0 +1,13 @@ +#!/bin/bash +# +#SBATCH -p serial_requeue # partition (queue) +#SBATCH -c 48 # number of cores +#SBATCH --mem 184GB # memory +#SBATCH -t 0-12:00 # time (D-HH:MM) + +#singularity exec $HOME/singularity_images/satellite_pm25_raster2polygon_latest.sif snakemake --cores 16 -C polygon_name=county temporal_freq=monthly + +snakemake --cores 24 -C polygon_name=county temporal_freq=yearly +snakemake --cores 24 -C polygon_name=county temporal_freq=monthly +snakemake --cores 24 -C polygon_name=zcta temporal_freq=yearly +snakemake --cores 24 -C polygon_name=zcta temporal_freq=monthly diff --git a/jobs/zcta_monthly.sbatch b/jobs/zcta_monthly.sbatch new file mode 100644 index 0000000..2f1d953 --- /dev/null +++ b/jobs/zcta_monthly.sbatch @@ -0,0 +1,11 @@ +#!/bin/bash +# +#SBATCH -p shared # partition (queue) +#SBATCH -c 32 # number of cores +#SBATCH --mem 96GB # memory +#SBATCH -t 0-01:00 # time (D-HH:MM) + +#singularity exec $HOME/singularity_images/satellite_pm25_raster2polygon_latest.sif snakemake --cores 16 -C polygon_name=zcta temporal_freq=monthly + +snakemake --cores 32 -C polygon_name=zcta temporal_freq=yearly + diff --git a/jobs/zcta_yearly.sbatch b/jobs/zcta_yearly.sbatch new file mode 100644 index 0000000..9d94155 --- /dev/null +++ b/jobs/zcta_yearly.sbatch @@ -0,0 +1,10 @@ +#!/bin/bash +# +#SBATCH -p shared # partition (queue) +#SBATCH -c 32 # number of cores +#SBATCH --mem 96GB # memory +#SBATCH -t 0-01:00 # time (D-HH:MM) + +#singularity exec $HOME/singularity_images/satellite_pm25_raster2polygon_latest.sif snakemake --cores 16 -C polygon_name=zcta temporal_freq=monthly + +snakemake --cores 32 -C polygon_name=zcta temporal_freq=yearly diff --git a/notes/eda_input.ipynb b/notes/eda_input.ipynb index 9bb877c..55a1ae2 100644 --- a/notes/eda_input.ipynb +++ b/notes/eda_input.ipynb @@ -19,7 +19,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -38,7 +38,7 @@ ], "source": [ "# Open the netCDF file\n", - "file_path = \"../data/input/satellite_pm25/annual/V5GL04.HybridPM25c_0p10.NorthAmerica.202201-202212.nc\"\n", + "file_path = f\"../{cfg.datapaths.base_path}/input/satellite_pm25/yearly/V5GL04.HybridPM25c_0p10.NorthAmerica.202201-202212.nc\"\n", "dataset = netCDF4.Dataset(file_path)\n", "\n", "# Print the global attributes\n", @@ -183,7 +183,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -210,7 +210,7 @@ "import matplotlib.pyplot as plt\n", "\n", "# Open the netCDF file\n", - "file_path = \"data/V5GL04.HybridPM25.NorthAmerica.202201-202212.nc\"\n", + "file_path = f\"{cfg.datapaths.base_path}/V5GL04.HybridPM25.NorthAmerica.202201-202212.nc\"\n", "dataset = netCDF4.Dataset(file_path)\n", "\n", "# Get the latitude and longitude variables\n", @@ -260,7 +260,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -280,12 +280,12 @@ "import matplotlib.pyplot as plt\n", "\n", "# Read the CSV file\n", - "pm25_data = pd.read_csv('data/county_pm25.csv')\n", + "pm25_data = pd.read_csv(f'{cfg.datapaths.base_path}/county_pm25.csv')\n", "# Convert GEOID to string using trailing zeros\n", "pm25_data['GEOID'] = pm25_data['GEOID'].astype(str).str.zfill(5)\n", "\n", "# Read the shapefile\n", - "shapefile = gpd.read_file('data/shapefile_cb_county_2015/shapefile.shp')\n", + "shapefile = gpd.read_file(f\"{cfg.datapaths.base_path}/shapefile_cb_county_2015/shapefile.shp\")\n", "\n", "# Merge the data\n", "merged_data = shapefile.merge(pm25_data, on='GEOID', how='left')\n", diff --git a/notes/eda_output.ipynb b/notes/eda_output.ipynb index 9ab63ca..dcc5f7e 100644 --- a/notes/eda_output.ipynb +++ b/notes/eda_output.ipynb @@ -19,7 +19,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -47,7 +47,7 @@ ], "source": [ "# Open the netCDF file\n", - "file_path = \"data/V5GL04.HybridPM25.NorthAmerica.202201-202212.nc\"\n", + "file_path = f\"{cfg.datapaths.base_path}/input/satellite_pm25/yearly/V5GL04.HybridPM25c_0p10.NorthAmerica.202201-202212.nc\"\n", "dataset = netCDF4.Dataset(file_path)\n", "\n", "# Print the global attributes\n", @@ -192,7 +192,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -219,7 +219,7 @@ "import matplotlib.pyplot as plt\n", "\n", "# Open the netCDF file\n", - "file_path = \"data/V5GL04.HybridPM25.NorthAmerica.202201-202212.nc\"\n", + "file_path = f\"{cfg.datapaths.base_path}/input/satellite_pm25/yearly/V5GL04.HybridPM25c_0p10.NorthAmerica.202201-202212.nc\"\n", "dataset = netCDF4.Dataset(file_path)\n", "\n", "# Get the latitude and longitude variables\n", @@ -269,7 +269,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -279,7 +279,7 @@ "import pyarrow.parquet as pq\n", "\n", "# Read parquet file with pm25 at county level for 2015\n", - "pm25_data = pq.read_table(\"data/output/satellite_pm25_raster2polygon/monthly/satellite_pm25_zcta_2015_01.parquet\").to_pandas()" + "pm25_data = pq.read_table(f\"{cfg.datapaths.base_path}/datapaths.base_path}/datapaths.base_path}/datapaths.base_path}/output/satellite_pm25_raster2polygon/monthly/satellite_pm25_zcta_2015_01.parquet\").to_pandas()" ] }, { @@ -368,7 +368,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -388,12 +388,12 @@ "import matplotlib.pyplot as plt\n", "\n", "# Read the CSV file\n", - "pm25_data = pd.read_csv('data/county_pm25.csv')\n", + "pm25_data = pd.read_csv(f'{cfg.datapaths.base_path}/county_pm25.csv')\n", "# Convert GEOID to string using trailing zeros\n", "pm25_data['GEOID'] = pm25_data['GEOID'].astype(str).str.zfill(5)\n", "\n", "# Read the shapefile\n", - "shapefile = gpd.read_file('data/shapefile_cb_county_2015/shapefile.shp')\n", + "shapefile = gpd.read_file('{cfg.datapaths.base_path}/datapaths.base_path}/datapaths.base_path}/shapefile_cb_county_2015/shapefile.shp')\n", "\n", "# Merge the data\n", "merged_data = shapefile.merge(pm25_data, on='GEOID', how='left')\n", diff --git a/src/aggregate_pm25.py b/src/aggregate_pm25.py index 21a7dd1..0d722af 100644 --- a/src/aggregate_pm25.py +++ b/src/aggregate_pm25.py @@ -35,13 +35,16 @@ def main(cfg): # == load shapefile LOGGER.info("Loading shapefile.") - shapefile_years_list = list(cfg.shapefiles[cfg.polygon_name].keys()) + shapefile_years_list = cfg.shapefiles[cfg.polygon_name].years #use previously available shapefile shapefile_year = available_shapefile_year(cfg.year, shapefile_years_list) + shapefile_prefix = cfg.shapefiles[cfg.polygon_name].prefix + shapefile_name = f"{shapefile_prefix}{shapefile_year}" - shape_path = f'data/input/shapefiles/shapefile_{cfg.polygon_name}_{shapefile_year}/shapefile.shp' + shape_path = f'{cfg.datapaths.base_path}/input/shapefiles/{cfg.polygon_name}_yearly/{shapefile_name}/{shapefile_name}.shp' + LOGGER.info(f"Loading shapefile from: {shape_path}") polygon = gpd.read_file(shape_path) - polygon_ids = polygon[cfg.shapefiles[cfg.polygon_name][shapefile_year].idvar].values + polygon_ids = polygon[cfg.shapefiles[cfg.polygon_name].idvar].values # == filenames to be aggregated if cfg.temporal_freq == "yearly": @@ -62,7 +65,7 @@ def main(cfg): # load the first file to obtain the affine transform/boundaries LOGGER.info("Mapping polygons to raster cells.") - ds = xarray.open_dataset(f"data/input/pm25__washu__raw/{cfg.temporal_freq}/{filenames[0]}") + ds = xarray.open_dataset(f"{cfg.datapaths.base_path}/input/raw/{cfg.temporal_freq}/{filenames[0]}") layer = getattr(ds, cfg.satellite_pm25.layer) # obtain affine transform/boundaries @@ -90,7 +93,7 @@ def main(cfg): if i > 0: # reload the file only if it is different from the first one - ds = xarray.open_dataset(f"data/input/pm25__washu__raw/{cfg.temporal_freq}/{filename}") + ds = xarray.open_dataset(f"{cfg.datapaths.base_path}/input/raw/{cfg.temporal_freq}/{filename}") layer = getattr(ds, cfg.satellite_pm25.layer) # === obtain stats quickly using precomputed mapping @@ -111,15 +114,17 @@ def main(cfg): # == save output file if cfg.temporal_freq == "yearly": # ignore month since len(filenames) == 1 - output_filename = f"pm25__washu__{cfg.polygon_name}_{cfg.temporal_freq}__{cfg.year}.parquet" + output_filename = f"pm25__randall__{cfg.polygon_name}_{cfg.temporal_freq}__{cfg.year}.parquet" + output_path = f"{cfg.datapaths.base_path}/output/{cfg.polygon_name}_{cfg.temporal_freq}/{output_filename}" elif cfg.temporal_freq == "monthly": # use month in filename since len(filenames) = 12 month = f"{i + 1:02d}" df["month"] = month - output_filename = f"pm25__washu__{cfg.polygon_name}_{cfg.temporal_freq}__{cfg.year}_{month}.parquet" + output_filename = f"pm25__randall__{cfg.polygon_name}_{cfg.temporal_freq}__{cfg.year}_{month}.parquet" + # Save monthly outputs to intermediate folder + output_path = f"{cfg.datapaths.base_path}/intermediate/{cfg.polygon_name}_{cfg.temporal_freq}/{output_filename}" - output_path = f"data/output/pm25__washu/{cfg.polygon_name}_{cfg.temporal_freq}/{output_filename}" df.to_parquet(output_path) # plot aggregation map using geopandas diff --git a/src/concat_monthly.py b/src/concat_monthly.py new file mode 100644 index 0000000..873b7cd --- /dev/null +++ b/src/concat_monthly.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +""" +Consolidate monthly PM2.5 aggregation files into yearly files. + +This script takes the monthly parquet files from the intermediate directory +and combines all 12 months for each year into a single yearly file in the output directory. + +Usage: + python src/concat_monthly.py year=2020 + + # With overrides: + python src/concat_monthly.py polygon_name=zcta year=2020 +""" + +import pandas as pd +import os +import hydra +import logging +from pathlib import Path + +logging.basicConfig(level=logging.INFO, format='[%(asctime)s][%(levelname)s] - %(message)s') +LOGGER = logging.getLogger(__name__) + + +def consolidate_year(intermediate_dir, output_dir, year, polygon_name, input_freq="monthly"): + """ + Consolidate all monthly files for a given year into a single yearly file. + Reads from intermediate directory, outputs to main output directory. + + Args: + intermediate_dir: Directory containing monthly parquet files + output_dir: Directory to save yearly parquet file + year: Year to consolidate + polygon_name: Name of polygon type (county, zcta, etc.) + input_freq: Frequency of input files (monthly) + + Returns: + List of monthly files that were consolidated + """ + monthly_files = [] + + # Pattern: pm25__randall__county_monthly__2020_01.parquet + # Collect all monthly files for this year from intermediate directory + for month in range(1, 13): + month_str = str(month).zfill(2) + filename = f"pm25__randall__{polygon_name}_{input_freq}__{year}_{month_str}.parquet" + filepath = os.path.join(intermediate_dir, filename) + + if os.path.exists(filepath): + monthly_files.append(filepath) + else: + LOGGER.warning(f"Missing file: {filepath}") + + if not monthly_files: + LOGGER.error(f"No monthly files found for year {year} in {intermediate_dir}") + raise FileNotFoundError(f"No monthly files found for year {year}") + + if len(monthly_files) != 12: + LOGGER.warning(f"Only found {len(monthly_files)}/12 months for year {year}") + + # Read and concatenate all monthly files + LOGGER.info(f"Reading {len(monthly_files)} monthly files for year {year}") + dfs = [pd.read_parquet(f) for f in monthly_files] + yearly_df = pd.concat(dfs, ignore_index=False) + + # Sort by polygon ID and month for consistent ordering + if 'month' in yearly_df.columns: + yearly_df = yearly_df.sort_values(['month']) + + # Save yearly file to output directory + # Pattern: pm25__randall__county_monthly__2020.parquet + output_file = os.path.join(output_dir, f"pm25__randall__{polygon_name}_monthly__{year}.parquet") + LOGGER.info(f"Saving consolidated file: {output_file} ({len(yearly_df)} rows)") + yearly_df.to_parquet(output_file) + + return monthly_files + + + + +@hydra.main(config_path="../conf", config_name="config", version_base=None) +def main(cfg): + """ + Consolidate monthly PM2.5 files for a specific year into a yearly file using Hydra configuration. + Reads from intermediate directory, outputs to main output directory. + """ + + polygon_name = cfg.polygon_name + year = cfg.year + + # Build paths using datapaths configuration + monthly_dir = f"{cfg.datapaths.base_path}/output/{polygon_name}_monthly" + intermediate_dir = f"{cfg.datapaths.base_path}/intermediate/{polygon_name}_monthly" + + LOGGER.info(f"Polygon name: {polygon_name}") + LOGGER.info(f"Year: {year}") + LOGGER.info(f"Input directory (intermediate): {intermediate_dir}") + LOGGER.info(f"Output directory: {monthly_dir}") + + # Process the specified year + try: + monthly_files = consolidate_year(intermediate_dir, monthly_dir, year, polygon_name) + LOGGER.info(f"Successfully consolidated {len(monthly_files)} monthly files for year {year}") + + except Exception as e: + LOGGER.error(f"Error processing year {year}: {e}") + raise + + +if __name__ == "__main__": + main() diff --git a/utils/create_dir_paths.py b/src/create_datapaths.py similarity index 77% rename from utils/create_dir_paths.py rename to src/create_datapaths.py index 696390d..a3d0d13 100644 --- a/utils/create_dir_paths.py +++ b/src/create_datapaths.py @@ -5,14 +5,31 @@ LOGGER = logging.getLogger(__name__) +def init_folder(folder_cfg=None): + folder_dict = folder_cfg.dirs + + # defines a base path for the data + datapath = folder_cfg.base_path + if datapath is None: + datapath = "data" + # check if datapath exists, if not create it + if os.path.exists(datapath): + LOGGER.info(f"Base path {datapath} already exists") + else: + LOGGER.info(f"Creating base path {datapath}") + os.makedirs(datapath, exist_ok=True) + + # create subfolders and symbolic links + create_subfolders_and_links(datapath=datapath, folder_dict=folder_dict) def create_subfolders_and_links(datapath="data", folder_dict=None): """ Recursively create subfolders and symbolic links. """ if not os.path.exists(datapath): - LOGGER.info(f"Error: {datapath} does not exists.") + LOGGER.info(f"Error: {datapath} does not exist.") return + if isinstance(folder_dict, DictConfig): for path, subfolder_dict in folder_dict.items(): sub_datapath = os.path.join(datapath, path) @@ -50,7 +67,7 @@ def create_subfolders_and_links(datapath="data", folder_dict=None): @hydra.main(config_path="../conf", config_name="config", version_base=None) def main(cfg): """Create data subfolders and symbolic links as indicated in config file.""" - create_subfolders_and_links(folder_dict=cfg.datapaths) + init_folder(folder_cfg=cfg.datapaths) if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/src/download_pm25.py b/src/download_pm25.py index a063c41..2fc0fa8 100644 --- a/src/download_pm25.py +++ b/src/download_pm25.py @@ -25,7 +25,7 @@ def main(cfg): # == setup chrome driver # Expand the tilde to the user's home directory - download_dir = f"data/input/pm25__washu__raw/" + download_dir = f"{cfg.datapaths.base_path}/input/raw/" download_dir = os.path.abspath(download_dir) download_zip = f"{download_dir}/{cfg.satellite_pm25[cfg.temporal_freq].zipname}.zip" src_dir = f"{download_dir}/{cfg.satellite_pm25[cfg.temporal_freq].zipname}" @@ -78,16 +78,20 @@ def main(cfg): with zipfile.ZipFile(download_zip, "r") as zip_ref: zip_ref.extractall(download_dir) - # Move all files from the src_dir to dest_dir + # Move all files from the src_dir to dest_dir, flattening the directory structure os.makedirs(dest_dir, exist_ok=True) - for file in os.listdir(src_dir): - shutil.move(os.path.join(src_dir, file), dest_dir) - - # Remove the zip file and the empty folder + for root, dirs, files in os.walk(src_dir): + for file in files: + src_file = os.path.join(root, file) + dest_file = os.path.join(dest_dir, file) + shutil.move(src_file, dest_file) + logger.info(f"Moved {file} to {dest_dir}") + + # Remove the zip file and the source folder (including any empty subfolders) os.remove(download_zip) - os.rmdir(src_dir) + shutil.rmtree(src_dir) - # Remove anyfile starging with Unconfirmed (this might be a Chrome bug/artifact) + # Remove any file starting with Unconfirmed (this might be a Chrome bug/artifact) for file in os.listdir(download_dir): if file.startswith("Unconfirmed"): os.remove(os.path.join(download_dir, file)) diff --git a/src/download_shapefile.py b/src/download_shapefile.py deleted file mode 100644 index 492c9ca..0000000 --- a/src/download_shapefile.py +++ /dev/null @@ -1,38 +0,0 @@ -import logging -import os -import zipfile -import hydra -import wget - -@hydra.main(config_path="../conf", config_name="config", version_base=None) -def main(cfg): - url = cfg.shapefiles[cfg.polygon_name][cfg.shapefile_year].url - - tgt = f"data/input/shapefiles/shapefile_{cfg.polygon_name}_{cfg.shapefile_year}" - - tgtdir = os.path.dirname(tgt) - tgtfile = os.path.basename(tgt) - - tgt = f"{tgtdir}/{tgtfile}" - logging.info(f"Downloading {url}") - wget.download(url, f"{tgt}.zip") - logging.info("Done.") - - # unzip with unzip library - with zipfile.ZipFile(f"{tgt}.zip", "r") as zip_ref: - zip_ref.extractall(tgt) - logging.info(f"Unzipped {tgt} with files:\n {os.listdir(tgt)}") - - # remove dirty zip file - os.remove(f"{tgt}.zip") - logging.info(f"Removed {tgt}.zip") - - logging.info(f"Rename files to shapefile.*") - files = os.listdir(tgt) - for f in files: - _, ext = os.path.splitext(f) - os.rename(f"{tgt}/{f}", f"{tgt}/shapefile{ext}") - logging.info(f"Done.") - -if __name__ == "__main__": - main()