Source code for sarracenia.flowcb.poll.noaa_hydrometric

"""
Posts updated files of NOAA water level/temperature hydrometric data. Station site IDs provided
in the poll_noaa_stn_file. Compatible with Python 3.5+.

usage:
sample url: https://tidesandcurrents.noaa.gov/api/datagetter?range=1&station=9450460&product=water_temperature&units=metric&time_zone=gmt&application=web_services&format=csv

in an sr_poll configuration file::

	pollUrl http://tidesandcurrents.noaa.gov/api
        retrievePathPattern /datagetter?range=1&station={0:}&product={1:}&units=metric&time_zone=gmt&application=web_services&format=csv

	poll_noaa_stn_file [path/to/stn/file]
	callback noaa_hydrometric

sample station file::

        7|70678|9751639|Charlotte Amalie|US|VI|-4.0
        7|70614|9440083|Vancouver|US|WA|-8.0

The poll:
If poll_noaa_stn_file isn't set, it'll grab an up-to-date version of all station site code data from the 
NOAA website. The station list file is in the following format:
SourceID | SiteID | SiteCode | SiteName | CountryID | StateID | UTCOffset
Each station on its own line.
Posts the file on the exchange if the request returns a valid URL. 

in v2, one needed a matching downloader plugin, but in sr3 we can leverage the retrievePath feature
so that normalk downloader works, so only the poll one needed.

"""

import copy
import datetime
import logging

import os
import sarracenia
from sarracenia.flowcb import FlowCB
import urllib.request
import xml.etree.ElementTree as ET

logger = logging.getLogger(__name__)


[docs] class Noaa_hydrometric(FlowCB):
[docs] def __init__(self, options): super().__init__(options,logger) # these options are only for the poll. self.o.add_option(option='poll_noaa_stn_file', kind='str') self.o.add_option( option='retrievePathPattern', kind='str', \ default_value='datagetter?range=1&station={0:}&product={1:}&units=metric&time_zone=gmt&application=web_services&format=csv' ) if self.o.identity_method.startswith('cod,'): m, v = self.o.identity_method.split(',') self.identity = {'method': m, 'value': v}
def poll(self) -> list: # Make list of site codes to pass to http get request sitecodes = [] if hasattr(self.o, 'poll_noaa_stn_file'): stn_file = self.o.poll_noaa_stn_file # Parse file to make list of all site codes try: with open(stn_file) as f: for line in f: items = line.split('|') sitecodes.append(items[2]) logger.info("poll_noaa used stn_file %s" % stn_file) except IOError as e: logger.error("poll_noaa couldn't open stn file: %s" % stn_file) else: # Grab station site codes from https://opendap.co-ops.nos.noaa.gov/stations/stationsXML.jsp tree = ET.parse(urllib.request.urlopen\ ('https://opendap.co-ops.nos.noaa.gov/stations/stationsXML.jsp')) root = tree.getroot() for child in root: sitecodes.append(child.attrib['ID']) incoming_message_list = [] # Every hour, form the link of water level/temp data to post for site in sitecodes: retrievePath = self.o.retrievePathPattern.format(site, 'water_temperature') url = self.o.pollUrl + retrievePath logger.info(f'polling {site}, polling: {url}') # Water temp request resp = urllib.request.urlopen(url).getcode() logger.info(f"poll_noaa file posted: {url} %s") mtime = datetime.datetime.utcnow().strftime('%Y%m%d_%H%M') fname = f'noaa_{mtime}_{site}_WT.csv' m = sarracenia.Message.fromFileInfo(fname, self.o) m['identity'] = self.identity m['retrievePath'] = retrievePath m['new_file'] = fname incoming_message_list.append(m) # Water level request retrievePath = self.o.retrievePathPattern.format( site, 'water_level') + '&datum=STND' url = self.o.pollUrl + retrievePath resp = urllib.request.urlopen(url).getcode() logger.info(f"poll_noaa file posted: {url}") fname = f'noaa_{mtime}_{site}_WL.csv' m = sarracenia.Message.fromFileInfo(fname, self.o) m['identity'] = self.identity m['retrievePath'] = retrievePath m['new_file'] = fname incoming_message_list.append(m) return incoming_message_list