diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb3241e..2d1d088 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# 更新紀錄
+## [1.0.2] - 2026-05-25
+
+### ⚡ 效能優化
+
+- 將設定檔格式從 YAML 改為 TOML,改用 Python 內建 `tomllib` 解析,減少第三方依賴並加快啟動速度
+- 在背景執行緒預熱重量級模組(`plotly`、`folium`、`gpxpy`、`numpy`、`geopy`),縮短首次載入頁面時間
+
+---
+
## [1.0.0] - 2025-12-03
### 🎉 首次正式發布
diff --git a/Dockerfile b/Dockerfile
index 8d8d4fb..66fc266 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -47,5 +47,5 @@ EXPOSE 8800
CMD ["python", "-m", "streamlit", "run", "app.py"]
# Build
-# docker build --platform=linux/amd64 -t hiking_assistant:1.0.1 .
-# docker save -o hiking_assistant.tar hiking_assistant:1.0.1
\ No newline at end of file
+# docker build --platform=linux/amd64 -t hiking_assistant:1.0.2 .
+# docker save -o hiking_assistant.tar hiking_assistant:1.0.2
\ No newline at end of file
diff --git a/hiking_assistant/app.py b/hiking_assistant/app.py
index b9152a8..ab9ab48 100644
--- a/hiking_assistant/app.py
+++ b/hiking_assistant/app.py
@@ -3,26 +3,24 @@
# author: deng
# date: 20251127
+import threading
from datetime import datetime
import streamlit as st
-import yaml
-from elevation import ElevationRenderer
-from gpx import GPXProcessor
-from map import MapRenderer
-from streamlit_folium import st_folium
-from utils import convert_image_to_base64
-from weather import WeatherFetcher
+from utils import convert_image_to_base64, parse_config
+
+
+def _prewarm_imports() -> None:
+ """Pre-import heavy modules in background."""
+ import elevation # noqa: F401 (imports plotly)
+ import gpx # noqa: F401 (imports gpxpy, numpy, geopy)
+ import map # noqa: F401 (imports folium)
+ import weather # noqa: F401
class HikingAssistant:
- def __init__(self, config_path: str = 'assets/config.yaml') -> None:
- self._config = self._load_config(config_path)
-
- @staticmethod
- def _load_config(config_path: str) -> dict:
- with open(config_path, 'r') as f:
- return yaml.safe_load(f)
+ def __init__(self, config_path: str = 'assets/config.toml') -> None:
+ self._config = parse_config(config_path)
def _set_page_background(self, image_path: str, opacity: float = 0.8) -> None:
"""Set background image for the application.
@@ -94,6 +92,8 @@ class HikingAssistant:
if 'gpx_file_key' not in st.session_state or st.session_state.gpx_file_key != file_key:
# Process GPX file (only when it's a new file)
with st.spinner('正在解析 GPX 檔案...'):
+ from gpx import GPXProcessor
+
gpx_processor = GPXProcessor(uploaded_file)
if not gpx_processor.validate_and_parse():
@@ -177,6 +177,9 @@ class HikingAssistant:
# Map section
with st.spinner('正在渲染地圖...'):
+ from map import MapRenderer
+ from streamlit_folium import st_folium
+
map_renderer = MapRenderer()
route_map = map_renderer.create_route_map(all_points, start_point, end_point, waypoints)
@@ -190,6 +193,8 @@ class HikingAssistant:
with profile_col:
with st.spinner('正在繪製海拔剖面圖...'):
+ from elevation import ElevationRenderer
+
profile_renderer = ElevationRenderer()
elevation_fig = profile_renderer.create_elevation_profile(distances, elevations, gradients)
@@ -214,6 +219,8 @@ class HikingAssistant:
st.header('☀️ 天氣安抓')
if start_point:
+ from weather import WeatherFetcher
+
weather_fetcher = WeatherFetcher(forecast_days=self._config['app']['weather']['forecast_days'])
weather_key = f'weather_{start_point[0]:.6f}_{start_point[1]:.6f}'
@@ -344,6 +351,12 @@ class HikingAssistant:
st.markdown(footer_text, unsafe_allow_html=True)
+@st.cache_resource(show_spinner=False, max_entries=1)
+def get_app_instance() -> HikingAssistant:
+ return HikingAssistant()
+
+
if __name__ == '__main__':
- app = HikingAssistant()
+ threading.Thread(target=_prewarm_imports, daemon=True).start()
+ app = get_app_instance()
app.run()
diff --git a/hiking_assistant/assets/config.toml b/hiking_assistant/assets/config.toml
new file mode 100644
index 0000000..e45aac1
--- /dev/null
+++ b/hiking_assistant/assets/config.toml
@@ -0,0 +1,22 @@
+[app]
+version = "1.0.2"
+page_title = "山山登山小助手"
+page_favicon_path = "./assets/new_favicon.webp"
+page_footer_text = "⚠️ 本服務提供之資訊僅供規劃參考,山區氣候瞬息萬變,請務必依據現場狀況與自身能力進行風險評估
Made with ❤️ by deng"
+page_background_path = "./assets/background_compressed.jpg"
+page_background_opacity = 0.8
+
+[app.altitude_sickness]
+elevation_threshold = 2100
+warning_text = "此路線海拔較高,請留意[高山症](https://www.ysnp.gov.tw/StaticPage/MountainSickness)發生風險"
+emoji = "💊"
+
+[app.estimated_time]
+horizontal_speed = 3
+vertical_speed = 400
+
+[app.map]
+height = 550
+
+[app.weather]
+forecast_days = 7
diff --git a/hiking_assistant/utils.py b/hiking_assistant/utils.py
index 3adceb6..233c145 100644
--- a/hiking_assistant/utils.py
+++ b/hiking_assistant/utils.py
@@ -4,6 +4,7 @@
# date: 20251128
import base64
+import tomllib
import streamlit as st
@@ -12,3 +13,17 @@ import streamlit as st
def convert_image_to_base64(image_path: str) -> str:
with open(image_path, 'rb') as f:
return base64.b64encode(f.read()).decode()
+
+
+def parse_config(config_path: str) -> dict:
+ """Config parser
+
+ Args:
+ config_path (str): path of config toml.
+
+ Returns:
+ dict: configuration dictionary
+ """
+ with open(config_path, 'rb') as file:
+ config = tomllib.load(file)
+ return config
diff --git a/pyproject.toml b/pyproject.toml
index b0daf09..6e3a983 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "hiking-assistant"
-version = "1.0.1"
+version = "1.0.2"
description = "This is a web app to analyze gpx for hiking planning"
readme = "README.md"
requires-python = ">=3.13"
@@ -13,7 +13,6 @@ dependencies = [
"requests>=2.32.3",
"geopy>=2.4.1",
"streamlit-plotly-events>=0.0.6",
- "pyyaml>=6.0.3",
]
[dependency-groups]
diff --git a/uv.lock b/uv.lock
index a266e56..779cd6b 100644
--- a/uv.lock
+++ b/uv.lock
@@ -227,14 +227,13 @@ wheels = [
[[package]]
name = "hiking-assistant"
-version = "1.0.1"
+version = "1.0.2"
source = { virtual = "." }
dependencies = [
{ name = "folium" },
{ name = "geopy" },
{ name = "gpxpy" },
{ name = "plotly" },
- { name = "pyyaml" },
{ name = "requests" },
{ name = "streamlit" },
{ name = "streamlit-folium" },
@@ -254,7 +253,6 @@ requires-dist = [
{ name = "geopy", specifier = ">=2.4.1" },
{ name = "gpxpy", specifier = ">=1.6.2" },
{ name = "plotly", specifier = ">=5.24.1" },
- { name = "pyyaml", specifier = ">=6.0.3" },
{ name = "requests", specifier = ">=2.32.3" },
{ name = "streamlit", specifier = ">=1.51.0" },
{ name = "streamlit-folium", specifier = ">=0.23.1" },