How to search the IOOS CSW catalog with Python tools

This notebook demonstrates a how to query the IOOS Catalog Catalog Service for the Web (CSW), parse resulting records to obtain web data service endpoints, and retrieve data from these service endpoints.

Let’s start by creating the search filters. The filter used here constraints the search on a certain geographical region (bounding box), a time span (last week), and some CF variable standard names that represent sea surface temperature.

from datetime import datetime

# Region: Northwest coast.
bbox = [-127, 43, -123.75, 48]
min_lon, max_lon = -127, -123.75
min_lat, max_lat = 43, 48

bbox = [min_lon, min_lat, max_lon, max_lat]
crs = "urn:ogc:def:crs:OGC:1.3:CRS84"

# Temporal range of 1 week.
start = datetime(2017, 4, 14, 0, 0, 0)
stop = datetime(2017, 4, 21, 0, 0, 0)

# Sea surface temperature CF names.
cf_names = [
    "sea_water_temperature",
    "sea_surface_temperature",
    "sea_water_potential_temperature",
    "equivalent_potential_temperature",
    "sea_water_conservative_temperature",
    "pseudo_equivalent_potential_temperature",
]

With these 3 elements it is possible to assemble a OGC Filter Encoding (FE) using the owslib.fes* module.

* OWSLib is a Python package for client programming with Open Geospatial Consortium (OGC) web service (hence OWS) interface standards, and their related content models.

Although CSW has a built-in feature to find datasets within a specified bounding box, it doesn’t have a feature to find datasets within a specified time interval. We therefore create the function fes_date_filter below that finds all datasets that have at least part of their data within the specified interval. So we find all datasets that start before the end of the interval and stop after the beginning of the interval.

from owslib import fes


def fes_date_filter(start, stop, constraint="overlaps"):
    """
    Take datetime-like objects and returns a fes filter for date range
    (begin and end inclusive).
    NOTE: Truncates the minutes!!!

    Examples
    --------
    >>> from datetime import datetime, timedelta
    >>> stop = datetime(2010, 1, 1, 12, 30, 59).replace(tzinfo=pytz.utc)
    >>> start = stop - timedelta(days=7)
    >>> begin, end = fes_date_filter(start, stop, constraint='overlaps')
    >>> begin.literal, end.literal
    ('2010-01-01 12:00', '2009-12-25 12:00')
    >>> begin.propertyoperator, end.propertyoperator
    ('ogc:PropertyIsLessThanOrEqualTo', 'ogc:PropertyIsGreaterThanOrEqualTo')
    >>> begin, end = fes_date_filter(start, stop, constraint='within')
    >>> begin.literal, end.literal
    ('2009-12-25 12:00', '2010-01-01 12:00')
    >>> begin.propertyoperator, end.propertyoperator
    ('ogc:PropertyIsGreaterThanOrEqualTo', 'ogc:PropertyIsLessThanOrEqualTo')

    """
    start = start.strftime("%Y-%m-%d %H:00")
    stop = stop.strftime("%Y-%m-%d %H:00")
    if constraint == "overlaps":
        propertyname = "apiso:TempExtent_begin"
        begin = fes.PropertyIsLessThanOrEqualTo(propertyname=propertyname, literal=stop)
        propertyname = "apiso:TempExtent_end"
        end = fes.PropertyIsGreaterThanOrEqualTo(
            propertyname=propertyname, literal=start
        )
    elif constraint == "within":
        propertyname = "apiso:TempExtent_begin"
        begin = fes.PropertyIsGreaterThanOrEqualTo(
            propertyname=propertyname, literal=start
        )
        propertyname = "apiso:TempExtent_end"
        end = fes.PropertyIsLessThanOrEqualTo(propertyname=propertyname, literal=stop)
    else:
        raise NameError("Unrecognized constraint {}".format(constraint))
    return begin, end
kw = dict(wildCard="*", escapeChar="\\", singleChar="?", propertyname="apiso:AnyText")

or_filt = fes.Or([fes.PropertyIsLike(literal=("*%s*" % val), **kw) for val in cf_names])

begin, end = fes_date_filter(start, stop)
bbox_crs = fes.BBox(bbox, crs=crs)

filter_list = [
    fes.And(
        [
            bbox_crs,  # bounding box
            begin,
            end,  # start and end date
            or_filt,  # or conditions (CF variable names)
        ]
    )
]
from owslib.csw import CatalogueServiceWeb

endpoint = "https://data.ioos.us/csw"

csw = CatalogueServiceWeb(endpoint, timeout=60)

We have created a csw object, but nothing has been searched yet.

Below we create a get_csw_records function that calls the OSWLib method getrecords2 iteratively to retrieve all the records matching the search criteria specified by the filter_list.

def get_csw_records(csw, filter_list, pagesize=10, maxrecords=1000):
    """Iterate `maxrecords`/`pagesize` times until the requested value in
    `maxrecords` is reached.
    """
    from owslib.fes import SortBy, SortProperty

    # Iterate over sorted results.
    sortby = SortBy([SortProperty("dc:title", "ASC")])
    csw_records = {}
    startposition = 0
    nextrecord = getattr(csw, "results", 1)
    while nextrecord != 0:
        csw.getrecords2(
            constraints=filter_list,
            startposition=startposition,
            maxrecords=pagesize,
            sortby=sortby,
        )
        csw_records.update(csw.records)
        if csw.results["nextrecord"] == 0:
            break
        startposition += pagesize + 1  # Last one is included.
        if startposition >= maxrecords:
            break
    csw.records.update(csw_records)
get_csw_records(csw, filter_list, pagesize=10, maxrecords=1000)

records = "\n".join(csw.records.keys())
print("Found {} records.\n".format(len(csw.records.keys())))
for key, value in list(csw.records.items()):
    print(u"[{}]\n{}\n".format(value.title, key))
Found 70 records.

[SATURN Estuary Station #07, Baker Bay, WA]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46116

[STONEWALL BANK - 20NM West of Newport, OR]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46050

[TILLAMOOK, OR - 85 NM WNW of Tillamook, OR]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46089

[UCSC California Current System ROMS Nowcast 10km]
UCSC

[UMPQUA OFFSHORE, OR (139)]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46229

[UNH Appledore Island CO2]
UNH_WBD

[urn:ioos:station:NOAA.NOS.CO-OPS:9435380 station, South Beach, OR]
opendap.co-ops.nos.noaa.gov-urn_ioos_station_NOAA.NOS.CO-OPS_9435380

[urn:ioos:station:NOAA.NOS.CO-OPS:9440581 station, Cape Disappointment, WA]
opendap.co-ops.nos.noaa.gov-urn_ioos_station_NOAA.NOS.CO-OPS_9440581

[urn:ioos:station:NOAA.NOS.CO-OPS:9440910 station, Toke Point, WA]
opendap.co-ops.nos.noaa.gov-urn_ioos_station_NOAA.NOS.CO-OPS_9440910

[urn:ioos:station:NOAA.NOS.CO-OPS:9442396 station, La Push, Quillayute River, WA]
opendap.co-ops.nos.noaa.gov-urn_ioos_station_NOAA.NOS.CO-OPS_9442396

[Astoria Canyon, OR  (179)]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46248

[CAPE ELIZABETH- 45NM NW of Aberdeen, WA]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46041

[ce_312-20170419T2040-delayed]
org.oceanobservatories:ce_312-20170419T2040-delayed_f070_8f49_1646

[ce_312-20170419T2040-delayed]
ce_312-20170419T2040-delayed

[ce_319-20170410T2034]
org.oceanobservatories:ce_319-20170410T2034_f070_8f49_1646

[ce_319-20170410T2034]
ce_319-20170410T2034

[ce_319-20170410T2034-delayed]
ce_319-20170410T2034-delayed

[ce_319-20170410T2034-delayed]
org.oceanobservatories:ce_319-20170410T2034-delayed_f070_8f49_1646

[ce_320-20170410T1745]
org.oceanobservatories:ce_320-20170410T1745_f070_8f49_1646

[ce_320-20170410T1745]
ce_320-20170410T1745

[ce_320-20170410T1745-delayed]
ce_320-20170410T1745-delayed

[ce_320-20170410T1745-delayed]
org.oceanobservatories:ce_320-20170410T1745-delayed_f070_8f49_1646

[ce_382-20160410T2230]
org.oceanobservatories:ce_382-20160410T2230_f070_8f49_1646

[ce_382-20160410T2230]
ce_382-20160410T2230

[ce_382-20170410T2230]
org.oceanobservatories:ce_382-20170410T2230_f070_8f49_1646

[ce_382-20170410T2230]
ce_382-20170410T2230

[ce_382-20170410T2230-delayed]
ce_382-20170410T2230-delayed

[ce_382-20170410T2230-delayed]
org.oceanobservatories:ce_382-20170410T2230-delayed_f070_8f49_1646

[ce_383-20170131T1942]
org.oceanobservatories:ce_383-20170131T1942_f070_8f49_1646

[ce_383-20170131T1942]
ce_383-20170131T1942

[ce_383-20170131T1942-delayed]
ce_383-20170131T1942-delayed

[ce_384-20170420T0137]
ce_384-20170420T0137

[ce_384-20170420T0137]
org.oceanobservatories:ce_384-20170420T0137_f070_8f49_1646

[ce_384-20170420T0137-delayed]
ce_384-20170420T0137-delayed

[ce_384-20170420T0137-delayed]
org.oceanobservatories:ce_384-20170420T0137-delayed_f070_8f49_1646

[Cha'Ba La Push]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46119

[Clatsop Spit, OR (162)]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46243

[COAMPS 12-hour Total Precipitation forecast, 4km]
COAMPS_4KM_TTL_PRCP

[COAMPS Cloud Base Altitude Forecast, 4km]
COAMPS_4KM_CLD_BASE

[COAMPS Ground and Sea Surface Temperature, 4km]
COAMPS_4KM_GRND_SEA_TEMP

[COAMPS Net short wave radiation at surface, 4km]
COAMPS_4KM_SOL_RAD

[COAMPS Relative Humidity, 4km]
COAMPS_4KM_RLTV_HUM

[COAMPS Visibility Forecast, 4km]
COAMPS_4KM_VISIB

[COLUMBIA RIVER BAR - 20NM West of Columbia River Mouth]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46029

[Columbia River Entrance - SATURN Station #02 Offshore]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46096

[Destruction Island, WA]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_desw1

[Global SST & Sea Ice Analysis, L4 OSTIA, 0.05 deg daily (METOFFICE-GLO-SST-L4-NRT-OBS-SST-V2)]
AOOS_OSTIA

[Grays Harbor, WA (036)]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46211

[Hourly CDIP 46211 Data]
CDIP_46211

[Hourly CDIP 46229 Data]
CDIP_46229

[Hourly CDIP 46248 Data]
CDIP_46248

[Hourly NDBC 46029 data]
NDBC_46029

[Hourly NDBC 46041 data]
NDBC_46041

[Hourly NDBC 46050 data]
NDBC_46050

[Hourly NDBC 46089 data]
NDBC_46089

[Hourly NOS Astoria Met Data]
NOS_9439040_Met

[Hourly NOS Charleston Met Data]
NOS_9432780_Met

[Hourly NOS Garibaldi Met Data]
NOS_9437540_Met

[Hourly NOS Southbeach Met Data]
NOS_9435380_Met

[Hourly NOS Toke Pt Met Data]
NOS_9440910_Met

[NDBC Standard Meteorological Buoy Data, 1970-present]
cwwcNDBCMet

[Newport, OR]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_nwpo3

[NOAA Coral Reef Watch Operational Daily Near-Real-Time Global 5-km Satellite Coral Bleaching Monitoring Products]
dhw_5km

[OOI Newport Shelf]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46097

[OOI Waldport Offshore]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46098

[OOI Westport Offshore]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46100

[OOI Westport Shelf]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46099

[(OSU) NH-10 Buoy, Newport]
data.nanoos.org-urn_ioos_station_nanoos_osu_nh10

[Reedsport, OR (232)]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46261

[SATURN Estuary Station #03]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_seto3

That search returned a lot of records! What if we are not interested in those model results nor global dataset? We can those be excluded from the search with a fes.Not filter.

kw = dict(wildCard="*", escapeChar="\\\\", singleChar="?", propertyname="apiso:AnyText")


filter_list = [
    fes.And(
        [
            bbox_crs,  # Bounding box
            begin,
            end,  # start and end date
            or_filt,  # or conditions (CF variable names).
            fes.Not([fes.PropertyIsLike(literal="*NAM*", **kw)]),  # no NAM results
            fes.Not([fes.PropertyIsLike(literal="*CONUS*", **kw)]),  # no NAM results
            fes.Not([fes.PropertyIsLike(literal="*GLOBAL*", **kw)]),  # no NAM results
            fes.Not([fes.PropertyIsLike(literal="*ROMS*", **kw)]),  # no NAM results
        ]
    )
]

get_csw_records(csw, filter_list, pagesize=10, maxrecords=1000)

records = "\n".join(csw.records.keys())
print("Found {} records.\n".format(len(csw.records.keys())))
for key, value in list(csw.records.items()):
    print(u"[{}]\n{}\n".format(value.title, key))
Found 65 records.

[UNH Appledore Island CO2]
UNH_WBD

[urn:ioos:station:NOAA.NOS.CO-OPS:9435380 station, South Beach, OR]
opendap.co-ops.nos.noaa.gov-urn_ioos_station_NOAA.NOS.CO-OPS_9435380

[urn:ioos:station:NOAA.NOS.CO-OPS:9440581 station, Cape Disappointment, WA]
opendap.co-ops.nos.noaa.gov-urn_ioos_station_NOAA.NOS.CO-OPS_9440581

[urn:ioos:station:NOAA.NOS.CO-OPS:9440910 station, Toke Point, WA]
opendap.co-ops.nos.noaa.gov-urn_ioos_station_NOAA.NOS.CO-OPS_9440910

[urn:ioos:station:NOAA.NOS.CO-OPS:9442396 station, La Push, Quillayute River, WA]
opendap.co-ops.nos.noaa.gov-urn_ioos_station_NOAA.NOS.CO-OPS_9442396

[Astoria Canyon, OR  (179)]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46248

[CAPE ELIZABETH- 45NM NW of Aberdeen, WA]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46041

[ce_312-20170419T2040-delayed]
org.oceanobservatories:ce_312-20170419T2040-delayed_f070_8f49_1646

[ce_312-20170419T2040-delayed]
ce_312-20170419T2040-delayed

[ce_319-20170410T2034]
org.oceanobservatories:ce_319-20170410T2034_f070_8f49_1646

[ce_319-20170410T2034]
ce_319-20170410T2034

[ce_319-20170410T2034-delayed]
ce_319-20170410T2034-delayed

[ce_319-20170410T2034-delayed]
org.oceanobservatories:ce_319-20170410T2034-delayed_f070_8f49_1646

[ce_320-20170410T1745]
org.oceanobservatories:ce_320-20170410T1745_f070_8f49_1646

[ce_320-20170410T1745]
ce_320-20170410T1745

[ce_320-20170410T1745-delayed]
ce_320-20170410T1745-delayed

[ce_320-20170410T1745-delayed]
org.oceanobservatories:ce_320-20170410T1745-delayed_f070_8f49_1646

[ce_382-20160410T2230]
org.oceanobservatories:ce_382-20160410T2230_f070_8f49_1646

[ce_382-20160410T2230]
ce_382-20160410T2230

[ce_382-20170410T2230]
ce_382-20170410T2230

[ce_382-20170410T2230]
org.oceanobservatories:ce_382-20170410T2230_f070_8f49_1646

[ce_382-20170410T2230-delayed]
ce_382-20170410T2230-delayed

[ce_382-20170410T2230-delayed]
org.oceanobservatories:ce_382-20170410T2230-delayed_f070_8f49_1646

[ce_383-20170131T1942]
org.oceanobservatories:ce_383-20170131T1942_f070_8f49_1646

[ce_383-20170131T1942]
ce_383-20170131T1942

[ce_384-20170420T0137]
org.oceanobservatories:ce_384-20170420T0137_f070_8f49_1646

[ce_384-20170420T0137-delayed]
org.oceanobservatories:ce_384-20170420T0137-delayed_f070_8f49_1646

[ce_384-20170420T0137-delayed]
ce_384-20170420T0137-delayed

[Cha'Ba La Push]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46119

[Clatsop Spit, OR (162)]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46243

[COAMPS 12-hour Total Precipitation forecast, 4km]
COAMPS_4KM_TTL_PRCP

[COAMPS Cloud Base Altitude Forecast, 4km]
COAMPS_4KM_CLD_BASE

[COAMPS Ground and Sea Surface Temperature, 4km]
COAMPS_4KM_GRND_SEA_TEMP

[COAMPS IR_heat_flux @ surface, 4km]
COAMPS_4KM_IR_FLUX

[COAMPS Net short wave radiation at surface, 4km]
COAMPS_4KM_SOL_RAD

[COAMPS Visibility Forecast, 4km]
COAMPS_4KM_VISIB

[COLUMBIA RIVER BAR - 20NM West of Columbia River Mouth]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46029

[Columbia River Entrance - SATURN Station #02 Offshore]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46096

[Destruction Island, WA]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_desw1

[Grays Harbor, WA (036)]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46211

[Hourly CDIP 46211 Data]
CDIP_46211

[Hourly CDIP 46229 Data]
CDIP_46229

[Hourly CDIP 46243 Data]
CDIP_46243

[Hourly CDIP 46248 Data]
CDIP_46248

[Hourly NDBC 46029 data]
NDBC_46029

[Hourly NDBC 46050 data]
NDBC_46050

[Hourly NDBC 46089 data]
NDBC_46089

[Hourly NOS Astoria Met Data]
NOS_9439040_Met

[Hourly NOS Charleston Met Data]
NOS_9432780_Met

[Hourly NOS Garibaldi Met Data]
NOS_9437540_Met

[Hourly NOS Southbeach Met Data]
NOS_9435380_Met

[Hourly NOS Toke Pt Met Data]
NOS_9440910_Met

[Lakeside, OR (231)]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46260

[NDBC Standard Meteorological Buoy Data, 1970-present]
cwwcNDBCMet

[Newport, OR]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_nwpo3

[OOI Waldport Offshore]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46098

[OOI Westport Offshore]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46100

[OOI Westport Shelf]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46099

[(OSU) NH-10 Buoy, Newport]
data.nanoos.org-urn_ioos_station_nanoos_osu_nh10

[Reedsport, OR (232)]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46261

[SATURN Estuary Station #03]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_seto3

[SATURN Estuary Station #04 at Mott Basin]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_sefo3

[SATURN Estuary Station #07, Baker Bay, WA]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46116

[STONEWALL BANK - 20NM West of Newport, OR]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46050

[TILLAMOOK, OR - 85 NM WNW of Tillamook, OR]
sdf.ndbc.noaa.gov-urn_ioos_station_wmo_46089

Now we got fewer records to deal with. That’s better. But if the user is interested in only some specific service, it is better to filter by a string, like CO-OPS.

filter_list = [
    fes.And(
        [
            bbox_crs,  # Bounding box
            begin,
            end,  # start and end date
            or_filt,  # or conditions (CF variable names).
            fes.PropertyIsLike(literal="*CO-OPS*", **kw),  # must have CO-OPS
        ]
    )
]

get_csw_records(csw, filter_list, pagesize=10, maxrecords=1000)

records = "\n".join(csw.records.keys())
print("Found {} records.\n".format(len(csw.records.keys())))
for key, value in list(csw.records.items()):
    print("[{}]\n{}\n".format(value.title, key))
Found 4 records.

[urn:ioos:station:NOAA.NOS.CO-OPS:9435380 station, South Beach, OR]
opendap.co-ops.nos.noaa.gov-urn_ioos_station_NOAA.NOS.CO-OPS_9435380

[urn:ioos:station:NOAA.NOS.CO-OPS:9440581 station, Cape Disappointment, WA]
opendap.co-ops.nos.noaa.gov-urn_ioos_station_NOAA.NOS.CO-OPS_9440581

[urn:ioos:station:NOAA.NOS.CO-OPS:9440910 station, Toke Point, WA]
opendap.co-ops.nos.noaa.gov-urn_ioos_station_NOAA.NOS.CO-OPS_9440910

[urn:ioos:station:NOAA.NOS.CO-OPS:9442396 station, La Push, Quillayute River, WA]
opendap.co-ops.nos.noaa.gov-urn_ioos_station_NOAA.NOS.CO-OPS_9442396

The easiest way to get more information is to explorer the individual records. Here is the abstract and subjects from the last station in the list.

import textwrap

value = csw.records[key]

print("\n".join(textwrap.wrap(value.abstract)))
NOAA.NOS.CO-OPS Sensor Observation Service (SOS) Server  This station
provides the following variables: Air pressure, Air temperature, Sea
surface height amplitude due to equilibrium ocean tide, Sea water
temperature, Water surface height above reference datum, Wind from
direction, Wind speed, Wind speed of gust
print("\n".join(value.subjects))
Air Temperature
Barometric Pressure
Conductivity
Currents
Datum
Harmonic Constituents
Rain Fall
Relative Humidity
Salinity
Visibility
Water Level
Water Level Predictions
Water Temperature
Winds
air_pressure
air_temperature
sea_surface_height_amplitude_due_to_equilibrium_ocean_tide
sea_water_temperature
water_surface_height_above_reference_datum
wind_from_direction
wind_speed
wind_speed_of_gust
climatologyMeteorologyAtmosphere

The next step is to inspect the type services/schemes available for downloading the data. The easiest way to accomplish that is with by “sniffing” the URLs with geolinks.

from geolinks import sniff_link

msg = "geolink: {geolink}\nscheme: {scheme}\nURL: {url}\n".format
for ref in value.references:
    print(msg(geolink=sniff_link(ref["url"]), **ref))
geolink: OGC:SOS
scheme: OGC:SOS:DescribeSensor
URL: https://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/SOS?procedure=urn:ioos:station:NOAA.NOS.CO-OPS:9442396&version=1.0.0&service=SOS&outputFormat=text/xml; subtype="sensorML/1.0.1/profiles/ioos_sos/1.0"&request=DescribeSensor

geolink: OGC:SOS
scheme: WWW:LINK - text/csv
URL: http://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/SOS?observedProperty=http://mmisw.org/ont/cf/parameter/air_pressure&service=SOS&responseFormat=text/csv&request=GetObservation&offering=urn:ioos:station:NOAA.NOS.CO-OPS:9442396&eventTime=2020-07-13T15:13:35/2020-07-13T17:13:35&version=1.0.0

geolink: OGC:SOS
scheme: WWW:LINK - text/csv
URL: http://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/SOS?observedProperty=http://mmisw.org/ont/cf/parameter/air_temperature&service=SOS&responseFormat=text/csv&request=GetObservation&offering=urn:ioos:station:NOAA.NOS.CO-OPS:9442396&eventTime=2020-07-13T15:13:35/2020-07-13T17:13:35&version=1.0.0

geolink: OGC:SOS
scheme: WWW:LINK - text/csv
URL: http://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/SOS?observedProperty=http://mmisw.org/ont/cf/parameter/sea_surface_height_amplitude_due_to_equilibrium_ocean_tide&service=SOS&responseFormat=text/csv&request=GetObservation&offering=urn:ioos:station:NOAA.NOS.CO-OPS:9442396&eventTime=2020-07-13T15:13:35/2020-07-13T17:13:35&version=1.0.0

geolink: OGC:SOS
scheme: WWW:LINK - text/csv
URL: http://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/SOS?observedProperty=http://mmisw.org/ont/cf/parameter/sea_water_temperature&service=SOS&responseFormat=text/csv&request=GetObservation&offering=urn:ioos:station:NOAA.NOS.CO-OPS:9442396&eventTime=2020-07-13T15:13:35/2020-07-13T17:13:35&version=1.0.0

geolink: OGC:SOS
scheme: WWW:LINK - text/csv
URL: http://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/SOS?observedProperty=http://mmisw.org/ont/cf/parameter/water_surface_height_above_reference_datum&service=SOS&responseFormat=text/csv&request=GetObservation&offering=urn:ioos:station:NOAA.NOS.CO-OPS:9442396&eventTime=2020-07-13T15:13:35/2020-07-13T17:13:35&version=1.0.0

geolink: OGC:SOS
scheme: WWW:LINK - text/csv
URL: http://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/SOS?observedProperty=http://mmisw.org/ont/cf/parameter/wind_from_direction&service=SOS&responseFormat=text/csv&request=GetObservation&offering=urn:ioos:station:NOAA.NOS.CO-OPS:9442396&eventTime=2020-07-13T15:13:35/2020-07-13T17:13:35&version=1.0.0

geolink: OGC:SOS
scheme: WWW:LINK - text/csv
URL: http://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/SOS?observedProperty=http://mmisw.org/ont/cf/parameter/wind_speed&service=SOS&responseFormat=text/csv&request=GetObservation&offering=urn:ioos:station:NOAA.NOS.CO-OPS:9442396&eventTime=2020-07-13T15:13:35/2020-07-13T17:13:35&version=1.0.0

geolink: OGC:SOS
scheme: WWW:LINK - text/csv
URL: http://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/SOS?observedProperty=http://mmisw.org/ont/cf/parameter/wind_speed_of_gust&service=SOS&responseFormat=text/csv&request=GetObservation&offering=urn:ioos:station:NOAA.NOS.CO-OPS:9442396&eventTime=2020-07-13T15:13:35/2020-07-13T17:13:35&version=1.0.0

geolink: OPeNDAP:OPeNDAP
scheme: WWW:LINK
URL: https://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/images/tide_gauge.jpg

geolink: None
scheme: WWW:LINK
URL: https://tidesandcurrents.noaa.gov/publications/NOAA_Technical_Report_NOS_CO-OPS_030_QC_requirements_doc(revised)-11102004.pdf

geolink: None
scheme: WWW:LINK
URL: https://tidesandcurrents.noaa.gov/stationhome.html?id=9442396

geolink: OGC:SOS
scheme: OGC:SOS
URL: http://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/SOS?request=GetCapabilities&service=SOS&acceptVersions=1.0.0

There are many direct links to Comma Separated Value (CSV) and eXtensible Markup Language (XML) responses to the various variables available in that station.

In addition to those links, there are three very interesting links for more information: 1.) the QC document, 2.) the station photo, 3.) the station home page.

For a detailed description of what those geolink results mean check the lookup table.

The original search was focused on sea water temperature, so there is the need to extract only the endpoint for that variable.

PS: see also the pyoos example for fetching data from CO-OPS.

start, stop
(datetime.datetime(2017, 4, 14, 0, 0), datetime.datetime(2017, 4, 21, 0, 0))
for ref in value.references:
    url = ref["url"]
    if "csv" in url and "sea" in url and "temperature" in url:
        print(msg(geolink=sniff_link(url), **ref))
        break
geolink: OGC:SOS
scheme: WWW:LINK - text/csv
URL: http://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/SOS?observedProperty=http://mmisw.org/ont/cf/parameter/sea_water_temperature&service=SOS&responseFormat=text/csv&request=GetObservation&offering=urn:ioos:station:NOAA.NOS.CO-OPS:9442396&eventTime=2020-07-13T15:13:35/2020-07-13T17:13:35&version=1.0.0

Note that the URL returned by the service has some hard-coded start/stop dates. It is easy to overwrite those with the same dates from the filter.

fmt = (
    "https://opendap.co-ops.nos.noaa.gov/ioos-dif-sos/SOS?"
    "service=SOS&"
    "eventTime={0:%Y-%m-%dT00:00:00}/{1:%Y-%m-%dT00:00:00}&"
    "observedProperty=http://mmisw.org/ont/cf/parameter/sea_water_temperature&"
    "version=1.0.0&"
    "request=GetObservation&offering=urn:ioos:station:NOAA.NOS.CO-OPS:9439040&"
    "responseFormat=text/csv"
)

url = fmt.format(start, stop)

Finally, it is possible to download the data directly into a data pandas data frame and plot it.

import io

import pandas as pd
import requests

r = requests.get(url)

df = pd.read_csv(
    io.StringIO(r.content.decode("utf-8")), index_col="date_time", parse_dates=True
)
%matplotlib inline
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(11, 2.75))
ax = df["sea_water_temperature (C)"].plot(ax=ax)
ax.set_xlabel("")
ax.set_ylabel(r"Sea water temperature ($^\circ$C)")
ax.set_title(value.title)
Text(0.5, 1.0, 'urn:ioos:station:NOAA.NOS.CO-OPS:9442396 station, La Push, Quillayute River, WA')
../../../_images/2016-12-19-exploring_csw_27_1.png