---
# System prepended metadata

title: 作品介紹｜健行軌跡
tags: [Side Project]

---

![Frame 14 (2)](https://hackmd.io/_uploads/HkagWqTzee.png)

最近為了練習 React，我開發了一個紀錄登山路線的平台。中間歷經規劃、設計與前後端開發，執行許多作業才完成，所以決定把過程寫下成文章記錄下來。

這個網站的具體功能如下：

1. 路線資料的增刪查改
2. 具有登入功能，資料的增刪改需要登入
3. 統計資料頁面，顯示個人成就
4. 搜尋不同用戶資料
5. 手機版、PWA 版

## 緣起

本身有在爬山的習慣，每次爬山完也會使用 QGIS（一種編輯地理資料的軟體）。不過每次要分享時，總得截圖或匯出靜態地圖，無法互動（例如：縮放、查詢），使用上十分受限。

![Intro_GIS 1](https://hackmd.io/_uploads/B1HOZqTMgg.png)

若改用 Google 地圖，雖然能快速上傳所有資料，但受限於圖層數量，無法拆開管理每筆路線。更新資料時，也必須重新匯出所有路線再整筆上傳，維護不便。

![image 5](https://hackmd.io/_uploads/HytNM56fex.png)

為了改善上述問題，我列出了以下目標：
1. 展示所有路線資料，地圖可搜尋、互動
2. 編輯所有路線資料，包含屬性資料（不包含路線形狀）
3. 輸出所有路線資料（額外項目）

## 規劃

一開始我構想這是一個單頁應用，透過多 Panel 呈現資料、細節與地圖。

![image 1 (1)](https://hackmd.io/_uploads/S1ZwRRCGxe.png)

初版僅能顯示我個人的資料，其他用戶無法互動，易讓人困惑。為了讓網站更加完整，我增加了多用戶的功能，各用戶也能新增自己的路線資料，網站逐漸轉變為登山愛好者的交流平台。

為了釐清流程與介面轉場，我製作了 UI flow 規劃網站的使用流程。

![image](https://hackmd.io/_uploads/BkDZD56Gll.png)

使用流程說明如下：
1. **首頁**，包含「登山路線」或「用戶資料」的區塊
2. 點入後進入**資料頁**，「登山路線」或「用戶資料」的統計資料
3. 點入「全部軌跡」後，進入**地圖頁**，顯示地圖與表格資料
4. 如果具有帳號，可進入**登入頁面**，登入後在**地圖頁**可編輯資料
6. 其他功能：**搜尋使用者**、**介紹頁面**

## 開發

在這個專案中，我除了開發前端頁面，也設計整體架構與後端 API。雖然很多技術是邊做邊學，但也因此對整個流程有了完整掌握。以下是專案中我實際實作過的重點技術與經驗整理：

### 前端（React + TypeScript + Leaflet）
為了讓使用者能流暢操作地圖並編輯資料，我選擇使用 React 搭配 Leaflet 實作地圖互動，並使用 TypeScript 提升開發穩定度。
- 使用 React 搭配 Context 與自訂 Hooks 管理功能模組（例如：地圖、表格、全螢幕、登入狀態等）。
- 使用 TypeScript 撰寫元件與資料流程，減少開發錯誤與強化可維護性。
- 以 Leaflet 實作地圖與表格聯動功能（highlight、點擊後 panTo 等）。
- 網站支援 PWA（Progressive Web App），並設計 OG 圖、RWD、Autocomplete 搜尋與鍵盤操作等互動體驗。
- 使用 SCSS 撰寫樣式，模組化管理每個元件的樣式。
- 使用 D3.js 繪製統計圖表（圓餅圖與長條圖），並實作動畫。
- 實作使用者登入狀態切換後的操作權限控制（僅本人可編輯）。
- 前端架設於 Vercel。

### 後端（NestJS + PostgreSQL + Swagger）
因為熟悉 .NET 的模組化設計，我選擇 NestJS 作為後端框架，讓我能更清晰地拆分 Controller、Service 與 Entity 的邏輯，並搭配 PostgreSQL 建立資料層。

- 使用 NestJS 撰寫後端，並導入 JWT 驗證機制（含 token 解析與過期驗證）。
- 採 RESTful API 設計，並使用 Swagger 自動生成 API 文件以便測試與維護。
- 設計資料表與 Entity 結構，清楚區分使用者、路線與圖層等資料。
- API 支援 CRUD、匯出、統計等操作，並依權限控制資料可見性。
- 後端部署於 Render，資料庫使用 Neon 雲端 PostgreSQL。

![Frame 18 (2)](https://hackmd.io/_uploads/ry8FOey7xg.png)

### 資料處理與空間邏輯
為了讓地圖資料能正確儲存與後續查詢，我使用 PostGIS 擴充 PostgreSQL，並在資料上傳時進行幾何運算處理。

- 使用 PostGIS 儲存並處理路線資料，支援 GeoJSON 格式輸入。
- 匯入時自動計算並儲存以下幾項幾何資訊：
  - `geom`：主體幾何資料（經過 `ST_Force2D` 標準化）
  - `length`：路線長度（轉換為 TWD97 投影後計算，單位公里）
  - `center`：路線中心點（`ST_Centroid`）

```sql
INSERT INTO users_trails (uuid, geom, length, center, name)
VALUES (
  $1,
  ST_Force2D(ST_GeomFromGeoJSON($2)),
  ROUND(
    ST_Length(ST_Transform(ST_Force2D(ST_GeomFromGeoJSON($2)), 3826))::numeric / 1000,
    3
  ),
  ST_Centroid(ST_Force2D(ST_GeomFromGeoJSON($2))),
  $3
);
```
- 支援 GeoJSON / GPX / CSV 多種格式匯出，前端可自由選擇下載類型。

## 成果
### 首頁、用戶
![Frame 16 (1)](https://hackmd.io/_uploads/HJGv3ARMee.png)

左圖為首頁畫面，進入後的 Hero Banner 傳達這是關於「山」與「路線」的平台。上方可搜尋用戶或特定路線，下方則列出所有已上傳的軌跡資料，（目前示意為三筆樣本）。

右圖為個人資料頁，包含四個區塊：
- 成就：顯示百岳完成進度。
- 統計：呈現近期活動狀況與熱門地區。
- 全部軌跡：以地圖／表格呈現所有資料（同一頁切換）。
- 歷次軌跡：檢視單筆資料，僅登入本人可見非公開資料，確保使用者隱私。

### 地圖與編輯
![Frame 17](https://hackmd.io/_uploads/HyWqnR0Ggg.png)

在地圖頁中，點擊表格中的任一路線，地圖會自動 panTo 對應位置，並顯示詳細資料卡片。若使用者已登入，則可進行資料編輯。

### PWA
![Frame 15 (1)](https://hackmd.io/_uploads/Hkl_WbAzlx.png)
由於地圖頁面在手機瀏覽器中常會受到系統手勢（例如：下滑重新整理）干擾，因此平台支援 PWA，可將網站安裝為原生應用，帶來更穩定的操作體驗。

## 挑戰
#### 地圖資料展示效能
以個人使用的體感來說，地圖展示效能仍在可接受範圍，但未來隨著路線數量增加，效能瓶頸恐怕會浮現。針對這個問題，我考慮了幾種可能的解法：

（1）在儲存階段先簡化路線資料，顯示時再依地圖縮放比例切換不同精度的版本。
（2）改用圖磚方式顯示資料，不過這需要搭配雲端服務或自架伺服器並預處理資料才能提供圖磚。
（3）在地圖縮放到一定比例尺之前僅顯示中心點，等放大後再載入完整路徑。

這些方案目前仍在規劃階段，會視未來資料量成長的實際情況再決定是否實作。

### 新用戶註冊／OAuth
使用者系統方面，目前只提供登入，還沒有開放註冊。雖然開發初期已經有設定使用者資料表與登入機制，但尚未實作註冊功能。

未來希望能接入 Google 或 LINE 的 OAuth 機制，讓更多人能輕鬆登入並使用。不過這部分我還沒有詳細研究，所以暫時先擱置。

### 百岳資料尚未串接
在百岳、小百岳、百大步道的資料對應上，目前資料表內已經有對應的欄位（以布林值呈現），但其實並沒有串接實際的路徑清單資料。

這樣的設計在初期是方便的，但實際使用下來，如果同座山多次爬過、並個別上傳，就會在個人成就頁面的圖表中重複統計。

我預計會從「健行筆記」等平台爬取正式的資料清單後匯入資料庫，並將前端的編輯方式改為下拉選單選取對應的路線名稱，讓統計邏輯更加嚴謹。

## 心得
這是我第一次獨立使用 React 開發多頁式的網頁應用程式，在此之前甚至沒有用過前端框架開發這種規模的專案。從規劃、設計到前後端實作，全都自己來，過程中真的非常耗精力，但也因此對整個開發流程有了完整理解。

我學習了 React 的模組化寫法，知道如何更有效率地拆分與管理元件；也從身邊朋友的使用回饋中，理解到「技術穩不穩」固然重要，但最終還是「有沒有讓使用者看得懂、用得順」才是真的。不過還好有其他人的意見，讓這個作品有了更高的完整度，不是只有我自己看得懂的系統。
