Skip to content

Instantly share code, notes, and snippets.

@typebrook
Last active June 27, 2022 06:12
Show Gist options
  • Save typebrook/c03326c77541733045331183c46032c3 to your computer and use it in GitHub Desktop.
Save typebrook/c03326c77541733045331183c46032c3 to your computer and use it in GitHub Desktop.
匯入臺中市開放門牌資料 #osm #open_data #perl #awk #address #taichung #gov

臺中市門牌資料匯入作業

引子

目前(2021.Mar)政府資料開放平台data.gov.tw中,臺中市和臺南市等兩個地方政府已率先公開戶政系統的門牌座標資料集。

三月上旬,經臺中市政府資訊中心的趙先生與社群接洽後,決定先行匯入臺中市的門牌資料集到OSM中。本筆記旨在紀錄作業過程,並提供討論和回饋的空間。

資料集

  • 在目前時間點,最新資料為110年二月份。
  • 政府資料開放平台上的資料集連結為: https://data.gov.tw/dataset/137998
  • 資料筆數為1,191,053,也就是119萬個門牌點位。
  • 使用資料集提供的CSV格式檔案進行處理,檔案大小為130MB

原始資料格式如下:

省市縣市代碼 鄉鎮市區代碼 村里 街_路段 地區 TWD97橫坐標 TWD97縱坐標 WGS84經度 WGS84緯度
66 6601800 員林里 007 大林 322巷 35號 214924.1924 2682213.1004 120.654590162 24.2451180475
66 6601400 文化里 011 中興路 67巷 28弄 21號四樓之5 202758.3428 2683046.3807 120.534761999 24.2523226275

作業流程

  • 使用curl, perl和其它GNU基本工具,搭配Makefile撰寫腳本。 適用於LinuxMac OS,以及 Windows中的 git-bashWSL,不需特別安裝額外套件或程式。
  • 腳本內容包含:
    1. 下載資料集
    2. 過濾資料
    3. 轉換為OSM物件(node)格式
    4. 依序上傳所有物件(包含changeset創建、上傳osc格式檔案、關閉changeset)
  • 腳本撰寫於Github gist,連結為: https://gist.github.com/typebrook/c03326c77541733045331183c46032c3#file-makefile
  • 使用者能以make [FILE]形式輸入指令,依序產生相關檔案。

資料處理方式

  • 有關各筆門牌資料的匯入,標記方式參照Rex、李昕迪等人2015年於hackpad上的討論,連結可見:https://hackmd.io/@osm-tw/address
  • 含有特殊字元,?,的資料,一律過濾掉。這可能是戶政人員在登記或轉換上的筆誤

座標

TWD97橫坐標 TWD97縱坐標 WGS84經度 WGS84緯度
214924.1924 2682213.1004 120.654590162 24.2451180475
202758.3428 2683046.3807 120.534761999 24.2523226275

第十二、十三欄的經緯度用作node物件的座標,第十、十一欄的TWD97座標捨去不用。

省市縣市代碼

省市縣市代碼
66

第一欄的省市縣市代碼,其值皆為66

全部標記為addr:city=臺中市

鄉鎮市區代碼

鄉鎮市區代碼
6601800
6601400

第二欄的鄉鎮市區代碼,與實際行政區的對應關係記載於檔案district.list

標記範例如:addr:district=中區

所有代碼與直轄市區的對照如下:

6600100,中區
6600200,東區
6600300,南區 
6600400,西區
6600500,北區
6600600,西屯區
6600700,南屯區
6600800,北屯區
6600900,豐原區
6601000,東勢區
6601100,大甲區
6601200,清水區
6601300,沙鹿區
6601400,梧棲區
6601500,后里區
6601600,神岡區
6601700,潭子區
6601800,大雅區
6601900,新社區
6602000,石岡區
6602100,外埔區
6602200,大安區
6602300,烏日區
6602400,大肚區
6602500,龍井區
6602600,霧峰區
6602700,太平區
6602800,大里區
6602900,和平區

村里

村里
員林里
文化里

第三欄的村里,直接使用原始文字。

標記範例如:addr:hamlet=大城里

007
011

第四欄的,使用原始數字並刪去開頭的0,例如:

  • 015 -> 15鄰

標記範例如:addr:neighbourhood=15鄰

街_路段、巷、弄

街_路段
中興路 67巷 28弄
成功路 306巷
崇德十一路
322巷

第五、七、八欄的街_路段使用addr:street標記。處理規則如下:

  • 街_路段大致使用原始文字,保留中文數字,將全型數字改為半型數字。例如:
    • 工業區十七路 -> 工業區十七路
    • 豐原大道四段三0三街 -> 豐原大道四段三0三街
  • 大致使用原始文字,將全型數字改為半型數字。例如:
    • 39巷 -> 39巷
  • 大致使用原始文字,將全型數字改為半型數字。例如:
    • 5弄 -> 5弄
  • 接著再將上述三者按順序結合

標記範例如:addr:street=黎明路一段1086巷37之1弄

地區

地區
大林
豐西
貿易北一巷
龍興三莊

第六欄的地區,直接使用原始文字並套用addr:place標籤。

標記範例如:addr:place=春社東巷

將第九欄的值切開,分別套用至addr:hournumberaddr:flooraddr:unit,原則上會將中文小寫數字和全型數字轉換為半型數字,並保留其它原始文字。可能有以下型式:

  • 402號十一樓之1 -> addr:houenumber=402號 , addr:floor=11樓,addr:unit=之1
  • 66之1號 -> addr:houenumber=66之1號
  • 33號之2三樓 -> addr:houenumber=33號之2, addr:floor=3樓
  • 190 -> addr:houenumber=190
  • 758號地下一層之1 -> addr:houenumber=758號, addr:floor=地下1層, addr:unit=之1

其它資料集所包含的格式,可以使用指令make number.format, make floor.formatmake details.format查看去掉數字後的結果,其輸出如下:

> make number.format
 ⋮
X
XA
XA號
XB
XB號
X之
X之X
X之XA號
X之XB號
X之X之X之X號
X之X之X號
X之X號
X之X號X號
X之號
X心市場X之X號
X心市場X號
 ⋮
 
> make details.format
⋮
之X
之XA
之X,之X
之X之X
之X之X之X
之X室
A室
B
之X附X
⋮

上傳方式

考慮到資料數量,採用多個changeset進行上傳,每個changeset約含1000~5000個物件,並以行政區來分組。可以直接使用以下指令進行: make [行政區].commit

舉例來說,要上傳大肚區的地址資料,可以使用以下指令: make 大肚區.commit

若想要省略OSM帳號密碼的輸入,可以改為輸入: OSM_USER_PASSWD=[YOUR ACCOUNT]:[YOUR PASSWD] make 大肚區.commit

考量到人為失誤,上傳目標預設為測試伺服器(master.apis.dev.openstreetmap.org)。若需要上傳到真正的伺服器(openstreetmap.org),則需要加上額外變數: EXTRA='--serious' make 大肚區.commit

討論事項

  1. 資料集中含有一些奇怪格式,我將注意到的例子放在檔案notice中。
  2. 可能是因為在同一棟大樓,不同門牌會有相同的座標,是否該將它們全部匯入?或者僅保留一個較大的單位即可?
6600100,中區
6600200,東區
6600300,南區
6600400,西區
6600500,北區
6600600,西屯區
6600700,南屯區
6600800,北屯區
6600900,豐原區
6601000,東勢區
6601100,大甲區
6601200,清水區
6601300,沙鹿區
6601400,梧棲區
6601500,后里區
6601600,神岡區
6601700,潭子區
6601800,大雅區
6601900,新社區
6602000,石岡區
6602100,外埔區
6602200,大安區
6602300,烏日區
6602400,大肚區
6602500,龍井區
6602600,霧峰區
6602700,太平區
6602800,大里區
6602900,和平區
.ONESHELL:
# 臺中市110年2月GIS門牌號碼
# 1 2 3 4 5 6 7 8 9 10 11 12 13
# 縣市代碼 行政區代碼 村里 鄰 街_路段 地區 巷 弄 號 TWD97橫坐標 TWD97縱坐標 WGS84經度 WGS84緯度
# 66 6600100 大誠里 015 大誠街 1號 217244.0832 2671044.8955 120.677688605 24.1443281796
taichung.csv:
curl -L https://datacenter.taichung.gov.tw/swagger/OpenData/46e642ac-6829-473b-ae8a-486515dc6544 | \
tr -d '\r' | tee $@.bak >$@
# 1 2 3 4 5 6 7 8 9 10 11 12
# WGS84經度 WGS84緯度 行政區 村里 鄰 街_路段 地區 巷 弄 號 樓 單位
# 120.677688605 24.1443281796 大里區 大誠里 15 大誠街 1號 3樓 之一
reformated.csv: taichung.csv
sed 1d $< | \
perl -plC -Mutf8 -e 'y/0-9A-Z褸熡摟耬螻/0-9A-B樓樓樓樓樓/' | \
awk -F',' 'BEGIN{ OFS="," } { print $$8,$$9 >"tmp_1" } { print $$12,$$13,$$2,$$3,$$4+0,$$5,$$6,$$7 >"tmp_2"}'
<tmp_1 perl -plC -Mutf8 -e '
s/(.+)?.*//g;
/,.*之.+[樓層F]/ && s/,([之A-B0-9號]+)([^0-9]+[樓層F])(.*)/,$$1,$$2,$$3/
|| s/,([A-B0-9之]+[號]?)(.+[樓層F])?(.*)/,$$1,$$2,$$3/;
s/廿/二十/g; s/十/*10+/g; y/一二三四五六七八九零/1-9 /;
s/([^0-9])\*/$$1/g;
s/\+([^0-9]|\+$$)/$$1/g;
s/(\d?\*?\d+\+?\d+?)/$$1/gee;
' | paste -d',' tmp_2 - >$@
rm tmp*
filtered.csv: reformated.csv
sed -n '/[■?∮]/ !p' <$< >$@
street.format: reformated.csv
awk -F',' '{ print $$6 }' $< | sed -E 's/[^街路段道]/X/g' | sort -u | tee $@
number.format: reformated.csv
awk -F',' '{ print $$10 }' $< | sed -E 's/[0-9]+/X/g' | sort -u | tee $@
floor.format: reformated.csv
awk -F',' 'BEGIN{ OFS="," } { print $$11 }' $< | sed -E 's/[0-9]+/X/g' | sort -u | tee $@
details.format: reformated.csv
awk -F',' 'BEGIN{ OFS="," } { print $$12 }' $< | sed -E 's/[0-9]+/X/g' | sort -u | tee $@
all.osm.list: district.list filtered.csv
LANG=C.UTF-8 awk -F',' '
BEGIN {
format_node = "<node id=\x22-%s\x22 version=\x221\x22 lat=\x22%s\x22 lon=\x22%s\x22>"
format_tag = "<tag k=\x22%s\x22 v=\x22%s\x22/>"
}
NR==FNR { a[$$1]=$$2; next }
{
printf format_node, FNR, $$2, $$1
printf format_tag, "addr:TW:dataset", "137998"
printf format_tag, "addr:city", "臺中市"
if ($$3!="") printf format_tag, "addr:district", a[$$3]
if ($$4!="") printf format_tag, "addr:hamlet", $$4;
if ($$4!="") printf format_tag, "addr:neighbourhood", $$5"鄰";
if ($$6!="") printf format_tag, "addr:street", $$6$$8$$9
if ($$7!="") printf format_tag, "addr:place", $$7
if ($$10!="") printf format_tag, "addr:housenumber", $$10
if ($$11!="") printf format_tag, "addr:floor", $$11
if ($$12!="") printf format_tag, "addr:unit", $$12
print "</node>"
}
' $^ >$@
%.osm.list: all.osm.list
district=$(@:.osm.list=)
grep "k=\"addr:district\" v=\"$$district\"" $< >$@
commit.sh:
curl -s https://raw.githubusercontent.com/typebrook/helper/dev/tools/osm/osm.api.changeset.commit >$@
chmod +x $@
%.commit: %.osm.list commit.sh
DISTRICT=$(@:.commit=)
CHECKLIST=$$DISTRICT.uploaded.list
touch $$list
TOTAL=$$(wc -l $< | cut -d' ' -f1)
for i in $$(seq 1 1000 $$TOTAL); do
# If uploaded, skip this bunch of entries
grep "^$$i$$" $$CHECKLIST >/dev/null && continue
# Make a new changeset in background
{
TMPFILE=tmp.$$DISTRICT.$$i;
echo Processing $$i to $$(($$i+999));
sed -n "
1 i <osmChange version=\"0.6\" generator=\"bash script\">
1 i <create>
$$i,+999 p
$$ a </create>
$$ a </osmChange>
" $< >$$TMPFILE;
echo "Import Open data of addresses from data.gov.tw, licensed by OGDL-v1.
This changeset is created by script on https://gist.github.com/c03326c77541733045331183c46032c3, with addresses in $$DISTRICT from $$i to $$(($$i+999)).
" | SOURCE=https://data.gov.tw/dataset/137998 ./commit.sh $(EXTRA) $$TMPFILE && \
echo $$i >>$$CHECKLIST && echo Finished || echo Failed;
rm $$TMPFILE;
} &
# Only run limited processes at the same time
while [ $$(jobs -p | wc -l) -ge 3 ]; do
sleep 1
done
done
clean:
rm -f *.csv *.osm *.format *.sh *osm.list tmp*
## Rule
check https://osmtw.hackpad.tw/1AvlkwLVRaN
## Same location
大興里,011,南屯路2段,,,,179號2樓,120.650446011,24.1362616925
大興里,011,南屯路2段,,,,179號3樓,120.650446011,24.1362616925
大興里,011,南屯路2段,,,,179號3樓,120.650446011,24.1362616925
## Need fix
L952174: 東海里,007,東海里007鄰遠東街
L496064: 依主計規定辦理虛擬新增
L604305: 廍子巷133之1號(廍子區段,,,徵收地區建物拆除)
L365168: 3號以上(原編3號以上後更正8331,215426.0328,2676547.1609,120.659667938,24.1939705125
L204580: 22之號二樓
L644606: 青?莊15號
L632730: 5號巷5號
L753176: 2號地下室巷2號地下室
L193288: 東興市塲二樓43號
L240162: 二鍛2之1號
L203291: 三緞380之2號
L193421: 124之4號45號三樓
L255398: 139巷,,7號2號二樓
L981922: 2號9號六樓
?連潭
褸|熡|摟|耬|螻
沒有「號」
@iigmir
Copy link

iigmir commented Mar 22, 2021

我編輯地圖時,發現突然一個區域內,多了上百個相同的的點,點在地圖上的一個地方。
查了一下這些點的 Key 有什麼區別,區別好像只有樓層;再從歷史紀錄來看,發現和你的程式運行有關。
請問連樓層也加在同一個點,是你要的功能嗎?


我看到討論事項了:

可能是因為在同一棟大樓,不同門牌會有相同的座標,是否該將它們全部匯入?或者僅保留一個較大的單位即可?

我個人認為,應該只保留一個較大的單位即可。不過我不太清楚其他人的想法就是了。

@typebrook
Copy link
Author

typebrook commented Mar 22, 2021

@iigmir

請問連樓層也加在同一個點,是你要的功能嗎?

沒錯,符合我撰寫腳本的意圖。

這項匯入工作同時有在OSM TW的FB社團HackMD發出聲明,並已在測試伺服器(master.apis.dev.openstreetmap.org/)進行過測試:
https://www.facebook.com/groups/OpenStreetMap.TW/permalink/3915688645163242/
https://hackmd.io/@osm-tw/import-taichung-address

其實這一點也有版友(Kuang-che Wu @Facebook)提出疑問,不過既然沒有反對的聲音,我就依照臺中市政府資料集的原樣進行匯入。

因為目前為止,匯入的物件都有標示ref:TW:dataset=137998,在搜尋或修改上相對容易進行。
若您覺得不妥,可以在FB社團或 mailing list 發起討論,我會以社群成員的共識為依據,對目前匯入的物件進行修改或刪除

@typebrook
Copy link
Author

typebrook commented Mar 22, 2021

@iigmir

我個人認為,應該只保留一個較大的單位即可。不過我不太清楚其他人的想法就是了。

既然你這麼認為,只需要發起討論並取得社群的共識即可。即使參與討論的人數相對少,票數是1比0,我也會按照社群夥伴做出的結論去做修改。

建議你可以在FB社團發文,因為有投票功能,而且能見度高,我認為統計大家的意向相對容易。

我之所以沒有自己來做這件事情,是因為撰寫程式和hackmd的文件已佔去我太多心力。若大家不表示意見,我就當作是大家默認我的作法,畢竟多一事不如少一事。

PS
hackmd的留言數目前還是零,我很桑心:cry:

@iigmir
Copy link

iigmir commented Mar 23, 2021

建議你可以在FB社團發文,因為有投票功能,而且能見度高,我認為統計大家的意向相對容易。

我個人很少在用臉書,不過謝謝你的建議。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment