Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
| 1 |
import os
|
|
|
|
|
|
|
|
|
|
| 2 |
import uuid
|
| 3 |
import tempfile
|
| 4 |
from datetime import datetime, timedelta, timezone
|
|
@@ -17,9 +20,9 @@ from linebot.v3.webhooks import MessageEvent, TextMessageContent
|
|
| 17 |
import requests
|
| 18 |
import pandas as pd
|
| 19 |
|
| 20 |
-
# --- Matplotlib (
|
| 21 |
import matplotlib
|
| 22 |
-
matplotlib.use("Agg") #
|
| 23 |
import matplotlib.pyplot as plt
|
| 24 |
from matplotlib.colors import Normalize
|
| 25 |
import matplotlib.cm as cm
|
|
@@ -29,7 +32,7 @@ CHANNEL_ACCESS_TOKEN = os.getenv("CHANNEL_ACCESS_TOKEN")
|
|
| 29 |
CHANNEL_SECRET = os.getenv("CHANNEL_SECRET")
|
| 30 |
HF_SPACE_URL = os.getenv("SPACEURL") # e.g., https://<space>.hf.space
|
| 31 |
|
| 32 |
-
# --- Writable static directory
|
| 33 |
STATIC_DIR = os.getenv("STATIC_DIR", os.path.join(tempfile.gettempdir(), "static"))
|
| 34 |
os.makedirs(STATIC_DIR, exist_ok=True)
|
| 35 |
|
|
@@ -67,7 +70,6 @@ def home():
|
|
| 67 |
def healthz():
|
| 68 |
return "ok"
|
| 69 |
|
| 70 |
-
# Serve files from our writable static dir (e.g., /tmp/static)
|
| 71 |
@app.route("/static/<path:filename>")
|
| 72 |
def serve_static(filename):
|
| 73 |
return send_from_directory(STATIC_DIR, filename)
|
|
@@ -145,12 +147,9 @@ def fetch_taiwan_df_this_year(min_mag=5.0) -> pd.DataFrame | str:
|
|
| 145 |
except Exception as e:
|
| 146 |
return f"❌ 查詢失敗: {e}"
|
| 147 |
|
| 148 |
-
# --- Map
|
| 149 |
def create_and_save_map(df: pd.DataFrame) -> str:
|
| 150 |
-
# Figure
|
| 151 |
fig, ax = plt.subplots(figsize=(9, 6), dpi=150)
|
| 152 |
-
|
| 153 |
-
# Taiwan bounding box & grid
|
| 154 |
lon_min, lon_max = 118.5, 123.5
|
| 155 |
lat_min, lat_max = 20.5, 26.8
|
| 156 |
ax.set_xlim(lon_min, lon_max)
|
|
@@ -158,17 +157,13 @@ def create_and_save_map(df: pd.DataFrame) -> str:
|
|
| 158 |
ax.set_xlabel("Longitude (°E)")
|
| 159 |
ax.set_ylabel("Latitude (°N)")
|
| 160 |
ax.set_title(f"今年 ({datetime.now(timezone.utc).year}) 台灣區域顯著地震 (M≥5.0) — UTC")
|
| 161 |
-
|
| 162 |
ax.grid(True, linestyle="--", linewidth=0.5, alpha=0.4)
|
| 163 |
|
| 164 |
-
# Scatter colored by magnitude
|
| 165 |
mags = df["magnitude"].astype(float).clip(lower=0)
|
| 166 |
norm = Normalize(vmin=max(4.5, mags.min()), vmax=max(6.5, mags.max()))
|
| 167 |
cmap = cm.get_cmap("YlOrRd")
|
| 168 |
colors = cmap(norm(mags.values))
|
| 169 |
-
|
| 170 |
-
# Size scale (points^2): tune for LINE preview
|
| 171 |
-
sizes = 15 + (mags - mags.min()) * 25 # 15–approx 140
|
| 172 |
|
| 173 |
sc = ax.scatter(
|
| 174 |
df["longitude"].values,
|
|
@@ -180,16 +175,13 @@ def create_and_save_map(df: pd.DataFrame) -> str:
|
|
| 180 |
alpha=0.9,
|
| 181 |
)
|
| 182 |
|
| 183 |
-
# Colorbar
|
| 184 |
cbar = fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax, pad=0.02)
|
| 185 |
cbar.set_label("Magnitude")
|
| 186 |
|
| 187 |
-
# Legend example (size ~ mag)
|
| 188 |
for m in [5.0, 6.0, 7.0]:
|
| 189 |
ax.scatter([], [], s=15 + (m - mags.min()) * 25, c="none", edgecolor="k", label=f"M {m:.1f}")
|
| 190 |
ax.legend(title="Size ∝ Magnitude", loc="lower right", framealpha=0.9)
|
| 191 |
|
| 192 |
-
# Save
|
| 193 |
filename = f"map_{uuid.uuid4().hex}.png"
|
| 194 |
filepath = os.path.join(STATIC_DIR, filename)
|
| 195 |
fig.tight_layout()
|
|
@@ -221,7 +213,6 @@ def handle_message(event):
|
|
| 221 |
with ApiClient(configuration) as api_client:
|
| 222 |
line_bot_api = MessagingApi(api_client)
|
| 223 |
|
| 224 |
-
# Taiwan map command
|
| 225 |
if ("臺灣地震畫圖" in user_message) or ("台灣地震畫圖" in user_message):
|
| 226 |
result = fetch_taiwan_df_this_year()
|
| 227 |
if isinstance(result, pd.DataFrame):
|
|
@@ -245,7 +236,6 @@ def handle_message(event):
|
|
| 245 |
line_bot_api.reply_message_with_http_info(reply)
|
| 246 |
return
|
| 247 |
|
| 248 |
-
# Help
|
| 249 |
if user_message == "/help":
|
| 250 |
text = (
|
| 251 |
"📖 地震預警 dayichen 指令說明\n\n"
|
|
@@ -260,7 +250,6 @@ def handle_message(event):
|
|
| 260 |
)
|
| 261 |
return
|
| 262 |
|
| 263 |
-
# Taiwan list
|
| 264 |
if ("臺灣地震" in user_message) or ("台灣地震" in user_message):
|
| 265 |
result = fetch_taiwan_df_this_year()
|
| 266 |
if isinstance(result, pd.DataFrame):
|
|
@@ -274,13 +263,11 @@ def handle_message(event):
|
|
| 274 |
reply_text = "\n\n".join(lines)
|
| 275 |
else:
|
| 276 |
reply_text = result
|
| 277 |
-
|
| 278 |
line_bot_api.reply_message_with_http_info(
|
| 279 |
ReplyMessageRequest(reply_token=event.reply_token, messages=[TextMessage(text=reply_text)])
|
| 280 |
)
|
| 281 |
return
|
| 282 |
|
| 283 |
-
# Global last 24h
|
| 284 |
if ("地震" in user_message) or ("quake" in user_message):
|
| 285 |
reply_text = fetch_global_last24h_text()
|
| 286 |
line_bot_api.reply_message_with_http_info(
|
|
@@ -288,7 +275,6 @@ def handle_message(event):
|
|
| 288 |
)
|
| 289 |
return
|
| 290 |
|
| 291 |
-
# Greeting
|
| 292 |
if ("你好" in user_message) or ("hi" in user_message):
|
| 293 |
line_bot_api.reply_message_with_http_info(
|
| 294 |
ReplyMessageRequest(
|
|
@@ -296,7 +282,4 @@ def handle_message(event):
|
|
| 296 |
messages=[TextMessage(text="👋 你好!我是地震查詢機器人���\n\n輸入 /help 查看所有指令。")]
|
| 297 |
)
|
| 298 |
)
|
| 299 |
-
return
|
| 300 |
-
|
| 301 |
-
# Unmatched: no-op
|
| 302 |
-
return
|
|
|
|
| 1 |
import os
|
| 2 |
+
# ✅ Fix Matplotlib config/cache permission issue in read-only containers
|
| 3 |
+
os.environ["MPLCONFIGDIR"] = "/tmp/matplotlib"
|
| 4 |
+
|
| 5 |
import uuid
|
| 6 |
import tempfile
|
| 7 |
from datetime import datetime, timedelta, timezone
|
|
|
|
| 20 |
import requests
|
| 21 |
import pandas as pd
|
| 22 |
|
| 23 |
+
# --- Matplotlib (headless)
|
| 24 |
import matplotlib
|
| 25 |
+
matplotlib.use("Agg") # Use Agg backend (no display server required)
|
| 26 |
import matplotlib.pyplot as plt
|
| 27 |
from matplotlib.colors import Normalize
|
| 28 |
import matplotlib.cm as cm
|
|
|
|
| 32 |
CHANNEL_SECRET = os.getenv("CHANNEL_SECRET")
|
| 33 |
HF_SPACE_URL = os.getenv("SPACEURL") # e.g., https://<space>.hf.space
|
| 34 |
|
| 35 |
+
# --- Writable static directory ---
|
| 36 |
STATIC_DIR = os.getenv("STATIC_DIR", os.path.join(tempfile.gettempdir(), "static"))
|
| 37 |
os.makedirs(STATIC_DIR, exist_ok=True)
|
| 38 |
|
|
|
|
| 70 |
def healthz():
|
| 71 |
return "ok"
|
| 72 |
|
|
|
|
| 73 |
@app.route("/static/<path:filename>")
|
| 74 |
def serve_static(filename):
|
| 75 |
return send_from_directory(STATIC_DIR, filename)
|
|
|
|
| 147 |
except Exception as e:
|
| 148 |
return f"❌ 查詢失敗: {e}"
|
| 149 |
|
| 150 |
+
# --- Map with Matplotlib ---
|
| 151 |
def create_and_save_map(df: pd.DataFrame) -> str:
|
|
|
|
| 152 |
fig, ax = plt.subplots(figsize=(9, 6), dpi=150)
|
|
|
|
|
|
|
| 153 |
lon_min, lon_max = 118.5, 123.5
|
| 154 |
lat_min, lat_max = 20.5, 26.8
|
| 155 |
ax.set_xlim(lon_min, lon_max)
|
|
|
|
| 157 |
ax.set_xlabel("Longitude (°E)")
|
| 158 |
ax.set_ylabel("Latitude (°N)")
|
| 159 |
ax.set_title(f"今年 ({datetime.now(timezone.utc).year}) 台灣區域顯著地震 (M≥5.0) — UTC")
|
|
|
|
| 160 |
ax.grid(True, linestyle="--", linewidth=0.5, alpha=0.4)
|
| 161 |
|
|
|
|
| 162 |
mags = df["magnitude"].astype(float).clip(lower=0)
|
| 163 |
norm = Normalize(vmin=max(4.5, mags.min()), vmax=max(6.5, mags.max()))
|
| 164 |
cmap = cm.get_cmap("YlOrRd")
|
| 165 |
colors = cmap(norm(mags.values))
|
| 166 |
+
sizes = 15 + (mags - mags.min()) * 25
|
|
|
|
|
|
|
| 167 |
|
| 168 |
sc = ax.scatter(
|
| 169 |
df["longitude"].values,
|
|
|
|
| 175 |
alpha=0.9,
|
| 176 |
)
|
| 177 |
|
|
|
|
| 178 |
cbar = fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax, pad=0.02)
|
| 179 |
cbar.set_label("Magnitude")
|
| 180 |
|
|
|
|
| 181 |
for m in [5.0, 6.0, 7.0]:
|
| 182 |
ax.scatter([], [], s=15 + (m - mags.min()) * 25, c="none", edgecolor="k", label=f"M {m:.1f}")
|
| 183 |
ax.legend(title="Size ∝ Magnitude", loc="lower right", framealpha=0.9)
|
| 184 |
|
|
|
|
| 185 |
filename = f"map_{uuid.uuid4().hex}.png"
|
| 186 |
filepath = os.path.join(STATIC_DIR, filename)
|
| 187 |
fig.tight_layout()
|
|
|
|
| 213 |
with ApiClient(configuration) as api_client:
|
| 214 |
line_bot_api = MessagingApi(api_client)
|
| 215 |
|
|
|
|
| 216 |
if ("臺灣地震畫圖" in user_message) or ("台灣地震畫圖" in user_message):
|
| 217 |
result = fetch_taiwan_df_this_year()
|
| 218 |
if isinstance(result, pd.DataFrame):
|
|
|
|
| 236 |
line_bot_api.reply_message_with_http_info(reply)
|
| 237 |
return
|
| 238 |
|
|
|
|
| 239 |
if user_message == "/help":
|
| 240 |
text = (
|
| 241 |
"📖 地震預警 dayichen 指令說明\n\n"
|
|
|
|
| 250 |
)
|
| 251 |
return
|
| 252 |
|
|
|
|
| 253 |
if ("臺灣地震" in user_message) or ("台灣地震" in user_message):
|
| 254 |
result = fetch_taiwan_df_this_year()
|
| 255 |
if isinstance(result, pd.DataFrame):
|
|
|
|
| 263 |
reply_text = "\n\n".join(lines)
|
| 264 |
else:
|
| 265 |
reply_text = result
|
|
|
|
| 266 |
line_bot_api.reply_message_with_http_info(
|
| 267 |
ReplyMessageRequest(reply_token=event.reply_token, messages=[TextMessage(text=reply_text)])
|
| 268 |
)
|
| 269 |
return
|
| 270 |
|
|
|
|
| 271 |
if ("地震" in user_message) or ("quake" in user_message):
|
| 272 |
reply_text = fetch_global_last24h_text()
|
| 273 |
line_bot_api.reply_message_with_http_info(
|
|
|
|
| 275 |
)
|
| 276 |
return
|
| 277 |
|
|
|
|
| 278 |
if ("你好" in user_message) or ("hi" in user_message):
|
| 279 |
line_bot_api.reply_message_with_http_info(
|
| 280 |
ReplyMessageRequest(
|
|
|
|
| 282 |
messages=[TextMessage(text="👋 你好!我是地震查詢機器人���\n\n輸入 /help 查看所有指令。")]
|
| 283 |
)
|
| 284 |
)
|
| 285 |
+
return
|
|
|
|
|
|
|
|
|