I need help implementing a new forecasting service on weewx using the NWS National Digital Forecast Database (https://graphical.weather.gov/xml/). This should allow for much more precise local forecasting than the existing NWS forecast module in weewx. I wrote the attached class and pasted it into forecast.py. But cannot figure out how to get forecast.py to actually run forecasts using this class. Any suggestions? Thanks.
-- You received this message because you are subscribed to the Google Groups "weewx-user" group. To unsubscribe from this group and stop receiving emails from it, send an email to weewx-user+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/weewx-user/c924906c-9acd-4e0a-a49c-767a9c514e1b%40googlegroups.com.
import xml.etree.ElementTree as ET # ----------------------------------------------------------------------------- # National Digital Forecast Database # # Forecasts from the National Digital Forecast Database. Returns xml data. # # For documentation, see: # https://graphical.weather.gov/xml/ # # 12-hour: # pop12 - likelihood of measurable precipitation (1/100 inch) # maxt - temperature in degrees F # mint - temperature in degrees F # # 6-hour: # qpf - quantitative precipitation forecast; amount or range in inches # wgust - only displayed if gusts exceed windspd by 10 mph # # 3-hour: # temp - degrees F # dew - degrees F # rh - relative humidity % # wdir - 8 compass points # wspd - miles per hour # sky - sky coverage # icons - url for weather condition icon # ----------------------------------------------------------------------------- NDFD_KEY = 'NDFD' NDFD_DEFAULT_URL = 'https://graphical.weather.gov/xml/SOAP_server/ndfdXMLclient.php?whichClient=' class NDFDForecast(Forecast): def __init__(self, engine, config_dict): super(NDFDForecast, self).__init__(engine, config_dict, NDFD_KEY, interval=10800) d = config_dict.get('Forecast', {}).get(NDFD_KEY, {}) self.url = d.get('url', NDFD_DEFAULT_URL) self.max_tries = int(d.get('max_tries', 3)) self.location = d.get('location', None) self.units = d.get('units', 'e') errmsg = [] if self.location is None: errmsg.append('location is not specified') if errmsg: for e in errmsg: logerr("%s: %s" % (NDFD_KEY, e)) logerr('%s: forecast will not be run' % NDFD_KEY) return loginf('%s: interval=%s max_age=%s location=%s' % (NDFD_KEY, self.interval, self.max_age, self.location)) self._bind() def get_forecast(self, dummy_event): """Return a parsed forecast.""" text = self.download(location=self.location, url=self.url, units=self.units, max_tries=self.max_tries) if text is None: logerr('%s: no forecast data for %s from %s' % (NDFD_KEY, self.location, self.url)) return None if self.save_raw: self.save_raw_forecast(text, basename='ndfd-raw') records, msgs = self.parse(text, location=self.location) if self.save_failed and len(msgs) > 0: self.save_failed_forecast(text, basename='ndfd-fail', msgs=msgs) loginf('%s: got %d forecast records' % (NDFD_KEY, len(records))) return records @staticmethod def download(location, url=NDFD_DEFAULT_URL, units='e', max_tries=3): """Download a forecast from the National Digital Forecast Database location - location for which the forecast is required, either as a five-digit zipcode or in the format lat,lon. url - URL to the forecast service. if anything other than the default is specified, that entire URL is used. if the default is specified, it is used as the base and other items are added to it. units - units to be used in the forecast, either 'e' for US or 'm' for metric. max_tries - how many times to try before giving up """ if url == NDFD_DEFAULT_URL: begDate = datetime.datetime.utcnow() begStr = begDate.strftime("%Y-%m-%dT00%%3A00%%3A00") endDate = begDate.replace(year=begDate.year+1) endStr = endDate.strftime("%Y-%m-%dT00%%3A00%%3A00") optString = '&product=time-series&begin=' + begStr + '&end=' endStr + '&Unit=' + units +'&maxt=maxt&mint=mint&temp=temp&qpf=qpf&pop12=pop12&dew=dew&wspd=wspd&wdir=wdir&sky=sky&icons=icons&rh=rh&wgust=wgust&Submit=Submit' locPoint = None locZip = re.match(r'^\d{5}$', location.strip()) if locZip is not None: u = url + 'LatLonListZipCode&listZipCodeList=' + str(locZip) + '&Submit=Submit' request = urllib2.Request(u) loginf("%s: downloading latitude and longitude for zipcode %s" % (NDFD_KEY, locZip)) for count in range(max_tries): try: response = urllib2.urlopen(request) tree = ET.parse(response) root = tree.getroot() locPoint = re.match(r'^(-?\d+\.\d+),\s*(-?\d+\.\d+)$', root.find('latLonList').text.strip()) break except (urllib2.URLError, socket.error, httplib.BadStatusLine, httplib.IncompleteRead), e: logerr('%s: failed attempt %d to download location: %s' % (NDFD_KEY, count + 1, e)) else: logerr('%s: failed to download location' % NDFD_KEY) else: locPoint = re.match(r'^(-?\d+\.\d+),\s*(-?\d+\.\d+)$', location.strip()) if locPoint is not None: u = url + 'NDFDgen&lat=' + locPoint.group(1) + '&lon=' + locPoint.group(2) + optString else: u = None else: u = url if u is not None: request = urllib2.Request(u) loginf("%s: downloading forecast from '%s'" % (NDFD_KEY, u)) for count in range(max_tries): try: response = urllib2.urlopen(request) return response except (urllib2.URLError, socket.error, httplib.BadStatusLine, httplib.IncompleteRead), e: logerr('%s: failed attempt %d to download forecast: %s' % (NDFD_KEY, count + 1, e)) else: logerr('%s: failed to download forecast' % NDFD_KEY) return None def date2ts(text): dStr = re.search(r"[1-9]\d{3}-\d{1,2}-\d{1,2}T\d{1,2}:\d{1,2}:\d{1,2}", text) return int(time.mktime(time.strptime(dStr.group(0), "%Y-%m-%dT%H:%M:%S"))) def getValue(var, type, key, idx): srchStr = "./data/parameters/" + var + "[@type='" + type + "']" valBlock = root.find(srchStr) valList = valBlock.findall("value") if key == valBlock.get('time-layout'): return valList[idx] else: return None @staticmethod def parse(text, issued_ts=None, now=None, location=None): """Parse a raw forecast.""" tree = ET.parse(text) root = tree.getroot() createdDate = date2ts(root.find('./head/product/creation-date').text) - (datetime.utcnow() - datetime.now()).seconds if self.units == 'e': usUnits = 1 else: usUnits = 0 allPeriods = {} timeLayouts = {} for timeLayout in root.findall('./data/time-layout'): layoutKey = timeLayout.find('layout-key').text layoutPeriods = [] for idx, startTime in enumerate(timeLayout.findall('start-valid-time')): ts = date2ts(startTime) layoutPeriods.append(ts) allPeriods[ts]['method'] = NDFD_KEY allPeriods[ts]['usUnits'] = usUnits allPeriods[ts]['dateTime'] = createdDate allPeriods[ts]['issued_ts'] = createdDate allPeriods[ts]['event_ts'] = ts allPeriods[ts]['ts'] = ts allPeriods[ts]['duration'] = 10800 allPeriods[ts]['location'] = location allPeriods[ts]['hour'] = time.localtime(ts).tm_hour allPeriods[ts]['windChar'] = None try: allPeriods[ts]['tempMax'] except NameError: allPeriods[ts]['tempMax'] = None if allPeriods[ts]['tempMax'] is None: allPeriods[ts]['tempMax'] = getValue('temperature', 'maximum', layoutKey, idx) try: allPeriods[ts]['tempMin'] except NameError: allPeriods[ts]['tempMin'] = None if allPeriods[ts]['tempMin'] is None: allPeriods[ts]['tempMin'] = getValue('temperature', 'minimum', layoutKey, idx) try: allPeriods[ts]['temp'] except NameError: allPeriods[ts]['temp'] = None if allPeriods[ts]['temp'] is None: allPeriods[ts]['temp'] = getValue('temperature', 'hourly', layoutKey, idx) try: allPeriods[ts]['dewpoint'] except NameError: allPeriods[ts]['dewpoint'] = None if allPeriods[ts]['dewpoint'] is None: allPeriods[ts]['dewpoint'] = getValue('temperature', 'dew point', layoutKey, idx) try: allPeriods[ts]['qpf'] except NameError: allPeriods[ts]['qpf'] = None if allPeriods[ts]['qpf'] is None: allPeriods[ts]['qpf'] = getValue('precipitation ', 'liquid', layoutKey, idx) try: allPeriods[ts]['windSpeed'] except NameError: allPeriods[ts]['windSpeed'] = None if allPeriods[ts]['windSpeed'] is None: allPeriods[ts]['windSpeed'] = getValue('wind-speed', 'sustained', layoutKey, idx) try: allPeriods[ts]['windDir'] except NameError: allPeriods[ts]['windDir'] = None if allPeriods[ts]['windDir'] is None: allPeriods[ts]['windDir'] = Forecast.deg2dir(getValue('direction', 'wind', layoutKey, idx)) try: allPeriods[ts]['clouds'] except NameError: allPeriods[ts]['clouds'] = None if allPeriods[ts]['clouds'] is None: allPeriods[ts]['clouds'] = Forecast.pct2clouds(getValue('cloud-amount', 'total', layoutKey, idx)) try: allPeriods[ts]['pop'] except NameError: allPeriods[ts]['pop'] = None if allPeriods[ts]['pop'] is None: allPeriods[ts]['pop'] = getValue('probability-of-precipitation', '12 hour', layoutKey, idx) try: allPeriods[ts]['windGust'] except NameError: allPeriods[ts]['windGust'] = None if allPeriods[ts]['windGust'] is None: allPeriods[ts]['windGust'] = getValue('wind-speed', 'gust', layoutKey, idx) try: allPeriods[ts]['humidity'] except NameError: allPeriods[ts]['humidity'] = None if allPeriods[ts]['humidity'] is None: allPeriods[ts]['humidity'] = getValue('humidity', 'relative', layoutKey, idx) timeLayouts[layoutKey] = layoutPeriods records = [] for period in allPeriods: records.append(period) msgs = [] return records, msgs