Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Disaster Data Analytics

Recipe 1 — Connecting to the Montandon STAC API

IFRC

Environment Setup

Binder
Google Colab
Local

All dependencies are pre-installed. Click Launch Binder at the top of the page — no setup needed.

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:

Events
Hazards
Impacts

Main disaster occurrence records — one item per discrete event. Collections: gdacs-events, emdat-events, glide-events, 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_df
Fetching all collections...

============================================================
Total Collections Available: 29
============================================================

Loading...

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:

OperatorPurposeField
a_containsCountry code matches ESPmonty:country_codes
a_overlapsHazard codes include flood typesmonty:hazard_codes
t_intersectsDatetime falls within Oct–Nov 2024datetime

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:

Loading...

================================================================================

Summary by Category and Type:

Loading...

Summary & Key Takeaways

Key Properties Reference

Table 1:Core Montandon STAC Properties

PropertyDescriptionExample
monty:corr_idCorrelation ID linking events, hazards, impacts20241113-ESP-NAT-HYD-FLO-FLO-1-GCDB
monty:country_codesISO 3166-1 alpha-3 country codes['ESP']
monty:hazard_codesHazard classification codes['nat-hyd-flo-flo', 'FL']
monty:impact_detailImpact metrics (category, type, value, unit)see above
datetimeEvent 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

References
  1. European Commission Joint Research Centre. (2024). Global Disaster Alert and Coordination System (GDACS). https://www.gdacs.org
  2. Guha-Sapir, D. (2024). EM-DAT: The Emergency Events Database. Centre for Research on the Epidemiology of Disasters (CRED). https://www.emdat.be
  3. Asian Disaster Reduction Center. (2024). GLobal IDEntifier Number (GLIDE). https://glidenumber.net
  4. Internal Displacement Monitoring Centre. (2024). IDMC Global Internal Displacement Database. https://www.internal-displacement.org
  5. Element 84. (2024). pystac-client: Python client for searching STAC APIs. https://github.com/stac-utils/pystac-client
  6. Radiant Earth Foundation. (2024). SpatioTemporal Asset Catalog (STAC) Specification. https://stacspec.org
  7. Open Geospatial Consortium. (2023). OGC API — Features — Part 3: Filtering (CQL2). https://docs.ogc.org/is/21-065r2/21-065r2.html
  8. IFRC. (2024). Monty STAC Extension Specification. https://ifrcgo.org/monty-stac-extension/