~kris/dots

srice

ref: ff7367cb2ef828502c3f7fde003f45d5daa33206 srice/.local/bin/supervolcano-monitor -rwxr-xr-x 16.2 KiB
ff7367cb — Kris Yotam add ram memory visualization script 4 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
#!/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 <command> [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