├── Denver_Airport_Example ├── DEN_2016.PFL ├── DEN_2016.SFC ├── README.md └── run_framework_den.py ├── LICENSE ├── README.md ├── Supplemental Scripts ├── README.md └── map_plotting.py ├── aermod.exe ├── aerplot.exe ├── input_script_functions.py ├── mainframe.py ├── output_processing_functions.py └── run_framework.py /Denver_Airport_Example/README.md: -------------------------------------------------------------------------------- 1 | This is an example of the framework working with meteorological data from the 2 | denver international airport. The coordinates for aerplot also plot to the 3 | denver international airport. The framework is currently set to a grid 4 | receptor system and to run aerplot to create a contour plot of the 5 | emission concentration data over the airport. 6 | 7 | This code also contains an example of a script that randomly creates emittor 8 | source coordinates and emission rates. There is more information at the top of the 9 | 'run_framework_den.py' on how to run the example code snippet, but to activate this 10 | code snippet, simply uncomment lines 3-8 and lines 209-235. 11 | 12 | You will need to add the following files to the folder with the 13 | run_framework_den.py file and the '.pfl' and '.sfc' files: 14 | aermod.exe, 15 | aerplot.exe, 16 | mainframe.py, 17 | output_processing_functions.py, 18 | input_script_functions.py 19 | 20 | 21 | -------------------------------------------------------------------------------- /Denver_Airport_Example/run_framework_den.py: -------------------------------------------------------------------------------- 1 | from mainframe import run_aermod_framework 2 | # # imports for randomly assigning source data example 3 | # from random import random 4 | # from random import seed 5 | # from math import pi 6 | # from math import cos 7 | # from math import sin 8 | # from time import time 9 | __author__ = 'Max' 10 | 11 | # DENVER INTERNATIONAL AIRPORT EXAMPLE PROGRAM 12 | # The meteorological data in this script comes from the Denver Airport weather station 13 | # There is currently a grid system setup to show functionality of aerplot plotting. 14 | 15 | # @@@ RANDOM SOURCE COORDINATE AND EMISSION MAKER @@@ 16 | # to activate this section of code, uncomment lines 3-8 and lines 209-235 17 | # this randomly creates source emittors in a circular radius around the origin, with a specified emission range 18 | # parameters for setting up this section start at line 209 19 | 20 | # run_framework.py is the interface for the framework, and calls the run_framework function that runs mainframe 21 | # mainframe.py is the framework, it checks inputs, writes the input files, runs AERMOD and processes the outputs 22 | # input_script_functions contains all functions to write AERMOD/AERPLOT input files and check inputs 23 | # output_processing_functions contains all functions to process AERMOD output files 24 | # more details about output processing are presented below, refer to the receptor_style and run_aerplot variables 25 | 26 | # the overview of the framework is easily allowing one to add emission sources in discrete locations and assigning 27 | # them emission rates, which AERMOD will use to predict concentrations at locations also specified by the user. This 28 | # framework also processes the output data, currently set to hourly averages for a year of data, into a spreadsheet 29 | # to easily be used to create graphs, analyze maxima or anything else. The output spreadsheet is currently configured 30 | # to neatly display the time information and yearly average information. If wanted, one can easily go into excel and 31 | # delete all the columns that don't contain emission concentration data. 32 | # concentration data that aermod produces is in the form *****micrograms per meter cubed****** 33 | 34 | # meteorological data comes from AERMET processor 35 | # AERMET will be needed to obtain meteorological data used for AERMOD 36 | # processing meteorological data with AERMET is the *only* requirement to run this framework 37 | # AERMOD is currently set to calculate all 1-hour concentration averages for a given year of meteorological data 38 | # To change this, go to the writing control and output lines functions in input_script_functions.py 39 | 40 | # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 41 | # @@@@@@@@@@@@ INPUT OPTIONS @@@@@@@@@@@@@@ 42 | # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 43 | 44 | # Name of the surface observations file 45 | # Mandatory, file type: SFC 46 | # Enter as string data type 47 | surface_observations_file = 'DEN_2016.SFC' 48 | 49 | # Name of the upper air observations file 50 | # Mandatory, file type: PFL 51 | # Enter as string data type 52 | upper_air_data_file = 'DEN_2016.PFL' 53 | 54 | # list of pollutant source points coordinates in meters 55 | # INPUT LIST OF X COORDINATES IN source_x_points AND Y COORDINATES IN source_y_points 56 | # INDEXES WILL CORRESPOND TO EACH OTHER IN THE TWO LISTS 57 | # LISTS MUST BE MATCHING LENGTH 58 | source_coordinate_list_x = [100, 200, -1032.2, -370.6] 59 | source_coordinate_list_y = [100, -489, 55.6, -622.2] 60 | 61 | # list of pollutant source release heights in meters 62 | # can be a single data point in which case the value will be applied to EVERY pollutant source 63 | # if manually entering each height, must be the exact same length as the source_x_points/source_y_points 64 | # each height will be associated to the source with the same index as in the coordinate list 65 | source_release_height_list = [2] 66 | 67 | # list of emission rates that will correspond to the source points list 68 | # can be a single data point in which case the value will be applied to EVERY pollutant source 69 | # if manually entering each rate, must be the exact same length as the source_x_points/source_y_points 70 | # each rate will be associated to the source with the same index as in the coordinate list 71 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 72 | # $$**** IF USING POINT SOURCE, WILL BE INTERPRETED IN GRAMS PER SECOND ***********************$$ 73 | # $$**** IF USING AREA SOURCE, WILL BE INTERPRETED IN GRAMS PER SECOND PER METER SQUARED ******$$ 74 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 75 | source_emission_rate_list = [.6232, .2132, 1.004, .1401] 76 | 77 | # determines what pollutant source type you want 78 | # data required for simulation depends on what type is chosen 79 | # if point is chosen, all 'source_point_...' data is needed 80 | # if area is chosen, all 'source_area_...' data is needed 81 | # MUST BE EITHER 'point' OR 'area' OR FRAMEWORK WILL NOT WORK 82 | # Enter as string data type 83 | source_type = 'point' 84 | 85 | # area source dimensions of area source polluters, in meters 86 | # this will determine the size of the pollutant source and the emission rate, 87 | # since emissions for area sources are in g/s/m^2, 88 | # ONLY NEEDED IF USING AREA SOURCE, IF NOT NEEDED SET EQUAL TO 'None' 89 | # can be a single data point in which case the value will be applied to EVERY pollutant source 90 | # if manually entering each value, must be the exact same length as the source_x_points/source_y_points 91 | # each value will be associated to the source with the same index as in the coordinate list 92 | source_area_x_direction_length_list = [15.6] 93 | source_area_y_direction_length_list = [18.3] 94 | 95 | # temperature of gas from the point source as it exits 96 | # UNITS ARE IN ***KELVIN***, ENTER 0 TO JUST USE AMBIENT TEMPERATURE 97 | # ONLY NEEDED IF USING POINT SOURCE, IF NOT NEEDED SET EQUAL TO 'None' 98 | # can be a single data point in which case the value will be applied to EVERY pollutant source 99 | # if manually entering each value, must be the exact same length as the source_x_points/source_y_points 100 | # each value will be associated to the source with the same index as in the coordinate list 101 | source_point_stack_gas_exit_temperature_list = [0] 102 | 103 | # velocity of gas from the point source as it exits 104 | # units are in meters/second 105 | # ONLY NEEDED IF USING POINT SOURCE, IF NOT NEEDED SET EQUAL TO 'None' 106 | # can be a single data point in which case the value will be applied to EVERY pollutant source 107 | # if manually entering each value, must be the exact same length as the source_x_points/source_y_points 108 | # each value will be associated to the source with the same index as in the coordinate list 109 | source_point_stack_gas_exit_velocity_list = [20.1] 110 | 111 | # diameter of pollutant stack from point source emittor 112 | # units are in meters 113 | # ONLY NEEDED IF USING POINT SOURCE, IF NOT NEEDED SET EQUAL TO 'None' 114 | # can be a single data point in which case the value will be applied to EVERY pollutant source 115 | # if manually entering each value, must be the exact same length as the source_x_points/source_y_points 116 | # each value will be associated to the source with the same index as in the coordinate list 117 | source_point_stack_inside_diameter_list = [1.2] 118 | 119 | # the year the meteorological data starts 120 | # Should reflect meteorological data 121 | met_data_start_year = '2016' 122 | 123 | # there are two options for receptor styles 124 | # the first is discrete in which you put in the coordinates you want AERMOD to calculate concentrations at 125 | # the second is grid in which AERMOD calculates concentrations in a full grid 126 | # grid simulations can take a lot longer since there are much more receptor locations 127 | # either enter 'discrete' or 'grid' 128 | # for discrete you only have to enter the receptor_coordinate_list_... variables 129 | # for grid you have to enter all receptor_grid_... variables 130 | # EXTRACTING CONCENTRATION DATA TO EXCEL SPREADSHEET ONLY WORKS WITH DISCRETE COORDINATES 131 | receptor_style = 'grid' 132 | 133 | # coordinate list of discrete receptors in METERS 134 | # enter as many as you want, enter as lists of numbers 135 | # length of each list must match 136 | # the index of each coordinate in each list will correspond to the index of the coordinate in the other list 137 | # if using grid receptors, enter as None 138 | receptor_coordinate_list_x = [0, 70.3, 225.5, -405.6, -334.1] 139 | receptor_coordinate_list_y = [0, 101.1, -299.1, 368.7, -545.2] 140 | 141 | # the starting coordinate for the x and y receptors 142 | # the first receptor will be at this location, followed by as many receptors specified at the spacing distance 143 | # entered in meters 144 | # hint: enter negative coordinate so receptors form a grid around the origin 145 | receptor_grid_starting_point_x = -3000 146 | receptor_grid_starting_point_y = -3000 147 | 148 | # the number of receptors in the x and y location 149 | # this will determine the size of the grid since there will be as many receptors as specified, 150 | # seperated by the spacing distance 151 | receptor_grid_number_receptors_x = 21 152 | receptor_grid_number_receptors_y = 21 153 | 154 | # this determines the spacing distance between receptors in the x and y direction 155 | # the distance between each receptor multiplied by the number of receptors minus one in each direction (x/y), 156 | # will determine the size of the grid 157 | # units are in meters 158 | # for example, starting point -1000, number receptors = 21, grid_spacing = 100, the x length of the grid is 2000 159 | receptor_grid_spacing_x = 300 160 | receptor_grid_spacing_y = 300 161 | 162 | # the base elevation for the region in the study 163 | # data is MANDATORY by AERMOD 164 | # units are in METERS 165 | base_elevation = '1418.6' 166 | 167 | # the station number that the upper air data was collected at 168 | # data is MANDATORY by AERMOD 169 | uair_data_station_number = '72469' 170 | 171 | # the station number that the surface data observations were collected at 172 | # data is MANDATORY by AERMOD 173 | surf_data_station_number = '725650' 174 | 175 | # if you ran an AERMAP simulation for this scenario 176 | # enter the names of the source and receptor files that AERMAP outputted 177 | # these files should be in a txt format 178 | # example; receptor_aermap_output_file_name='aermap_receptor.txt' 179 | # if AERMAP was not used set both variables to None and the program will run fine 180 | receptor_aermap_output_file_name = None, 181 | source_aermap_output_file_name = None, 182 | 183 | # if you want to run AERPLOT to make contour plots of the results 184 | # all you need is information about the location of the area of interest 185 | 186 | # for running aerplot, set run_aerplot to 'yes' or 'no' 187 | run_aerplot = 'yes' 188 | 189 | # the northing and easting **UTM** coordinates of the location of interest 190 | # if you don't care about the location that the plot is overlayed in on google earth 191 | # you can enter 0 for the northing and easting and just see the contour plot on the ocean 192 | # enter the coordinates, or set them to None if not running aerplot 193 | aerplot_northing = '4409278.1' 194 | aerplot_easting = '529264' 195 | 196 | # the utm zone for the location of interest 197 | # set to None if not using aerplot 198 | aerplot_UTM_zone = '13' 199 | 200 | # if using aerplot, define if in the northern hemisphere or not 201 | # **ENTER ARGUMENT AS A STRING** 202 | # if not using aerplot, set as None. Otherwise, set *ONLY* to 'True' or 'False' with quotations 203 | aerplot_northern_hemisphere = 'True' 204 | 205 | 206 | #################################################################################### 207 | ####### RANDOMLY ASSIGNING SOURCE COORDINATES AND EMISSION RATES EXAMPLES ########## 208 | #################################################################################### 209 | # # @@@@@@@@@@@@@ UNCOMMENT THE SECTION STARTING HERE @@@@@@@@@@@@@@@@@@@@@@@@@@@ 210 | # # parameters 211 | # # assigns coordinates in a circle around the origin with radius of maximum_range 212 | # # @@@ PARAMETERS LIST @@@ 213 | # # the total number of emittor sources 214 | # number_sources = 10 # enter integer 215 | # # the radius around the origin that sources will appear in 216 | # maximum_range = 5280 * .3048 # a mile in meters 217 | # # the range of emissions that will be randomly assigned to each source 218 | # source_emission_rate_range = [.1, 1.3] # entered as [lower bound, upper bound] 219 | # 220 | # # setup - clearing previous source coordinates and emission entries 221 | # source_coordinate_list_y = [] 222 | # source_coordinate_list_x = [] 223 | # source_emission_rate_list = [] 224 | # emission_rate_range = source_emission_rate_range[1] - source_emission_rate_range[0] 225 | # seed(time()) 226 | # for coord in range(number_sources): 227 | # theta = 2 * pi * random() 228 | # radius = random() * maximum_range 229 | # x_coord_temp = cos(theta) * radius 230 | # y_coord_temp = sin(theta) * radius 231 | # source_coordinate_list_x.append(x_coord_temp) 232 | # source_coordinate_list_y.append(y_coord_temp) 233 | # # randomly assigning emission rates in specified range 234 | # emission_rate_val_temp = emission_rate_range * random() + source_emission_rate_range[0] 235 | # source_emission_rate_list.append(emission_rate_val_temp) 236 | 237 | # # @@@@@@@@@@@@@@ STOP UNCOMMENTING HERE @@@@@@@@@@@@@@@@@@@ 238 | # 239 | # # print(source_coordinate_list_x) 240 | # # print(source_coordinate_list_y) 241 | # # print(source_emission_rate_list) 242 | 243 | 244 | #################################################################################### 245 | #################################################################################### 246 | #################################################################################### 247 | ################################### FUNCTION CALL ################################## 248 | #################################################################################### 249 | #################################################################################### 250 | #################################################################################### 251 | 252 | run_aermod_framework(surface_observations_file=surface_observations_file, 253 | upper_air_data_file=upper_air_data_file, 254 | source_coordinate_list_x=source_coordinate_list_x, 255 | source_coordinate_list_y=source_coordinate_list_y, 256 | source_release_height_list=source_release_height_list, 257 | source_emission_rate_list=source_emission_rate_list, 258 | source_type=source_type, 259 | source_area_x_direction_length_list=source_area_x_direction_length_list, 260 | source_area_y_direction_length_list=source_area_y_direction_length_list, 261 | source_point_stack_gas_exit_temperature_list=source_point_stack_gas_exit_temperature_list, 262 | source_point_stack_gas_exit_velocity_list=source_point_stack_gas_exit_velocity_list, 263 | source_point_stack_inside_diameter_list=source_point_stack_inside_diameter_list, 264 | met_data_start_year=met_data_start_year, 265 | receptor_style=receptor_style, 266 | receptor_coordinate_list_x=receptor_coordinate_list_x, 267 | receptor_coordinate_list_y=receptor_coordinate_list_y, 268 | receptor_grid_starting_point_x=receptor_grid_starting_point_x, 269 | receptor_grid_starting_point_y=receptor_grid_starting_point_y, 270 | receptor_grid_number_receptors_x=receptor_grid_number_receptors_x, 271 | receptor_grid_number_receptors_y=receptor_grid_number_receptors_y, 272 | receptor_grid_spacing_x=receptor_grid_spacing_x, 273 | receptor_grid_spacing_y=receptor_grid_spacing_y, 274 | base_elevation=base_elevation, 275 | uair_data_station_number=uair_data_station_number, 276 | surf_data_station_number=surf_data_station_number, 277 | receptor_aermap_output_file_name=receptor_aermap_output_file_name, 278 | source_aermap_output_file_name=source_aermap_output_file_name, 279 | run_aerplot=run_aerplot, 280 | aerplot_northing=aerplot_northing, 281 | aerplot_easting=aerplot_easting, 282 | aerplot_UTM_zone=aerplot_UTM_zone, 283 | aerplot_northern_hemisphere=aerplot_northern_hemisphere 284 | ) 285 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Max Nyffenegger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AERMOD_Framework 2 | This framework is designed to help people who are doing air quality analysis 3 | simulations to simply set parameters to write the inputs, run, and process 4 | the outputs of AERMOD. 5 | 6 | OPERATING SYSTEM: Windows (versions of AERPLOT and AERMOD needed for other OS) 7 | 8 | PYTHON VERSION: Python 3.x (should work with 2.7 also) 9 | 10 | DEPENDENCIES: openpyxl 11 | 12 | The requirements to run this framework is that you need to have meteorological data 13 | already processed by AERMET. Once you have processed AERMET data, this framework is 14 | fully ready to be used. AERMAP isn't required to run this framework, but the program 15 | can accept AERMAP data and use it in AERMOD runs if specified. 16 | 17 | run_framework.py contains in depth detail about the parameters used to run the framework, 18 | and should be used as a reference for how the framework works and for more information 19 | about the outputs. 20 | 21 | Denver_Airport_Example contains a fully running example of the framework that is ready 22 | to run as is. There is more information in the README in the Denver_Airport_Example 23 | folder. 24 | 25 | Authors: 26 | Max Nyffenegger, 27 | Matthew Alongi, 28 | Joseph Kasprzyk. 29 | 30 | AERMOD.exe version: 18081 31 | 32 | AERPLOT.exe version: 13329 33 | 34 | AERMOD and AERPLOT are published by the U.S. Environmental Protection Agency and are publicly 35 | available to download on their website (epa.gov). 36 | -------------------------------------------------------------------------------- /Supplemental Scripts/README.md: -------------------------------------------------------------------------------- 1 | This folder contains supplemental scripts. 2 | 3 | There is more information about how to run each script at the top of the 4 | script itself, but in general you need to import the function from the script 5 | in mainframe.py and then add a function call to the script's function at the 6 | bottom of mainframe.py's run_aermod_framework() function. 7 | 8 | map_plotting.py: 9 | 10 | DEPENDENCIES: openpyxl, matplotlib, numpy 11 | 12 | map_plotting creates a heatmap of AERMOD concentration data from the Excel 13 | spreadsheet that this framework creates. There are instructions on how to 14 | run the script in map_plotting.py 15 | 16 | Some code in this script comes from the matplotlib website, which I am not claiming is mine. 17 | You can view the code at the link below. 18 | Title: Creating annotated heatmaps, 19 | Availability: https://matplotlib.org/3.1.1/gallery/images_contours_and_fields/image_annotated_heatmap.html#sphx-glr-gallery-images-contours-and-fields-image-annotated-heatmap-py 20 | -------------------------------------------------------------------------------- /Supplemental Scripts/map_plotting.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import matplotlib 3 | from openpyxl import load_workbook 4 | import numpy as np 5 | 6 | # this script creates a 2d heat map of emission concentration data based on AERMOD outputs *only* using a 7 | # GRID RECEPTOR SYSTEM, will not work for discrete receptors 8 | # MAKE SURE IN run_framework.py THE RECEPTOR SYSTEM IS SET TO GRID --- WILL NOT WORK OTHERWISE 9 | # you can change the colormap of the heatmap in line 198, by changing the 'cmap=' variable 10 | 11 | # to add to the aermod framework mainframe complete the following instructions: 12 | # 1) add the following import statement to the very top of mainframe.py 13 | """ 14 | from map_plotting import create_concentration_map 15 | """ 16 | # 2) add the following function call statement to the very bottom of run_aermod_framework() in mainframe.py 17 | # 2) *** You can change the names of the files to whatever you want*** 18 | """ 19 | create_concentration_map(spreadsheet_name, "concentration heatmap") 20 | """ 21 | 22 | # if you want to just run this script on a set of excel data that you already have, simply add the line of 23 | # code above, the code from section '2)' to the very bottom of this script and change spreadsheet_name 24 | # to the actual name of the spreadsheet data, for example "AERMOD concentration output.xlsx" 25 | 26 | """ 27 | heatmap and heatmap_annotations functions are not mine 28 | code comes from matplotlib website, I am not claiming this code is mine 29 | Title: Creating annotated heatmaps 30 | Availability: https://matplotlib.org/3.1.1/gallery/images_contours_and_fields/image_annotated_heatmap.html#sphx-glr-gallery-images-contours-and-fields-image-annotated-heatmap-py 31 | """ 32 | 33 | 34 | def heatmap(data, row_labels, col_labels, ax=None, 35 | cbar_kw={}, cbarlabel="", **kwargs): 36 | """ 37 | Create a heatmap from a numpy array and two lists of labels. 38 | 39 | Parameters 40 | ---------- 41 | data 42 | A 2D numpy array of shape (N, M). 43 | row_labels 44 | A list or array of length N with the labels for the rows. 45 | col_labels 46 | A list or array of length M with the labels for the columns. 47 | ax 48 | A `matplotlib.axes.Axes` instance to which the heatmap is plotted. If 49 | not provided, use current axes or create a new one. Optional. 50 | cbar_kw 51 | A dictionary with arguments to `matplotlib.Figure.colorbar`. Optional. 52 | cbarlabel 53 | The label for the colorbar. Optional. 54 | **kwargs 55 | All other arguments are forwarded to `imshow`. 56 | """ 57 | 58 | if not ax: 59 | ax = plt.gca() 60 | 61 | # Plot the heatmap 62 | im = ax.imshow(data, **kwargs) 63 | 64 | # Create colorbar 65 | cbar = ax.figure.colorbar(im, ax=ax, **cbar_kw) 66 | cbar.ax.set_ylabel(cbarlabel, rotation=-90, va="bottom", fontsize=18) 67 | 68 | # We want to show all ticks... 69 | ax.set_xticks(np.arange(data.shape[1])) 70 | ax.set_yticks(np.arange(data.shape[0])) 71 | # ... and label them with the respective list entries. 72 | ax.set_xticklabels(col_labels) 73 | ax.set_yticklabels(row_labels) 74 | 75 | # Let the horizontal axes labeling appear on top. 76 | ax.tick_params(top=False, bottom=True, 77 | labeltop=False, labelbottom=True) 78 | 79 | # Rotate the tick labels and set their alignment. 80 | plt.setp(ax.get_xticklabels(), rotation=-30, ha="right", 81 | rotation_mode="anchor") 82 | plt.xticks(ha='left') 83 | # Turn spines off and create white grid. 84 | for edge, spine in ax.spines.items(): 85 | spine.set_visible(False) 86 | 87 | ax.set_xticks(np.arange(data.shape[1]+1)-.5, minor=True) 88 | ax.set_yticks(np.arange(data.shape[0]+1)-.5, minor=True) 89 | ax.grid(which="minor", color="w", linestyle='-', linewidth=1) 90 | ax.tick_params(which="minor", bottom=False, left=False) 91 | 92 | return im, cbar 93 | 94 | 95 | def annotate_heatmap(im, data=None, valfmt="{x:.2f}", 96 | textcolors=["black", "white"], 97 | threshold=None, **textkw): 98 | """ 99 | A function to annotate a heatmap. 100 | 101 | Parameters 102 | ---------- 103 | im 104 | The AxesImage to be labeled. 105 | data 106 | Data used to annotate. If None, the image's data is used. Optional. 107 | valfmt 108 | The format of the annotations inside the heatmap. This should either 109 | use the string format method, e.g. "$ {x:.2f}", or be a 110 | `matplotlib.ticker.Formatter`. Optional. 111 | textcolors 112 | A list or array of two color specifications. The first is used for 113 | values below a threshold, the second for those above. Optional. 114 | threshold 115 | Value in data units according to which the colors from textcolors are 116 | applied. If None (the default) uses the middle of the colormap as 117 | separation. Optional. 118 | **kwargs 119 | All other arguments are forwarded to each call to `text` used to create 120 | the text labels. 121 | """ 122 | 123 | if not isinstance(data, (list, np.ndarray)): 124 | data = im.get_array() 125 | 126 | # Normalize the threshold to the images color range. 127 | if threshold is not None: 128 | threshold = im.norm(threshold) 129 | else: 130 | threshold = im.norm(data.max())/2. 131 | 132 | # Set default alignment to center, but allow it to be 133 | # overwritten by textkw. 134 | kw = dict(horizontalalignment="center", 135 | verticalalignment="center") 136 | kw.update(textkw) 137 | 138 | # Get the formatter in case a string is supplied 139 | if isinstance(valfmt, str): 140 | valfmt = matplotlib.ticker.StrMethodFormatter(valfmt) 141 | 142 | # Loop over the data and create a `Text` for each "pixel". 143 | # Change the text's color depending on the data. 144 | texts = [] 145 | for i in range(data.shape[0]): 146 | for j in range(data.shape[1]): 147 | kw.update(color=textcolors[int(im.norm(data[i, j]) > threshold)]) 148 | text = im.axes.text(j, i, valfmt(data[i, j], None), **kw) 149 | texts.append(text) 150 | 151 | return texts 152 | 153 | 154 | # this function creates a heatmap from an excel spreadsheet with the following assumptions 155 | # the spreadsheet is a complete rectangle of data 156 | # the top row contains x-axis headers for the x location of the data below it 157 | # the furthest left column contains y-axis headers for the y location of the data to the right 158 | # the following is an example of the format that a 3x4 spreadsheet should be in 159 | # this is the format AERMOD concentration outputs would be in, notice x axis is increasing and y axis is decreasing 160 | # | |-1| 0| 1| 2| 161 | # | 1| @| @| @| @| 162 | # | 0| @| @| @| @| 163 | # |-1| @| @| @| @| 164 | def create_concentration_map(spreadsheet_file_name, figure_file_name): 165 | # this is so the figure won't be shown, only saved as a file 166 | matplotlib.use('agg') 167 | # setup 168 | workbook = load_workbook(spreadsheet_file_name) 169 | worksheet = workbook.active 170 | number_rows = worksheet.max_row 171 | number_cols = worksheet.max_column 172 | number_y_data = number_rows - 1 173 | number_x_data = number_cols - 1 174 | spreadsheet_data = [] 175 | header_list_x = [] 176 | header_list_y = [] 177 | 178 | # for loop puts all data into one 2d array 179 | # starts at 2 to *NOT* include header data 180 | # since openpyxl starts at 1, not 0, have to go one extra in range of data 181 | # when adding data, we subtract two to start the data in the '0' column 182 | for row in range(2, number_rows+1): 183 | spreadsheet_data.append([]) 184 | for col in range(2, number_cols+1): 185 | spreadsheet_data[row - 2].append(worksheet.cell(row=row, column=col).value) 186 | 187 | # creating lists of x and y headers 188 | for row in range(2, number_rows+1): 189 | header_list_y.append(worksheet.cell(row=row,column=1).value) 190 | for col in range(2, number_cols+1): 191 | header_list_x.append(worksheet.cell(row=1,column=col).value) 192 | 193 | # turning data into numpy array 194 | spreadsheet_data_numpy = np.array(spreadsheet_data) 195 | 196 | # creating heatmap 197 | fig, ax = plt.subplots() 198 | im, cbar = heatmap(data=spreadsheet_data_numpy, row_labels=header_list_y, col_labels=header_list_x, 199 | ax=ax, cbarlabel="Emissions Concentration (micrograms/meters$^3$)", cmap='RdBu') 200 | 201 | # # uncomment the following line if you want to see individual concentration data on each grid data point 202 | # annotate_heatmap(im,data=spreadsheet_data_numpy, valfmt="{x:.1f}") 203 | 204 | # title, labels, saving formatting settings 205 | plt.title("Concentration Heat Map", fontsize=24) 206 | fig.canvas.set_window_title("Concentration Heat Map") 207 | plt.xlabel("X Axis Receptor Locations", fontsize=18) 208 | plt.ylabel("Y Axis Receptor Locations", fontsize=18) 209 | x_size_inches = number_x_data*2/3 # can easily change if too small/big 210 | y_size_inches = number_y_data*2/3 211 | plt.gcf().set_size_inches(x_size_inches, y_size_inches) 212 | plt.savefig(figure_file_name+'.png') 213 | 214 | # # show plot if wanted, just saves as png by default 215 | # plt.show() 216 | 217 | # # print data for debugging 218 | # print(header_list_x) 219 | # for row in range(number_y_data): 220 | # print(str(header_list_y[row])+str(spreadsheet_data[row])) 221 | 222 | 223 | -------------------------------------------------------------------------------- /aermod.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxnyf/AERMOD_Framework/d050d427da9f7a37864bfd55da13869d57485854/aermod.exe -------------------------------------------------------------------------------- /aerplot.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxnyf/AERMOD_Framework/d050d427da9f7a37864bfd55da13869d57485854/aerplot.exe -------------------------------------------------------------------------------- /input_script_functions.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Max' 2 | 3 | 4 | # this function writes the initial setting lines for AERMOD 5 | # currently setup to do a yearly average 6 | def write_control_lines(input_file): 7 | #'''Writing COntrol Pathway Lines''' 8 | # The only things that might change between runs is the pollutant ID or possible adding 'FLAT' to the MODELOPT kewyword 9 | title = 'INPUT FILE' 10 | input_file.write('CO STARTING\n TITLEONE ' + title + '\n MODELOPT DFAULT CONC\n') 11 | # Pathway start, title, and model options 12 | input_file.write(' AVERTIME 1 ANNUAL\n') 13 | # Average time periods 14 | ID = 'POLLUTANT' 15 | input_file.write(' POLLUTID ' + ID + '\n') 16 | # Pollutant ID 17 | input_file.write(' ERRORFIL ERRORS.AER\n') 18 | input_file.write(' RUNORNOT RUN\nCO FINISHED\n\n') 19 | # Error file name, run specification, pathway ending 20 | 21 | 22 | # this function writes lines to establish the receptors for AERMOD 23 | # this function works only with discrete receptor coordinates 24 | # all parameters are defined in run_framework.py 25 | def write_receptor_lines_discrete(disccart_coordinate_list_x,disccart_coordinate_list_y,aermap_receptor_output,input_file): 26 | input_file.write('RE STARTING\n') 27 | # Extracting the 'DISCCART' keyword lines from the AERMAP receptor output if available 28 | if aermap_receptor_output: 29 | aermap_receptor_output_lines = [] 30 | 31 | # Copying the lines we need from the aermap output file 32 | for line in open(aermap_receptor_output): 33 | if '**' not in line: 34 | aermap_receptor_output_lines.append(line) 35 | del aermap_receptor_output_lines[0] 36 | 37 | # Writing these lines to the input file 38 | for receptor_line in aermap_receptor_output_lines: 39 | input_file.write(receptor_line) 40 | 41 | # Writing discrete cartesian points if no AERMAP output is available 42 | else: 43 | for coordinate in range(len(disccart_coordinate_list_x)): 44 | disccart_x_coord = str(disccart_coordinate_list_x[coordinate]) 45 | disccart_y_coord = str(disccart_coordinate_list_y[coordinate]) 46 | input_file.write(' DISCCART ' + disccart_x_coord + ' ' + disccart_y_coord + '\n') 47 | 48 | input_file.write('RE FINISHED\n\n') 49 | # Pathway ending 50 | 51 | 52 | # this function writes the input section for a grid receptor system 53 | # inputs are defined in run_framework.py 54 | def write_receptor_lines_grid(receptor_grid_starting_point_x, 55 | receptor_grid_starting_point_y, 56 | receptor_grid_number_receptors_x, 57 | receptor_grid_number_receptors_y, 58 | receptor_grid_spacing_x, 59 | receptor_grid_spacing_y, 60 | input_file): 61 | input_file.write('RE STARTING\n') 62 | # Pathway start 63 | receptor_network_name = 'NETWORK' 64 | # Grid receptor network creation start 65 | input_file.write(' GRIDCART ' + receptor_network_name + ' STA\n ') 66 | input_file.write( 67 | 'GRIDCART ' + str(receptor_network_name) + ' XYINC ' + str(receptor_grid_starting_point_x) + ' ' 68 | + str(receptor_grid_number_receptors_x) + ' ' + str(receptor_grid_spacing_x) + ' ' 69 | + str(receptor_grid_starting_point_y) + ' ' + str(receptor_grid_number_receptors_y) + ' ' 70 | + str(receptor_grid_spacing_y) + '\n ') 71 | input_file.write('GRIDCART ' + receptor_network_name + ' END\n') 72 | # Receptor network ending 73 | input_file.write('RE FINISHED\n\n') 74 | 75 | 76 | # this function writes area source emitter lines for AERMOD 77 | # all parameters are defined in run_framework.py 78 | def write_source_location_lines(source_x_points, source_y_points, source_x_coordinates_for_naming, 79 | source_y_coordinates_for_naming, source_type, aermap_source_output, input_file): 80 | # Writing SOurce Pathway Location Lines 81 | input_file.write('SO STARTING\n') 82 | # Pathway start 83 | 84 | # Extracting the 'location' keyword lines from the AERMAP source output if available 85 | if aermap_source_output: 86 | aermap_source_output_lines = [] 87 | 88 | for line in open(aermap_source_output): 89 | if '**' not in line: 90 | aermap_source_output_lines.append(line) 91 | del aermap_source_output_lines[0] 92 | 93 | for receptor_line in aermap_source_output_lines: 94 | input_file.write(receptor_line) 95 | 96 | else: 97 | for point in range(len(source_x_points)): 98 | source_name = str(source_x_coordinates_for_naming[point]) + str(source_y_coordinates_for_naming[point]) 99 | # Name of pollutant source 100 | if source_type == 'point': 101 | # Type of pollutant source (point, volume, area, etc.) 102 | source_type_for_file = 'POINT' 103 | if source_type == 'area': 104 | source_type_for_file = 'AREA' 105 | source_x_coord = str(source_x_points[point]) 106 | source_y_coord = str(source_y_points[point]) 107 | source_z_coord = '0' 108 | # Coordinates of source 109 | input_file.write( 110 | ' LOCATION ' + source_name + ' ' + source_type_for_file + ' ' + source_x_coord + ' ' + source_y_coord + ' ' + source_z_coord + '\n') 111 | # LOCATION keyword line 112 | 113 | 114 | # this function writes the source params for area source emitters for AERMOD 115 | # all parameters are defined in run_framework.py 116 | def write_source_data_area_lines(source_x_coordinates_for_naming, source_y_coordinates_for_naming, 117 | source_emission_rate_list, source_release_height_list, 118 | source_area_x_direction_length_list, source_area_y_direction_length_list, 119 | input_file): 120 | for value in range(len(source_x_coordinates_for_naming)): 121 | source_name = str(source_x_coordinates_for_naming[value]) + str(source_y_coordinates_for_naming[value]) 122 | source_emission_rate = source_emission_rate_list[value] 123 | # Source emission rate (g/(s-m^2)) 124 | source_height = source_release_height_list[value] 125 | # Source release height above ground 126 | x_direction_length = source_area_x_direction_length_list[value] 127 | # Length of side of area source in x-direction (m) 128 | y_direction_length = source_area_y_direction_length_list[value] 129 | input_file.write(' SRCPARAM '+source_name+' '+str(source_emission_rate)+' '+str(source_height)+' '+str(x_direction_length)+' '+str(y_direction_length)+'\n') 130 | 131 | input_file.write(' SRCGROUP ALL\nSO FINISHED\n\n') 132 | # Source group name and pathway ending 133 | 134 | 135 | # this function writes the source params for point source emitters for AERMOD 136 | # all parameters are defined in run_framework.py 137 | def write_source_data_point_lines(source_x_coordinates_for_naming, source_y_coordinates_for_naming, 138 | source_emission_rate_list, source_height_list, 139 | stack_gas_exit_temperature_list, stack_gas_exit_velocity_list, 140 | stack_inside_diameter_list, input_file): 141 | for value in range(len(source_x_coordinates_for_naming)): 142 | # BPIP Input 143 | source_name = str(source_x_coordinates_for_naming[value]) + str(source_y_coordinates_for_naming[value]) 144 | source_emission_rate = source_emission_rate_list[value] 145 | # Source emission rate (g/s) 146 | source_height = source_height_list[value] 147 | # Source physical release height above ground 148 | stack_gas_exit_temperature = stack_gas_exit_temperature_list[value] 149 | # Source stack gas exit temperature 150 | stack_gas_exit_velocity = stack_gas_exit_velocity_list[value] 151 | # Source stack gas exit velocity 152 | stack_inside_diameter = stack_inside_diameter_list[value] 153 | # Source stack inside diameter 154 | input_file.write( 155 | ' SRCPARAM ' + source_name + ' ' + str(source_emission_rate) + ' ' + str(source_height) + ' ' + str( 156 | stack_gas_exit_temperature) + ' ' + str(stack_gas_exit_velocity) + ' ' + str( 157 | stack_inside_diameter) + '\n') 158 | input_file.write(' SRCGROUP ALL\nSO FINISHED\n\n') 159 | 160 | 161 | # this function writes the meteorological data settings for the AERMOD input file 162 | # all parameters are defined in run_framework.py 163 | def write_met_data_lines(surface_observations_file, upper_air_data_file, 164 | met_data_start_year,uairdata_station_number,surfdata_station_number,base_elevation, input_file): 165 | # '''Writing MEteorological Data pathway lines''' 166 | # These lines will vary depending on meteorological data 167 | surffile = surface_observations_file 168 | # Surface data file, must end with .SFC 169 | proffile = upper_air_data_file 170 | # Profile data file, must end with .PFL 171 | input_file.write('ME STARTING\n SURFFILE ' + surffile + '\n PROFFILE ' + proffile + '\n ') 172 | # Pathway start, specification of MET Data files 173 | surfdata_year = met_data_start_year 174 | input_file.write('SURFDATA ' + surfdata_station_number + ' ' + surfdata_year + '\n ') 175 | # Surface data details 176 | uairdata_year = met_data_start_year 177 | input_file.write('UAIRDATA ' + uairdata_station_number + ' ' + uairdata_year + '\n ') 178 | # Upper air data details 179 | input_file.write('PROFBASE ' + str(base_elevation) + '\nME FINISHED\n\n') 180 | # Base elevation of temperature profile and pathway ending 181 | 182 | 183 | # this function writes the output settings for the AERMOD input file 184 | # if you want to change what information aermod displays do it here 185 | # currently configured to just printing hourly averages for a year 186 | def write_output_option_lines(run_aerplot, input_file): 187 | input_file.write('OU STARTING\n') 188 | input_file.write(' DAYTABLE ALLAVE\n') 189 | if run_aerplot == 'yes': 190 | input_file.write(' PLOTFILE ANNUAL ALL aerplot_data.PLT\n') 191 | input_file.write('OU FINISHED') 192 | 193 | 194 | # this function is used to make source inputs the correct length if only one data point is entered 195 | def check_source_data_for_length(input, number_terms): 196 | if input is None: 197 | return None 198 | elif type(input) == list: 199 | if len(input) == 1: 200 | return copy_number_to_list_length(input[0], number_terms) 201 | elif len(input) == number_terms: 202 | return input 203 | else: 204 | return 'error' 205 | elif type(input) == int or type(input) == float: 206 | return copy_number_to_list_length(input, number_terms) 207 | else: 208 | return 'error' 209 | 210 | 211 | # this function is used to copy a single data point to a list for a specific number 212 | def copy_number_to_list_length(data_value, number_terms): 213 | data_list = [] 214 | for i in range(number_terms): 215 | data_list.append(data_value) 216 | return data_list 217 | 218 | 219 | # this function checks the inputs to see if they were inputted correctly 220 | # returns true if all data is correct, false if there is a problem with the data 221 | # all parameters are defined in run_framework.py 222 | def check_for_valid_inputs(source_x_points, 223 | source_y_points, 224 | source_type, 225 | source_emission_rate_list, 226 | source_release_height_list, 227 | source_area_x_direction_length_list, 228 | source_area_y_direction_length_list, 229 | source_point_stack_gas_exit_temperature_list, 230 | source_point_stack_gas_exit_velocity_list, 231 | source_point_stack_inside_diameter_list, 232 | receptor_coordinate_list_y, 233 | receptor_coordinate_list_x, 234 | receptor_style 235 | ): 236 | 237 | number_errors = 0 238 | # checking if number of x and y coordinates for sources match 239 | if len(source_x_points) != len(source_y_points): 240 | print("x and y source coordinate lists don't have matching lengths") 241 | number_errors += 1 242 | 243 | if source_type != 'point' and source_type != 'area': 244 | number_errors += 1 245 | print("source_type is not 'area' or 'point'") 246 | 247 | # if the following data is a string it was inputted correctly via check_source_data_for_length(...) function 248 | 249 | if type(source_emission_rate_list) == str: 250 | number_errors += 1 251 | print("source_emission_rate_list inputted incorrectly") 252 | 253 | if type(source_release_height_list) == str: 254 | number_errors += 1 255 | print("source_release_height_list inputted incorrectly") 256 | 257 | if source_type == 'area': 258 | 259 | if type(source_area_x_direction_length_list) == str or source_area_x_direction_length_list is None: 260 | number_errors += 1 261 | print("source_area_x_direction_length_list inputted incorrectly") 262 | 263 | if type(source_area_y_direction_length_list) == str or source_area_y_direction_length_list is None: 264 | number_errors += 1 265 | print("source_area_y_direction_length_list inputted incorrectly") 266 | 267 | if source_type == 'point': 268 | 269 | if type(source_point_stack_gas_exit_temperature_list) == str or source_point_stack_gas_exit_temperature_list\ 270 | is None: 271 | number_errors += 1 272 | print("source_point_stack_gas_exit_temperature_list inputted incorrectly") 273 | 274 | if type(source_point_stack_gas_exit_velocity_list) == str or source_point_stack_gas_exit_velocity_list is None: 275 | number_errors += 1 276 | print("source_point_stack_gas_exit_velocity_list inputted incorrectly") 277 | 278 | if type(source_point_stack_inside_diameter_list) == str or source_point_stack_inside_diameter_list is None: 279 | number_errors += 1 280 | print("source_point_stack_inside_diameter_list inputted incorrectly") 281 | if receptor_style == 'discrete': 282 | if len(receptor_coordinate_list_y) != len(receptor_coordinate_list_x): 283 | number_errors += 1 284 | print("number of receptor y coordinates does not equal number of receptor x coordinates") 285 | 286 | # Returns false if there are any errors, otherwise true 287 | if number_errors > 0: 288 | return False 289 | else: 290 | return True 291 | 292 | 293 | # this function writes an aerplot input file 294 | def write_aerplot_input_file(easting, northing, utm_zone, northern_hemisphere, google_earth_display_name, 295 | plot_file_name, output_file_name): 296 | '''Write aerplot.inp file''' 297 | # Create file 298 | input_file = open('aerplot.inp', 'w') 299 | 300 | # Version line and other comment information 301 | # Do not edit the version line 302 | input_file.write('version=1\n; This line must be the first non-comment line\n\n; Altitude options\n') 303 | 304 | # Altitude choice 305 | # Comment out one of the following two lines within the write command 306 | # To do so add a ';' directly after the ' in the write command 307 | # This makes it easier to remember the options when looking at the input file later on 308 | # The first is relative to ground and the second is height above sea level 309 | input_file.write('altitudeChoice=relativeToGround\n') 310 | input_file.write(';altitudeChoice=absolute\n') 311 | 312 | # The height can offset from the height indicated in the .plt file from AERMOD 313 | input_file.write('altitude=0\n\n') 314 | 315 | # Input utm zone and utm coordinates of location to display on Google Earth 316 | input_file.write('; UTM Location Information\n') 317 | input_file.write('easting='+str(easting)+'\n') 318 | input_file.write('northing='+str(northing)+'\n') 319 | input_file.write('utmZone='+str(utm_zone)+'\n') 320 | 321 | # Specifying hemisphere 322 | # If right on the equator, set this to True 323 | input_file.write('inNorthernHemisphere='+str(northern_hemisphere)+'\n\n') 324 | # Name of kmz file in Google Earth 325 | input_file.write('; File naming\n') 326 | input_file.write('NameDisplayedInGoogleEarth=' + google_earth_display_name + '\n') 327 | 328 | # Name of plot file from AERMOD 329 | input_file.write('PlotFileName=' + plot_file_name + '\n') 330 | 331 | # Name of kmz file in file directory 332 | input_file.write('OutputFileNameBase=' + output_file_name + '\n\n') 333 | 334 | # Binning choice 335 | # The options are "Linear" and "Log" 336 | input_file.write('; Binning choice\n') 337 | input_file.write('binningChoice=Log\n\n') 338 | 339 | # Comment out one of the following lines within the write command to choose the color scheme 340 | input_file.write('; Contour display options\n') 341 | input_file.write('sIconSetChoice=redBlue\n') 342 | input_file.write(';sIconSetChoice=redGreen\n') 343 | 344 | # .7 is the recommended scale for Google Earth 345 | input_file.write('IconScale = 0.70\n') 346 | 347 | # The program will automatically pick the maximum and minimum values for the color scale if "DATA" is specified 348 | # The min and max can be manually set below if desired 349 | input_file.write('minbin=DATA\n') 350 | input_file.write('maxbin=DATA\n') 351 | 352 | # if "FALSE" Google Earth will launch after AERPLOT finishes running 353 | # if "True" it will not 354 | input_file.write('sDisableEarthBrowser = TRUE\n') 355 | 356 | # Creating contours 357 | input_file.write('makeContours = True\n') 358 | 359 | # Number of times to perform contour smoothing 360 | # One smoothing can make the contours much less chaotic 361 | # A second one can move the contours farther from their proper locations according to the receptor values 362 | # However, a setting greater than one may be beneficial when there is greater spacing between receptors 363 | input_file.write('numberOfTimesToSmoothContourSurface = 1\n') 364 | 365 | # Number of columns and rows is generally good at 400 but may need to be increased for particularly large domains 366 | input_file.write('numberOfGridCols = 400\n') 367 | input_file.write('numberOfGridRows = 400\n\n') 368 | input_file.close() 369 | 370 | 371 | # main function that writes AERMOD input file 372 | # all parameters are defined in run_framework.py 373 | # aermap files are defaulted to None since there is no aermap framework for creating and running aermap.exe 374 | # aermap will run and be used if the files are given, the inputs are configured to use aermap data if provided 375 | def write_aermod_input_file(surface_observations_file, 376 | upper_air_data_file, 377 | source_x_points, 378 | source_y_points, 379 | source_release_height_list, 380 | source_emission_rate_list, 381 | source_type, 382 | source_area_x_direction_length_list, 383 | source_area_y_direction_length_list, 384 | source_point_stack_gas_exit_temperature_list, 385 | source_point_stack_gas_exit_velocity_list, 386 | source_point_stack_inside_diameter_list, 387 | met_data_start_year, 388 | receptor_style, 389 | receptor_coordinate_list_x, 390 | receptor_coordinate_list_y, 391 | receptor_grid_starting_point_x, 392 | receptor_grid_starting_point_y, 393 | receptor_grid_number_receptors_x, 394 | receptor_grid_number_receptors_y, 395 | receptor_grid_spacing_x, 396 | receptor_grid_spacing_y, 397 | base_elevation, 398 | uair_data_station_number, 399 | surfdata_station_number, 400 | aermap_receptor_output=None, 401 | aermap_source_output=None, 402 | run_aerplot='no' 403 | ): 404 | input_file = open('aermod.inp', 'w') 405 | 406 | '''Writing COntrol lines''' 407 | write_control_lines(input_file) 408 | 409 | # creating names for each well that AERMOD will use to identify 410 | # name of each well is just the integer of the x coord followed by the y coord 411 | # for example, a well at location [501.134, -25.3] will have the name '501-25' 412 | source_x_coordinates_for_naming = [] 413 | source_y_coordinates_for_naming = [] 414 | for x_coord in source_x_points: 415 | source_x_coordinates_for_naming.append(int(x_coord)) 416 | for y_coord in source_y_points: 417 | source_y_coordinates_for_naming.append(int(y_coord)) 418 | 419 | '''Writing SOurce data pathway lines''' 420 | write_source_location_lines(source_x_points, source_y_points, source_x_coordinates_for_naming, 421 | source_y_coordinates_for_naming, source_type, aermap_source_output, input_file) 422 | 423 | # POINT SOURCE CURRENTLY NOT WORKING 424 | if source_type == 'point': 425 | write_source_data_point_lines(source_x_coordinates_for_naming, source_y_coordinates_for_naming, 426 | source_emission_rate_list, source_release_height_list, 427 | source_point_stack_gas_exit_temperature_list, 428 | source_point_stack_gas_exit_velocity_list, 429 | source_point_stack_inside_diameter_list, input_file) 430 | if source_type == 'area': 431 | write_source_data_area_lines(source_x_coordinates_for_naming, source_y_coordinates_for_naming, 432 | source_emission_rate_list, source_release_height_list, 433 | source_area_x_direction_length_list, source_area_y_direction_length_list, 434 | input_file) 435 | 436 | '''Writing REceptor pathway lines''' 437 | if receptor_style == 'discrete': 438 | write_receptor_lines_discrete(receptor_coordinate_list_x, receptor_coordinate_list_y, 439 | aermap_receptor_output, input_file) 440 | elif receptor_style == 'grid': 441 | write_receptor_lines_grid(receptor_grid_starting_point_x, 442 | receptor_grid_starting_point_y, 443 | receptor_grid_number_receptors_x, 444 | receptor_grid_number_receptors_y, 445 | receptor_grid_spacing_x, 446 | receptor_grid_spacing_y, 447 | input_file) 448 | else: 449 | print('receptor_style inputted incorrectly, program will not run') 450 | 451 | '''Writing MEteorological Data pathway lines''' 452 | write_met_data_lines(surface_observations_file, upper_air_data_file, str(met_data_start_year), 453 | str(uair_data_station_number), str(surfdata_station_number), str(base_elevation), input_file) 454 | 455 | '''Writing OUtput options pathway lines''' 456 | # currently set up for just hourly concentrations 457 | write_output_option_lines(run_aerplot, input_file) 458 | input_file.close() -------------------------------------------------------------------------------- /mainframe.py: -------------------------------------------------------------------------------- 1 | from input_script_functions import * 2 | from output_processing_functions import * 3 | from os import system 4 | from sys import exit 5 | from time import time 6 | from openpyxl import Workbook 7 | __author__ = 'Max' 8 | 9 | 10 | # all parameters are defined in depth in run_framework.py 11 | def run_aermod_framework(surface_observations_file, 12 | upper_air_data_file, 13 | source_coordinate_list_x, 14 | source_coordinate_list_y, 15 | source_release_height_list, 16 | source_emission_rate_list, 17 | source_type, 18 | source_area_x_direction_length_list, 19 | source_area_y_direction_length_list, 20 | source_point_stack_gas_exit_temperature_list, 21 | source_point_stack_gas_exit_velocity_list, 22 | source_point_stack_inside_diameter_list, 23 | met_data_start_year, 24 | receptor_style, 25 | receptor_coordinate_list_x, 26 | receptor_coordinate_list_y, 27 | receptor_grid_starting_point_x, 28 | receptor_grid_starting_point_y, 29 | receptor_grid_number_receptors_x, 30 | receptor_grid_number_receptors_y, 31 | receptor_grid_spacing_x, 32 | receptor_grid_spacing_y, 33 | base_elevation, 34 | uair_data_station_number, 35 | surf_data_station_number, 36 | receptor_aermap_output_file_name=None, 37 | source_aermap_output_file_name=None, 38 | run_aerplot='no', 39 | aerplot_northing=None, 40 | aerplot_easting=None, 41 | aerplot_UTM_zone=None, 42 | aerplot_northern_hemisphere=None 43 | ): 44 | 45 | # setup see time it took for program to run 46 | start_time = time() 47 | 48 | # ********* INPUT CHECKS ************** 49 | number_sources = len(source_coordinate_list_x) 50 | if receptor_style == 'discrete': 51 | number_receptors = len(receptor_coordinate_list_x) 52 | else: 53 | number_receptors = receptor_grid_number_receptors_x * receptor_grid_number_receptors_y 54 | 55 | # running the check_source_data_for_length on parameters 56 | # if there is only one data point, copies to correct number of source points 57 | # also checks if data is valid, returns a string if data is not valid 58 | source_emission_rate_list = check_source_data_for_length(source_emission_rate_list, number_sources) 59 | source_release_height_list = check_source_data_for_length(source_release_height_list, number_sources) 60 | source_area_x_direction_length_list = check_source_data_for_length(source_area_x_direction_length_list, 61 | number_sources) 62 | source_area_y_direction_length_list = check_source_data_for_length(source_area_y_direction_length_list, 63 | number_sources) 64 | source_point_stack_gas_exit_temperature_list = \ 65 | check_source_data_for_length(source_point_stack_gas_exit_temperature_list, number_sources) 66 | source_point_stack_gas_exit_velocity_list = \ 67 | check_source_data_for_length(source_point_stack_gas_exit_velocity_list, number_sources) 68 | source_point_stack_inside_diameter_list = \ 69 | check_source_data_for_length(source_point_stack_inside_diameter_list, number_sources) 70 | 71 | # runs check_for_valid_inputs to check if check_source_data_for_length returned an error for any input 72 | valid_inputs_result = check_for_valid_inputs(source_coordinate_list_x, 73 | source_coordinate_list_y, 74 | source_type, 75 | source_emission_rate_list, 76 | source_release_height_list, 77 | source_area_x_direction_length_list, 78 | source_area_y_direction_length_list, 79 | source_point_stack_gas_exit_temperature_list, 80 | source_point_stack_gas_exit_velocity_list, 81 | source_point_stack_inside_diameter_list, 82 | receptor_coordinate_list_y, 83 | receptor_coordinate_list_x, 84 | receptor_style 85 | ) 86 | 87 | # exits the program if there is any problems in the inputs 88 | if not valid_inputs_result: 89 | print('errors exist in inputs, exiting program') 90 | exit() 91 | 92 | # ***** CREATING AERMOD INPUT FILE ***** 93 | write_aermod_input_file(surface_observations_file, 94 | upper_air_data_file, 95 | source_coordinate_list_x, 96 | source_coordinate_list_y, 97 | source_release_height_list, 98 | source_emission_rate_list, 99 | source_type, 100 | source_area_x_direction_length_list, 101 | source_area_y_direction_length_list, 102 | source_point_stack_gas_exit_temperature_list, 103 | source_point_stack_gas_exit_velocity_list, 104 | source_point_stack_inside_diameter_list, 105 | met_data_start_year, 106 | receptor_style, 107 | receptor_coordinate_list_x, 108 | receptor_coordinate_list_y, 109 | receptor_grid_starting_point_x, 110 | receptor_grid_starting_point_y, 111 | receptor_grid_number_receptors_x, 112 | receptor_grid_number_receptors_y, 113 | receptor_grid_spacing_x, 114 | receptor_grid_spacing_y, 115 | base_elevation, 116 | uair_data_station_number, 117 | surf_data_station_number, 118 | receptor_aermap_output_file_name[0], 119 | source_aermap_output_file_name[0], 120 | run_aerplot 121 | ) 122 | 123 | # ***** CREATING AERPLOT INPUT FILE ***** 124 | if run_aerplot == 'yes': 125 | write_aerplot_input_file(easting=aerplot_easting, 126 | northing=aerplot_northing, 127 | utm_zone=aerplot_UTM_zone, 128 | northern_hemisphere=aerplot_northern_hemisphere, 129 | google_earth_display_name="aerplot_output", 130 | plot_file_name="aerplot_data.PLT", 131 | output_file_name="aerplot_output") 132 | 133 | # ***** RUNNING AERMOD ***** 134 | # for running on linux you will need a version of aermod compiled for linux, add a ./ in front of aermod 135 | system("aermod.exe >nul") 136 | 137 | # if aerplot is set to run, runs aerplot as well 138 | if run_aerplot == 'yes': 139 | system("aerplot.exe >nul") 140 | 141 | # ***** PROCESSING OUTPUTS ***** 142 | # setting up spreadsheet and output file data 143 | output_file_name = 'aermod.out' 144 | spreadsheet_name = "AERMOD concentration outputs.xlsx" 145 | output_workbook = Workbook() 146 | output_spreadsheet = output_workbook.active 147 | output_spreadsheet.title = "Data" 148 | if receptor_style == 'discrete': 149 | # setting up spreadsheet headers 150 | spreadsheet_setup_discrete(number_receptors,output_spreadsheet, receptor_coordinate_list_x, 151 | receptor_coordinate_list_y) 152 | 153 | # setting up list of times that the concentration was analyzed at 154 | find_time_lines(output_file_name, output_spreadsheet) 155 | 156 | # adding concentrations from all receptors to excel spreadsheet 157 | find_concentration_lines_discrete(output_file_name, number_receptors, output_spreadsheet) 158 | 159 | elif receptor_style == 'grid': 160 | # just runs script to find annual averages at each receptor in grid 161 | find_grid_concentration_average(output_spreadsheet, output_file_name) 162 | 163 | # Saving workbook to excel file and printing runtime of program 164 | output_workbook.save(spreadsheet_name) 165 | run_time = time() - start_time 166 | print("Program runtime: " + str(int(run_time / 60)) + " minutes " + str(int(run_time % 60)) + " seconds") 167 | -------------------------------------------------------------------------------- /output_processing_functions.py: -------------------------------------------------------------------------------- 1 | from openpyxl import Workbook 2 | from openpyxl.utils.cell import get_column_letter 3 | from openpyxl.styles import PatternFill, Color 4 | from math import ceil 5 | __author__ = 'Max' 6 | 7 | 8 | # This function takes the hour, day and year data from an aermod line 9 | # example input: 10 | # *** CONCURRENT 1-HR AVERAGE CONCENTRATION VALUES ENDING WITH HOUR 5 FOR DAY 1 OF 2016 *** 11 | # returns list with data in form [hour, day, year] 12 | def find_time_data_from_line(line): 13 | # removing blank spaces at beginning and end of line and then splitting the line into words 14 | line = line.strip() 15 | line_split = line.split(' ') 16 | 17 | # removing all blank items since there are some double and triple spaces in the line 18 | line_split = list(word for word in line_split if word != '') 19 | 20 | # finding the number value after the keyword 'HOUR' 21 | hour_index = line_split.index('HOUR') 22 | hour = line_split[hour_index + 1] 23 | 24 | # finding the number value after the keyword 'DAY' 25 | day_index = line_split.index('DAY') 26 | day = line_split[day_index + 1] 27 | 28 | # finding the number value after the keyword 'OF' for the year 29 | year_index = line_split.index('OF') 30 | year = line_split[year_index + 1] 31 | 32 | return [hour, day, year] 33 | 34 | 35 | # this function sets up the headers at the top of the excel spreadsheet file 36 | # inputs 37 | # worksheet: openpyxl Workbook spread sheet *that has already been opened* (workbook.active) 38 | # receptor information for formatting 39 | def spreadsheet_setup_discrete(number_receptors, worksheet, receptor_x_list, receptor_y_list): 40 | # setting up time headers 41 | worksheet.cell(row=1, column=1, value='Hour') 42 | worksheet.cell(row=1, column=2, value='Day') 43 | worksheet.cell(row=1, column=3, value='Year') 44 | 45 | # setting up emissions data headers depending how many receptors there are 46 | # also sets up titles for different concentration averages 47 | # current_column is used to keep track of the columns so it spaces headers out correctly 48 | current_column = 5 49 | for i in range(number_receptors): 50 | # formatting headers and average value titles 51 | receptor_name = '[' + str(receptor_x_list[i]) + ', ' + str(receptor_y_list[i]) + ']' 52 | worksheet.cell(row=1, column=current_column - 1, value="Receptor at:") 53 | worksheet.cell(row=2, column=current_column - 1, value=receptor_name + " Average (excluding '0' values):") 54 | worksheet.cell(row=5, column=current_column - 1, value=receptor_name + " Average (including '0' values):") 55 | worksheet.cell(row=1, column=current_column, value=receptor_name) 56 | worksheet.column_dimensions[get_column_letter(current_column)].width = 10 57 | worksheet.column_dimensions[get_column_letter(current_column - 1)].width = 40 58 | current_column += 2 59 | 60 | # setting up informational cell 61 | worksheet.cell(row=9, column=4).value = "concentrations in micrograms/meters^3" 62 | worksheet['D9'].fill = PatternFill(fgColor=Color('FFFF00'), fill_type='solid', patternType='solid') 63 | 64 | 65 | # this function finds all the hour times that AERMOD used to calculate concentration 66 | # inputs 67 | # output_file: the aermod output file name 68 | # output_spreadsheet: openpyxl Workbook spread sheet *that has already been opened* (workbook.active) 69 | def find_time_lines(output_file_name, output_spreadsheet): 70 | output_file = open(output_file_name, 'r') 71 | curr_row = 2 72 | for line in output_file: 73 | # looks through each line to see if it is the correct line that has time data 74 | # example for type of line that AERMOD produces 75 | # *** CONCURRENT 1-HR AVERAGE CONCENTRATION VALUES ENDING WITH HOUR 5 FOR DAY 1 OF 2016 *** 76 | line_strip = line.strip() 77 | line_split = line_strip.split(' ') 78 | 79 | # checks each line to see if it is the correct line 80 | if line_split[0] == '***' and line_split[1] == 'CONCURRENT': 81 | # find_time_data_from_line returns time data in form [hour, day, year] 82 | time_data = find_time_data_from_line(line) 83 | output_spreadsheet.cell(row=curr_row, column=1).value = int(time_data[0]) 84 | output_spreadsheet.cell(row=curr_row, column=2).value = int(time_data[1]) 85 | output_spreadsheet.cell(row=curr_row, column=3).value = int(time_data[2]) 86 | curr_row += 1 87 | output_file.close() 88 | 89 | 90 | # this function adds concentrations and their coordinates to the spreadsheet 91 | # takes in single emission point data 92 | # spreadsheet: openpyxl Workbook spread sheet *that has already been opened* (workbook.active) 93 | def add_concentration_to_spreadsheet(emission_data, current_row, receptor_number, spreadsheet): 94 | # formatting is setup for excel spreadsheet with time information in first 3 columns 95 | # remove the '3 + ' to just have emissions data in the final spreadsheet 96 | column_number = 3 + receptor_number*2 97 | spreadsheet.cell(row=current_row, column=column_number).value = float(emission_data) 98 | 99 | 100 | # this function removes the last values from the spreadsheet since they are averages and not calculated data 101 | def format_average_values_discrete(output_spreadsheet, number_receptors): 102 | # loops through receptor columns to find last data point 103 | current_column = 3 104 | max_row = output_spreadsheet.max_row 105 | for receptor in range(number_receptors): 106 | # finding the column the current receptors data is at, and the column to put the average value in 107 | current_column += 2 108 | average_column = current_column - 1 109 | 110 | # finding last element in list, setting to correct average cell 111 | current_average = output_spreadsheet.cell(row=max_row, column=current_column).value 112 | output_spreadsheet.cell(row=3, column=average_column).value = current_average 113 | 114 | # adding averages including zeros calculated by excel 115 | column_identifier = get_column_letter(current_column) + ":" + get_column_letter(current_column) 116 | output_spreadsheet.cell(row=6, column=average_column).value = "=AVERAGE("+column_identifier+")" 117 | 118 | # deleting last row 119 | output_spreadsheet.delete_rows(max_row) 120 | 121 | 122 | # this function finds all concentrations at all receptors that AERMOD calculated 123 | # inputs 124 | # output_file_name: the aermod output file name 125 | # number_receptors: the number of receptors specified for formatting excel spreadsheet 126 | # output_spreadsheet: openpyxl Workbook spread sheet *that has already been opened* (workbook.active) 127 | def find_concentration_lines_discrete(output_file_name, number_receptors, output_spreadsheet): 128 | output_file = open(output_file_name, 'r') 129 | number_data_lines_to_read = int(ceil(number_receptors / 2.0)) 130 | current_row = 1 131 | # uses while loop since you cant readline() if iterating lines in a for loop in python 2.7 132 | while True: 133 | line = output_file.readline() 134 | if not line: 135 | break 136 | current_line_stripped = line.strip() 137 | current_line_split = current_line_stripped.split(' ') 138 | 139 | # checking to see if the line is two lines up from the data 140 | # this appears to be the most viable way to collect the emissions data 141 | if current_line_split[0] == 'X-COORD': 142 | # keeps track of what row to enter data on 143 | current_row += 1 144 | 145 | # parses down to get to the correct line 146 | output_file.readline() 147 | 148 | # counts which receptor the parser is on for formatting data on excel sheet 149 | receptor_count = 1 150 | 151 | # since there are two receptor values per line, loops through lines if there are more than two receptors 152 | for i in range(number_data_lines_to_read): 153 | # formatting line to get location and emission data from each line 154 | data_line = output_file.readline() 155 | data_line = data_line.strip() 156 | data_line_split = data_line.split(' ') 157 | 158 | # getting rid of extraneous blank items in the list 159 | data_line_split = list(word for word in data_line_split if word != '') 160 | 161 | # adding the emission results data point to the spreadsheet 162 | add_concentration_to_spreadsheet(data_line_split[2], current_row, receptor_count, output_spreadsheet) 163 | receptor_count += 1 164 | 165 | # if there are two data points on the line, adds the second data point as well 166 | if len(data_line_split) == 6: 167 | add_concentration_to_spreadsheet(data_line_split[5], current_row, receptor_count, 168 | output_spreadsheet) 169 | receptor_count += 1 170 | 171 | # deleting last row of data since it is averages computed by AERMOD 172 | # saves data and places near top 173 | # adds excel calculations that calculate concentration averages including 0 174 | format_average_values_discrete(output_spreadsheet, number_receptors) 175 | output_file.close() 176 | 177 | 178 | # this function finds the average concentration values for a grid receptor system 179 | # output_spreadsheet: openpyxl Workbook spread sheet *that has already been opened* (workbook.active) 180 | def find_grid_concentration_average(output_spreadsheet, output_file_name): 181 | output_file = open(output_file_name, 'r') 182 | # variables to keep track of location in spreadsheet 183 | code_end = False 184 | current_column_header = 1 185 | current_column = 1 186 | column_start_index = 0 187 | 188 | # uses while loop since you cant readline() if iterating lines in a for loop in python 2.7 189 | while True: 190 | line = output_file.readline() 191 | # breaks at the end 192 | if not line: 193 | break 194 | # processing line 195 | line_stripped = line.strip() 196 | line_split = line_stripped.split(' ') 197 | # removing blank entries 198 | line_split = list(word for word in line_split if word != '') 199 | # checks to see if at the end of the aermod output file to get annual concentration data 200 | if len(line_split) > 5: 201 | if line_split[0] == '***' and line_split[1] == 'THE' and line_split[2] == 'ANNUAL': 202 | # marking that the code has parsed to the last entry 203 | code_end = True 204 | 205 | # checking if x coordinate header line 206 | if len(line_split) > 0 and code_end and line_split[0] == "(METERS)": 207 | # removing non data in line split list 208 | line_split = line_split[2:] 209 | 210 | for column in line_split: 211 | # adding to the column count for spreadsheet formatting 212 | # this for loop adds the headers of the receptor locations 213 | current_column_header += 1 214 | output_spreadsheet.cell(row=1, column=current_column_header).value = float(column) 215 | 216 | # parsing down two lines to get to concentration data 217 | output_file.readline() 218 | output_file.readline() 219 | current_row = 1 220 | 221 | # parsing all y - coord values (rows) 222 | while True: 223 | current_row += 1 224 | line = output_file.readline() 225 | line_stripped = line.strip() 226 | line_split = line_stripped.split(' ') 227 | # removing the '|' from data table 228 | del line_split[1] 229 | # gets rid of the y - coord value from list if there is already one there 230 | if column_start_index > 0: 231 | del line_split[0] 232 | # removing blank entries 233 | line_split = list(word for word in line_split if word != '') 234 | # checks if current line contains concentration data 235 | try: 236 | line_split = [float(num) for num in line_split] 237 | # resets column if there is more data to read 238 | current_column = column_start_index 239 | except ValueError: 240 | # once a set of data has been processed, sets index to current column so data keeps 241 | # being put to the right in the correct order 242 | if current_column > 0: 243 | column_start_index = current_column 244 | break 245 | 246 | for column in line_split: 247 | # adding data to spreadsheet from table 248 | current_column += 1 249 | output_spreadsheet.cell(row=current_row, column=current_column).value = column 250 | 251 | output_file.close() 252 | 253 | -------------------------------------------------------------------------------- /run_framework.py: -------------------------------------------------------------------------------- 1 | from mainframe import run_aermod_framework 2 | __author__ = 'Max' 3 | 4 | # run_framework.py is the interface for the framework, and calls the run_framework function that runs mainframe 5 | # mainframe.py is the framework, it checks inputs, writes the input files, runs AERMOD and processes the outputs 6 | # input_script_functions contains all functions to write AERMOD/AERPLOT input files and check inputs 7 | # output_processing_functions contains all functions to process AERMOD output files 8 | # more details about output processing are presented below, refer to the receptor_style and run_aerplot variables 9 | 10 | # the overview of the framework is easily allowing one to add emission sources in discrete locations and assigning 11 | # them emission rates, which AERMOD will use to predict concentrations at locations also specified by the user. This 12 | # framework also processes the output data, currently set to hourly averages for a year of data, into a spreadsheet 13 | # to easily be used to create graphs, analyze maxima or anything else. The output spreadsheet is currently configured 14 | # to neatly display the time information and yearly average information. If wanted, one can easily go into excel and 15 | # delete all the columns that don't contain emission concentration data. 16 | # concentration data that aermod produces is in the form *****micrograms per meter cubed****** 17 | 18 | # meteorological data comes from AERMET processor 19 | # AERMET will be needed to obtain meteorological data used for AERMOD 20 | # processing meteorological data with AERMET is the *only* requirement to run this framework 21 | # AERMOD is currently set to calculate all 1-hour concentration averages for a given year of meteorological data 22 | # To change this, go to the writing control and output lines functions in input_script_functions.py 23 | 24 | # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 25 | # @@@@@@@@@@@@ INPUT OPTIONS @@@@@@@@@@@@@@ 26 | # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 27 | 28 | # Name of the surface observations file 29 | # Mandatory, file type: SFC 30 | # Enter as string data type 31 | surface_observations_file = 'temp.SFC' 32 | 33 | # Name of the upper air observations file 34 | # Mandatory, file type: PFL 35 | # Enter as string data type 36 | upper_air_data_file = 'temp.PFL' 37 | 38 | # list of pollutant source points coordinates in meters 39 | # INPUT LIST OF X COORDINATES IN source_x_points AND Y COORDINATES IN source_y_points 40 | # INDEXES WILL CORRESPOND TO EACH OTHER IN THE TWO LISTS 41 | # LISTS MUST BE MATCHING LENGTH 42 | source_coordinate_list_x = [] 43 | source_coordinate_list_y = [] 44 | 45 | # list of pollutant source release heights in meters 46 | # can be a single data point in which case the value will be applied to EVERY pollutant source 47 | # if manually entering each height, must be the exact same length as the source_x_points/source_y_points 48 | # each height will be associated to the source with the same index as in the coordinate list 49 | source_release_height_list = [] 50 | 51 | # list of emission rates that will correspond to the source points list 52 | # can be a single data point in which case the value will be applied to EVERY pollutant source 53 | # if manually entering each rate, must be the exact same length as the source_x_points/source_y_points 54 | # each rate will be associated to the source with the same index as in the coordinate list 55 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 56 | # $$**** IF USING POINT SOURCE, WILL BE INTERPRETED IN GRAMS PER SECOND ***********************$$ 57 | # $$**** IF USING AREA SOURCE, WILL BE INTERPRETED IN GRAMS PER SECOND PER METER SQUARED ******$$ 58 | # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 59 | source_emission_rate_list = [] 60 | 61 | # determines what pollutant source type you want 62 | # data required for simulation depends on what type is chosen 63 | # if point is chosen, all 'source_point_...' data is needed 64 | # if area is chosen, all 'source_area_...' data is needed 65 | # MUST BE EITHER 'point' OR 'area' OR FRAMEWORK WILL NOT WORK 66 | # Enter as string data type 67 | source_type = 'area' # 'point' 68 | 69 | # area source dimensions of area source polluters, in meters 70 | # this will determine the size of the pollutant source and the emission rate, 71 | # since emissions for area sources are in g/s/m^2, 72 | # ONLY NEEDED IF USING AREA SOURCE, IF NOT NEEDED SET EQUAL TO 'None' 73 | # can be a single data point in which case the value will be applied to EVERY pollutant source 74 | # if manually entering each value, must be the exact same length as the source_x_points/source_y_points 75 | # each value will be associated to the source with the same index as in the coordinate list 76 | source_area_x_direction_length_list = [] 77 | source_area_y_direction_length_list = [] 78 | 79 | # temperature of gas from the point source as it exits 80 | # UNITS ARE IN ***KELVIN***, ENTER 0 TO JUST USE AMBIENT TEMPERATURE 81 | # ONLY NEEDED IF USING POINT SOURCE, IF NOT NEEDED SET EQUAL TO 'None' 82 | # can be a single data point in which case the value will be applied to EVERY pollutant source 83 | # if manually entering each value, must be the exact same length as the source_x_points/source_y_points 84 | # each value will be associated to the source with the same index as in the coordinate list 85 | source_point_stack_gas_exit_temperature_list = [0] 86 | 87 | # velocity of gas from the point source as it exits 88 | # units are in meters/second 89 | # ONLY NEEDED IF USING POINT SOURCE, IF NOT NEEDED SET EQUAL TO 'None' 90 | # can be a single data point in which case the value will be applied to EVERY pollutant source 91 | # if manually entering each value, must be the exact same length as the source_x_points/source_y_points 92 | # each value will be associated to the source with the same index as in the coordinate list 93 | source_point_stack_gas_exit_velocity_list = [] 94 | 95 | # diameter of pollutant stack from point source emittor 96 | # units are in meters 97 | # ONLY NEEDED IF USING POINT SOURCE, IF NOT NEEDED SET EQUAL TO 'None' 98 | # can be a single data point in which case the value will be applied to EVERY pollutant source 99 | # if manually entering each value, must be the exact same length as the source_x_points/source_y_points 100 | # each value will be associated to the source with the same index as in the coordinate list 101 | source_point_stack_inside_diameter_list = [] 102 | 103 | # the year the meteorological data starts 104 | # Should reflect meteorological data 105 | met_data_start_year = '' 106 | 107 | # there are two options for receptor styles 108 | # the first is discrete in which you put in the coordinates you want AERMOD to calculate concentrations at 109 | # the second is grid in which AERMOD calculates concentrations in a full grid 110 | # grid simulations can take a lot longer since there are much more receptor locations 111 | # either enter 'discrete' or 'grid' 112 | # for discrete you only have to enter the receptor_coordinate_list_... variables 113 | # for grid you have to enter all receptor_grid_... variables 114 | # EXTRACTING CONCENTRATION DATA TO EXCEL SPREADSHEET ONLY WORKS WITH DISCRETE COORDINATES 115 | receptor_style = 'grid' # 'discrete' 116 | 117 | # coordinate list of discrete receptors in METERS 118 | # enter as many as you want, enter as lists of numbers 119 | # length of each list must match 120 | # the index of each coordinate in each list will correspond to the index of the coordinate in the other list 121 | # if using grid receptors, enter as None 122 | receptor_coordinate_list_x = [] 123 | receptor_coordinate_list_y = [] 124 | 125 | # the starting coordinate for the x and y receptors 126 | # the first receptor will be at this location, followed by as many receptors specified at the spacing distance 127 | # entered in meters 128 | # hint: enter negative coordinate so receptors form a grid around the origin 129 | receptor_grid_starting_point_x = 0 130 | receptor_grid_starting_point_y = 0 131 | 132 | # the number of receptors in the x and y location 133 | # this will determine the size of the grid since there will be as many receptors as specified, 134 | # seperated by the spacing distance 135 | receptor_grid_number_receptors_x = 0 136 | receptor_grid_number_receptors_y = 0 137 | 138 | # this determines the spacing distance between receptors in the x and y direction 139 | # the distance between each receptor multiplied by the number of receptors minus one in each direction (x/y), 140 | # will determine the size of the grid 141 | # units are in meters 142 | # for example, starting point -1000, number receptors = 21, grid_spacing = 100, the x length of the grid is 2000 143 | receptor_grid_spacing_x = 0 144 | receptor_grid_spacing_y = 0 145 | 146 | # the base elevation for the region in the study 147 | # data is MANDATORY by AERMOD 148 | # units are in METERS 149 | base_elevation = '' 150 | 151 | # the station number that the upper air data was collected at 152 | # data is MANDATORY by AERMOD 153 | uair_data_station_number = '' 154 | 155 | # the station number that the surface data observations were collected at 156 | # data is MANDATORY by AERMOD 157 | surf_data_station_number = '' 158 | 159 | # if you ran an AERMAP simulation for this scenario 160 | # enter the names of the source and receptor files that AERMAP outputted 161 | # these files should be in a txt format 162 | # example; receptor_aermap_output_file_name='aermap_receptor.txt' 163 | # if AERMAP was not used set both variables to None and the program will run fine 164 | receptor_aermap_output_file_name = None, 165 | source_aermap_output_file_name = None, 166 | 167 | # if you want to run AERPLOT to make contour plots of the results 168 | # all you need is information about the location of the area of interest 169 | 170 | # for running aerplot, set run_aerplot to 'yes' or 'no' 171 | # *** aerplot needs more than one receptor or it will throw an error 172 | run_aerplot = 'no' 173 | 174 | # the northing and easting **UTM** coordinates of the location of interest 175 | # if you don't care about the location that the plot is overlayed in on google earth 176 | # you can enter 0 for the northing and easting and just see the contour plot on the ocean 177 | # enter the coordinates, or set them to None if not running aerplot 178 | aerplot_northing = '' 179 | aerplot_easting = '' 180 | 181 | # the utm zone for the location of interest 182 | # set to None if not using aerplot 183 | aerplot_UTM_zone = '' 184 | 185 | # if using aerplot, define if in the northern hemisphere or not 186 | # **ENTER ARGUMENT AS A STRING** 187 | # if not using aerplot, set as None. Otherwise, set *ONLY* to 'True' or 'False' with quotations 188 | aerplot_northern_hemisphere = 'True' # 'False' 189 | 190 | 191 | 192 | 193 | #################################################################################### 194 | #################################################################################### 195 | #################################################################################### 196 | ################################### FUNCTION CALL ################################## 197 | #################################################################################### 198 | #################################################################################### 199 | #################################################################################### 200 | 201 | run_aermod_framework(surface_observations_file=surface_observations_file, 202 | upper_air_data_file=upper_air_data_file, 203 | source_coordinate_list_x=source_coordinate_list_x, 204 | source_coordinate_list_y=source_coordinate_list_y, 205 | source_release_height_list=source_release_height_list, 206 | source_emission_rate_list=source_emission_rate_list, 207 | source_type=source_type, 208 | source_area_x_direction_length_list=source_area_x_direction_length_list, 209 | source_area_y_direction_length_list=source_area_y_direction_length_list, 210 | source_point_stack_gas_exit_temperature_list=source_point_stack_gas_exit_temperature_list, 211 | source_point_stack_gas_exit_velocity_list=source_point_stack_gas_exit_velocity_list, 212 | source_point_stack_inside_diameter_list=source_point_stack_inside_diameter_list, 213 | met_data_start_year=met_data_start_year, 214 | receptor_style=receptor_style, 215 | receptor_coordinate_list_x=receptor_coordinate_list_x, 216 | receptor_coordinate_list_y=receptor_coordinate_list_y, 217 | receptor_grid_starting_point_x=receptor_grid_starting_point_x, 218 | receptor_grid_starting_point_y=receptor_grid_starting_point_y, 219 | receptor_grid_number_receptors_x=receptor_grid_number_receptors_x, 220 | receptor_grid_number_receptors_y=receptor_grid_number_receptors_y, 221 | receptor_grid_spacing_x=receptor_grid_spacing_x, 222 | receptor_grid_spacing_y=receptor_grid_spacing_y, 223 | base_elevation=base_elevation, 224 | uair_data_station_number=uair_data_station_number, 225 | surf_data_station_number=surf_data_station_number, 226 | receptor_aermap_output_file_name=receptor_aermap_output_file_name, 227 | source_aermap_output_file_name=source_aermap_output_file_name, 228 | run_aerplot=run_aerplot, 229 | aerplot_northing=aerplot_northing, 230 | aerplot_easting=aerplot_easting, 231 | aerplot_UTM_zone=aerplot_UTM_zone, 232 | aerplot_northern_hemisphere=aerplot_northern_hemisphere 233 | ) 234 | --------------------------------------------------------------------------------