Environment Setup¶
All dependencies are pre-installed. Click Launch Binder at the top of the page — no setup needed.
Run this in the first code cell before anything else:
!pip install -q pystac-client pandas matplotlib seaborn
import os; from getpass import getpass
if 'MONTANDON_API_TOKEN' not in os.environ:
os.environ['MONTANDON_API_TOKEN'] = getpass('API token: ')pip install -e . # from repo root
export MONTANDON_API_TOKEN='your_token_here'
jupyter labSee Getting Started for token instructions.
Step 1 — Setup and Library Imports¶
# Import libraries
import pandas as pd
from pystac_client import Client
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import seaborn as sns
from typing import List, Dict, Any
import warnings
import os
from getpass import getpass
import json
warnings.filterwarnings('ignore')
# Set display options for better readability
pd.set_option('display.max_rows', 20)
pd.set_option('display.max_columns', None)
pd.set_option('display.max_colwidth', 50)
pd.set_option('display.width', None)
# Plotting style
sns.set_style('whitegrid')
plt.rcParams['figure.figsize'] = (12, 6)
print("All libraries imported successfully!")All libraries imported successfully!
Step 2 — Authenticating with the Montandon STAC API¶
The API requires a Bearer Token issued by the IFRC GO Platform.
Tokens are passed in the Authorization header of every request.
# Connect to Montandon STAC API with Authentication
STAC_API_URL = "https://montandon-eoapi-stage.ifrc.org/stac"
# Get authentication token
# Option 1: From environment variable (recommended for automation)
api_token = os.getenv('MONTANDON_API_TOKEN')
# Option 2: Prompt user for token if not in environment
if api_token is None:
print("=" * 70)
print("AUTHENTICATION REQUIRED")
print("=" * 70)
print("\nThe Montandon STAC API requires a Bearer Token for authentication.")
print("\nHow to get your token:")
print(" 1. Visit: https://goadmin-stage.ifrc.org/")
print(" 2. Log in with your IFRC credentials")
print(" 3. Generate an API token from your account settings")
print("\nAlternatively, set the MONTANDON_API_TOKEN environment variable:")
print(" PowerShell: $env:MONTANDON_API_TOKEN = 'your_token_here'")
print(" Bash: export MONTANDON_API_TOKEN='your_token_here'")
print("\n" + "=" * 70)
# Prompt for token (hidden input)
api_token = getpass("Enter your Montandon API Token: ")
if not api_token or api_token.strip() == "":
raise ValueError("API token is required to access the Montandon STAC API")
# Create authentication headers
auth_headers = {"Authorization": f"Bearer {api_token}"}
# Connect to STAC API with authentication
try:
client = Client.open(STAC_API_URL, headers=auth_headers)
print(f"\n[OK] Connected to: {STAC_API_URL}")
print(f"[OK] API Title: {client.title}")
print(f"[OK] Authentication: Bearer Token (OpenID Connect)")
print(f"[OK] Auth Provider: https://goadmin-stage.ifrc.org/o/.well-known/openid-configuration")
except Exception as e:
print(f"\n[ERROR] Authentication failed: {e}")
print("\nPlease check:")
print(" 1. Your token is valid and not expired")
print(" 2. You have the correct permissions")
print(" 3. The API endpoint is accessible")
raise======================================================================
AUTHENTICATION REQUIRED
======================================================================
The Montandon STAC API requires a Bearer Token for authentication.
How to get your token:
1. Visit: https://goadmin-stage.ifrc.org/
2. Log in with your IFRC credentials
3. Generate an API token from your account settings
Alternatively, set the MONTANDON_API_TOKEN environment variable:
PowerShell: $env:MONTANDON_API_TOKEN = 'your_token_here'
Bash: export MONTANDON_API_TOKEN='your_token_here'
======================================================================
[OK] Connected to: https://montandon-eoapi-stage.ifrc.org/stac
[OK] API Title: stac-fastapi
[OK] Authentication: Bearer Token (OpenID Connect)
[OK] Auth Provider: https://goadmin-stage.ifrc.org/o/.well-known/openid-configuration
Checking API Conformance¶
What is STAC conformance?
A conformant STAC API declares which specification classes it supports
(e.g., Item Search, Filter, Sort) via the /conformance endpoint. This
tells client libraries which features are available Radiant Earth Foundation (2024).
# Check API conformance and capabilities
# Note: The API supports STAC specifications - conformance details available via API endpoints
print("API Connection Verified")
print("=" * 60)
print(f"Connected to: {STAC_API_URL}")
print(f"API Title: {client.title}")
print("\nThe API is ready for data queries.")
print("You can now explore collections and search for disaster event data.")API Connection Verified
============================================================
Connected to: https://montandon-eoapi-stage.ifrc.org/stac
API Title: stac-fastapi
The API is ready for data queries.
You can now explore collections and search for disaster event data.
# Define helper function using pystac_client's client.search() method
from typing import Optional
def search_stac(
collections: Optional[List[str]] = None,
max_items: int = 100,
bbox: Optional[List[float]] = None,
datetime_range: Optional[str] = None,
query: Optional[Dict[str, Any]] = None,
sortby: Optional[List[str]] = None,
filter_body: Optional[Dict[str, Any]] = None,
filter_lang: Optional[str] = None,
) -> list:
"""
Search STAC API using pystac_client's client.search() method.
This uses the standard pystac_client search interface which handles
pagination automatically.
Parameters:
-----------
collections : list of str
Collection IDs to search
max_items : int
Maximum number of results to return
bbox : list of float
Bounding box [min_lon, min_lat, max_lon, max_lat]
datetime_range : str
ISO 8601 datetime range string (e.g., '2024-01-01/2024-12-31')
query : dict
Query parameters for filtering
sortby : list of str
Sort order (e.g., [{'field': 'properties.datetime', 'direction': 'desc'}])
filter_body : dict
CQL2 filter expression
filter_lang : str
Filter language (e.g., 'cql2-json')
Returns:
--------
list : List of pystac Item objects matching the search criteria
"""
# Build search parameters
search_params = {
"max_items": max_items
}
if collections:
search_params["collections"] = collections
if bbox:
search_params["bbox"] = bbox
if datetime_range:
search_params["datetime"] = datetime_range
if query:
search_params["query"] = query
if sortby:
search_params["sortby"] = sortby
if filter_body:
search_params["filter"] = filter_body
if filter_lang:
search_params["filter_lang"] = filter_lang
# Use client.search() - pagination is handled automatically
search = client.search(**search_params)
# Return list of items
return list(search.items())
print("✅ PySTAC helper function initialized (using client.search())")✅ PySTAC helper function initialized (using client.search())
Step 3 — Exploring Available Collections¶
Montandon organises its catalogue into collections that follow a
<source>-<type> naming convention:
Main disaster occurrence records — one item per discrete event.
Collections: gdacs-events, emdat-events, glide-events, etc.
Physical parameters of a hazard instance (e.g., wind speed, magnitude).
Collections: gdacs-hazards, usgs-hazards, ibtracs-hazards, etc.
Human, economic, and infrastructure consequences.
Collections: emdat-impacts, idmc-gidd-impacts, gdacs-impacts, etc.
# Get all collections using pystac_client
# The client handles authentication and pagination automatically
print("Fetching all collections...")
# Get all collections using the client's get_collections() method
collections_list = list(client.get_collections())
print(f"\n{'='*60}")
print(f"Total Collections Available: {len(collections_list)}")
print(f"{'='*60}\n")
# Create a DataFrame for better visualization
collection_data = []
for coll in collections_list:
desc = coll.description or 'N/A'
if desc and desc != 'N/A' and len(desc) > 80:
desc = desc[:80] + '...'
collection_data.append({
'Collection ID': coll.id,
'Title': coll.title or 'N/A',
'Description': desc,
'License': coll.license or 'N/A'
})
collections_df = pd.DataFrame(collection_data)
collections_dfFetching all collections...
============================================================
Total Collections Available: 29
============================================================
Categorising Collections by Type¶
The helper below groups all collections into Events, Hazards, and Impacts using simple string matching on the collection ID.
# Categorize collections
events_collections = [c for c in collections_list if '-events' in c.id]
hazards_collections = [c for c in collections_list if '-hazards' in c.id]
impacts_collections = [c for c in collections_list if '-impacts' in c.id]
print("Collections by Type:\n")
print(f"Events Collections ({len(events_collections)}):")
for col in events_collections:
print(f" • {col.id}")
print(f"Hazards Collections ({len(hazards_collections)}):")
for col in hazards_collections:
print(f" • {col.id}")
print(f"Impacts Collections ({len(impacts_collections)}):")
for col in impacts_collections:
print(f" • {col.id}")Collections by Type:
Events Collections (12):
• desinventar-events
• emdat-events
• gdacs-events
• gfd-events
• glide-events
• ibtracs-events
• idmc-gidd-events
• idmc-idu-events
• ifrcevent-events
• pdc-events
• reference-events
• usgs-events
Hazards Collections (8):
• emdat-hazards
• gdacs-hazards
• gfd-hazards
• glide-hazards
• ibtracs-hazards
• ifrcevent-hazards
• pdc-hazards
• usgs-hazards
Impacts Collections (9):
• desinventar-impacts
• emdat-impacts
• gdacs-impacts
• gfd-impacts
• idmc-gidd-impacts
• idmc-idu-impacts
• ifrcevent-impacts
• pdc-impacts
• usgs-impacts
Categorising by Data Source¶
Each source prefix (e.g., gdacs, emdat, usgs) denotes an upstream
provider. The cell below extracts these prefixes automatically.
# Extract data sources
sources = {}
for col in collections_list:
source = col.id.split('-')[0]
if source not in sources:
sources[source] = []
sources[source].append(col.id)
print("Collections by Data Source:\n")
for source, cols in sorted(sources.items()):
print(f"{source.upper()} ({len(cols)} collections):")
for col in sorted(cols):
print(f" • {col}")
print()Collections by Data Source:
DESINVENTAR (2 collections):
• desinventar-events
• desinventar-impacts
EMDAT (3 collections):
• emdat-events
• emdat-hazards
• emdat-impacts
GDACS (3 collections):
• gdacs-events
• gdacs-hazards
• gdacs-impacts
GFD (3 collections):
• gfd-events
• gfd-hazards
• gfd-impacts
GLIDE (2 collections):
• glide-events
• glide-hazards
IBTRACS (2 collections):
• ibtracs-events
• ibtracs-hazards
IDMC (4 collections):
• idmc-gidd-events
• idmc-gidd-impacts
• idmc-idu-events
• idmc-idu-impacts
IFRCEVENT (3 collections):
• ifrcevent-events
• ifrcevent-hazards
• ifrcevent-impacts
PDC (3 collections):
• pdc-events
• pdc-hazards
• pdc-impacts
REFERENCE (1 collections):
• reference-events
USGS (3 collections):
• usgs-events
• usgs-hazards
• usgs-impacts
Deep Dive — Examining a Single Collection¶
Let’s inspect the gdacs-hazards collection to understand its spatial
and temporal extent, keywords, and data providers.
# Get a specific collection using client.get_collection()
collection_id = 'gdacs-hazards'
# Fetch collection details using pystac_client
gdacs_hazards = client.get_collection(collection_id)
print(f"Collection Details: {collection_id}\n")
print(f"Title: {gdacs_hazards.title or 'N/A'}")
print(f"Description: {gdacs_hazards.description or 'N/A'}")
print(f"License: {gdacs_hazards.license or 'N/A'}")
# Spatial extent
if gdacs_hazards.extent:
if gdacs_hazards.extent.spatial:
print(f"\nSpatial Extent:")
print(f" Bounding Box: {gdacs_hazards.extent.spatial.bboxes}")
if gdacs_hazards.extent.temporal:
print(f"\nTemporal Extent:")
print(f" Intervals: {gdacs_hazards.extent.temporal.intervals}")
# Additional properties (keywords, providers, etc.)
if gdacs_hazards.keywords:
print(f"\nKeywords: {', '.join(gdacs_hazards.keywords)}")
if gdacs_hazards.providers:
print(f"\nProviders:")
for provider in gdacs_hazards.providers:
print(f" • {provider.name} ({provider.roles})")Collection Details: gdacs-hazards
Title: GDACS Hazards
Description: Hazard records from the Global Disaster Alert and Coordination System (GDACS). GDACS is a cooperation framework between the United Nations, the European Commission and disaster managers worldwide to improve alerts, information exchange and coordination in the first phase after major sudden-onset disasters. It provides detailed hazard information including affected areas, alert levels, and severity scores for earthquakes, tsunamis, floods, tropical cyclones, volcanic eruptions, and wildfires. Each hazard includes specific information based on the hazard type and uses specialized models for assessment. More information on the GDACS mapping in Monty can be found in the [GDACS Hazard Source Mappings](https://github.com/IFRCGo/monty-stac-extension/tree/main/model/sources/GDACS#hazard-item).
License: MIT
Spatial Extent:
Bounding Box: [[-180, -90, 180, 90]]
Temporal Extent:
Intervals: [[datetime.datetime(2000, 1, 1, 0, 0, tzinfo=tzutc()), None]]
Keywords: gdacs, hazard, natural hazard, hazard footprint, hazard severity, earthquake intensity, flood extent, cyclone path, tsunami wave height, volcanic ash cloud, drought severity, wildfire extent, early warning, risk assessment, hazard modeling, global
Providers:
• GDACS (['producer'])
Step 4 — Basic Data Retrieval¶
With the connection established we can start querying items. The examples below progress from the simplest possible search to multi-filter queries.
4.1 Minimal Query — Retrieve 10 Items¶
The simplest call: just pass a collection name and a
max_items limit.
# Simple search using client.search()
try:
search = client.search(
collections=["gdacs-hazards"],
max_items=10
)
items = list(search.items())
print(f"✅ Retrieved {len(items)} items from gdacs-hazards collection\n")
# Display first item details
if items:
first_item = items[0]
print("📄 First Item Details:")
print(f"ID: {first_item.id}")
print(f"Collection: {first_item.collection_id}")
print(f"Geometry Type: {first_item.geometry.get('type', 'None')}")
print(f"\nProperties:")
for key, value in list(first_item.properties.items())[:10]: # Show first 10 properties
print(f" • {key}: {value}")
else:
print("No items found in the search results")
except Exception as e:
print(f"❌ Search failed: {e}")✅ Retrieved 10 items from gdacs-hazards collection
📄 First Item Details:
ID: gdacs-hazard-1514353-1676468
Collection: gdacs-hazards
Geometry Type: Point
Properties:
• roles: ['source', 'hazard']
• title: Earthquake in Colombia
• source: NEIC
• datetime: 2025-12-10T08:27:06Z
• keywords: ['Geological', 'Seismic', 'COL', 'Earthquake']
• description: Green M 5.5 Earthquake in Colombia at: 10 Dec 2025 08:27:06.
• end_datetime: 2025-12-10T08:27:06Z
• monty:etl_id: 6c6185c9-68a6-4f70-863c-5bcfed162c25
• severitydata: {'severity': 5.5, 'severitytext': 'Magnitude 5.5M, Depth:140.692km', 'severityunit': 'M'}
• monty:corr_id: 20251210-COL-GH0101-1676468-GCDB
4.2 Using the Convenience Wrapper¶
The search_stac() helper (defined above) centralises all search
parameters and returns a plain Python list of pystac.Item objects.
# Direct search using pystac_client's client.search() method
# Perform search using client.search()
search = client.search(
collections=["gdacs-hazards"],
max_items=5
)
items_list = list(search.items())
print(f"📡 PySTAC Client Response")
print(f"\n📊 Results:")
print(f" • Number of Items: {len(items_list)}")
print(f" • Collection: gdacs-hazards")
# Display feature IDs
if items_list:
print(f"\n Item IDs:")
for item in items_list:
print(f" • {item.id}")📡 PySTAC Client Response
📊 Results:
• Number of Items: 5
• Collection: gdacs-hazards
Item IDs:
• gdacs-hazard-1514353-1676468
• gdacs-hazard-1514350-1676464
• gdacs-hazard-1514341-1676453
• gdacs-hazard-1514332-1676442
• gdacs-hazard-1514294-1676432
Step 5 — Advanced Filtering Techniques¶
The Montandon API supports CQL2-JSON Open Geospatial Consortium (2023) filters, enabling complex temporal, spatial, and property-based queries in a single request.
5.1 Temporal Filtering — Date Range¶
Filter items by a datetime window using the standard datetime
parameter (ISO 8601 interval).
# Define date range - last 90 days
end_date = datetime.now()
start_date = end_date - timedelta(days=90)
# Format as ISO 8601
datetime_range = f"{start_date.strftime('%Y-%m-%dT%H:%M:%SZ')}/{end_date.strftime('%Y-%m-%dT%H:%M:%SZ')}"
print(f"📅 Searching for events between:")
print(f" Start: {start_date.strftime('%Y-%m-%d')}")
print(f" End: {end_date.strftime('%Y-%m-%d')}\n")
# Search with datetime filter using client.search()
search = client.search(
collections=['gdacs-events'],
datetime=datetime_range,
max_items=20
)
items = list(search.items())
print(f"✅ Found {len(items)} events in the last 90 days")
# Display event details
if items:
print("\n📋 Recent Events:")
for i, item in enumerate(items[:5], 1):
event_date = item.properties.get('datetime', 'N/A')
event_name = item.properties.get('title', item.id)
print(f" {i}. {event_name} - {event_date}")📅 Searching for events between:
Start: 2025-09-11
End: 2025-12-10
✅ Found 20 events in the last 90 days
📋 Recent Events:
1. Earthquake in Colombia - 2025-12-10T08:27:06Z
2. Earthquake in Indonesia - 2025-12-10T07:42:55Z
3. Earthquake in Chile - 2025-12-10T06:38:27Z
4. Earthquake in Volcano Islands, Japan Region - 2025-12-10T05:18:49Z
5. Earthquake in Russia - 2025-12-09T23:19:52Z
5.2 Spatial Filtering — Bounding Box¶
Pass a bbox ([min_lon, min_lat, max_lon, max_lat]) to restrict
results to a geographic rectangle.
# Define bounding box for Southeast Asia [min_lon, min_lat, max_lon, max_lat]
southeast_asia_bbox = [95.0, -10.0, 141.0, 28.0]
print("🗺️ Searching for events in Southeast Asia")
print(f" Bounding Box: {southeast_asia_bbox}\n")
# Search with bbox using client.search()
search = client.search(
collections=['gdacs-events', 'emdat-events', 'usgs-events'],
bbox=southeast_asia_bbox,
max_items=30
)
items = list(search.items())
print(f"✅ Found {len(items)} events in Southeast Asia")
# Analyze by collection
if items:
collection_counts = {}
for item in items:
col = item.collection_id
collection_counts[col] = collection_counts.get(col, 0) + 1
print("\n📊 Events by Collection:")
for col, count in sorted(collection_counts.items()):
print(f" • {col}: {count} events")🗺️ Searching for events in Southeast Asia
Bounding Box: [95.0, -10.0, 141.0, 28.0]
✅ Found 30 events in Southeast Asia
📊 Events by Collection:
• gdacs-events: 14 events
• usgs-events: 16 events
5.3 Combined Filters — Space + Time¶
Temporal and spatial parameters can be combined freely:
# Search for recent events in Europe
europe_bbox = [-10.0, 35.0, 40.0, 71.0]
recent_datetime = "2024-01-01T00:00:00Z/2024-12-31T23:59:59Z"
print("🔍 Searching for 2024 events in Europe\n")
search = client.search(
collections=['gdacs-events', 'emdat-events'],
bbox=europe_bbox,
datetime=recent_datetime,
max_items=50
)
items = list(search.items())
print(f"✅ Found {len(items)} events in Europe during 2024")
# Display sample
if items:
print("\n📋 Sample Events:")
for i, item in enumerate(items[:3], 1):
print(f"\n {i}. ID: {item.id}")
print(f" Collection: {item.collection_id}")
print(f" Title: {item.properties.get('title', 'N/A')}")
print(f" Date: {item.properties.get('datetime', 'N/A')}")
print(f" Country: {item.properties.get('monty:country_codes', 'N/A')}")🔍 Searching for 2024 events in Europe
✅ Found 50 events in Europe during 2024
📋 Sample Events:
1. ID: emdat-event-2024-0960-TUN
Collection: emdat-events
Title: Water in Tunisia of December 2024
Date: 2024-12-31T00:00:00Z
Country: ['TUN']
2. ID: emdat-event-2024-0954-ITA
Collection: emdat-events
Title: Water in Italy of December 2024
Date: 2024-12-31T00:00:00Z
Country: ['ITA']
3. ID: emdat-event-2024-0949-ESP
Collection: emdat-events
Title: Water in Spain of December 2024
Date: 2024-12-26T00:00:00Z
Country: ['ESP']
5.4 CQL2-JSON Filters — Case Study: Spain Floods¶
Common Query Language 2 (CQL2) Open Geospatial Consortium (2023) enables powerful server-side filtering on any queryable property. The filter below combines three conditions with a logical AND:
| Operator | Purpose | Field |
|---|---|---|
a_contains | Country code matches ESP | monty:country_codes |
a_overlaps | Hazard codes include flood types | monty:hazard_codes |
t_intersects | Datetime falls within Oct–Nov 2024 | datetime |
5.5 Event → Hazard Correlation¶
Every Montandon item carries a correlation ID (monty:corr_id).
Items with the same correlation ID describe different facets of the
same real-world occurrence — enabling event → hazard → impact joins.
# CQL2 filter for Spain flood events
spain_flood_late_oct_filter = {
"op": "and",
"args": [
{
"op": "a_contains",
"args": [
{"property": "monty:country_codes"},
"ESP"
]
},
{
"op": "a_overlaps",
"args": [
{"property": "monty:hazard_codes"},
["nat-hyd-flo-flo", "FL"]
]
},
{
"op": "t_intersects",
"args": [
{"property": "datetime"},
{"interval": ["2024-10-20T00:00:00Z", "2024-11-05T23:59:59Z"]}
]
}
]
}
# Search using client.search() with CQL2 filter
search = client.search(
collections=["gdacs-events", "emdat-events", "glide-events", "reference-events"],
filter=spain_flood_late_oct_filter,
filter_lang="cql2-json",
max_items=100
)
spain_flood_late_oct_events = list(search.items())
print(f"✅ Found {len(spain_flood_late_oct_events)} flood events in Spain (Oct 20 - Nov 5, 2024)\n")
if spain_flood_late_oct_events:
print(f"📋 All {len(spain_flood_late_oct_events)} Spain Late October/Early November Flood Events:\n")
for i, event in enumerate(spain_flood_late_oct_events, 1):
print(f"\n{i}. Event Details:")
print(f" ID: {event.id}")
print(f" Title: {event.properties.get('title', 'N/A')}")
print(f" Date: {event.properties.get('datetime', 'N/A')}")
print(f" Collection: {event.collection_id}")
print(f" Countries: {event.properties.get('monty:country_codes', 'N/A')}")
print(f" Hazard Codes: {event.properties.get('monty:hazard_codes', 'N/A')}")
print(f" Correlation ID: {event.properties.get('monty:corr_id', 'N/A')}")
else: print("⚠️ No flood events found in Spain during Oct 20 - Nov 5, 2024")✅ Found 4 flood events in Spain (Oct 20 - Nov 5, 2024)
📋 All 4 Spain Late October/Early November Flood Events:
1. Event Details:
ID: gdacs-event-1102983
Title: Flood in Spain
Date: 2024-10-27T15:00:00Z
Collection: gdacs-events
Countries: ['ESP']
Hazard Codes: ['FL']
Correlation ID: 20241027-ESP-NAT-HYD-FLO-FLO-1-GCDB
2. Event Details:
ID: glide-event-FL-2024-000199-ESP
Title:
Date: 2024-10-27T00:00:00Z
Collection: glide-events
Countries: ['ESP']
Hazard Codes: ['nat-hyd-flo-flo', 'FL']
Correlation ID: 20241027-ESP-NAT-HYD-FLO-FLO-1-GCDB
3. Event Details:
ID: emdat-event-2024-0796-ESP
Title: Flood (General) in Spain
Date: 2024-10-27T00:00:00Z
Collection: emdat-events
Countries: ['ESP']
Hazard Codes: ['nat-hyd-flo-flo']
Correlation ID: 20241027-ESP-NAT-HYD-FLO-FLO-1-GCDB
4. Event Details:
ID: gdacs-event-1102955
Title: Flood in Spain
Date: 2024-10-11T01:00:00Z
Collection: gdacs-events
Countries: ['ESP']
Hazard Codes: ['FL']
Correlation ID: 20241011-ESP-NAT-HYD-FLO-FLO-1-GCDB
# Get correlation IDs from Spain flood events and find related hazards
if spain_flood_late_oct_events:
# Collect unique correlation IDs
correlation_ids = set()
for event in spain_flood_late_oct_events:
corr_id = event.properties.get('monty:corr_id')
if corr_id:
correlation_ids.add(corr_id)
print(f"🔗 Found {len(correlation_ids)} unique correlation IDs from Spain flood events\n")
print("="*80)
# Search for hazards for each correlation ID
all_hazards = []
hazard_collections_used = ["glide-hazards", "gdacs-hazards", "usgs-hazards", "reference-hazards"]
for idx, corr_id in enumerate(sorted(correlation_ids), 1):
print(f"\n📍 CORRELATION ID {idx}/{len(correlation_ids)}: {corr_id}")
print("="*80)
# Search for related hazards using CQL2 filter
hazard_filter = {
"op": "=",
"args": [
{"property": "monty:corr_id"},
corr_id
]
}
hazard_search = client.search(
collections=hazard_collections_used,
filter=hazard_filter,
filter_lang="cql2-json",
max_items=50
)
hazards = list(hazard_search.items())
print(f"\n✅ Found {len(hazards)} hazard(s) across collections: {', '.join(hazard_collections_used)}\n")
if hazards:
all_hazards.extend(hazards)
# Group hazards by collection for clearer display
hazards_by_collection = {}
for hazard in hazards:
coll = hazard.collection_id
if coll not in hazards_by_collection:
hazards_by_collection[coll] = []
hazards_by_collection[coll].append(hazard)
# Display hazards grouped by collection
for collection_name, coll_hazards in sorted(hazards_by_collection.items()):
print(f"📦 Collection: {collection_name} ({len(coll_hazards)} hazard{'s' if len(coll_hazards) > 1 else ''})")
print("-" * 80)
for i, hazard in enumerate(coll_hazards, 1):
print(f"\n {i}. Hazard ID: {hazard.id}")
print(f" Date: {hazard.properties.get('datetime', 'N/A')}")
print(f" Title: {hazard.properties.get('title', 'N/A')}")
# Display hazard detail if available
hazard_detail = hazard.properties.get('monty:hazard_detail', {})
if hazard_detail:
severity = hazard_detail.get('severity_value', 'N/A')
severity_unit = hazard_detail.get('severity_unit', '')
cluster = hazard_detail.get('cluster', 'N/A')
print(f" Severity: {severity} {severity_unit}")
print(f" Cluster: {cluster}")
else:
print(f" Severity: N/A")
print(f" Cluster: N/A")
print()
else:
print("⚠️ No hazards found for this correlation ID")
print("\n" + "="*80)
print(f"\n🎯 FINAL SUMMARY")
print("="*80)
print(f"Total Correlation IDs: {len(correlation_ids)}")
print(f"Total Hazards Found: {len(all_hazards)}")
# Summary by collection
if all_hazards:
print(f"\n📊 Hazards by Collection:")
collection_summary = {}
for hazard in all_hazards:
coll = hazard.collection_id
collection_summary[coll] = collection_summary.get(coll, 0) + 1
for coll, count in sorted(collection_summary.items()):
print(f" • {coll}: {count} hazard{'s' if count > 1 else ''}")
else:
print("ℹ️ No Spain flood events found to search for hazards")🔗 Found 2 unique correlation IDs from Spain flood events
================================================================================
📍 CORRELATION ID 1/2: 20241011-ESP-NAT-HYD-FLO-FLO-1-GCDB
================================================================================
✅ Found 1 hazard(s) across collections: glide-hazards, gdacs-hazards, usgs-hazards, reference-hazards
📦 Collection: gdacs-hazards (1 hazard)
--------------------------------------------------------------------------------
1. Hazard ID: gdacs-hazard-1102955-1
Date: 2024-10-11T01:00:00Z
Title: Flood in Spain
Severity: 0.5 GDACS Severity Score
Cluster: nat-hyd-flo-flo
📍 CORRELATION ID 2/2: 20241027-ESP-NAT-HYD-FLO-FLO-1-GCDB
================================================================================
✅ Found 3 hazard(s) across collections: glide-hazards, gdacs-hazards, usgs-hazards, reference-hazards
📦 Collection: gdacs-hazards (2 hazards)
--------------------------------------------------------------------------------
1. Hazard ID: gdacs-hazard-1102983-2
Date: 2024-10-27T15:00:00Z
Title: Flood in Spain
Severity: 0.5 GDACS Severity Score
Cluster: nat-hyd-flo-flo
2. Hazard ID: gdacs-hazard-1102983-1
Date: 2024-10-27T15:00:00Z
Title: Flood in Spain
Severity: 1.5 GDACS Severity Score
Cluster: nat-hyd-flo-flo
📦 Collection: glide-hazards (1 hazard)
--------------------------------------------------------------------------------
1. Hazard ID: glide-hazard-FL-2024-000199-ESP
Date: 2024-10-27T00:00:00Z
Title:
Severity: N/A glide
Cluster: nat-hyd-flo-flo
================================================================================
🎯 FINAL SUMMARY
================================================================================
Total Correlation IDs: 2
Total Hazards Found: 4
📊 Hazards by Collection:
• gdacs-hazards: 3 hazards
• glide-hazards: 1 hazard
5.6 Retrieving Impact Records¶
Using the same country- and hazard-code filters, we can pull
impact items across gdacs-impacts, emdat-impacts, idmc-gidd-impacts,
and idmc-idu-impacts.
# Search for impacts related to Spain flood events
if spain_flood_late_oct_events:
print("🔍 Searching for impacts related to Spain flood events\n")
print("="*80)
# Define impact filter for Spain floods
impact_filter = {
"op": "and",
"args": [
{
"op": "a_contains",
"args": [
{"property": "monty:country_codes"},
"ESP"
]
},
{
"op": "a_overlaps",
"args": [
{"property": "monty:hazard_codes"},
["nat-hyd-flo-flo", "FL"]
]
},
{
"op": "t_intersects",
"args": [
{"property": "datetime"},
{"interval": ["2024-10-20T00:00:00Z", "2024-11-15T23:59:59Z"]}
]
}
]
}
# Search across impact collections
impact_collections = ["gdacs-impacts", "emdat-impacts", "idmc-gidd-impacts", "idmc-idu-impacts"]
impact_search = client.search(
collections=impact_collections,
filter=impact_filter,
filter_lang="cql2-json",
max_items=100
)
impacts = list(impact_search.items())
print(f"✅ Found {len(impacts)} impact(s) across collections: {', '.join(impact_collections)}\n")🔍 Searching for impacts related to Spain flood events
================================================================================
✅ Found 46 impact(s) across collections: gdacs-impacts, emdat-impacts, idmc-gidd-impacts, idmc-idu-impacts
# Diagnostic: Examine the actual structure of impact items
if impacts:
print("🔍 DIAGNOSTIC: Examining Impact Data Structure\n")
print("="*80)
# Sample a few impacts from different collections
sample_impacts = {}
for impact in impacts[:10]:
coll = impact.collection_id
if coll not in sample_impacts:
sample_impacts[coll] = impact
for collection_name, impact in sample_impacts.items():
print(f"\n📦 Collection: {collection_name}")
print(f"Impact ID: {impact.id}")
print("-" * 80)
# Show all properties
print("\n📋 All Properties:")
for key, value in impact.properties.items():
if isinstance(value, dict):
print(f" {key}:")
for sub_key, sub_value in value.items():
print(f" {sub_key}: {sub_value}")
else:
print(f" {key}: {value}")
# Show monty:impact_detail specifically
impact_detail = impact.properties.get('monty:impact_detail', {})
if impact_detail:
print(f"\n⚡ monty:impact_detail structure:")
for key, val in impact_detail.items():
print(f" {key}: {val}")
else:
print(f"\n⚠️ monty:impact_detail is empty or missing")
print("\n" + "="*80)
else:
print("No impacts to examine")🔍 DIAGNOSTIC: Examining Impact Data Structure
================================================================================
📦 Collection: idmc-idu-impacts
Impact ID: idmc-idu-impact-28064168623-displaced
--------------------------------------------------------------------------------
📋 All Properties:
roles: ['source', 'impact']
title: Spain: Flood - Andalusia (Málaga) - 13/11/2024
sources: Local Authorities
datetime: 2024-11-13T00:00:00Z
location: Alhaurín de la Torre, Malaga, Andalusia, Spain; Álora, Malaga, Andalusia, Spain; Cártama, Malaga, Andalusia, Spain; Málaga, Malaga, Andalusia, Spain; Pizarra, Malaga, Andalusia, Spain; Vélez-Málaga, La Axarquía, Malaga, Andalusia, Spain
description: **Spain: 4,210 displacements, 13 November - 14 November**
According to local authority, a total of 4210 people were displaced in Álora, Cártama, Alhaurín de la Torre, Pizarra, Málaga, Vélez-Málaga due to flood on 13 November.
[Local Authorities - 14 November 2024](https://www.juntadeandalucia.es/presidencia/portavoz/emergencias112/198838/suspension/clases/Malaga/Granada/Sevilla/Cadiz/Huelva/balance/incidencias/dana/andalucia/desalojos/avisosmeteorologicos/recomendaciones)
end_datetime: 2024-11-14T00:00:00+00:00
monty:etl_id: ac352e3d-f395-4b61-8b74-0d339fdd587a
monty:corr_id: 20241113-ESP-NAT-HYD-FLO-FLO-1-GCDB
start_datetime: 2024-11-13T00:00:00+00:00
monty:hazard_codes: ['nat-hyd-flo-flo']
monty:country_codes: ['ESP']
monty:impact_detail:
type: displaced_internal
unit: count
value: 4210
category: people
estimate_type: primary
monty:episode_number: 1
⚡ monty:impact_detail structure:
type: displaced_internal
unit: count
value: 4210
category: people
estimate_type: primary
================================================================================
📦 Collection: idmc-gidd-impacts
Impact ID: idmc-gidd-impact-28064168623-Internal Displacements
--------------------------------------------------------------------------------
📋 All Properties:
roles: ['source', 'impact']
title: Internal Displacements-Person-Person-Spain: Flood - Andalusia (Málaga) - 13/11/2024
country: Spain
sources: ['Local Authorities']
datetime: 2024-11-13T00:00:00Z
keywords: ['Flooding', 'Water-related', 'ESP', 'Meteorological & Hydrological']
location: ['Álora, Malaga, Andalusia, Spain', 'Cártama, Malaga, Andalusia, Spain', 'Alhaurín de la Torre, Malaga, Andalusia, Spain', 'Pizarra, Malaga, Andalusia, Spain', 'Málaga, Malaga, Andalusia, Spain', 'Vélez-Málaga, La Axarquía, Malaga, Andalusia, Spain']
publishers: ['Local Authorities']
figure_unit: Person
end_datetime: 2024-11-14T00:00:00Z
figure_cause: Disaster
monty:etl_id: 83d5fab7-4dc9-4c07-a1ea-b396ac6133e4
location_type: ['Origin', 'Origin', 'Origin', 'Origin', 'Origin', 'Origin']
monty:corr_id: 20241113-ESP-MH0600-1-GCDB
start_datetime: 2024-11-13T00:00:00Z
figure_category: Internal Displacements
location_accuracy: ['County/City/town/Village/Woreda (ADM3)', 'County/City/town/Village/Woreda (ADM3)', 'County/City/town/Village/Woreda (ADM3)', 'County/City/town/Village/Woreda (ADM3)', 'County/City/town/Village/Woreda (ADM3)', 'County/City/town/Village/Woreda (ADM3)']
monty:hazard_codes: ['MH0600', 'FL', 'nat-hyd-flo-flo']
geographical_region: Europe and Central Asia
monty:country_codes: ['ESP']
monty:impact_detail:
type: displaced_internal
unit: count
value: 4210
category: people
estimate_type: primary
monty:episode_number: 1
⚡ monty:impact_detail structure:
type: displaced_internal
unit: count
value: 4210
category: people
estimate_type: primary
================================================================================
📦 Collection: emdat-impacts
Impact ID: emdat-impact-2024-0839-ESP-total_dam
--------------------------------------------------------------------------------
📋 All Properties:
roles: ['source', 'impact']
title: Flood (General) in Spain - total_dam
datetime: 2024-11-13T00:00:00Z
description: Flood in Álora, Cártama, Alhaurín de la Torre, Pizarra municipalities and Malaga city (Malaga); Catalonia province, Spain of November 2024
end_datetime: 2024-11-14T00:00:00Z
monty:etl_id: cbea5a9f-dcc2-4e34-ad2f-02f5583c716b
monty:corr_id: 20241113-ESP-NAT-HYD-FLO-FLO-1-GCDB
start_datetime: 2024-11-13T00:00:00Z
monty:hazard_codes: ['nat-hyd-flo-flo']
monty:country_codes: ['ESP']
monty:impact_detail:
type: cost
unit: count
value: 50000.0
category: total_affected
estimate_type: primary
monty:episode_number: 1
⚡ monty:impact_detail structure:
type: cost
unit: count
value: 50000.0
category: total_affected
estimate_type: primary
================================================================================
# Create a DataFrame with all impact data
if impacts:
impact_data = []
for impact in impacts:
impact_detail = impact.properties.get('monty:impact_detail', {})
# Extract all relevant fields
impact_record = {
'Impact ID': impact.id,
'Collection': impact.collection_id,
'Correlation ID': impact.properties.get('monty:corr_id', 'N/A'),
'Date': impact.properties.get('datetime', 'N/A'),
'Title': impact.properties.get('title', 'N/A'),
'Category': impact_detail.get('category', 'N/A') if impact_detail else 'N/A',
'Type': impact_detail.get('type', 'N/A') if impact_detail else 'N/A',
'Value': impact_detail.get('value', None) if impact_detail else None,
'Unit': impact_detail.get('unit', 'N/A') if impact_detail else 'N/A',
'Estimate Type': impact_detail.get('estimate_type', 'N/A') if impact_detail else 'N/A'
}
impact_data.append(impact_record)
# Create DataFrame
impacts_df = pd.DataFrame(impact_data)
# Display summary
print("Impact Data Summary")
print("="*80)
print(f"Total Impacts: {len(impacts_df)}")
print(f"\nDataFrame Shape: {impacts_df.shape}")
print(f"Columns: {list(impacts_df.columns)}\n")
# Display the DataFrame
print("="*80)
print("\nAll Impacts Table:\n")
display(impacts_df)
# Summary statistics by category and type
print("\n" + "="*80)
print("\nSummary by Category and Type:\n")
summary = impacts_df.groupby(['Category', 'Type']).agg({
'Value': ['count', 'sum', 'mean', 'min', 'max']
}).round(0)
# Reset index to make Category and Type regular columns
summary = summary.reset_index()
# Flatten the multi-level column names
summary.columns = ['Category', 'Type', 'Count', 'Sum', 'Mean', 'Min', 'Max']
display(summary)
else:
print("No impacts available to create DataFrame")Impact Data Summary
================================================================================
Total Impacts: 46
DataFrame Shape: (46, 10)
Columns: ['Impact ID', 'Collection', 'Correlation ID', 'Date', 'Title', 'Category', 'Type', 'Value', 'Unit', 'Estimate Type']
================================================================================
All Impacts Table:
================================================================================
Summary by Category and Type:
Summary & Key Takeaways¶
Key Properties Reference¶
Table 1:Core Montandon STAC Properties
| Property | Description | Example |
|---|---|---|
monty:corr_id | Correlation ID linking events, hazards, impacts | 20241113-ESP-NAT-HYD-FLO-FLO-1-GCDB |
monty:country_codes | ISO 3166-1 alpha-3 country codes | ['ESP'] |
monty:hazard_codes | Hazard classification codes | ['nat-hyd-flo-flo', 'FL'] |
monty:impact_detail | Impact metrics (category, type, value, unit) | see above |
datetime | Event timestamp (ISO 8601) | 2024-11-13T00:00:00Z |
Case Study Results: Spain Floods Oct–Nov 2024
Events found: 4 (GDACS 1102983, GLIDE FL-2024-000199-ESP, EMDAT 2024-0796, GDACS 1102955)
Hazards found: 4 total (3 GDACS, 1 GLIDE)
Impacts found: 46 total — 7 EMDAT, 20 GDACS, 14 IDMC-GIDD, 5 IDMC-IDU
Primary impact types: internal displacements (4 210+ people), economic costs (50 000+), affected population counts. Data spans November 2024 with focus on Andalusia (Málaga, Granada).
Practice Exercise¶
# Your practice code here
# Example: Search for floods in your region
- European Commission Joint Research Centre. (2024). Global Disaster Alert and Coordination System (GDACS). https://www.gdacs.org
- Guha-Sapir, D. (2024). EM-DAT: The Emergency Events Database. Centre for Research on the Epidemiology of Disasters (CRED). https://www.emdat.be
- Asian Disaster Reduction Center. (2024). GLobal IDEntifier Number (GLIDE). https://glidenumber.net
- Internal Displacement Monitoring Centre. (2024). IDMC Global Internal Displacement Database. https://www.internal-displacement.org
- Element 84. (2024). pystac-client: Python client for searching STAC APIs. https://github.com/stac-utils/pystac-client
- Radiant Earth Foundation. (2024). SpatioTemporal Asset Catalog (STAC) Specification. https://stacspec.org
- Open Geospatial Consortium. (2023). OGC API — Features — Part 3: Filtering (CQL2). https://docs.ogc.org/is/21-065r2/21-065r2.html
- IFRC. (2024). Monty STAC Extension Specification. https://ifrcgo.org/monty-stac-extension/