Last active
July 30, 2022 23:58
-
-
Save typebrook/2fc690e826d1af510196d7605d4d5045 to your computer and use it in GitHub Desktop.
Update OSM villages with wikidata #osm #wikidata #village #script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
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 -E '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 -E '/<\/relation>\t$$/ d; s/(<\/relation>)\t(.+)$$/<tag k="wikidata" v="\2"><\/tag>\1/' | \ | |
sed -r '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> | |
⋮ | |
``` | |
得到檔案`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 | |
- [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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
all: final.osc | |
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/helper/dev/tools/osm/osm.api.changeset.commit | \ | |
bash /dev/stdin $< |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Q7930614 village | |
P5020 nat_ref |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment