👋

住所オープンデータ提供Web APIの使い方

15 min read

先日紹介した、住所オープンデータ提供サイトですがWeb APIも利用可能です。

https://uedayou.net/loa/

まだ以下の投稿見てない方いましたら、先に見ていただいたほうがわかりやすいと思います。

https://zenn.dev/uedayou/articles/3b1459597bd017

Web APIは、RDF用クエリ言語SPARQLで検索可能なSPARQLエンドポイントになります。

ここではそのSPARQLエンドポイントの使い方を紹介します。

SPARQLとは

SPARQLはRDF用のクエリ言語で、RDFのトリプル(主語、述語、目的語のセット)を検索することができます。一般的なWeb APIよりも自由度が高いですが、その分、目的のデータを検索するクエリを一から作るのは若干難しいです。
基本的な書き方は以下を参照ください。

有料になりますが私のかかわるサークルでSPARQLについての技術同人誌を発行しています。

https://techbookfest.org/product/5642815304368128

SPARQLクエリで検索できるWeb APIはこれ以外にもたくさん公開されています。SPARQLクエリが書けると以下で紹介されている様々なAPIでも応用が可能です。

https://qiita.com/uedayou/items/9e4c6029a2cb6b76de9f

この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クエリを試してみたい方は、以下をブラウザで開いてフォームにクエリを入力して実行してください。

https://uedayou.net/loa/sparql.html

Webブラウザ上から検索

Web APiとして検索

アプリやプログラム内で使いたい場合は以下を使用します。

https://uedayou.net/loa/sparql

GETメソッドに以下のパラメータを与えてください。

パラメータ 必須
query UTF8でURLエンコードされたSPARQLクエリ
format 取得したいファイル形式、json xml (デフォルト:json)

例えば、Node.jsaxiosを使って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.
}

結果

p o
http://www.w3.org/1999/02/22-rdf-syntax-ns#type http://imi.go.jp/ns/core/rdf#住所型
http://imi.go.jp/ns/core/rdf#表記 東京都千代田区
http://imi.go.jp/ns/core/rdf#都道府県 東京都
http://www.w3.org/2000/01/rdf-schema#label 東京都千代田区
http://imi.go.jp/ns/core/rdf#市区町村 千代田区
http://www.geonames.org/ontology#parentFeature 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 ?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

結果

uri geohash
https://uedayou.net/loa/三重県いなべ市北勢町下平 http://geohash.org/xn304d6bj
https://uedayou.net/loa/三重県いなべ市北勢町中山 http://geohash.org/xn1pfe2ed
https://uedayou.net/loa/三重県いなべ市北勢町二之瀬 http://geohash.org/xn3074cqx
https://uedayou.net/loa/三重県いなべ市北勢町京ケ野新田 http://geohash.org/xn304ksv2
https://uedayou.net/loa/三重県いなべ市北勢町其原 http://geohash.org/xn1pghewp
https://uedayou.net/loa/三重県いなべ市北勢町別名 http://geohash.org/xn1pcqw19
https://uedayou.net/loa/三重県いなべ市北勢町北中津原 http://geohash.org/xn3056xdm
https://uedayou.net/loa/三重県いなべ市北勢町千司久連新田 http://geohash.org/xn3069tcs
... ...

クエリ解説

ジオハッシュは町・丁目レベルに含まれますが、丁目はブランクノードになっているので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

結果

uri geohash
https://uedayou.net/loa/東京都中央区日本橋兜町 http://geohash.org/xn76uxwcr
https://uedayou.net/loa/東京都中央区日本橋小網町 http://geohash.org/xn76uzc4w
https://uedayou.net/loa/東京都中央区日本橋箱崎町 http://geohash.org/xn76uzm49
https://uedayou.net/loa/東京都中央区明石町 http://geohash.org/xn76uszp2
https://uedayou.net/loa/東京都中央区浜離宮庭園 http://geohash.org/xn76u7uf0
https://uedayou.net/loa/東京都中央区豊海町 http://geohash.org/xn76ud8c3
https://uedayou.net/loa/東京都千代田区日比谷公園 http://geohash.org/xn76unpr7
https://uedayou.net/loa/東京都千代田区皇居外苑 http://geohash.org/xn76ur348
https://uedayou.net/loa/東京都中央区京橋1丁目 http://geohash.org/xn76ux4br
https://uedayou.net/loa/東京都中央区京橋2丁目 http://geohash.org/xn76uwdr9
https://uedayou.net/loa/東京都中央区京橋3丁目 http://geohash.org/xn76uw926
... ...

サンプルアプリ

ジオハッシュ値: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: '&copy; <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

ログインするとコメントできます