2019年10月30日水曜日

引っ越しました

わたくし、引っ越しました。
肉体の住まいの引っ越しではありません。
精神の住まいであるWEBサーバの引っ越しです。
非エンジニアの私が自力で引っ越しするのは大変でした。
住所はこちらです。 https://www.mirko.jp

なぜ引っ越そうと思ったか。
これまでは一般的な共用レンタルサーバに住んでいました。
年間3000円ちょっとの料金でサクサク動くし、PHPやPythonなどサーバサイドプログラムも動かせるし、インフラはプロに任せといたら安心だし、特段不自由を感じていませんでした。

でも次第にやりたいことが増えてきちゃうんですね。人の欲望は際限がない。
賃貸アパートでは壁に釘も打てないし、グランドピアノも置けない。

でも、自分の家に物理サーバを建てるのはさすがに無理(奥様を説得できない)

検討の結果、流行りのVPS仮想マシンを借りることにしました。
コンクリート打ちっぱなしで中身は全くの空っぽ、でも電気ガス水道は引いてある賃貸マンション、というイメージでしょうか。
仮想サーバでは Amazon AWS が有名ですが従量制なのでバズったときが怖い。
自治体消滅アプリを公開したときは毎日10万を超えるアクセスがあり、おしっこ漏れそうなくらいビビりました。(アプリはすぐ消滅してしまいましたが…)
よって、固定費用で、かつ京都の地元企業の KAGOYA にしました。

ちなみに、今まで使っていたレンタルサーバも京都の地元企業 ネットオウル です。
格安なのに安定したすばらしい速度でした。今までありがとう。
mirko.jp のドメイン管理はこれまでどおりネットオウルでやってもらおう。

で、VPSのサーバ構築ですが、まずOSを選ぶところからやるんですね。
僕はここ20年くらいWindowsしか触ってないのでUNIXの最近のトレンドを全く知りません。頭の中は FreeBSD か RedHat か、というところで止まっていました。
今は Ubuntu か CentOS のどっちにしよか、なんですね。
ググったところ、日本国内では CentOS のシェアが高いけど世界的には Ubuntu が人気らしい。意識高い系のグローバルな僕は Ubuntu を選びました。英語できないけど。

次にWEBサーバを作る。
WEBサーバといえばアパッチ。
アパッチのおたけび「ウラララーーー!!」
と叫びたくなったが、近年のトレンドはApacheではなくNginx(エンジンエックス)なんだって。
Nginxのほうが静的サイトの公開には抜群に強いらしい。
ということで、タッグパートナーのジェロニモを裏切りNginxと手を組む。

次に mirko.jpドメインの適用。
DNSサーバの設定はやったことなかったけど、なんとか完了。

次にWEBサーバにファイルを送り込む方法。
最近のトレンドはやっぱGitなんすかね。でもやっぱり馴染みのあるFTPがいいなぁ。
ということで、Gitは一旦横に置いておいて、vsftpdを導入。

次にPHP、MySql、phpMyAdminの導入。
ワードプレスは使っていないけど、PHPで動くアプリは沢山作っているので、僕にとってはこいつらは必須。

次にSSL証明書の導入。
今やSSLは現代人のマストアイテムになってしまいました。そして僕のような下級国民には無料の Let's Encrypt 一択。貧者の味方。
有効期限が3ヶ月しかないけど、CRONに書き込んでおけば自動更新もできちゃう。

ここまでが、僕にとっての必須項目。
でもせっかくなので他にもいろいろやってみる。

まずメールサーバをたてる。
僕がいつも使っているメールは、20年前から使っているプロバイダのやつ。
POP3しか対応していないので、いいかげんIMAPにしたいという思いがある。
かなり手こずったが、なんとか mirko.jpドメインのIMAPセキュアメールを作ることに成功。

で、いざメールを乗り換えようと思ったが、ここで問題点がわかる。
一つは、サーバがトラブったときにメールが使えなくなること。
素人が趣味でたてたサーバなのでときどき落ちる。WEBサイトがしばらく落ちても別にどうってことないけど、メールサーバが落ちて大事なメールがブラックホールに吸い込まれたらヤバい。
もう一つは、スパムのフィルターをかけるのが難しいこと。
人気者である僕のアドレスには、夫の莫大な遺産を持て余している未亡人やら、某アイドルの謎マネージャーやらから山ほどメールが届くので対策が必要なのだ。
頑張れば何とかなりそうな気もするが、僕の技術力ではちょっと難しいという印象。
ということで、目途がつくまではテスト扱いにする。残念。

次にグラフ型データベースの導入。
ミスターLODとしては、自分専用のRDFストアが欲しいところ。
これもかなり苦労したが、なんとかVirtuosoを導入することに成功。
僕んちのSPARQLエンドポイント
Endpoint URIは https://sparql.mirko.jp
今のところラーメンデータしか入れてませんが、よかったら遊んでみてね。
CORS対応、service句も使えます。

ここまでの総括
-------------------------------------------------------
かかった時間:10日間(約80時間)
費用:税込660円(月額。KAGOYA VPS KVMの1コア/1G版)
勉強になった度:★★★★★
得た満足感:プライスレス
代償:ひどい肩こり、腰痛、目の疲れ、家族の冷ややかな視線
-------------------------------------------------------

ここでクイズです。
普通に仕事しながらどうやって10日で80時間を捻出したのでしょうか?
答えはこれ。
平日 5時間×7日を捻出
休日 15時間×3日を捻出
睡眠時間を削りました。頑丈な体に産んでくれた親に感謝。
そして白い目で冷たく見守ってくれた家族にも感謝しよう。
今日は早く寝よ。おやすみ。

2019年9月18日水曜日

法人しりとりのSPARQLクエリ解説

法人しりとりは、経済産業省の 法人インフォ のSPARQL APIを利用したアプリである。
法人インフォでは、情報処理推進機構が提唱する 共通語彙基盤 を活用してデータモデルを作っており、RDFで実装されLODとして活用できるようになっている。
こちら が公開エンドポイント。
法人しりとりでは、以下のSPARQLクエリをリクエストし、しりとりに必要な情報を取得している。

PREFIX  hj: <http://hojin-info.go.jp/ns/domain/biz/1#>
PREFIX  ic: <http://imi.go.jp/ns/core/rdf#>
SELECT ?s ?corporateName ?corporateKana ?pref ?city
FROM <http://hojin-info.go.jp/graph/hojin>
WHERE{
  ?s hj:法人基本情報 ?key.
  ?key ic:名称 _:keyCorporateName .
     _:keyCorporateName ic:種別 '商号又は名称'.
     _:keyCorporateName ic:表記 ?corporateName .
     _:keyCorporateName ic:カナ表記 ?corporateKana .
  ?key ic:住所 _:keyAddress .
     _:keyAddress ic:種別 '住所' .
     _:keyAddress ic:都道府県 ?pref .
     _:keyAddress ic:市区町村 ?city .
  FILTER(regex(str(?corporateKana), '^ア' ))
}
LIMIT 1
OFFSET 300
(←0から300までの乱数をセット)

上記クエリで、
・法人番号(主語「s」から取得)
・法人名
・法人名カナ
・法人住所(都道府県/市区町村)

を取得する。

ポイントは
FILTER(regex(str(?corporateKana), '^ア' ))
のところ。
正規表現regex関数で、法人名カナの頭文字が「ア」のものを抽出する。
ここの「ア」を変更することにより、しりとりを継続していく。

悩むのがOFFSETの扱い。
しりとりゲームを成立させるにはランダムに法人を抽出する必要があり、OFFSETに整数の乱数をセットすることによりそれを実装する。
しかしながら先頭文字によって法人の数は大きく異なる(アから始まる法人の数とヌから始まる法人の数は大差がある)ので、大きな数をOFFSETにセットすると字によっては法人総数をオーバーしてしまうし、小さな数にすると抽出されない法人が多数出てくる字もある。
解決策は、以下のクエリで各文字の法人数を取得し、その値を最大値とする乱数をセットすること。

PREFIX  hj: <http://hojin-info.go.jp/ns/domain/biz/1#>
PREFIX  ic: <http://imi.go.jp/ns/core/rdf#>
SELECT (COUNT(?corporateKana) AS ?count)
FROM <http://hojin-info.go.jp/graph/hojin>
WHERE{
   ?s hj:法人基本情報 / ic:名称 / ic:カナ表記 ?corporateKana .
   FILTER(regex(str(?corporateKana), '^ア' ))
}


しかしながら上記のようにすると結局2回クエリを送信することになり、時間がかかり、しりとりのゲーム性が大きく損なわれる。
ゲームバランスを考えた結果、OFFSETは全ての文字で0~300で固定とすることとした。
(OFFSETが小さいほど処理が速くなる)

法人インフォのSPARQL APIのレスポンスがもっと速くなったら何の問題もないんですけどね…。


2019年9月4日水曜日

「自治体の未来」のSPARQLクエリ

「自治体の未来」は、政府統計総合窓口(e-Stat)の 統計LOD から、過去の統計調査結果のデータ(国勢調査・人口動態等)を取得しています。
以下のクエリを、統計LODのSPARQL Endpoint にリクエストすると、統計LOD「社会・人口統計体系データセット」から、特定の自治体のあらゆる統計データを取得することができます。当アプリもこのクエリを使っています。
------------------------------------------
PREFIX rdfs:<http://www.w3.org/2000/01/rdf-schema#>
PREFIX g00200502-dimension:<http://data.e-stat.go.jp/lod/ontology/g00200502/dimension/> 
PREFIX cd-dimension:<http://data.e-stat.go.jp/lod/ontology/crossDomain/dimension/> 
PREFIX sdmx-measure:<http://purl.org/linked-data/sdmx/2009/measure#> 
PREFIX sdmx-dimension:<http://purl.org/linked-data/sdmx/2009/dimension#> 
PREFIX sac:<http://data.e-stat.go.jp/lod/sac/> 
select ?indicatorURI ?indicatorName ?year ?value 
where { 
?s  sdmx-dimension:refArea  sac:C26103-19700401 ; 
    cd-dimension:timePeriod  ?year ;  
    g00200502-dimension:indicator  ?indicatorURI ; 
    sdmx-measure:obsValue  ?value . 
?indicatorURI  rdfs:label  ?indicatorName . 
filter(LANG(?indicatorName)='ja') 
} ORDER BY ASC(?indicatorURI) ASC(?year) 
------------------------------------------

上記は「京都市左京区」の統計データを取得するクエリ。
sac:C26103-19700401 の部分が、京都市左京区の「期間付き標準地域コード」です。
この部分を変えることにより、他の自治体のデータを取得することができます。

(例)
C02201-20070901 青森市
C10201-20090505 前橋市
C13104-19830818 東京都新宿区
C13421-19700401 小笠原村
C23100-20030729 名古屋市
C18201-20060201 福井市
C26103-19700401 京都市左京区

以下のクエリで、2015年時点の全国の「期間付き標準地域コード」一覧を取得することができます。
------------------------------------------
PREFIX sdmx-dimension: <http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX qb: <http://purl.org/linked-data/cube#>
PREFIX sacs: <http://data.e-stat.go.jp/lod/terms/sacs#>
PREFIX ic: <http://imi.go.jp/ns/core/rdf#>
select distinct ?code ?pref ?area
where {
?s  sdmx-dimension:refArea  ?code ;
    qb:dataSet  <http://data.e-stat.go.jp/lod/dataset/g00200521/d0003148521>.
?code  sacs:prefectureLabel  ?pref ;
       ic:表記  ?area ;
}order by ?code
------------------------------------------

2019年6月5日水曜日

横浜市保育所&幼稚園MAPのSPARQLクエリについて

このたび公開させていただいた 横浜市保育所&幼稚園MAP は、横浜市オープンデータポータルのWEB API(SPARQLエンドポイント)から必要なデータを取得している。

・横浜市WEB APIの説明は こちら
・横浜市SPARQLエンドポイントは こちら

このAPIデータは、共通語彙基盤の語彙とデータ構造が用いられている。保育所等のオープンデータを共通語彙基盤対応のLODとしたのはおそらく全国初であり、とても先駆的な取り組みといえる。

やや残念な点は、施設の座標情報(緯度経度)データがないところ。自分の通勤経路にマッチした保育施設を探すには、やはり座標情報が欲しい!

さて本稿では、横浜市APIで使えるSPARQLクエリと、それに対する考察を紹介する。

1 全ての施設のID、名称、区、住所を取得するクエリ

select *
where {
 ?s ic:ID [ic:識別値 ?id ]  ;
    ic:住所 [ ic:区 ?ku ; ic:表記 ?jusho ].
 OPTIONAL{?s rdfs:label ?name .}
}ORDER BY xsd:decimal(?id)

【備考1】
施設の「名称」データが不存在のインスタンスがいくつかある。
(データ作成時の何らかのミスが原因?)
不存在に対応するため、名称を取得する部分はOPTIONAL句を付けている。
OPTIONAL{?s rdfs:label ?name .}

【備考2】
結果を「施設ID」順に並べるのにORDER BYを利用するが、このIDは「桁が揃っていない文字列」なので、数値型に変換してから並べ直す必要がある。
ORDER BY xsd:decimal(?id)
ID番号は、桁数を揃えることが重要だよね。


2 保土ケ谷区の施設を抽出するクエリ

select *
where {
 ?s ic:ID [ic:識別値 ?id ]  ;
    ic:住所 [ ic:区 "保土ケ谷区" ; ic:表記 ?jusho ].
  OPTIONAL{?s rdfs:label ?name .}
}


3 「認可保育所」を抽出するクエリ

select *
where {
 ?s ic:ID [ic:識別値 ?id ]  ;
    ic:住所 [ ic:区 ?ku ;  ic:表記 ?jusho ].
  OPTIONAL{?s rdfs:label ?name .}
  FILTER CONTAINS(STR(?s),"保育所型/10")
}

【考察】
すべての施設は、「保育所型」か「幼稚園型」のいずれかのクラスに属している。
ただし、保育所型クラスに属する施設の、さらに詳細な施設種別は、クラスではなくプロパティで分類されている。よって、詳細な施設種別で分類するのは条件分岐がやや煩雑となる。(そもそもプロパティ設定自体が怪しい施設もあるし…)
そこで着目したのが対象施設のSubject URI(各施設の主語URI。?s のこと)。
施設種別で分類するには、?s に対し、下記の文字列を含むかどうかでフィルタリングするのが一番手っ取り早い。
FILTER CONTAINS(STR(?s),"保育所型/10")

幼稚園型施設   → 「幼稚園型」文字列が含まれる
認可保育所    → 「保育所型/10」文字列が含まれる
幼保連携型認定こども園 → 「保育所型/20」文字列が含まれる
小規模保育事業  → 「保育所型/30」文字列が含まれる
家庭的保育事業  → 「保育所型/40」文字列が含まれる
事業所内保育事業 → 「保育所型/50」文字列が含まれる
横浜保育室    → 「保育所型/60」文字列が含まれる
届出済認可外保育施設 → 「保育所型/70」文字列が含まれる


4 1歳児の入所が可能な施設を抽出

select *
where {
 ?s ic:ID [ic:識別値 ?id ]  ;
    ic:住所 [ ic:区 ?ku ;  ic:表記 ?jusho ];
    dsv:入所可能人数 [ic:種別 "1歳児" ; ic:数値 ?h_kanou_ninzu ].
   OPTIONAL{?s rdfs:label ?name .}
   FILTER (xsd:decimal(?h_kanou_ninzu) > 0 )
}


5 「認定こども園 日野幼稚園」の全情報を取得

PREFIX hoiku:<https://data.city.yokohama.lg.jp/lod/v2/保育所型/> 
PREFIX yochi:<https://data.city.yokohama.lg.jp/lod/v2/幼稚園型/> 
select *
where {
{
 hoiku:202060017 ic:メタデータ / ic:日付 [ic:標準型日付 ?a_joho_koshinbi]  .
}UNION{
 hoiku:202060017 ic:ID [ic:識別値 ?a_id ]  .
}UNION{
 hoiku:202060017 rdfs:label ?a_name  .
}UNION{
 hoiku:202060017 ic:種別コード [ic:種別 ?a_shubetsu ; ic:識別値 ?a_shubetsuData ] .
}UNION{
 hoiku:202060017 ic:住所 ?bn5 .
 ?bn5 ic:区 ?a_ku .
 ?bn5 ic:表記 ?a_jusho .
 OPTIONAL {?bn5 ic:郵便番号 ?a_yuubin.}
}UNION{
 hoiku:202060017 ic:アクセス ?bn5_2 .
 OPTIONAL {?bn5_2 ic:備考 ?h_ikikata .}
}UNION{
 hoiku:202060017 ic:連絡先 ?bn6 .
 OPTIONAL{ ?bn6 ic:電話番号 ?a_tel.}
 OPTIONAL{ ?bn6 ic:FAX番号  ?a_fax.}
 OPTIONAL{ ?bn6 ic:Webサイト ?a_Web.}
}UNION{
 hoiku:202060017 ic:関与 ?bn8 .
 OPTIONAL{ ?bn8 ic:関与者 [ic:名称 [ic:表記 ?a_secchisha]].}
 OPTIONAL{ ?bn8 ic:関与者 [ic:氏名 [ic:表記 ?y_encho]].}
}UNION{
 hoiku:202060017 ic:記述 ?bn10 .
 OPTIONAL{ ?bn10 ic:種別 ?a_sonotaL .}
 OPTIONAL{ ?bn10 ic:説明 ?a_sonotaD .}
}UNION{
hoiku:202060017 ic:利用可能時間 ?bn6_2 .
 OPTIONAL{ ?bn6_2 ic:種別 ?h_syubetsu }
 OPTIONAL{ ?bn6_2 ic:説明 ?h_setsumei }
}UNION{
 hoiku:202060017 ic:建物 ?bn7 .
 OPTIONAL{ ?bn7 ic:建物面積 [ic:数値 ?h_tatemono]. }
 OPTIONAL{ ?bn7 ic:敷地面積 [ic:数値 ?h_shikichi]. }
}UNION{
 hoiku:202060017 dsv:評価 ?bn9 .
 ?bn9 dsv:評価ステータス ?h_dai3sya .
 OPTIONAL{ ?bn9 dsv:結果公表日時 ?h_dai3date .}
 OPTIONAL{ ?bn9 ic:参照 [ic:参照先 ?h_dai3URL ] .}
}UNION{
 hoiku:202060017 ic:収容人数 [ic:種別 ?h_syuyou ; ic:数値 ?h_syuyouninzu ].
}UNION{
 hoiku:202060017 dsv:入所児童数 ?bn12 .
 OPTIONAL{ ?bn12 ic:種別 ?h_nyusyojidou .}
 OPTIONAL{ ?bn12 ic:数値 ?h_nyusyojidou_ninzu .}
 OPTIONAL{ ?bn12 ic:メタデータ / ic:日付 / ic:標準型日付 ?h_nyusyojidou_meta. }
}UNION{
 hoiku:202060017 dsv:入所可能人数 ?bn13 .
 OPTIONAL{ ?bn13  ic:種別 ?h_kanou .}
 OPTIONAL{ ?bn13 ic:数値 ?h_kanou_ninzu .}
 OPTIONAL{ ?bn13 ic:メタデータ / ic:日付 / ic:標準型日付 ?h_kanou_meta. }
}UNION{
 hoiku:202060017 dsv:入所待ち人数 ?bn14 .
 OPTIONAL{ ?bn14  ic:種別 ?h_machi .}
 OPTIONAL{ ?bn14 ic:数値 ?h_machi_ninzu .}
 OPTIONAL{ ?bn14 ic:メタデータ/ ic:日付 / ic:標準型日付 ?h_machi_meta. }
}UNION{
 hoiku:202060017 ic:備考 ?y_bikou .
}UNION{
 hoiku:202060017 ic:料金 [ ic:種別 ?y_ryokins ; ic:説明 ?y_ryokin ].
}UNION{
 hoiku:202060017 dsv:認可年月日 ?y_ninka .
}
}

【雑感】
上記が、ある施設が有している全プロパティの値を表示させるクエリだ。
施設によって、プロパティの存在・不存在がバラバラなので、UNION句とOPTIONAL句の組み合わせを工夫することによりそれに対応させている。

しかしながらいつも思うのが、共通語彙基盤のRDFをグラフ型データベースから抽出するには、このような複雑な呪文を詠唱する必要があるため、利活用のハードルがどーんと上がってしまうのが残念すぎること。呪文が長い分、抽出効率も悪いし。

一方、フラットな構造であるウィキデータから、例えば「横浜市役所」の持つ全部の情報を手に入れるのは、たった1行のクエリでできる。超かんたんで速い。スマート。
SELECT * WHERE { wd:Q11543018 ?p ?o .}

共通語彙基盤のデータ構造はマークアップ的でありXMLとは相性がいいが、それをグラフ構造(トリプル)にするとブランクノードが挟まりすぎて非常に扱いづらくなる。
共通語彙基盤対応データは、グラフデータベースに突っ込んで使うより、XMLを直接パーシングするなり、JSON-LDのままjavascriptで取り込んじゃったりするほうが向いてるのではないかと思えてきた。このあたり、IMI関係者や有識者の意見をじっくり聞いてみたいところ。

なお、横浜市の保育所等LODはURIの参照解決に対応しており、施設のSubject URIにアクセスするとJSON-LD形式で当該施設の全データが取り出せる。
https://data.city.yokohama.lg.jp/lod/v2/保育所型/202060017
(読みたいときはUnicode(UTF16)でデコードしてください。)
エンドポイントに呪文を送るより、素直に参照解決データを使うほうが簡単だったかも、とアプリを完成させてから思うのでありました。うーむ。。。

2019年3月6日水曜日

統計LOD「社会・人口統計体系」について

社会・人口統計体系 とは、別名「統計でみる都道府県・市区町村のすがた」ともいい、あらゆる統計データを収集・加工し、これを地域別に編成し整備したものである。

このデータをLOD化し、統計LODのエンドポイントからAPIでデータを取得できるようしたのは、総務省統計局及び(独)統計センターの大きな成果の一つだ。

この社会・人口統計体系LOD、上手に使いこなせれば非常に便利で、アイディア次第で面白いことがたくさんできる。

あんなこと や こんなこと

しかしながら現在、社会・人口統計体系LODが、世の中に広く普及し多くの人に利用されているとは全く言い難い状況だ。
(私以外にガチで使っている人っています?)

そして今後、統計LODの予算が減らされ、更新が停止してしまったり、事業そのものが無くなってしまうことを私は危惧している。

ということで、本日はもっと多くの方に社会・人口統計体系LODの良さを知っていただくために、便利なクエリをご紹介。

自作の 統計指標の一覧表(CC0)もプレゼント。

1 ある統計データの自治体ランキングクエリ

これは、ある統計指標(この例では2015年の人口総数)について、全国の自治体(この例では都道府県)の状況を調べるためのクエリ。

PREFIX g00200502-dimension:<http://data.e-stat.go.jp/lod/ontology/g00200502/dimension/>
PREFIX g00200502-code:<http://data.e-stat.go.jp/lod/ontology/g00200502/code/>
PREFIX cd-dimension:<http://data.e-stat.go.jp/lod/ontology/crossDomain/dimension/>
PREFIX sdmx-measure:<http://purl.org/linked-data/sdmx/2009/measure#>
PREFIX sdmx-dimension:<http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX sacs:<http://data.e-stat.go.jp/lod/terms/sacs#>
PREFIX ic:<http://imi.go.jp/ns/core/rdf#>
select  ?pref ?observation
where {
?s  g00200502-dimension:indicator  g00200502-code:indicator-A1101 ;
    cd-dimension:timePeriod  "2015"^^xsd:gYear ;
    sdmx-measure:obsValue  ?observation ;
    sdmx-dimension:refArea  ?areacode .
?areacode  sacs:administrativeClass  sacs:Prefecture ;
           ic:表記  ?pref .
} ORDER BY DESC(?observation)


2 ある自治体の様々な統計データを並べるクエリ

これは、ある自治体(この例では京都府)について、社会・人口統計体系にあるすべての統計データを調べるためのクエリ。

以下のクエリはすべての調査年のデータが吐き出されるため、データ量が多くブラウザが固まる可能性大。
PREFIX g00200502-dimension:<http://data.e-stat.go.jp/lod/ontology/g00200502/dimension/>
PREFIX cd-dimension:<http://data.e-stat.go.jp/lod/ontology/crossDomain/dimension/>
PREFIX sdmx-measure:<http://purl.org/linked-data/sdmx/2009/measure#>
PREFIX sdmx-dimension:<http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX sac:<http://data.e-stat.go.jp/lod/sac/>
select ?indicator ?year ?o
where {
?s  sdmx-dimension:refArea  sac:C26000-19700401 ;
    cd-dimension:timePeriod  ?year ;
    g00200502-dimension:indicator  ?indicator ;
    sdmx-measure:obsValue  ?o .


2015年調査のデータのみ取得の場合はこう。これは固まらない。
PREFIX g00200502-dimension:<http://data.e-stat.go.jp/lod/ontology/g00200502/dimension/>
PREFIX cd-dimension:<http://data.e-stat.go.jp/lod/ontology/crossDomain/dimension/>
PREFIX sdmx-measure:<http://purl.org/linked-data/sdmx/2009/measure#>
PREFIX sdmx-dimension:<http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX sac:<http://data.e-stat.go.jp/lod/sac/>
select ?indicator ?o
where {
?s  sdmx-dimension:refArea  sac:C26000-19700401 ;
    cd-dimension:timePeriod  "2015"^^xsd:gYear ;
    g00200502-dimension:indicator  ?indicator ;
    sdmx-measure:obsValue  ?o .
} ORDER BY ASC(?indicator)


2013~2015年調査のデータを取得する場合はこうなる。
PREFIX g00200502-dimension:<http://data.e-stat.go.jp/lod/ontology/g00200502/dimension/>
PREFIX cd-dimension:<http://data.e-stat.go.jp/lod/ontology/crossDomain/dimension/>
PREFIX sdmx-measure:<http://purl.org/linked-data/sdmx/2009/measure#>
PREFIX sdmx-dimension:<http://purl.org/linked-data/sdmx/2009/dimension#>
PREFIX sac:<http://data.e-stat.go.jp/lod/sac/>
select ?indicator ?year ?o
where {
?s  sdmx-dimension:refArea  sac:C26000-19700401 ;
    g00200502-dimension:indicator  ?indicator ;
    sdmx-measure:obsValue  ?o ;
    cd-dimension:timePeriod ?year .
 { ?s  cd-dimension:timePeriod  "2015"^^xsd:gYear .}
 UNION
 { ?s  cd-dimension:timePeriod  "2014"^^xsd:gYear .}
 UNION
 { ?s  cd-dimension:timePeriod  "2013"^^xsd:gYear .}
} ORDER BY ASC(?indicator) DESC(?year)





IODD2019大阪 ウィキデータ・ソン

インターナショナル・オープンデータ・デイ2019(IODD2019)の大阪会場に参加してきた。

今回のテーマは、ウィキデータの編集。

「ウィキペディア本文」を編集したり新規で項目を作成するのは相応の知識が必要だが、今回はウィキデータに地理情報を追加するという、誰でも手軽に実施できる内容。

具体的には、ウィキペディアに掲載されている様々な施設について、そのウィキデータを見て、座標や住所などの地理情報データがないものを、ネットで情報を調べてどんどん追加していくというもの。

私自身はこれまでウィキペディアの編集やウィキデータの利用をしたことがなかったため、当イベントは、自身の見識を深め、新たなアイディアのインスピレーションを得るのに大いに参考になった。

また、当イベントのおまけ成果物として、ウィキデータを使ったWEBアプリを会場でこしらえた。
これ→ https://www.mirko.jp/iodd2019/

ウィキデータはオープンなSPARQL APIで提供されており、またCORS(クロスオリジンリソースシェアリング)にも対応しているため、ブラウザJavascriptのみで手軽に活用アプリが作成できる。

以下、私がよく使うJavascriptのみのお手軽Webアプリのひな形だ。
これをベースとして色々作れちゃうので、ご参考に。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ウィキデータ可視化アプリ</title>
<script>
function execute() { // ボタンクリック時の動作
    var endpoint = 'https://query.wikidata.org/sparql'; //Endpointをセット
    var method = "POST"; //メソッド(POST or GET)
    //都市コードを取得
    var index = document.selectForm.cityselect.selectedIndex;
       var cityCode = document.selectForm.cityselect.options[index].value;
    //クエリ文字列をセット
    var query =  'SELECT DISTINCT ?s ?label ?point ';
        query += 'WHERE{ ?s rdfs:label ?label;';
        query += 'wdt:P131 wd:' + cityCode +';';
        query += 'wdt:P625 ?point.';
        query += 'FILTER(lang(?label)="ja")}';
    sparqlQuery(query,endpoint,method) ; //スパークルクエリ送信
}
function sparqlQuery(queryStr,endpoint,method) { // XMLHttpRequestでクエリ送信
    var querypart = "query=" + encodeURIComponent(queryStr);
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open(method, endpoint, true);
    xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xmlhttp.setRequestHeader("Accept", "application/sparql-results+json");
    xmlhttp.onreadystatechange = function() {
        if(xmlhttp.readyState == 4) {
            if(xmlhttp.status == 200 || xmlhttp.status == 201 ) {
                onSuccessQuery(xmlhttp.responseText);
            } else {
                document.getElementById("results").innerHTML = "エラー" ;
            }
        }
    }
    xmlhttp.send(querypart);
}
function onSuccessQuery(text) { // 結果(JSON文字列)を配列に格納
    var jsonObj = JSON.parse(text);
    var head , rows ;
    if (jsonObj.responseJSON) {
        head = jsonObj.responseJSON.head.vars;
        rows = jsonObj.responseJSON.results.bindings;
    } else {
        if(!(jsonObj.head)){
            document.getElementById("results").innerHTML = "スパークル構文エラー" ;
            return;
        }
        head = jsonObj.head.vars;
        rows = jsonObj.results.bindings;
    }
    if (rows.length === 0) {
        document.getElementById("results").innerHTML = "検索条件の該当データなし" ;
        return;
    }
    makeTable(head, rows);
}
function makeTable(head, rows) { // 配列をテーブルにして出力
    var html = "<table border='1'><tr>";
    for (var i=0; i<head.length; i++) { //ヘッダ部分の書込み
        html += "<th>" + head[i] + "</th>";
    }
    html += "</tr>";
    for (var i=0; i<rows.length; i++) { //内容の書込み
        html += "<tr>";
        for (var j=0; j<head.length; j++) {
            var col = head[j];
            if(rows[i][col] != null){
                html += "<td>" + rows[i][col].value + "</td>";
            }else{
                html += "<td></td>";
            }
        }
        html += "</tr>";
    }
    html += "</table>";
    document.getElementById("results").innerHTML = html;
}
</script>
</head>
<body>
<form name="selectForm">
<select name='cityselect'>
<option value="Q35765">大阪市</option>
<option value="Q193428">堺市</option>
<option value="Q335423">高槻市</option>
<option value="Q270912">枚方市</option>
<option value="Q467479">豊中市</option>
<option value="Q243863">東大阪市</option>
<option value="Q740456">岸和田市</option>
<option value="Q653510">吹田市</option>
<option value="Q502403">茨木市</option>
<option value="Q389633">寝屋川市</option>
</select>
<input type="button" value="クエリ実行" onclick="execute()">
</form>
<div id="results"></div>
</body>
</html>