住所オープンデータ提供Web APIの使い方
先日紹介した、住所オープンデータ提供サイトですがWeb APIも利用可能です。
まだ以下の投稿見てない方いましたら、先に見ていただいたほうがわかりやすいと思います。
Web APIは、RDF用クエリ言語SPARQLで検索可能なSPARQLエンドポイントになります。
ここではそのSPARQLエンドポイントの使い方を紹介します。
SPARQLとは
SPARQLはRDF用のクエリ言語で、RDFのトリプル(主語、述語、目的語のセット)を検索することができます。一般的なWeb APIよりも自由度が高いですが、その分、目的のデータを検索するクエリを一から作るのは若干難しいです。
基本的な書き方は以下を参照ください。
有料になりますが私のかかわるサークルでSPARQLについての技術同人誌を発行しています。
SPARQLクエリで検索できるWeb APIはこれ以外にもたくさん公開されています。SPARQLクエリが書けると以下で紹介されている様々なAPIでも応用が可能です。
このWeb APIを使ってみたい方は、SPARQLを少し勉強されることをお勧めしますが、SPARQLがわからなくてもコードが少し書ける方であれば、後述するサンプルクエリ節とサンプルアプリ節を見れば、このWeb APIで出来ることは大体できると思います。
格納データ
Webサイトとの相違点
Webサイト http://uedayou.net/loa/ には、都道府県から番地レベルまでの膨大な住所データを提供していますが、Web APIとしてそれらすべてを格納して運用することが難しいため、都道府県から町・丁目レベルに限定しています。
格納データの相違
住所 | Webサイト | Web API |
---|---|---|
都道府県 | 〇 | 〇 |
市区町村 | 〇 | 〇 |
町・丁目 | 〇 | △ |
番地補足 | 〇 | × |
番地 | 〇 | × |
なお、Web APIで提供する丁目のデータは、データ数の削減のためにWebサイトでの提供されるものの一部のみ(丁目とジオハッシュ)となっています。詳しくは後述します。
データ構造
データ構造は以下の通りになります。
運用可能なデータ数にするため、Webサイトのデータから住所コード、都道府県コード、市区町村コード、緯度、経度、行政区画ポリゴンデータ、下位概念、GeoNames.jpへのリンク、地図画像を除外しています。
緯度、経度は除外していますが、その代わりジオハッシュは格納しています。ジオハッシュを使えば、簡易的に緯度経度への変換や地理空間検索ができますので、是非活用してください。後述のサンプルクエリでも使い方を触れます。
ジオハッシュ は町・丁目レベルに含まれます。
プロパティ | 説明 | 値例 |
---|---|---|
rdfs:label | 住所表記 | "東京都千代田区丸の内"@ja |
ic:表記 | 住所表記(rdfs:labelと同じ) | "東京都千代田区丸の内"@ja |
ic:都道府県 | 都道府県 | "東京都"@ja |
ic:市区町村 | 市区町村 | "千代田区"@ja |
ic:町名 | 町名 | "丸の内"@ja |
geonames:parentFeature | 住所の上位概念 | loa:東京都千代田区 |
schem:geo | ジオハッシュURI | http://geohash.org/xn30426f1 |
dcterms:hasPart | 丁目のブランクノード ※丁目を持つもののみ |
※丁目ブランクノードのデータモデル参照 |
丁目レベルのデータについては、以下のようにブランクノードとして格納しています。
プロパティ | 説明 | 値例 |
---|---|---|
ic:丁目 | 丁目 | 1 |
schem:geo | ジオハッシュURI | http://geohash.org/xn30426f1 |
データは少ないですが、SPARQLクエリの書き方によっては大体補完できます。
検索方法
Webブラウザ上から検索
手軽にSPARQLクエリを試してみたい方は、以下をブラウザで開いてフォームにクエリを入力して実行してください。
Web APiとして検索
アプリやプログラム内で使いたい場合は以下を使用します。
https://uedayou.net/loa/sparql
GETメソッドに以下のパラメータを与えてください。
パラメータ | 値 | 必須 |
---|---|---|
query | UTF8でURLエンコードされたSPARQLクエリ | 〇 |
format | 取得したいファイル形式、json か xml (デフォルト:json ) |
例えば、Node.js
とaxios
を使ってWeb APIにアクセスする場合は、以下のようにコードを書きます。
const axios = require('axios');
(async()=>{
const endpoint = "https://uedayou.net/loa/sparql";
const query = "select * where{ ?s ?p ?o } limit 10";
try {
const res = await axios(`${endpoint}?query=${encodeURIComponent(query)}`);
console.log(JSON.stringify(res.data, null, 2));
} catch (e) {
console.error(e);
}
})();
サンプルクエリ
サンプルクエリをいくつか紹介します。以下の接頭辞(PREFIX)をクエリに追加しておくと、多くのURIが省略して書けます。
PREFIX | URI |
---|---|
loa | https://uedayou.net/loa/ |
rdf | http://imi.go.jp/ns/core/rdf# |
rdfs | http://www.w3.org/2000/01/rdf-schema# |
geonames | http://www.geonames.org/ontology# |
schem | http://schema.org/ |
dcterms | http://purl.org/dc/terms/ |
東京都に含まれる市区町村
東京都の市区町村のURIを検索する場合は以下のようにクエリを書きます。
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX schema: <http://schema.org/>
PREFIX ic: <http://imi.go.jp/ns/core/rdf#>
PREFIX geonames: <http://www.geonames.org/ontology#>
PREFIX loa: <https://uedayou.net/loa/>
select ?uri where {
?uri geonames:parentFeature loa:東京都.
}
結果
uri |
---|
https://uedayou.net/loa/東京都あきる野市 |
https://uedayou.net/loa/東京都三宅村 |
https://uedayou.net/loa/東京都三鷹市 |
https://uedayou.net/loa/東京都世田谷区 |
https://uedayou.net/loa/東京都中央区 |
... |
東京都千代田区の住所データ
東京都千代田区 の住所データをWeb APIから取得する場合は以下のように書きます。
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX schema: <http://schema.org/>
PREFIX ic: <http://imi.go.jp/ns/core/rdf#>
PREFIX geonames: <http://www.geonames.org/ontology#>
PREFIX loa: <https://uedayou.net/loa/>
select ?p ?o where {
loa:東京都千代田区 ?p ?o.
}
結果
「永田町」を含む住所
住所のキーワード検索は以下のようにできます。
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX schema: <http://schema.org/>
PREFIX ic: <http://imi.go.jp/ns/core/rdf#>
PREFIX geonames: <http://www.geonames.org/ontology#>
PREFIX loa: <https://uedayou.net/loa/>
select ?uri ?label where {
?uri rdfs:label ?label.
filter(regex(?label, "永田町" ))
}
limit 100
結果
uri | label |
---|---|
https://uedayou.net/loa/埼玉県秩父市永田町 | 埼玉県秩父市永田町 |
https://uedayou.net/loa/岐阜県岐阜市永田町 | 岐阜県岐阜市永田町 |
https://uedayou.net/loa/新潟県長岡市永田町 | 新潟県長岡市永田町 |
https://uedayou.net/loa/東京都千代田区永田町 | 東京都千代田区永田町 |
https://uedayou.net/loa/栃木県那須塩原市永田町 | 栃木県那須塩原市永田町 |
https://uedayou.net/loa/長崎県長崎市永田町 | 長崎県長崎市永田町 |
https://uedayou.net/loa/静岡県富士市永田町 | 静岡県富士市永田町 |
https://uedayou.net/loa/鹿児島県奄美市名瀬永田町 | 鹿児島県奄美市名瀬永田町 |
東京都千代田区丸の内の丁目とジオハッシュ
東京都千代田区丸の内内に何丁目まであるか調べるクエリです。
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX schema: <http://schema.org/>
PREFIX ic: <http://imi.go.jp/ns/core/rdf#>
PREFIX geonames: <http://www.geonames.org/ontology#>
PREFIX loa: <https://uedayou.net/loa/>
select ?address ?cho ?geohash where {
loa:東京都千代田区丸の内 rdfs:label ?address;
dcterms:hasPart ?n.
?n ic:丁目 ?cho;
schema:geo ?geohash.
}
結果
address | cho | geohash |
---|---|---|
東京都千代田区丸の内 | 1 | http://geohash.org/xn76urxk5 |
東京都千代田区丸の内 | 2 | http://geohash.org/xn76urkeh |
東京都千代田区丸の内 | 3 | http://geohash.org/xn76uqu9m |
全てのジオハッシュを含む住所
町・丁目レベルにはジオハッシュが含まれますが、全てを取得しようとするとちょっとクエリの書き方にコツがいります。以下のように書きます。データだけほしい方はクエリをコピペして使ってください。何をやってるか知りたい方は下の解説を見てください。
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX schema: <http://schema.org/>
PREFIX ic: <http://imi.go.jp/ns/core/rdf#>
PREFIX geonames: <http://www.geonames.org/ontology#>
PREFIX loa: <https://uedayou.net/loa/>
select ?uri ?geohash where {
{
?uri rdfs:label ?address;
schema:geo ?geohash.
} UNION {
?s rdfs:label ?address;
dcterms:hasPart ?n.
?n schema:geo ?geohash;
ic:丁目 ?cho.
BIND (URI(CONCAT(str(?s), str(?cho), "丁目")) as ?uri)
}
}
limit 100
結果
クエリ解説
ジオハッシュは町・丁目レベルに含まれますが、丁目はブランクノードになっているのでWeb API内ではユニークなURIが存在しないことになります。そのため、そのまま検索すると町と丁目のデータを統一した形で取得できません。
そのため、上記クエリではWeb API内のデータを使って丁目のユニークなURIを復元しています。具体的には、
BIND (URI(CONCAT(str(?s), str(?cho), "丁目")) as ?uri)
で復元しています。丁目のURIは
https://uedayou.net/loa/[都道府県][市区町村][町名]〇丁目
で構成され、上位概念は必ず
https://uedayou.net/loa/[都道府県][市区町村][町名]
になるので、URI(CONCAT(str(?s), str(?cho), "丁目"))
で文字列として結合したURIをURI型に変換して、?uri
に割り当てています。
これを、違う検索式の同変数を検索結果で統合できるUNION
句を使って統合しています。
ジオハッシュ値:xn76u(東京駅の周辺)に含まれる住所
ジオハッシュを使うと、簡易的に地理空間検索ができます。
filter(regex(str(?geohash), '^http://geohash.org/xn76u'))
のhttp://geohash.org/
以後の文字列を検索したいジオハッシュ値に変更すればほかのジオハッシュで検索可能です。
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX schema: <http://schema.org/>
PREFIX ic: <http://imi.go.jp/ns/core/rdf#>
PREFIX geonames: <http://www.geonames.org/ontology#>
PREFIX loa: <https://uedayou.net/loa/>
select ?uri ?geohash where {
{
?uri rdfs:label ?address;
schema:geo ?geohash.
} UNION {
?s rdfs:label ?address;
dcterms:hasPart ?n.
?n schema:geo ?geohash;
ic:丁目 ?cho.
BIND (URI(CONCAT(str(?s), str(?cho), "丁目")) as ?uri)
}
filter(regex(str(?geohash), '^http://geohash.org/xn76u'))
}
limit 100
結果
サンプルアプリ
ジオハッシュ値:xn76u(東京駅の周辺)に含まれる住所 のクエリを使って、検索された住所を地図上にマーカーとして表示するアプリは以下のように実現できます。
ジオハッシュ値から緯度経度を算出して、マーカーとして表示しています。
以下のコードは、Vue.js 2系
で書いてます。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>住所LOD Web APIとの連携サンプルアプリ</title>
<link href="https://cdn.jsdelivr.net/npm/vue-loading-overlay@3/dist/vue-loading.css" rel="stylesheet" />
<link href="https://unpkg.com/leaflet/dist/leaflet.css" rel="stylesheet" />
<style> html, body, #app { height: 100%; margin: 0; } </style>
</head>
<body>
<div id="app">
<loading :active.sync="isLoading" :is-full-page="true"></loading>
<v-map ref="map">
<v-tilelayer :url="url" :attribution="attribution"></v-tilelayer>
<v-marker
v-for="(item, index) in markers"
:key="'mk' + index"
:lat-lng="item.point">
<v-popup :content="item.uri"></v-popup>
</v-marker>
</v-map>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-loading-overlay@3"></script>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="https://unpkg.com/vue2-leaflet"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/latlon-geohash@1.1.0/latlon-geohash.min.js"></script>
<script>
Vue.use(VueLoading);
Vue.component('loading', VueLoading)
Vue.component('v-map', Vue2Leaflet.LMap);
Vue.component('v-tilelayer', Vue2Leaflet.LTileLayer);
Vue.component('v-marker', Vue2Leaflet.LMarker);
Vue.component('v-popup', Vue2Leaflet.LPopup);
new Vue({
el: '#app',
data: {
url: 'https://{s}.tile.openstreetmap.jp/{z}/{x}/{y}.png',
attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
endpoint: 'https://uedayou.net/loa/sparql',
geohash: 'xn76u',
isLoading: true,
markers: null
},
mounted() {
var query = `PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX schema: <http://schema.org/>
PREFIX ic: <http://imi.go.jp/ns/core/rdf#>
PREFIX geonames: <http://www.geonames.org/ontology#>
PREFIX loa: <https://uedayou.net/loa/>
select ?uri ?geohash where {
{
?uri rdfs:label ?address;
schema:geo ?geohash.
} UNION {
?s rdfs:label ?address;
dcterms:hasPart ?n.
?n schema:geo ?geohash;
ic:丁目 ?cho.
BIND (URI(CONCAT(str(?s), str(?cho), "丁目")) as ?uri)
}
filter(regex(str(?geohash), '^http://geohash.org/${this.geohash}'))
}
limit 100`;
this.$nextTick(function() {
axios.get(`${this.endpoint}?query=${encodeURIComponent(query)}`)
.then(response => {
var _markers = [];
for (var res of response.data.results.bindings) {
var gh = Geohash.decode(res.geohash.value.substring(19));
_markers.push({
uri: res.uri.value,
point: [gh.lat, gh.lon]
});
}
this.markers = _markers;
var gh = Geohash.bounds(this.geohash);
this.$refs.map.mapObject.fitBounds([
[gh.sw.lat, gh.sw.lon],
[gh.ne.lat, gh.ne.lon]
]);
this.isLoading = false;
});
})
}
});
</script>
</body>
</html>
デモ用:
https://uedayou.net/loa-api-sample-vue.html
上記は、Web APIを直接アクセスしていますが、Web APIの作り上、応答に時間がかかる場合があります。速さを求められるアプリの場合は、一旦JSONなどでダウンロードしてそのファイルを読むようにしたほうが良いと思います。
Discussion