A hobby project for collecting, analyzing, and visualizing earthquake data in Myanmar. Built on the Myanmar Earthquake API, covering records from 1950 to present.
Live dashboard: https://akzedevops.github.io/mmeqopendata/
Write-up: paper/main.pdf — a casual hobby write-up on Myanmar dam seismic risk with 14 figures and open data.
- Fetches earthquake data from the API, validates, deduplicates, exports as CSV + JSON
- Scores 254 dams by seismic risk using PGA, fault proximity, and structural exposure
- Computes site-specific Vs30 from the USGS ShakeMap grid (Wald & Allen 2007 slope proxy)
- Builds probabilistic hazard curves (Cornell-McGuire PSHA) at each dam
- Uses Abrahamson & Silva (2008) GMPE with rupture distance to fault trace
- Runs b-value / completeness analysis, aftershock forecasting (Omori law)
- DBSCAN clustering in UTM coordinates, Monte Carlo sensitivity on risk weights
- Coulomb stress transfer modeling from USGS finite fault data
- Generates interactive maps (Folium), dashboards (Plotly), 3D views, animated timelines
- Auto-deploys to GitHub Pages via CI/CD
git clone https://github.com/akzedevops/mmeqopendata.git
cd mmeqopendata
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
mmeq export # Fetch earthquake data
mmeq report --output ./report # Generate everything
mmeq analyze --type all # Run analyses only
mmeq visualize --min-mag 3.0 # Interactive mapFetches earthquake data from the API and exports as CSV/JSON.
mmeq export # Fetch all pending months
mmeq export --workers 5 # Use 5 parallel threads (default: 10)Output structure:
quake_exports/
├── csv/
│ ├── monthly/ # earthquakes_YYYY_MM.csv
│ ├── yearly/ # earthquakes_YYYY.csv
│ └── combined/ # earthquakes_combined.csv
└── json/
├── monthly/ # earthquakes_YYYY_MM.json
├── yearly/ # earthquakes_YYYY.json
└── combined/ # earthquakes_combined.json
mmeq analyze --type all # Run everything
mmeq analyze --type temporal # Frequency, distribution, depth charts
mmeq analyze --type clustering # DBSCAN cluster map with dam overlay
mmeq analyze --type seismology --decluster # b-value, Mc, declustering
mmeq analyze --dam-risk # Dam risk scoring
mmeq analyze --min-mag 4.0 --mc 3.5 # Filter and override Mcmmeq visualize # All events, all layers
mmeq visualize --min-mag 4.0 # Only M4.0+
mmeq visualize --no-heatmap # Disable heatmap
mmeq visualize --no-dams # Disable dam overlayFull pipeline — all analyses + all outputs in one command.
mmeq report --output ./report # Generate everything
mmeq report --no-pdf # Skip PDF
mmeq report --no-3d # Skip 3D cross-section
mmeq report --no-forecast # Skip aftershock forecast
mmeq report --no-dashboard # Skip Plotly dashboard
mmeq report --no-animated # Skip animated map
mmeq report --no-population # Skip population exposure
mmeq report --no-dams # Skip dam risk analysis| Output | File |
|---|---|
| Interactive Map | enhanced_earthquake_map.html |
| Dashboard | dashboard.html |
| 3D View | depth_cross_section.html |
| Animation | animated_earthquake_map.html |
| PDF Report | myanmar_earthquake_report.pdf |
| Dam Risk | dam_risk_scores.csv |
| Hazard Curves | hazard_curves/*.csv |
| Population | population_exposure.csv |
| Sensitivity | sensitivity_analysis.csv |
254 dams from Open Development Mekong (IFC/WLE, CC BY-SA 4.0).
Each dam gets a composite risk score from four components:
| Component | Weight | What it measures |
|---|---|---|
| PGA (ground shaking) | 35% | Abrahamson & Silva 2008 GMPE with site-specific Vs30 |
| Fault proximity | 30% | Distance to nearest active fault segment |
| Mainshock proximity | 20% | Distance to the 2025 M7.7 epicenter |
| Structural exposure | 15% | Dam height, capacity, storage volume |
The ground motion model uses rupture distance (not epicentral distance) — this matters a lot for the 2025 M7.7 event because the fault rupture extended ~475 km along the Sagaing Fault. A dam might be 300 km from the epicenter but only 5 km from the rupture trace.
Vs30 (shear-wave velocity in the top 30 m) is sampled at each dam from the USGS ShakeMap Vs30 grid (a Wald & Allen 2007 topographic-slope proxy). Range: ~230–875 m/s across the 254 sites (mean ~450 m/s) — dams in the soft sediments of the central basin amplify shaking relative to those founded on rock.
I validated the GMPE against the actual Naypyidaw ShakeMap recording — predicted 0.51g vs observed 0.57g (ratio 1.12), which is well within one standard deviation.
Results:
| Grade | Count |
|---|---|
| Critical | 44 |
| High | 116 |
| Moderate | 45 |
| Low | 49 |
160 dams (63%) scored Critical or High. (This number has moved as two ground-motion bugs were fixed: an earlier GMPE bug floored the PGA of distant dams and under-counted risk, while the scenario "rupture distance" was mistakenly measured to a global plate-boundary file — correcting it to the actual 2025 rupture geometry lowered the scenario PGA of dams near non-ruptured fault sections. The scenario event also silently used the 1988 Lancang M7.7 until the max-magnitude tie was broken by recency. See CHANGELOG.)
The catalog has 9,679 events. Most are small (mean M 3.2), but there are 6 events ≥ M7.0 and 393 ≥ M5.0.
USGS completeness backfill: the upstream API is a multi-network union (62% Thai-network ids, 22% USGS, …) with gaps — most notably an ~18-month hole (2013-09…2015-02, all of 2014 empty). Missing events were backfilled from USGS FDSN (M≥4.0, epicenter inside a Myanmar ADM1 polygon), spatio-temporally deduplicated against the existing catalog so no cross-network duplicate is re-injected: 98 events for the 2014 window (spec 007) + 160 more across 1970–2019 (spec 008), pulled 2026-07-07, reproducible via tools/backfill_usgs.py. ~10 pre-existing cross-network near-duplicate pairs remain in the union. Completeness caveat: because the backfill stops at 2019, the catalog is more uniformly complete at M≥4 for 1970–2019 than for 2020–2025 (still the raw API), so time-resolved rate / temporal-b analysis should treat the 2019/2020 boundary as a coverage change, not a seismicity change. The exact backfilled event ids are frozen in specs/008-backfill-manifest.csv (ComCat is mutable).
b-value and completeness: On the full catalog the Gutenberg-Richter b-value at Mc = 4.0 is 0.72 (N≈2,757), but the raw value (auto-Mc ≈ 2.4) drops to 0.34 — an artifact of the 2025 aftershock sequence flooding the small-magnitude bins. After Gardner-Knopoff (1974) declustering — magnitude-dependent space-time windows, e.g. ~86 km / ~967 days for the M7.7 — the catalog yields Mc ≈ 4.7 and b ≈ 1.04, typical for a tectonic region. The probabilistic hazard below uses these declustered rates.
Clustering: DBSCAN in UTM Zone 47N coordinates (so distances are in meters, not degrees) identifies ~6 seismic zones (the exact count is sensitive to the DBSCAN radius; the dominant central cluster is the robust feature). The central Myanmar cluster contains ~96% of all events.
Aftershock forecasting: Modified Omori law fitted by maximum likelihood (Ogata 1983) to the 2025 M7.7 Sagaing sequence gives p ≈ 0.77 with c ≈ 16 h — a slow decay, partly reflecting early-sequence catalog incompleteness after the mainshock — forecasting expected M≥3 aftershocks at 7, 30, and 90 day windows. (Earlier releases quoted p = 0.93 from a biased least-squares fit that had also selected the 1988 Lancang M7.7 as the mainshock.)
Coulomb stress transfer: Using the USGS finite fault model (530 slip patches, 0–7 m variable slip), I computed which dams are in stress-triggered zones (16 dams, 6%) vs stress shadows (72 dams, 28%), at the standard ±0.01 MPa (0.1 bar) threshold. (An earlier version used a stress kernel that violated point-source symmetry and a ±0.001 MPa threshold inside tidal-stress noise, reporting 34/163 — see CHANGELOG.)
Beyond single-event PGA, each dam gets a full hazard curve using the Cornell-McGuire PSHA approach — integrating the Gutenberg-Richter magnitude distribution with the GMPE over all possible magnitudes (M5–8).
The 475-year PGA (10% chance of exceedance in 50 years) ranges from ~0.002g to ~0.56g across the dam portfolio, with a mean of ~0.14g; 172 dams exceed 0.1g and 46 exceed 0.2g at this return period. The rates come from a Frankel (1995)-style smoothed-seismicity source model built from the Gardner-Knopoff-declustered catalog (b ≈ 1.04), with hazard integrated over the source-to-site distance distribution. These are a catalog-only estimate (no fault-source recurrence term — the committed fault file has no slip rates). The rock 475-yr PGA peaks near ~0.44g at the central-Myanmar seismicity concentration, comparable to GEM v2018.1 reference-rock (~0.2–0.55g) for the Sagaing corridor, but ~2× below the site-amplified >1g near-fault values of Yang et al. (2023, Geoscience Letters 10:48). (An earlier version collapsed the whole regional rate onto each dam's single nearest-fault distance, inflating near-fault 475-yr PGA to ~1.9g — see CHANGELOG.)
34,224 critical infrastructure sites pulled from OSM via Overpass API — schools, hospitals, clinics, police stations, fire stations, universities, and places of worship within the shaking zone.
For each building, PGA is estimated using the same ASK08 GMPE with rupture distance to the combined fault trace.
| Shaking Intensity | Buildings |
|---|---|
| Severe (VIII) | ~5,700 |
| Very Strong (VII) | ~6,200 |
| Strong (VI) | ~8,100 |
2,166 schools and 901 hospitals had PGA above 0.1g.
Uses site-specific Vs30 from the USGS ShakeMap grid (mean 320 m/s) — soft soils in the Irrawaddy basin amplify shaking significantly compared to reference rock.
As a one-off offline check against the USGS ShakeMap grid (not tracked in this repo, unlike the station-list comparison, which regenerates in CI), the Naypyidaw prediction was ~0.43g vs USGS ~0.55g (ratio ~0.78). Near-fault underprediction is expected because this was a supershear rupture.
14 figures in paper/figures/ (300 DPI, PDF + PNG):
| Figure | Description |
|---|---|
| fig1 | Myanmar seismicity and dam locations |
| fig2 | Frequency-magnitude distribution + b-value stability |
| fig3 | Depth histogram + magnitude vs depth |
| fig4 | Dam risk map (color-coded by grade) |
| fig5 | ASK08 PGA attenuation curves with recorded data |
| fig6 | Annual earthquake counts (temporal evolution) |
| fig7 | Risk score distribution + grade counts + Monte Carlo sensitivity |
| fig8 | Dam status and function breakdowns |
| fig9 | Vs30 site classification map |
| fig10 | PSHA hazard curves at representative dams |
| fig11 | Coulomb stress transfer from the 2025 rupture |
| fig12 | Fragility curves (slight / moderate / extensive damage) |
| fig13 | Monte Carlo PGA sensitivity |
| fig14 | OSM building exposure map |
Regenerate all:
python generate_figures.pysrc/mmeq/
├── cli.py # CLI entry point
├── config.py # Config constants
├── export/
│ ├── fetcher.py # API fetching, date range generation
│ ├── writer.py # CSV/JSON export, validation, dedup, rebuild
│ └── pipeline.py # run_export orchestration
├── analysis/
│ ├── aftershock.py # Modified Omori law
│ ├── clustering.py # DBSCAN in UTM coordinates
│ ├── coulomb.py # Coulomb stress transfer
│ ├── dam_risk.py # ASK08 GMPE, Vs30, PGA, hazard curves, risk scoring
│ ├── finite_fault.py # USGS finite fault model loader
│ ├── fragility.py # Dam fragility curves
│ ├── gem_faults.py # GEM Global Active Faults loader
│ ├── osm_exposure.py # OSM building exposure analysis
│ ├── population.py # Population exposure (city-based)
│ ├── geocoder.py # admin/place enrichment (point-in-polygon + kd-tree)
│ ├── seismology.py # b-value, Mc, declustering, scenario selection
│ ├── shakemap_validation.py # GMPE validation against ShakeMap stations
│ └── temporal.py # Temporal analysis charts
└── visualization/
├── animated_map.py # Animated earthquake timeline
├── cross_section.py # 3D depth visualization
├── dashboard.py # Plotly dashboard
├── map.py # Folium interactive map
├── report.py # PDF report generation
└── assets.py + vendor/ # self-hosted JS/CSS (see tools/fetch_vendor.py)
go/ # Go rewrite of the export pipeline (specs/002):
├── cmd/mmeq-export/ # single static binary replacing `mmeq export`
└── internal/{api,catalog,config, # in the daily CI; byte-parity with the Python
export,geocoder} # writer is golden-file tested
tools/ # see tools/README.md
├── backfill_usgs.py # USGS gap/completeness fill (spatio-temporal dedup)
├── backfill.py # mmeq-API monthly re-fetch/merge helper
├── build_figure_data.py # precomputes dashboard/figure data
├── diff_exports.py # Python-vs-Go export tree diff (shadow CI gate)
└── fetch_vendor.py # download + sha256-pin the vendored JS/CSS
A nightly shadow workflow (.github/workflows/shadow_go_export.yml) runs both
exporters against the live API and diffs the trees; the Go binary takes over the
daily fetch only after ≥3 consecutive clean cycles (see specs/002 and specs/004).
Override defaults with environment variables:
| Variable | Default | Description |
|---|---|---|
MMEQ_API_URL |
https://mmeq.akze.net/api/myanmar-quakes |
API endpoint |
MMEQ_START_YEAR |
1950 |
Earliest year to fetch |
MMEQ_EXPORT_DIR |
quake_exports |
Data output directory |
MMEQ_MAX_WORKERS |
10 |
Parallel download threads |
MMEQ_FAULT_LINES |
fault_lines.json |
Fault line GeoJSON path |
| Data | Source | License |
|---|---|---|
| Earthquakes | Myanmar Earthquake API (USGS/ISC) | Open |
| Dams | Open Development Mekong (IFC/WLE) | CC BY-SA 4.0 |
| Fault lines | USGS plate boundary data | Public domain |
| ShakeMap / Finite Fault | USGS event us7000pn9s | Public domain |
| Active faults | GEM Global Active Faults | CC BY-SA 4.0 |
| Buildings | OpenStreetMap via Overpass API | ODbL |
| Population | City-based estimates (in-repo); WorldPop Myanmar 1km grid used for a one-off offline exposure figure (raster not tracked) | CC BY 4.0 |
| Elevation | Copernicus DEM 30m | Open |
| PGA model | Abrahamson & Silva (2008) NGA-West1 | Academic |
| Vs30 proxy | Wald & Allen (2007) slope-based | Academic |
ruff check . # lint (must be clean)
pytest tests/ -v # Python suite (109 tests)
cd go && go test ./... # Go exporter suite incl. golden-parity fixturesPython tests cover data validation, date ranges, CSV/JSON I/O, deduplication, seismology, the hazard model (Coulomb parity, PSHA), map XSS-escaping, vendored assets, and dam-risk scoring. The Go suite additionally pins byte-level parity with the Python writer/geocoder via golden files generated by running the actual Python code.
Contributions welcome — see CONTRIBUTING.md for setup, the
dev loop, and the domain-correctness rules. Non-trivial changes are developed
spec-first (see specs/). Maintenance scripts are documented in
tools/README.md; agent-oriented guidance is in
CLAUDE.md / AGENTS.md.
Interactive docs: mmeq.akze.net/docs (Swagger UI) · OpenAPI spec: /openapi.yaml
Three routes: /api/v2/earthquakes (typed JSON — use this for programmatic access),
/api/v2/export (full-fidelity raw records — what this repo's export pipeline uses),
and the legacy /api/myanmar-quakes (kept for compatibility, shown below).
GET https://mmeq.akze.net/api/myanmar-quakes?from=YYYY-MM-DD&to=YYYY-MM-DD
curl -s "https://mmeq.akze.net/api/myanmar-quakes?from=2025-03-01&to=2025-03-31" | jq '.earthquakes[:3]'import requests
resp = requests.get(
"https://mmeq.akze.net/api/myanmar-quakes",
params={"from": "2025-03-01", "to": "2025-03-31"}
)
for q in resp.json()["earthquakes"]:
print(f"M{q['mag']} at {q['location']}")No auth required. Free and open.
MIT — see LICENSE.