def _generate_summary_json(self, output_directory): """Creates summary.json (like BSF's) if configured to do so """ if vis_hysplit_config('create_summary_json'): d_from = d_to = None try: d_from = datetime_parsing.parse(self._start_time) d_to = d_from + datetime.timedelta(hours=self._num_hours) except: pass contents = { "websky_version": vis_hysplit_config("websky_version"), "output_version": "2.0.1", # TODO: populate with real values "dispersion_period": { "from": d_from and d_from.strftime("%Y%m%d %HZ"), "to": d_to and d_to.strftime("%Y%m%d %HZ") }, "width_longitude": self._grid_params.get("width_longitude"), "height_latitude": self._grid_params.get("height_latitude"), "center_latitude": self._grid_params.get("center_latitude"), "center_longitude": self._grid_params.get("center_longitude"), "model_configuration": "HYSPLIT", "carryover": (self._fires_manager.dispersion and self._fires_manager.dispersion.get("carryover") or {}) } contents_json = json.dumps(contents, indent=4) logging.debug("generating summary.json: %s", contents) with open(os.path.join(output_directory, 'summary.json'), 'w') as f: f.write(contents_json)
def add_kml(self, kmlfile, fire, hour): if fire['id'] not in self.fires: self.fires[fire['id']] = [] # TODO: pass in and use model start time instead ? fire_dt = datetime_parsing.parse(fire['start']) hour_delta = timedelta(hours=1) dt = fire_dt + hour_delta * hour content = ''' <NetworkLink> <name>%s - %s</name> <visibility>1</visibility> <TimeSpan><begin>%s</begin><end>%s</end></TimeSpan> <Link><href>%s</href></Link> <Style> <ListStyle> <listItemType>checkHideChildren</listItemType> </ListStyle> </Style> </NetworkLink> ''' % (fire['id'], dt.strftime('Hour %HZ'), dt.isoformat(), (dt + hour_delta).isoformat(), kmlfile) self.fires[fire['id']].append(content) if self.min_time == '' or dt < self.min_time: self.min_time = dt if self.max_time == '' or (dt + hour_delta) > self.max_time: self.max_time = dt
def _convert_keys_to_datetime(self, d): return {datetime_parsing.parse(k): v for k, v in d.items()}
def _on_or_after(self, dt1, dt2): # make sure same type, and convert to datetimes if not if type(dt1) != type(dt2): dt1 = datetime_parsing.parse(dt1) dt2 = datetime_parsing.parse(dt2) return dt1 >= dt2
def write(self, fires_manager): fires_info = fires_manager.fires country_process_list = ['US', 'USA', 'United States'] ptinv = open(self._ptinv_pathname, 'w') ptday = open(self._ptday_pathname, 'w') pthour = open(self._pthour_pathname, 'w') ptinv.write("#IDA\n#PTINV\n#COUNTRY %s\n" % country_process_list[0]) ptinv.write("#YEAR %d\n" % self.file_year) ptinv.write("#DESC POINT SOURCE BlueSky Framework Fire Emissions\n") ptinv.write("#DATA PM2_5 PM10 CO NH3 NOX SO2 VOC PTOP PBOT LAY1F\n") ptday.write("#EMS-95\n#PTDAY\n#COUNTRY %s\n" % country_process_list[0]) ptday.write("#YEAR %d\n" % self.file_year) ptday.write("#DESC POINT SOURCE BlueSky Framework Fire Emissions\n") pthour.write("#EMS-95\n#PTHOUR\n#COUNTRY %s\n" % country_process_list[0]) pthour.write("#YEAR %d\n" % self.file_year) pthour.write("#DESC POINT SOURCE BlueSky Framework Fire Emissions\n") num_of_fires = 0 skip_no_lat_lng = 0 skip_no_emiss = 0 skip_no_lat_lng = 0 skip_no_plume = 0 skip_bad_fips = 0 total_skipped = 0 for fire_info in fires_info: for fire_loc in fire_info.locations: with fires_manager.fire_failure_handler(fire_info): if any([k not in fire_loc for k in ('lat', 'lng')]): skip_no_lat_lng += 1 total_skipped += 1 continue num_of_fires += 1 if "emissions" not in fire_loc.keys(): skip_no_emiss += 1 total_skipped += 1 continue if "plumerise" not in fire_loc.keys(): skip_no_plume += 1 total_skipped += 1 continue lat, lng = fire_loc['lat'], fire_loc['lng'] # try to get CYID/STID, but skip record if lat/lng invalid try: cyid, stid = self._get_state_county_fips(lat, lng) except ValueError: print("Invalid Lat:%s and/or Invalid Lng:%s" % (lat, lng)) skip_bad_fips += 1 total_skipped += 1 continue scc = self._map_scc(fire_info.type) start_dt = datetime_parsing.parse(fire_loc['start']) start_hour = start_dt.hour # if timeprofile key has 0 values, (for flaming/resid/etc), do we # ignore or still right the value. num_hours = len(fire_loc['timeprofile'].keys()) num_days = num_hours // 24 if num_hours % 24 > 0: num_days += 1 fcid = self._generate_fcid(fire_info, num_hours, lat, lng) """ NOTE: In the old BSF runs, timezone was never operational, and defaulted to EST in every output I reviewed. Per https://www.cmascenter.org/smoke/documentation/2.7/html/ch02s09s14.html, the timezone field is only used if timezone cannot be determined through FIPS code. """ tzonnam = self._set_timezone_name(lat, lng, start_dt) # Define fire types, defaults to total. if self._separate_smolder: fire_phases = ("flaming", "smoldering") else: fire_phases = ("total", ) # iterate through fire_phases for fire_phase in fire_phases: if fire_phase == "total": ptid = '1' # Point ID skid = '1' # Stack ID elif fire_phase == "flaming": ptid = '1' # Point ID skid = '1' # Stack ID scc = scc[:-2] + "F" + scc[-1] elif fire_phase == "smoldering": ptid = '2' # Point ID skid = '2' # Stack ID scc = scc[:-2] + "S" + scc[-1] prid = '' # Process ID date = start_dt.strftime('%m/%d/%y') # Date EMISSIONS_MAPPING = [('PM2_5', "pm2.5"), ('PM10', "pm10"), ('CO', "co"), ('NH3', "nh3"), ('NOX', "nox"), ('SO2', "so2"), ('VOC', "voc")] """ PTINV """ # PTINVRecord ptinv_rec = PTINVRecord() ptinv_rec.STID = stid ptinv_rec.CYID = cyid ptinv_rec.PLANTID = fcid ptinv_rec.POINTID = ptid ptinv_rec.STACKID = skid ptinv_rec.SCC = scc ptinv_rec.LATC = lat ptinv_rec.LONC = lng ptinv_rec_str = str(ptinv_rec) if self._write_ptinv_totals: logging.debug( 'Writing SmokeReady PTINV File to %s', self._ptinv_pathname) for var, vkey in EMISSIONS_MAPPING: for fuelbed in fire_loc['fuelbeds']: if vkey.upper( ) in fuelbed['emissions'][fire_phase]: if fuelbed['emissions'][fire_phase][ vkey.upper()] is None: continue prec = PTINVPollutantRecord() prec.ANN = fuelbed['emissions'][ fire_phase][vkey.upper()][0] prec.AVD = fuelbed['emissions'][ fire_phase][vkey.upper()][0] ptinv_rec_str += str(prec) ptinv.write(ptinv_rec_str + "\n") """ PTDAY """ if self._write_ptday_file: logging.debug( 'Writing SmokeReady PTHOUR File to %s', self._ptday_pathname) for var, vkey in EMISSIONS_MAPPING: for fuelbed in fire_loc['fuelbeds']: if vkey.upper( ) in fuelbed['emissions'][fire_phase]: if fuelbed['emissions'][fire_phase][ vkey.upper()] is None: continue for day in range(num_days): dt = start_dt + timedelta(days=day) date = dt.strftime('%m/%d/%y') # PTDAYRecord ptday_rec = PTDAYRecord() ptday_rec.STID = stid ptday_rec.CYID = cyid ptday_rec.FCID = fcid ptday_rec.SKID = ptid ptday_rec.DVID = skid ptday_rec.PRID = prid ptday_rec.POLID = var ptday_rec.DATE = date ptday_rec.TZONNAM = tzonnam start_slice = max( (24 * day) - start_hour, 0) end_slice = min( (24 * (day + 1)) - start_hour, len(fuelbed['emissions'][ fire_phase][vkey.upper()])) if fire_phase == "flaming": if isinstance( fuelbed['emissions'] [fire_phase][vkey.upper()], tuple): daytot = fuelbed[ 'emissions'][ fire_phase][ vkey.upper( )][0] else: daytot = sum( tup for tup in fuelbed['emissions'] [fire_phase][ vkey.upper()] [start_slice:end_slice] ) elif fire_phase == "smoldering": if isinstance( fuelbed['emissions'] [fire_phase][vkey.upper()], tuple): daytot = fuelbed[ 'emissions'][fire_phase][ vkey.upper( )][1] + fuelbed[ 'emissions'][ fire_phase][ vkey. upper( )][2] else: # comment for Yufei # why are we including residual here? smoldering = sum( fuelbed['emissions'] [fire_phase][ vkey.upper()] [start_slice:end_slice] ) residual = sum( fuelbed['emissions'] ['residual'][ vkey.upper()] [start_slice:end_slice] ) daytot = smoldering + residual else: if isinstance( fuelbed['emissions'] [fire_phase][vkey.upper()], tuple): daytot = sum( fuelbed['emissions'] [fire_phase][ vkey.upper()]) else: daytot = sum( sum(fuelbed[ 'emissions'] [fire_phase][ vkey.upper()] [start_slice: end_slice])) ptday_rec.DAYTOT = daytot # Daily total ptday_rec.SCC = scc # Source Classification Code ptday.write(str(ptday_rec)) """ PTHOUR """ PTHOUR_MAPPING = [('PTOP', "percentile_100"), ('PBOT', "percentile_000"), ('LAY1F', "smoldering_fraction"), ('PM2_5', "pm2.5"), ('PM10', "pm10"), ('CO', "co"), ('NH3', "nh3"), ('NOX', "nox"), ('SO2', "so2"), ('VOC', "voc")] logging.debug('Writing SmokeReady PTHOUR File to %s', self._pthour_pathname) for var, vkey in PTHOUR_MAPPING: species_key = vkey.upper() if var in ('PTOP', 'PBOT', 'LAY1F'): if fire_loc['plumerise'] is None: continue else: if species_key not in fuelbed['emissions'][ 'total'].keys(): continue # collect sorted hour list ordered_hours = sorted( fire_loc['plumerise'].keys()) for day in range(num_days): dt = start_dt + timedelta(days=day) date = dt.strftime('%m/%d/%y') # Date # PTHOUR Record pthour_rec = PTHOURRecord() pthour_rec.STID = stid pthour_rec.CYID = cyid pthour_rec.FCID = fcid pthour_rec.SKID = ptid pthour_rec.DVID = skid pthour_rec.PRID = prid pthour_rec.POLID = var pthour_rec.DATE = date pthour_rec.TZONNAM = tzonnam pthour_rec.SCC = scc daytot = 0.0 for hour in range(24): h = (day * 24) + hour - start_hour if h < 0: setattr(pthour_rec, 'HRVAL' + str(hour + 1), 0.0) continue try: plumerise_hour = fire_loc['plumerise'][ ordered_hours[h]] timeprofile_hour = fire_loc[ 'timeprofile'][ordered_hours[h]] emissions_summary = fire_loc[ 'emissions']['summary'] if var in ('PTOP', 'PBOT', 'LAY1F'): if fire_phase == "flaming": if var == 'LAY1F': value = 0.0001 elif var == 'PTOP': value = plumerise_hour[ 'heights'][-1] elif var == 'PBOT': value = plumerise_hour[ 'heights'][0] else: value = None elif fire_phase == "smoldering": value = { 'LAY1F': 1.0, 'PTOP': 0.0, 'PBOT': 0.0 }[var] else: if var == 'LAY1F': value = plumerise_hour[ 'smoldering_fraction'] elif var == 'PTOP': value = plumerise_hour[ 'heights'][-1] elif var == 'PBOT': value = plumerise_hour[ 'heights'][0] else: value == None else: if fire_phase == "flaming": flaming_total = self._phase_emissions_for_species( fire_loc, fire_phase, species_key) value = (timeprofile_hour[ fire_phase] * flaming_total) elif fire_phase == "smoldering": smoldering_total = self._phase_emissions_for_species( fire_loc, fire_phase, species_key) residual_total = self._phase_emissions_for_species( fire_loc, 'residual', species_key) smoldering_value = ( timeprofile_hour[ fire_phase] * smoldering_total) residual_value = ( timeprofile_hour[ 'residual'] * residual_total) # smoke wants smoldering + residual value = smoldering_value + residual_value else: # fallback to total if species_key in emissions_summary.keys( ): value = emissions_summary[ species_key] daytot += value setattr(pthour_rec, 'HRVAL' + str(hour + 1), value) except IndexError: self.log.debug( "IndexError on hour %d for fire" % (h)) setattr(pthour_rec, 'HRVAL' + str(hour + 1), 0.0) if var not in ('PTOP', 'PBOT', 'LAY1F'): pthour_rec.DAYTOT = daytot pthour.write(str(pthour_rec)) # Close Files ptinv.close() if self._write_ptday_file: ptday.close() pthour.close() logging.debug("SmokeReady Process Complete.") logging.debug(" #FIRES: %s", num_of_fires) logging.debug(" #SKIPPED: %s", total_skipped) logging.debug(" #NO LAT/LNG: %s", skip_no_lat_lng) logging.debug(" #NO EMISS: %s", skip_no_emiss) logging.debug(" #NO PLUME: %s", skip_no_plume) logging.debug(" #BAD FIPS: %s", skip_bad_fips)
def __init__(self, fireLoc): # Basic fire info self.fireID = fireLoc['id'] self.alat = float(fireLoc['latitude']) self.along = float(fireLoc['longitude']) self.acres = fireLoc['area'] # Fire Date and time information # TODO: pass in and use model start time instead ? fire_dt = datetime_parsing.parse(fireLoc['start']) self.iyear = fire_dt.year self.imo = fire_dt.month self.iday = fire_dt.day self.hrstrt = fire_dt.hour self.tfire = fire_dt.hour self.firestart = fire_dt.strftime('%H%M') # Title for input files self.title = ("'VSMOKE input for fire ID %s'" % fireLoc['id']) vsmoke_meta = fireLoc.meta.get('vsmoke', {}) # Stability self.stability = vsmoke_meta.get("stability", None) if self.stability: self.stability = int(self.stability) if self.stability < INPUTVariables.MIN_STAB: self.stability = INPUTVariables.MIN_STAB elif self.stability > INPUTVariables.MAX_STAB: self.stability = INPUTVariables.MAX_STAB # Mixing Height self.mix_ht = vsmoke_meta.get('mixht', None) if self.mix_ht: self.mix_ht = float(self.mix_ht) if self.mix_ht > INPUTVariables.MAX_MIXHT: self.mix_ht = INPUTVariables.MAX_MIXHT # Wind speed self.ua = vsmoke_meta.get('ws', None) if self.ua is None: raise ValueError("Wind speed ('meta' > 'vsmoke' > 'ws') " "required for each fire") else: self.ua = float(self.ua) if self.ua <= INPUTVariables.MIN_WS: self.ua = INPUTVariables.MIN_WS + 0.001 # Wind direction self.wdir = vsmoke_meta.get("wd", None) if self.wdir is None: raise ValueError("Wind direction ('meta' > 'vsmoke' > 'wd') " "required for each fire") else: self.wdir = float(self.wdir) if self.wdir <= INPUTVariables.MIN_DIR: self.wdir = INPUTVariables.MIN_DIR + 0.001 elif self.wdir > INPUTVariables.MAX_DIR: self.wdir = INPUTVariables.MAX_DIR # Surface temperature self.temp_fire = vsmoke_meta.get("temp", None) if self.temp_fire: self.temp_fire = float(self.temp_fire) # Surface pressure self.pres = vsmoke_meta.get("pressure", None) if self.pres: self.pres = float(self.pres) # Surface relative humidity self.irha = vsmoke_meta.get("rh", None) if self.irha: self.irha = int(self.irha) # Is fire during daylight hours or nighttime self.ltofdy = vsmoke_meta.get("sun", None) if self.ltofdy: self.ltofdy = str(self.ltofdy) # Initial horizontal dispersion self.oyinta = vsmoke_meta.get("oyinta", None) if self.oyinta: self.oyinta = float(self.oyinta) # Initial vertical dispersion self.ozinta = vsmoke_meta.get("ozinta", None) if self.ozinta: self.ozinta = float(self.ozinta) # Background PM 2.5 self.bkgpma = vsmoke_meta.get("bkgpm", None) if self.bkgpma: self.bkgpma = float(self.bkgpma) # Background CO self.bkgcoa = vsmoke_meta.get("bkgco", None) if self.bkgcoa: self.bkgcoa = float(self.bkgcoa)
# sunrise_hour,sunset_hour,snow_month,rain_days,heat,pm25,pm10,co,co2, # ch4,nox,nh3,so2,voc,canopy,event_url,fccs_number,owner,sf_event_guid, # sf_server,sf_stream_name,timezone,veg FIRE_LOCATIONS_CSV_FIELDS = ( [ ('id', lambda f, loc: f.id), ('event_id', lambda f, loc: f.get('event_of', {}).get('id')), ('latitude', lambda f, loc: locationutils.LatLng(loc).latitude), ('longitude', lambda f, loc: locationutils.LatLng(loc).longitude), ('utc_offset', lambda f, loc: loc.get('utc_offset')), ('source', lambda f, loc: loc.get('source')), # Note: We're keeping the 'type' field consistent with the csv files # generated by smartfire, which use 'RX' and 'WF' ('type', lambda f, loc: 'RX' if f.type == 'rx' else 'WF'), ('date_time', lambda f, loc: datetime_parsing.parse( loc['start']).strftime(BLUESKYKML_DATE_FORMAT)), ('event_name', lambda f, loc: f.get('event_of', {}).get('name')), ('fccs_number', _pick_representative_fuelbed), ('fuelbed_fractions', _get_fuelbed_fractions), # TDOO: add 'VEG'? (Note: sf2 has 'veg' field, but # it seems to be a fuelbed discription which is probably for # the one fccs id in the sf2 feed. This single fccs id and its description # don't necesasrily represent the entire fire area, which could have # multiple fuelbeds. we could set 'VEG' to # a concatenation of the fuelbeds or the one one making up the largest # fraction of the fire.) ('heat', _get_heat) ] # emissions + [(s.lower(), _get_emissions_species('PM2.5' if s is 'pm25' else s))