拆解區議會分區建議pdf
最近參與了一個項目,目的是想把歷屆區議會的相關資訊製作成互動的網站,而其中涉及到一些區議會的選區分界資訊。政府的網站中雖然有著往年的紀錄,但卻是令人可恨的pdf格式。故此專門為了這堆PDF寫了一個簡單的小程序,可以把歷年的pdf轉換成json。萬幸的是這些pdf都是統一格式,因此在開發過程中省卻了很多的麻煩。
目的
- 取得區議會選區代號、名稱、預計人口、偏離百份比等數據
- 更甚者希望可以取得主要屋邨範圍
- 輸出json格式
TL;DR
- 使用
pdf2json庫寫一個nodejs程序, - 先把pdf資料讀取成坐標(x,y)及文字
- 把y軸相近的資料歸納為array
- 再使用regex去辨認資料,並以簡單的state machine去把資料分類
詳細步驟
先安裝pdf2json庫
npm i pdf2json
簡單的用2019的pdf來跑一下範例
let fs = require('fs'),
PDFParser = require("pdf2json");
let pdfParser = new PDFParser();
pdfParser.on("pdfParser_dataError", errData => {
console.error(errData.parserError)
});
pdfParser.on("pdfParser_dataReady", pdfData => {
fs.writeFileSync("output.json", JSON.stringify(pdfData, null, 2));
});
pdfParser.loadPDF("raw/pdf/2019_A.pdf");
這邊會把pdf解壓成一個json,pdf2json的文檔有說明大概格式
{
"formImage": {
...
"Pages": [{
...
"Texts": [
{
"x": 2.938,
"y": 3.503,
"w": 2,
"sw": 0.32553125,
"clr": 0,
"A": "left",
"R": [
{
"T": "%E5%9C%B0%E5%8D%80",
"S": -1,
"TS": [
0,
15,
0,
0
]
}
]
},
}, ...]
}
pdf2json把pdf解壓成一個array
Pages裡面裝著每一頁的所有東西,包括文字、線條等等
然裡面的Textsarray就是裝著該頁的所有文字
接下來我們就需要把文字處理,先把文字跟(x,y)都打印出來
const texts = page.Texts;
for (const text of texts) {
console.log(`${text.x}, ${text.y}, ${text.R.map(r => decodeURIComponent(r.T)).join('')}`)
}
// 2.938, 3.503, 地區
// 4.82, 3.503, :
// 5.442, 3.503, 中西區
// 22.527, 3.615, 建議
// 24.282, 3.615, 區議會選區範圍
// 2.938, 6.368, 代號
// 5.652, 6.368, 建議
// 7.152, 6.368, 選區名稱
// 19.438, 6.368, 區界說明
// 28.527, 6.368, 主要屋
// 30.777, 6.368,
// 31.723, 6.368, /
// 32.3, 6.368, 範圍
// 40.528, 6.368, 預計人口
// 44.188, 4.642, 標準人口基數
// 44.563, 5.618, 偏離百
// 46.813, 5.618, 分
// 47.57, 5.618, 比
// 45.155, 6.57, (16
// 46.34, 6.57, 599)
// 2.938, 7.529999999999999, A01
// 5.652, 7.553000000000001, 中環
// 40.528, 7.4030000000000005, 13 351
// 45.635, 7.4030000000000005, -
// 45.837, 7.4030000000000005, 19.57
// 14.068, 9.772, 北
// 15.402, 9.772, 區界線
// 28.527, 9.547, 1.
// 29.608, 9.547, 荷李活華庭
// 13.752, 10.8, 東北
// 15.402, 10.8, 區界線
// 14.068, 11.835, 東
// 15.402, 11.835, 區界線
// 13.752, 12.862, 東南
// 15.402, 12.862, 堅尼地道、萬茂里
// 14.068, 13.897, 南
// 15.402, 13.897, 花園道、堅尼地道、下亞厘畢道
// 15.402, 14.895, 麥當勞道
// 13.752, 15.93, 西南
// 15.402, 15.93, 亞畢諾道、贊善里、伊利近街
// 15.402, 16.927, 下亞厘畢道、奧卑利街、卑利街
// 15.402, 17.925, 士丹頓街、雲咸街
// 14.068, 18.96, 西
// 15.402, 18.96, 鴨巴甸街、急庇利街、干諾道中
// 15.402, 19.957, 荷李活道、樓梯街、皇后大道中
// 15.402, 20.955, 士丹頓街
// 13.752, 21.99, 西北
// 15.402, 21.99, 中港道
// 25.678, 34.537, A
// 26.12, 34.537, 1
從中可以看到(y < 7)的都是頁首一些資訊,可以扔掉
然後把相鄰資料的y接近的話(|y2 - y1| < 1.0|)放在同一行
整理後得出這些資料
[ [ 'A01', '中環', '13 351', '-', '19.57' ],
[ '北', '區界線', '1.', '荷李活華庭' ],
[ '東北', '區界線' ],
[ '東', '區界線' ],
[ '東南', '堅尼地道、萬茂里' ],
[ '南', '花園道、堅尼地道、下亞厘畢道', '麥當勞道' ],
[ '西南', '亞畢諾道、贊善里、伊利近街', '下亞厘畢道、奧卑利街、卑利街', '士丹頓街、雲咸街' ],
[ '西', '鴨巴甸街、急庇利街、干諾道中', '荷李活道、樓梯街、皇后大道中', '士丹頓街' ],
[ '西北', '中港道' ],
[ 'A', '1' ] ]
這裡只有兩種資料
- 該區的metadata
- 整行的區界說明/屋邨範圍資料(或混合顥示)
剩下就只需要一些簡單的regex及constant就可以判定該行到底屬於哪一種 然後就可以把資料放在不同的array了
這個項目也放在Github上,有興趣的朋友可以clone來看看