{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Accessing NOAA's Sea Surface Temperature - Optimum Interpolation CDR Climate Data Record (CDR) with the Planetary Computer STAC API\n", "\n", "The NOAA 1/4° daily Optimum Interpolation Sea Surface Temperature (or daily OISST) Climate Data Record (CDR) provides complete ocean temperature fields constructed by combining bias-adjusted observations from different platforms (satellites, ships, buoys) on a regular global grid, with gaps filled in by interpolation. The main input source is satellite data from the Advanced Very High Resolution Radiometer (AVHRR), which provides high temporal-spatial coverage from late 1981-present. This input must be adjusted to the buoys due to erroneous cold SST data following the Mt Pinatubo and El Chichon eruptions. Applications include climate modeling, resource management, ecological studies on annual to daily scales." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Data access\n", "\n", "This notebook works with or without an API key, but you will be given more permissive access to the data with an API key. The [Planetary Computer Hub](https://planetarycomputer.microsoft.com/compute) sets the environment variable \"PC_SDK_SUBSCRIPTION_KEY\" when your server is started. When your Planetary Computer [account request](https://planetarycomputer.microsoft.com/account/request) was approved, a pair of subscription keys were automatically generated for you. You can view your keys by singing in to the [developer portal](https://planetarycomputer.developer.azure-api.net/). The API key may be set manually via the following code:\n", "\n", "```python\n", "pc.settings.set_subscription_key()\n", "```\n", "\n", "The datasets hosted by the Planetary Computer are available from [Azure Blob Storage](https://docs.microsoft.com/en-us/azure/storage/blobs/). We'll use [pystac-client](https://pystac-client.readthedocs.io/) to search the Planetary Computer's [STAC API](https://planetarycomputer.microsoft.com/api/stac/v1/docs) for the subset of the data that we care about, and then we'll load the data directly from Azure Blob Storage. We'll specify a `modifier` so that we can access the data stored in the Planetary Computer's private Blob Storage Containers. See [Reading from the STAC API](https://planetarycomputer.microsoft.com/docs/quickstarts/reading-stac/) and [Using tokens for data access](https://planetarycomputer.microsoft.com/docs/concepts/sas/) for more. \n", "\n", "First, let's fetch all items from the one day in 1981." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "import planetary_computer\n", "import pystac_client\n", "\n", "client = pystac_client.Client.open(\n", " \"https://planetarycomputer.microsoft.com/api/stac/v1\",\n", " modifier=planetary_computer.sign_inplace,\n", ")\n", "item_search = client.search(\n", " collections=\"noaa-cdr-sea-surface-temperature-whoi\",\n", " datetime=\"1988-01-01T22:00:00Z\",\n", ")\n", "item = next(item_search.items())\n", "print(item)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Assets\n", "\n", "Each item has a Cloud Optimized GeoTIFF (COG) asset containing the sea surface temperature." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "tags": [] }, "outputs": [ { "data": { "text/html": [ "
┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n",
       "┃ Key                      Title                               ┃\n",
       "┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩\n",
       "│ fill_missing_qc         │ Quality flag for missing data       │\n",
       "│ sea_surface_temperature │ NOAA CDR of sea surface temperature │\n",
       "│ tilejson                │ TileJSON with default rendering     │\n",
       "│ rendered_preview        │ Rendered preview                    │\n",
       "└─────────────────────────┴─────────────────────────────────────┘\n",
       "
\n" ], "text/plain": [ "┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n", "┃\u001b[1m \u001b[0m\u001b[1mKey \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mTitle \u001b[0m\u001b[1m \u001b[0m┃\n", "┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩\n", "│ fill_missing_qc │ Quality flag for missing data │\n", "│ sea_surface_temperature │ NOAA CDR of sea surface temperature │\n", "│ tilejson │ TileJSON with default rendering │\n", "│ rendered_preview │ Rendered preview │\n", "└─────────────────────────┴─────────────────────────────────────┘\n" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from rich.table import Table\n", "\n", "table = Table(\"Key\", \"Title\")\n", "for key, asset in item.assets.items():\n", " table.add_row(key, asset.title)\n", "table" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Load data\n", "\n", "Let's load all the data into an xarray using **odc-stac**, converting our nodata values to `nan` and scaling the data to degrees Celsius." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "tags": [] }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.Dataset>\n",
       "Dimensions:                  (latitude: 720, longitude: 1440, time: 1)\n",
       "Coordinates:\n",
       "  * latitude                 (latitude) float64 89.88 89.62 ... -89.62 -89.88\n",
       "  * longitude                (longitude) float64 -179.9 -179.6 ... 179.6 179.9\n",
       "    spatial_ref              int32 4326\n",
       "  * time                     (time) datetime64[ns] 1988-01-01T21:00:00\n",
       "Data variables:\n",
       "    sea_surface_temperature  (time, latitude, longitude) float32 nan nan ... nan
" ], "text/plain": [ "\n", "Dimensions: (latitude: 720, longitude: 1440, time: 1)\n", "Coordinates:\n", " * latitude (latitude) float64 89.88 89.62 ... -89.62 -89.88\n", " * longitude (longitude) float64 -179.9 -179.6 ... 179.6 179.9\n", " spatial_ref int32 4326\n", " * time (time) datetime64[ns] 1988-01-01T21:00:00\n", "Data variables:\n", " sea_surface_temperature (time, latitude, longitude) float32 nan nan ... nan" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import odc.stac\n", "\n", "data = odc.stac.load([item], bands=\"sea_surface_temperature\")\n", "data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Visualize\n", "\n", "Now, let's visualize one day's data." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "tags": [] }, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from cartopy import crs\n", "from matplotlib import pyplot\n", "\n", "figure = pyplot.figure(figsize=(12, 8))\n", "axes = pyplot.axes(projection=crs.Mercator())\n", "data[\"sea_surface_temperature\"][0].plot.imshow(cmap=\"RdYlBu_r\", vmin=0, vmax=35);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### NetCDF data\n", "\n", "We recommend using the Cloud-Optimized GeoTIFF assets provided by the `noaa-cdr-sea-surface-temperature-whoi` collection, but if you'd like to use the source NetCDFs that the COGs were created from, you can as well.\n", "Those are stored in the `noaa-cdr-sea-surface-temperature-whoi-netcdf` collection." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "item_search = client.search(\n", " collections=\"noaa-cdr-sea-surface-temperature-whoi-netcdf\",\n", " datetime=\"1988-01-01T22:00:00Z\",\n", ")\n", "item = next(item_search.items())\n", "print(item)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can use `xarray` (via `fsspec`) to access the data in the NetCDF." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "tags": [] }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.Dataset>\n",
       "Dimensions:                  (time: 8, lat: 720, lon: 1440)\n",
       "Coordinates:\n",
       "  * time                     (time) float32 1.5 4.5 7.5 10.5 13.5 16.5 19.5 22.5\n",
       "  * lat                      (lat) float32 -89.88 -89.62 -89.38 ... 89.62 89.88\n",
       "  * lon                      (lon) float32 0.125 0.375 0.625 ... 359.6 359.9\n",
       "Data variables:\n",
       "    sea_surface_temperature  (time, lat, lon) float32 ...\n",
       "    fill_missing_qc          (time, lat, lon) float32 ...\n",
       "Attributes: (12/39)\n",
       "    Conventions:                CF-1.6\n",
       "    title:                      NOAA Climate Data Record of Sea Surface Tempe...\n",
       "    source:                     SEAFLUX-OSB-CDR_V02R00_ATMOS_D19880101_C20160...\n",
       "    Metadata_Conventions:       CF-1.6, Unidata Dataset Discovery v2.0.2, NOA...\n",
       "    standard_name_vocabulary:   CF Standard Name Table (v26, 08 November 2013)\n",
       "    ID:                         SEAFLUX-OSB-CDR_V02R00_SST_D19880101_C2016082...\n",
       "    ...                         ...\n",
       "    cdr_variable:               sea_surface_temperature\n",
       "    metadata_link:              gov.noaa.ncdc:C00972\n",
       "    product_version:            V02R00\n",
       "    platform:                   NASA Earth System Science Pathfinder\n",
       "    sensor:                     AVHRR > Advanced Very High Resolution Radiometer\n",
       "    spatial_resolution:         0.25 degree x 0.25 degree; equal-angle grid
" ], "text/plain": [ "\n", "Dimensions: (time: 8, lat: 720, lon: 1440)\n", "Coordinates:\n", " * time (time) float32 1.5 4.5 7.5 10.5 13.5 16.5 19.5 22.5\n", " * lat (lat) float32 -89.88 -89.62 -89.38 ... 89.62 89.88\n", " * lon (lon) float32 0.125 0.375 0.625 ... 359.6 359.9\n", "Data variables:\n", " sea_surface_temperature (time, lat, lon) float32 ...\n", " fill_missing_qc (time, lat, lon) float32 ...\n", "Attributes: (12/39)\n", " Conventions: CF-1.6\n", " title: NOAA Climate Data Record of Sea Surface Tempe...\n", " source: SEAFLUX-OSB-CDR_V02R00_ATMOS_D19880101_C20160...\n", " Metadata_Conventions: CF-1.6, Unidata Dataset Discovery v2.0.2, NOA...\n", " standard_name_vocabulary: CF Standard Name Table (v26, 08 November 2013)\n", " ID: SEAFLUX-OSB-CDR_V02R00_SST_D19880101_C2016082...\n", " ... ...\n", " cdr_variable: sea_surface_temperature\n", " metadata_link: gov.noaa.ncdc:C00972\n", " product_version: V02R00\n", " platform: NASA Earth System Science Pathfinder\n", " sensor: AVHRR > Advanced Very High Resolution Radiometer\n", " spatial_resolution: 0.25 degree x 0.25 degree; equal-angle grid" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import fsspec\n", "import xarray\n", "from IPython.display import display\n", "\n", "figure = pyplot.figure(figsize=(12, 8))\n", "axes = pyplot.axes(projection=crs.Mercator())\n", "with fsspec.open(item.assets[\"netcdf\"].href) as file:\n", " with xarray.open_dataset(file, decode_times=False) as dataset:\n", " display(dataset)\n", " dataset[\"sea_surface_temperature\"].isel(time=0).squeeze().plot.imshow(\n", " cmap=\"RdYlBu_r\", vmin=0, vmax=35\n", " )" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.9" }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": {}, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 4 }