Webアプリを創る

気象庁 XML を取得するサンプルを PHP から C# にしてみた

2014年2月21日

自分は、2008年の後半から2009年の前半にかけて、Web を作成するのに、PHP を使うのか ASP.NET で C# を使うべきなのか、それとも Java を使うのか迷った時期がありました。ASP.NET MVC がリリースされ、また、CMS も Umbraco が使えるということで最終的には C# を使うということにしました。

気象庁が提供する天気予報、気象警報、地震、津波情報などの気象庁 XML の取得については、PubSubHubbub の受信サーバーを用意する必要があります。自分でも受信サーバーをたてていて、最近C#で天気予報を取得するプログラムを書き始めています。気象庁の資料をみていると PHP のサンプルコードが、平成25年3月に開催された気象庁XML利活用セミナー気象庁XMLを入手しようにあったので、そのサンプルコードを C# を使って書いてみました。

資料のサンプルコードでシンプルな例は以下のとおりです。

<?php 
$method = $_SERVER['REQUEST_METHOD'];

// subscribe (or unsubscribe) a feed from the HUB 
if ($method == 'GET') { 
  $hubmode = $_REQUEST['hub_mode']; 
  $hubchallenge = $_REQUEST['hub_challenge']; 
  if ($hubmode == 'subscribe' || $hubmode == 'unsubscribe')
    // response a challenge code to the HUB
    header('HTTP/1.1 200 "OK"', null, 200);
    header('Content-Type:text/plain');
    echo $hubchallenge;
  }else{
    header('HTTP/1.1 404 "Not Found"', null, 404)
  }
}

// receive a feed from the HUB
if ($method == 'POST') {
  // feed Receive
  $string = file_get_contents("php://input");
  // feed save
  $fp = fopen(dateDateTime.UtcNow.ToString('YmdHis') . "_atom" . ".xml", "w");
  fwrite($fp, $string);
  fclose($fp);
?>

これと同じものを、ASP.NET MVC の C#で書いた例です。

using System;
using System.IO;
using System.Web.Mvc;
using System.Xml;

namespace PuSH.Controllers
{
  public class SubscriberController : Controller
  {
    // subscribe (or unsubscribe) a feed from the HUB
    [HttpGet]
    public ActionResult Index()
    {
      string hubMode = Request.QueryString["hub.mode"];
      string hubchallenge = Request.QueryString["hub.challenge"];
      if (hubMode == "subscribe" || hubMode == "unsubscribe")
        // response a challenge code to the HUB
        return Content(Request.QueryString["hub.challenge"]);
      else
        return NotFound();
    }

    // receive a feed from the HUB
    [HttpPost]
    [ActionName("Index")]
    public ActionResult IndexPost()
    {
      // feed Receive
      var reader = new StreamReader(Request.InputStream);
      string str = reader.ReadToEnd();
      // feed save
      using(var sw = new StreamWriter(DateTime.Now.ToString("yyyyMMddHHmmss) + "_atom.xml"))
      {
        sw.Write(str);
      }
      return Content("");
    }
  }
}

このプログラムだとは、PHP も C#はよく似たものだと思います。C#の方が型の宣言をしないといけないのと、関数名やプロパティ名が長いので、記述量が多くなるように見えますが、型宣言に var が使えてコンパイラーが自動で型を決めてくれるし、関数名等で名前が長いのはVSが補間してくれるので、入力が特に面倒だということはありません。

次に POST処理の部分を、変更したサンプルが紹介されています。コードは以下のとおりです。

// receive a feed from the HUB
if ($method == 'POST') {
  // feed Receive
  $string = file_get_contents("php://input");
  //feed Parse & XML GET
  if(FALSE === ($feed = simplexml_load_string($string)))
  {
    exit("feed Parse ERROR");
  }
  foreach($feed->entry as $entry)
  {
    $url = $entry->link['href'];
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, 60);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    $fp = fopen(basename($url), "w");
    curl_setopt($ch, CURLOPT_FILE, $fp);
    curl_exec($ch);
    curl_close($ch);
    fclose($fp);
  }
}
?>

これを単純に ASP.NET MVC の C# に変更するだけだったら面白くないので、自分が現在使っているルーチンを紹介したいと思います。

追加と変更部分のみ
using System.ServiceModel.Syndication;
using Nlog;

    private static Logger logger = LogManager.GetCurrentClassLogger();

    [HttpPost]
    [ActionName("Index")]
    public ActionResult IndexPost()
    {
      using (XmlReader xmlReader = new XmlTextReader(Request.InputStream))
      {
        try
        {
          var feed = SyndicationFeed.Load(xmlReader);
          if (feed != null)
          {
            string feedtype = "e"; //定時:regular、随時:extra、地震火山:eqvol、その他:other
            foreach (var link in feed.Links)
            {
              if (link.RelationshipType == "self")
              {
                var uris = link.Uri.Segments;
                feedtype = uris[uris.Length - 1].Split('.')[0]; 
              }
            }
            var xmlWriter = XmlWriter.Create(
              Server.MapPath("~/App_Data/feed/") + feedtype + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + ".xml");
            feed.SaveAsRss20(xmlWriter);
            xmlWriter.Close();
                    
            logger.Info("ID:{0} Update:{1} Type:{2}", feed.Id, feed.LastUpdatedTime, feedtype);
            foreach (var item in feed.Items)
            {
              string title = item.Title.Text;
              string name = item.Authors[0].Name;
              string updatetime = item.LastUpdatedTime.ToString();
              foreach (var link in item.Links)
              {
                logger.Info("{0} {1} Update:{2} uri:{3}", title, name, updatetime, link.Uri);
                switch (title)
                {
                  case "府県週間天気予報":
                    処理
                    break;
                  case "府県天気予報":
                    処理
                    break;
                }
              }
            }
          }
        }
        catch(Exception e1)
        {
          logger.Error("Feed Error:" + e1.Message);
        }
      }
      return Content("");
    }

ログの記録には、NLog を使っています。まだプログラムを始めたばかりなので、ログを取ったり、RSSをファイルに保存したりしていますが、最終的には、ログやファイルへの保存は最低限にしていこうと思っています。ログを取ってみると、気象庁 XML は、1分間に1回処理が動いてフィードが飛んでくることがわかります。ここで最大で約1分間の遅延があること、また、たまにフィードの送信が遅延することがあるので数分間程度のタイムラグがあると思った方がいいということが分かります。

実際にはプログラムができたら、気象庁への登録申請が必要になりますが、登録申請の前にきちんとテストをしておきましょう。Google のハブを使ったテストの方法は、気象庁の電文公開の仕組みのページの情報提供に係る仕様とSubscriberの構築についてというPDFファイルに記載があります。

この例からわかるように Web だけの話だったら、PHP はとてもいい言語だと思います。でも、PHP は Web以外で動かそうと思ったら結構大変です。Web 用のオープンソースのアプリケーションは非常に多いのですが、自分は Web 以外でもプログラムをしたかったので PHP は捨てました。C#は Web の世界でも、PHP に決して劣っているわけではありません。そして、多くのデバイスで動作することが魅力です。Android/iPhone のネイティブアプリが作れる Xamarin に期待しているので、時間ができれば Xamarin も使ってみたいと思います。

Intel D510MO + AOpen S110 自宅サーバー ハードディスクが故障

2014年2月9日

2010年4月から使ってきた自宅サーバーが起動しなくなった。ハードディスクの故障と思われるけど、HDDは、2.5インチ SATA 250GB HTS545025B9A300 なので、24時間稼働で4年近くも故障せずによく動いてくれたと思っている。Intel NUC D54250WYK を購入して自宅サーバーを立ち上げたばかりなので、しばらくは Intel NUC の方だけで作業しようと思っている。Intel NUC の方は今のところ快調に動作しているし、データも基本的にはそちらに移していたので故障してもあまり問題はなかったのはよかった。故障した Intel D510MO の自宅サーバーは、しばらくして時間ができたらハードディスクの交換をしてみようと思っている。

Web アプリをスマフォのホーム画面に登録してもらうためのメモ 1

2014年2月6日

Web アプリでもネイティブアプリのように iPhone や Android のホーム画面に登録できるし、オフラインで動作させることもできるということなので、実際にやってみて、まとめてみました。

まず、一番最初に準備するのがホーム画面に登録するためのアイコンで、そのサイズは以下のとおりです。

  • iPhone (iOS7) 120px × 120px
  • iPhone(iPhone 4以降でiOS6まで)114px × 114px
  • iPhone(iPhone 3 まで)57px × 57px
  • iPad (iOS7)152px × 152px 
  • iPad (iOS6までのRetina)144px × 144px
  • iPad (iOS6までの普通)72px × 72px
  • Android 48dp × 48dp

Android の場合は、Developers の Iconography をみると1x、1.5x、2x、3x、4x の機種があるので、48px, 72px, 96px, 144px, 192px のものになりますが、4x の XXXHDPi の機種は現実にはまだ殆ど存在しないはずで、3x と 2x が主流です。

これだけたくさんの解像度のアイコンを作るのは大変なので、取りあえずは152pxのアイコンを作っておけば、スマートフォン側で自動的に縮小、拡大してくれるので、それで対応することにしました。

作成したアイコンを表示させるためには、iPhone の場合は、それをPNG画像にして apple-touch-icon.png というファイル名でサイトのルートディレクトリに置いておけばいいだけです。apple-touch-icon-precomposed.png という名前で保存してもいいのですが、この場合は iPhone が自動でハイライト/シャドウを付けなくなります。iOS7からはフラットデザインになってiOS6以前のようなハイライト/シャドウはなくなったので、apple-touch-icon-precomposed.png を使う意味はあまりないと思います。

サイズを複数設置する場合は以下のようにアイコンのサイズを指定して設置すると、iPhone側で自動的に適当なサイズのものを選択してくれます。

apple-touch-icon-120x120.png

Android の場合は、ファイルを設置しただけでは認識してくれないので、以下のように link ダグで指定してやります。この指定の方法は、iOSの場合と同じです。

<link rel="apple-touch-icon" href="apple-touch-icon.png">

アイコンのサイズを指定したい場合は、以下のように sizes プロパティで指定することができます。この指定の方法も、iOSの場合と同じです。

<link rel="apple-touch-icon-precomposed" sizes="120x120" href="apple-touch-icon-120x120.png">

以上で、スマートフォンのホーム画面にアイコンを貼り付けることができるようになります。スマートフォン側でホーム画面にアイコンを貼り付ける操作をどうするかについては省略します。

Safari のスタンドアローンモード(フルスクリーンモード)について

Safari では、スタンドアローンモード(フルスクリーンモード、Webアプリモード)と呼ばれているモードがあります。このモードに設定して、ホーム画面から起動するとURLバーが無くなってネイティブアプリに近くなります。

設定は、metaタグを使って以下のようにします。

<meta name="apple-mobile-web-app-capable" content="yes" />

また、以下の設定をすると下側にある Safari のステイタスバーの色を自分のアプリの色に合わせて変更することができます。

<meta name="apple-mobile-web-app-status-bar-style" content="black">

しかし、このモードを実際に使ってみて、注意が必要だと思われる点がありました。具体的には以下のとおりです。

1. localStorage が mobile Safari とは別になってしまう。

mobile Safari で設定した内容が使えなくなるので、結構不便だと思いました。

2. 普通のリンクだと mobile Safari が起動してしまう

普通にリンクすると mobile Safari が起動して、mobile Safari の該当のページが表示されます。だから、最早スタンドアローンモードはなくなってしまって、簡単に戻ることもできなくなります。これを防ぐためには、リンクをページ内リンクにするか、JavaScript での移動にします。

下の参考に記載した stackoverflow の Q&A によると下のようなスクリプトを書いてやればいいようです。

var a=document.getElementsByTagName("a");
for(var i=0;i<a.length;i++)
{
a[i].onclick=function()
{
window.location='this.getAttribute("href");
return false
}
}

http://d.hatena.ne.jp/izit_kosuke/20110219/1298073430

http://stackoverflow.com/questions/2898740/iphone-safari-web-app-opens-links-in-new-window

3. 処理速度が遅くなる

スタンドアローンモードにすると、どうも起動時間が遅くなるように感じます。stackoverflow の Q&A にもあって、セキュリティの関係で Nitro という高速のJavaScriptエンジンが使えなくなるためのようです。

http://stackoverflow.com/questions/13413984/slow-initial-loadingscreen-ios-web-app

Android の場合にどうなるかを Nexus 5 で試してみました。Nexus 5 の場合は、localStorage は同じものを使います。普通のリンクにしておいてもモードが変わることはありません。その代わり、普通のリンクでは外部リンクであっても全画面表示のままになってしまいます。そして、ホーム画面のアイコンから立ち上げた場合、登録したページが表示されるのではなく、直近に表示されていたページがそのまま表示されます。

スタンドアローンモードを使うと見かけはアプリらしくなるのですが、採用するのがいいのかどうかは結構微妙です。localStorage が mobile Safari とは別になってしまうというのは、利用者にとっては結構戸惑うと思います。自分の場合は、アプリとしての完成度が低いので、当面はスタンドアローンモードを使わないことにしました。

次回は、Web アプリをオフラインで動作させることに挑戦したいと思っています。

Haswell版Core i5を搭載したIntel NUC D54250WYK を自宅サーバーにしました

2014年2月3日

NUC の本命といわれている Haswell版Core i5 4250U を搭載した Intel NUC D54250WYK を購入して自宅サーバーにしました。Haswell版CPUは低消費電力で発熱を押さえることができるので小さな筐体のNUCでも問題は少ないのではないかと思って購入しました。

どの程度の期間故障せずに使えるかは運の要素が強いのですが、2010年4月に購入した Intel D510MO+ケースAOpen S110 の自宅サーバーは3年半を経過しても利用できています。このことからNUCもある程度の実用性はあると思っています。

自宅サーバーの D54250WYK の構成は以下の通りです。

本体 D54250WYB 46,980円
mSATA SSD 240GB プレクスター PX-256M5M 22,980円
メモリー 8GB Kingston DDR3-1600 SODIMM 1.35V  8,655円
FILCO 直角ケーブル3号 482円
PLANEX Mini Displayport - DVI端子変換アダプタ 1,318円
Microsoft Windows 8.1 Pro (DSP版) 64bit 15,981円

メモリーはノート用で、1.35V のものしか動作しないので注意が必要です。また、ディスプレイへの接続は、miniHDMI と miniDisplayPort の端子が装備されているので、Mac用の変換ケーブルを購入しました。

合計で96,396円でした。結構な値段になりますが、Mac Book Air 及び Surface Pro 2 とほぼ同じスペックで、ディスプレイがない分やや安価になっているので、まあ妥当な価格だと思っています。実際のところ、安価なノートパソコンよりはずっと早く動作します。

性能がどれぐらい向上しているかということで cpuboss を見てみると、PassMark スコアで旧の D510 の約5倍早くなっています。

写真の上側が D54250WYK で、下側が 2010年4月に購入した Intel D510MO+AOpen S110 ケースのものです。写真を見るとD54250WYK の小ささがよく分かると思います。

IntelNuc 

Webサーバーについては、Windows Azure と AWS にすべて移したため、自宅サーバーは、現在はWebサーバーとしては利用していません。しかし、次世代統計利用システムの統計データAPIのようなオープンデータを利用する場合、処理が複雑で非常に重くなるものがあります。そういう処理を自宅サーバーでさせようと思っています。D54250WYK であれば、自宅でも 10台ぐらい動作させても消費電力や設置場所の問題はありません。そういう意味では、個人でもものすごい計算能力が簡単に使える時代になったなと実感します。

なお、D54250WYK は、実際には11月末に購入して、12月の中旬には動作させていたのですが、忙しくてメモをするのが遅れてしまいました。また、D54250WYK は結構人気で品薄のようです。

 

Linked Open Data を使って自治体コード表を作ってみた

2014年1月28日

前回のブログでは、次世代統計利用システムで都道府県・市区町村コード情報が LOD(Linked Open Data)で提供されるようになったので、実際に使ってみてその使い方をメモした。今回は、その LOD を使って、実際に自治体コード表(地方公共団体コード表)を作ってみた。(公開している場所 http://ecitizen.jp/sac

自治体コード表をLODを使って作ってみて感じたことは、LODはプログラマーにとっては確かに便利だと思う。でも、現在の政府統計窓口では、市町村コード表や廃置分合等情報がダウンロードできるようになっている。それを使ってプログラムする場合とLODを使ってプログラムをする場合の差はあまりないと思う。結局は、LODを使ってもデータの内容を把握しないとプログラムはつくれない。オープンデータの5つ星がいわれているが、自分の経験からいえば、星が0と1の間はものすごく大きいが、星1つと星5つの差はそれと比べるとかなり小さく、星5つというよりは星1.5だというのが正直な感想である。

以下は具体的にどうしたかをメモしておく。まず、自治体コード表を作るため、前回作成した埼玉県の市町村一覧を選択するクエリーを変更して、SELECT に「ふりがな表記」と「自治体コードの6桁目であるチェックディジットコード」を追加し、FILTERに「特別区」及び「政令指定都市の区」を追加して表示されるようにした。変更後のクエリーは以下のとおりである。

SELECT ?s ?name ?kana ?cd WHERE { 
  GRAPH ?g { ?s ?p ?o .
    ?s rdf:type sacs:CurrentStandardAreaCode.
    {{
      ?o dcterms:isPartOf sac:C11000-19700401.
    }UNION{
      ?o dcterms:isPartOf ?district.
      ?district dcterms:isPartOf sac:C11000-19700401.
    }}
    ?o sacs:administrativeClass ?ad.
    ?o rdfs:label ?name.
    ?o rdfs:label ?kana.
    ?o sacs:checkDigit ?cd.
  }
  FILTER( lang(?name) = "ja")
FILTER( lang(?kana) = "ja-hrkt") FILTER(?ad = sacs:DesignatedCity || ?ad = sacs:CoreCity || ?ad = sacs:SpecialCity || ?ad = sacs:SpecialWard || ?ad = sacs:Ward || ?ad = sacs:City || ?ad = sacs:Town || ?ad = sacs:Village) }

そのクエリーをベースにして、JavaScript を使ってWebブラウザーで自治体コードが表示されるようにしてみた。期限付きの都道府県コードは、CXX000-19700401で調べた範囲ではすべての都道府県が大丈夫のようだ。沖縄県の場合は1970年4月1日現在では琉球政府の時代で日本復帰は1972年5月15日であるが、C47000-19700401 となっていた。なお、沖縄県にある市町村の方の期限付き標準地域コードは1972年5月15日以降の分しかない。

このクエリーを処理する JavaScript のルーティンを書くことはそれほど難しいことはない。注意する点は、ブラウザーでの処理には、クロスドメインの制限があるということである。そのため、使うプロトコルは JSONP になるので、データとして jsonp/callback を送信する必要がある。また、その値がコールバックルーティンの名前なので、jQuery の場合であれば jsonpCallback にその名前を設定してやる必要がある。

なお、ソートは本来はクエリー側ですべきで、ORDER BY ?s をクエリーの最後につけてやればコード順のソートが出来るはずなのだが、理由はよくわからないが動作させられなかったので、JavaScript 側の方でソートをしている。

function makeTable(sacQuery){
  $.ajax({  
        url: http://statdb.nstac.go.jp/lod/sparql?,
        data: {
            query: sacQuery,
            output: "json",
            jsonp: "sacsac"
        },
        jsonpCallback: 'sacsac',
        dataType: "jsonp"
    })
    .done(function (data) {
        var mn = data.results.bindings;
        mn.sort(function (a, b) {
            if (a.s.value < b.s.value)
                return -1
            else
                return 1;
        });
        var html = "<table class=\"sactable\">";
        html += "<tr><th>団体コード</th><th>団体名</th><th>ふりがな</th></tr>";
        for (var n = 0; n < mn.length; n++) {
            html += "<tr><td>" + mn[n].s.value.substring(mn[n].s.value.length - 5) + mn[n].cd.value + "</td><td>" +
mn[n].name.value + "</td><td>" + mn[n].kana.value + "</td></tr>";
        }
        html += "</table>";
        return html;
    });
}

これで動作させてみると、下の図のように政令指定都市の区が二重になった。なぜか調べると、政令指定都市の区は dcterms:isPartOf で複数の期限付きの政令指定都市のコードを持つ場合があるということのためのようだ。

image

それで、期限付きの政令指定都市コードのうち現在の政令指定都市コードを選択してやらないといけないということで、ここでは、少し処理を簡単にするため、廃止年月日のない政令指定都市コードを選択することにした。

SPARQL 1.1 だと、dcterms:valid 値がないレコードを取得するののは NOT EXISTS を使えば簡単に記述できるが、NOT EXISTS は動作しなかった。それで、以下のように SPARQL 1.0 で記述してみると動作した。

SELECT ?s ?name ?kana ?cd WHERE { 
  GRAPH ?g { ?s ?p ?o .
    ?s rdf:type sacs:CurrentStandardAreaCode.
    {{
      ?o dcterms:isPartOf sac:C11000-19700401.
    }UNION{
      ?o dcterms:isPartOf ?district.
      ?district dcterms:isPartOf sac:C11000-19700401.
      OPTIONAL {?district dcterms:valid ?valid.}
    }}
    ?o sacs:administrativeClass ?ad.
    ?o rdfs:label ?name.
?o rdfs:label ?kana. ?o sacs:checkDigit ?cd.
}
FILTER( lang(?name) = "ja")
FILTER( lang(?kana) = "ja-hrkt") FILTER(?ad = sacs:DesignatedCity || ?ad = sacs:CoreCity || ?ad = sacs:SpecialCity || ?ad = sacs:SpecialWard || ?ad = sacs:Ward || ?ad = sacs:City || ?ad = sacs:Town || ?ad = sacs:Village)
FILTER (!BOUND(?valid))
}

次世代統計利用システムの都道府県・市区町村コード情報は、SPARQL 1.0 で動作しているように思われる。これから使うシステムなので、NOT EXISTS や MINUS が使える SPARQL 1.1 で動作させて欲しいと思う。

今回作った Webページは、統計メモ帳の以下にアドレスに公開しておきます。

http://ecitizen.jp/sac

もう少し手間をかければ、e-stat の「標準地域コードを探す」というページに近いものが作れると思っている。しかし、レスポンスや次世代統計利用システムのサーバー側の負荷を考えると、SPARQL を直接使うのはどうかなと思っていて、自分のサーバーで、データのキャッシュ等をして負荷の軽減をすべきだと思っている。それで、次はサーバー側での SPARQL の処理を試してみようと思っている。C#には、dotNetRDF というオープンスースの RDF 関連のライブラリーもあるので、そのライブラリーも試してみたいと思っている。