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

Reply via email to