現在、福岡県の福岡市、糸島市において公共施設情報や統計情報を取得できるAPIが公開されています。
公共施設等情報のオープンデータ実証 開発者サイト
http://teapot.bodic.org/
この記事ではPHP、JavaScriptを用いてこのAPIを操作する事例を記述します。
これらを利用して以下のような物が作れます。
福岡市病院マップ
http://needtec.sakura.ne.jp/fukuoka_map/page/hospital_map?lang=ja
福岡市災害マップ
http://needtec.sakura.ne.jp/fukuoka_map/page/disaster_map?lang=ja
Github:
https://github.com/mima3/fukuoka_map/
提供中のAPI
現在提供されているAPIは大きく2種類存在します。
1つはSPARQLクエリーを用いた検索用のAPIで、もう一つは経度緯度より検索する地理的検索APIです。
これらのAPIは開発者サイトの以下のページから実験できます。
##RDFについて
Resource Description Framework(RDF)はWeb上にあるリソースを記述するために統一された枠組みです。
RDFはトリプルと言われる主語、述語(プロパティともいう)、目的語の集合です。
主語、述語、目的語のトリプルの1組をRDFグラフとよび、ノードと有向グラフで表現できます。
実際に「公共施設等情報のオープンデータ」のデータの一部のデータを見てみましょう。
この例では「ひかり保育園〒815_0082」を主語としたデータの一部を表示しています。
主語と述語はURIで表現しています。
目的語は、「ttp://teapot.bodic.org/dataset/福岡市公共施設」というURIと「福岡市」、「ひかり保育園」、「2014-03-31」といったリテラルで表現しています。
次に複雑な構造をもっているデータの例をみてみましょう。
たとえば、所在地といった場合、県、市、区、町・・・といった様々な要素から構成されています。これらは空白ノードを用いることで、構造体として表現できます。
この例では「ttp://teapot.bodic.org/predicate/所在地」は空白ノードを目的語としています。
そして、その空白ノードを主語として、県、市、区、町を表現します。
RDFについての詳細は以下の文献を参考にしてください。
RDF入門
http://www.asahi-net.or.jp/~ax2s-kmtn/internet/rdf/rdf-primer.html
RDF(Resource Description Framework):概念および抽象構文
http://www.asahi-net.or.jp/~ax2s-kmtn/internet/rdf/rdf-concepts.html
##SPARQLを使用して「公共施設等情報のオープンデータ」を探索する
SPARQLはRDF用のクエリー言語で、RDFに対しての操作を行えます。
下記のURLにその詳細があります。
RDF用クエリ言語SPARQL
http://www.asahi-net.or.jp/~ax2s-kmtn/internet/rdf/rdf-sparql-query.html
この章では、実際に「公共施設等情報のオープンデータ」をSPARQLで探索してみましょう。
「公共施設等情報のオープンデータ」に対しては以下のページからSPARQLを実行できます。
APIを試す(公式)
http://teapot.bodic.org/test_api.html
teapotのSPARQL実行
http://needtec.sakura.ne.jp/fukuoka_map/page/teapot_sparql?lang=ja
今回は、「teapotのSPARQL実行」を用いて説明します。
###もっとも単純な例
まずは、なんでもいいので、トリプルを取得するクエリーを実行します。
select * where { ?s ?p ?o . } LIMIT 1
ここでは、構文の詳細は説明しませんが、SQLと似た感じであることがわかります。
この実行結果は以下のようになります。
s | p | o |
---|---|---|
http://teapot.bodic.org/facility/福岡県財産9200375 (uri) | http://teapot.bodic.org/predicate/用途 (uri) | 体育館 (literal) |
もしかしたら、データの内容は異なるかもしれませんが、列数と行数は同じになります。
なお、()の中身はデータの型を表します。以下のいずれかになります。
type | 説明 |
---|---|
uri | URI |
literal | リテラル |
bnode | 空白ノード |
では、where区を次のように変更して実行してみましょう。
select * where { ?a ?b ?c . } LIMIT 1
こんどは、列名が「a,b,c」となることがわかります。
a | b | c |
---|---|---|
http://teapot.bodic.org/facility/福岡県財産9200375 (uri) | http://teapot.bodic.org/predicate/用途 (uri) | 体育館 (literal) |
where区には、主語、述語、目的語のトリプルを設定します。
この際、「?」ではじまる文字を指定した場合、それは変数となり、任意の主語、述語、目的語を取得します。
では、特定のuriまたはリテラルでデータを取得する場合、どうしたらいいのでしょうか?
これについては次で説明します。
###特定の主語で検索する。
ここでは、主語が「ttp://teapot.bodic.org/facility/ひかり保育園〒815_0082」のデータを取得してみましょう。
select * where { <http://teapot.bodic.org/facility/ひかり保育園〒815_0082> ?p ?o . }
where区の主語が変数ではなく、明示的にURIを指定します。この際、前後を<>で囲みます。
この結果は次のようになります。
このように、「ttp://teapot.bodic.org/facility/ひかり保育園〒815_0082」を主語とするデータが取得できました。
###特定の述語で検索する。
ここでは、「ttp://teapot.bodic.org/predicate/延長保育」を述語とするデータを取得してみましょう。
select * where { ?s <http://teapot.bodic.org/predicate/延長保育> ?o . }
今度はwhere区で述語の箇所にURIを明示します。
s | o |
---|---|
http://teapot.bodic.org/facility/泰幸保育園〒811_1361 (uri) | 1時間 (literal) |
http://teapot.bodic.org/facility/こじか保育園〒819_0383 (uri) | 1時間 (literal) |
http://teapot.bodic.org/facility/ゆなの木保育園〒814_0123 (uri) | 1時間 (literal) |
http://teapot.bodic.org/facility/信和保育園〒814_0175 (uri) | 1時間 (literal) |
http://teapot.bodic.org/facility/西新保育園〒814_0004 (uri) | 2時間 (literal) |
このように、特定の述語(プロパティ)のトリプルの一覧を容易に取得できます。
###特定の目的語で検索する
ここでは特定の目的語を検索してみます。
まずは、「ttp://teapot.bodic.org/dataset/福岡市保育園」が目的語のトリプルを取得してみましょう。
select * where { ?s ?p <http://teapot.bodic.org/dataset/福岡市保育園> . }
この結果は次のようになります。
さて、目的語にはURIだけでなく、リテラルも格納されている可能性があります。
このリテラルを指定して検索する場合は次のようにダブルクォーテーションで値を囲んで実行します。
select * where { ?s ?p "保育園" . }
###主語、述語、目的語のいずれか2つを指定した検索
ここまでで、主語、述語、目的語のいずれか1つを指定した検索を紹介しました。
主語、述語、目的語は同時に2つ指定可能です。
ここでは、「ttp://teapot.bodic.org/predicate/延長保育」が「3時間」の保育園の一覧を取得してみましょう。
select * where { ?s <http://teapot.bodic.org/predicate/延長保育> "3時間" . }
この結果は次のようになります。
s |
---|
http://teapot.bodic.org/facility/まつぼっくり保育園〒812_0053 (uri) |
このように、主語、述語、目的語を指定して検索することが確認できました。
###結果を並び替える。
ORDER BY区を使用して結果を並び替えることができます。
select * where {
?s <http://teapot.bodic.org/predicate/延長保育> ?o.
} order by DESC(?o)
この結果は目的語が大きいもの順に並び替えて出力されます。
s | o |
---|---|
http://teapot.bodic.org/facility/玉川保育園分園向野保育園〒815_0035 (uri) | - (literal) |
http://teapot.bodic.org/facility/荒江保育園分園〒814_0104 (uri) | - (literal) |
http://teapot.bodic.org/facility/西浦保育園〒819_0202 (uri) | - (literal) |
http://teapot.bodic.org/facility/観音寺保育園愛宕浜小学校分園〒819_0013 (uri) | - (literal) |
http://teapot.bodic.org/facility/あすなろ保育園〒811_1323 (uri) | 4時間 (literal) |
http://teapot.bodic.org/facility/どろんこ保育園〒812_0018 (uri) | 4時間 (literal) |
http://teapot.bodic.org/facility/舞鶴保育園〒810_0072 (uri) | 4時間 (literal) |
http://teapot.bodic.org/facility/まつぼっくり保育園〒812_0053 (uri) | 3時間 (literal) |
http://teapot.bodic.org/facility/あかつき保育園〒813_0036 (uri) | 2時間 (literal) |
http://teapot.bodic.org/facility/あゆみらい保育園〒819_0041 (uri) | 2時間 (literal) |
なお、以下のように、DESC()を省略した場合、昇順になります。
select * where {
?s <http://teapot.bodic.org/predicate/延長保育> ?o.
} order by ?o
複数の列を指定する場合は以下のように、byの後に列を列挙します。
select * where {
?s <http://teapot.bodic.org/predicate/延長保育> ?o.
} order by DESC(?o) ?s
###データを分割して取得する
大量の結果が返ってくるクエリーを実行するとエラーが発生します。
たとえば、次のようなクエリーを実行してみましょう。
select * where {
?s ?p <http://teapot.bodic.org/dataset/福岡市公共施設> .
}
この時、以下のようなエラーが発生します。
{"@message":"The SPARQL result is too large. Consider using limit and offset (the server limit is at 2097152 bytes of string)."}
公共施設等情報のオープンデータのSPARQLのAPIでは2MBを超える結果の場合、エラーとして処理されます。
このような大量の結果を取得するには、ORDER BYで結果を並びかえて、OFFSETとLIMITで部分取得します。
select * where {
?s ?p <http://teapot.bodic.org/dataset/福岡市公共施設> .
} ORDER BY ?s ?p OFFSET 0 LIMIT 5
この例では大量の結果のうち、5件のみ抽出しています。
次の5件を取得するにはOFFSETを増やします。
select * where {
?s ?p <http://teapot.bodic.org/dataset/福岡市公共施設> .
} ORDER BY ?s ?p OFFSET 5 LIMIT 5
このように、OFFSETを増加させていき、データが取得できなくなるまで繰り返せば大量の結果であっても取得することが可能です。
###列の絞込みと重複削除
大量のデータをLIMITとOFFSETで分割取得する方法を紹介しましたが、列の絞込みと、同じ行の重複を削除することで結果のサイズを削減することが可能です。
この場合、以下のようにDISTINCT区と、表示する列名を指定してください。
select distinct ?s where {
?s ?p <http://teapot.bodic.org/dataset/福岡市公共施設> .
}
###1行に複数の列を指定する
今までは最大で、主語、述語、目的語の3つの列数でした。
しかし、WHERE区を複数書くことで、1行に複数の列を指定することが可能になります。
以下の例では1行に施設名、担当部局、経度、緯度を表示するようにしています。
select distinct ?name ?tantou ?lat ?lng where {
?s <http://teapot.bodic.org/predicate/施設名> ?name .
?s <http://teapot.bodic.org/predicate/担当部局> ?tantou .
?s <http://teapot.bodic.org/predicate/緯度> ?lat.
?s <http://teapot.bodic.org/predicate/緯度> ?lng.
} limit 100
name | tantou | lat | lng |
---|---|---|---|
小呂中学校 (literal) | 福岡市教育委員会 (literal) | 33.869652 (literal) | 33.869652 (literal) |
防火女原 (literal) | 福岡市消防局管理課 (literal) | 33.5732291 (literal) | 33.5732291 (literal) |
道路百道浜2丁目 (literal) | 福岡市道路下水道局道路管理課 (literal) | 33.593386 (literal) | 33.593386 (literal) |
用悪水路立花寺2丁目 (literal) | 福岡市道路下水道局下水道河川管理課 (literal) | 33.5659894 (literal) | 33.5659894 (literal) |
道路荒津2丁目 (literal) | 福岡市道路下水道局道路管理課 (literal) | 33.5916838 (literal) | 33.5916838 (literal) |
千代1丁目 (literal) | 福岡市道路下水道局下水道河川管理課 (literal) | 33.6064121 (literal) | 33.6064121 (literal) |
住宅仮称吉塚西 (literal) | 福岡市住宅都市局住宅計画課 (literal) | 33.5994292 (literal) | 33.5994292 (literal) |
見上納骨堂 (literal) | 福岡市市民局地域施策課 (literal) | 33.5693551 (literal) | 33.5693551 (literal) |
下山門公衆便所 (literal) | 福岡市環境局収集管理課 (literal) | 33.580178 (literal) | 33.580178 (literal) |
###データのフィルタリング
結果を減らす別の方法としてはフィルタリングが存在します。
以下の例では特定の「内科」または「アレルギー科」をもつ病院の一覧を取得します。
select distinct ?s where {
?s <http://teapot.bodic.org/predicate/診療科目> ?o.
FILTER(?o="アレルギー科" || ?o="内科")
}
FILTERには正規表現が使用できます。
以下の例では住所に中央区が含まれるデータを取得します。
select * where {
?s <http://teapot.bodic.org/predicate/addressClean> ?o.
FILTER regex(?o, "中央区", "i").
}
s | o |
---|---|
http://teapot.bodic.org/facility/白金2丁目(福岡市財産5084) (uri) | 福岡県福岡市中央区白金2丁目2番21号 (literal) |
http://teapot.bodic.org/facility/メンタルクリニック桜坂〒810_0023(渡邊泰良) (uri) | 福岡県福岡市中央区警固3丁目6番1号 (literal) |
http://teapot.bodic.org/facility/道路平尾4丁目(福岡市財産3916) (uri) | 福岡県福岡市中央区平尾4丁目14番31号 (literal) |
FILTER区を二つ続けた場合はAND条件となります。
以下の例では診察科目に「内科」または「アレルギー科」をもつ病院で、かつ、住所が「中央区」のものを取得しています。
select distinct * where {
?s <http://teapot.bodic.org/predicate/診療科目> ?kamoku.
?s <http://teapot.bodic.org/predicate/addressClean> ?address.
FILTER(?kamoku="アレルギー科" || ?kamoku="内科")
FILTER regex(?address, "中央区", "i").
}
s | kamoku | address |
---|---|---|
http://teapot.bodic.org/facility/みかどクリニック〒810_0041(三角大児) (uri) | 内科 (literal) | 福岡県福岡市中央区大名2丁目4番33号 (literal) |
http://teapot.bodic.org/facility/せと山荘クリニック〒810_0014((医)せと山荘クリニック) (uri) | 内科 (literal) | 福岡県福岡市中央区平尾5丁目4番21号 (literal) |
http://teapot.bodic.org/facility/浜の町病院〒810_8539(国家公務員共済組合連合会) (uri) | アレルギー科 (literal) | 福岡県福岡市中央区長浜3丁目3番1号 (literal) |
http://teapot.bodic.org/facility/天神つじクリニック〒810_0001((医)鸛進会天神つじクリニック) (uri) | 内科 (literal) | 福岡県福岡市中央区天神3丁目10番11号 (literal) |
###空白ノードの取り扱い
以下のクエリを実行すると、b0、b1という空白ノードが表示されます。
select * where { <http://teapot.bodic.org/facility/ひかり保育園〒815_0082> ?p ?o . }
p | o |
---|---|
http://teapot.bodic.org/predicate/所在地 (uri) | b0 (bnode) |
http://teapot.bodic.org/predicate/address (uri) | b1 (bnode) |
空白ノードの名称は、結果作成時に一意にわりあてられるだけのものです。
たとえば、次のクエリーを実行してみてください。
select * where { <http://teapot.bodic.org/facility/ひかり保育園〒815_0082> ?p ?o . } order by ?o limit 1
この結果、次の列が表示されます。
p | o |
---|---|
http://teapot.bodic.org/predicate/所在地 (uri) | b0 (bnode) |
では、OFFSET区で続きを取得したら「b1」というノードが取得できるのでしょうか?
select * where { <http://teapot.bodic.org/facility/ひかり保育園〒815_0082> ?p ?o . } order by ?o offset 1
実際には、以下のように「b0」となります。
p | o |
---|---|
http://teapot.bodic.org/predicate/address (uri) | b0 (bnode) |
これにより、空白ノードの名称は、結果中で一意の名称となるように便宜的に付与されていることがわかります。
では、次に空白ノードに紐づくデータを取得するにはどうしたらいいでしょうか?
この例では、空白ノードの住所に紐ずく県、市などをどうやって取得するかを示します。
select ?p ?o where {
{
<http://teapot.bodic.org/facility/ひかり保育園〒815_0082> ?p ?o .
FILTER(!isBlank(?o))
}
union {
<http://teapot.bodic.org/facility/ひかり保育園〒815_0082> ?x ?y .
?y ?p ?o .
FILTER(isBlank(?y))
}
}
まず、空白ノード以外のデータを取得します。
次にUNIONにより、そのデータに空白ノードを主語とするデータを結合します。
この実行結果は以下のようになります。
##プログラミング言語からAPIを呼ぶ方法
以下のエンドポイントに対してクエリーを指定してgetまたはpostを行うだけです。
このAPIについてのドキュメントは下記を参照してください。
http://teapot.bodic.org/api_doc.html
###JavaScriptからの実行例
ここではJavaScriptから実行する例を紹介します。
var sql = "SELECT * WHERE{?s ?p ?o} LIMIT 10"//
$.post(
"https://teapot-api.bodic.org/api/v1/sparql",
{
query: sql
},
function (res) {
console.log(res);
},
"json"
).error(function(e){
console.log("Error: " , e);
});
実際コードを組む場合、文字列の連結がメンドクサイのでORMっぽく実行できるようにしました。
var query = new teapot.Query();
query.columns(['?s', '?p', '?o']);
query.where('?s', '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>', '<' + item + '>')
.where('?s', '?p', '?o')
.filter('!isBlank(?o)')
.filter('!isBlank(?s)')
.union()
.where('?s', '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>', '<' + item + '>')
.where('?s', '?p', '?a')
.where('?a', '?p', '?o')
.filter('isBlank(?a)')
.filter('!isBlank(?s)')
.union()
.where('?x', '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>', '<' + item + '>')
.where('?x', '?p', '?o')
.where('?s', '?p', '?x')
.filter('!isBlank(?o)')
.filter('isBlank(?x)')
.union()
.where('?x', '<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>', '<' + item + '>')
.where('?x', '?p', '?a')
.where('?a', '?p', '?o')
.where('?x', '?p', '?a')
.where('?s', '?p', '?x')
.filter('isBlank(?a)')
.filter('!isBlank(?x)')
.orderby('?s');
query.executeSpilit(5000, function(err, res) {
callback(err, res);
});
このライブラリは以下から取得できます。
https://github.com/mima3/fukuoka_map/blob/master/js/teapot.js
デモ:
http://needtec.sakura.ne.jp/fukuoka_map/page/teapot_sparql?lang=ja
http://needtec.sakura.ne.jp/fukuoka_map/page/teapot_explore?lang=ja
###PHPからの実行例
JavaScriptと同様にPHPでもORMっぽく実行できるようにしてみました。
$query = $query->where('<http://teapot.bodic.org/facility/さくら病院〒814_0142(医療法人社団江頭会さくら病院)>', '?p', '?o')
->filter('!isBlank(?o)')
->orderby('?p ?o');
以下のソースコードをダウンロードしてプロジェクトに取り込むことで使用できます。
https://github.com/mima3/fukuoka_map/tree/master/src/MyLib/TeapotWhere.php
https://github.com/mima3/fukuoka_map/tree/master/src/MyLib/TeapotQuery.php
https://github.com/mima3/fukuoka_map/tree/master/src/MyLib/TeapotCtrl.php
https://github.com/mima3/fukuoka_map/tree/master/src/MyLib/ApiCtrlBase.php
##まとめ
このページでは公共施設等情報のオープンデータ実証が提供しているデータの取得方法を説明しました。
また、SPARQLの簡単な使い方と、PHP,JavaScriptからどのように実行するかも説明しました。
これにより、公共施設等情報のオープンデータ実証が提供するデータを自由に探索できるかと思います。