Source code for siphon.radarserver

# Copyright (c) 2013-2015 Siphon Contributors.
# Distributed under the terms of the BSD 3-Clause License.
# SPDX-License-Identifier: BSD-3-Clause
"""Support making data requests to the radar data query service (radar server) on a TDS.

This includes forming proper queries as well as parsing the returned catalog.
"""

from collections import namedtuple
import xml.etree.ElementTree as ET

from .catalog import TDSCatalog
from .http_util import BadQueryError, DataQuery, HTTPEndPoint, urljoin


[docs]class RadarQuery(DataQuery): """Represent a query to the THREDDS radar server. Expands on the queries supported by :class:`~siphon.http_util.DataQuery` to add queries specific to the radar data query service. """
[docs] def stations(self, *stns): """Specify one or more stations for the query. This modifies the query in-place, but returns `self` so that multiple queries can be chained together on one line. This replaces any existing spatial queries that have been set. Parameters ---------- stns : one or more strings One or more names of variables to request Returns ------- self : RadarQuery Returns self for chaining calls """ self._set_query(self.spatial_query, stn=stns) return self
[docs]class RadarServer(HTTPEndPoint): """Wrap access to the THREDDS radar query service (radar server). Simplifies access via HTTP to the radar server endpoint. Parses the metadata, provides query catalog results download and parsing based on the appropriate query. Attributes ---------- metadata : :class:`~siphon.metadata.TDSCatalogMetadata` Contains the result of parsing the radar server endpoint's dataset.xml. This has information about the time and space coverage, as well as full information about all of the variables. variables : set(str) Names of all variables available in this dataset stations : dict[str, Station] Mapping of station ID to a :class:`Station`, which is a namedtuple containing the station's id, name, latitude, longitude, and elevation. """
[docs] def __init__(self, url): """Create a RadarServer instance. Parameters ---------- url : str The base URL for the endpoint """ xmlfile = '/dataset.xml' if url.endswith(xmlfile): url = url[:-len(xmlfile)] super(RadarServer, self).__init__(url)
def _get_metadata(self): ds_cat = TDSCatalog(self.url_path('dataset.xml')) self.metadata = ds_cat.metadata self.variables = {k.split('/')[0] for k in self.metadata['variables']} self._get_stations() def _get_stations(self, station_file='stations.xml'): resp = self.get_path(station_file) self.stations = parse_station_table(ET.fromstring(resp.content))
[docs] def query(self): """Return a new query for the radar server. Returns ------- RadarQuery The new query """ return RadarQuery()
[docs] def validate_query(self, query): """Validate a query. Determines whether `query` is well-formed. This includes checking for all required parameters, as well as checking parameters for valid values. Parameters ---------- query : RadarQuery The query to validate Returns ------- valid : bool Whether `query` is valid. """ valid = True # Make sure all stations are in the table if 'stn' in query.spatial_query: valid = valid and all(stid in self.stations for stid in query.spatial_query['stn']) if query.var: valid = valid and all(var in self.variables for var in query.var) return valid
[docs] def get_catalog(self, query): """Fetch a parsed THREDDS catalog from the radar server. Requests a catalog of radar data files data from the radar server given the parameters in `query` and returns a :class:`~siphon.catalog.TDSCatalog` instance. Parameters ---------- query : RadarQuery The parameters to send to the radar server Returns ------- catalog : TDSCatalog The catalog of matching data files Raises ------ :class:`~siphon.http_util.BadQueryError` When the query cannot be handled by the server See Also -------- get_catalog_raw """ # TODO: Refactor TDSCatalog so we don't need two requests, or to do URL munging try: url = self._base[:-1] if self._base[-1] == '/' else self._base url += '?' + str(query) return TDSCatalog(url) except ET.ParseError: raise BadQueryError(self.get_catalog_raw(query))
[docs] def get_catalog_raw(self, query): """Fetch THREDDS catalog XML from the radar server. Requests a catalog of radar data files data from the radar server given the parameters in `query` and returns the raw XML. Parameters ---------- query : RadarQuery The parameters to send to the radar server Returns ------- catalog : bytes The XML of the catalog of matching data files See Also -------- get_catalog """ return self.get_query(query).content
[docs]def get_radarserver_datasets(server): """Get datasets from a THREDDS radar server's top-level catalog. This is a helper function to construct the appropriate catalog URL from the server URL, fetch the catalog, and return the contained catalog references. Parameters ---------- server : string The base URL to the THREDDS server Returns ------- datasets : dict[str, :class:`~siphon.catalog.CatalogRef`] Mapping of dataset name to the catalog reference """ if server[-1] != '/': server += '/' return TDSCatalog(urljoin(server, 'radarServer/catalog.xml')).catalog_refs
# # The remainder of the file is not considered part of the public API. # Use at your own risk! # Station = namedtuple('Station', 'id elevation latitude longitude name')
[docs]def parse_station_table(root): """Parse station list XML file.""" stations = [parse_xml_station(elem) for elem in root.findall('station')] return {st.id: st for st in stations}
[docs]def parse_xml_station(elem): """Create a :class:`Station` instance from an XML tag.""" stid = elem.attrib['id'] name = elem.find('name').text lat = float(elem.find('latitude').text) lon = float(elem.find('longitude').text) elev = float(elem.find('elevation').text) return Station(id=stid, elevation=elev, latitude=lat, longitude=lon, name=name)