{ "cells": [ { "cell_type": "markdown", "id": "aa2f68b4", "metadata": {}, "source": [ "## Accessing gridMET data with the Planetary Computer STAC API\n", "\n", "gridMET is a dataset of daily high-spatial resolution (~4-km, 1/24th degree) surface meteorological data covering the contiguous US from 1979. These data can provide important inputs for ecological, agricultural, and hydrological models.\n", "\n", "This example will show you how to create an animation of air temperature for a year's worth of data.\n", "\n", "## Data Access\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." ] }, { "cell_type": "code", "execution_count": 1, "id": "43495016", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pystac_client\n", "import planetary_computer\n", "\n", "catalog = pystac_client.Client.open(\n", " \"https://planetarycomputer.microsoft.com/api/stac/v1\",\n", " modifier=planetary_computer.sign_inplace,\n", ")\n", "\n", "asset = catalog.get_collection(\"gridmet\").assets[\"zarr-abfs\"]\n", "asset" ] }, { "cell_type": "markdown", "id": "6a3f3e8e", "metadata": {}, "source": [ "Notice that we signed the asset using `planetary_computer`. This places a read-only SAS token in the `xarray:storage_options` dictionary. See more at [Using tokens for data access](https://planetarycomputer.microsoft.com/docs/concepts/sas/).\n", "\n", "Now this asset can be opened with xarray." ] }, { "cell_type": "code", "execution_count": 2, "id": "6e207f4c", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.Dataset>\n",
       "Dimensions:                                    (time: 15341, lat: 585,\n",
       "                                                lon: 1386, crs: 1)\n",
       "Coordinates:\n",
       "  * crs                                        (crs) uint16 3\n",
       "  * lat                                        (lat) float64 49.4 ... 25.07\n",
       "  * lon                                        (lon) float64 -124.8 ... -67.06\n",
       "  * time                                       (time) datetime64[ns] 1979-01-...\n",
       "Data variables:\n",
       "    air_temperature                            (time, lat, lon) float32 dask.array<chunksize=(30, 585, 1386), meta=np.ndarray>\n",
       "    burning_index_g                            (time, lat, lon) float32 dask.array<chunksize=(30, 585, 1386), meta=np.ndarray>\n",
       "    dead_fuel_moisture_1000hr                  (time, lat, lon) float32 dask.array<chunksize=(30, 585, 1386), meta=np.ndarray>\n",
       "    dead_fuel_moisture_100hr                   (time, lat, lon) float32 dask.array<chunksize=(30, 585, 1386), meta=np.ndarray>\n",
       "    mean_vapor_pressure_deficit                (time, lat, lon) float32 dask.array<chunksize=(30, 585, 1386), meta=np.ndarray>\n",
       "    potential_evapotranspiration               (time, lat, lon) float32 dask.array<chunksize=(30, 585, 1386), meta=np.ndarray>\n",
       "    precipitation_amount                       (time, lat, lon) float32 dask.array<chunksize=(30, 585, 1386), meta=np.ndarray>\n",
       "    relative_humidity                          (time, lat, lon) float32 dask.array<chunksize=(30, 585, 1386), meta=np.ndarray>\n",
       "    specific_humidity                          (time, lat, lon) float32 dask.array<chunksize=(30, 585, 1386), meta=np.ndarray>\n",
       "    surface_downwelling_shortwave_flux_in_air  (time, lat, lon) float32 dask.array<chunksize=(30, 585, 1386), meta=np.ndarray>\n",
       "    wind_from_direction                        (time, lat, lon) float32 dask.array<chunksize=(30, 585, 1386), meta=np.ndarray>\n",
       "    wind_speed                                 (time, lat, lon) float32 dask.array<chunksize=(30, 585, 1386), meta=np.ndarray>\n",
       "Attributes: (12/19)\n",
       "    Conventions:                CF-1.6\n",
       "    author:                     John Abatzoglou - University of Idaho, jabatz...\n",
       "    coordinate_system:          EPSG:4326\n",
       "    date:                       02 July 2019\n",
       "    geospatial_bounds:          POLYGON((-124.7666666333333 49.40000000000000...\n",
       "    geospatial_bounds_crs:      EPSG:4326\n",
       "    ...                         ...\n",
       "    geospatial_lon_units:       decimal_degrees east\n",
       "    note1:                      The projection information for this file is: ...\n",
       "    note2:                      Citation: Abatzoglou, J.T., 2013, Development...\n",
       "    note3:                      Data in slices after last_permanent_slice (1-...\n",
       "    note4:                      Data in slices after last_provisional_slice (...\n",
       "    note5:                      Days correspond approximately to calendar day...
" ], "text/plain": [ "\n", "Dimensions: (time: 15341, lat: 585,\n", " lon: 1386, crs: 1)\n", "Coordinates:\n", " * crs (crs) uint16 3\n", " * lat (lat) float64 49.4 ... 25.07\n", " * lon (lon) float64 -124.8 ... -67.06\n", " * time (time) datetime64[ns] 1979-01-...\n", "Data variables:\n", " air_temperature (time, lat, lon) float32 dask.array\n", " burning_index_g (time, lat, lon) float32 dask.array\n", " dead_fuel_moisture_1000hr (time, lat, lon) float32 dask.array\n", " dead_fuel_moisture_100hr (time, lat, lon) float32 dask.array\n", " mean_vapor_pressure_deficit (time, lat, lon) float32 dask.array\n", " potential_evapotranspiration (time, lat, lon) float32 dask.array\n", " precipitation_amount (time, lat, lon) float32 dask.array\n", " relative_humidity (time, lat, lon) float32 dask.array\n", " specific_humidity (time, lat, lon) float32 dask.array\n", " surface_downwelling_shortwave_flux_in_air (time, lat, lon) float32 dask.array\n", " wind_from_direction (time, lat, lon) float32 dask.array\n", " wind_speed (time, lat, lon) float32 dask.array\n", "Attributes: (12/19)\n", " Conventions: CF-1.6\n", " author: John Abatzoglou - University of Idaho, jabatz...\n", " coordinate_system: EPSG:4326\n", " date: 02 July 2019\n", " geospatial_bounds: POLYGON((-124.7666666333333 49.40000000000000...\n", " geospatial_bounds_crs: EPSG:4326\n", " ... ...\n", " geospatial_lon_units: decimal_degrees east\n", " note1: The projection information for this file is: ...\n", " note2: Citation: Abatzoglou, J.T., 2013, Development...\n", " note3: Data in slices after last_permanent_slice (1-...\n", " note4: Data in slices after last_provisional_slice (...\n", " note5: Days correspond approximately to calendar day..." ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import xarray as xr\n", "\n", "ds = xr.open_zarr(\n", " asset.href,\n", " storage_options=asset.extra_fields[\"xarray:storage_options\"],\n", " **asset.extra_fields[\"xarray:open_kwargs\"]\n", ")\n", "ds" ] }, { "cell_type": "markdown", "id": "e0ea6b0c", "metadata": {}, "source": [ "### Animating air temperature\n", "\n", "The dataset contains many variable indexed by `(time, lat, lon)`. We'll use matplotlib to create an animation of air temperature over time." ] }, { "cell_type": "code", "execution_count": 3, "id": "db6e0dbc", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import matplotlib.animation\n", "import cartopy.crs as ccrs\n", "import numpy as np\n", "import numpy.ma\n", "\n", "plt.style.use(\"dark_background\")" ] }, { "cell_type": "markdown", "id": "6c221c94", "metadata": {}, "source": [ "Let's load a year's worth of data." ] }, { "cell_type": "code", "execution_count": 4, "id": "e43815a7", "metadata": {}, "outputs": [], "source": [ "chunk = ds[\"air_temperature\"][:30].compute()" ] }, { "cell_type": "markdown", "id": "f1903e33", "metadata": {}, "source": [ "And now we can make the animation using [`matplotlib.animation.FuncAnimation`](https://matplotlib.org/stable/api/_as_gen/matplotlib.animation.FuncAnimation.html)." ] }, { "cell_type": "code", "execution_count": 5, "id": "066b435f", "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "map_proj = ccrs.LambertConformal(central_longitude=-95, central_latitude=45)\n", "\n", "fig, ax = plt.subplots(figsize=(16, 9), subplot_kw=dict(projection=map_proj))\n", "ax.set_axis_off()\n", "quadmesh = chunk[0].plot(\n", " ax=ax, add_colorbar=False, add_labels=False, transform=ccrs.PlateCarree()\n", ")\n", "\n", "\n", "def animate(i):\n", " a = chunk[i].data.ravel()\n", " a2 = numpy.ma.array(a, mask=np.isnan(a))\n", " quadmesh.set_array(a2)\n", " return [quadmesh]\n", "\n", "\n", "anim = matplotlib.animation.FuncAnimation(fig, animate, frames=len(chunk), interval=120)\n", "anim.save(\"anim.mp4\", fps=15, extra_args=[\"-vcodec\", \"libx264\"])" ] }, { "cell_type": "markdown", "id": "70a7fd02", "metadata": {}, "source": [ "We can use IPython to display the video in the notebook." ] }, { "cell_type": "code", "execution_count": null, "id": "e26b6611-59e4-4867-b8ce-5afbb3056d13", "metadata": {}, "outputs": [], "source": [ "from IPython.display import Video\n", "\n", "Video(\"anim.mp4\")" ] }, { "cell_type": "markdown", "id": "f50bb30f", "metadata": {}, "source": [ "The video will only be embedded if you're running the notebook interactively. If you're just reading this example, you can see the output at\n", "\n", "