{ "cells": [ { "cell_type": "markdown", "id": "9dc63b4a-ebb9-41be-bf43-ff4549f37cbd", "metadata": {}, "source": [ "# Animating NEXRAD Level II Data\n", "---" ] }, { "cell_type": "markdown", "id": "79d482d1-ae86-44bc-ae65-7abbc8afe278", "metadata": {}, "source": [ "## Overview\n", " \n", "Within this notebook, we will cover:\n", "\n", "1. Exploring the \"guts\" of a NEXRAD radar file \n", "1. Animating a sequence of AWS-served NEXRAD Level 2 Radar scans\n", "1. Multi-hour plots\n", "\n", "## Prerequisites\n", "| Concepts | Importance | Notes |\n", "| --- | --- | --- |\n", "| [Cartopy Intro](https://foundations.projectpythia.org/core/cartopy/cartopy.html) | Required | Projections and Features |\n", "| [Matplotlib Basics](https://foundations.projectpythia.org/core/matplotlib/matplotlib-basics.html) | Required | Basic plotting |\n", "| [Py-ART Basics](../foundations/pyart-basics) | Required | IO/Visualization |\n", "\n", "- **Time to learn**: 20 minutes\n", "---" ] }, { "cell_type": "markdown", "id": "44203db4-00ab-4fad-8b61-ead013282b80", "metadata": { "tags": [] }, "source": [ "## Imports" ] }, { "cell_type": "code", "execution_count": null, "id": "77872ced-4f00-4520-8e4e-651c7aa2961b", "metadata": { "tags": [] }, "outputs": [], "source": [ "import pyart\n", "import fsspec\n", "from metpy.plots import USCOUNTIES, ctables\n", "import matplotlib.pyplot as plt\n", "import cartopy.crs as ccrs\n", "import cartopy.feature as cfeature\n", "import warnings\n", "from datetime import datetime as dt\n", "from datetime import timedelta\n", "import pandas as pd\n", "import matplotlib\n", "matplotlib.rcParams['animation.html'] = 'html5'\n", "from matplotlib.animation import ArtistAnimation\n", "\n", "warnings.filterwarnings(\"ignore\")" ] }, { "cell_type": "markdown", "id": "87af5f44-55e6-4d23-9523-6e49e2563f88", "metadata": {}, "source": [ "## Select the time and NEXRAD site" ] }, { "cell_type": "code", "execution_count": null, "id": "0e400af6-472d-435e-9a07-2b16978f63f4", "metadata": { "tags": [] }, "outputs": [], "source": [ "start_time = dt(2025,4,26,17)\n", "start_time_str = dt.strftime(start_time, format=\"%Y%m%d%H\")\n", "site = 'KENX'" ] }, { "cell_type": "markdown", "id": "21ed898b-007e-4370-9b00-d0d3d6f4623b", "metadata": {}, "source": [ "### Select number of hours. 1 hour is strongly recommended! " ] }, { "cell_type": "code", "execution_count": null, "id": "74deb5ac-2b0b-4978-9949-4b6ba687e8a1", "metadata": {}, "outputs": [], "source": [ "nHours = 4" ] }, { "cell_type": "markdown", "id": "e53147e6-a08a-444b-a7df-9b2a958c4c3b", "metadata": {}, "source": [ "Point to the [NOAA NEXRAD Level 2 S3 Bucket on AWS](https://noaa-nexrad-level2.s3.amazonaws.com/index.html)" ] }, { "cell_type": "code", "execution_count": null, "id": "79521c33-842a-4eb5-b9f7-e57cf420ea96", "metadata": { "tags": [] }, "outputs": [], "source": [ "fs = fsspec.filesystem(\"s3\", anon=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "beef4aa3-9558-4fe6-a2dc-12b2e5639e6b", "metadata": {}, "outputs": [], "source": [ "fileList = []\n", "for n in range(0, nHours):\n", " datTime = start_time + timedelta(hours = n)\n", " year = dt.strftime(datTime,format=\"%Y\")\n", " month = dt.strftime(datTime,format=\"%m\")\n", " day = dt.strftime(datTime,format=\"%d\")\n", " hour = dt.strftime(datTime,format=\"%H\")\n", " timeStr = f'{year}{month}{day}{hour}'\n", "\n", "# Depending on the year, the radar files will have different naming conventions. \n", " pattern1 = f's3://noaa-nexrad-level2/{year}/{month}/{day}/{site}/{site}{year}{month}{day}_{hour}*V06'\n", " pattern2 = f's3://noaa-nexrad-level2/{year}/{month}/{day}/{site}/{site}{year}{month}{day}_{hour}*V*.gz'\n", " pattern3 = f's3://noaa-nexrad-level2/{year}/{month}/{day}/{site}/{site}{year}{month}{day}_{hour}*.gz'\n", "\n", " files = sorted(fs.glob(pattern1))\n", "\n", " if (len(files) == 0):\n", " files = sorted(fs.glob(pattern2)) \n", "\n", " if (len(files) == 0):\n", " files = sorted(fs.glob(pattern3)) \n", "\n", " fileList.extend(files)" ] }, { "cell_type": "markdown", "id": "e84d8c1d-0f7c-4eb1-ae4d-670ab4537fc2", "metadata": { "tags": [] }, "source": [ "If we have an empty list, either there are no files available for that site/date, or the file name does not match any of the patterns above. " ] }, { "cell_type": "code", "execution_count": null, "id": "ca2573bb-f9be-4720-96c4-7f9c9c081c01", "metadata": { "tags": [] }, "outputs": [], "source": [ "if (len(fileList) == 0):\n", " print (\"There are no files found for this date and location. Either try a different date/site, \\\n", "or browse the NEXRAD2 archive to see if the file name uses a different pattern.\")\n", "else:\n", " print (fileList)" ] }, { "cell_type": "markdown", "id": "2df4be79-bba7-4d94-a4de-a13e4e2a247a", "metadata": {}, "source": [ "## Read the Data into PyART\n", "\n", "Read in the first radar file in the group and list the available fields." ] }, { "cell_type": "code", "execution_count": null, "id": "01b86b2b-78e5-444b-b23b-a7322e6236c6", "metadata": { "tags": [] }, "outputs": [], "source": [ "radar = pyart.io.read_nexrad_archive(f's3://{fileList[0]}')\n", "list(radar.fields)" ] }, { "cell_type": "markdown", "id": "e5452855-71e1-47af-9aea-c38390191635", "metadata": {}, "source": [ "The `radar` object has a lot of useful data ... and *metadata*! One way to look at the attributes of any Python object is to use the `vars` function. It returns a Python `dictionary`; each dictionary `key` has an associated value ... which might be a single value ... a list of values ... another dictionary ... and so on." ] }, { "cell_type": "code", "execution_count": null, "id": "5cf0b6bf-0a52-4a7d-97e5-b54c8a31b1f3", "metadata": { "tags": [] }, "outputs": [], "source": [ "radarDict = vars(radar)" ] }, { "cell_type": "code", "execution_count": null, "id": "7dd8a694-ca11-4fdf-bf39-eb89fd242084", "metadata": { "tags": [] }, "outputs": [], "source": [ "radarDict" ] }, { "cell_type": "markdown", "id": "ad662746-3852-4ac7-b13c-8f0163df0988", "metadata": {}, "source": [ "We can get a list of all the individual keys in the dictionary:" ] }, { "cell_type": "code", "execution_count": null, "id": "ba40b88a-1b89-43fd-b292-a8911c325a7e", "metadata": { "tags": [] }, "outputs": [], "source": [ "radarDict.keys()" ] }, { "cell_type": "markdown", "id": "17b21ae3-9aa0-4949-b792-532ef516dd55", "metadata": {}, "source": [ "The first key is `time`. Let's look at it:" ] }, { "cell_type": "code", "execution_count": null, "id": "5dfe99e1-a72e-40c3-b1f0-5ebcbffb5522", "metadata": { "tags": [] }, "outputs": [], "source": [ "radarDict['time']" ] }, { "cell_type": "markdown", "id": "f42b1d9e-4db8-43f6-b75b-10a3a18c952b", "metadata": {}, "source": [ "It's also a Python dictionary! It's shorter than its parent's ... so we can see that there are six keys. Let's look at the first one, `units`." ] }, { "cell_type": "code", "execution_count": null, "id": "a479ab5a-23ca-414f-acb9-888a1418e5ef", "metadata": { "tags": [] }, "outputs": [], "source": [ "radarDict['time']['units']" ] }, { "cell_type": "markdown", "id": "30df8dd1-cf32-4311-934a-c5312824113c", "metadata": {}, "source": [ "It's a string ... and a useful one ... as it provides a base time that we can use to infer timestamps for the start of each of the individual sweeps that make up the full volume scan of this particular radar file." ] }, { "cell_type": "markdown", "id": "ce1632bd-88c3-4cc9-9550-c5c74c2429fb", "metadata": {}, "source": [ "Let's look at `time`'s `data` key:" ] }, { "cell_type": "code", "execution_count": null, "id": "9151c63a-7e98-4b52-be48-7fd2a16a2eb8", "metadata": { "tags": [] }, "outputs": [], "source": [ "radar.time['data'] # equivalent to: radarDict['time']['data']" ] }, { "cell_type": "markdown", "id": "1a0b2b99-85d6-4f81-86f6-92d358747415", "metadata": {}, "source": [ "
Note: Each key in the top-level radar dictionary can be accessed as an attribute in the radar object itself.
" ] }, { "cell_type": "markdown", "id": "1203facf-52cc-4ecb-b9b6-d8663b7674e2", "metadata": {}, "source": [ "
Question: What does each value represent, in terms of time?
" ] }, { "cell_type": "markdown", "id": "4d9c259b-f0d2-4805-bb6b-f26575163d5b", "metadata": {}, "source": [ "How many elements are in this array?" ] }, { "cell_type": "code", "execution_count": null, "id": "9b6c723f-8485-4020-a54f-2f448a2368c5", "metadata": { "tags": [] }, "outputs": [], "source": [ "len(radar.time['data'])" ] }, { "cell_type": "markdown", "id": "e7cb8e77-9d0b-4d29-87d4-a992dc058fba", "metadata": {}, "source": [ "Other keys include `longitude` and `latitude`. Let's have a look at them:" ] }, { "cell_type": "code", "execution_count": null, "id": "5d982863-4b05-42d4-bb67-2ca810135d6c", "metadata": { "tags": [] }, "outputs": [], "source": [ "radar.longitude, radar.latitude" ] }, { "cell_type": "markdown", "id": "bcb0e298-4507-4b2e-9990-a03977e1cdb2", "metadata": {}, "source": [ "These are also dictionaries! What do you think the `data` keys represent in both of them?" ] }, { "cell_type": "markdown", "id": "da62e74d-5060-46a7-8587-632251af566d", "metadata": {}, "source": [ "Get the central longitude and latitude for the scan, which should correspond to the coordinates of the radar site. Older data may have these coordinates set to 0. If so, get the coordinates from a table, and update the `radar` object." ] }, { "cell_type": "code", "execution_count": null, "id": "06994c3a-b43c-4870-bea9-1b6d4a82369c", "metadata": { "tags": [] }, "outputs": [], "source": [ "def verify_radar_latlon (radar):\n", " if radar.longitude['data'] == 0 or radar.latitude['data'] == 0:\n", " print (f\"No latitude/longitude for {site}; will get them from NEXRAD site table\")\n", " nexrad_sites = pd.read_csv('/spare11/atm350/data/nexrad_sites.csv')\n", " site_row = nexrad_sites.query(\"siteID == @site\")\n", " radar.longitude['data'], radar.latitude['data'] = site_row['X'].values, site_row['Y'].values\n", " return radar" ] }, { "cell_type": "code", "execution_count": null, "id": "6bc17ada-9eb4-46ec-8583-14f76cc02a40", "metadata": {}, "outputs": [], "source": [ "radar = verify_radar_latlon(radar)" ] }, { "cell_type": "markdown", "id": "86fabf32-766f-498f-922f-2a6351c13b5f", "metadata": {}, "source": [ "Let's explore the `sweep_number`, `elevation` and `fixed_angle` keys." ] }, { "cell_type": "code", "execution_count": null, "id": "6ab08fb1-e3b0-494b-89d4-9fc4ced5aae2", "metadata": {}, "outputs": [], "source": [ "radarDict['sweep_number']" ] }, { "cell_type": "code", "execution_count": null, "id": "16dd7700-4c1c-4f83-8c86-87a2d9c96efa", "metadata": { "tags": [] }, "outputs": [], "source": [ "radarDict['elevation']" ] }, { "cell_type": "code", "execution_count": null, "id": "87aa5243-d906-45e1-b7f4-2339a1811b09", "metadata": { "tags": [] }, "outputs": [], "source": [ "radarDict['elevation']['data']" ] }, { "cell_type": "markdown", "id": "7f9b0902-b496-478c-bb25-ab948e04dac7", "metadata": {}, "source": [ "What is the length of the data array associated with elevation?" ] }, { "cell_type": "code", "execution_count": null, "id": "a7346813-8c3b-4071-9496-1101b956fc7d", "metadata": { "tags": [] }, "outputs": [], "source": [ "len(radarDict['elevation']['data'])" ] }, { "cell_type": "code", "execution_count": null, "id": "d0d62ec3-9205-427a-94eb-a2a2822b4152", "metadata": {}, "outputs": [], "source": [ "radarDict['fixed_angle']['data']" ] }, { "cell_type": "markdown", "id": "dcb16550-cf41-491a-b464-edab5476f2a7", "metadata": {}, "source": [ "Notice the first several fixed-elevation angles may be duplicated!" ] }, { "cell_type": "markdown", "id": "22e65c69-d663-461f-ba4f-dbeeaefccb5a", "metadata": {}, "source": [ "Let's look at the number of rays in each sweep." ] }, { "cell_type": "code", "execution_count": null, "id": "28735727-c1a7-4868-9dcf-e3187b63ea1f", "metadata": { "tags": [] }, "outputs": [], "source": [ "ssri = radarDict['sweep_start_ray_index']\n", "ssri" ] }, { "cell_type": "code", "execution_count": null, "id": "38bc1149-1863-4bed-ac3e-6e00f37423d3", "metadata": { "tags": [] }, "outputs": [], "source": [ "seri = radarDict['sweep_end_ray_index']\n", "seri" ] }, { "cell_type": "code", "execution_count": null, "id": "caa7ed8e-ff53-41d4-8130-4072f134dfd5", "metadata": {}, "outputs": [], "source": [ "seri['data'] - ssri['data'] + 1" ] }, { "cell_type": "markdown", "id": "9d45a341-57fc-4837-afc4-64655a9f8bb1", "metadata": {}, "source": [ "
The first eight sweeps, corresponding to the lowest 8 elevation angles, contain 720 rays. The next 12 contain 360.
" ] }, { "cell_type": "markdown", "id": "bcc5bf0d-a64d-4eae-9029-5f394b1ee54f", "metadata": {}, "source": [ "
Question:What does this imply in terms of the resolution of the sweeps for these two groups?
" ] }, { "cell_type": "code", "execution_count": null, "id": "c1305fa5-4c58-4fed-a5ce-7ddf7de9e735", "metadata": { "tags": [] }, "outputs": [], "source": [ "cLon, cLat = radar.longitude['data'], radar.latitude['data']" ] }, { "cell_type": "markdown", "id": "a41d0c7b-52aa-4536-ba7e-e8f4a7304c3c", "metadata": {}, "source": [ "Specify latitude and longitude bounds for the resulting maps, the resolution of the cartographic shapefiles, and the desired sweep level." ] }, { "cell_type": "code", "execution_count": null, "id": "10c457c5-de71-4cf5-b181-51ef2585f395", "metadata": { "tags": [] }, "outputs": [], "source": [ "lonW = cLon - 2\n", "lonE = cLon + 2\n", "latS = cLat - 2\n", "latN = cLat + 2\n", "domain = lonW, lonE, latS, latN\n", "\n", "res = '10m'\n", "sweep = 0" ] }, { "cell_type": "markdown", "id": "b88c92a1-80bc-482d-8105-a3e22b474717", "metadata": {}, "source": [ "Define a function that will determine at which ray a particular sweep begins; also define some strings for the figure title." ] }, { "cell_type": "code", "execution_count": null, "id": "7a2062b8-35f9-444b-bc4d-b7407da6cf16", "metadata": { "tags": [] }, "outputs": [], "source": [ "def nexRadSweepTimeElev (radar, sweep):\n", " \n", " sweepRayIndex = radar.sweep_start_ray_index['data'][sweep]\n", "\n", " baseTimeStr = radar.time['units'].split()[-1]\n", " baseTime = dt.strptime(baseTimeStr, \"%Y-%m-%dT%H:%M:%SZ\")\n", "\n", " timeSweep = baseTime + timedelta(seconds=radar.time['data'][sweepRayIndex])\n", "\n", " timeSweepStr = dt.strftime(timeSweep, format=\"%Y-%m-%d %H:%M:%S UTC\")\n", "\n", " elevSweep = radar.fixed_angle['data'][sweep]\n", " elevSweepStr = f'{elevSweep:.1f}°'\n", " return timeSweepStr, elevSweepStr" ] }, { "cell_type": "code", "execution_count": null, "id": "509e3137-9773-4d3c-9ce2-e3143abf4fbf", "metadata": { "tags": [] }, "outputs": [], "source": [ "radar.sweep_start_ray_index" ] }, { "cell_type": "code", "execution_count": null, "id": "1625e8a7-6afe-4089-8f45-7602d0ffc304", "metadata": { "tags": [] }, "outputs": [], "source": [ "radar.sweep_number" ] }, { "cell_type": "code", "execution_count": null, "id": "23f3764b-1ebc-493a-8f4d-752461127a5b", "metadata": { "tags": [] }, "outputs": [], "source": [ "field = 'reflectivity'\n", "shortName = 'REFL'" ] }, { "cell_type": "markdown", "id": "b477f6a0-dd49-4455-aec9-12f0e7a137a2", "metadata": {}, "source": [ "## Create a single figure of reflectivity, zoomed into the area of interest.\n" ] }, { "cell_type": "markdown", "id": "e8526e21-5c50-4337-bb2c-0ee01d2d3eb4", "metadata": {}, "source": [ "
PPI Display We construct a pseudo Plan Position Indicator (PPI) display. A true PPI display has the radar antenna at its center, with distance from it and the height above ground drawn as concentric circles (Source: Wikipedia)
" ] }, { "cell_type": "code", "execution_count": null, "id": "d9ddb189-ecaa-47d6-8e26-c682be63f1ef", "metadata": {}, "outputs": [], "source": [ "# Creating color tables for reflectivity (every 5 dBZ starting with 5 dBZ):\n", "ref_norm, ref_cmap = ctables.registry.get_with_steps('NWSReflectivity', 5, 5)" ] }, { "cell_type": "code", "execution_count": null, "id": "260f1f9b-8ad8-44ae-9ae2-76ccd93f5764", "metadata": { "tags": [] }, "outputs": [], "source": [ "ref_cmap" ] }, { "cell_type": "markdown", "id": "92c7c83c-c195-4f22-a609-d64d8f8a7a32", "metadata": {}, "source": [ "#### We'll add range rings to the display." ] }, { "cell_type": "code", "execution_count": null, "id": "c5e8df98-d041-451d-bae2-832bfbf68fad", "metadata": { "tags": [] }, "outputs": [], "source": [ "# Call the function that creates the title string, among other things.\n", "timeSweepStr, elevSweepStr = nexRadSweepTimeElev (radar, sweep)\n", "titleStr = f'{site} {shortName} {elevSweepStr} {timeSweepStr}'\n", "\n", "# Create our figure\n", "fig = plt.figure(figsize=[15, 6])\n", "\n", "# Set up a single axes and plot reflectivity\n", "ax = plt.subplot(111, projection=ccrs.PlateCarree())\n", "ax.set_extent ([lonW, lonE, latS, latN])\n", "\n", "display = pyart.graph.RadarMapDisplay(radar)\n", "ref_map = display.plot_ppi_map(field,sweep=sweep, vmin=20, vmax=80, ax=ax, raster=False, title=titleStr,\n", " colorbar_label='Equivalent Relectivity ($Z_{e}$) (dBZ)', norm=ref_norm, cmap=ref_cmap, resolution=res)\n", "range_rings = display.plot_range_rings([10,50,100,200],ax=ax, col='brown',ls='dashed',lw=0.5)\n", "# Add counties\n", "ax.add_feature(USCOUNTIES, linewidth=0.5);" ] }, { "cell_type": "markdown", "id": "c7bb5fb4-beea-45b9-a4b0-7b7d9181baab", "metadata": {}, "source": [ "Next, let's create a function to create a plot, so we can loop over all the radar files of the specified hour." ] }, { "cell_type": "code", "execution_count": null, "id": "b9218c65-bb8d-46ef-89ce-26afa9e14daf", "metadata": { "tags": [] }, "outputs": [], "source": [ "def plot_radar_refl (idx, site, radar):\n", " proj = ccrs.PlateCarree()\n", " # New axes with the specified projection\n", " ax = fig.add_subplot(111, projection=proj)\n", " ax.set_extent(domain)\n", " ax.add_feature(USCOUNTIES.with_scale('5m'),edgecolor='grey', linewidth=1, zorder = 3 )\n", " display = pyart.graph.RadarMapDisplay(radar)\n", " ref_map = display.plot_ppi_map(field,sweep=sweep, cmap=ref_cmap, norm=ref_norm, ax=ax, colorbar_flag = False, \n", " title_flag = False, colorbar_label='Equivalent Relectivity ($Z_{e}$) (dBZ)', resolution=res)\n", " return ax " ] }, { "cell_type": "markdown", "id": "147e85e7-cf3b-4554-8a47-7603f5e6a4e2", "metadata": {}, "source": [ "
Color bar omitted Embedding the colorbar into an animation sequence is non-trivial ... so we do not include a colorbar here.
" ] }, { "cell_type": "markdown", "id": "fee4c8fd-e0d4-4dc9-a6ce-35bf2ea89fa9", "metadata": {}, "source": [ "Loop over the files. Save each image." ] }, { "cell_type": "code", "execution_count": null, "id": "5bc36464-9c42-4f89-a442-414b9d462bd8", "metadata": { "tags": [] }, "outputs": [], "source": [ "backend_ = matplotlib.get_backend() \n", "backend_" ] }, { "cell_type": "markdown", "id": "eae9fbd8-4273-45bd-84dc-ed390f4ab2de", "metadata": { "tags": [] }, "source": [ "Set the Jupyter matplotlib backend to one that will not show the graphics inline (this will save time in notebook execution)" ] }, { "cell_type": "markdown", "id": "dfa92865-0c3d-4db2-9518-2e580e7a249d", "metadata": {}, "source": [ "
Be patient! For an hour's worth of radar files (which can be 15 or so if the radar is in precip mode) this cell, and the animation creation cell that follows, will take a few minutes!
" ] }, { "cell_type": "markdown", "id": "17af450a-ef6d-4153-9b01-7f36cf9d4b5e", "metadata": {}, "source": [ "### Set an increment for how many individual frames we should skip. In general, this should be set to the same value as the number of hours, in order to keep the animation size as well as the time to create the animation manageable." ] }, { "cell_type": "code", "execution_count": null, "id": "41b9c079-8880-4d2b-88d3-31fb0db1e9b5", "metadata": {}, "outputs": [], "source": [ "incr = nHours" ] }, { "cell_type": "code", "execution_count": null, "id": "25ff9711-2d97-4840-acbc-2e4a5bb3b0c5", "metadata": { "tags": [] }, "outputs": [], "source": [ "matplotlib.use(\"Agg\") # Prevent showing stuff\n", "\n", "meshes = []\n", "fig = plt.figure(figsize=(10,10))\n", "for n, name in enumerate(fileList[::nHours]):\n", " print (n, name, site)\n", " radar = pyart.io.read_nexrad_archive(f's3://{name}') \n", " radar = verify_radar_latlon(radar)\n", " ax1 = plot_radar_refl(n, site, radar)\n", " timeSweepStr, elevSweepStr = nexRadSweepTimeElev (radar, sweep)\n", " titleStr = f'{site} {shortName} {elevSweepStr} {timeSweepStr}' \n", " print (titleStr)\n", " title = ax1.text(0.5,0.92,titleStr,horizontalalignment='center',\n", " verticalalignment='center', transform=fig.transFigure, fontsize=15)\n", " meshes.append((ax1,title))" ] }, { "cell_type": "markdown", "id": "974bb09f-4d70-4d4d-b39b-ffbfadb05db1", "metadata": {}, "source": [ "Set the Jupyter matplotlib backend to the default value, so we see output once again." ] }, { "cell_type": "code", "execution_count": null, "id": "88291ec2-d401-4cb9-b527-3e3cd0a5f7cc", "metadata": { "tags": [] }, "outputs": [], "source": [ "matplotlib.use(backend_)" ] }, { "cell_type": "code", "execution_count": null, "id": "4f1ac794-8bf1-4bc3-b10b-06cfa192fc47", "metadata": {}, "outputs": [], "source": [ "anim = ArtistAnimation(fig, meshes)" ] }, { "cell_type": "markdown", "id": "55f9271d-7c5f-4568-9ec1-0b137085a297", "metadata": { "tags": [] }, "source": [ "Display the animation (this may take some time, depending on the number of frames)" ] }, { "cell_type": "code", "execution_count": null, "id": "4c492e6e-0d68-496e-90ad-d01bc6038fba", "metadata": { "tags": [] }, "outputs": [], "source": [ "anim" ] }, { "cell_type": "markdown", "id": "644304a2-6f51-428c-9723-e3041fe45133", "metadata": {}, "source": [ "Save the animation to the current directory (this may also take some time)" ] }, { "cell_type": "code", "execution_count": null, "id": "bf82afc2-4040-4559-97b8-cd403549555a", "metadata": {}, "outputs": [], "source": [ "anim.save(f'{site}_{start_time_str}_refl.mp4')" ] }, { "cell_type": "markdown", "id": "c268e3ac-6532-4d2f-b620-c79d08364cdb", "metadata": {}, "source": [ "---\n", "### Things to try\n", "Create an animation for a time and site of interest. Download the mp4 file and try loading it in Powerpoint.\n" ] }, { "cell_type": "markdown", "id": "6f873ca8-d9f9-42c0-b5ed-a9f9add55fb9", "metadata": {}, "source": [ "#### References\n", "1. [NEXRAD data on AWS](https://registry.opendata.aws/noaa-nexrad/)\n", "2. [Project Pythia Radar Cookbook](https://projectpythia.org/radar-cookbook)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 Jan. 2025 Environment", "language": "python", "name": "jan25" }, "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.12.8" } }, "nbformat": 4, "nbformat_minor": 5 }