#!/bin/bash # Supervolcano Monitor - Live Volcanic Surveillance Grid # Fetches seismograms, webcams, and earthquake data for Earth's supervolcanoes # Assembles a monitoring dashboard grid via ImageMagick montage # Alias: svm # # Tracked: Yellowstone | Long Valley | Campi Flegrei | Taupo | Toba | Aira # Sources: USGS Earthquake Hazards, USGS HANS, GeoNet NZ # Requires: curl, jq, imagemagick, mpv export DISPLAY=:0 # ═══════════════════════════════════════════════════════════════ # CONFIGURATION # ═══════════════════════════════════════════════════════════════ cache="$HOME/.cache/maritime/supervolcano" grid_file="$cache/grid.png" mkdir -p "$cache" PW=640 PH=480 # Fonts FONT_BOLD=$(fc-match -f '%{file}' 'DejaVu Sans:Bold' 2>/dev/null) FONT_REG=$(fc-match -f '%{file}' 'DejaVu Sans' 2>/dev/null) FB=() FR=() [ -n "$FONT_BOLD" ] && FB=(-font "$FONT_BOLD") [ -n "$FONT_REG" ] && FR=(-font "$FONT_REG") # Grid layout order (3 columns x 2 rows) ORDER=(yellowstone longvalley campi_flegrei taupo toba aira) # ═══════════════════════════════════════════════════════════════ # VOLCANO DEFINITIONS # ═══════════════════════════════════════════════════════════════ declare -A VNAME VLOC VBBOX VVNUM VIMG VNAME=( [yellowstone]="YELLOWSTONE" [longvalley]="LONG VALLEY" [campi_flegrei]="CAMPI FLEGREI" [taupo]="TAUPO" [toba]="TOBA" [aira]="AIRA" ) VLOC=( [yellowstone]="Wyoming, USA" [longvalley]="California, USA" [campi_flegrei]="Naples, Italy" [taupo]="New Zealand" [toba]="Sumatra, Indonesia" [aira]="Kagoshima, Japan" ) # Bounding boxes: minlat|maxlat|minlon|maxlon VBBOX=( [yellowstone]="44.0|45.0|-111.5|-109.5" [longvalley]="37.4|38.0|-119.3|-118.5" [campi_flegrei]="40.6|40.95|13.9|14.25" [taupo]="-39.0|-38.0|175.5|176.5" [toba]="2.0|3.0|98.0|99.5" [aira]="31.3|31.8|130.4|131.0" ) # USGS HANS volcano numbers (US volcanoes only) VVNUM=( [yellowstone]="325010" [longvalley]="323822" ) # Live image sources (seismograms / webcams) VIMG=( [yellowstone]="https://earthquake.usgs.gov/static/earthquake-network-operations/Seismic_Data/telemetry_data/LKWY_24hr.png" [taupo]="https://images.geonet.org.nz/volcano/cameras/latest/ruapehunorth.jpg" ) # Alternate images for slideshow mode declare -A VIMG_ALT VIMG_ALT=( [yellowstone_flwy]="https://earthquake.usgs.gov/static/earthquake-network-operations/Seismic_Data/telemetry_data/FLWY_24hr.png" [yellowstone_snow]="https://earthquake.usgs.gov/static/earthquake-network-operations/Seismic_Data/telemetry_data/SNOW_24hr.png" [taupo_tongariro]="https://images.geonet.org.nz/volcano/cameras/latest/tongariro.jpg" [taupo_ngauruhoe]="https://images.geonet.org.nz/volcano/cameras/latest/ngauruhoe.jpg" ) # ═══════════════════════════════════════════════════════════════ # DATA FETCHING # ═══════════════════════════════════════════════════════════════ # Earthquake count for a volcano's bounding box (last 7 days) fetch_quake_count() { local key="$1" IFS='|' read -r minlat maxlat minlon maxlon <<< "${VBBOX[$key]}" local start start=$(date -u -d '7 days ago' +%Y-%m-%d) local count count=$(curl -sf "https://earthquake.usgs.gov/fdsnws/event/1/count?format=text&starttime=${start}&minlatitude=${minlat}&maxlatitude=${maxlat}&minlongitude=${minlon}&maxlongitude=${maxlon}") echo "${count:-0}" > "$cache/count_${key}.txt" } # USGS HANS alert level fetch_usgs_alert() { local key="$1" local vnum="${VVNUM[$key]}" [ -z "$vnum" ] && return local json json=$(curl -sf "https://volcanoes.usgs.gov/hans-public/api/volcano/getVolcano/${vnum}") local alert alert=$(echo "$json" | jq -r '.alertLevel // "NORMAL"' 2>/dev/null) echo "${alert:-NORMAL}" > "$cache/alert_${key}.txt" } # GeoNet NZ alert levels fetch_geonet_alerts() { local json json=$(curl -sf "https://api.geonet.org.nz/volcano/val") local level level=$(echo "$json" | jq -r '[.features[] | select(.properties.volcanoID | test("taupo";"i"))][0].properties.level // 0' 2>/dev/null) case "$level" in 0) echo "NORMAL" ;; 1) echo "MINOR UNREST" ;; 2) echo "MODERATE UNREST" ;; 3) echo "MINOR ERUPTION" ;; 4) echo "MODERATE ERUPTION" ;; 5) echo "MAJOR ERUPTION" ;; *) echo "NORMAL" ;; esac > "$cache/alert_taupo.txt" } # Fetch live image for a volcano fetch_live_image() { local key="$1" local url="${VIMG[$key]}" [ -z "$url" ] && return 1 curl -sLo "$cache/raw_${key}.img" "$url" } # ═══════════════════════════════════════════════════════════════ # PANEL GENERATION # ═══════════════════════════════════════════════════════════════ # Alert level -> color alert_color() { case "$1" in NORMAL) echo "#3fb950" ;; ADVISORY|"MINOR UNREST") echo "#d29922" ;; WATCH|"MODERATE UNREST") echo "#db6d28" ;; WARNING|*ERUPTION*) echo "#f85149" ;; *) echo "#8b949e" ;; esac } # Generate status card (no live image available) make_card() { local key="$1" local name="${VNAME[$key]}" local loc="${VLOC[$key]}" local quakes alert ac quakes=$(cat "$cache/count_${key}.txt" 2>/dev/null || echo "?") alert=$(cat "$cache/alert_${key}.txt" 2>/dev/null || echo "—") ac=$(alert_color "$alert") magick -size ${PW}x${PH} xc:'#0d1117' \ "${FB[@]}" \ -fill '#58a6ff' -pointsize 36 -gravity North -annotate +0+30 "$name" \ "${FR[@]}" \ -fill '#8b949e' -pointsize 20 -annotate +0+75 "$loc" \ -fill white -pointsize 80 -gravity Center -annotate +0-20 "$quakes" \ -fill '#8b949e' -pointsize 16 -annotate +0+45 "earthquakes (7 days)" \ "${FB[@]}" \ -fill "$ac" -pointsize 22 -gravity South -annotate +0+30 "$alert" \ "$cache/panel_${key}.png" } # Label a live image with overlay make_labeled() { local key="$1" local name="${VNAME[$key]}" local loc="${VLOC[$key]}" local quakes quakes=$(cat "$cache/count_${key}.txt" 2>/dev/null || echo "?") local input="$cache/raw_${key}.img" local output="$cache/panel_${key}.png" [ ! -f "$input" ] || [ ! -s "$input" ] && make_card "$key" && return magick "$input" \ -resize ${PW}x${PH}! \ -fill 'rgba(0,0,0,0.7)' -draw "rectangle 0,0 ${PW},60" \ -fill 'rgba(0,0,0,0.7)' -draw "rectangle 0,$((PH-40)) ${PW},${PH}" \ "${FB[@]}" \ -fill '#58a6ff' -pointsize 26 -gravity NorthWest -annotate +10+16 "$name" \ "${FR[@]}" \ -fill '#8b949e' -pointsize 16 -gravity NorthEast -annotate +10+22 "$loc" \ -fill white -pointsize 16 -gravity SouthWest -annotate +10+12 "Quakes (7d): $quakes" \ "${FB[@]}" \ -fill '#3fb950' -pointsize 16 -gravity SouthEast -annotate +10+12 "LIVE" \ "$output" } # Build panel for a volcano (picks labeled or card) make_panel() { local key="$1" if [ -n "${VIMG[$key]}" ] && [ -f "$cache/raw_${key}.img" ] && [ -s "$cache/raw_${key}.img" ]; then make_labeled "$key" else make_card "$key" fi } # ═══════════════════════════════════════════════════════════════ # GRID ASSEMBLY # ═══════════════════════════════════════════════════════════════ generate_grid() { local panels=() for key in "${ORDER[@]}"; do make_panel "$key" panels+=("$cache/panel_${key}.png") done montage "${panels[@]}" \ -tile 3x2 -geometry ${PW}x${PH}+4+4 \ -background '#010409' \ "${FB[@]}" -fill '#58a6ff' -pointsize 22 \ -title "SUPERVOLCANO MONITOR · $(date -u '+%Y-%m-%d %H:%M UTC')" \ "$grid_file" echo "Grid saved: $grid_file" } # ═══════════════════════════════════════════════════════════════ # COMMANDS # ═══════════════════════════════════════════════════════════════ update() { echo "Fetching supervolcano data..." # Earthquake counts (parallel) for key in "${ORDER[@]}"; do fetch_quake_count "$key" & done # Alert levels (parallel) fetch_usgs_alert yellowstone & fetch_usgs_alert longvalley & fetch_geonet_alerts & # Live images (parallel) for key in "${ORDER[@]}"; do [ -n "${VIMG[$key]}" ] && fetch_live_image "$key" & done # Alternate images (parallel) for alt_key in "${!VIMG_ALT[@]}"; do curl -sLo "$cache/raw_${alt_key}.img" "${VIMG_ALT[$alt_key]}" & done wait echo "Data fetched. Generating grid..." generate_grid notify-send -t 3000 -i "$grid_file" "Supervolcano Monitor" "Dashboard updated" } # Resolve shorthand volcano name resolve() { case "${1,,}" in y*) echo "yellowstone" ;; l*|lv) echo "longvalley" ;; c*|cf) echo "campi_flegrei" ;; ta*|nz) echo "taupo" ;; to*) echo "toba" ;; a*) echo "aira" ;; *) echo "" ;; esac } # Display grid in mpv show_grid() { [ ! -f "$grid_file" ] && echo "No grid cached. Run: svm update" && return 1 setsid -f mpv --title="Supervolcano Monitor" --autofit=90% \ --loop-file=inf "$grid_file" } # View specific volcano panel show_volcano() { local key key=$(resolve "$1") [ -z "$key" ] && echo "Unknown volcano: $1" && return 1 local panel="$cache/panel_${key}.png" [ ! -f "$panel" ] && echo "No data for $key. Run: svm update" && return 1 setsid -f mpv --title="${VNAME[$key]}" --autofit=60% \ --loop-file=inf "$panel" } # Slideshow of all panels + alternates show_all() { local imgs=() for key in "${ORDER[@]}"; do [ -f "$cache/panel_${key}.png" ] && imgs+=("$cache/panel_${key}.png") done for alt_key in "${!VIMG_ALT[@]}"; do [ -f "$cache/raw_${alt_key}.img" ] && imgs+=("$cache/raw_${alt_key}.img") done setsid -f mpv --title="Supervolcano Monitor - All Data" --idle --autofit=70% \ --loop-playlist --image-display-duration=4 "${imgs[@]}" } # Send notification notify() { if [ -n "$1" ]; then local key key=$(resolve "$1") [ -z "$key" ] && return local panel="$cache/panel_${key}.png" [ -f "$panel" ] && notify-send -t 60000 -i "$panel" "${VNAME[$key]}" "${VLOC[$key]}" else [ -f "$grid_file" ] && notify-send -t 60000 -i "$grid_file" "Supervolcano Monitor" "" fi } # Text status report status() { echo "SUPERVOLCANO STATUS — $(date -u '+%Y-%m-%d %H:%M UTC')" echo "═══════════════════════════════════════════════════════" for key in "${ORDER[@]}"; do local quakes alert quakes=$(cat "$cache/count_${key}.txt" 2>/dev/null || echo "?") alert=$(cat "$cache/alert_${key}.txt" 2>/dev/null || echo "—") printf " %-16s %-22s %4s quakes %s\n" "${VNAME[$key]}" "${VLOC[$key]}" "$quakes" "$alert" done echo "═══════════════════════════════════════════════════════" } # Help help() { cat << 'EOF' Supervolcano Monitor - Live Volcanic Surveillance Grid ══════════════════════════════════════════════════════════ Usage: svm [volcano] Commands: update, u Fetch all data and generate grid grid, g Display cached dashboard grid notify, n [volcano] Send grid/volcano to dunst notification images, i [volcano] View specific volcano panel in mpv all Slideshow of all panels and alternates status, s Text status report help, h Show this help Volcanoes: yellowstone, y Yellowstone Caldera (Wyoming, USA) longvalley, l Long Valley Caldera (California, USA) campi_flegrei, c Campi Flegrei (Naples, Italy) taupo, t Taupo Caldera (New Zealand) toba, to Toba Caldera (Sumatra, Indonesia) aira, a Aira Caldera (Kagoshima, Japan) Examples: svm update Refresh all data and build grid svm grid Open dashboard grid in mpv svm images yellowstone View Yellowstone seismogram svm notify taupo Send Taupo webcam to dunst svm status Print text status report Live Data Sources: Yellowstone USGS 24hr seismogram (LKWY station) Taupo GeoNet Ruapehu North webcam All USGS earthquake API (7-day count) US volcanoes USGS HANS alert levels NZ volcanoes GeoNet volcanic alert levels Grid Layout (3x2): ┌──────────────┬──────────────┬──────────────┐ │ Yellowstone │ Long Valley │ Campi Flegrei │ │ (seismogram) │ (status) │ (status) │ ├──────────────┼──────────────┼──────────────┤ │ Taupo │ Toba │ Aira │ │ (webcam) │ (status) │ (status) │ └──────────────┴──────────────┴──────────────┘ Requires: curl, jq, imagemagick, mpv ══════════════════════════════════════════════════════════ EOF } # Status bar output tobar() { local elevated=false for key in "${ORDER[@]}"; do local alert alert=$(cat "$cache/alert_${key}.txt" 2>/dev/null || echo "NORMAL") case "$alert" in NORMAL|—) ;; *) elevated=true ;; esac done if $elevated; then echo " SVM" else echo " SVM" fi } # ═══════════════════════════════════════════════════════════════ # MAIN # ═══════════════════════════════════════════════════════════════ case "$1" in u*) update ;; g*) show_grid ;; n*) shift notify "$1" ;; i*) shift if [ -n "$1" ]; then show_volcano "$1" else show_grid fi ;; all) show_all ;; s*) status ;; h*|--help) help ;; "") tobar ;; *) help ;; esac # Status bar click handlers (dwmblocks) case $BLOCK_BUTTON in 1) notify-send -t 60000 -i "$grid_file" "Supervolcano Monitor" "$(status)" ;; 2) update ;; 3) show_grid ;; 4) notify-send -t 60000 -i "$cache/panel_yellowstone.png" "Yellowstone" "24hr Seismogram" ;; 5) notify-send -t 60000 -i "$cache/panel_taupo.png" "Taupo" "Ruapehu North Webcam" ;; 6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;; esac