Published
- 11 min read
應用Node.JS與QGIS展示數位建模成果:以太平溪流域歷史文化資源清查計畫為例

背景
之前在花蓮林業三部曲的發表會結束之後,用平板展示了自己在舊木瓜機關庫重建的3D模型,後來便有了參與這次調查計畫的機會。在這個計畫裡面,我主要是用攝影測量法協助紀錄結構較完整的家屋遺構,並在電腦裡重建家屋現況的立體模型。
執行過程中就有在想要怎麼呈現這些建模後的檔案,若直接存成.obj
或3D PDF
都需要使用者下載額外的程式才能顯示,且模型解析度與尺寸提高時,obj
檔的效率對公部門的電腦來說可能會是個問題。
除了立體模型之外,田調同時也記錄了家屋的位置、座向(方位角)、空間、全景相片與手繪的平面圖,是否有機會能整合這些資料到一個平台上,同時滿足效能與資料離線儲存的需求。
這篇筆記算是簡單紀錄我用一些Node.JS套件來展示田調資料的開發過程,目的是想將家屋的3D模型、全景照片、位置與座向等資料整合到同一個App來呈現,以解決過去展示調查資料時,使用者需為了不同的資料格式下載不同瀏覽程式的問題。
這算是我第一次使用Node JS等網頁前端的語言,都是去應用npm上現有的套件(還有請Github Copilot教我語法XD),不一定是最佳的做法,但就順便把過程紀錄下來。
方法
想法是用基於Node.JS的套件開發,將所有資源輸出成靜態網頁後打包成.exe
可執行檔交付。
- React Three Fiber: 立體模型render與互動
- Photo Sphere Viewer: 360全景照片render與互動
- QGIS: 整理GIS資料並發布靜態網頁
- Electron JS: 把做好的靜態網頁包成exe執行檔
React Three Fiber
3D模型檔案格式的選擇上,使用適合網頁render並將材質(Texture)包在同個檔案的.glb
格式,較.obj
檔有較好的儲存的效率1。
安裝Node.JS,並透過npm
安裝React, React Three Fiber等相依套件後,參考基礎的React app說明文件建立React app。
npx create-react-app my-app
cd my-app
npm run start
立體模型介面與render設定則直接參考React Three Fiber說明文件,將路徑指向所需的立體模型.glb
檔後,調整模型顯示的尺寸與旋轉姿態,透過OrbitControls
限制使用者的互動範圍並加上ambientLight
光源,擷取部分App.js
內容如下。
const Model = () => {
const gltf = useLoader(GLTFLoader, "./model.glb");
return <primitive object={gltf.scene} scale={0.5} rotation={[0, Math.PI / 2, 0]} />;
};
export default function App() {
return (
<div className="App">
<Canvas>
<Suspense fallback={<Loader />}>
<Model />
<OrbitControls minPolarAngle={0} maxPolarAngle={Math.PI/2} />
<color attach="background" args={['#373a3f']} />
<ambientLight intensity={2} />
</Suspense>
</Canvas>
</div>
);
}
由於網站發布的位置在本機,檔案路徑是相對的,我是直接在package.json
裡面加上"homepage": "."
設定成相對路徑。
執行npm run start
後會在本機http://localhost:3000/
部屬預覽用的靜態伺服器,透過瀏覽器即可看到當下的編輯結果。
![]() |
---|
透過瀏覽器預覽React Three Fiber與家屋模型 |
執行npm run build
後即會在build
資料夾內產生立體模型瀏覽器的靜態網頁。此時若直接點開資料夾內的index.html
,會發現資源被CORS Policy擋住無法載入,此問題在利用Electron打包後即可解決。
Photo Sphere Viewer
除了立體模型外,田調時也有以RICOH相機拍攝家屋的全景照片。為了整合所有田調資料到同個平台,這裡使用React app配合Photo Sphere Viewer套件達成。
透過npm install @photo-sphere-viewer/core
安裝套件後,一樣參考Photo Sphere Viewer的說明文件替換全景照片的路徑,再分別執行npm run start
與npm run build
即可預覽與編譯全景照片瀏覽器的靜態網頁。
QGIS
QGIS在這個專案的功能是作為最上層的圖台,讓使用者在圖台中點選後連結到上面提到的立體模型或全景照片的頁面,並利用QGIS2Web功能將圖台打包成基於Leaflet的靜態網頁。
將田野調查的家屋點位與空間資訊以python程式轉換為座向的點位資料,匯入QGIS製成家屋的座向圖,再將每棟家屋的Attribute table填入3D模型與全景照片瀏覽器的相對路徑,如<a href="./BU01/index.html" target="_blank">前往檢視</a>
,即可讓QGIS2Web輸出時以超連結的形式render。
![]() |
---|
家屋座向圖。由於撰文時計畫尚未結案,故遮蔽部分識別資訊,並以OpenStreetMap作為底圖 |
![]() |
---|
家屋的Attribute table,連結指向3D模型與全景照片瀏覽器路徑 |
圖台介接中研院百年歷史地圖,提供1989-臺灣經建1版地形圖-1:25,000
, 1954-臺灣地形圖-1:50,000
,1921-日治臺灣堡圖(大正版)-1:20,000
,1916-日治蕃地地形圖-1:50,000
等圖層瀏覽,同時以OpenStreetMap Tile Server2作為底圖。
QGIS2Web輸出時,一直沒辦法顯示WMTS Layer,但將所有連線換成XYZ Tiles又沒有問題,不確定是不是CRS不一致造成的。
![]() |
---|
QGIS2Web輸出的靜態網頁,點選家屋後即會顯示先前在QGIS內設定的Attribute table內容 |
Electron JS
到目前為止,我們已經將立體模型檢視器、全景相片檢視器與上層的圖台輸出成靜態網頁,但若直接點開html
檔會被CORS Policy阻擋而無法載入資源,此時便需要Electron JS協助將所有檔案打包成每個人執行後就可以瀏覽的程式。
這裡一樣是參考Electron JS的說明文件以及其餘線上資訊3,建立my-electron-app
後將前面React Three Fiber、Photo Sphere Viewer與QGIS2Web打包出來的靜態網頁複製到my-electron-app/dist
資料夾內,調整package.json
內的欄位(節錄如下),執行npm run start
即可預覽應用程式,安裝electron-packager
後執行npm run build_win
則可以將全部的資源編譯並部屬成exe
執行檔。
{
"name": "太平溪流域家屋建模圖台",
"version": "1.0.0",
"description": "my electron app",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "electron .",
"build_win": "electron-packager . WinApp --platform=win32 --arch=x64 --asar"
},
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "^30.0.1"
},
"dependencies": {
"electron-packager": "^17.1.2"
}
}
初步結果
Demo影片
程式截圖
![]() |
---|
圖台介面,透過QGIS2Web與Leaflet呈現家屋空間座向,使用者點選後可連結至立體模型與全景相片 |
![]() |
---|
以React Three Fiber顯示立體模型glb 檔,可平移、旋轉、縮放以調整視角 |
![]() |
---|
以Photo Sphere Viewer顯示360全景照片 |
未來可以做什麼?
-
立體模型的呈現: 透過攝影測量法解析的相機姿態訓練Gaussian splatting模型,以重建環境中的植被與光影等更細緻的視覺效果。Gaussian splatting可理解成透過多個橢圓球點雲表示環境,相較與傳統攝影測量法輸出的網格模型通常有更好的視覺效果,但若要應用到幾何尺寸的量測,可能需要進一步的探討
-
圖台發布: 目前圖台的底層實際上是個靜態網頁(只是用Electron包成執行檔),若未來有在網路上公開發表的需求,可直接部屬在靜態Web server上
Footnotes
-
https://tile.openstreetmap.org/{z}/{x}/{y}.png
↩ -
https://jonny-huang.github.io/angular/training/18_electron/ ↩