メイキング「NERV極秘資料 - 電力使用状況」こと技術解説
先日、東京電力の電力使用状況をエヴァンゲリオン風デザインで表示するWebサイトを作ったものが、あちこちで反響を頂きました。
ねとらぼ:「NERV専用監視装置」で東電の電力状況をチェック エヴァ風サイト登場 - ITmedia NEWS
NERVの一員になったつもりで節電! エヴァ風電力使用状況メーター | ギズモード・ジャパン
Twitterでは16000以上ツイート頂けたようです。
http://topsy.com/kanmisikou.net/lab/power/
こちらの、はてなブックマーク週間ランキングでもIT・コンピュータカテゴリ2位を頂きました。ありがとうございます。
http://b.hatena.ne.jp/ranking/weekly/20110321/it
これだけの反響を頂きましたし、せっかくなのでWebアプリを作る工程を解説します。
最近メイキング映像色々見てて、自分もやりたくなったとか言えない
とりあえず作ろうと思ったキッカケは、Twitter見てたときにid:TERRAZIさんが
とか言ってたのを見て、そういえばヤシマ作戦発動時のコンソールとか画面めっちゃカッコ良かったし
結構グラフィカルで分かりやすいから、これくらい単純に表示出来たら面白いかもなー と思ったからです。単純ですね(棒
1. とりあえず数値データを手に入れる
今では東電さんがCSVデータを公開してくれてますが、
3/22当時はグラフだけの表示だったので数値データは自分でグラフを解析して手に入れるほかありませんでした。
ちなみにその翌日から、あちこちで同じようなAPIが公開されてて、うおおおこっち使いたかったあああとか思ってました。極力サボります。
さて、解析にはPHPを使いました。
とりあえずグラフから数値を取る、簡単な方法としては色を識別して高さのピクセルを取得する方法です。
JPEGなら画像を圧縮する際にノイズが載って、微妙に色が変わってくるので揺らぎを吸収する必要がありますが、
幸い提供されていたグラフ画像はGIFだったので、色の違いがなかったのでその方法でいきました。
こんな感じで、24回。下から上へと1pxずつナメていきます。
PHPにはGDがありますので、簡単に処理可能です。
以下にGDを使ってピクセルを取得する部分のコードを載せてコメントで解説します。
<?php // GIF画像を取得してイメージリソースとする $image=ImageCreateFromGIF("http://www.tepco.co.jp/forecast/html/images/juyo-j.gif"); $x=46; // X軸のカーソルを置くピクセルの初期値 // 24時間分、繰り返す for($j=0;$j<24;$j++) { $x+=22; // グラフ軸を1つずらす(px数はPhotoshopなどで測る) // 棒グラフの高さ分、繰り返す for($i=225;$i>=0;$i--) { $y=60+$i; // 下から順番になめていく $rgb=imagecolorat($image,$x,$y); // X軸、Y軸のピクセルを取得 $colors=imagecolorsforindex($image, $rgb); // RGB値を連想配列に格納 if( ($colors["red"]==0) && ($colors["green"]==0) && ($colors["blue"]==255) ) { // 〜 R:0 G:0 B:255 (ブルー)のピクセルが見つかったときの処理 〜 } } }
あとは、各色が見つかった時の処理を書いていくだけです。
ただ、あまり確実なものではないので、フェイルセーフ設計で例外に対処出来るようにするべきでしょう。
また、これを毎回叩くとサーバーの負荷になるだけではなく、東京電力のサーバーにも毎回画像を取りに行って大変迷惑をかけてしまうので
キャッシュ処理は確実に行うようにしましょう。
さらに、セキュリティも気にしましょう。Yahoo!のエンジニアさんも言っていました。「何も信じるな」と。
(参考:ヤフーにおけるインプットバリデーション「何も信じるな」 - Yahoo! JAPAN Tech Blog )
このブログでも以前に解説していますので、参考にしてみてください。
(PHPで誰でも簡単Webサービス製作!でなんか作って公開した奴ちょっと来い - 甘味志向@はてな )
さて。これで以下のようなデータが取れました。
余談ですがPHPは「json_encode()」という関数を使えば、連想配列を一発でJSONに変換出来ます。
ただ今回はXMLに整形して出力することにしました。
それがこちらです。
http://kanmisikou.net/lab/power/today.xml
こちらはAPIとして開放していますので、自由に使ってくださいね。
(ただ他のところの方が、色々使いやすいと思います-^)
2. デザインをこねる
とりあえずヤシマ作戦を流れるように一回見て(気分転換とも言う)エヴァを堪能したあと、Illustratorを起動します。
ヱヴァンゲリヲン新劇場版:序 (EVANGELION:1.11) [Blu-ray]
- 出版社/メーカー: キングレコード
- 発売日: 2009/05/27
- メディア: Blu-ray
- 購入: 61人 クリック: 525回
- この商品を含むブログ (247件) を見る
そしてゴリゴリとデザインを起こします。ゴリゴリ。
この時、円形メーターの盤をどうやって描こうかなーと思ってたんですが、
ちょっと調べてみたらすごくスマートなやり方がありました。
http://www.designioustimes.com/tutorials/5-steps-for-drawing-complex-vector-sun-rays.html
(円を描いて、円の幅と線の太さを設定して、破線を設定するだけで放射線が描ける)
これ考えた人はホント頭いい。
ちなみに破線は「アピアランスを分割」ではパスに出来ないので「透明部分を分割・統合」でベクトル側に振ってパス化すればOKです。
デザインしながら、HTMLでどうやって実装するか考えていきます。
メーターを動かす程度の動きならFlashを使うよりもHTML5を利用した方が楽だと思ったので、
Text-ShadowやWeb Fontsなどを使うことを意識してデザインします。
たとえば、Text-Shadowで再現出来ないようなシャドウを使わないようにしたり、
シャドウが画像の裏側に回らないと行けないようなデザインはしないようにします。
またWeb Fontsは基本的にフォントファイルを読み込まないといけないので、1つか2つまでには抑えたいところです。
Opera等ではフォントファイルの読み込みが完了するまではローカルの通常フォントで表示されますが、
Chrome等ではロードが完了するまで文字が表示されないので極力ロード時間は減らすべきです。
そういう意味でも、日本語フォントをWeb Fontsで利用するのはオススメ出来ませんね。
デザインが出来ました。
3. HTMLに起こす
さて、まずHTMLで表示出来るようにデザインを起こす必要があります。
先ほど作ったIllustratorファイル(.ai)をPNG形式で書き出し、Photoshopに突っ込みます。
今回は文字やギミックを入れないといけないので、imgタグを使って表示させるよりも、
ボックス要素のbackground-imageとして表示させた方が、あとから入れやすそうです。
なので、ボックス要素ごとに背景として配置出来るように画像を分割します。
スライスツールで画像をサクサクっと切ったら「Webおよびデバイス用に画像を書き出し」ダイアログを使ってファイルサイズを調整しつつ書き出し。
色が変わらないように、プロファイルもチェックしましょう。
あと、必要な画像をそれぞれ作って切り分けます。(メーターの針とか、ロゴとかその他色々)
これで大体画像が揃ったので、いよいよコーディングです。
ちなみに先程のPHPのプログラミングもそうですが、Codaというエディタを使っています。
なかなか小奇麗なのに多機能で便利なやつで、Dreamweaverから乗り換えて使っています。
App Storeで買えますよ↓
http://itunes.apple.com/jp/app/coda/id406001464?mt=12
とりあえずdiv要素をfloatで組み上げて、カカカッと段組みを作ります。
そして、それぞれのdiv要素にbackground-imageを設定して、一枚の画像になるように組みます。
それが出来たら、各ボックスに突っ込むギミックを色々入れます。
テキストを動かすときはspan要素、メーターの針はcanvas要素ですね。
画像を入れ替えるだけのようなギミックは、そのままimg要素を入れます。
<div id="container"> <div id="mater1"></div> <div id="mater2"><span class="count" id="count1">0</span><canvas id="pw1" width="400" height="400"></canvas></div> <div id="mater3"><span class="count" id="count2">0</span><canvas id="pw2" width="400" height="400"></canvas></div> <div id="mater4"><span id="gettime" class="number">0000-00-00 00:00:00</span><span id="jst" class="number">0000-00-00 00:00:00</span></div> <div id="mater5"><img id="blackout" src="images/blackout.png" alt="" width="148" height="259" /></div> <div id="mater6"><span class="number" id="rs1">0</span><span class="number" id="rs2">0</span><span class="number" id="rs3">0</span></div> <div id="mater7"></div> <div id="mater8"><iframe src="http://110chang.com/rinban/" id="rinban"></iframe><div id="rinban_txt">情報提供元 : http://110chang.com/rinban/</div></div> <div id="mater9"></div> </div>
また、110changさんの「輪番停電|関東」を埋めこまさせていただいているので、そこにはiframe要素を設定しておきました。
さて、ここからは飾り付けです。
4. CSS3でデザイン
CSS3で追加された様々なプロパティを使うと、かなーり豪華に彩ることができます。
といっても、処理が重いものが多々あるので、多様は厳禁。
見た目もダサくなるし、何にでも使えばいいってもんじゃないですね。
今回使ったのは、Web fontsとText-Shadow。
まずWeb fontsは「@font-face」というものを最初に記述して、フォント名とURLを紐付けます。
以降は、font-familyに普段どおりフォント名を記述するだけ。ね、簡単でしょ?
@font-face { font-family: yourname; src: url(YournameD7UltimateCondensed.ttf); } .count { font-size:110px; text-shadow: 0px 0px 20px rgba(148,219,119,1); text-align: right; color: #dcf0dc; font-family:yourname; }
ちなみに、フォントファイルとして指定できるものは種類があって、ブラウザごとに処理が異なってきます。
上記コードは省略しているものですが、興味があれば「Webfonts WOFF」などでググッてみてください。
text-shadowは解説サイトが大量にあるので割愛します(棒
5. JavaScriptでギミックを作る
さて、最終段階ですね!
ぼくはjQueryラブなのでjQueryを使います。
(もちろん要件ごとに得意不得意あるのですが、今回はDOM操作が多いので適していると思います)
jQueryの「The Write Less, Do More.」という考え方は結構好きです。
というか、プログラマならシンプルでスマートかつ、セキュアなコードは好物だと思いますw
そんなことはさておき、まずページがロードされてJavaScriptが呼び出される部分からです。
$(document).ready(function(){ $.ajax({ url: "today.xml", type: "GET", dataType: "xml", cache: false, success: init }); });
ページがロードされて、jQueryがreadyを叩いたら最初にXMLデータをAjaxで取得します。
どうでもいいですが、Ajaxと言われる度に洗剤が頭の中で浮かぶのは何とかならないんでしょうか。
ロードが成功したら、init関数が呼び出されます。
init関数では、読み込んだXMLの数値をHTMLに入れて数値を表示させたり、画像を入れ替えたりします。
ここでメーターを操作してもいいんですが、「メーターの針がぐいーんって上がっていくのがカッコイイんだろ!!」という中二病精神を遺憾なく発揮したため、その処理を開始します。
// メーターの針は先に読み込んでおきます var arrow = new Image(); arrow.src = "images/m.png"; function drawImage () { // canvas要素を取得します。 // ちなみに以下の1文はjQueryを使っている場合「var c=$("#pw1")[0];」または「var c=$("#pw1").get();]でも可能。これ豆な。 var c=document.getElementById("pw1"); // canvas要素の2D描画コンテキストにアクセスします var cnt=c.getContext("2d"); // 描画されている内容を全消しします cnt.clearRect(0,0,c.width,c.height); // 描画位置を中心にします cnt.translate(c.width/2, c.height/2); // ぐいーんと数値を上げていく部分です。指定された数値になるまで5ずつ上げていきます if(nowrot<rot) nowrot+=5; else nowrot=rot; // ランダムな数値を加算して、針をぶらします base=nowrot+(Math.floor(Math.random()*5)/10); // 円周率で角度からアングルを算出します base=base*Math.PI/180; // 描画コンテキストを回転させます cnt.rotate(base); // 針は画像中央よりも上部にあるので、ピクセル数を計算してその位置に描画します cnt.drawImage(arrow,-17,-203); // 描画コンテキストを元の角度に戻します cnt.rotate(-base); // 描画コンテキストを元の位置に戻します cnt.translate(-c.width/2, -c.height/2); // 50ミリ秒後に再度呼び出します setTimeout(drawImage,50); }
結構簡単なのが分かりますでしょうか。
イメージとしては、セル画(描画したい画像)を用意して、透明なセルを移動&回転させて、その位置に焼き込む。という感じ。
あと、きちんと元の位置に描画コンテキストを戻さないと、次に呼び出されたときにどんどんおかしくなっていきます。
さて、これで完成です!
6. おわりに
面白かったのが、海外からの反応でした。
そのときは、単純にnew Date()で取っていたので、なるほど! と思いましたw(すぐなおしました)
あと驚いたのは、みなさん結構ソースコード見るんですね…
いたずら心で入れたコメントのアスキーアートが話題になっててびっくりしましたw
いまはそれぞれが自分に出来る日常を生きるのが一番だと思っています。
ぼくは面白いものを作って他人に見せて、笑わせたり楽しませたりするのが好きなので、そんな日常を目指してみました。
適度に節電して、適度に買い物にでかけ、適度に募金して、そういうふうに過ごせればいいなぁと思いました。
義援金など、Googleさんのところがまとまっていてみやすかったです
http://www.google.co.jp/intl/ja/crisisresponse/japanquake2011.html