まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:curl

これらの記事の続きです(シリーズとしては今回が最終回です):
Cloudant の便利な API (1) : バルクインサート
Cloudant の便利な API (2) : View Design Document
Cloudant の便利な API (3) : List Design Document

DBaaS である IBM Cloudant の便利で特徴的な API を紹介しています。前回までは Design Document の1つである View Design Document と List Design Document を紹介して、データベース内の特定の条件を満たすドキュメントデータだけを「ビュー」としてまとめ、かつそのビューの UI も格納ドキュメントの一部として定義する、という内容を紹介しました(47都道府県のドキュメントデータから海なし県だけを取り出して HTML の UI で表示する、というところまでを作りました):
2017100602

↑ https://username.cloudant.com/mydb/_design/nosea/_list/nosea/nosea にアクセスした結果(username は Cloudant インスタンスに接続するための username です)

最終回である今回は、この一覧からクリックした各ドキュメントも HTML で表示するための Show Design Document と、その API を紹介します。

前回のおさらいとして、このビューで表示される各ドキュメントをクリックすると、以下の URL に移動します(現時点ではエラーになります):
https://username.cloudant.com/mydb/_design/nosea/_show/nosea/(doc.id)

これは doc.id で示される id 値を持つドキュメントデータを nosea という Show Design Document の設計内容を使って表示する際の URL です(そして現時点では nosea という Show Design Document を定義していないためエラーになります)。

ではこの nosea Show Design Document を定義します。前回までに紹介した View Design Document と List Design Document を含む JSON の内容に赤字部分を加えて以下のようなドキュメント(nosea_show.json)を作ります:
{
 "language": "javascript",
 "views": {
  "nosea": {
   "map": "function(doc){ if( doc.code && [9,10,11,19,20,21,25,29].indexOf(doc.code) > -1 ){ emit( doc._id, {code:doc.code,prefecture:doc.prefecture,capital:doc.capital,lat:doc.lat,lng:doc.lng} ); } }"
  }
 },
 "lists": {
  "nosea": "function( head, row ){ start( { 'headers': { 'content-type': 'text/html' } } ); send( '<ul>' ); var row; while( row = getRow() ){  var url = '../../_show/nosea/';  send( ' <li><a href=\"' + url + row.id + '\">' + row.value.prefecture + '(' + row.value.capital + ')</a></li>' ); } send( '</ul>' );}"
 },
 "shows": {
  "nosea": "(function( doc, req ){ if( doc ){  var str = '<h2>' + doc.prefecture + '</h2><h3>' + doc.capital + '</h3><hr/>緯度: ' + doc.lat + '<br/>経度: ' + doc.lng;  return str; }else{  return 'empty'; }})"
 }
}

赤字部分が Show Desgin Document に相当する部分です。この中では同ファイル内の上位部分で定義された nosea ビューからクリックされた各ドキュメントが表示する際に実行される処理が記載されています。上記例では理解しやすさを優先して、ドキュメントの各属性(県名、県庁所在地名、緯度、経度)を単純に HTML で表示する内容にしています。

なお、この内容と同じファイル(nosea_show.json)をこちらからダウンロードできるよう用意しました:
https://raw.githubusercontent.com/dotnsf/samples/master/nosea_show.json


前回同様に、この JSON ファイルを指定して Design Document を更新します。まずは API で現在の Design Document を確認し、現在のリビジョンを確認します:
$ curl -u "username:password" -XGET "https://username.cloudant.com/mydb/_design/nosea"

{"_id":"_design/nosea","_rev":"YYYY..YYYY","language":"javascript", ... }

この例の場合の YYYY..YYYY 部分("_rev" の値)が現在のリビジョン ID なので、この値をダウンロードした nesea_show.json ファイルに追加します:
{
 "_rev": "YYYY..YYYY",
 "language": "javascript",
 "views": {
  "nosea": {
   "map": "function(doc){ if( doc.code && [9,10,11,19,20,21,25,29].indexOf(doc.code) > -1 ){ emit( doc._id, {code:doc.code,prefecture:doc.prefecture,capital:doc.capital,lat:doc.lat,lng:doc.lng} ); } }"
  }
 },
 "lists": {
  "nosea": "function( head, row ){ start( { 'headers': { 'content-type': 'text/html' } } ); send( '<ul>' ); var row; while( row = getRow() ){  var url = '../../_show/nosea/';  send( ' <li><a href=\"' + url + row.id + '\">' + row.value.prefecture + '(' + row.value.capital + ')</a></li>' ); } send( '</ul>' );}"
 },
 "shows": {
  "nosea": "(function( doc, req ){ if( doc ){  var str = '<h2>' + doc.prefecture + '</h2><h3>' + doc.capital + '</h3><hr/>緯度: ' + doc.lat + '<br/>経度: ' + doc.lng;  return str; }else{  return 'empty'; }})"
 }
}

これで更新用の nosea_show.json ファイルが完成しました。改めてこのファイルを指定して Design Document を更新します:
$ curl -u "username:password" -XPUT -H "Content-Type: application/json" "https://username.cloudant.com/mydb/_design/nosea" -d@nosea_show.json

{"ok":true, "id":_design/nosea", "rev":"ZZZZ..ZZZZ"}

これで Design Document が更新されました。改めてウェブブラウザで https://username.cloudant.com/mydb/_design/nosea/_list/nosea/nosea にアクセスすると、List Design Document で定義された内容に従って nosea ビューが表示されます(ここまでは前回と同様):
2017100602


そしていずれかのドキュメント(県名)をクリックすると、そのドキュメントデータが Show Design Document に定義された内容で表示されます。これで一覧から詳細情報まで表示するアプリケーションとして繋がりました!:
2017100604


先程は Design Document の理解のため比較的シンプルな Show Design Document を使いましたが、少し UI にも凝ったバージョンも用意しました:
https://raw.githubusercontent.com/dotnsf/samples/master/nosea_show2.json

こちらの JSON ファイルをダウンロードして、上記同様にリビジョン ID を調べて追加し、API で PUT すると、少しだけ凝った UI で海なし県の一覧と詳細画面を確認することができます(View Design Document は変更せずに、List Design Document と Show Design Document を変更したものです)。

nosea_show2.json を PUT した場合、ビューはテーブル形式で表示されます。県名または県庁所在地名がクリック可能になっています:
2017100605


クリックすると OpenStreetMap API を使って、その県庁所在地周辺の地図が表示されます。海なし県の海までの遠さを視覚的にも確認できる UI にしました(笑):
2017100606



4回に渡って IBM Cloudant の特徴的な Design Document とその API を中心に紹介してきました。以前にも触れましたが、IBM Cloudant のベースとなった CouchDB は IBM Notes/Domino と似た生まれであり、その設計思想などにも似た部分が多くあると感じています。私自身がノーツ大好きということもあってそんな Cloudant の特徴的な機能をまとめて紹介してみました。


これらの記事の続きです:
Cloudant の便利な API (1) : バルクインサート
Cloudant の便利な API (2) : View Design Document


DBaaS である IBM Cloudant の便利で特徴的な API を紹介しています。前回は Design Document の1つである View Design Document とその API を紹介して、データベース内の特定の条件を満たすドキュメントデータだけを「ビュー」としてまとめる方法を紹介しました。

今回紹介するのはやはり Design Document の1つで、「ビューの見た目を定義する」List Design Document です。前回の「ビュー」がドキュメント集合の条件を定義していたのに対して、今回の「リスト」は「ビューの見た目」を定義します。


一般的にデータベースを使ったウェブアプリケーションを作る場合、データベースサーバーにデータを格納した上で、そのデータを読み/書き/更新/検索して画面に表示する部分はアプリケーションサーバーに実装されることが多いです。しかし(HTTP をベースとした) REST API で動かす Cloudant は、その HTTP 部分を更に活用してデータの見た目に関わる部分までを定義・実装することができます。特に今回紹介する List Design Document ではデータの集合体であるビューの見た目を実装して格納します。個別の文書の見た目を実装する Show Design Document については次回紹介する予定です。


※余談ですが、この「見た目を定義する情報がドキュメントの一部として格納される」という点も「ノーツのビューやフォーム」に近い考え方や設計だと思っています。

このリスト(List)も Design Document の1つなので、ビューと同様に設計文書を用意します。前回紹介した View Design Document の JSON の内容に赤字部分を加えて以下のようなドキュメント(nosea_list.json)を作ります:

{
 "language": "javascript",
 "views": {
  "nosea": {
   "map": "function(doc){ if( 

doc.code && [9,10,11,19,20,21,25,29].indexOf(doc.code) > -1 ){ emit( doc._id, 

{code:doc.code,prefecture:doc.prefecture,capital:doc.capital} ); } }"
  }
 },
 "lists": {
  "nosea": "function( head, row ){ 

start( { 'headers': { 'content-type': 'text/html' } } ); send( '<ul>' ); var row; while( row = getRow() ){  var url = 

'../../_show/nosea/';  send( ' <li><a href=\"' + url + row.id + '\">' + row.value.prefecture + '(' + row.value.capital + ')

</a></li>' ); } send( '</ul>' );}"
 }
}

赤字部分が List Desgin Document に相当する部分です。この中では同ファイル内の上位部分で定義された nosea ビューを表示する際に実行される処理が記載されています。

なお、この内容と同じファイル(nosea_list.json)をこちらからダウンロードできるよう用意しました:
https://raw.githubusercontent.com/dotnsf/samples/master/nosea_list.json


赤字部分について簡単に内容を紹介すると、まず HTTP レスポンスヘッダとして 'Content-Type: text/html' を指定し、全体が HTML として返されるよう宣言しています。次に <ul> と </ul> の間が while でループ処理され、このビューに含まれる各文書(doc)の値を row として取り出します。そして
  <li><a href="../../_show/nosea/(doc.id)">県名(県庁所在地名)</a></li>
という <li> 要素を動的に作って <ul> と </ul> の間に挿入し、最後に全体を返しています。 要するに nosea ビューに含まれる海無し県の一覧を <ul> タグを使ってリスト表示している、というものです。

この JSON ファイルを指定して API を実行すれば List Design Document が作成される、、、のですが、もう1つだけ準備が必要です。今回作成する Design Document は前回作成した Design Document を上書きして保存する必要があります。そのため単にこのままのファイルを指定して API を実行しても(同一 ID への新規作成コマンドと解釈され)コンフリクトを起こしてしまい、更新できません。Cloudant の既存ドキュメントを更新する場合は既存ドキュメントのリビジョン ID を明示して実行する必要があるのでした。

というわけで、まずは API で現在の Design Document を確認し、現在のリビジョンを確認します。前回同様、コマンド内の username, password はそれぞれ Cloudant インスタンスに接続するための username, password で、実行結果を青字で表しています(以下同様):
$ curl -u "username:password" -XGET "https://username.cloudant.com/mydb/_design/nosea"

{"_id":"_design/nosea","_rev":"XXXX..XXXX","language":"javascript","views":{"nosea":{"map":"function(doc){ if( doc.code && [9,10,11,19,20,21,25,29].indexOf(doc.code) > -1 ){ emit( doc._id, {code:doc.code,prefecture:doc.prefecture,capital:doc.capital} ); } }"}}}

この例の場合の XXXX..XXXX 部分("_rev" の値)が現在のリビジョンIDです。nosea_list.json をテキストエディタで開いて、この値を使って以下のように書き換えます:
{
 "_rev": "xxxx..xxxx",
"language": "javascript", "views": { "nosea": { "map": "function(doc){ if( doc.code && [9,10,11,19,20,21,25,29].indexOf(doc.code) > -1 ){ emit( doc._id, {code:doc.code,prefecture:doc.prefecture,capital:doc.capital} ); } }" } }, "lists": { "nosea": "function( head, row ){ start( { 'headers': { 'content-type': 'text/html' } } ); send( '<ul>' ); var row; while( row = getRow() ){ var url = '../../_show/nosea/'; send( ' <li><a href=\"' + url + row.id + '\">' + row.value.prefecture + '(' + row.value.capital + ') </a></li>' ); } send( '</ul>' );}" } }

これで更新用の nosea_list.json ファイルが完成しました。改めてこのファイルを指定して Design Document を更新します:
$ curl -u "username:password" -XPUT -H "Content-Type: application/json" "https://username.cloudant.com/mydb/_design/nosea" -d@nosea_list.json

{"ok":true, "id":_design/nosea", "rev":"YYYY..YYYY"}

これで Design Document が更新されました。この時点でダッシュボードの nosea ビューを確認すると・・・一見、何も変わっていないようにみえます:
2017100601


しかしウェブブラウザで https://username.cloudant.com/mydb/_design/nosea/_list/nosea/nosea にアクセスすると、List Design Document で定義された内容に従って nosea ビューが表示されます。結果としてこのような画面が表示されます:
2017100602


これが Cloudant(CouchDB) の特徴ともいえる Design Document の機能です。通常であれば別途アプリケーションサーバーを用意した上で UI やロジックを実現するのですが、Cloudant の場合は Design Document を使うことで Cloudant の REST API(HTTP) の機能を使って UI を実現することができるようになるのでした。 ただ上のUIはシンプルすぎるので実用段階ではもう少しちゃんとしたほうがいいとは思います(苦笑)。

なお、この結果表示されている画面の各海なし都道府県へのリンクをクリックすると、以下のようなエラーになります:
2017100603


これはビューの UI とリンクまでは作成したのですが、各ドキュメントの内容(UI)を表示する Design Document についてはまだ作成していないために(リンク先が見当たらずに)エラーとなっているのでした。というわけで、次回は各ドキュメントの UI を定義する Show Design Document とその API について紹介し、各ドキュメントの内容まで表示できるようなアプリケーションを完成させる予定です。



こちらの続きです:
Cloudant の便利な API (1) : バルクインサート


NoSQL な DBaaS である IBM Cloudant の便利で特徴的な API を紹介しています。前回は1回の POST API 呼び出しでまとめて複数のドキュメントデータを登録するバルクインサート機能を紹介しました。この登録したドキュメントデータを使って、今回からは Cloudant のユニークな API を紹介すると共に、このドキュメントデータを使った参照アプリケーションを作っていきます。

今回紹介する便利な API は View Design Document API です。Cloudant には Design Document と呼ばれる特殊なドキュメントを格納することができます。一般的なドキュメントはいわば「データとしてのドキュメント」であるのに対し、Design Document は「設計情報としてのドキュメント」です。特に今回紹介する View Design Document は複数のドキュメントの集合である「ビュー」の定義情報を保持するドキュメントです。具体的にはどのような条件を満たすドキュメントがビューに含まれるのか、その条件を満たすドキュメントのどの要素をビューに格納するのか、、といった情報を保持する、(ドキュメントデータとは異なる)特殊なドキュメントです。

なお、Cloudant の Design Document に関する API のリファレンスはこちらを参照ください:
https://docs.cloudant.com/design_documents.html


では前回作った47都道府県ドキュメントデータに View Design Document を使ってビューを作ってみます。今回はこの47ドキュメントから、いわゆる「海無し県(=栃木県、群馬県、埼玉県、山梨県、長野県、岐阜県、滋賀県、奈良県)」だけを抜き出して集めた nosea ビューを定義してみます。

まず海無し県は上述の8県です。海無し県に含まれる条件は各ドキュメントデータ(doc)の code (都道府県コード)の値が「9か、10か、11か、19か、20か、21か、25か、29のいずれかであれば海無し県」ということになります。これを JavaScript で表現すると、以下のようになります:
if( doc.code && [9,10,11,19,20,21,25,29].indexOf( doc.code ) > -1 ){
    //. doc.code が存在していて、かつその値が [9,10,11,19,20,21,25,29] のいずれかであった場合、
        :
}

この条件を満たしたドキュメントデータの各値を nosea ビューに含める、という処理の場合に指定するデータは以下のような内容になります:
{
  "language": "javascript",
  "views": {
    "nosea": {
      "map": "function( doc ){ if( doc.code && [9,10,11,19,20,21,25,29].indexOf( doc.code ) > -1 ){ emit( doc._id, {code:doc.code,prefecture:doc.prefecture,capital:doc.capital} ); } }"
    }
  }
}

JSON 内の views.nosea(ビュー名).map に、このビューへのマッピングの関数を記述します。function のパラメータである doc がデータベース内の各ドキュメントを示しており、全ドキュメントがこの関数によってマッピングされ、条件を満たしたものだけがビューに含まれるよう定義されています。

関数内で使われている emit() 関数は第一パラメータがキー、第二パラメータがデータの値となります。第二パラメータに全データを含める必要はなく、今回は code, prefecture, capital という3つのデータだけを取り出して表示するようにアプリ化するので、これらの値だけを含めるようにしました。なお、上記の定義ファイル nosea_view.json はこちらからダウンロード可能です:
https://raw.githubusercontent.com/dotnsf/samples/master/nosea_view.json


ではこの(ダウンロードした)nosea_view.json と View Design Document API を使って、実際に海無し県ビューを作ってみましょう。前回同様に、nosea_view.json を保存したディレクトリで以下のコマンドを実行します(青字は実行結果、成功した時の例です):
$ curl -u "username:password" -XPUT "https://username.cloudant.com/mydb/_design/nosea" -H "Content-Type: application/json" -d @nosea_view.json

{"ok":true, "id":"_design/nosea", "rev":"XXXX..XXXX"}

前回作成した mydb データベースの中に nosea という名前のビューを、nosea_view.json で定義された内容で作成する、という API を実行しています。username および password は Cloudant に接続するためのユーザー名およびパスワードです。詳しくは前回の記事を参照してください。

このコマンドを実行後、改めてダッシュボードの画面をリロードすると、まずドキュメントデータそのものが1つ増えて 47 データから 48 データになっていることが分かります。つまりこのコマンドでデータベース内のドキュメントが1つ追加されたことが分かります(追加されたドキュメントの id は:"_design/nosea" です):
2017100504
2017100504


また mydb データベースに nosea という Design Document が追加されていることがわかります:
2017100501


nosea を更に展開し、Views の中にある nosea を選択すると、この nosea ビューで選別されたドキュメントデータが一覧できます。想定通りの8つの海無し県が並んでいれば成功です:
2017100502


value の部分が途中で切れてみれなくなっている場合は、その部分にマウスカーソルを置いてみると value の結果がオーバーレイして表示されます。JSON ファイルで指定した通りに3つの値が取得できていることが確認できます:
2017100503


なお、ビューの一覧結果は API からも参照できます:
$ curl -u "username:password" -XGET "https://username.cloudant.com/mydb/_design/nosea/_view/nosea"

{"total_rows":8, "offset":0, "rows":[
 { "id": "aaaa..aaaa", "key": "aaaa..aaaa", "value": { "code":9, "prefecture":"栃木県", "capital": "宇都宮市" } },
 { "id": "bbbb..bbbb", "key": "bbbb..bbbb", "value": { "code":10, "prefecture":"群馬県", "capital": "前橋市" } },
    :
 { "id": "cccc..cccc", "key": "cccc..cccc", "value": { "code":29, "prefecture":"奈良県", "capital": "奈良市" } }
]}


今回は Cloudant の View Design Document を使うことで特定条件を満たすドキュメントデータだけを選別してビューにする例を紹介しました。このようなドキュメント集合体の定義が1つのドキュメントデータとしてデータベース内に格納される、という所が Cloudant のユニークな点であると同時に、その集合体が「ビュー」と呼ばれている点などがなんとなくノーツっぽい※ところもあって、個人的には親和性を感じます。

※ちなみに Cloudant のベースとなっている CouchDB を開発した Damien Katz さんは元 Lotus のエンジニアです。
https://www.linkedin.com/in/damienkatz/


なお、次回は今回紹介した View Design Document を使って定義したビューを「どのような UI で見せるか」という情報を定義するための List Design Document を紹介する予定です。

NoSQL な DBaaS である IBM Cloudant を使うようになり、単に「管理/環境構築の手間がない」とか「クラスタリングされてる」という以上に便利な機能がいくつもあることが分かってきました。そのいくつかを実際のサンプルを使って動かす形で紹介しようと思います。今回はいわゆる「バルクインサート」の API で、複数のドキュメントを1回の API でまとめて作成する方法です。


以下の内容を読み進めるにあたり、「実際に自分でも試してみたい」という人は是非 IBM Cloudant のアカウントを取得して、サービスインスタンスを作って動かしてみてください。まだ環境をお持ちでない場合、IBM Bluemix のアカウントを登録して、"Cloudant NoSQL DB" を選択してサービスを作成してください。加えて API の実行には curl コマンドを使うので、curl の実行環境を用意してください(Windows であればこちら、他はおそらく標準のはず):
2017100401


サービス作成の際に利用プランを選択します。1GB かつ 30 日利用がないと削除されるという条件であれば無料の Lite プランを選択することも可能です。利用用途や目的に合わせて選択してください:
2017100402


IBM Cloudant のサービスインスタンスを作成したら(或いは既にお持ちであれば)、作成済みのサービスインスタンスの「サービス接続情報」から「資格情報の表示」を選択して、IBM Cloudant に接続するための情報を確認します。具体的にはここで表示される username と password の値を後で利用することになるので、メモしておいてください:
2017100403


また(今回はあまり使いませんが)IBM Cloudant はウェブのダッシュボード機能があり、データを確認したり、一部の操作をこのダッシュボードから行ったりすることができます。ダッシュボードにアクセスするにはサービスインスタンスの「管理」から「LAUNCH」ボタンをクリックすることで移行できます(或いは上記の「資格情報の表示」の中に表示されている "url" の値をウェブブラウザで指定して開きます。この場合はユーザー名とパスワードを聞かれるので、上記でメモした username と password を指定して開きます):
2017100404


IBM Cloudant のダッシュボード画面です。左ペインの上から2つ目がデータベース一覧タブになっており、ここから現在作成済みのデータベースを一覧できます(下図ではまだ1つもありません)。この後の作業用に1つデータベースを作っておきます。画面右上の「Create Database」をクリックします:
2017100405


適当な名前(以下例では "mydb")を入力して「Create」ボタンをクリックします:
2017100406


指定した名前(mydb)のデータベースができました。この時点ではまだ1つもドキュメント(データ)が入っていないので空の状態です。再度画面左のデータベースアイコンをクリックして、データベース一覧に戻ります:
2017100407


先程の画面に戻りました。mydb というデータベースが追加されて、現在のサイズやデータ数(# of Docs)などが確認できる状態になっています:
2017100408


ではこのデータベースに対して、バルクインサート API を実行してみます。まずはインサートするドキュメントデータのサンプル(prefs.json)をここからダウンロードします:
https://raw.githubusercontent.com/dotnsf/samples/master/prefs.json


prefs.json の内容は以下のようになっています。"docs" 内に日本の47都道府県のコード(code)と名前(prefecture)、都道府県庁所在地名(capital)、そしてその緯度(lat)経度(lng)が JSON オブジェクトで定義されており、その配列を docs としています:
{
  "docs": [
    { "code": 1, "prefecture": "北海道", "capital": "札幌市", "lat": 43.06417, "lng": 141.34694 },
    { "code": 2, "prefecture": "青森県", "capital": "青森市", "lat": 40.82444, "lng": 140.74 },
    { "code": 3, "prefecture": "岩手県", "capital": "盛岡市", "lat": 39.70361, "lng": 141.1525 },
       :
    { "code": 46, "prefecture": "鹿児島県", "capital": "鹿児島市", "lat": 31.56028, "lng": 130.55806 },
    { "code": 47, "prefecture": "沖縄県", "capital": "那覇市", "lat": 26.2125, "lng": 127.68111 }
  ]
}

これが Cloudant のバルクインサート API で使う場合のデータフォーマットになります。挿入したい複数のドキュメントデータを { "docs": [ .. ] } という形式で、"docs" の配列として定義します。

ではこのドキュメントデータ(prefs.json)を上記で作成した mydb データベースにまとめて格納します。curl を使って以下のように実行します:
$ curl -u "username:password" -XPOST "https://username.cloudant.com/mydb/_bulk_docs" -H "Content-Type: application/json" -d @prefs.json

※↑青字usernamepassword の部分には IBM Cloudant の資格情報で確認した値をそのまま指定します。またデータファイル prefs.json はコマンド実行時のカレントディレクトリに存在しているものとします。


コマンド実行後にダッシュボード画面を(リロードして)確認すると、mydb データベースに 47 件の(47都道府県の)ドキュメントデータが格納されていることが分かります。詳しく見るために mydb を選択します:
2017100401


先程は空だった mydb に 47 件のドキュメントデータが格納されていることが確認できます。個々のドキュメントデータの中身はこの(デフォルトの "Metadata" の)画面だとわかりにくいので、表示形式を "Table" か "JSON" に切り替えてみます(ここでは JSON を選択します):
2017100402


ドキュメントデータが JSON フォーマットで、実際に格納した中身(doc)まで含めて表示されます。画面では北海道の1ドキュメントデータしか表示されていませんが、下にスクロールすると他のドキュメントデータも確認することができます:
2017100403


なお、データベースのドキュメントデータ一覧は curl で以下のコマンドを実行した結果でも確認することができます:
$ curl -u "username:password" -XGET "https://username.cloudant.com/mydb/_all_docs?include_docs=true"

include_docs=true のパラメータを付けて実行すると doc の中身まで、付けずに実行すると id と key の一覧のみ取得します。


今回は1回の API 実行で複数のドキュメントデータをまとめて Cloudant に格納するバルクインサート API を紹介しました。バルクインサート自体は必ずしも Cloudant の特徴というわけではなく、他のデータベースシステムにも存在している機能だと思いますが、今後 Cloudant の特徴でもある Design Document API を紹介していくつもりで、その時には今回作成した都道府県データを使ったサンプルを紹介する予定です。


なお、IBM Cloudant の REST API リフェレンスはこちらを参照ください:
https://console.bluemix.net/docs/services/Cloudant/api/http.html
 

IBM Bluemix を通じて提供されている Watson API の1つ Visual Recognition(画像認識) API に「類似イメージ検索」というベータ版の新機能が追加されました:
https://new-console.ng.bluemix.net/catalog/services/visual-recognition/


2016101101


この API はその名の通り、「ある画像に似た画像を(あらかじめ登録しておいた画像群の中から)探す」というものです。個々の REST API としては画像の登録や削除、そして類似検索といった機能が用意されており、それらを組み合わせて実装することになります。また他の Watson API 同様、検索した結果に対してはスコアという検索結果に対する自信の数値根拠が合わせて返される、という特徴があります。


またこの類似画像検索 API のデモサイトも公開されています。選択した画像に似た画像が表示される、というものです。一度使ってみると、どういったことが可能になるのか、というイメージが付きやすくなると思います:
https://similarity-search-demo.mybluemix.net/

2016101201


このブログは公私混同を売りにしている(笑)こともあるので、実際に自分のサービスを使って試してみた様子を以下に紹介します。自分の場合は(お約束ですが)マンホールマップの画像を使わせていただきました。複数の種類のマンホール画像をあらかじめ登録しておいて、後から登録していないマンホール画像を指定して類似検索した時に同じ絵柄のマンホールを見つけることができるかどうか!? という挑戦です。

実はこの挑戦はかなりハードルの高い挑戦でもあります。なぜなら「単にマンホールとして認識」されてしまうと、登録画像は全てマンホールなので「似た絵柄のマンホール」を探すことができないと思われるからです。またマンホールの外側にある部分の類似性は無視してほしいわけですが、その辺りの所、ワトソンはどうなのよ!? ということを確認するための実験的要素の強い作業です。

なおこちらのページのマンホール画像はあらかじめ登録しておく画像の1つとしています:
2016101202


詳しくは API Reference を参照いただきたいのですが、あらかじめ画像を登録する際には画像データに加えて、JSON 形式のメタデータファイルを用意する必要があります。このメタデータに登録された内容は検索結果に含まれて返されることになるものです。そのため ID や作成日時、作成ユーザーといった情報をこのメタデータに加えておくと、画像検索した結果から作成ユーザー情報まで取り出す、ということも可能になります。

今回は登録するデータとしてこの画像と、



画像に加えて、こんな内容のメタデータを用意しておきました:
{
  "id":120002,
  "username":"morimo_t",
  "created":"2010-08-17 22:03:26",
  "text":"川崎市の色蓋。よく通る道路だったのにはじめて気がつきました。",
  "address":"神奈川県川崎市幸区堀川町",
  "lat":35.53229904174805,
  "lng":139.697998046875,
  "nice":5
}

↑細かい説明は省きますが、画像検索した結果にこの画像が含まれていた場合に、これらのメタデータと合わせて結果が取得できる、というためのものです(なのでメタデータが不要であれば空オブジェクトでも構いません)。このように画像とメタデータの組み合わせを登録する画像全てに対して用意しておきます。

実際に画像を登録する前にいくつか準備が必要です。まず当たりまえですが、IBM Bluemix にログインし、Visual Recognition サービスを追加しておきます:
2016101203


サービス追加後、「サービス資格情報」の「資格情報の表示」を選択して、認証情報の "api_key" の値を確認しておいてください(この後で使います):
2016101204


ここからは curl を使って REST API を実行するので、Linux か Mac のユーザーはターミナルを開いてください。Windows ユーザーの場合、curl は別途インストールする必要があります。以下のサイトから環境にあった Windows 用 curl をダウンロードし、インストールしてください:
https://curl.haxx.se/download.html

2016101205


curl コマンドを使う準備ができたら実際に REST API を実行してみましょう。画像を登録するにはまず入れ物(「コレクション」といいます)を用意します。ちなみに類似画像検索はこのコレクション単位で実行して、回答を取得することになります。上記デモサイトのようにショッピング用の商品画像から類似画像を探す、という使い方であれば商品の画像を登録するコレクションを作って、そこに商品画像をまとめて登録して検索する、という使い方をすることになります。

今回は「類似マンホール検索」が目的なので、マンホール画像だけを登録するコレクションをあらかじめ作っておくことにします(そしてそこにはマンホール画像以外は登録しないようにします)。新たにコレクションを作成するにはこの REST API を実行します:
# curl -X POST -F "name=XXXXXXXX" "https://gateway-a.watsonplatform.net/visual-recgnition/api/v3/collections?api_key=(apy_key)&version=2016-05-20"

メソッドは POST で、マルチパートフォームデータとして name パラメータでコレクションの名前(上の例では "XXXXXXXX")を指定します。また URL パラメータで Visual Recognition サービスを参照して確認した api_key と、version("2016-05-20")を指定して実行します。

この実行が成功すると、指定した名前のコレクションが作成され、以下のような実行結果 JSON が返ってきます:
{
  "collection_id": "XXXXXXXX_xxxxxx",
  "name": "XXXXXXXX",
  "status": "available",
  "created": "2016-10-11T05:58:47.129Z",
  "images": 0,
  "capacity": 1000000
}

"name" に指定したコレクション名が入っていて、その名称に "_xxxxxx" という形式が付いた "collection_id" が得られているはずです。この後からはこの collection_id を使って API を実行するので、この値をメモしておきましょう。またこのコレクションには現在 0 枚の画像が登録されており("images" の値)、最大で 1000000 枚の画像が登録できる、という状態になっているようです。

では入れ物ができたので、この入れ物に画像を登録します。画像ファイル名が image.png、対応するメタファイル名が image.json である場合、以下の API を実行します:
# curl -X POST -F "image_file=@image.png" -F "metadata=@image.json" "https://gateway-a.watsonplatform.net/visual-recgnition/api/v3/collections/XXXXXXXX_xxxxxx/images?api_key=(apy_key)&version=2016-05-20"

マルチパートフォームデータで画像ファイルとメタデータファイルを POST メソッドで送り、URL パラメータの中で collection_id を指定しています。この API の実行が成功すると、このような JSON が返ってきます:
{
  "images": [
    {
      "image_id": "mmmmmm",
      "image_file": "image.png",
      "created": "2016-10-11T16:12:16.435Z",
      "metadata": {
        "id":120002,
        "username":"morimo_t",
        "created":"2010-08-17 22:03:26",
        "text":"川崎市の色蓋。よく通る道路だったのにはじめて気がつきました。",
        "address":"神奈川県川崎市幸区堀川町",
        "lat":35.53229904174805,
        "lng":139.697998046875,
        "nice":5
      }
    }
  ],
  "images_processed": 1
}

実行結果として image_id などが生成されています。また metadata の中身は指定してメタデータファイルの中身になっているはずです。

この時点でコレクションの状態を確認してみることにします。コレクションの状態を確認するにはこの APIを実行します:
# curl "https://gateway-a.watsonplatform.net/visual-recgnition/api/v3/collections/XXXXXXXX_xxxxxx?api_key=(apy_key)&version=2016-05-20"

実行が成功すると、以下のような JSON が返ってきます。画像が1つ登録されたので "images" の値が 0 から 1 に変化しています:
{
  "collection_id": "XXXXXXXX_xxxxxx",
  "name": "XXXXXXXX",
  "status": "available",
  "created": "2016-10-11T05:58:47.129Z",
  "images": 1,
  "capacity": 1000000
}

こんな調子であらかじめ登録しておく画像(=検索対象となる画像)をすべて登録しておきます。なお、コレクションに追加できる画像は(API Reference によると) 2MB 以内にするべきとのこと。また1つのコレクションに 1000000 (100万)画像まで登録できることになっていますが、1つのファイルを登録するのに仮に1秒かかるとすると、100 万画像で 100 万秒、つまりノンストップで行っても約 11.5 日かかる計算になります。派手に使う場合は準備に2週間~程度かかる、という心づもりが必要になりますね。

全ての画像が登録されたら、最後に類似画像認識を実行してみます。今回はこの画像を検索してみました(この画像はコレクションには登録していません):



↑上記で紹介した登録画像と同じ絵柄のマンホール画像です。この画像を search.png として保存し、以下の REST API を実行します:
# curl -X POST -F "image_file=@search.png" "https://gateway-a.watsonplatform.net/visual-recgnition/api/v3/collections/XXXXXXXX_xxxxxx/find_similar?api_key=(apy_key)&version=2016-05-20&limit=5"

collection_id を指定して、マルチパートフォームデータが画像ファイルを POST 送信しています。またこの例では URL パラメータに limit=5 を指定して、類似度の上位5つまでを取得するように指示しています。

この API が正しく実行されると、以下のような結果が取得できます:
{
  "image_file": "5137462154683827112.png",
  "similar_images": [
    {
      "image_id": "2f98c6",
      "image_file": "120002.png",
      "score": 0.7548828,
      "created": "2016-10-11T15:22:25.824Z",
      "metadata": {
        "address": "神奈川県川崎市幸区堀川町",
        "created": "2010-08-17 22:03:26",
        "id": 120002,
        "lat": 35.53229904174805,
        "lng": 139.697998046875,
        "nice": 5,
        "text": "川崎市の色蓋。よく通る道路だったのにはじめて気がつきました。€‚",
        "username": "morimo_t"
      }
    },
    {
      "image_id": "d20e86",
      "image_file": "804002.png",
      "score": 0.7416992,
      "created": "2016-10-11T15:59:02.442Z",
      "metadata": {
        "id": 804002,
           :
           :
      }
    },
    {
      "image_id": "b6f1e7",
      "image_file": "1594034.png",
      "score": 0.74121094,
      "created": "2016-10-11T15:17:22.733Z",
      "metadata": {
        "id": 1.594034e+06,
           :
           :
      }
    },
    {
      "image_id": "f0bb1f",
      "image_file": "1888484723171738767.png",
      "score": 0.73828125,
      "created": "2016-10-11T15:31:14.768Z",
      "metadata": {
        "id": 1.888484723171739e+18,
           :
           :
      }
    },
    {
      "image_id": "9f72ed",
      "image_file": "90003.png",
      "score": 0.72998047,
      "created": "2016-10-11T15:31:21.935Z",
      "metadata": {
        "id": 90003,
           :
           :
      }
    }
  ],
  "images_processed": 1
}

この形式だとわかりにくいので、実行結果を表&画像にしてみました:
#画像スコア
10.7548828
20.7416992
30.74121094
40.73828125
50.72998047


なんとも評価の難しい結果になりました。まず1位は期待通りに、事前に登録しておいた同じ絵柄のマンホールを取得することができました!これは素晴らしい!! そして問題は2位以下です。これらはマンホールの絵柄としては明らかに異なり、「類似画像」としてはふさわしくないのですが、そのスコアが1位の画像と大差ない、という結果になりました。

まず今回登録した画像には正解(というか、同じ絵柄のマンホール)が1つしかないので、それが1位になってることは評価に値すると思っています。ただ間違いである2位との差がほとんどないというのも実は厄介で、「似た画像はこの1つだけです」という判断をさせたり、正解が1個もないような検索時に「似た画像はありません」という判断をするのが難しくなってしまうのでした。うーむ・・・

一方で、今回は 500 枚のマンホール画像を事前に登録したのですが、この枚数が少なすぎた可能性はありますね。

とはいえ、最初に触れているように、今回の検証は「マンホールの絵柄の類似性を確認できるか?」というかなりハードルの高い検証をしたつもりでしたが、ある程度は判断できているようにも見えます。今後は登録画像枚数も増やした上で再度色んなパターンで検証することになると思ってます。



なお、この類似画像検索 API の詳細については API Reference を参照ください:
http://www.ibm.com/watson/developercloud/visual-recognition/api/v3/#collections


このページのトップヘ