The Hyperactive Spring 2024 Tornado Season

A detailed overview of the historic season.
Authors

Andrew Blackford (UAH), Trent Cowan (UAH), Udaysankar Nair (UAH), Josh Wurman (CSU), Karen Kosiba (CSU), David Roueche (AU), Catherine Finley (UND), and Jana Houser (OSU)

Andrew Blackford and Kyle Lesinger (editors)

Published

August 13, 2025

Run This Notebook

πŸš€ Launch in VEDA JupyterHub (requires access)

To obtain credentials to VEDA Hub, follow this link for more information.

Disclaimer: it is highly recommended to run a tutorial within NASA VEDA JupyterHub, which already includes functions for processing and visualizing data specific to VEDA stories. Running the tutorial outside of the VEDA JupyterHub may lead to errors, specifically related to EarthData authentication. Additionally, it is recommended to use the Pangeo workspace within the VEDA JupyterHub, since certain packages relevant to this tutorial are already installed.

If you do not have a VEDA Jupyterhub Account you can launch this notebook on your local environment using MyBinder by clicking the icon below.


Binder

Environment Setup

If running this notebook outside of the VEDA JupyterHub, install the following packages:

#!pip install -q earthaccess pandas xarray fsspec requests branca pystac_client matplotlib

Import the necessary libraries used to run this notebook.

# Load libraries

import glob
import os
import requests
import matplotlib as mpl
import matplotlib.pyplot as plt
import plotutils as putils
from pystac_client import Client
import folium
# For retrieving data already catalogued in VEDA STAC
STAC_API_URL = "https://openveda.cloud/api/stac"
RASTER_API_URL = "https://openveda.cloud/api/raster"

# Open STAC client designed for interacting with SpatioTemporal Asset Catalog (STAC) APIs and Catalogs
client_STAC = Client.open(STAC_API_URL)

The VEDA Data Story on the 2024 Hyperactive Tornado Season can be found HERE

Overview

The Spring 2024 tornado season was one of the most active on record in the United States. As of May 31st, there were 1,176 tornadoes confirmed in the United States. The vast majority of these occurred in the meteorological spring months of March-May, placing this period within the 90th percentile of activity compared to climatological averages. This three-month period was responsible for 36 fatalities, hundreds of injuries, and was the second most prolific tornado season since at least 1950–second only to the deadly 2011 tornado season. Through the end of May, severe thunderstorms alone accounted for nearly 42 billion dollars (USD) in damage across the United States, with four tornado outbreaks during the spring attributing over $1 billion in damage each. Four tornadoes were rated violent EF-4s on the Enhanced Fujita Scale with wind speeds from 166-200 mph, and 27 were rated as intense EF-3s with wind speeds from 136-165 mph.

The National Weather Service (NWS) issued 1,728 tornado warnings across the United States from March to May, with several of these being upgraded to Particularly Dangerous Situation (PDS) Tornado Warnings if confirmed to be large and actively damaging structures. An even smaller subset was upgraded further from PDS to Tornado Emergencies if the tornado was confirmed to be large and was directly threatening a larger population center. As a result, millions of Americans were impacted by tornado warnings during the Spring of 2024, with the vast majority of counties in the Midwest, Southeast, and Southern Great Plains seeing a warning at least once during the three-month period. Compared to average spring severe weather seasons, the Southeast United States saw a less active season, while the Great Plains and Midwest saw a more active season.

Example: NWS DAT Tornado Tracks (March-May 2024)

This example pulls tornado paths from March-May 2024 via the VEDA STAC catalog and visualizes them. These tornado tracks were rasterized from the NWS Damage Assessment Toolkit (DAT), which is a geographic information system (GIS)-hosted dataset where post-storm damage survey results are uploaded and referenced to the location where each description of damage occurred. This dataset includes center lines of all tornado tracks, polygons that break down the EF rating at each point along the path, descriptions of the damage at each survey location, and ground damage pictures of the damage at most entry points.

Processing steps:

1.) Choose collection ID from the STAC catalog and date for analysis
2.) Retrieve collection information and items from VEDA STAC catalog
3.) Retrieve item statistics and tiling information
4.) Plot data

Choose variable and retrieve json from VEDA STAC catalogue

collection_id = "tornadoes-2024-paths"
date = "2024-05-31"

results = client_STAC.search(collections=[collection_id], datetime=date)

# ── VEDA Collection Request ─────────────────────────────────────────────────────────────────────────────────────

items = list(results.items())
assert len(items) != 0, "No items found"
item = items[0]
collection = item.get_collection()

# grab the dashboard render block
dashboard_render = collection.extra_fields["renders"]["dashboard"]

assets = dashboard_render["assets"][0]
# Use the same information as tornado paths for vmin and vmax range
(vmin, vmax) = (0,6)

collection
# ── VEDA Tile Request ─────────────────────────────────────────────────────────────
colormap_name = "tornado_ef_scale"

# Build endpoint URL. Do not add rescale parameters with discrete data
response = requests.get(
    f"{RASTER_API_URL.rstrip('/')}/collections/{collection_id}"
    f"/items/{item.id}/WebMercatorQuad/tilejson.json?"
    f"&assets={assets}"
    f"&colormap_name={colormap_name}"
)

response.raise_for_status()

tiles = response.json()
print(tiles)
{'tilejson': '2.2.0', 'version': '1.0.0', 'scheme': 'xyz', 'tiles': ['https://openveda.cloud/api/raster/collections/tornadoes-2024-paths/items/Tornado_Tracks_cog_2024-05-31/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?assets=cog_default&colormap_name=tornado_ef_scale'], 'minzoom': 0, 'maxzoom': 24, 'bounds': [-103.501720443, 18.467731627, -66.701720443, 46.592731627], 'center': [-85.101720443, 32.530231627, 0]}

Plot data

# First we will present the different basemaps that we have access to underlay beneath our tile requests
# For the first map, we will utilize the 'esri-satellite-labels' map layer
putils.get_available_basemaps()
{'openstreetmap': 'OpenStreetMap standard tiles',
 'cartodb-positron': 'Light gray CartoDB basemap (subtle, good for data visualization)',
 'cartodb-dark': 'Dark CartoDB basemap (good for bright data)',
 'esri-satellite': 'ESRI satellite imagery without labels',
 'esri-satellite-labels': 'ESRI satellite imagery with place labels overlay',
 None: 'No basemap (transparent background)'}
# Define EF scale categories with specific colors to replicate predefined colorscale in VEDA backend. 
# Colors have been predifined in veda-backend https://github.com/NASA-IMPACT/veda-backend/tree/develop/raster_api/runtime/src/cmap_data#tornadoes-colormap

ef_categories = [
    {"color": "#add8e6", "label": "EF0", "value": 0},  # Light blue
    {"color": "#90ee90", "label": "EF1", "value": 1},  # Green
    {"color": "#ffe71f", "label": "EF2", "value": 2},  # Yellow
    {"color": "#ffa500", "label": "EF3", "value": 3},  # Orange
    {"color": "#ff0000", "label": "EF4", "value": 4},  # Red
    {"color": "#ff00ff", "label": "EF5", "value": 5},  # Pink
    {"color": "#b3bcc9", "label": "EFU", "value": 6}   # Gray-blue (Unknown)
]

# Extract colors from ef_categories
ef_colors = [cat["color"] for cat in ef_categories]
n_bins = len(ef_colors)

# Create a discrete colormap
cmap_tornado = mpl.colors.ListedColormap(ef_colors, name='tornado_ef_scale')


# Register the colormap with matplotlib using the new method [for newer matplotlib versions (3.5+)]
try:
    plt.colormaps.register(cmap_tornado)
except ValueError:
    pass #Custom colormap scale already applied. This is necessary if you re-run this cell block.

m = putils.plot_folium_from_VEDA_STAC(
    tiles_url_template=tiles["tiles"][0],
    center_coords=[41.31, -94.46],
    zoom_level=9,
    rescale=(vmin, vmax),
    colormap_name='tornado_ef_scale',
    custom_colors=ef_categories,
    capitalize_cmap=False,
    layer_name="Tornado Tracks",
    date="(March-May 2024)",
    colorbar_caption="EF Rating",
    attribution="VEDA - NWS DAT Tornado Tracks",
    tile_name="Tornado Tracks (March-May 2024)",
    opacity=0.8,
    height="800px",
    remove_default_legend=False,  # Only show custom legend
    basemap_style="esri-satellite-labels"  # Use ESRI satellite with city/town labels
)

# Display the map
m
Make this Notebook Trusted to load map: File -> Trust Notebook

Example: NWS Tornado Polygons (March-May 2024)

Pull the NWS Tornado Polygon data from the VEDA STAC catalog and visualize

collection_id = "tornadoes-2024-polygons"
date = "2024-05-31"

results = client_STAC.search(collections=[collection_id], datetime=date)

# ── VEDA Collection Request ─────────────────────────────────────────────────────────────────────────────────────

items = list(results.items())
assert len(items) != 0, "No items found"
item = items[0]
collection = item.get_collection()

# grab the dashboard render block
dashboard_render = collection.extra_fields["renders"]["dashboard"]

assets = dashboard_render["assets"][0]
# Use 0-6 range to include EFU (Unknown) category (the same as the last map for tornado tracks)
(vmin, vmax) = (0,6)

collection
# ── VEDA Tile Request ─────────────────────────────────────────────────────────────
# Use the same tornado_ef_scale colormap for polygons
colormap_name = "tornado_ef_scale"

# Build endpoint URL - NO RESCALE for discrete categorical data
response = requests.get(
    f"{RASTER_API_URL.rstrip('/')}/collections/{collection_id}"
    f"/items/{item.id}/WebMercatorQuad/tilejson.json?"
    f"&assets={assets}"
    f"&colormap_name={colormap_name}"
    # Removed rescale completely - let the server use raw integer values
)

response.raise_for_status()

tiles = response.json()
tiles
{'tilejson': '2.2.0',
 'version': '1.0.0',
 'scheme': 'xyz',
 'tiles': ['https://openveda.cloud/api/raster/collections/tornadoes-2024-polygons/items/Tornado_Polygons_2024-05-31_cog/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?assets=cog_default&colormap_name=tornado_ef_scale'],
 'minzoom': 0,
 'maxzoom': 24,
 'bounds': [-104.72280125674247,
  27.9971496536041,
  -71.9717534735214,
  46.847529413454325],
 'center': [-88.34727736513193, 37.42233953352921, 0]}
# Create the base map using the unified function (ef_categories already defined above)
# NOTE: Not passing rescale for discrete data
m = putils.plot_folium_from_VEDA_STAC(
    tiles_url_template=tiles["tiles"][0],
    center_coords=[40.30, -84.05],
    zoom_level=10.25,
    rescale=(vmin, vmax),
    colormap_name='tornado_ef_scale',
    custom_colors=ef_categories,
    capitalize_cmap=False,
    layer_name="Tornado Tracks [Polygons]",
    date="(March-May 2024)",
    colorbar_caption="EF Rating",
    attribution="VEDA - NWS DAT Tornado Polygons",
    tile_name="Tornado Tracks [Polygons] (March-May 2024)",
    opacity=0.8,
    height="800px",
    remove_default_legend=True,  # Only show custom legend
    basemap_style="esri-satellite-labels"  # Use ESRI satellite with city/town labels
)

# Display the map
m
Make this Notebook Trusted to load map: File -> Trust Notebook

Example: Black Marble Nightlights

Pull the NASA Black Marble Nightlights data from from the VEDA STAC catalog and visualize

NASA’s Black Marble night lights dataset provides satellite images of Earth at night, capturing the light pollution from cities, roads, and other human activity. These images are collected by the Visible Infrared Imaging Radiometer Suite (VIIRS) sensor on the Suomi National Polar-orbiting Partnership (NPP) satellite, and can be used to examine changes in illumination over time. In this story, night lights data is used to assess changes before and after a tornado impacts a town, highlighting areas affected by power outages that may cause recovery slowdowns.

collection_id = "lakeview-nightlights-tornadoes-2024"
date = "2024-03-14"

results = client_STAC.search(collections=[collection_id], datetime=date)

# ── VEDA Collection Request ─────────────────────────────────────────────────────────────────────────────────────

items = list(results.items())
assert len(items) != 0, "No items found"
item = items[0]
collection = item.get_collection()

# grab the dashboard render block
dashboard_render = collection.extra_fields["renders"]["dashboard"]

assets = dashboard_render["assets"][0]
((vmin, vmax),) = dashboard_render["rescale"]

collection
# ── VEDA Tile Request ─────────────────────────────────────────────────────────────────────────────────────
colormap_name = "bwr"

# Build endpoint URL without worrying about trailing slashes
response = requests.get(
    f"{RASTER_API_URL.rstrip('/')}/collections/{collection_id}"
    f"/items/{item.id}/WebMercatorQuad/tilejson.json?"
    f"&assets={assets}"
    f"&color_formula=gamma+r+1.05&colormap_name={colormap_name}"
    f"&rescale={vmin},{vmax}",
)

response.raise_for_status()

tiles = response.json()
print(tiles)
{'tilejson': '2.2.0', 'version': '1.0.0', 'scheme': 'xyz', 'tiles': ['https://openveda.cloud/api/raster/collections/lakeview-nightlights-tornadoes-2024/items/nightlights_LakeviewOH_diff_cog_2024-03-14/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?assets=cog_default&color_formula=gamma+r+1.05&colormap_name=bwr&rescale=-150%2C150'], 'minzoom': 0, 'maxzoom': 24, 'bounds': [-84.2954, 40.20127368220017, -83.57612536287245, 40.69122631779985], 'center': [-83.93576268143622, 40.446250000000006, 0]}
# Use the new plot_folium_from_VEDA_STAC function
m = putils.plot_folium_from_VEDA_STAC(
    tiles_url_template=tiles["tiles"][0],
    center_coords=[40.496, -83.884],
    zoom_level=12.5,
    rescale=(vmin, vmax),
    colormap_name=colormap_name,
    capitalize_cmap=False,  # to better match VEDA colors and matplotlib colors
    layer_name="Black Marble Nightlights (Indian Lake, OH)",
    date=f"{date}T00:00:00Z",
    colorbar_caption="Artificial Light",
    attribution="VEDA Black Marble Nightlights (Indian Lake, OH)",
    tile_name="Black Marble Nightlights (Indian Lake, OH)",
    opacity=0.8,
    height="800px",
    basemap_style="cartodb-dark"  # Use dark basemap for better nightlight visibility
)

print(
    "Visualization of Indian Lake, Ohio showing large decreases in artificial light emissions after an EF-3 tornado on March 14, 2024."
)
# Display the map
m
Visualization of Indian Lake, Ohio showing large decreases in artificial light emissions after an EF-3 tornado on March 14, 2024.
Make this Notebook Trusted to load map: File -> Trust Notebook

Example: DOW7 Base Reflectivity and Velocity (Radar)

Pull the DOW7 radar data from from the VEDA STAC catalog and visualize using a side-by-side layer slider

The Doppler on Wheels (DOW) radar, which is mounted on vehicles and can get closer to storms, can be examined in cases where it was deployed in order to gather more detailed information than the NWS regional WSR-88D radars. DOW radars are typically used for research and can capture clearer, higher-resolution data on tornadoes, while WSR-88D provides a wider view but with slightly less detail.

# Retrieve both DOW7 reflectivity and velocity data from VEDA STAC

date = "2024-04-26" #Use the same date for both reflectivity and velocity

# ── DOW7 Base Reflectivity ─────────────────────────────────────────────────────────────────────────────────────

# Get reflectivity collection and tiles
collection_id_refl = "tornadoes-2024-dow-refl-harlan"
results_refl = client_STAC.search(collections=[collection_id_refl], datetime=date)
items_refl = list(results_refl.items())
item_refl = items_refl[0]
collection_refl = item_refl.get_collection()
dashboard_render_refl = collection_refl.extra_fields["renders"]["dashboard"]
assets_refl = dashboard_render_refl["assets"][0]
((vmin_refl, vmax_refl),) = dashboard_render_refl["rescale"]
colormap_name = collection_refl.extra_fields['renders']['dashboard']['colormap_name']

# Build reflectivity tile URL
response_refl = requests.get(
    f"{RASTER_API_URL.rstrip('/')}/collections/{collection_id_refl}"
    f"/items/{item_refl.id}/WebMercatorQuad/tilejson.json?"
    f"&assets={assets_refl}"
    f"&color_formula=gamma+r+1.05&colormap_name={colormap_name}"
    f"&rescale={vmin_refl},{vmax_refl}",
)
response_refl.raise_for_status()
tiles_refl = response_refl.json()

# ── DOW7 Velocity ─────────────────────────────────────────────────────────────────────────────────────

# Get velocity collection and tiles
collection_id_vel = "tornadoes-2024-dow-v-harlan"
results_vel = client_STAC.search(collections=[collection_id_vel], datetime=date)
items_vel = list(results_vel.items())
item_vel = items_vel[0]
collection_vel = item_vel.get_collection()
dashboard_render_vel = collection_vel.extra_fields["renders"]["dashboard"]
assets_vel = dashboard_render_vel["assets"][0]
((vmin_vel, vmax_vel),) = dashboard_render_vel["rescale"]
colormap_name = collection_vel.extra_fields['renders']['dashboard']['colormap_name']

# Build velocity tile URL
response_vel = requests.get(
    f"{RASTER_API_URL.rstrip('/')}/collections/{collection_id_vel}"
    f"/items/{item_vel.id}/WebMercatorQuad/tilejson.json?"
    f"&assets={assets_vel}"
    f"&color_formula=gamma+r+1.05&colormap_name={colormap_name}"
    f"&rescale={vmin_vel},{vmax_vel}",
)
response_vel.raise_for_status()
tiles_vel = response_vel.json()
#View DOW7 reflectivity collection
collection_refl
#View DOW7 velocity collection
collection_vel
# Use the new plot_folium_SidebySide_layer_from_VEDA_STAC function from plotutils
# Get the colormap names from the collections
colormap_refl = collection_refl.extra_fields['renders']['dashboard']['colormap_name']
colormap_vel = collection_vel.extra_fields['renders']['dashboard']['colormap_name']

m = putils.plot_folium_SidebySide_layer_from_VEDA_STAC(
    tiles_url_left=tiles_refl["tiles"][0],
    tiles_url_right=tiles_vel["tiles"][0],
    center_coords=[41.668, -95.372],
    zoom_level=14,
    title="DOW7 Comparison β€” Harlan, IA β€” April 26, 2024",
    label_left="← Reflectivity",
    label_right="Velocity β†’",
    layer_name_left="DOW7 Reflectivity",
    layer_name_right="DOW7 Velocity",
    opacity=0.8,
    basemap_style='esri-satellite-labels',
    height="800px",
    width="100%",
    # New parameters for HTML colorbars
    colormap_left=colormap_refl,
    colormap_right=colormap_vel,
    rescale_left=(vmin_refl, vmax_refl),
    rescale_right=(vmin_vel, vmax_vel),
    units_left="dBZ",
    units_right="m/s"
)

print("Interactive side-by-side comparison of DOW7 reflectivity and velocity data.")
print("Drag the vertical slider to reveal more of either dataset.")
print()

# Display the map
m
Interactive side-by-side comparison of DOW7 reflectivity and velocity data.
Drag the vertical slider to reveal more of either dataset.
Make this Notebook Trusted to load map: File -> Trust Notebook

Example: DOW7 Correlation Coefficient (Radar)

Pull the DOW7 radar data from from the VEDA STAC catalog and visualize

# Retrieve both DOW7 correlation coefficient and velocity data from VEDA STAC

date = "2024-05-21" #Use the same date for both reflectivity and velocity

# ── DOW7 Correlation Coefficient ─────────────────────────────────────────────────────────────────────────────────────

# Get reflectivity collection and tiles
collection_id_rhohv = "tornadoes-2024-dow-rhohv-greenfield"
results_rhohv = client_STAC.search(collections=[collection_id_rhohv], datetime=date)
items_rhohv = list(results_rhohv.items())
item_rhohv = items_rhohv[0]
collection_rhohv = item_rhohv.get_collection()
dashboard_render_rhohv = collection_rhohv.extra_fields["renders"]["dashboard"]
assets_rhohv = dashboard_render_rhohv["assets"][0]
((vmin_rhohv, vmax_rhohv),) = dashboard_render_rhohv["rescale"]
colormap_name = collection_rhohv.extra_fields['renders']['dashboard']['colormap_name']

# Build correlation coefficient tile URL
response_rhohv = requests.get(
    f"{RASTER_API_URL.rstrip('/')}/collections/{collection_id_rhohv}"
    f"/items/{item_rhohv.id}/WebMercatorQuad/tilejson.json?"
    f"&assets={assets_rhohv}"
    f"&color_formula=gamma+r+1.05&colormap_name={colormap_name}"
    f"&rescale={vmin_rhohv},{vmax_rhohv}",
)
response_rhohv.raise_for_status()
tiles_rhohv = response_rhohv.json()

# ── DOW7 Velocity ─────────────────────────────────────────────────────────────────────────────────────

# Get velocity collection and tiles
collection_id_vel = "tornadoes-2024-dow-vg-greenfield"
results_vel = client_STAC.search(collections=[collection_id_vel], datetime=date)
items_vel = list(results_vel.items())
item_vel = items_vel[0]
collection_vel = item_vel.get_collection()
dashboard_render_vel = collection_vel.extra_fields["renders"]["dashboard"]
assets_vel = dashboard_render_vel["assets"][0]
((vmin_vel, vmax_vel),) = dashboard_render_vel["rescale"]
colormap_name = collection_vel.extra_fields['renders']['dashboard']['colormap_name']

# Build velocity tile URL
response_vel = requests.get(
    f"{RASTER_API_URL.rstrip('/')}/collections/{collection_id_vel}"
    f"/items/{item_vel.id}/WebMercatorQuad/tilejson.json?"
    f"&assets={assets_vel}"
    f"&color_formula=gamma+r+1.05&colormap_name={colormap_name}"
    f"&rescale={vmin_vel},{vmax_vel}",
)
response_vel.raise_for_status()
tiles_vel = response_vel.json()
# Use the new plot_folium_SidebySide_layer_from_VEDA_STAC function from plotutils
# Get the colormap names from the collections
colormap_rhohv = collection_rhohv.extra_fields['renders']['dashboard']['colormap_name']
colormap_vel = collection_vel.extra_fields['renders']['dashboard']['colormap_name']

m = putils.plot_folium_SidebySide_layer_from_VEDA_STAC(
    tiles_url_left=tiles_rhohv["tiles"][0],
    tiles_url_right=tiles_vel["tiles"][0],
    center_coords=[41.3036, -94.4569],
    zoom_level=16,
    title="DOW7 Comparison β€” Greenfield, IA β€” May 21, 2024",
    label_left="← Correlation Coefficient",
    label_right="Velocity β†’",
    layer_name_left="DOW7 Correlation Coefficient",
    layer_name_right="DOW7 Velocity",
    opacity=0.8,
    basemap_style='esri-satellite-labels',
    height="800px",
    width="100%",
    # New parameters for HTML colorbars
    colormap_left=colormap_rhohv,
    colormap_right=colormap_vel,
    rescale_left=(vmin_rhohv, vmax_rhohv),
    rescale_right=(vmin_vel, vmax_vel),
    units_left="",  # Correlation coefficient is unitless
    units_right="m/s"
)

print("Interactive side-by-side comparison of DOW7 correlation coefficient and velocity data.")
print("Drag the vertical slider to reveal more of either dataset.")
print("The lower correlation coefficient values that are collocated with the high velocities indicate the tornado throwing debris in the air.")
print()

# Display the map
m
Interactive side-by-side comparison of DOW7 correlation coefficient and velocity data.
Drag the vertical slider to reveal more of either dataset.
The lower correlation coefficient values that are collocated with the high velocities indicate the tornado throwing debris in the air.
Make this Notebook Trusted to load map: File -> Trust Notebook

Example: DOW7-Derived Maximum Velocity Swath (Radar)

Pull the DOW7 radar data from from the VEDA STAC catalog and visualize

collection_id = "tornadoes-2024-dow-vmax-greenfield"
date = "2024-05-21"

# ── VEDA Collection Request ─────────────────────────────────────────────────────────────────────────────────────

results = client_STAC.search(collections=[collection_id], datetime=date)

items = list(results.items())
assert len(items) != 0, "No items found"
item = items[0]
collection = item.get_collection()

# grab the dashboard render block
dashboard_render = collection.extra_fields["renders"]["dashboard"]

assets = dashboard_render["assets"][0]
((vmin, vmax),) = dashboard_render["rescale"]

collection
# ── VEDA Tile Request ─────────────────────────────────────────────────────────────────────────────────────
colormap_name = "gist_ncar"

# Build endpoint URL without worrying about trailing slashes
response = requests.get(
    f"{RASTER_API_URL.rstrip('/')}/collections/{collection_id}"
    f"/items/{item.id}/WebMercatorQuad/tilejson.json?"
    f"&assets={assets}"
    f"&color_formula=gamma+r+1.05&colormap_name={colormap_name}"
    f"&rescale={vmin},{vmax}",
)

response.raise_for_status()

tiles = response.json()
print(tiles)
{'tilejson': '2.2.0', 'version': '1.0.0', 'scheme': 'xyz', 'tiles': ['https://openveda.cloud/api/raster/collections/tornadoes-2024-dow-vmax-greenfield/items/DOW_Vmax_Greenfield_mph_cog_2024-05-21/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?assets=cog_default&color_formula=gamma+r+1.05&colormap_name=gist_ncar&rescale=55%2C250'], 'minzoom': 0, 'maxzoom': 24, 'bounds': [-94.4784, 41.29080000000008, -94.416, 41.31980000000008], 'center': [-94.4472, 41.30530000000008, 0]}
# Use the new plot_folium_from_VEDA_STAC function
m = putils.plot_folium_from_VEDA_STAC(
    tiles_url_template=tiles["tiles"][0],
    center_coords=[41.3036, -94.4569],
    zoom_level=15.5,
    rescale=(vmin, vmax),
    colormap_name=colormap_name,
    capitalize_cmap=False,  # to better match VEDA colors and matplotlib colors
    layer_name="DOW7-Derived Maximum Velocity (Greenfield, IA)",
    date=f"{date}T00:00:00Z",
    colorbar_caption="mph",
    attribution="DOW7-Derived Maximum Velocity  (Greenfield, IA)",
    tile_name="DOW7-Derived Maximum Velocity (Greenfield, IA)",
    opacity=0.8,
    height="800px",
    basemap_style="esri-satellite-labels"  # Use satellite with labels for better context
)

print(
    "DOW-derived maximum velocity values from the Greenfield, Iowa EF-4 tornado on May 21, 2024 through Greenfield."
)
# Display the map
m
DOW-derived maximum velocity values from the Greenfield, Iowa EF-4 tornado on May 21, 2024 through Greenfield.
Make this Notebook Trusted to load map: File -> Trust Notebook

Example: PlanetScope Greenfield Pre-Tornado and Post-Tornado Imagery

High-resolution satellite imagery from PlanetScope showing Greenfield, Iowa before the tornado impact and afterwards.

Pre-Tornado

date_pre = "2024-05-20"  # Select date one day before tornado 
collection_id_pre = "ps-greenfield-pre-tornadoes-2024"

# ── VEDA Collection Request ─────────────────────────────────────────────────────────────────────────────────────

results_pre = client_STAC.search(collections=[collection_id_pre], datetime=date_pre)
items_pre = list(results_pre.items())
item_pre = items_pre[0]
collection_pre = item_pre.get_collection()

dashboard_render_pre = collection_pre.extra_fields["renders"]["dashboard"]
assets_pre = dashboard_render_pre["assets"][0]
((vmin_pre, vmax_pre),) = dashboard_render_pre["rescale"]
bidx_pre = dashboard_render_pre.get("bidx", [3, 2, 1])

# ── VEDA Tile Request ─────────────────────────────────────────────────────────────────────────────────────

response_pre = requests.get(
    f"{RASTER_API_URL.rstrip('/')}/collections/{collection_id_pre}"
    f"/items/{item_pre.id}/WebMercatorQuad/tilejson.json?"
    f"&assets={assets_pre}"
    f"&bidx={bidx_pre[0]}&bidx={bidx_pre[1]}&bidx={bidx_pre[2]}"
    f"&rescale={vmin_pre},{vmax_pre}"
    f"&resampling=bilinear"
)
response_pre.raise_for_status()
tiles_pre = response_pre.json()
print(tiles_pre)

collection_pre
{'tilejson': '2.2.0', 'version': '1.0.0', 'scheme': 'xyz', 'tiles': ['https://openveda.cloud/api/raster/collections/ps-greenfield-pre-tornadoes-2024/items/Planet_Greenfield_Before_cog_2024-05-20/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?assets=cog_default&bidx=3&bidx=2&bidx=1&rescale=0%2C2500&resampling=bilinear'], 'minzoom': 0, 'maxzoom': 24, 'bounds': [-94.57407952376526, 41.20937000457795, -94.17411959542935, 41.432508029615924], 'center': [-94.37409955959731, 41.32093901709693, 0]}

Post-Tornado

collection_id_post = "ps-greenfield-post-tornadoes-2024"
date_post = "2024-05-22"  # Select date one day after tornado 

# ── VEDA Collection Request ─────────────────────────────────────────────────────────────────────────────────────

results_post = client_STAC.search(collections=[collection_id_post], datetime=date_post)

items_post = list(results_post.items())
assert len(items_post) != 0, "No items found"
item_post = items_post[0]
collection_post = item_post.get_collection()

# grab the dashboard render block
dashboard_render_post = collection_post.extra_fields["renders"]["dashboard"]

assets_post = dashboard_render_post["assets"][0]
((vmin_post, vmax_post),) = dashboard_render_post["rescale"]

# Special handling for RGB bands - post tornado uses different band order (1,2,3)
bidx_post = dashboard_render_post.get("bidx", [1, 2, 3])  # RGB band order for post


# ── VEDA Tile Request for Post-Tornado ─────────────────────────────────────────────────────────────────────

response_post = requests.get(
    f"{RASTER_API_URL.rstrip('/')}/collections/{collection_id_post}"
    f"/items/{item_post.id}/WebMercatorQuad/tilejson.json?"
    f"&assets={assets_post}"
    f"&bidx={bidx_post[0]}&bidx={bidx_post[1]}&bidx={bidx_post[2]}"  # RGB bands (1,2,3 for post)
    f"&rescale={vmin_post},{vmax_post}"
    f"&resampling=bilinear"
)

response_post.raise_for_status()

tiles_post = response_post.json()
print(tiles_post)

collection_post
{'tilejson': '2.2.0', 'version': '1.0.0', 'scheme': 'xyz', 'tiles': ['https://openveda.cloud/api/raster/collections/ps-greenfield-post-tornadoes-2024/items/Planet_Greenfield_After_cog_2024-05-22/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?assets=cog_default&bidx=1&bidx=2&bidx=3&rescale=0%2C255&resampling=bilinear'], 'minzoom': 0, 'maxzoom': 24, 'bounds': [-94.78355154698768, 41.12696496878226, -94.3507332170628, 41.464387283686655], 'center': [-94.56714238202524, 41.295676126234454, 0]}

Side-by-Side Comparison: Before and After the Tornado

# Create side-by-side comparison using the create_side_by_side_map function
m_comparison = putils.plot_folium_SidebySide_layer_from_VEDA_STAC(
    tiles_url_left=tiles_pre["tiles"][0],  # Pre-tornado (May 20)
    tiles_url_right=tiles_post["tiles"][0],  # Post-tornado (May 22)
    center_coords=[41.3036, -94.4569],  # Greenfield, Iowa
    zoom_level=15,
    title="PlanetScope: Greenfield, IA",
    label_left="← Pre-Tornado (May 20)",
    label_right="Post-Tornado (May 22) β†’",
    layer_name_left="PlanetScope Pre-Tornado",
    layer_name_right="PlanetScope Post-Tornado",
    opacity=1.0,  # Full opacity for satellite imagery
    basemap_style='cartodb-positron',  # Light basemap
    height="800px",
    width="100%"
)

# Display the comparison map
m_comparison
Make this Notebook Trusted to load map: File -> Trust Notebook

Clean Up (Optional)

Remove any core files that were created if the kernel crashed.

# find all core files in the current directory
for core_path in glob.glob("core.*"):
    try:
        os.remove(core_path)
        print(f"Removed {core_path}")
    except OSError as e:
        print(f"Error removing {core_path}: {e}")