Skip to content

Repository files navigation

Myanmar Earthquake Open Data

CI Daily Fetch License: MIT Python 3.9+ Events

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.

What It Does

  • 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

Quick Start

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 map

CLI

mmeq export

Fetches 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

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 Mc

mmeq visualize

mmeq 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 overlay

mmeq report

Full 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

How the Dam Risk Scoring Works

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.)

How the Seismology Works

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.)

Probabilistic Hazard

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.)

Building Exposure from OpenStreetMap

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.

Figures

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.py

Project Structure

src/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).

Configuration

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 Sources

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

Tests

ruff check .              # lint (must be clean)
pytest tests/ -v          # Python suite (109 tests)
cd go && go test ./...    # Go exporter suite incl. golden-parity fixtures

Python 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.

Contributing

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.

API

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.

License

MIT — see LICENSE.

About

Open Data For Myanmar Earthquake Alert

Resources

Contributing

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages