| 1 |
// USGS earthquake feed. Fetches GeoJSON from the USGS summary endpoint. |
| 2 |
// Configurable magnitude threshold and time window. Converts millisecond |
| 3 |
// timestamps to seconds; color-codes severity by magnitude bracket. |
| 4 |
|
| 5 |
const USGS_API = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary"; |
| 6 |
|
| 7 |
fn id() { |
| 8 |
"earthquakes" |
| 9 |
} |
| 10 |
|
| 11 |
fn name() { |
| 12 |
"USGS Earthquakes" |
| 13 |
} |
| 14 |
|
| 15 |
fn capabilities() { |
| 16 |
#{ |
| 17 |
supports_pagination: false |
| 18 |
} |
| 19 |
} |
| 20 |
|
| 21 |
fn config_schema() { |
| 22 |
#{ |
| 23 |
description: "Real-time earthquake data from the USGS. Choose a minimum magnitude and time window.", |
| 24 |
fields: [ |
| 25 |
#{ |
| 26 |
key: "magnitude", |
| 27 |
label: "Minimum Magnitude", |
| 28 |
field_type: "select", |
| 29 |
required: true, |
| 30 |
description: "Minimum earthquake magnitude to show", |
| 31 |
default_value: "significant", |
| 32 |
options: ["significant", "4.5", "2.5", "1.0", "all"] |
| 33 |
}, |
| 34 |
#{ |
| 35 |
key: "timeframe", |
| 36 |
label: "Time Window", |
| 37 |
field_type: "select", |
| 38 |
required: true, |
| 39 |
description: "How far back to look", |
| 40 |
default_value: "day", |
| 41 |
options: ["hour", "day", "week", "month"] |
| 42 |
} |
| 43 |
] |
| 44 |
} |
| 45 |
} |
| 46 |
|
| 47 |
fn fetch(config, cursor) { |
| 48 |
let magnitude = "significant"; |
| 49 |
if config.magnitude != () { |
| 50 |
magnitude = config.magnitude; |
| 51 |
} |
| 52 |
|
| 53 |
let timeframe = "day"; |
| 54 |
if config.timeframe != () { |
| 55 |
timeframe = config.timeframe; |
| 56 |
} |
| 57 |
|
| 58 |
// Build the feed URL |
| 59 |
let feed_name = get_feed_name(magnitude); |
| 60 |
let url = USGS_API + "/" + feed_name + "_" + timeframe + ".geojson"; |
| 61 |
|
| 62 |
let data = http_get_json(url); |
| 63 |
|
| 64 |
if data == () { |
| 65 |
return #{ items: [], has_more: false }; |
| 66 |
} |
| 67 |
|
| 68 |
let features = data.features; |
| 69 |
if features == () { |
| 70 |
return #{ items: [], has_more: false }; |
| 71 |
} |
| 72 |
|
| 73 |
let items = []; |
| 74 |
for feature in features { |
| 75 |
let item = parse_quake(feature); |
| 76 |
if item != () { |
| 77 |
items.push(item); |
| 78 |
} |
| 79 |
} |
| 80 |
|
| 81 |
#{ |
| 82 |
items: items, |
| 83 |
has_more: false |
| 84 |
} |
| 85 |
} |
| 86 |
|
| 87 |
fn get_feed_name(magnitude) { |
| 88 |
if magnitude == "4.5" { return "4.5"; } |
| 89 |
if magnitude == "2.5" { return "2.5"; } |
| 90 |
if magnitude == "1.0" { return "1.0"; } |
| 91 |
if magnitude == "all" { return "all"; } |
| 92 |
"significant" |
| 93 |
} |
| 94 |
|
| 95 |
fn parse_quake(feature) { |
| 96 |
let props = feature.properties; |
| 97 |
if props == () { |
| 98 |
return (); |
| 99 |
} |
| 100 |
|
| 101 |
let title = "Unknown earthquake"; |
| 102 |
if props.title != () { |
| 103 |
title = props.title; |
| 104 |
} else if props.place != () { |
| 105 |
title = props.place; |
| 106 |
} |
| 107 |
|
| 108 |
let mag = 0.0; |
| 109 |
if props.mag != () { |
| 110 |
mag = props.mag; |
| 111 |
} |
| 112 |
|
| 113 |
let place = ""; |
| 114 |
if props.place != () { |
| 115 |
place = props.place; |
| 116 |
} |
| 117 |
|
| 118 |
let time = timestamp_now(); |
| 119 |
if props.time != () { |
| 120 |
// USGS times are in milliseconds |
| 121 |
time = props.time / 1000; |
| 122 |
} |
| 123 |
|
| 124 |
let url = ""; |
| 125 |
if props.url != () { |
| 126 |
url = props.url; |
| 127 |
} |
| 128 |
|
| 129 |
let tsunami = 0; |
| 130 |
if props.tsunami != () { |
| 131 |
tsunami = props.tsunami; |
| 132 |
} |
| 133 |
|
| 134 |
let felt = 0; |
| 135 |
if props.felt != () { |
| 136 |
felt = props.felt; |
| 137 |
} |
| 138 |
|
| 139 |
let alert = ""; |
| 140 |
if props.alert != () { |
| 141 |
alert = props.alert; |
| 142 |
} |
| 143 |
|
| 144 |
let quake_id = ""; |
| 145 |
if feature.id != () { |
| 146 |
quake_id = feature.id; |
| 147 |
} |
| 148 |
|
| 149 |
// Coordinates |
| 150 |
let coords = ""; |
| 151 |
if feature.geometry != () { |
| 152 |
let geo = feature.geometry; |
| 153 |
if geo.coordinates != () { |
| 154 |
let c = geo.coordinates; |
| 155 |
if c.len() >= 2 { |
| 156 |
coords = "Lat: " + c[1] + ", Lon: " + c[0]; |
| 157 |
if c.len() >= 3 { |
| 158 |
coords += ", Depth: " + c[2] + " km"; |
| 159 |
} |
| 160 |
} |
| 161 |
} |
| 162 |
} |
| 163 |
|
| 164 |
// Build body |
| 165 |
let body = "**Location:** " + place + "\n"; |
| 166 |
body += "**Magnitude:** " + mag + "\n"; |
| 167 |
if coords != "" { |
| 168 |
body += "**Coordinates:** " + coords + "\n"; |
| 169 |
} |
| 170 |
if tsunami > 0 { |
| 171 |
body += "**Tsunami warning:** Yes\n"; |
| 172 |
} |
| 173 |
if felt > 0 { |
| 174 |
body += "**Felt reports:** " + felt + "\n"; |
| 175 |
} |
| 176 |
if alert != "" { |
| 177 |
body += "**Alert level:** " + alert + "\n"; |
| 178 |
} |
| 179 |
|
| 180 |
// Indicator based on magnitude |
| 181 |
let indicator = "๐ข"; |
| 182 |
if mag >= 7.0 { |
| 183 |
indicator = "๐ด"; |
| 184 |
} else if mag >= 5.0 { |
| 185 |
indicator = "๐ "; |
| 186 |
} else if mag >= 3.0 { |
| 187 |
indicator = "๐ก"; |
| 188 |
} |
| 189 |
|
| 190 |
let secondary = "M" + mag + " ยท " + place; |
| 191 |
|
| 192 |
let tags = ["earthquake", "usgs"]; |
| 193 |
if tsunami > 0 { |
| 194 |
tags.push("tsunami"); |
| 195 |
} |
| 196 |
|
| 197 |
#{ |
| 198 |
id: #{ source: "earthquakes", item_id: quake_id }, |
| 199 |
bite: #{ |
| 200 |
author: "USGS", |
| 201 |
text: truncate(title, 100), |
| 202 |
secondary: truncate(secondary, 80), |
| 203 |
indicator: indicator |
| 204 |
}, |
| 205 |
content: #{ |
| 206 |
title: title, |
| 207 |
body: body, |
| 208 |
url: url |
| 209 |
}, |
| 210 |
meta: #{ |
| 211 |
source_name: "USGS Earthquakes", |
| 212 |
published_at: time, |
| 213 |
tags: tags |
| 214 |
} |
| 215 |
} |
| 216 |
} |
| 217 |
|