avatar

[QGIS]-專案檔(.qgz)解析

QGIS介紹

QGIS是一款開源免費的地圖編輯工具,透過QGIS這類的地圖編輯器可以很快地建立GIS藍圖,GIS藍圖指的是規劃好要有甚麼圖層圖層的資料來源圖層樣式...等等。 QGIS編輯後儲存的專案檔案為 .qgz,qgz本質上是一個壓縮檔案解壓縮後可以看到qgs檔案這個才是我們所需要的檔案

image

qgs檔案

打開qgs檔案會是一個xml格式的檔案,我們需要關注的是maplayer的資料,這代表著在QGIS中我們新增了多少的圖層以及其資料來源,圖層的資料來源常見 的有geojsondb,我採用的是postgres。

maplayer這個tag中的datasource存放著該圖層的資料來源,下列的xml代表著我有一個圖層是來自database,資料庫名稱為map、資料表名稱為gps。

知道了圖層資料來源我們就可以透過Openlayer去還原QGIS的圖層。

<datasource>dbname='map' service='map' key='id' srid=4326 type=Point checkPrimaryKeyUnicity='1' table="public"."gps" (geom)</datasource>

Parse qgs file

使用到的套件有yauzlxml2js

流程:解壓縮qgz取得qgs => 用xml2js將qgs轉換成json格式 => 解析出圖層與其資料來源。

/** * 解析 QGIS .qgs 或 .qgz 專案檔並提取圖層資訊。 * * @param {string} filePath - QGIS 專案檔的相對路徑或絕對路徑。 * @returns {Promise<LayerInfo[]>} 包含每個圖層詳細資訊的陣列。 * @throws {Error} 如果檔案格式不支援或解析失敗。 */ const unpackQgisProject = async (filePath: string) => { return new Promise<string>((resolve, reject) => { yauzl.open(filePath, { lazyEntries: true }, (err, zipfile) => { if (err || !zipfile) return reject(err); zipfile.readEntry(); zipfile.on("entry", (entry) => { if (/\.qgs$/.test(entry.fileName)) { zipfile.openReadStream(entry, (err, readStream) => { if (err || !readStream) return reject(err); let data = ""; readStream.on("data", (chunk) => { data += chunk; }); readStream.on("end", () => { resolve(data); zipfile.close(); }); }); } else { zipfile.readEntry(); } }); zipfile.on("end", () => { reject(new Error("No .qgs file found in the archive")); }); }); }); };
/** * 讀取並解析 QGIS 專案檔 (.qgs)。 * @param {string} filePath - QGIS 專案檔的路徑。 * @returns {Promise<Qgis>} 解析後的 QGIS 專案物件。 * @throws {Error} 如果檔案無效或解析失敗。 */ const readQgisProject = async (filePath: string): Promise<Qgis> => { const data = await fs.readFile(filePath, "utf-8"); const result = await parser.parseStringPromise(data); if (!result || !result.qgis) { throw new Error("Invalid QGIS project file"); } return result; };
/** * 解析 QGIS 專案物件並提取圖層資訊。 * @param {Qgis} qgis - 解析後的 QGIS 專案物件。 * @returns {LayerInfo[]} 包含每個圖層詳細資訊的陣列。 * @throws {Error} 如果 QGIS 專案結構無效。 */ const parseLayers = (qgis: Qgis) => { if (!qgis || !qgis.qgis || !qgis.qgis.projectlayers) { throw new Error("Invalid QGIS project structure"); } const layers: LayerInfo[] = []; qgis.qgis.projectlayers.forEach((layer) => { if (layer.maplayer) { layer.maplayer.forEach((ml: QgisMaplayer, index: number) => { const datasource = ml.datasource[0]; const layerObj = {} as LayerInfo; layerObj.layerName = ml.layername[0]; if (datasource.includes("dbname=")) { layerObj.type = "database"; layerObj.tableName = extractDatasourceString( datasource, layerObj.type )!; } layers.push(layerObj); }); } }); return layers; }; /** * 從資料來源字串中提取資料表名稱或 GeoJSON 檔案名稱。 * @param {string} dataSourceString - 資料來源字串。 * @param {string} sourceType - 資料來源類型 ("database" 或 "geojson")。 * @returns {string | null} 提取的名稱,若無法提取則返回 null。 */ const extractDatasourceString = ( dataSourceString: string, sourceType: string ): string | null => { if (!dataSourceString || typeof dataSourceString !== "string") { return null; } if (sourceType === "database") { const regex = /table="public"\."(.+?)"/; const match = dataSourceString.match(regex); if (match && match[1]) { return match[1]; } } else if (sourceType === "geojson") { const regex = /\.\/(.+?)\|/; const match = dataSourceString.match(regex); if (match && match[1]) { return match[1]; } } return null; };

最後解析出來的物件會是一個Object Array

{ "layerName": "gps", "type": "database", "tableName": "gps" }