diff --git a/generate_earthquakes.py b/generate_earthquakes.py new file mode 100644 index 0000000..379c1be --- /dev/null +++ b/generate_earthquakes.py @@ -0,0 +1,143 @@ +import requests +import json +import os +from datetime import datetime, timedelta +import re +import pytz + +def parse_geonet_title(title): + match = re.search(r'\d+\skm\s(north|south|west|east|north-east|north-west|south-east|south-west|of)?\s*(.*)', title) + if match: + location = match.group(2).strip() + location = location.replace('region', '').strip() + return location if location else "Unknown Location" + + match = re.match(r'M\s\d+\.?\d*,\s*(.*)', title) + if match: + location = match.group(1).strip() + location = location.replace('region', '').strip() + return location if location else "Unknown Location" + + return title + +def get_earthquakes(min_mmi=8, limit=20, from_today_only=True): + url = "https://api.geonet.org.nz/quakes/services/quake/" + try: + response = requests.get(url) + response.raise_for_status() + data = response.json() + + all_quakes = [] + + utc_timezone = pytz.utc + nzt_timezone = pytz.timezone('Pacific/Auckland') + + current_nzt_date = datetime.now(nzt_timezone).date() + + for feature in data.get('features', []): + props = feature.get('properties', {}) + mmi = props.get('mmi') + time_utc_ms = props.get('time') + status = props.get('status', 'latest') + quake_id = feature.get('id') + + if time_utc_ms is None: + continue + + dt_object_utc_aware = utc_timezone.localize(datetime.fromtimestamp(time_utc_ms / 1000)) + dt_object_nzt = dt_object_utc_aware.astimezone(nzt_timezone) + + if from_today_only: + if dt_object_nzt.date() != current_nzt_date: + continue + + if status != 'deleted': + if mmi is None or mmi < min_mmi: + continue + + title = props.get('title', 'Unknown Event') + time_str = dt_object_nzt.strftime("%d - %b - %Y %I:%M %p %z") + location = parse_geonet_title(title) + + all_quakes.append({ + 'mmi': mmi, + 'magnitude': props.get('magnitude'), + 'depth': props.get('depth'), + 'location': location, + 'time': time_str, + 'id': quake_id, + 'utc_timestamp': time_utc_ms, + 'status': status + }) + + all_quakes.sort(key=lambda x: x['utc_timestamp'], reverse=True) + return all_quakes[:limit] + + except requests.exceptioins.RequestException as e: + print(f"Error fetching data from Geonet API: {e}") + return [] + +def generate_html(quakes, output_file="geonet_earthquakes.html", css_file="style.css"): + html_content = f""" + + + + + + Latest Earthquakes NZ + + + +
+ """ + + if not quakes: + html_content += """ +
+
--
+ """ + else: + for quake in quakes: + deleted_class = " deleted-item" if quake['status'] == 'deleted' else "" + + mmi_box_classes = "" + mmi_box_content = "" + + if quake['status'] == 'deleted': + mmi_box_classes = "deleted-marker" + mmi_box_content = "X" + else: + mmi_class_value = min(quake['mmi'] if quake['mmi'] is not None else 0, 11) + if mmi_class_value >= 8: + mmi_box_classes = "mmi-8" + else: + mmi_box_classes = f"mmi-{mmi_class_value}" + mmi_box_content = str(quake['mmi']) if quake['mmi'] is not None else '--' + + magnitude_display = f"M{quake['magnitude']:.1f}" if quake['magnitude'] is not None else '--' + depth_display = f"{quake['depth']}km" if quake['depth'] is not None else '--km' + location_display = quake['location'] if quake['location'] else 'Unknown Location' + time_display = quake['time'] if quake['time'] else 'Unknown Time' + status_display = quake['status'].capitalize() if quake['status'] else '--' + id_display = quake['id'] if quake['id'] else '--' + + html_content += f""" +
+
{mmi_box_content}
+
Status: {status_display}
+
+ Magnitude: {magnitude_display} + Depth: {depth_display} +
+
Where: {location_display}
+
When: {time_display}
+
Quake ID: {id_display}
+ """ + + html_content += """ +
+ + + """ + + diff --git a/geonet_earhtquakes_list.html b/geonet_earhtquakes_list.html new file mode 100644 index 0000000..e69de29 diff --git a/style.css b/style.css new file mode 100644 index 0000000..f07073a --- /dev/null +++ b/style.css @@ -0,0 +1,109 @@ +/* import for google fonts */ +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap'); /* Roboto */ +@import url('https://fonts.googleapis.com/css2?familt=Source+Code+Pro&display=swap'); /* Monospace */ + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; /* fallback if imports fail */ + color: white; + background-color: transparent; + margin: 0; + padding: 5px; + overflow: hidden; + width: 125px; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.earthquake-item { + display: grid; + grid-template-columns: 20px 1fr; + grid-template-rows: auto auto auto auto auto auto; + gap: 2px 5px; + width: 100%; + margin-bottom: 12px; + padding: 5px; + background-color: rgba(0, 0, 0, 0.6); + border-radius: 4px; + box-shaddow: 0 1px 3px rgba(0, 0, 0, 0.4); + font-size: 0.85em; + line-height: 1.3; +} + +.earthquake-item.deleted-item { + opacity: 0.5; + transition: opacity 0.3s ease; +} + +.earthquake-item.deleted-item .detail-label span{ + text-decoration: line-through 2px red; +} + +.earthquake-item.deleted-item .status-line, +.earhtquake-item.delted-item .status-line span{ + text-decoration: none !important; + color: #bbb !important; +} + +.earthquake-item.deleted-item .status-line span{ + color: white !important; +} + +.mmi-box { + grid-column: 1 / 2; + grid-row: 1 / span 4; + color: black; + font-family: 'Roboto', sans-serif; + font-size: 1.2em; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + border-radius: 3px; + width: 20px; + height: 100%; + box-sizing: border-box; + padding: 2px; +} + +.mmi-box.deleted-marker { + background-color: transparent; + color: red; + font-size: 2em; + font-weight: bold; + font-family: Arial, sans-serif; + text-shaddow: 1px 1px 2px rgba(192, 192, 192, 0.7); +} + +/* geonet colors */ +.mmi-1: {background-color: #fff7f3;} /* Unnoticable */ +.mmi-2: {background-color: #feedde;} /* Unnoticable */ +.mmi-3: {background-color: #fdd0a2;} /* weak */ +.mmi-4: {background-color: #fdae6b;} /* Light */ +.mmi-5: {background-color: #fd8d3c;} /* moderate */ +.mmi-6: {background-color: #f16913;} /* strong */ +.mmi-7: {background-color: #f03b20;} /* servere */ + +/* .mmi 8-11 mapped to the same color */ +.mmi-8: {background-color: #bd0026;} /* Extreme */ + +.detail-label { + grid-column: 2 / 3; + font-weight: bold; + color: #bbb; + white-space: nowrap; + font-family: sans-serif; +} + +.detail-label span{ + font-family: 'Source Code Pro', monospace; + font-weight: normal; + color: white; +} + +.status-line {grid-row: 1 / 2;} +.mag-depth {grid-row: 2 / 3; display: flex; justify-content: space-between;} +.location {grid-row: 3 / 4;} +.time {grid-row: 4 / 5;} +.id-line {grid-row: 5 / 6;}