カテゴリ: プログラミング

シアターオーブのスケジュールアラートをopen&shareします。

ざっくり言うと

  • シアターオーブ公演前後は普段より遙かに混雑する
  • 公演スケジュールを取得して、ircに通知するスクリプトを書いた
  • ヒカリエに入っている:D社にもニーズありそうだからOpen&Shareする

背景

去年10月から勤務地が渋谷ヒカリエになりました。渋谷駅直結で雨に濡れずにいけるので大変ありがたいのですが、ヒカリエには一つ頭を悩ませることがあります。

それはオフィス入り口が11Fにあるため、11Fまでは一般の利用者とバッティングしてしまいます。普段はそんなに気になることは無いんですが、11Fにはシアターオーブというシアターがあって、公演前後は普段より遙かに混雑します。残業後にバッティングすると、見事に心が折れます。

事前に公演スケジュールが解っていれば、時間をずらせば良いだけなので、怒りにまかせてシアターオーブの公演スケジュールを取得して、ircに通知するスクリプトを書いたところ、なかなか評判がよかった&同じビルに入っている、某:D社にもニーズあるんじゃないか?ということで、Open&Shareしようと思います。

コードについて

コードはgistに置いておきます。

構成としては、Web::Scraperでシアターオーブの公演スケジュール(http://theatre-orb.com/lineup/calendar/)から情報を取得してApp::Ikachanを経由してircに投げています。

開始時間の1時間前と、公演時間が解っていれば、公演終了30分前にircにポストします。開始時刻の方が早めにアラートだしているのは、開始時刻の30分前ぐらいが開場なので、少し早い時間に混雑するからです。公演終了の方は、公演終了10分前~終了時間15分後ぐらいが混雑する時間の様です。(実際観察した)

特殊な構成として、シアターオーブの公演スケジュールからは公演時間が取れないので、Webataデータを持つことにしました。手動メンテですが、そんなに公演の種類ないし、とりあえずの実装です。

シアターオーブに問い合わせしたら、スケジュールの方に乗せてくれそうな雰囲気でもあったので、そうなったらスクレイピングに切り替えます。

使い方はこんな感じで、cronなんかで10分おきに起動するのを想定しています。

 $ orb-rush-timer.pl -i [ikachanのurl] -c [ポストするチャンネル]
ex) orb-rush-timer.pl -i "http://ikachan.hackmylife.net/notice" -c "#orb"

注意点

ちなみに、幾つか注意点として、公演終了のアラートを投げるのは、公演時間が解っているときのみです。また、時間の比較にTime::Pieceを使っているのですが、古いバージョンだとタイムゾーン反映してくれなかったりするので、新しいバージョン(おそらく1.17以降)を使う必要があります。


[6/24 14:17追記]
toku_bassさんよりコメントでuse Time::Pieceでバージョン指定しては?とご意見頂きました。1.17以降で正常な動作を確認したので、バージョン指定を追加しました。ありがとうございます!


それでは快適なヒカリエライフを!



このエントリーをはてなブックマークに追加

WebサイトでiPhoneのユーザーエージェントを判定してスマートフォンに最適化されたページを表示したいってことありますよね。

簡単にググってみると"iPhone"という文字をフィルターすれば良いと出てきます。

ApacheでやるならBrowserMatchでこんな感じに書く感じでしょうか。

BrowserMatch "iPhone" lite

が、最近これだと駄目なケースに出会いました。

iPadに入れているfacebookアプリから、シェアされたリンクを辿ってWebViewで開くと、以下のようなユーザーエージェントで来ます。

Mozilla/5.0 (iPad; CPU OS 6_0_2 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Mobile/10A550 [FBAN/FBIOS;FBAV/5.3;FBBV/89182;FBDV/iPad2,5;FBMD/iPad;FBSN/iPhone OS;FBSV/6.0.2;FBSS/1; FBCR/;FBID/tablet;FBLC/ja_JP]

途中にiPhoneという文字列があるせいで、iPadで見てもスマートフォン向けのページに行ってしまいます。

これを回避するのには、以下のように前後を含めて上げれば良さそうです。

BrowserMatch "\(iPhone;" lite

これだけだと、以下の様なiPod touchのUAがヒットしなくなってしまうので

Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8G4 Safari/6533.18.5

iPodのケースも追加して

BrowserMatch "\(iPhone;" lite
BrowserMatch "\(iPod;" lite

とするのが良いようです。他にこのケース抜けてるよ!とかあれば教えてください。

このエントリーをはてなブックマークに追加

ブログ記事内のリンククリックイベントをトラップして、Google Analyticsに投げるみたいなのを書いてみたら、そこそこ面白かったのでご紹介

37
 

jQuery("div.article-body a[target='_blank']").filter(function(){
    var link = $(this).attr('href');
    return !(link.match(/^http\:\/\/livedoor.blogimg.jp\//));
}).click( function(index){
    var obj = $(this);
    var name = obj.attr('href');
    var title = obj.attr('title');
    var text = obj.text();
    var alt = obj.children().filter(":first").attr('alt');

    if ( text ) name = text;
    if ( title ) name = title + '/' + name;
    if ( alt ) name = alt;
    var entry_title = obj.parents('div.article-outer-3').children('div.article-header').children('div.article-title-outer').children('h2').children('a').text() || 'noname';

    _gaq.push(['_trackEvent', '記事内リンク', entry_title, name]);
});

割と要素がキメ打ちになってしまうのでそのまま使い回せるかは微妙だけど、これはlivedoorブログ公式のテンプレにあわせたものなので、livedoorブログならそのまま使えるかもしれないし、解る人ならちょっと変えれば使えるはず。

やっていることは記事のブロック見つけたら、そのブロック内のaタグにイベントを仕込んで行くというもの。target='_blank'を指定しているのは、内部リンクは無視するためだけど、微妙なんで、別になくても良い。

_gaq.pushしていくときに使うパラメータは「記事内リンク」(固定) > 記事タイトル > リンク名なのだけど、リンク名はalt > title + url > text要素の優先順位で決定しています。これはアフィリエイトの効果測定用に頼まれてつくったものなので、そのリンク要素の性質にかなり影響されてます。普通に考えれば、alt > text要素でもいいきもする。記事タイトルは取得が若干面倒で、ここの取り方がデザインに寄って結構かわるかも。

こんなスクリプトをonloadで仕込んでおくと、Analyticsにこんな感じでデータが蓄積されます。

02

これが中分類で記事毎の数値が見れる

21

詳細はこっちで。どのリンクがどれだけ押されているか見えます。

ちなみに、自分のブログでは使ってないけど、サイドカラムに広告はってるサイト向けのスクリプトのもあります。

jQuery("div.sidewrapper a").click( function(index){
    var obj = $(this);
    var name = obj.attr('href');
    var title = obj.attr('title');
    var text = obj.text();
    var alt = obj.children().filter(":first").attr('alt');
 
    if ( text ) name = text;
    if ( title ) name = title + '/' + name;
    if ( alt ) name = alt;
    var plugin_title = obj.parents('div.sidewrapper').children('div.sidetitlebody').children('div.sidetitle').text() || 'noname';
 
    _gaq.push(['_trackEvent', 'サイドメニュー', plugin_title, name]);
});

こんな感じでとれるはずです、livedoorブログならね!

このエントリーをはてなブックマークに追加

最近すっかりグラフ厨になってるんですが、ちょっとした解析ツールとかにグラフあったらいいなーなんて思う事ありますよね?

webアプリで使うならjsでグラフ書けると色々使い勝手がいいんですけど、毎回どのライブラリがいいかなー?と悩む訳ですよ。Highcharts.jsは格好いいけど、商用利用は有料だし・・しかも結構高いし・・

そんな中、「これでFAで良いんじゃないの?」ってのがあったので紹介してみます。

dygraphs.jsといいます。他にも幾つかライブラリを試してみたんですが、これはかなりシンプルに書けるわりに、高機能だったのが良かった。なかなか書き方の参考になりそうなところ見つからなかったので、幾つかサンプル残しておこうと思います。

一番シンプルなグラフ

こんな感じに書くだけです。データはCSV形式でシンプルな受け渡し。

<script type="text/javascript" src="dygraph-combined.js"></script>
<script type="text/javascript">
  container = document.getElementById("graphdiv");
  g = new Dygraph(container,
    "Date,Temperature\n" +
    "20121128,12\n" +
    "20121129,16\n" +
    "20121130,14\n" +
    "20121201,12\n" +
    "20121202,9\n" +
    "20121203,13\n" +
    "20121204,14\n"
  );
</script>

デフォルトでドラック&ドロップで範囲を絞ることができます。

複数行のケース

複数のグラフ書くのは、CSVに列追加するだけ。

<script type="text/javascript" src="dygraph-combined.js"></script>
<script type="text/javascript">
  container = document.getElementById("graphdiv");
  g = new Dygraph(container,
    "Date,Tokyo,NewYork\n" +
    "20121128,12,5\n" +
    "20121129,16,6\n" +
    "20121130,14,6\n" +
    "20121201,12,6\n" +
    "20121202,9,8\n" +
    "20121203,13,12\n" +
    "20121204,14,10\n"
  );
</script>

沢山のデータ表示

ベタに書かなくてもcsvファイルを指定することも可能

<script type="text/javascript" src="dygraph-combined.js"></script>
<script type="text/javascript">
  container = document.getElementById("graphdiv");
  g = new Dygraph(container,"graph3.csv");
</script>

使ったCSVはこんな感じです

Date,Data1,Data2,Data3,Data4,Data5,Data6,Data7,Data8,Data9,Data10
20121120,0,0,0,0,0,875,25,80,3035,0
20121121,0,2575,0,2950,0,1775,0,250,5900,4475
20121122,0,5725,0,3000,0,2000,0,145,9510,9350
20121123,0,4325,0,2525,0,2000,0,150,7735,5375
20121124,0,3750,0,2425,0,1600,1150,165,7330,5175
20121125,0,3075,0,2000,0,800,2125,925,6885,4800
20121126,0,2400,0,1400,0,675,1800,1485,6350,4275
20121127,0,2100,125,1525,0,2725,1250,1150,5950,3200
20121128,0,2475,1625,1125,0,2885,1350,1140,5725,2775
20121129,0,2525,1805,775,1510,2425,1650,820,5290,2575
20121130,560,2125,2050,800,3020,2650,975,880,5865,2900
20121201,975,1900,1840,850,2510,2525,700,1130,5320,2525
20121202,1000,1650,1810,750,2810,2325,1825,900,5330,2250
20121203,1200,1900,2020,700,2785,2575,1075,935,4690,2375
20121204,1075,2025,1860,700,2045,2425,1225,720,5065,2100

より複雑な例

上のケースだとデータ量が多すぎて視認性が悪いので、色々オプションを加えます

  1. グラフのハイライト
  2. クリック時のハイライト固定
  3. ラベルリストの外だし
  4. データのオン/オフスイッチ
  5. レンジセレクタ

色々触ったりクリックしたりすると感じが解ると思います。

若干コードが長くなりますが、一つ一つはシンプルです。CSVは上の例からヘッダ行とっただけ(ラベルの外だしするため)。

<script type="text/javascript" src="dygraph-combined.js"></script>
<script type="text/javascript">
  // クリック時に実行されるcallback、ハイライトを固定する
  var makeClickCallback = function(graph) {
    var isLocked = false;
    return function(ev) {
      if (isLocked) {
        graph.clearSelection();
        isLocked = false;
      } else {
        graph.setSelection(graph.getSelection(), graph.getHighlightSeries(), true);
        isLocked = true;
      }
    };
  };

  // グラフ描画ロジック
  container = document.getElementById("graphdiv");
  labels = document.getElementById("labels");
  labels_arr = ['Date','Data1','Data2','Data3','Data4','Data5','Data6','Data7','Data8','Data9','Data10'];
  g = new Dygraph(
    container,
    "graph4.csv",
    {
       labels: labels_arr, // ラベル名指定
       labelsDiv: labels,  // ラベル表示位置
       showRangeSelector: true, // 表示範囲セレクターを表示
       labelsSeparateLines: true, // ラベルの後ろに改行挟む
       highlightSeriesOpts: {  // ハイライトの設定
         strokeWidth: 3,
         strokeBorderWidth: 1,
         highlightCircleSize: 5
       }
  });
  // クリック時のcallback指定
  g.updateOptions({clickCallback: makeClickCallback(g)}, true);

  // 表示非表示切り替え
  function change(el) {
    g.setVisibility(parseInt(el.id), el.checked);
  }
</script>

コールバックや表示切り替え部分はそのまま流用できるし、その他はオプションだけで設定できます。ライブラリ自体がかなり大きくて、他にも色々な機能があるのでデモを見ると解ると思います。ここで紹介した以外に、グラフの特定箇所に記しをつけるannotationなんかを使っています。

デモが幾つか分散していて、ギャラリータイプのと独立しているものがあります。お目当てを探すのが面倒なのですが、始めにざっと見て何ができるか把握すると良さそうです。

オススメです。

このエントリーをはてなブックマークに追加

IPhoneやらAndroidで取った写真のExifをみて画像を回転させるのをたまーに実装するんですが、滅多にすることなくて、やる度に仕様調べないと思い出せないので、ブログに書いておこうと思います。

画像をどう回転させれば良いかは写真についてくるAPP1セグメントに定義されているTIFFフォーマットの0thIFDにあるOrientationタグみると解ります。

*5と7が逆だったのをブコメで指摘頂いて訂正しました。下記のpdfドキュメントに誤りがあったので修正版に差し替えています(2013/9/2)

まぁちゃんと理解はしてないんですけど、仕様はCIPAにあるhttp://www.cipa.jp/hyoujunka/kikaku/pdf/DC-008-2010_J.pdfhttp://www.cipa.jp/hyoujunka/kikaku/pdf/DC-008-2012_J.pdfに定義されています。これみてもいまいち解らないので、図解してあるこっちが解り易かったです。

GPS情報もExifから取れるので、必要な方は仕様をみてみると良いと思いますが、すでにライブラリ沢山あると思うので読まなくても大丈夫だと思います。

で、このOrientationタグの中身が数値で、どれがどれだかすぐに忘れます。

Orientation=1: そのまま
Orientation=2: 水平反転
Orientation=3: 180度回転
Orientation=4: 180度回転て水平反転
Orientation=5: 時計回りに90度回転して水平反転
Orientation=6: 時計回りに90度回転
Orientation=7: 時計回りに270度回転して水平反転
Orientation=8: 時計回りに270度回転

図にしたのが上にはったpdfにかいてあったので抜粋(2013/9/2差し替え)

43

これをimage magicのconvertコマンドでやると、こんな感じになります。

Orientation=1: そのまま
Orientation=2: convert -flop original.jpg flop.jpg
Orientation=3: convert -rotate 180 original.jpg rotate180.jpg
Orientation=4: convert -rotate 180 -flop original.jpg rotate180-flop.jpg または convert -flip original.jpg flip.jpg
Orientation=5: convert -rotate 90 -flop original.jpg rotate90-flop.jpg または convert -transpose original.jpg transpose.jpg
Orientation=6: convert -rotate 90 original.jpg rotate90.jpg
Orientation=7: convert -rotate 270 -flop original.jpg rotate270-flop.jpg または convert -transverse original.jpg transverse.jpg
Orientation=8: convert -rotate 270 original.jpg rotate270.jpg

transposeとtransverseはちょっと解り辛いですね。最初5と7を取り違えちゃいました。

実際に画像でやってみたほうが解りやすそうなので、先日コンプしたLINEシールの画像つかって試して見ました(これ使うと四隅が解りやすい)

1 6 3 8
original
rotate90
rotate180
rotate270
2 5 4 7
flop
rotate90-flop
rotate180-flop
rotate270-flop
となります。とはいっても、下段の水平反転系はほとんど見たことありません。水平反転ってどうとったらなるんでしょうね?

最後にPerlでの実装例の一部を乗っけておきます。

こっちは、ImageMagicで変換するパターン

if (substr($org_data, 0, 2) eq "\xFF\xD8") {   # if jpeg
    my $img_meta = Image::MetaData::JPEG->new(\$org_data);
    my $exif_image_data = $img_meta->get_Exif_data('IMAGE_DATA', 'TEXTUAL');
    $jpeg_orient = ($exif_image_data ? $exif_image_data->{Orientation}->[0] : 1);
    if ($jpeg_orient == 2) {
        $img->Flop();
    } elsif ($jpeg_orient == 3) {
        $img->Rotate(180);
    } elsif ($jpeg_orient == 4) {
        $img->Flip();
    } elsif ($jpeg_orient == 5) {
        $img->Transpose();
    } elsif ($jpeg_orient == 6) {
        $img->Rotate(90);
    } elsif ($jpeg_orient == 7) {
        $img->Transverse();
    } elsif ($jpeg_orient == 8) {
        $img->Rotate(270);
    }
}

もう一つImagerを使ったケース。やっている事は変わりません

if (substr($image, 0, 2) eq "\xFF\xD8") { # if jpeg
    my $img_meta = new Image::MetaData::JPEG( \$image ) or return $im;
    my $im = Imager->new;
    $im->read(data => $image) or return undef;
    my $exif_data = $img_meta->get_Exif_data('IMAGE_DATA', 'TEXTUAL');
    my $jpeg_orient = ($exif_data ? $exif_data->{Orientation}->[0] : 1);
    if ( $jpeg_orient == 2 ) {
        $im->flip(dir=>"h");
    } elsif ( $jpeg_orient == 3 ) {
        $im = $im->rotate(right=>180);
    } elsif ( $jpeg_orient == 4 ) {
        $im->flip(dir=>"v");
    } elsif ( $jpeg_orient == 5 ) {
        $im = $im->rotate(right=>90);
        $im->flip(dir=>"v");
    } elsif ( $jpeg_orient == 6 ) {
        $im = $im->rotate(right=>90);
    } elsif ( $jpeg_orient == 7 ) {
        $im = $im->rotate(right=>270);
        $im->flip(dir=>"v");
    } elsif ( $jpeg_orient == 8 ) {
        $im = $im->rotate(right=>270);
    }
} 
このエントリーをはてなブックマークに追加

↑このページのトップヘ