さくらのクラウドを使ってLS220DにアクセスするダイナミックDNSを実現する
目的
自宅に導入したLS220Dにスマホアプリの WebAccessiでアクセスを行える環境を作ります。
アクセスする時にはhttpsでアクセスしたいので独自ドメインを導入し、サーバ証明書も設置します。BuffaloNas.comからbuffalonas.comネームでアクセスすると、自宅に割り当てられたプロバイダのIPアドレスにリダイレクトされます。自宅に割り当てられるプロバイダーのIPアドレスは不定期に変更されているので、httpsでアクセスするには非常に都合が悪い状態です。そのため、独自ドメインを取得しダイナミックDNSを実現する必要があります。
ここでは書かないこと
独自ドメインの取得やサーバ証明書の設置方法、buffalo.comネームの設定方法は書きません。
実現イメージ
LS220Dは定期的にBuffaloNas.comに登録されている自身のIPアドレスを更新しています。別に契約しているVPSがあるのでVPSにさくらのクラウドのDNSを更新するために仕組みを作ります。
BuffaloNas.comに自分のLS220Dのbuffalonas.comネームを入れると、自宅のIPアドレスにリダイレクトされるので、これを利用します。リダイレクトされるということは buffalonas.comからのHTTPステータスが302で帰ってくるはずで、HTTPヘッダーを見れば、リダイレクト先のURLが記載されています。これのホストは自宅のIPアドレスになっています。
自宅のIPアドレスが分かれば、さくらのクラウドのAPIでDNSを更新することができます。
できあがったもの
GitHubに置いてあります。既にAPIでさくらのクラウドのDNSを更新している方がいたのでありがたくforkさせていただきました。
forkして変更した内容は下の通りです。
- DNSに登録するIPアドレスはBuffaloNas.comに登録してあるホストにしました。
- jsonを処理している json コマンドの入手方法がわからないのでjqに変更しました。
- docker コンテナにしました。
- config.jsonを廃止し、実行環境に依存する値はscddns.envファイルで指定します。このファイルはdocker composeのenv_fileに使われています。
- lastip.txt は予め作成する必要はありません。またこのファイルはデータコンテナに入れてあります。
- ログをデータコンテナに書き出し、後から参照できるようにしました。
これをcronで実行すれば目的達成です。
チュートリアル 「D3.jsとTopoJSONで地図を作る」を元に日本地図を作ってみる。
やりたいこと
前回は http://ja.d3js.node.ws/blocks/mike/map/ をに従ってイギリスの地図を作成しました。イギリスでは、いまいち実感がなく、チュートリアルの序盤で行うデータ作成がイギリスを対象にしていたからです。それならば日本を対象にしてデータを作成して、日本地図を作って見ます。
小笠原諸島が「Bonin island」であり、南方諸島が「Volcano island」 なので外国の人が作ったことを窺わせる名称になっています。
できあがったもの
日本の行政区には関係がなく、島を単位にしたデータのようです。d3.jsで日本地図を作る方法を調べると 国土数値情報 行政区域データの詳細 のデータを使う理由を納得できました。
実際のページ http://huruyosiathatena.github.io/d3js_map01/map_mercator_jp.html
ソースコード https://github.com/huruyosiathatena/d3js_map01/tree/master
チュートリアルからの変更
データ作成
下の方法でtopojsonを作ります。
$ ogr2ogr -f GeoJSON -where "adm0_a3 = 'JPN'" subunits_jp.json ne_10m_admin_0_map_subunits.shp $ ogr2ogr -f GeoJSON -where "iso_a2 = 'JP' " places_jp.json ne_10m_populated_places.shp $ topojson --id-property SU_A3 -p NAME=name -p NAME -o jp.json subunits_jp.json places_jp.json
地図の投影方法と中心位置を変更
メルカトル法にして、中心位置を東経135度、北緯35度にしました。
var projection = d3.geo.mercator() .center([135, 35]) .scale(2400) .translate([width / 2, height / 2]);
色付け
d3.jsが提供する d3.scale.category20() を使うことにしました。
オブジェクト名変更
理由が分からないのですが、topojsonのobjectsの下にあり、形状のsubunits
と地名のplaces
のオブジェクト名に「_jp」がついていました。
全体のソース
<!DOCTYPE html> <meta charset="utf-8"> <style> .subunit-boundary { fill: none; stroke: #777; stroke-dasharray: 2,2; stroke-linejoin: round; } .place, .place-label { fill: #444; } text { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 10px; pointer-events: none; } .subunit-label { fill: #777; fill-opacity: .5; font-size: 20px; font-weight: 300; text-anchor: middle; } </style> <body> <script src="http://d3js.org/d3.v3.min.js"></script> <script src="http://d3js.org/topojson.v0.min.js"></script> <script> var width = 960, height = 1160; var color = d3.scale.category20(); var projection = d3.geo.mercator() .center([135, 35]) .scale(2400) .translate([width / 2, height / 2]); var path = d3.geo.path().projection(projection); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); d3.json("jp.json", function(error, jp) { console.log(jp); // 国を表示 svg.selectAll(".subunit") .data(topojson.object(jp, jp.objects.subunits_jp).geometries) .enter() .append("path") .attr("class", function(d){ console.log(d.id);return "subunit "+ d.id; } ) .attr("d", path) .attr("fill", function(d,i){ return color(i);}); // 都市名の○を表示 svg.append("path") .datum(topojson.object(jp, jp.objects.places_jp)) .attr("d", path) .attr("class", "place"); // 都市名を表示 svg.selectAll(".place-label") .data(topojson.object(jp, jp.objects.places_jp).geometries) .enter().append("text") .attr("class", "place-label") .attr("transform", function(d) { return "translate(" + projection(d.coordinates) + ")"; }) .attr("dy", ".35em") .text(function(d) { return d.properties.NAME; }); // 都市名を左側と右側で外に向かうようにする svg.selectAll(".place-label") .attr("x", function(d) { return d.coordinates[0] > 135 ? 6 : -6; }) .style("text-anchor", function(d) { return d.coordinates[0] > 135 ? "start" : "end"; }); // ラベル表示 svg.selectAll(".subunit-label") .data(topojson.object(jp, jp.objects.subunits_jp).geometries) .enter().append("text") .attr("class", function(d) { return "subunit-label " + d.id; }) .attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; }) .attr("dy", ".35em") .text(function(d) { return d.properties.NAME; }); }); </script>
チュートリアル 「D3.jsとTopoJSONで地図を作る」をやってみる
地図を描く
d3.jsを調べていてやってみたかったことの一つとして地図を描くことなので、D3.js と TopoJSON で地図を作るにチャレンジしてみます。
準備
地図を描くためのデータを準備します。
gdalをインストール
GDAL(Geospatial Data Abstraction Library (地理空間データ抽出ライブラリ)) と topojsonを使うために今回は Centos7.1を使います。
gdalは は公式サイトのバイナリダウンロード DownloadingGdalBinaries – GDAL にがあるということなので、ここからリポジトリのページ http://elgis.argeo.org/ に行ってみると RHEL/CentOSは6まででした。バイナリダウンロードのページを見るとFedoraには Fedora, since release 7, includes GDAL binariesと書かれているので、試しにyum search
してみます。
$ sudo yum search gdal 読み込んだプラグイン:fastestmirror Loading mirror speeds from cached hostfile * base: www.ftp.ne.jp * epel: mirrors.hustunique.com * extras: www.ftp.ne.jp * updates: www.ftp.ne.jp ============================== N/S matched: gdal =============================== gdal-devel.x86_64 : Development files for the GDAL file format library gdal-doc.noarch : Documentation for GDAL gdal-java.x86_64 : Java modules for the GDAL file format library gdal-javadoc.noarch : Javadocs for gdal gdal-libs.x86_64 : GDAL file format library gdal-perl.x86_64 : Perl modules for the GDAL file format library gdal-python.x86_64 : Python modules for the GDAL file format library gdal.x86_64 : GIS file format library
どこかのリポジトリに含まれているので今度はyum search
をしてみます。
$ sudo yum list gdal 読み込んだプラグイン:fastestmirror Loading mirror speeds from cached hostfile * base: www.ftp.ne.jp * epel: mirrors.hustunique.com * extras: www.ftp.ne.jp * updates: www.ftp.ne.jp インストール済みパッケージ gdal.x86_64 1.11.2-2.el7 @epel
EPEL に入っていました。yum install gdal
をすると ogr2ogr を使えるようになります。
topojsonをインストール
こちらは node.jsがインストールされていたので、 sudo npm install -g topojson
でインストールします。
イギリスを描画する
チュートリアルにそって、HTMLを作っていきます。 http://ja.d3js.node.ws/blocks/mike/map/#displaying-polygons のシミが表示できました。
「ポリゴンのスタイル設定」ができなかった
イギリスを構成する国にstyleで色をつけられませんでした。デバッガで調べると.attr("class", function(d) { return "subunit " + d.id; })
の戻りが「subunit undeifed」でした。undefinedとなってしまうということは、idプロパティが無いためです。実際に「uk.json」を見るとidがありませんでした。
uk.jsonを作る過程を見直しながらjsonの内容を見ていくとidについての説明に行き着きます。
最後に、出来上がった subunits.json と places.json の二つの GeoJSON ファイルを結合して一つの TopoJSON ファイル、uk.json にします。このステップではソースの細かい不一致の修正も行います。 NAME プロパティを name に改め、また、オブジェクトID に su_a3 プロパティを使うようにさせます。
subunits.json
を見ていくと、su_a3は大文字でした。なのでtopojson
でデータを作り直します。 --id-property
プロパティの引数を大文字にするとuk.jsonにidが出力されるので国に色をつけられます。
topojson \ --id-property SU_A3 \ <b>-p NAME=name</b> \ -p name \ -o uk.json \ subunits.json \ places.json
「都市名の表示」ができなかった
国に色をつけられたので次は都市名の表示になりました。チュートリアルの従ってソースを変更すれば都市の位置を表す「●」がでてくるはずが駄目でした。国の色と同じで、uk.jsonを見るとplaces
がありませんでいた。国の色と同じであれば、places.js が疑わしいので確認してみるとしっかりと大文字でした。uk.jsonを作り直します。
topojson \ --id-property SU_A3 \ -p NAME=name \ -p <b>NAME</b> \ -o uk.json \ subunits.json \ places.json
それに応じて.text()
も.text(function(d) { return d.properties.NAME; });
に修正しました。
「国ラベルの表示」が表示できませんでした
都市名と同じなので、以下省略。
「はい!。先生。できました!!。」
都市の位置を表す「●」が大きいのを除けばチュートリアルどおりの結果になりました。
次は日本地図
チュートリアルをこなしただけなので、確認のために日本地図を作ります。
General Update Pattern, IIIを読む
アニメーションを行うことで、 .enter()、.update()、.exit()を可視化する
最後の General Update Pattern, III では .data()
メソッドに与えられたデータに追加、更新、削除が生じた時に、それぞれに異なるアニメーションを行います。
アニメーションの指定
動きは下の3つのパラメータで指定します。
transition()
: アニメーションを開始duration(ms)
: アニメーションを開始してから終了するまでの時間をミリ秒で指定ease('linear')
: 値の変化量
transition()
をjQueryのanimate
と思うとなんとなく意味が分かってきます。ease()
の引数の意味は
D3.js Easing Checker が参考になります。
データが追加された
text.enter().append("text") .attr("class", "enter") .attr("dy", ".35em") .attr("y", -60) .attr("x", function(d, i) { return i * 32; }) .style("fill-opacity", 1e-6) .text(function(d) { return d; }) .transition() .duration(750) .attr("y", 0) .style("fill-opacity", 1);
.transition()
の後にアニメーション後のy座標(attr("y",0")
)と透明度(.style("fill-opacity", 1)
)を指定しています。.transition()
の前を見るとy座標に-60、透明度に1e-6を指定しているので、アニメーションは.append("text")
で追加された後にアルファベットが透明な状態から不透明になりながら上から降りてきます。
データが更新された(今回の場合では値は変わらない)
text.attr("class", "update") .transition() .duration(750) .attr("x", function(d, i) { return i * 32; });
duration(750)
の後に.attr("x", function(d, i) { return i * 32; })
とあるので、アルファベットのx座標を変えて新しい位置へ移動します。
データが削除された
text.exit() .attr("class", "exit") .transition() .duration(750) .attr("y", 60) .style("fill-opacity", 1e-6) .remove();
追加の場合と同様に x座標と透明度を変えることで、不透明の状態から下に降りながら透明なっていき、removwe()
で削除されます。
処理
// 表示するアルファベットの配列を作成します。 var alphabet = "abcdefghijklmnopqrstuvwxyz".split(""); var width = 960, height = 500; // svgタグを作成し、gタグを縦方向の中央に移動します。 var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(32," + (height / 2) + ")"); function update(data) { // DATA JOIN // Join new data with old elements, if any. // データとデータのキーを取得する無名関数を与えます。 var text = svg.selectAll("text") .data(data, function(d) { return d; }); // UPDATE // Update old elements as needed. // 更新されたデータ // 更新後の位置に移動します。 text.attr("class", "update") .transition() .duration(750) .attr("x", function(d, i) { return i * 32; }); // ENTER // Create new elements as needed. // 追加されたデータ // 表示されている文字の上に追加され、透明な状態から下に降りながら不透明になります。 text.enter().append("text") .attr("class", "enter") .attr("dy", ".35em") .attr("y", -60) .attr("x", function(d, i) { return i * 32; }) .style("fill-opacity", 1e-6) .text(function(d) { return d; }) .transition() .duration(750) .attr("y", 0) .style("fill-opacity", 1); // EXIT // Remove old elements as needed. // 削除されたデータ // 下に移動しながら透明になり、消えます。 text.exit() .attr("class", "exit") .transition() .duration(750) .attr("y", 60) .style("fill-opacity", 1e-6) .remove(); } // The initial display. // 最初の表示を行います。a~zまでの文字が描画されます。 update(alphabet); // Grab a random sample of letters from the alphabet, in alphabetical order. // 1.5秒ごとにアルファベットをランダムに決め、アルファベット順に表示します。 setInterval(function() { update(d3.shuffle(alphabet) .slice(0, Math.floor(Math.random() * 26)) .sort()); }, 1500);
General Update PatternのI~IIIを読んでみて
見えるぞ!私にもd3.jsが見える!
データの増減が分かったでの、やりたかったグラフのデータ更新を行えそうな気がしてきました。これまでは動きがついているサンプルを見ていても、さっぱり理解できなかったのが相当改善されています。
参考ページ
d3.js の General Update Pattern, II を読む
データにキーを持たせる方法
d3.js の General Update Pattern, I を読む - huruyosi’s blog でのデータ更新は配列の要素に基づいた方法であったので、サンプルを見ていても「更新」されたとするデータがみためとはずれている感覚がありました。
General Update Pattern, II ではデータにキーを持たせることで見た目とのズレがなくなりました。
二つのソースを比べるとデータの与え方を変え、それによって.enter()
で行う要素の追加と文字の描画方法も変えています。
データの与え方
var text = svg.selectAll("text") .data(data, function(d) { return d; });
dataメソッドの第2引数にデータに対応する一意なキーを返す無名関数を指定しています。第2引数の無名関数の引数にはデータが与えられるので、このサンプルではアルファベット1文字が渡ってきます。引数をそのまま返すことで、アルファベットがキーになり、値も同じアルファベットになります。
https://github.com/mbostock/d3/wiki/Selections#data
2回目以降の dataメソッドの呼び出しでは、同一のキーであればデータが更新され、新たなキーであれば追加なので.enter()
の対象になり、無くなったキーであれば削除なので.exit()
の対象になります。
データが追加された
text.enter().append("text") .attr("class", "enter") .attr("dy", ".35em") .text(function(d) { return d; });
前回と違うのは .attr("x", function(d, i) { return i * 32; })
がなくなり、.text(function(d) { return d; })
が追加されています。キーと値が同じなので、.text
でレンダリングする文字は変化しないので追加時に設定します。一方、文字を描画するx座標は毎回変化することが考えられるので、はずされています。
追加と更新されたデータのX座標
text.attr("x", function(d, i) { return i * 32; })
データのn番目に基づいて文字を描画するx座標を決めています。
無くなったデータ
text.exit().remove();
d3.js の General Update Pattern, I を読む - huruyosi’s blog と同様に削除しています。
処理
// 表示するアルファベットの配列を作成します。 var alphabet = "abcdefghijklmnopqrstuvwxyz".split(""); var width = 960, height = 500; // svgタグを作成し、gタグを縦方向の中央に移動します。 var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(32," + (height / 2) + ")"); function update(data) { // DATA JOIN // Join new data with old elements, if any. // データとデータのキーを取得する無名関数を与えます。 var text = svg.selectAll("text") .data(data, function(d) { return d; }); // UPDATE // Update old elements as needed. // 更新されたデータ // 前回の処理でも存在した要素のclassにupdateを指定することで文字色を指定します。 text.attr("class", "update"); // ENTER // Create new elements as needed. // 追加されたデータが対象 // 前回の処理よりも増えた要素は新たななtextタグを作成し文字色を指定します。 // また"text"属性で描画する文字を、"dy"属性でサイズを指定します。 text.enter().append("text") .attr("class", "enter") .attr("dy", ".35em") .text(function(d) { return d; }); // ENTER + UPDATE // Appending to the enter selection expands the update selection to include // entering elements; so, operations on the update selection after appending to // the enter selection will apply to both entering and updating nodes. // 追加と更新されたデータが対象 // データのn番目に基づいて描画する文字のx座標を"x"属性で指定します。 text.attr("x", function(d, i) { return i * 32; }) // EXIT // Remove old elements as needed. // 削除されたデータが対象 // 前回の処理では存在し、今回の処理では存在しない要素を削除します。 text.exit().remove(); } // The initial display. // 最初の表示を行います。a~zまでの文字が描画されます。 update(alphabet); // Grab a random sample of letters from the alphabet, in alphabetical order. // 1.5秒ごとにアルファベットをランダムに決め、アルファベット順に表示します。 setInterval(function() { update(d3.shuffle(alphabet) .slice(0, Math.floor(Math.random() * 26)) .sort()); }, 1500);
d3.js の General Update Pattern, I を読む
の続きです。
漠然と、d3.jsで作成したグラフを動的に更新する方法が分からないのでd3.js でデータを更新する方法を理解するために General Update Pattern, I を読んでみます。
やっていること。
a~zの文字列を任意に抽出して表示しています。表示する時に追加された部分を緑の文字にしています。ここでの「追加」はd3.jsでは.enter()
の対象になるデータを指しています。
たとえば、「fjopqy」の次に「acdghijlnopqtr」が生成されたら要素6のj以降が追加されたデータになります。
要素n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
前回 | f | j | o | p | q | ||||||||||
今回 | a | c | d | f | g | h | i | l | q | r | t | u | w | x | y |
減った要素は .exit()
の対象になり.remove()
で削除されます。
具体的な処理
// 表示するアルファベットの配列を作成します。 var alphabet = "abcdefghijklmnopqrstuvwxyz".split(""); var width = 960, height = 500; // svgタグを作成し、gタグを縦方向の中央に移動します。 var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(32," + (height / 2) + ")"); function update(data) { // DATA JOIN // Join new data with old elements, if any. // データを与え、textタグを作ります。 var text = svg.selectAll("text") .data(data); // UPDATE // Update old elements as needed. // 更新されたデータが対象 // 前回の処理でも存在した要素のclassにupdateを指定することで文字色を指定します。 text.attr("class", "update"); // ENTER // Create new elements as needed. // 追加されたデータが対象 // 前回の処理よりも増えた要素は新たななtextタグを作成し文字色を指定します。 // また"x"属性で文字の横位置を指定し、"dy"でサイズを指定します。 text.enter().append("text") .attr("class", "enter") .attr("x", function(d, i) { return i * 32; }) .attr("dy", ".35em"); // ENTER + UPDATE // Appending to the enter selection expands the update selection to include // entering elements; so, operations on the update selection after appending to // the enter selection will apply to both entering and updating nodes. // 追加と更新されたデータが対象 // 描画する文字を指定します。 text.text(function(d) { return d; }); // EXIT // Remove old elements as needed. // 削除されたデータが対象 // 前回の処理では存在し、今回の処理では存在しない要素を削除します。 text.exit().remove(); } // The initial display. // 最初の表示を行います。a~zまでの文字が描画されます。 update(alphabet); // Grab a random sample of letters from the alphabet, in alphabetical order. // 1.5秒ごとにアルファベットをランダムに決め、アルファベット順に表示します。 setInterval(function() { update(d3.shuffle(alphabet) .slice(0, Math.floor(Math.random() * 26)) .sort()); }, 1500);
やっと分かったこと
セクションの仕組みの 悟りの鍵 を読んだことで、データと描画の関係が分かってきた気がする。.date(data)
で与えられたデータを d3.jsが保持しているデータと比較し増減があれば、.enter()
で増加分の処理を行い、.exit()
でなくなった分の処理を行っていけばいい。
書き換えは少し不安があるけど、文字の書き換えは.text()
で描画したい文字を与えればいい。
d3.jsのグラフを思うようにカスタマイズできずに手が止まってしまう件。
悩み所
サンプルを参考にして望んでいたひとつのグラフを作成することができたものの、それに手を加えようとしたら手が止まってしまいました。 今、やりたいことは下の二つです。
- ブラウザのウィンドウがリサイズされたら、リサイズ後のウィンドウサイズに応じたグラフに更新する。
- タイマーで、サーバの更新されたデータを取得してグラフを更新する
どちらもsvgタグからマルッと消してイチから作り直す方法も考えられるのだけど、それはd3.js的に違うと思う。
なんで思い通りのものを作れないのだろうか。
リサイズについては、「d3.js レスポンシブ」でぐぐるといくつかのWebページがヒットするので、見よう見まねで手元のソースを変更しようとしたのだけど、どうしていいのかが分からない。
たとえば、線を引きなおす時に古い線を消す必要は?とか、データの個数が増減した場合にはグラフの線や棒などとの対応が崩れてしまうのではないかと。
どうしていいのかが分からない理由を考えると、「Data-Driven」の「Data」と「Driven」の区別をできていないからだと考えられます。これまでに経験したグラフを作成するライブラリを使うと、元になるデータを渡して、グラフを指定して、設定情報を指定して、えいっと実行すればグラフの画像を得られていたので、この考え方から転換できていないことに気づきました。
selectAllメソッドがしっくりこないのも、同じだと感じます。
ちょっと、探してみた。
しっくりこない感を解消できそうな予感がするページです。
読んでみた感想は、
- データを与える
- d3.js内でデータの要素との対比表を作る。
- 対比表に基づい描画する
という流れを気にしながら、もう一度チャレンジするといいのかも知れないと思いました。 もっとも「キタコレ」と感じたのは 悟りへの鍵でした。読んでいて最も知りたいことが書かれていました。
データを要素に結合するためには、どのデータをどの要素に割り当てるか知っておく必要がある。このために結合用のキーが用いられる。 キーとは「名前」のように単なる識別用の文字列だ。データと要素が同じキーを持つときにデータをその要素に割り当てる。
キーを割り当てるための一番簡単なメソッドはインデックスを使う方法だ。最初のデータと要素のキーが "0"、二番目のデータと要素のキーが "1"、、、となる。
キーでデータと要素が結びついているということが分かれば、リサイズは実現できる予感がしてきました。
インタラクティブ・データビジュアライゼーション ―D3.jsによるデータの可視化
- 作者: Scott Murray,長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2014/02/19
- メディア: 大型本
- この商品を含むブログ (3件) を見る