--- tags: wikidata, script, import --- # 新增Wikidata欄位 Wikidata中不少條目指涉的物件實體,和OSM中的物件是相同的。 當我們想在OSM新增[`wikidata`](https://wiki.openstreetmap.org/wiki/Key:wikidata)的tag,而對應的`wikidata`條目中又沒有座標([P625](https://www.wikidata.org/wiki/Property:P625))或OSM-ID([P402](https://www.wikidata.org/wiki/Property:P402))時,則可以透過有關聯的標識符(identifier)來尋找對應的條目。 下面以台灣村里單位的行政代號為例,利用Shell指令來更新`wikidata`識別碼至OSM資料中: ## 台灣村里界標記 ### OSM 台灣OSM地圖的村里界目前皆為`relation`,標記為: ``` admin_level=9 type=boundary boundary=administrative nat_ref= ``` 其中,`nat_ref`即為「行政代碼」(戶役政資訊系統資料代碼)。 ### Wikidata 台灣「村里單位」在`Wikidata`中,相關條目為[`Q7930614`](https://www.wikidata.org/wiki/Q7930614)。所以若一筆條目為台灣的村里,且假設其識別碼為`QXXXXXX`,可表示為: ```bash # QXXXXXX 屬於 台灣的村里 QXXXXXX P31 Q7930614 ``` 而「戶役政資訊系統資料代碼」在Wikidata中,相關屬性為[`P5020`](https://www.wikidata.org/wiki/Property:P5020),故可表示為: ```bash # QXXXXXX 的行政代碼為 [CODE] QXXXXXX P5020 [CODE] ``` `[CODE]`就是實際上的行政代碼。 ## 使用指令自動匯入 指令使用`Makefile`撰寫,方便閱讀每一步驟以及除錯。 可將每一步驟分別輸入,例如: ```bash make wd_villages.list # 取得wikidata中的相關資料 make villages.osm # 取得OSM中的相關資料 make matched.list # 取得比對清單 ``` 也可直接自動匯入 ```bash make changeset # 完成所有必需步驟,送出Changeset ``` Makefile撰寫於gist上,可使用以下指令取得 ```bash curl -O https://gist.githubusercontent.com/typebrook/2fc690e826d1af510196d7605d4d5045/raw/291531e947a444b4df5de15fdd182cf08edee7cc/Makefile ``` 以下為Makefile內容: ```bash= clean: rm *.list *.osm *.osc # P31=屬於 Q7930614=中華民國村里 P5020=中華民國戶政資料代碼 define quest SELECT DISTINCT ?village ?ref WHERE { ?village wdt:P31 wd:Q7930614. ?village wdt:P5020 ?ref. } ORDER BY ?ref endef export quest # 取得有戶政代碼的Wikidata村里清單 # 第一欄為Q identifier, 第二欄為戶政資料代碼, 以空白分格 wd_villages.list: curl -G 'https://query.wikidata.org/sparql' \ --header "Accept: text/csv" \ --data-urlencode query="$$quest" | \ sed -Ee '1d; s#.+/##; s/,/ /; s/\r$$//' >$@ OVERPASS_API := https://overpass.nchc.org.tw/api/interpreter TAIWAN_BBOX := 20.72799,118.1036,26.60305,122.9312 # 使用NCHC Overpass Server # 取得臺灣內, 有戶政代碼, 但沒有wikidata tag的"村里資料"(OSM格式) villages.osm: echo "[out:xml]; relation[admin_level=9][nat_ref][!wikidata]($(TAIWAN_BBOX));out meta;" | \ curl -d @- -X POST $(OVERPASS_API) >$@ # 簡化"村里資料"為"OSM村里清單", 去除外層標籤, 每一筆資料縮為一行 villages_oneline.osm: villages.osm xq -x --xml-root=relation '.osm.relation[] | {"@id": .["@id"], "@version": .["@version"], tag: .tag, member: .member}' $< | \ tr -d '\n' | \ sed -Ee 's/(<\/relation>)/\1\n/g' >$@ # 依"OSM村里清單",取得"戶政代碼清單"(一行一個) osm_nat_ref.list: villages.osm xq -r '.osm.relation[] | .tag[] | select(.["@k"]=="nat_ref") | .["@v"]' $< >$@ # 依"戶政代碼清單", 取得相對應的Q identifier matched.list: wd_villages.list osm_nat_ref.list awk 'NR==FNR {a[$$2]=$$1; next} {print a[$$1]}' $^ >$@ # 將對應的Q identifier加入"OSM村里清單"中 # 若無對應的Q identifier, 則刪去該村里relation # 將結果包裏成osmChange file (.osc file) .ONESHELL: final.osc: villages_oneline.osm matched.list paste $^ | \ sed -Ee '/<\/relation>\t$$/ d; s/(<\/relation>)\t(.+)$$/<tag k="wikidata" v="\2"><\/tag>\1/' | \ sed -e '1 i <osmChange version="0.6" generator="bash script"> 1 i <modify> $$ a </modify> $$ a </osmChange>' >$@ # 新建Changeset, 上傳osmChange file, 關閉該Changeset changeset: final.osc curl -fsS https://raw.githubusercontent.com/typebrook/settings/dev/tools/osm/osm.api.changeset.commit | \ bash /dev/stdin $< ``` ### 步驟 1. (L4-L21) **取得有戶政代碼的Wikidata村里清單:** - 使用`SPARQL`語法查詢清單。 - 要注意返回的資料格式是逗號分隔(`CSV`),而且用`CRLF`斷行,故使用`sed`手動處理成下列格式: ```bash # [wikidata識別碼] [行政代碼] Q64451271 09007010001 Q64451385 09007010002 Q21449461 09007010003 ⋮ ``` 得到檔案`wd_villages.list` 1. (L26-L30) **取得臺灣村里資料:** - 使用NCHC Overpass API - 使用`Overpass QL`語法查詢 - 資料為`OSM`格式 - 查詢有戶政代碼, 但沒有`wikidata` tag的村里 - 資料格式如下: ```xml <?xml version="1.0" encoding="UTF-8"?> <osm version="0.6"...> <meta osm_base="2020-08-07T13:33:03Z"/> <relation id="3339719" version="12"...> <member type="way" ref="9213418" role="outer"/> ⋮ <tag k="admin_level" v="9"/> <tag k="boundary" v="administrative"/> <tag k="name" v="大坪村"/> <tag k="nat_ref" v="09007030005"/> <tag k="type" v="boundary"/> </relation>relation> ⋮ ``` 得到檔案`villages.osm` 1. (L32-L36) **條列村里資料:** 為了編輯方便,加工前一步驟結果,將每項村里資料表示為一行,格式如下: ```xml <relation id="3339719" version="12"> <tag k="admin_level" v="9"></tag> <tag k="boundary" v="administrative"></tag> <tag k="is_in:city" v="連江縣"></tag> <tag k="is_in:country" v="TW"></tag> <tag k="is_in:county" v="連江縣"></tag> <tag k="is_in:district" v="莒光鄉"></tag> <tag k="is_in:town" v="莒光鄉"></tag> <tag k="name" v="大坪村"></tag> <tag k="name:en" v="Daping Village"></tag> <tag k="name:zh" v="大坪村"></tag> <tag k="nat_ref" v="09007030005"></tag> <tag k="type" v="boundary"></tag> <member type="way" ref="9213418" role="outer"></member> <member type="way" ref="369463474" role="outer"></member> <member type="way" ref="9209434" role="outer"></member></relation> <relation id="3713913" version="6"> <tag k="admin_level" v="9"></tag> <tag k="boundary" v="administrative"></tag> <tag k="is_in" v="臺南市中西區"></tag> <tag k="is_in:county" v="臺南市"></tag> <tag k="is_in:town" v="中西區"></tag> <tag k="name" v="兌悅里"></tag> <tag k="name:en" v="Duiyue Village"></tag> <tag k="name:zh" v="兌悅里"></tag> <tag k="nat_ref" v="67000370044"></tag> <tag k="type" v="boundary"></tag> <member type="way" ref="279300276" role="outer"></member> <member type="way" ref="282949509" role="outer"></member> <member type="way" ref="279300251" role="outer"></member> <member type="way" ref="279300248" role="outer"></member> <member type="way" ref="563074676" role="outer"></member> <member type="way" ref="279300272" role="outer"></member> <member type="way" ref="279300262" role="outer"></member></relation> <relation id="3713915" version="5"> <tag k="admin_level" v="9"></tag> <tag k="boundary" v="administrative"></tag> <tag k="is_in" v="臺南市中西區"></tag> <tag k="is_in:county" v="臺南市"></tag> <tag k="is_in:town" v="中西區"></tag> <tag k="name" v="淺草里"></tag> <tag k="name:en" v="Qiancao Village"></tag> <tag k="name:zh" v="淺草里"></tag> <tag k="nat_ref" v="67000370045"></tag> <tag k="type" v="boundary"></tag> <member type="way" ref="279300287" role="outer"></member> <member type="way" ref="279300256" role="outer"></member> <member type="way" ref="279300297" role="outer"></member> <member type="way" ref="279300247" role="outer"></member> <member type="way" ref="279300260" role="outer"></member> <member type="way" ref="279300324" role="outer"></member> <member type="way" ref="279300295" role="outer"></member> <member type="way" ref="279300254" role="outer"></member> <member type="way" ref="279300308" role="outer"></member></relation> ⋮ ``` 得到檔案`villages_oneline.osm` 4. (L38-L40) **取得戶政代碼清單:** - 處理前一步驟結果,只留下戶政代碼以供比對 - 格式如下: ``` 09007030005 67000370044 67000370045 ⋮ ``` 得到檔案`osm_nat_ref.list` 5. (L42-L44) **取得相對應的`Wikidata`識別碼:** - 將前一步驟結果依序和步驟1結果比對,得到`Wikidata`識別碼清單 - 格式如下: ```bash # 空行表示沒有比對到 Q64451271 Q64451272 Q64451385 ⋮ ``` 得到檔案`matched.list` 6. (L46-L56) 取得`osmChange`格式檔案: - 將上一步驟的比對結果加入到步驟3的清單中,使得每項村里資料都多了一個新的`wikidata`tag - 刪去沒有比對到的村里 - 將最後結果以`osmChange`的標籤包起來,做成可用於Changeset的`.osc`檔案 - 格式如下: ```xml <osmChange version="0.6"...> <modify> <relation id="9256118" ...> <member .../>... <tag k="wikidata".../>... <relation id="9256119" ...> <member .../>... <tag k="wikidata".../>... ⋮ </modify> </osmChange> ``` 得到檔案`final.osc` 7. (L58-L61) **上傳結果至OSM:** - 使用既有的Shell Script,將上一步驟的結果進行上傳 - 需手動輸入Changeset 留言以及使用者帳密 ## Reference - 使用上述指令完成的Changeset: https://www.openstreetmap.org/changeset/89069253 - [Makefile命令教程](http://www.ruanyifeng.com/blog/2015/02/make.html) - [Wikidata SPARQL 語法](https://www.wikidata.org/wiki/Wikidata:SPARQL_tutorial) - [Overpass Query Language 語法](https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL)