Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import os
|
| 2 |
-
# ✅
|
| 3 |
os.environ["MPLCONFIGDIR"] = "/tmp/matplotlib"
|
| 4 |
|
| 5 |
import uuid
|
|
@@ -20,17 +20,35 @@ from linebot.v3.webhooks import MessageEvent, TextMessageContent
|
|
| 20 |
import requests
|
| 21 |
import pandas as pd
|
| 22 |
|
| 23 |
-
# --- Matplotlib (headless)
|
| 24 |
import matplotlib
|
| 25 |
-
matplotlib.use("Agg")
|
| 26 |
import matplotlib.pyplot as plt
|
| 27 |
from matplotlib.colors import Normalize
|
| 28 |
import matplotlib.cm as cm
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
# --- Environment Variables ---
|
| 31 |
CHANNEL_ACCESS_TOKEN = os.getenv("CHANNEL_ACCESS_TOKEN")
|
| 32 |
CHANNEL_SECRET = os.getenv("CHANNEL_SECRET")
|
| 33 |
-
HF_SPACE_URL = os.getenv("SPACEURL") # e.g., https
|
| 34 |
|
| 35 |
# --- Writable static directory ---
|
| 36 |
STATIC_DIR = os.getenv("STATIC_DIR", os.path.join(tempfile.gettempdir(), "static"))
|
|
@@ -41,7 +59,7 @@ app = Flask(__name__)
|
|
| 41 |
configuration = Configuration(access_token=CHANNEL_ACCESS_TOKEN)
|
| 42 |
handler = WebhookHandler(CHANNEL_SECRET)
|
| 43 |
|
| 44 |
-
# ---
|
| 45 |
@app.route("/", methods=["GET"])
|
| 46 |
def home():
|
| 47 |
return """
|
|
@@ -74,7 +92,7 @@ def healthz():
|
|
| 74 |
def serve_static(filename):
|
| 75 |
return send_from_directory(STATIC_DIR, filename)
|
| 76 |
|
| 77 |
-
# --- Earthquake
|
| 78 |
USGS_API_BASE_URL = "https://earthquake.usgs.gov/fdsnws/event/1/query"
|
| 79 |
|
| 80 |
def _iso(dt: datetime) -> str:
|
|
@@ -147,7 +165,7 @@ def fetch_taiwan_df_this_year(min_mag=5.0) -> pd.DataFrame | str:
|
|
| 147 |
except Exception as e:
|
| 148 |
return f"❌ 查詢失敗: {e}"
|
| 149 |
|
| 150 |
-
# --- Map
|
| 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
|
|
@@ -156,7 +174,7 @@ def create_and_save_map(df: pd.DataFrame) -> str:
|
|
| 156 |
ax.set_ylim(lat_min, lat_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)
|
| 160 |
ax.grid(True, linestyle="--", linewidth=0.5, alpha=0.4)
|
| 161 |
|
| 162 |
mags = df["magnitude"].astype(float).clip(lower=0)
|
|
@@ -165,7 +183,7 @@ def create_and_save_map(df: pd.DataFrame) -> str:
|
|
| 165 |
colors = cmap(norm(mags.values))
|
| 166 |
sizes = 15 + (mags - mags.min()) * 25
|
| 167 |
|
| 168 |
-
|
| 169 |
df["longitude"].values,
|
| 170 |
df["latitude"].values,
|
| 171 |
s=sizes,
|
|
@@ -178,10 +196,6 @@ def create_and_save_map(df: pd.DataFrame) -> str:
|
|
| 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()
|
|
@@ -190,6 +204,7 @@ def create_and_save_map(df: pd.DataFrame) -> str:
|
|
| 190 |
return filename
|
| 191 |
|
| 192 |
def _base_url_for_images() -> str:
|
|
|
|
| 193 |
if HF_SPACE_URL:
|
| 194 |
return HF_SPACE_URL.rstrip("/")
|
| 195 |
return request.url_root.rstrip("/")
|
|
@@ -241,7 +256,7 @@ def handle_message(event):
|
|
| 241 |
"📖 地震預警 dayichen 指令說明\n\n"
|
| 242 |
"➡️ /help\n 說明:顯示此幫助訊息。\n\n"
|
| 243 |
"➡️ 地震\n 說明:查詢全球最近 24 小時內,M≥5.0 的顯著地震。\n\n"
|
| 244 |
-
"➡️ 臺灣地震 / 台灣地震\n 說明:查詢今年以來台灣區域
|
| 245 |
"➡️ 臺灣地震畫圖 / 台灣地震畫圖\n 說明:繪製今年台灣區域 M≥5.0 地震分佈圖並回傳圖片。\n\n"
|
| 246 |
"➡️ 你好\n 說明:顯示歡迎訊息。"
|
| 247 |
)
|
|
|
|
| 1 |
import os
|
| 2 |
+
# ✅ Avoid permission issues for Matplotlib config/cache
|
| 3 |
os.environ["MPLCONFIGDIR"] = "/tmp/matplotlib"
|
| 4 |
|
| 5 |
import uuid
|
|
|
|
| 20 |
import requests
|
| 21 |
import pandas as pd
|
| 22 |
|
| 23 |
+
# --- Matplotlib (headless) ---
|
| 24 |
import matplotlib
|
| 25 |
+
matplotlib.use("Agg")
|
| 26 |
import matplotlib.pyplot as plt
|
| 27 |
from matplotlib.colors import Normalize
|
| 28 |
import matplotlib.cm as cm
|
| 29 |
+
from matplotlib import font_manager as fm
|
| 30 |
+
|
| 31 |
+
# ✅ Try to set a CJK-capable font for Chinese text
|
| 32 |
+
# Install fonts-noto-cjk in container for best result
|
| 33 |
+
possible_fonts = [
|
| 34 |
+
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
| 35 |
+
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
|
| 36 |
+
"/usr/share/fonts/truetype/arphic/ukai.ttc",
|
| 37 |
+
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc"
|
| 38 |
+
]
|
| 39 |
+
for font_path in possible_fonts:
|
| 40 |
+
if os.path.exists(font_path):
|
| 41 |
+
font_prop = fm.FontProperties(fname=font_path)
|
| 42 |
+
matplotlib.rcParams["font.family"] = font_prop.get_name()
|
| 43 |
+
print(f"✅ Using CJK font: {font_prop.get_name()}")
|
| 44 |
+
break
|
| 45 |
+
else:
|
| 46 |
+
print("⚠️ No CJK font found, Chinese glyphs may not display correctly.")
|
| 47 |
|
| 48 |
# --- Environment Variables ---
|
| 49 |
CHANNEL_ACCESS_TOKEN = os.getenv("CHANNEL_ACCESS_TOKEN")
|
| 50 |
CHANNEL_SECRET = os.getenv("CHANNEL_SECRET")
|
| 51 |
+
HF_SPACE_URL = os.getenv("SPACEURL") # e.g., https://your-space.hf.space
|
| 52 |
|
| 53 |
# --- Writable static directory ---
|
| 54 |
STATIC_DIR = os.getenv("STATIC_DIR", os.path.join(tempfile.gettempdir(), "static"))
|
|
|
|
| 59 |
configuration = Configuration(access_token=CHANNEL_ACCESS_TOKEN)
|
| 60 |
handler = WebhookHandler(CHANNEL_SECRET)
|
| 61 |
|
| 62 |
+
# --- Routes ---
|
| 63 |
@app.route("/", methods=["GET"])
|
| 64 |
def home():
|
| 65 |
return """
|
|
|
|
| 92 |
def serve_static(filename):
|
| 93 |
return send_from_directory(STATIC_DIR, filename)
|
| 94 |
|
| 95 |
+
# --- Earthquake Data Functions ---
|
| 96 |
USGS_API_BASE_URL = "https://earthquake.usgs.gov/fdsnws/event/1/query"
|
| 97 |
|
| 98 |
def _iso(dt: datetime) -> str:
|
|
|
|
| 165 |
except Exception as e:
|
| 166 |
return f"❌ 查詢失敗: {e}"
|
| 167 |
|
| 168 |
+
# --- Map Creation ---
|
| 169 |
def create_and_save_map(df: pd.DataFrame) -> str:
|
| 170 |
fig, ax = plt.subplots(figsize=(9, 6), dpi=150)
|
| 171 |
lon_min, lon_max = 118.5, 123.5
|
|
|
|
| 174 |
ax.set_ylim(lat_min, lat_max)
|
| 175 |
ax.set_xlabel("Longitude (°E)")
|
| 176 |
ax.set_ylabel("Latitude (°N)")
|
| 177 |
+
ax.set_title(f"今年 ({datetime.now(timezone.utc).year}) 台灣區域顯著地震 (M≥5.0) — UTC")
|
| 178 |
ax.grid(True, linestyle="--", linewidth=0.5, alpha=0.4)
|
| 179 |
|
| 180 |
mags = df["magnitude"].astype(float).clip(lower=0)
|
|
|
|
| 183 |
colors = cmap(norm(mags.values))
|
| 184 |
sizes = 15 + (mags - mags.min()) * 25
|
| 185 |
|
| 186 |
+
ax.scatter(
|
| 187 |
df["longitude"].values,
|
| 188 |
df["latitude"].values,
|
| 189 |
s=sizes,
|
|
|
|
| 196 |
cbar = fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax, pad=0.02)
|
| 197 |
cbar.set_label("Magnitude")
|
| 198 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
filename = f"map_{uuid.uuid4().hex}.png"
|
| 200 |
filepath = os.path.join(STATIC_DIR, filename)
|
| 201 |
fig.tight_layout()
|
|
|
|
| 204 |
return filename
|
| 205 |
|
| 206 |
def _base_url_for_images() -> str:
|
| 207 |
+
# Always prefer HF_SPACE_URL for LINE
|
| 208 |
if HF_SPACE_URL:
|
| 209 |
return HF_SPACE_URL.rstrip("/")
|
| 210 |
return request.url_root.rstrip("/")
|
|
|
|
| 256 |
"📖 地震預警 dayichen 指令說明\n\n"
|
| 257 |
"➡️ /help\n 說明:顯示此幫助訊息。\n\n"
|
| 258 |
"➡️ 地震\n 說明:查詢全球最近 24 小時內,M≥5.0 的顯著地震。\n\n"
|
| 259 |
+
"➡️ 臺灣地震 / 台灣地震\n 說明:查詢今年以來台灣區域 M≥5.0 地震。\n\n"
|
| 260 |
"➡️ 臺灣地震畫圖 / 台灣地震畫圖\n 說明:繪製今年台灣區域 M≥5.0 地震分佈圖並回傳圖片。\n\n"
|
| 261 |
"➡️ 你好\n 說明:顯示歡迎訊息。"
|
| 262 |
)
|