awips2/edexOsgi/com.raytheon.uf.common.aviation/utility/common_static/base/aviation/python/WindRose.py
2022-05-05 12:34:50 -05:00

410 lines
15 KiB
Python

##
# This software was developed and / or modified by Raytheon Company,
# pursuant to Contract DG133W-05-CQ-1067 with the US Government.
#
# U.S. EXPORT CONTROLLED TECHNICAL DATA
# This software product contains export-restricted data whose
# export/transfer/disclosure is restricted by U.S. law. Dissemination
# to non-U.S. persons whether in the United States or abroad requires
# an export license or other authorization.
#
# Contractor Name: Raytheon Company
# Contractor Address: 6825 Pine Street, Suite 340
# Mail Stop B8
# Omaha, NE 68106
# 402.291.0100
#
# See the AWIPS II Master Rights File ("Master Rights File.pdf") for
# further licensing information.
##
#
# Name:
# WindRose.py
# GFS1-NHD:A9008.0000-SCRIPT;31
#
# Status:
# DELIVERED
#
# History:
# Revision 31 (DELIVERED)
# Created: 10-AUG-2009 07:43:51 GILMOREDM
# Now gracefully handles the situation where a user selects
# 'Save for Google Earth' and then clicks 'Cancel' on the
# save image dialog. This was producing an error message.
#
# Revision 30 (DELIVERED)
# Created: 03-AUG-2009 10:11:27 OBERFIEL
# Better computation of lat/lon box for Google Earth to
# preserve aspect ratio,
# what appears as circles in the GUI stays as circles in GE.
# Also size of image is
# configurable -- defaults as 10km. Minor enhancements for
# saving filenames.
#
# Revision 29 (REVIEW)
# Created: 31-JUL-2009 15:09:57 GILMOREDM
# Added final functionality to create KML files
#
# Revision 28 (REVIEW)
# Created: 31-JUL-2009 15:02:49 GILMOREDM
# Added functionality to create KML files for Ground Overlay
# in Google Earth
#
# Revision 27 (DELIVERED)
# Created: 26-FEB-2008 14:20:54 OBERFIEL
# imported re module in CigVisDist; Fix output and help
# documentation in WindRose
#
# Revision 26 (DELIVERED)
# Created: 29-NOV-2007 10:13:40 OBERFIEL
# Removed CR characters.
#
# Revision 25 (REVIEW)
# Created: 28-NOV-2007 10:01:01 GILMOREDM
# Corrected issue that produced a misleading month name
# display.
#
# Revision 24 (DELIVERED)
# Created: 30-OCT-2007 14:18:17 OBERFIEL
# Removed CR characters.
#
# Revision 23 (REVIEW)
# Created: 26-OCT-2007 10:25:56 GILMOREDM
# Changed so that a user can select a range of months and
# hours
#
# Revision 22 (DELIVERED)
# Created: 18-MAY-2007 11:10:59 OBERFIEL
# Sync'd up code between 3.4 and 3.5 worksets since snapshot
#
# Revision 21 (DELIVERED)
# Created: 25-APR-2007 13:04:23 OBERFIEL
# Updated staging script to fix weird timestamp, remove CR
# characters. Fixed final bug in computing scale for Wind
# Rose.
#
# Revision 20 (DELIVERED)
# Created: 24-APR-2007 20:48:33 OBERFIEL
# Fixed so that when scale is NaN that drawing area graphic
# is preserved and error message is issued.
#
# Revision 19 (DELIVERED)
# Created: 18-APR-2007 14:11:33 OBERFIEL
# Updated help documentation to reflect the new 'Auto-Update'
# feature.
#
# Revision 18 (DELIVERED)
# Created: 11-APR-2007 13:51:26 OBERFIEL
# Updated logic to catch additional exceptions and report
# them to user. Canvas should only
# be cleared when all the data are validated and just before
# plotting the new data. Corrected misspelling
# of criteria. Thanks Amanda.
#
# Revision 17 (DELIVERED)
# Created: 21-MAR-2007 09:54:49 OBERFIEL
# For AvnClimate.py: Updated the Help text; MetarMonitorP.py:
# fix potential permission problem;
# WindRose.py: Added Auto Update feature, by default always
# on.
#
# Revision 16 (REVIEW)
# Created: 20-MAR-2007 13:36:54 OBERFIEL
# Updated text input fields and printer dialogs to be
# consistent. Frame tag removed.
#
# Revision 15 (DELIVERED)
# Created: 05-FEB-2007 08:36:51 OBERFIEL
# Fixed misspelling in help documentation and corrected
# validators.
#
# Revision 14 (BUILD_RELEASE)
# Created: 04-JAN-2007 12:55:01 OBERFIEL
# Update print dialog and put a nice feature in.
#
# Revision 13 (DELIVERED)
# Created: 28-DEC-2006 11:14:05 OBERFIEL
# Added message when NaN detected. Redo validation critera
# on EntryField for print dialog.
#
# Revision 12 (DELIVERED)
# Created: 11-AUG-2006 10:28:07 OBERFIEL
# Corrected index to dictionary
#
# Revision 11 (DELIVERED)
# Created: 23-JUN-2006 13:49:36 OBERFIEL
# Updated title of graphic
#
# Revision 10 (DELIVERED)
# Created: 30-MAY-2006 15:10:40 TROJAN
# spr 7144: added auto-update feature, number of years in
# database
#
# Revision 9 (DELIVERED)
# Created: 19-MAY-2006 08:49:22 TROJAN
# spr 7144: added Num Hours counter
#
# Revision 8 (DELIVERED)
# Created: 02-MAY-2006 09:25:36 TROJAN
# SPR 7132: fixed wids array index
#
# Revision 7 (DELIVERED)
# Created: 02-MAY-2006 08:32:41 TROJAN
# SPR 7137: fixed wids array index
#
# Revision 6 (DELIVERED)
# Created: 24-MAR-2006 17:45:45 TROJAN
# spr 7106 Pmw code does not forward python exceptions to
# AvnFPS _Logger
#
# Revision 5 (DELIVERED)
# Created: 24-MAR-2006 09:48:24 TROJAN
# spr 7103: redirect all error messages to a log file
#
# Revision 4 (DELIVERED)
# Created: 16-FEB-2006 14:43:52 TROJAN
# removed unnecessary imports and variables
#
# Revision 3 (APPROVED)
# Created: 15-FEB-2006 14:34:48 TROJAN
# fixes transient dialog behavior - spr 7091
#
# Revision 2 (APPROVED)
# Created: 13-FEB-2006 10:37:29 TROJAN
# data retrieval in separate thread, changed GUI layout
#
# Revision 1 (APPROVED)
# Created: 30-JAN-2006 07:54:16 TROJAN
# stdr 945
#
# Change Document History:
# 1:
# Change Document: GFS1-NHD_SPR_7417
# Action Date: 28-AUG-2009 10:01:43
# Relationship Type: In Response to
# Status: APPROVED
# Title: AvnFPS: TUG code does not handle transition from warm to cold seasons
#
##
# SOFTWARE HISTORY
#
# Date Ticket# Engineer Description
# ------------- -------- --------- --------------------------------------------
# Mar 10, 2022 8808 randerso Update ConfigParser to better work with
# Java commons.configuration
#
##
# This is a base file that is not intended to be overridden.
##
import itertools
import os
import sys
import time
import numpy
import tables
import Avn
import AvnConfigParser
import ClimLib
sys.argv = [__name__]
_Help = {
'title': 'AvnFPS - Wind Rose Display Help',
'content': """
This application displays wind rose for selected month and hour,
or range of hours.
Time selection
Month - selects month.
Num Months - select number of months of data to display
Hour - selects hour.
Num Hours - selects number of hours of data to display
Flight Cat
This option menu restricts the search to flight category
conditions at or below the selected value. "All" means no
restrictions.
If Auto Redraw is selected, changing month, hour, or number of hours
fields will cause the wind rose to be redrawn for any valid value in
these fields.
Use the"Draw" button to display wind rose after selecting new site,
or flight category.
The displayed image can be printed or stored in a graphic file.
Use the options under the "File" menu for that purpose.
"""
}
###############################################################################
# private functions used by WindRose
def _periods(t, delta):
'''Splits event timespan into hourly intervals'''
h0, s0 = divmod(t % 86400, 3600.0)
h1, s1 = divmod((t + delta) % 86400, 3600.0)
if h1 == h0: # consecutive reports within the same hour
return [(h0, (s1 - s0) / 3600.0)]
elif (h1 - h0) % 24 == 1: # next hour
if s1 == 0.0:
return [(h0, 1.0 - s0 / 3600.0)]
else:
return [(h0, 1.0 - s0 / 3600.0), (h1, s1 / 3600.0)]
elif (h1 - h0) % 24 == 2: # 2 hours, but next one may be on the top
if s1 <= 61.0: # allow 1 min late
return [(h0, 1.0 - s0 / 3600.0), ((h1 - 1) % 24, 1.0)]
else:
return []
else:
return []
###############################################################################
class WindRose():
'''Displays wind rose for a site, given month and range of hours'''
AppName = """AvnFPS - Wind Rose"""
Days = list(itertools.accumulate([0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]))
Month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
'Oct', 'Nov', 'Dec']
def get_wind_config(self, fname):
# configure wind plot parameters
# set defaults
self.fx = 4
self.set_wind_dirs(36)
self.winds = [{'color': 'white'}, {'color': 'brown'},
{'color': 'blue', 'val_kt': 5}, {'color': 'green', 'val_kt': 12},
{'color': 'red', 'val_kt': 20}, {'color': 'purple'}]
for wind in self.winds:
if 'val_kt' in wind:
wind['val_mps'] = ClimLib.us2hd_wind_speed(wind['val_kt'])
# read configuration file
if not os.path.isfile(fname):
raise Avn.AvnError('Config file %s does not exist' % fname)
cp = AvnConfigParser.AvnConfigParser()
cp.read(fname)
self.num_winds = cp.getint('wind', 'num_spd')
self.set_wind_dirs(cp.getint('wind', 'num_dir'))
self.winds = []
for tag in ['calm', 'vrb'] + [str(x + 1) for x in range(self.num_winds)]:
section = 'wind_' + tag
color = cp.get(section, 'color')
if cp.has_option(section, 'value'):
v_kt = cp.getint(section, 'value')
v_mps = ClimLib.us2hd_wind_speed(v_kt)
self.winds.append({'val_kt': v_kt, 'val_mps': v_mps, 'color': color})
else:
self.winds.append({'color': color})
def set_wind_dirs(self, num):
# configure number of wind directions
self.num_dirs = num
self.vrb_dir = self.num_dirs
self.calm_dir = self.num_dirs + 1
def get_data(self, month, end_month, flight_cat, id_, fname, configFile, listener):
self.get_wind_config(configFile)
if not os.path.isfile(fname):
raise Avn.AvnError('File %s does not exist' % fname)
with tables.open_file(fname, 'r') as fh:
try:
table = fh.get_node('/obs')
self.wind_stats(table, month, end_month, flight_cat, listener)
except tables.exceptions.NoSuchNodeError:
raise Avn.AvnError('Bad data in %s' % fname)
except Exception as e:
raise Avn.AvnError(e)
def wind_stats(self, table, month, end_month, flight_cat, listener):
yday1 = (self.Days[month - 1] + 1) % 365
yday2 = (self.Days[end_month - 1]) % 366
cig_cat = None
vis_cat = None
if flight_cat < 3:
cig_cat = ClimLib.FlightCats[flight_cat]['cig']
vis_cat = ClimLib.FlightCats[flight_cat]['vis']
def _get(row):
d = {key: row[key] for key in ['date_time', 'wind_dir', 'wind_spd', 'wdir_type']}
return row['date_time'], d
def _filter(row):
if row['wind_spd'] == table.attrs.wind_spd['fill'] and row['wdir_type'] != 'C':
return False
if row['wind_dir'] == table.attrs.wind_dir['fill'] and row['wind_spd'] != 0.0 and row['wdir_type'] not in ['C', 'V']:
return False
if cig_cat and vis_cat:
if row['vis'] > vis_cat and row['cig'] > cig_cat:
return False
return True
def _process(dt, data):
if data['wdir_type'] == 'C' or data['wind_spd'] == 0:
dd_bin = self.calm_dir
elif data['wdir_type'] == 'V':
dd_bin = self.vrb_dir
else:
dd_bin = int((data['wind_dir'] * self.num_dirs) / 360.0 + 0.4)
if dd_bin == self.num_dirs:
dd_bin = 0
if dd_bin != self.calm_dir:
for n, wind in enumerate(self.winds[2:-1]):
if data['wind_spd'] < wind['val_mps']:
ff_bin = n + 1
break
else:
ff_bin = len(self.winds) - 2
else:
ff_bin = 0
periods = _periods(data['date_time'], dt)
if not periods:
print(time.ctime(data['date_time']), dt)
try:
for h, delta in periods:
stats[int(h)][dd_bin][ff_bin] += delta
except Exception as e:
print(e)
if yday1 < yday2:
tmp = [_get(row) for row in
table.where('(yday1<=yday) & (yday<=yday2)')
if _filter(row)]
else:
tmp = ([_get(row)
for row in table.where('(yday1<=yday) & (yday<=366)')
if _filter(row)
] +
[_get(row)
for row in table.where('(0<=yday) & (yday<=yday2)')
if _filter(row)
])
# sort by date
tmp.sort(key=lambda x: x[0])
first_year = time.gmtime(tmp[0][0]).tm_year
last_year = time.gmtime(tmp[-1][0]).tm_year
stats = numpy.zeros((24, 38, self.num_winds + 1), numpy.float32)
tmp = [(y[0] - x[0], x[1])
for x, y in Avn.window(tmp)
if y[0] - x[0] < 4200.0
]
for dt, data in tmp:
if listener.isCanceled():
raise KeyboardInterrupt
_process(dt, data)
listener.sendObj([first_year, last_year])
for hour in range(24):
for wind_dir in range(self.num_dirs + 2):
for wind_spd in range(self.num_winds + 1):
if listener.isCanceled():
raise KeyboardInterrupt
sendObj = [hour, wind_dir, wind_spd, float(stats[hour][wind_dir][wind_spd])]
listener.sendObj(sendObj)
listener.sendObj("done")