Step 1 — Setup & Connection¶
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 plotly requests
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.
# Import required libraries
import json
import pandas as pd
import numpy as np
from pystac_client import Client
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import requests
from typing import List, Dict, Any, Optional
import warnings
import os
from getpass import getpass
warnings.filterwarnings('ignore')
# Display settings
pd.set_option('display.max_rows', 30)
pd.set_option('display.max_columns', None)
pd.set_option('display.max_colwidth', 60)
pd.set_option('display.width', None)
# Plotting style
sns.set_style('whitegrid')
plt.rcParams['figure.figsize'] = (14, 6)
print("All libraries imported successfully!")All libraries imported successfully!
# 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✓ Connected to: {STAC_API_URL}")
print(f"✓ API Title: {client.title}")
print(f"✓ Authentication: Bearer Token (OpenID Connect)")
print(f"✓ Auth Provider: https://goadmin-stage.ifrc.org/o/.well-known/openid-configuration")
except Exception as e:
print(f"\n✗ 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'
======================================================================
✓ Connected to: https://montandon-eoapi-stage.ifrc.org/stac
✓ API Title: stac-fastapi
✓ Authentication: Bearer Token (OpenID Connect)
✓ Auth Provider: https://goadmin-stage.ifrc.org/o/.well-known/openid-configuration
# Helper function to handle search with fallback to direct HTTP request
def search_stac(filter_dict=None, collections=None,
max_items=100, filter_lang="cql2-json"):
"""
Search the STAC API with automatic fallback to direct HTTP requests.
This handles the /stac/ endpoint routing issue.
"""
try:
# Try PySTAC client search
search = client.search(
collections=collections,
filter=filter_dict,
filter_lang=filter_lang,
max_items=max_items
)
return list(search.items())
except Exception as e:
# Fallback to direct HTTP POST request
search_url = f"{STAC_API_URL}/search"
search_payload = {
"filter_lang": filter_lang,
"limit": max_items
}
if filter_dict:
search_payload["filter"] = filter_dict
if collections:
search_payload["collections"] = collections
response = requests.post(search_url, json=search_payload, headers=auth_headers)
if response.status_code == 200:
search_results = response.json()
# Convert features to simple objects
items = []
for feature in search_results.get('features', []):
item = type('Item', (), {
'id': feature.get('id'),
'collection_id': feature.get('collection'),
'properties': feature.get('properties', {}),
'geometry': feature.get('geometry'),
'bbox': feature.get('bbox'),
'assets': feature.get('assets', {})
})()
items.append(item)
return items
else:
print(f"Search failed: {response.status_code} - {response.text}")
return []
print("✓ Search helper function defined")✓ Search helper function defined
Step 2 — Discovering Queryables¶
The /queryables endpoint exposes a JSON Schema document listing
every property you can filter on. The cells below fetch and categorise them
into four groups:
| Group | Prefix | Examples |
|---|---|---|
| STAC Core | — | id, collection, datetime, geometry |
| Monty Core | monty: | monty:country_codes, monty:hazard_codes, monty:corr_id |
| Hazard Detail | monty:hazard_detail.* | cluster, severity_value, severity_unit |
| Impact Detail | monty:impact_detail.* | category, type, value, unit |
# Fetch the queryables schema from the API (with authentication)
queryables_url = f"{STAC_API_URL}/queryables"
response = requests.get(queryables_url, headers=auth_headers)
if response.status_code == 200:
queryables = response.json()
print("Queryables Schema Retrieved Successfully!\n")
print(f"Schema ID: {queryables.get('$id', 'N/A')}")
print(f"Title: {queryables.get('title', 'N/A')}")
print(f"Description: {queryables.get('description', 'N/A')}")
elif response.status_code == 401:
print(f"Authentication failed (401 Unauthorized)")
print("Your token may be invalid or expired. Please check your credentials.")
else:
print(f"Failed to fetch queryables: {response.status_code}")
print(f"Response: {response.text}")Queryables Schema Retrieved Successfully!
Schema ID: https://montandon-eoapi-stage.ifrc.org/queryables
Title: STAC Queryables.
Description: N/A
# List all available queryable properties
properties = queryables.get('properties', {})
print(f"Total Queryable Properties: {len(properties)}\n")
print("="*80)
# Categorize queryables
core_queryables = []
monty_core = []
hazard_detail = []
impact_detail = []
other = []
for prop_name, prop_info in properties.items():
if prop_name.startswith('monty:hazard_detail'):
hazard_detail.append((prop_name, prop_info))
elif prop_name.startswith('monty:impact_detail'):
impact_detail.append((prop_name, prop_info))
elif prop_name.startswith('monty:'):
monty_core.append((prop_name, prop_info))
elif prop_name in ['id', 'collection', 'datetime', 'geometry', 'roles']:
core_queryables.append((prop_name, prop_info))
else:
other.append((prop_name, prop_info))
print("STAC Core Queryables:")
for name, info in core_queryables:
print(f" - {name}: {info.get('type', 'N/A')} | {info.get('description', 'N/A')[:50]}...")
print("\nMonty Core Queryables:")
for name, info in monty_core:
print(f" - {name}: {info.get('type', 'N/A')}")
print("\nHazard Detail Queryables:")
for name, info in hazard_detail:
print(f" - {name}: {info.get('type', 'N/A')}")
print("\nImpact Detail Queryables:")
for name, info in impact_detail:
print(f" - {name}: {info.get('type', 'N/A')}")Total Queryable Properties: 15
================================================================================
STAC Core Queryables:
- id: string | Item identifier...
- roles: string | The roles of the item...
- datetime: string | Datetime...
- geometry: object | Geometry...
- collection: string | Collection identifier...
Monty Core Queryables:
- monty:corr_id: string
- monty:hazard_codes: string
- monty:country_codes: string
- monty:episode_number: integer
Hazard Detail Queryables:
- monty:hazard_detail.estimate_type: string
- monty:hazard_detail.severity_unit: string
- monty:hazard_detail.severity_value: number
Impact Detail Queryables:
- monty:impact_detail.type: string
- monty:impact_detail.value: number
- monty:impact_detail.category: string
Step 3 — Collections & Item Types¶
desinventar-events, gdacs-events, glide-events, emdat-events, gfd-events
gdacs-hazards, emdat-hazards, gfd-hazards, usgs-hazards, ibtracs-hazards
desinventar-impacts, gdacs-impacts, emdat-impacts, idmc-gidd-impacts, idmc-idu-impacts
# Search for recent events across all event collections
# We filter by collection names instead of the 'roles' property
event_collections = ["gdacs-events", "glide-events", "emdat-events", "desinventar-events", "gfd-events"]
# Optional: Add time filter for recent events
recent_events_filter = {
"op": "t_intersects",
"args": [
{"property": "datetime"},
{"interval": ["2024-01-01T00:00:00Z", "2024-12-31T23:59:59Z"]}
]
}
recent_events = search_stac(
collections=event_collections,
filter_dict=recent_events_filter,
max_items=10
)
print(f"Found {len(recent_events)} recent events (limited to 10)\n")
for item in recent_events[:5]:
print(f"ID: {item.id}")
print(f" Collection: {item.collection_id}")
print(f" Title: {item.properties.get('title', 'N/A')[:60]}...")
print(f" Roles: {item.properties.get('roles', [])}")Found 10 recent events (limited to 10)
ID: emdat-event-2024-0960-TUN
Collection: emdat-events
Title: Water in Tunisia of December 2024...
Roles: ['source', 'event']
ID: emdat-event-2024-0954-ITA
Collection: emdat-events
Title: Water in Italy of December 2024...
Roles: ['source', 'event']
ID: gdacs-event-1103065
Collection: gdacs-events
Title: Flood in Malaysia...
Roles: ['source', 'event']
ID: emdat-event-2024-0948-ETH
Collection: emdat-events
Title: Road in Ethiopia...
Roles: ['source', 'event']
ID: emdat-event-2024-0947-KOR
Collection: emdat-events
Title: Air in Republic of Korea of December 2024...
Roles: ['source', 'event']
Step 4 — Spatial Queries with s_intersects¶
s_intersects tests whether an item’s geometry overlaps a given GeoJSON
shape. Below we define a bounding-box polygon for South Asia and
combine it with a hazard-code filter.
# Example 1: Find all events in Japan using a_contains
japan_filter = {
"op": "a_contains",
"args": [
{"property": "monty:country_codes"},
"JPN"
]
}
japan_events = search_stac(
collections=["gdacs-events", "glide-events"],
filter_dict=japan_filter,
max_items=20
)
print(f"Events in Japan (JPN): {len(japan_events)}\n")
for item in japan_events[:5]:
print(f"- {item.properties.get('title', 'N/A')[:70]}")
print(f" Hazard Codes: {item.properties.get('monty:hazard_codes', [])}")Events in Japan (JPN): 20
- EQ in on 2025, 12, 8
Hazard Codes: ['GH0101', 'EQ', 'nat-geo-ear-gro']
- Earthquake in Japan
Hazard Codes: ['GH0101', 'EQ', 'nat-geo-ear-gro']
- Earthquake in Japan
Hazard Codes: ['GH0101', 'EQ', 'nat-geo-ear-gro']
- Earthquake in Off East Coast Of Honshu, Japan
Hazard Codes: ['GH0101', 'EQ', 'nat-geo-ear-gro']
- Earthquake in Japan
Hazard Codes: ['GH0101', 'EQ', 'nat-geo-ear-gro']
# Example 2: Find all flood-related events using
# a_overlaps with multiple hazard codes
# Flood codes across different classification systems:
# - FL (GDACS), MH0600 (UNDRR-ISC), nat-hyd-flo-flo (EMDAT)
flood_codes = ["FL", "MH0600", "nat-hyd-flo-flo"]
flood_filter = {
"op": "a_overlaps",
"args": [
{"property": "monty:hazard_codes"},
flood_codes
]
}
flood_events = search_stac(
collections=["gdacs-events", "glide-events", "emdat-events"],
filter_dict=flood_filter,
max_items=30
)
print(f"Flood-related events found: {len(flood_events)}\n")
# Count by collection
collections_count = {}
for item in flood_events:
coll = item.collection_id
collections_count[coll] = collections_count.get(coll, 0) + 1
print("Events by Collection:")
for coll, count in sorted(collections_count.items()):
print(f" {coll}: {count}")Flood-related events found: 30
Events by Collection:
emdat-events: 1
gdacs-events: 28
glide-events: 1
Step 5 — Array Operators: a_overlaps and a_contains¶
Array-valued properties like monty:hazard_codes and monty:country_codes
require specialised operators:
| Operator | Semantics | Example |
|---|---|---|
a_overlaps | Item array shares at least one element with the argument | Multi-code hazard search |
a_contains | Item array includes a specific element | Single-country filter |
a_equals | Item array is exactly equal | Exact match (rare) |
# Find hazards with notable severity values
# Note: severity_value means different things for different hazard types:
# - Earthquakes: Magnitude (e.g., 5.0, 6.5, 7.2)
# - Cyclones: Wind speed (e.g., 150 km/h, 200 km/h)
# - Floods: Water level or discharge rate
# We'll fetch hazards by specifying hazard collections
hazard_collections = ["gdacs-hazards", "emdat-hazards", "gfd-hazards", "usgs-hazards"]
hazards_filter = {
"op": ">",
"args": [{"property": "monty:hazard_detail.severity_value"}, 5]
}
all_hazards = search_stac(
collections=hazard_collections,
filter_dict=hazards_filter,
max_items=50
)
print(f"Total hazards with severity values: {len(all_hazards)}\n")
# Categorize hazards by cluster/type
hazard_data = []
for item in all_hazards:
hazard_detail = item.properties.get('monty:hazard_detail', {})
cluster = hazard_detail.get('cluster', 'N/A')
severity = hazard_detail.get('severity_value', 'N/A')
unit = hazard_detail.get('severity_unit', 'N/A')
hazard_data.append({
'ID': item.id[:30],
'Collection': item.collection_id,
'Title': item.properties.get('title', 'N/A')[:40],
'Hazard_Type': cluster,
'Severity': severity,
'Unit': unit
})
if hazard_data:
df_hazards = pd.DataFrame(hazard_data)
# Display summary by hazard type
print("="*70)
print("HAZARDS BY TYPE:")
print("="*70)
# Group by hazard type
for hazard_type in df_hazards['Hazard_Type'].unique():
if hazard_type == 'N/A':
continue
type_df = df_hazards[df_hazards['Hazard_Type'] == hazard_type]
print(f"\n{hazard_type.upper()} ({len(type_df)} records):")
print(f" Severity range: {type_df['Severity'].min():.2f} - {type_df['Severity'].max():.2f} {type_df['Unit'].iloc[0]}")
print(f" Mean severity: {type_df['Severity'].mean():.2f} {type_df['Unit'].iloc[0]}")
print("\n" + "="*70)
print("SAMPLE HAZARDS:")
print("="*70)
display(df_hazards.head(15))Total hazards with severity values: 50
======================================================================
HAZARDS BY TYPE:
======================================================================
======================================================================
SAMPLE HAZARDS:
======================================================================
Step 6 — Hazard Details: Severity Filtering¶
Filter on nested hazard properties like monty:hazard_detail.severity_value
to find only high-magnitude events.
# Find impacts with deaths reported
# Filter by impact collections instead of roles property
impact_collections = ["desinventar-impacts", "gdacs-impacts", "emdat-impacts", "idmc-gidd-impacts", "idmc-idu-impacts"]
death_impacts_filter = {
"op": "and",
"args": [
{"op": "=", "args": [{"property": "monty:impact_detail.type"}, "death"]},
{"op": ">", "args": [{"property": "monty:impact_detail.value"}, 0]}
]
}
death_impacts = search_stac(
collections=impact_collections,
filter_dict=death_impacts_filter,
max_items=50
)
print(f"Impact records with deaths: {len(death_impacts)}\n")
# Compile impact data
impact_data = []
for item in death_impacts[:20]:
impact_detail = item.properties.get('monty:impact_detail', {})
impact_data.append({
'ID': item.id[:25],
'Collection': item.collection_id,
'Country': ', '.join(item.properties.get('monty:country_codes', [])[:2]),
'Category': impact_detail.get('category', 'N/A'),
'Type': impact_detail.get('type', 'N/A'),
'Value': impact_detail.get('value', 'N/A'),
'Estimate': impact_detail.get('estimate_type', 'N/A')
})
if impact_data:
df_impacts = pd.DataFrame(impact_data)
display(df_impacts)Impact records with deaths: 50
# Find significant displacement impacts (> 1000 people)
# Filter by impact collections
impact_collections = ["desinventar-impacts", "gdacs-impacts", "emdat-impacts", "idmc-gidd-impacts", "idmc-idu-impacts"]
displacement_filter = {
"op": "and",
"args": [
{
"op": "or",
"args": [
{"op": "=", "args": [{"property": "monty:impact_detail.type"}, "displaced_internal"]},
{"op": "=", "args": [{"property": "monty:impact_detail.type"}, "displaced_total"]},
{"op": "=", "args": [{"property": "monty:impact_detail.type"}, "displaced_external"]}
]
},
{"op": ">", "args": [{"property": "monty:impact_detail.value"}, 1000]}
]
}
displacement_impacts = search_stac(
collections=impact_collections,
filter_dict=displacement_filter,
max_items=50
)
print(f"Significant displacement impacts (>1000 people): {len(displacement_impacts)}\n")
# Analyze by country
country_displacements = {}
for item in displacement_impacts:
countries = item.properties.get('monty:country_codes', [])
value = item.properties.get('monty:impact_detail', {}).get('value', 0)
for country in countries:
if country not in country_displacements:
country_displacements[country] = {'count': 0, 'total': 0}
country_displacements[country]['count'] += 1
country_displacements[country]['total'] += value if value else 0
# Sort by total displacement
sorted_countries = sorted(country_displacements.items(),
key=lambda x: x[1]['total'], reverse=True)[:10]
print("Top 10 Countries by Displacement:\n")
for country, data in sorted_countries:
print(f" {country}: {data['total']:,.0f} displaced ({data['count']} records)")Search failed: 500 - Internal Server Error
Significant displacement impacts (>1000 people): 0
Top 10 Countries by Displacement:
Step 7 — Impact Details: People & Economic Metrics¶
Query the monty:impact_detail nested properties to retrieve humanitarian
impact records (deaths, displacement, affected population).
# Complex Query: Find all flood impacts in South/Southeast Asia in 2024
# with significant affected population (>100 people)
asian_countries = ["IND", "BGD", "PAK", "NPL", "LKA", "MMR", "THA", "VNM", "PHL", "IDN", "MYS"]
flood_codes = ["FL", "MH0600", "nat-hyd-flo-flo"]
impact_collections = ["desinventar-impacts", "gdacs-impacts", "emdat-impacts", "idmc-gidd-impacts", "idmc-idu-impacts"]
asia_flood_impacts_filter = {
"op": "and",
"args": [
# Flood-related hazard codes
{
"op": "a_overlaps",
"args": [
{"property": "monty:hazard_codes"},
flood_codes
]
},
# In Asian countries
{
"op": "a_overlaps",
"args": [
{"property": "monty:country_codes"},
asian_countries
]
},
# Category is people-related
{"op": "=", "args": [{"property": "monty:impact_detail.category"}, "people"]},
# Value > 100
{"op": ">", "args": [{"property": "monty:impact_detail.value"}, 100]},
# In 2024
{
"op": "t_intersects",
"args": [
{"property": "datetime"},
{"interval": ["2024-01-01T00:00:00Z", "2024-12-31T23:59:59Z"]}
]
}
]
}
asia_flood_impacts = search_stac(
collections=impact_collections,
filter_dict=asia_flood_impacts_filter,
max_items=100
)
print(f"Flood impacts in South/Southeast Asia (2024): {len(asia_flood_impacts)}\n")
# Analyze results
if asia_flood_impacts:
impact_summary = []
for item in asia_flood_impacts:
impact_detail = item.properties.get('monty:impact_detail', {})
impact_summary.append({
'Country': ', '.join(item.properties.get('monty:country_codes', [])),
'Date': item.properties.get('datetime', 'N/A')[:10],
'Type': impact_detail.get('type', 'N/A'),
'Value': impact_detail.get('value', 0),
'Collection': item.collection_id
})
df_summary = pd.DataFrame(impact_summary)
# Summary by country and type
print(df_summary.groupby(['Country', 'Type'])['Value'].agg(['count', 'sum']).reset_index())
print(df_summary.groupby(['Country', 'Type'])['Value'].agg(['count', 'sum']).reset_index())Flood impacts in South/Southeast Asia (2024): 100
Country Type count sum
0 IDN affected_total 2 20320.0
1 IDN displaced_internal 35 41780.0
2 IND displaced_internal 2 19957.0
3 IND relocated 2 1881.0
4 LKA displaced_internal 1 165.0
5 LKA shelter_emergency 1 123.0
6 MYS affected_total 1 1650.0
7 MYS displaced_internal 29 28964.0
8 MYS relocated 4 7074.0
9 PHL displaced_internal 21 105803.0
10 THA affected_total 1 315.0
11 VNM affected_total 1 200.0
Country Type count sum
0 IDN affected_total 2 20320.0
1 IDN displaced_internal 35 41780.0
2 IND displaced_internal 2 19957.0
3 IND relocated 2 1881.0
4 LKA displaced_internal 1 165.0
5 LKA shelter_emergency 1 123.0
6 MYS affected_total 1 1650.0
7 MYS displaced_internal 29 28964.0
8 MYS relocated 4 7074.0
9 PHL displaced_internal 21 105803.0
10 THA affected_total 1 315.0
11 VNM affected_total 1 200.0
# Complex Query: Find correlated events, hazards, and impacts for a specific disaster
# Let's find a recent correlation ID first
event_collections = ["gdacs-events", "glide-events", "emdat-events", "desinventar-events", "gfd-events"]
recent_events_filter = {
"op": "t_intersects",
"args": [
{"property": "datetime"},
{"interval": ["2024-10-01T00:00:00Z", "2024-11-30T23:59:59Z"]}
]
}
recent_events = search_stac(
collections=event_collections,
filter_dict=recent_events_filter,
max_items=10
)
print(f"Recent reference events (Oct-Nov 2024): {len(recent_events)}\n")
# Select one correlation ID to explore
if recent_events:
sample_event = recent_events[0]
corr_id = sample_event.properties.get('monty:corr_id')
print(f"Selected Event: {sample_event.properties.get('title', 'N/A')}")
print(f"Correlation ID: {corr_id}")
print(f"Countries: {sample_event.properties.get('monty:country_codes', [])}")
print(f"Hazard Codes: {sample_event.properties.get('monty:hazard_codes', [])}")Recent reference events (Oct-Nov 2024): 10
Selected Event: Flood in Haiti
Correlation ID: 20241130-HTI-NAT-HYD-FLO-FLO-1-GCDB
Countries: ['HTI']
Hazard Codes: ['FL']
# Now find ALL related items using the correlation ID
if recent_events and corr_id:
corr_filter = {
"op": "=",
"args": [
{"property": "monty:corr_id"},
corr_id
]
}
correlated_items = search_stac(
filter_dict=corr_filter,
max_items=100
)
print(f"\nAll items with correlation ID '{corr_id}': {len(correlated_items)}\n")
# Categorize by roles
events = []
hazards = []
impacts = []
for item in correlated_items:
roles = item.properties.get('roles', [])
if 'event' in roles:
events.append(item)
elif 'hazard' in roles:
hazards.append(item)
elif 'impact' in roles:
impacts.append(item)
print(f"Events: {len(events)}")
print(f"Hazards: {len(hazards)}")
print(f"Impacts: {len(impacts)}")
# Display impact summary
if impacts:
print("\n" + "="*60)
print("IMPACT SUMMARY:")
print("="*60)
for impact in impacts[:10]:
detail = impact.properties.get('monty:impact_detail', {})
print(f"\n {impact.collection_id}:")
print(f" Category: {detail.get('category', 'N/A')}")
print(f" Type: {detail.get('type', 'N/A')}")
print(f" Value: {detail.get('value', 'N/A')} {detail.get('unit', '')}")
All items with correlation ID '20241130-HTI-NAT-HYD-FLO-FLO-1-GCDB': 11
Events: 2
Hazards: 2
Impacts: 7
============================================================
IMPACT SUMMARY:
============================================================
gdacs-impacts:
Category: people
Type: relocated
Value: 60 sendai
gdacs-impacts:
Category: people
Type: affected_total
Value: 160 sendai
gdacs-impacts:
Category: people
Type: death
Value: 2 sendai
emdat-impacts:
Category: people
Type: death
Value: 16.0 count
emdat-impacts:
Category: people
Type: affected_total
Value: 155025.0 count
emdat-impacts:
Category: people
Type: injured
Value: 25.0 count
emdat-impacts:
Category: people
Type: affected_total
Value: 155000.0 count
Step 8 — Temporal Queries with t_intersects¶
The t_intersects operator tests whether an item’s datetime falls within
a given ISO 8601 interval. Pass an interval array of two timestamps.
# Find all cyclone events in the last 6 months
cyclone_codes = ["TC", "MH0306", "nat-met-sto-tro"]
event_collections = ["gdacs-events", "glide-events", "emdat-events", "gfd-events"]
end_date = datetime.now()
start_date = end_date - timedelta(days=180)
cyclone_filter = {
"op": "and",
"args": [
{
"op": "a_overlaps",
"args": [
{"property": "monty:hazard_codes"},
cyclone_codes
]
},
{
"op": "t_intersects",
"args": [
{"property": "datetime"},
{
"interval": [
start_date.strftime("%Y-%m-%dT00:00:00Z"),
end_date.strftime("%Y-%m-%dT23:59:59Z")
]
}
]
}
]
}
cyclones = search_stac(
collections=event_collections,
filter_dict=cyclone_filter,
max_items=50
)
print(f"Cyclone events in last 6 months: {len(cyclones)}\n")
# Display cyclone data
cyclone_data = []
for item in cyclones:
cyclone_data.append({
'Date': item.properties.get('datetime', 'N/A')[:10],
'Title': item.properties.get('title', 'N/A')[:50],
'Countries': ', '.join(item.properties.get('monty:country_codes', [])[:3]),
'Collection': item.collection_id
})
if cyclone_data:
df_cyclones = pd.DataFrame(cyclone_data)
df_cyclones = df_cyclones.sort_values('Date', ascending=False)
display(df_cyclones)Cyclone events in last 6 months: 50
Step 9 — Focused Impact Analysis Examples¶
Five targeted queries demonstrating combined hazard + impact + temporal filtering using the official Monty taxonomy.
| Type | Description |
|---|---|
death | Fatalities |
injured | People injured |
displaced_internal | IDPs |
displaced_external | Refugees |
affected_total | Total affected |
homeless | Homeless |
| GLIDE | EM-DAT | UNDRR-ISC 2025 | Description |
|---|---|---|---|
FL | nat-hyd-flo-flo | MH0600 | Flood |
EQ | nat-geo-ear-gro | GH0101 | Earthquake |
TC | nat-met-sto-tro | MH0306 | Cyclone |
DR | nat-cli-dro-dro | MH0401 | Drought |
# Analysis 1: Deaths by Hazard Type (Recent Records)
# Query deaths from major hazard types in 2024
impact_collections = ["desinventar-impacts", "gdacs-impacts", "emdat-impacts"]
# Define major hazard types with their codes
hazard_types = {
"Flood": ["FL", "MH0600", "nat-hyd-flo-flo"],
"Earthquake": ["EQ", "GH0101", "nat-geo-ear-gro"],
"Cyclone": ["TC", "MH0306", "nat-met-sto-tro"],
"Drought": ["DR", "MH0401", "nat-cli-dro-dro"]
}
death_by_hazard = {}
for hazard_name, codes in hazard_types.items():
death_filter = {
"op": "and",
"args": [
{"op": "=", "args": [{"property": "monty:impact_detail.type"}, "death"]},
{"op": ">", "args": [{"property": "monty:impact_detail.value"}, 0]},
{"op": "a_overlaps", "args": [{"property": "monty:hazard_codes"}, codes]},
{
"op": "t_intersects",
"args": [
{"property": "datetime"},
{"interval": ["2024-01-01T00:00:00Z", "2024-12-31T23:59:59Z"]}
]
}
]
}
results = search_stac(
collections=impact_collections,
filter_dict=death_filter,
max_items=50
)
total_deaths = sum(
item.properties.get('monty:impact_detail', {}).get('value', 0) or 0
for item in results
)
death_by_hazard[hazard_name] = {
'records': len(results),
'total_deaths': total_deaths
}
print(f"{hazard_name}: {len(results)} records, {total_deaths:,.0f} deaths reported")
print(f"\n✓ Deaths by hazard type analysis complete")Flood: 50 records, 940 deaths reported
Earthquake: 8 records, 602 deaths reported
Cyclone: 50 records, 1,990 deaths reported
Drought: 0 records, 0 deaths reported
✓ Deaths by hazard type analysis complete
# Visualize Deaths by Hazard Type
if death_by_hazard:
hazards = list(death_by_hazard.keys())
deaths = [death_by_hazard[h]['total_deaths'] for h in hazards]
records = [death_by_hazard[h]['records'] for h in hazards]
fig = go.Figure()
fig.add_trace(go.Bar(
x=hazards,
y=deaths,
text=[f"{d:,.0f}" for d in deaths],
textposition='outside',
marker_color=['#3498db', '#e74c3c', '#9b59b6', '#f39c12']
))
fig.update_layout(
title="Deaths Reported by Hazard Type (2024)",
xaxis_title="Hazard Type",
yaxis_title="Total Deaths Reported",
height=550,
showlegend=False
)
fig.show()# Analysis 2: Displacement by Country (Top 10)
# Focus on internally displaced persons (IDPs) - a key humanitarian metric
displacement_filter = {
"op": "and",
"args": [
{
"op": "or",
"args": [
{"op": "=", "args": [{"property": "monty:impact_detail.type"}, "displaced_internal"]},
{"op": "=", "args": [{"property": "monty:impact_detail.type"}, "displaced_total"]}
]
},
{"op": ">", "args": [{"property": "monty:impact_detail.value"}, 100]},
{
"op": "t_intersects",
"args": [
{"property": "datetime"},
{"interval": ["2024-01-01T00:00:00Z", "2024-12-31T23:59:59Z"]}
]
}
]
}
displacement_results = search_stac(
collections=["desinventar-impacts", "gdacs-impacts", "emdat-impacts", "idmc-gidd-impacts"],
filter_dict=displacement_filter,
max_items=100
)
print(f"Found {len(displacement_results)} displacement records\n")
# Aggregate by country
country_displacement = {}
for item in displacement_results:
countries = item.properties.get('monty:country_codes', [])
value = item.properties.get('monty:impact_detail', {}).get('value', 0) or 0
for country in countries[:1]: # Primary country only
if country not in country_displacement:
country_displacement[country] = 0
country_displacement[country] += value
# Sort and get top 10
top_countries = sorted(country_displacement.items(), key=lambda x: x[1], reverse=True)[:10]
print("Top 10 Countries by Displacement (2024):")
for country, displaced in top_countries:
print(f" {country}: {displaced:,.0f} people displaced")Found 100 displacement records
Top 10 Countries by Displacement (2024):
AFG: 1,270,301 people displaced
CHN: 198,400 people displaced
IRQ: 170,274 people displaced
GEO: 48,582 people displaced
PHL: 44,429 people displaced
PNG: 21,144 people displaced
COD: 14,598 people displaced
ECU: 8,809 people displaced
TCD: 5,700 people displaced
PER: 5,521 people displaced
# Visualize Displacement by Country
if top_countries:
countries = [c[0] for c in top_countries]
displaced = [c[1] for c in top_countries]
fig = go.Figure()
fig.add_trace(go.Bar(
x=countries,
y=displaced,
text=[f"{d:,.0f}" for d in displaced],
textposition='outside',
marker_color='#27ae60'
))
fig.update_layout(
title="Top 10 Countries by Displacement (2024)",
xaxis_title="Country (ISO3)",
yaxis_title="People Displaced",
height=550
)
fig.show()# Analysis 3: Recent High-Impact Flood Events with Deaths
# Focused query combining hazard type + impact type + recent time
flood_death_filter = {
"op": "and",
"args": [
{"op": "a_overlaps", "args": [{"property": "monty:hazard_codes"}, ["FL", "MH0600", "nat-hyd-flo-flo"]]},
{"op": "=", "args": [{"property": "monty:impact_detail.type"}, "death"]},
{"op": ">", "args": [{"property": "monty:impact_detail.value"}, 10]},
{
"op": "t_intersects",
"args": [
{"property": "datetime"},
{"interval": ["2024-06-01T00:00:00Z", "2024-12-31T23:59:59Z"]}
]
}
]
}
flood_deaths = search_stac(
collections=["desinventar-impacts", "gdacs-impacts", "emdat-impacts"],
filter_dict=flood_death_filter,
max_items=20
)
print(f"Recent Flood Events with Significant Deaths (>10):\n")
print(f"{'Country':<10} {'Date':<12} {'Deaths':>10} {'Collection':<20}")
print("-" * 55)
flood_death_data = []
for item in flood_deaths[:15]:
detail = item.properties.get('monty:impact_detail', {})
country = ', '.join(item.properties.get('monty:country_codes', [])[:1]) or 'N/A'
date = item.properties.get('datetime', 'N/A')[:10]
deaths = detail.get('value', 0) or 0
collection = item.collection_id
print(f"{country:<10} {date:<12} {deaths:>10,.0f} {collection:<20}")
flood_death_data.append({'Country': country, 'Date': date, 'Deaths': deaths})Recent Flood Events with Significant Deaths (>10):
Country Date Deaths Collection
-------------------------------------------------------
HTI 2024-12-20 14 emdat-impacts
IDN 2024-12-03 12 emdat-impacts
KEN 2024-12-01 13 emdat-impacts
HTI 2024-11-30 16 emdat-impacts
MWI 2024-11-27 11 emdat-impacts
THA 2024-11-25 35 emdat-impacts
MDG 2024-11-12 16 gdacs-impacts
ESP 2024-10-27 62 gdacs-impacts
ESP 2024-10-27 217 gdacs-impacts
ESP 2024-10-27 217 gdacs-impacts
ESP 2024-10-27 232 emdat-impacts
IND 2024-10-04 15 emdat-impacts
BIH 2024-10-03 14 gdacs-impacts
BIH 2024-10-03 27 emdat-impacts
IRN 2024-10-01 15 gdacs-impacts
# Analysis 4: Earthquake Impact Comparison (Deaths vs Injured)
# Compare death and injury impacts from earthquakes
earthquake_codes = ["EQ", "GH0101", "nat-geo-ear-gro"]
earthquake_impacts = {}
for impact_type in ["death", "injured"]:
eq_filter = {
"op": "and",
"args": [
{"op": "a_overlaps", "args": [{"property": "monty:hazard_codes"}, earthquake_codes]},
{"op": "=", "args": [{"property": "monty:impact_detail.type"}, impact_type]},
{"op": ">", "args": [{"property": "monty:impact_detail.value"}, 0]},
{
"op": "t_intersects",
"args": [
{"property": "datetime"},
{"interval": ["2024-01-01T00:00:00Z", "2024-12-31T23:59:59Z"]}
]
}
]
}
results = search_stac(
collections=["desinventar-impacts", "gdacs-impacts", "emdat-impacts"],
filter_dict=eq_filter,
max_items=50
)
total = sum(item.properties.get('monty:impact_detail', {}).get('value', 0) or 0 for item in results)
earthquake_impacts[impact_type] = {'records': len(results), 'total': total}
print(f"Earthquake {impact_type}: {len(results)} records, {total:,.0f} people")
# Simple bar comparison
if earthquake_impacts:
fig = go.Figure()
fig.add_trace(go.Bar(
x=['Deaths', 'Injured'],
y=[earthquake_impacts.get('death', {}).get('total', 0),
earthquake_impacts.get('injured', {}).get('total', 0)],
marker_color=['#e74c3c', '#f39c12'],
text=[f"{earthquake_impacts.get('death', {}).get('total', 0):,.0f}",
f"{earthquake_impacts.get('injured', {}).get('total', 0):,.0f}"],
textposition='outside'
))
fig.update_layout(
title="Earthquake Impacts: Deaths vs Injured (2024)",
yaxis_title="Number of People",
height=550,
margin=dict(t=50, b=50, l=50, r=50)
)
fig.show()Earthquake death: 8 records, 602 people
Earthquake injured: 13 records, 3,156 people
# Analysis 5: Recent Cyclone Events with Affected Population
# Query affected_total for tropical cyclones
cyclone_codes = ["TC", "MH0306", "nat-met-sto-tro"]
cyclone_affected_filter = {
"op": "and",
"args": [
{"op": "a_overlaps", "args": [{"property": "monty:hazard_codes"}, cyclone_codes]},
{"op": "=", "args": [{"property": "monty:impact_detail.type"}, "affected_total"]},
{"op": ">", "args": [{"property": "monty:impact_detail.value"}, 1000]},
{
"op": "t_intersects",
"args": [
{"property": "datetime"},
{"interval": ["2024-01-01T00:00:00Z", "2024-12-31T23:59:59Z"]}
]
}
]
}
cyclone_affected = search_stac(
collections=["desinventar-impacts", "gdacs-impacts", "emdat-impacts"],
filter_dict=cyclone_affected_filter,
max_items=30
)
print(f"Cyclone Events with Significant Affected Population (>1000):\n")
cyclone_data = []
for item in cyclone_affected[:10]:
detail = item.properties.get('monty:impact_detail', {})
country = ', '.join(item.properties.get('monty:country_codes', [])[:1]) or 'N/A'
date = item.properties.get('datetime', 'N/A')[:10]
affected = detail.get('value', 0) or 0
cyclone_data.append({
'Country': country,
'Date': date,
'Affected': affected,
'Collection': item.collection_id
})
if cyclone_data:
df_cyclone = pd.DataFrame(cyclone_data)
df_cyclone = df_cyclone.sort_values('Affected', ascending=False)
display(df_cyclone)Cyclone Events with Significant Affected Population (>1000):
Queryables Reference Cheat Sheet¶
Core Queryables¶
| Property | Type | Description |
|---|---|---|
id | string | Item identifier |
collection | string | Collection identifier |
datetime | date-time | Event/record timestamp |
geometry | object | Spatial geometry |
roles | array | Item roles (event, hazard, impact, reference) |
monty:episode_number | integer | Episode number |
monty:country_codes | array | ISO 3166-1 alpha-3 country codes |
monty:corr_id | string | Correlation identifier |
monty:hazard_codes | array | Hazard classification codes |
Hazard Detail Queryables¶
| Property | Type | Description |
|---|---|---|
monty:hazard_detail.cluster | string | Hazard cluster |
monty:hazard_detail.severity_value | number | Severity / magnitude value |
monty:hazard_detail.severity_unit | string | Unit of severity |
monty:hazard_detail.estimate_type | string | primary, secondary, modelled |
Impact Detail Queryables¶
| Property | Type | Description |
|---|---|---|
monty:impact_detail.category | string | Impact category (people, buildings, …) |
monty:impact_detail.type | string | Impact type (death, injured, displaced, …) |
monty:impact_detail.value | number | Impact value |
monty:impact_detail.unit | string | Unit of measurement |
monty:impact_detail.estimate_type | string | primary, secondary, modelled |
Array Operators¶
| Operator | Description | Example |
|---|---|---|
a_contains | Array contains element | {"op": "a_contains", "args": [{"property": "monty:country_codes"}, "ESP"]} |
a_overlaps | Arrays share elements | {"op": "a_overlaps", "args": [{"property": "monty:hazard_codes"}, ["FL", "MH0600"]]} |
a_equals | Arrays are equal | {"op": "a_equals", "args": [{"property": "monty:country_codes"}, ["ESP"]]} |
Temporal Operators¶
| Operator | Description |
|---|---|
t_intersects | Time intersects interval |
t_during | Time falls within interval |
- 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
- U.S. Geological Survey. (2024). USGS Earthquake Hazards Program. https://earthquake.usgs.gov
- Knapp, K. R., Kruk, M. C., Levinson, D. H., Diamond, H. J., & Neumann, C. J. (2024). International Best Track Archive for Climate Stewardship (IBTrACS). NOAA National Centers for Environmental Information. https://www.ncei.noaa.gov/products/international-best-track-archive
- Internal Displacement Monitoring Centre. (2024). IDMC Global Internal Displacement Database. https://www.internal-displacement.org