Webアプリを創る 😊クリエイティブ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 も使ってみたいと思います。

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 関連のライブラリーもあるので、そのライブラリーも試してみたいと思っている。

市区町村コード情報がLODで公開されたので使ってみた

2014年1月25日

次世代統計利用システムで、都道府県・市区町村コード情報が LOD(Linked Open Data)で提供されるようになったので使ってみた。使い方を少しメモしておく。

まず、SPARQLエンドポイントが用意されているので、そのページに行くと下の図のように画面でも検索できるようになっているので、この画面でSPARQLを試してみた。

image

?s ?p ?o というのは、?s(Subject 主語)、?p(Property 述語)、?o(Object 目的語)で、tuple ステートメントというようです。そのままで、Show results inline: にチェックをして、「Send Query」ボタンをクリックすると以下のように情報が表示される。

image

次に市区町村コードから市町村名等が表示されるか試してみた。?s を埼玉県川越市の市区町村コード11201に以下のように置き換えてクエリーしてみた。なお、sac:C11201は、<http://statdb.nstac.go.jp/lod/sac/C11201>の省略形であり、Prefex が登録されているので簡単に記述可能である。sac:C11201 の代わりに、<http://statdb.nstac.go.jp/lod/sac/C11201>と入力しても同じ結果になる。

SELECT * WHERE { 
  GRAPH ?g { sac:C11201 ?p ?o . }
}

クエリーの結果は、プロパティとしては、rdf:type と owl:sameAs のみを持つていて、owl:sameAs プロパティの結果として期間付き標準地域コード sac:C11201-20030401 が返されるだけで、市町村名等は取得できない。ここで取得した期間付き標準地域コードのプロパティを取得する必要がある。そこでクエリーを次のように変更して実行してみた。

SELECT * WHERE { 
  GRAPH ?g { sac:C11201 ?p ?o . ?o ?a ?b. }
}

こうするとマニュアルにある期限付き標準地域コード・クラスのプリパティの一覧が表示され、その中に川越市という名前も表示される。プロパティのうち、dcterms:valid は廃止年月日のプロパティなので、現時点では値がない。その場合には、RDBのようにNULLとかが表示されるのではなく、プロパティには何も表示されない。それでは市町村名だけを表示するのはどうしたらいいかというとプロパティを以下のように市町村の日本語表記である rdfs:label@js 等に指定すればよい。

SELECT ?b WHERE { 
  GRAPH ?g { sac:C11201 owl:sameAs ?o . ?o rdfs:label ?b. } FILTER( lang(?b) = "ja")
}

なお、マニュアルでは、日本語表記のプロパティは、rdfs:label@ja となっているが、そのままでは動作せずFILTER を使う必要があるようだ。

次に、埼玉県川越市の市区町村コードを取得してみる。一番簡単なクエリーは以下になる。

SELECT * WHERE { 
  GRAPH ?g { ?s ?p "川越市" . }
}

これで、?s に川越市の期間付き標準地域コードの一覧が表示されるので、一応 11201 ということがわかる。もう少し厳密に現在の川越市の市区町村コードをクエリーで得る例は以下のとおり。

SELECT * WHERE { 
  GRAPH ?g { ?s ?p ?o . ?s rdf:type sacs:CurrentStandardAreaCode. ?o rdfs:label "川越市". }
}

次に埼玉県の現在の市町村の一覧を取得してみる。そのコード例は以下のとおり。

SELECT ?s ?name 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. } FILTER( lang(?name) = "ja")
FILTER( ?ad = sacs:DesignatedCity || ?ad = sacs:CoreCity || ?ad = sacs:SpecialCity || ?ad = sacs:City || ?ad = sacs:Town || ?ad = sacs:Village )
}

埼玉県の1970年4月1日以降のどの日の市町村一覧とか市町村がどのように合併をしてきたか等をSPARQLを使えば取得可能である。そういう点では、LODは非常に便利である。自分の場合は、市町村の合併等を考慮して統計数字を比較したいと思っているので、都道府県・市区町村コード情報の LODを使いたいと思っている。

しかし、普通の人がSPARQLを使うのは、はっきりいって難しすぎるし、少し複雑なことをしようと思えばSPARQLがどんどん複雑になってしまう。今のSPARQLだとオープンデータの専門家のおもちゃにすぎないと思う。オープンデータでLODの評価が非常に高くなっているが、それは一部の専門家の評価であって、普通の人が使えるようなソフトウェアを作らなければLODの評価は過大評価だと思う。

 

政府統計データ API で公開されている統計表のリストを作ってみた

2013年6月28日

総務省統計局と統計センターによって、次世代統計利用システム(API機能)の試行運用が6月10日から開始されました。自分も統計データAPIを使ってアプリケーションを作ろうと思っています。

ところで、統計APIの関係では「統計くん」というWebサービスがメディアに取り上げられています。例えばITmedia ニュースによると「個人開発者の矢野さとるさんは6月12日、10日に公開された「次世代統計利用システム」のAPIを活用し、国勢調査などの政府が持つ統計データをCSV形式でダウンロードできるWebサービス「統計くん」を公開した。」というような記事が掲載されています。

その「統計くん」を使ってみると、データの一覧には自分の欲しいデータがありませんでした。そのため今回のAPIの公開がかなり暫定的なものなのかと思ってしまいました。でも実際に調べていくと「統計くん」では統計APIで公開されている統計表のごく一部しか扱っていないことがわかりました。

自分で使うためにも、どういう統計データが公開されているかを知りたかったので、APIで公開されている統計表の一覧表を作成してみました。まだまだ未完成ですが、統計APIを使ってみようと思っている人には役に立つと思うので ecitizen.jp の方に「政府統計データAPI エクスプローラ」として公開してみました。