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

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

タグ:openstreetmap

オープンな地図データである OSM(OpenStreetMap) と、オープンな地図操作 JavaScript ライブラリである Leaflet.js を使うことで、ライセンスフリーな地図アプリを作ることができます。このブログでも過去に何度か扱ったことがあります:
http://dotnsf.blog.jp/tag/leaflet

例えば、単に地図を全画面表示するのであればこんな感じのコードを書くと実現できちゃいます:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3c.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta charset="utf8"/>
<meta http-equiv="pragma" content="no-cache"/>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.js"></script>

<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="apple-mobile-web-app-title" content="OSM Sample"/>

<title>OSM Sample</title>
<script>
</script>
<script>
//. 初期中心位置
var lat = 35.681377778;
var lng = 139.76736389;

//. 初期ズームレベル
var zoomlevel = 9;

var map = null;

$(function(){
  //. 初期位置を中心とした地図を OpenStreetMap データで表示
  map = L.map('demoMap', {}).setView( [ lat, lng ], zoomlevel );
  L.tileLayer(
    'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: 'Map data © <a href="//openstreetmap.org/">OpenStreetMap</a>',
    }
  ).addTo( map );
});
</script>
<style>
html, body {
  width: 100%;
  height: 100%;
  padding: 0px;
  margin: 0px;
}
#demoMap{
  position: fixed;
  width: 100%;
  height: 100%;
}
</style>
</head>
<body>
  <div>
    <div id="demoMap"></div>
  </div>
</body>
</html>

(東京あたりを中心に、ズームレベル9で地図を表示)
2020041501


さて、上図の左上にズームコントロールがあります(上記コードでは特に指定していませんが、明示的に非表示にしない限り、デフォルトでズームコントロールが表示されます)。スマホであればピンチイン・アウトの操作でズームイン・アウトができるのですが、タッチスクリーンに対応していない PC のブラウザで地図を見ている場合は、このコントールを使ってズームイン・アウトを行うことになります。

ただ例えば画面上部に常に表示されるメッセージがある場合など、メッセージとズームコントロールが重なってしまい、操作しにくくなってしまうことがあります:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3c.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta charset="utf8"/>
<meta http-equiv="pragma" content="no-cache"/>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.js"></script>

<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="apple-mobile-web-app-title" content="OSM Sample"/>

<title>OSM Sample</title>
<script>
</script>
<script>
//. 初期中心位置
var lat = 35.681377778;
var lng = 139.76736389;

//. 初期ズームレベル
var zoomlevel = 9;

var map = null;

$(function(){
  //. 初期位置を中心とした地図を OpenStreetMap データで表示
  map = L.map('demoMap', {}).setView( [ lat, lng ], zoomlevel );
  L.tileLayer(
    'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: 'Map data © <a href="//openstreetmap.org/">OpenStreetMap</a>',
    }
  ).addTo( map );
});
</script>
<style>
html, body {
  width: 100%;
  height: 100%;
  padding: 0px;
  margin: 0px;
}
#demoMap{
  position: fixed;
  width: 100%;
  height: 100%;
}
#message{
  position: absolute;
  top: 10px;
  font-size: 20px;
  color: red;
  font-weight: bold;
}
</style>
</head>
<body>
  <div>
    <div id="demoMap"></div>
    <div id="message">このメッセージが邪魔でズーム制御ができない?</div>
  </div>
</body>
</html>

(画面上のメッセージとズームコントロールが重なってしまう例)
2020041502


この状態をなんとか回避できないか、というのが今回のブログエントリのテーマです。メッセージ側の位置を変えるのは簡単ですが、当然意図があって画面上部にメッセージを表示しているわけですから、できればズームコントロールの位置を変えたい。一方、なまじデフォルトで表示されてしまっているズームコントロールだけにどうすると位置を変更できるのか、がわかりにくいのでした。

正解の一例を先にお見せすると、こんな感じになります:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3c.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta charset="utf8"/>
<meta http-equiv="pragma" content="no-cache"/>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.js"></script>

<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="apple-mobile-web-app-title" content="OSM Sample"/>

<title>OSM Sample</title>
<script>
</script>
<script>
//. 初期中心位置
var lat = 35.681377778;
var lng = 139.76736389;

//. 初期ズームレベル
var zoomlevel = 9;

var map = null;

$(function(){
  //. 初期位置を中心とした地図を OpenStreetMap データで表示
  map = L.map('demoMap', { dragging: true, zoomControl: false }).setView( [ lat, lng ], zoomlevel );
  L.control.zoom( { position: 'bottomright' } ).addTo( map );
  L.tileLayer(
    'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: 'Map data © <a href="//openstreetmap.org/">OpenStreetMap</a>',
    }
  ).addTo( map );
});
</script>
<style>
html, body {
  width: 100%;
  height: 100%;
  padding: 0px;
  margin: 0px;
}
#demoMap{
  position: fixed;
  width: 100%;
  height: 100%;
}
#message{
  position: absolute;
  top: 10px;
  font-size: 20px;
  color: red;
  font-weight: bold;
}
</style>
</head>
<body>
  <div>
    <div id="demoMap"></div>
    <div id="message">このメッセージがあってもズーム制御できる</div>
  </div>
</body>
</html>

青字部分が肝になります。初期化時(L.map() 実行時)のオプションで zoomControl: false をつけることでズームコントロールを(一旦)非表示にします。なおもう一つ指定しているオプション dragging: true は地図のスクロール(ドラッグ)ができるようにするオプションで、ここを false にするとスクロールできない地図になります。

このままだとズームコントロールは表示されないのですが、直後の1行でズームコントロールを再び表示しています。そしてこの行では position: 'bottomright' というオプションをつけることで「右下」にズームコントロールを表示させています(デフォルトは 'topleft'):

2020041503

↑こんな感じになって、メッセージと重ならない位置にズームコントロールを移動できました。


(参考)
https://www.wrld3d.com/wrld.js/latest/docs/leaflet/L.Control.zoom/



タイミングいいのか悪いのかわかりませんが、新型コロナウィルスの影響でしばらくしばらく千葉県に引きこもることになりそう・・・って所から今回のブログネタを思いつきました。

オープンソースで公開された東京都の新型コロナウィルス感染症対策サイトが話題になりました:
https://stopcovid19.metro.tokyo.lg.jp/

2020032601


オープンソースで公開されていることに加え、扱うデータもオープンデータを活用していることで他の自治体へも活用される動きが広がっています。スピード感も含めオープンソースやオープンデータはこういう時に強いですよね。


そんな流れに触発されたこともないわけではないのですが、自分もオープンデータを使ったサービスを作れないだろうか、それもできれば比較的得意としている地図情報を使う形で、、、といった経緯の中で1つアイデアを思いついて簡単な実装までしてみました。

技術的な説明は後述しますが、まずは動いているサービスをご覧ください。 PC またはスマホのブラウザで以下のサイトにアクセスしてください:
https://dotnsf.github.io/geojsonjapan/

2020032501


上記のような東京を中心とした日本地図が画面いっぱいに表示されます。画面左上の+ーで拡大縮小したり、マウスのドラッグ(スマホの場合はピンチイン/アウトやスクロール)で表示位置を変更することができます。

その上で、日本地図の陸地部分のどこかをタップすると、そのタップされた地点がどの都道府県に属しているのかがポップアップで表示される、というものです(下図は東京都の皇居付近をタップした時の様子):
2020032502


サービスとしてはただこれだけなんですが、そこそこの精度を実現しています。例えば千葉県の、チーバ君でいえば鼻の先端部分のこのあたり、、、
2020032503


千葉県民であれば常識だと思いますが、千葉県は四方を太平洋、東京湾、利根川、そして江戸川に囲まれていて、川にかかる橋を除けば全ての県境が水の上、と思われています。実際ほぼその通りなんですが、上記部分を拡大してみるとこんな感じになっています:
2020032504


向かって右側が利根川、左側が江戸川で、この2つに挟まれた三角州部分。そのわずかな先端部分だけはなぜか千葉県ではなく茨城県になっているのでした。そういった細かな県境がこのサービスではどうなっているかというと・・・ ちゃんと対応しています:
2020032505


また千葉県に関して言えば、富津岬の先にある2つの海堡も含めて対応しています。千葉県に限らず、そこそこ細かな所までは対応した都道府県判定ができていると思っているサービスです:
2020032506


もともとは「ジオロケーションを使って利用者の位置情報を取得して、香川県からのアクセスだとわかったら・・・ (^^; 」などと考えながらはじめた取り組みだったりしたのですが、対応範囲を日本の全都道府県にして、更に地図情報を連携させた結果がこういう形になりました。これ自体が何らかの直接の役に立つものではないと考えていますが、後述する技術要素も含めて今後更に精度を高めるためのカスタマイズをしたり、今後は「地形から場所を推測する」なんてことにも挑戦したいと考えていたりします。


以下、このサービスを実現するにあたっての技術要素の紹介です。興味ある方は引き続きご覧ください。

まずこのウェブサービスアプリケーションはオープンソースとして公開しています。興味ある方はこちらもどうぞ(実質的には index.html だけで実現しています):
https://github.com/dotnsf/geojsonjapan


次にこのサービスの URL (https://dotnsf.github.io/geojsonjapan/)を再確認いただきたいです。わかる人はわかると思うのですが、Github Pages を使って公開しているサービスです。要は Github のリポジトリを使い、静的なファイル(index.html)をウェブサーバーで表示することで、このサービスは成立しています。つまり外部 API 連携を含めたバックエンド機能を一切使わずに実現しています。では改めてその制約条件の上でどのような仕組みやロジックでこのサービスを実現しているか、わかりますか?


このサービスを実現する上で、以下のような実装をしています:
  1. 地図情報に OpenStreetMap 、地図操作ライブラリに Leaflet.js を使って、ブラウザ上に日本地図を表示する
  2. 各都道府県ごとの geoJSON ファイルを使って、日本地図上に各都道府県ごとの透明なレイヤーを重ねる。その際にレイヤーがタップされた場合に都道府県名がポップアップされるようなハンドラを定義する
  3. 各都道府県ごとの geoJSON ファイルはオープンデータとして提供されているものから作ったファイル(japan.geojson)を利用する。index.html からこの geoJSON ファイルを AJAX で取得して利用する

つまりオープンソースのツールと、オープンデータだけで作ったサービスということになります。

geoJSON ファイルについては補足しておきます。geoJSON とはこの地球上における緯度・経度、およびこれらの配列を用いて点、線、多角形、多角形の組み合わせを JSON ベースで表現する標準フォーマットです。上述の Leaflet.js も geoJSON に対応しており、関数一発で geoJSON のレイヤを地図上に追加することができたり、そのレイヤに対するクリックイベントハンドリングなどが実現できます。

今回公開したこのサービスでは、上述のように国土地理院から提供されている地球地図日本のデータを geoJSON ファイル化したものを使っています。このファイル(japan.geojson)を index.html からAJAX を使って読み込み、各都道府県の feature データごとに Leaflet.js の geoJSON メソッドでレイヤに追加しています(同時に bindPopup メソッドでレイヤがタップされた時の処理も定義しています)。オープンソースとオープンデータだけでここまで作れちゃうんですねー。

なお都道府県レベルではなく市区町村レベルで同様のことができるかというと、国土交通省の行政区域データを使って作られたものは存在しているので、仕組み上はそのままでもできないことはないと思います。一方で市区町村レベルとなるとデータ量がかなり膨大となってしまうため、そのままデータだけ変えて実現してしまうと通信料(量だけでなく料)の問題が発生しかねないと思っています。

一方このデータの精密度について、充分であるとは思いますが、まだまだ厳密性に欠ける部分もあります。例えばいわゆる「飛び地」データは僕が知る限りで試した範囲では存在していませんでした(つまり未対応)。例えば有名な所では埼玉県に囲まれた東京都練馬区西大泉町はこのデータでは「埼玉県」扱いになっています:
2020032602


また実際の geoJSON データを地図上に重ねてマッピングしてみると、そこまで正確とは言えない内容でした(※そもそも地図データ側がどこまで正確か、という問題もあるとは思っています):
2020032507

↑上述の富津岬部分の geoJSON データ、結構荒い・・・


ただ大事なことは API のようなブラックボックスではなく、ここまでの基礎情報がオープンデータとして提供されており、オープンソースのツールや標準化によってここまで実現でき、必要であれば自分で手元のデータを改良(カスタマイズ)して使うこともできるような形で実現できている、という点です(geoJSON は飛び地のようなデータにも対応したフォーマットです)。オープンデータとフロントエンド機能だけでここまで実現できるようになっていることには、あらためて位置情報サービスの人気や広がりを意識するし、今後のさらなる広がりにも期待できます。

OpenStreetMap とその API を使って簡易ドライブゲームを作って公開しました。

OpenStreetMap は誰でも無料&自由に使える地図や地理情報のオープンデータプロジェクトです。API も公開されており、位置情報アプリケーション開発時に利用することができます:
2019081707


この OpenStreetMap API を使ってドライブゲームを作って公開してみました。GitHub リポジトリはこちらです:
https://github.com/dotnsf/openmap_drive/

2019081708


実質的に index.html ファイル1つだけのプロジェクトなので GitHub Pages で公開もしています。実際に遊ぶには PC のブラウザでこちらを参照してください:
https://dotnsf.github.io/openmap_drive/

2019081702


パラメータを付けずにアクセスすると、デフォルトで三重県鈴鹿市の鈴鹿サーキットのメインストレートの一部が OpenStreetMap でブラウザ画面いっぱいに表示され、その中心に赤い車(この車でドライブします)が表示されます。

キー操作は以下の3つのみです:
 矢印キーの「下」: スタート/ストップ
 矢印キーの「左」: 左旋回
 矢印キーの「右」: 右旋回


とりあえず「下」を押すと動き出します(もう一度押すと止まります)。初期状態では5時の方角を向いているので右下方向に向かって移動を始めます(実際には車の位置は変わらず、地図部分がスクロールしていきます)。矢印の「左」や「右」を押して、なるべくコース上を走るようにしてみましょう:
2019081703
(↑ 1990年にセナとプロストが接触した1コーナー)


ぶっちゃけコースアウトしようが、何かと接触しようが車は動き続けます。なので「細かいことは忘れて」コースを走り続けましょう:
2019081704
(↑ 1989年にセナとプロストが接触したトライアングルシケイン、ここはこの赤い車でも難しい)


起動時に URL パラメータを与えることで初期位置と初期角度を変えることもできます:
 lat: 緯度
 lng: 経度
 anglelevel: 角度(何時の方向、という時計の短針の方角を示す。0~11 の整数)

下図は
 https://dotnsf.github.io/openmap_drive/?lat=43.733009&lng=7.4228981&anglelevel=4
にアクセスした時の様子です。画面下に有名なレストラン "La Rascasse(ラスカス)" が見えていますが、ここは F-1 モナコグランプリ市街地コース上です。これから最終コーナーに向かうラスカス・コーナーのあたり:
2019081705


地図で表示するのは必ずしもサーキットである必要はありません。こちら
 https://dotnsf.github.io/openmap_drive/?lat=35.6828558&lng=139.7596134&anglelevel=1
は日本の皇居前、内堀通りを走る状態からスタートします:
2019081706


実は以前に同様のゲームを Google MAPs API で作って教育目的に使っていたことがありました。他の地図 API と比較して多くの機能を持ち、今でも便利に使えると思ってはいるのですが、Google MAPs API は当時と異なり現在では API キーの取得が必要になってしまい、オープンな教材としては使いにくくなってしまったのでした。


という背景もあって、今回は Google MAPs API の API キーを使うのではなく、地図 API としてはOpenStreetMap API を、leaflet.js ライブラリを併用して使う形で作り直しました。加えて車の画像も「いらすとや」で公開されているものを直接参照し、CSS の transform で角度を変えて表示するように改良しました(この辺り、詳しくは index.html の内容を直接参照してください)。結果として(画像ファイルは不要になり)index.html ファイル1つだけで完結できるようになって、GitHub Pages で公開できる形にもなった、という副産物的なメリットも生まれました。


今回公開したファイルはあくまでテンプレートというか、「これを元に改良するための素」のつもりです。このゲームは上述のように3つのキーだけで(3種類の信号だけで)操作できるので、以前に紹介したキータッチと組み合わせて体感ゲームにしたり、IoT 的な要素を使ってデバイスからの信号を受け取れるようにしたり、身近なものをリモートコントローラー代わりにして使えたら面白いかな、と思っています。あと少し技術方向性は異なるけど、複数台の車を同時に走らせたりとかも面白そう。


マンホールマップなど、地図や位置情報を使ったアプリを何度か作ったことがあります。地図を扱うためのライブラリはいくつかありますが、leaflet.js というオープンソースのマップクライアントライブラリを使う機会がありました:
2017110901


leaflet.js 自体はオープンソースで提供された JavaScript によるマップ操作クライアントライブラリです。ここで扱うマップは OpenStreetMap だったり、国土地理院のものだったり、(プラグインを使えば)Google MAPs だったりを選ぶことができます。

使い方も簡単で、まずは(CDN などから)leaflet の css と javascript をロードしておきます(以下の例では leaflet 1.2.0 を使っています。また後で使うので jQuery もロードしています):
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.js"></script>

HTML 内にマップを表示する部分を定義します。この例ではブラウザ画面全体に広がる <div id="demoMap"></div> を定義しています:
<style>
html, body  {
	width: 100%;
	height: 100%;
	padding: 0px;
	margin: 0px;
}
#demoMap {
	width: 100%;
	height: 100%;
}
</style>
  :
  :
<body>
<div id="demoMap"></div>
</body>

そしてメイン部分。上記の demoMap 内に地図を表示します。以下の例では OpenStreetMap を使って千葉県船橋市役所周辺の地図を表示し、市役所の位置にマーカーを置いて、2秒おきにマーカーをランダムウォークさせる、というものです:
<script>
//. 船橋市役所の緯度経度(初期位置)
var lat = 35.69471100;
var lng = 139.98262100;

var map = null;
var marker = null;

$(function(){
  //. 船橋市役所を中心とした地図を OpenStreetMap データで表示
  map = L.map('demoMap').setView( [ lat, lng ], 15 );
  L.tileLayer(
    'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: 'Map data © <a href="http://openstreetmap.org/">OpenStreetMap</a>',
      maxZoom: 18
    }
  ).addTo( map );
  
  //. 初期状態で市役所にマーカーを設置
  marker = L.marker( [ lat, lng ] ).addTo( map );

  //. マーカーを2秒おきにランダムウォークさせる
  setInterval( randomWalk, 2000 );
});

function randomWalk(){
  //. マーカー位置をランダムに移動
  lat += Math.random() / 100.0 - 0.005;
  lng += Math.random() / 100.0 - 0.005;
  var latlng = new L.LatLng( lat, lng );
  marker.setLatLng( latlng );
}
</script>


完成品の HTML はこちらに用意しました。全体を確認したい方はこちらから HTML ファイルをダウンロードしてください:
https://raw.githubusercontent.com/dotnsf/samples/master/leafletjs.html


ダウンロードした HTML をウェブブラウザで表示すると、以下のように船橋市役所を中心としたマップが表示され、青いマーカーが2秒おきにその場所を少しずつ変えて移動する様子が確認できると思います:
2017110901


こういったライブラリを使って地図アプリを作っておくと、利用する地図を変える(例えば OpenStreetMap だったり、Google MAPs だったりに変える)のが楽になりますね。

これらの記事の続きです(シリーズとしては今回が最終回です):
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 の特徴的な機能をまとめて紹介してみました。


このページのトップヘ