---
# System prepended metadata

title: 新增Wikidata欄位
tags: [' import', wikidata, ' script']

---

---
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)
