miyupaca log

2020-10-10 yps学習記録その7〜本編〜

2020/10/10

LaravelでJSライブラリを使って可視化アプリを作る課題です。
前回の準備編を投稿してからだいぶ経ってしまいました。
なんだかんだで三週間くらいかけてうーんうーんと言いながらやっていた気がします。


やったこと

  • Laravelで可視化アプリを作る

    • 外部APIでデータを取得する
    • 取得したデータをGio.jsで利用する
    • ビューの調整、制御の追加など仕上げ

今回のお品書き

これは言い訳なのですが、中々実装直後にブログを書く手が動かず、細かい部分は忘れてるかもしれません。なので読んで試すには再現性は低いと思いますが、どんな感じのことをやったのかざっくり記そうと思います。


外部APIでデータを取得する

今回、貿易の輸入出データを可視化しようと思い、結構時間がかかりつつ辿り着いたのはここ。
RESAS-API - 地域経済分析システム(RESAS)のAPI提供情報
世界というより、日本の地域に関連するデータが中心の提供のようだけど、日本対世界の輸出入データもあったので利用させてもらった。

APIを利用するってまずどうすれば。。。?という何もかもわからない状態だったので調べていたら、Guzzleというものがあることを知ったので今回はこちらを利用することに。
今時のPHP HTTPクライアントのGuzzleを使ってみた - Qiita
LaravelでHTTP通信 | RE:ENGINES

composer require guzzlehttp/guzzle

でLaravelプロジェクトに導入できる。

ドキュメントでも紹介されている。
HTTPクライアント 7.x Laravel

あとは同じAPIを利用していた記事も参考にさせてもらった。
RESASで市の成績を出してみたら掛川市の人口は偏差値50.5 | 深ノオト

とにかくAPI?????となっていたのでググりまくりながら進めた。

コントローラ内にデータをGETしてくるアクションを作成する。
以下は輸出のデータをとってくるアクション。ベタ貼り。

public function gioExportData(Request $request)
{
    $year = $request->year; // 1988-2016
    $type = "1"; // 1:輸出、2:輸入

    $url = "https://opendata.resas-portal.go.jp/api/v1/industry/export/fromTo?itemCode3=-&customsCode2=-&year=". $year ."&regionCode=-&itemCode2=-&unitType=2&itemCode1=-&dispType=1&customsCode1=-&dataType=". $type ."&countryCode=-";
    $method = "GET";

    //接続
    $client = new Client();

    $options = [
        'http_errors' => false,
        'headers' => [
            'X-API-KEY' => '取得したAPIキー',
        ],
    ];

    // 輸出データ
    $response = $client->request($method, $url, $options);
    $datas = $response->getBody();
    $datas = json_decode($datas, true)['result']['data'][0]['to'];
    
    // 必要な情報を入れる配列
    $giodata = array();
    foreach($datas as $d) {
        // datasの名前と一致しているjpテーブルのレコードを取得
        $i = \DB::table('countryjp')->where('Name', $d['countryName'])->first();
        
        // jpのCode2とデータ量を含んだ連想配列
        if(!is_null($i) && $i->Code2 != "MO") { //MOのデータがないので除外
            array_push($giodata, array("e"=>"JP", "i"=>$i->Code2, "v"=>$d["value"] / 1000));
        }
    }
    // JSに渡すためにJSONに変換
    $gio = json_encode($giodata);
    $json = mb_convert_encoding($gio, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
    return $json;
}

一部分だけ取り上げてコメント。

  • year
$year = $request->year; // 1988-2016

あとでセレクトボックスで指定した年のデータを受け取るためリクエストを受け取るようにした。

  • url
$url = "https://opendata.resas-portal.go.jp/api/v1/industry/export/fromTo?itemCode3=-&customsCode2=-&year=". $year ."&regionCode=-&itemCode2=-&unitType=2&itemCode1=-&dispType=1&customsCode1=-&dataType=". $type ."&countryCode=-";

yearとtypeを入れ込んでデータを持ってくるurlを指定。
いろんなパラメータを入れ込んでいるけどホントは大元のurlだけここで指定してパラメータは全部別で扱うのが正規な気が。
今回はyear以外は固定だったので無理やり感ありつつここで入れてしまった。

  • APIデータから必要なデータの取り出し
$datas = json_decode($datas, true)['result']['data'][0]['to'];

多分、今回の課題で一番詰まったポイント。GETしてきたデータを後から使うために変数に格納したかったのだが、生データから必要な部分だけのデータの指定方法が分からなくて何度もエラーと戦った。

少しjsonの勉強した。
PHPでJSONのデータを処理する方法
例えば配列型になっている部分には[0]がいるなど最初分からなかった。。。 こことRESAS-APIのサンプルデータを見ながら試行錯誤してなんとか上記のような必要なデータの指定方法にたどり着いた。

  • ライブラリ用jsonにするデータの作成
foreach($datas as $d) {
    // datasの名前と一致しているjpテーブルのレコードを取得
    $i = \DB::table('countryjp')->where('Name', $d['countryName'])->first();

    ...

とってきたデータの国名と一致したcountryjpのデータを変数に入れる。 この急に出てきたcountryjpというテーブルは独自に用意した。
中身はこんな感じ。

mysql> select * from countryjp;
+--------------------------------------------------+-------+
| Name                                             | Code2 |
+--------------------------------------------------+-------+
| アイスランド                                     | IS    |
| アイルランド                                     | IE    |
| アゼルバイジャン                                 | AZ    |
| アフガニスタン                                   | AF    |
| アメリカ合衆国                                   | US    |
...

Gio.jsで読み込むデータでは2文字の国名コードが必要だが、利用しているAPIは日本語表記のの国名しかなかったため、このテーブルを作成して2文字の国名コードに変換する役割をしてもらうことにした。
確か郵便局が公表している対応表を利用したはず。
API側と日本語表記が違うものを手作業で調整もした。
(例えば、中国と中華人民共和国みたいな違いのもの。)

// jpのCode2とデータ量を含んだ連想配列
if(!is_null($i) && $i->Code2 != "MO") { //MOのデータがないので除外
    array_push($giodata, array("e"=>"JP", "i"=>$i->Code2, "v"=>$d["value"] / 1000));
}

先ほどの判定でとってきたデータを使って、Gio.jsに即したデータ形式で連想配列にデータをどんどん入れていった。

jsonでとってきて、配列としてで必要なデータだけ入れ込んで最後にライブラリ専用の形のjsonとして再整形。冗長な気もしつつ。

ともかく、アクションの準備ができたのでルートに記述する。

Route::get('/gio_e_data/{year}', 'GioController@gioExportData');

年数を入れてアクセスすると、jsonデータが出てきて感動!

ちなみに輸入データ取得用にも別のアクションを同じように作って、JS内で2つのJSONデータを呼び出し→マージしてからライブラリで読み込むという形をとった。やりようによってはLaravelの方でまとめたデータを吐き出せたかもなと思った。その方がきっと綺麗だと思うが知識が足りずそこにたどり着けなかった。

取得したデータをGio.jsで利用できるようにする

jQueryのajax通信でさっきのjsonファイルを持ってくる。

$('#year-select').on('change', function(){
  let year = $('#year-select').val();

  $.when(
      $.ajax({
      type:'GET',
      url:'gio_i_data/' + year,
      data:{ year : year },
      dataType:"json",
  }),
      $.ajax({
      type:'GET',
      url:'gio_e_data/' + year,
      data:{ year : year },
      dataType:"json",
  })
  ).then(function(data1,data2){
      let results = data1[0];
      results = results.concat(data2[0]);

      controller.addData(results);
  });
});

発火条件はidyear-selectを持つ年度指定のセレクトボックスの値変更。
前述の通り2つのjsonを一緒に持ってきたかったので並列処理をしている。 jQuery.when() で、複数の非同期処理を扱う | Tips Note by TAM

先ほど準備したLaravelのアクションにどうやって指定した年を渡すか結構悩んだが思った以上にシンプルだった。

2つのjsonを取得できたらconcatを使って2つのデータを統合する。
統合したデータをライブラリで読み込めたらok。

ビューの調整、制御の追加など仕上げ

ビュー用のアクションもjson読み込みをしていたコントローラ内に作成。

public function index()
{
    //
    $countries = Country::get();

    return view('welcome', compact('countries'));
}

ルーティング

Route::get('/', 'GioController@index');

あとは制御系の追加。

  • 発火に使っている年度選択のセレクトボックス。
<select id="year-select" name=”year”>
    @for ($year = 2016; $year > 1987; $year--)
    <option value="{{ $year }}">{{ $year }}</option>
    @endfor
</select>

jsコードは先ほど紹介したajax通信。

  • 国テーブルを利用した国名セレクトボックス
<select id="country-select" name=”country”>
    @foreach($countries as $country)
    <option value={{$country->Code2}} @if($country->Name === "Japan") selected @endif>
        {{$country->Name}} 
    </option>
    @endforeach
</select>
$('#country-select').on('change', function(){
    let country = $('#country-select').val();
    controller.switchCountry( country );
});

Gio.jsは直接地球モデルの国をクリックすることでその国に焦点が当たるようになっているが、このセレクトボックスで独自メソッドのswitchCountry()を使うことでセレクトボックスから焦点を当てる国を選択できるようにしている。これを設置したのは準備編で導入したMySQL公式DBのテーブルを使いたかったからw

ここから下はおまけのような追加要素。

  • 日本に焦点を合わせるボタン
<button id="japan">日本を焦点に戻す</button>
$( "#japan" ).click( function () {
    controller.switchCountry( "JP" );
    $('#country-select').val("JP");
});

採用したのが日本中心の貿易データだったのでセレクトボックスとは別に入れた。 セレクトボックスのselectedも一緒に変わるように指定。

  • 回転ON/OFFとデータのクリア
<button id="on">回転ON</button>
<button id="off">回転OFF</button>
<button id="clear">データクリア</button>
$( "#on" ).click( function () {
    controller.setAutoRotation(true, 1);
});
$( "#off" ).click( function () {
    controller.setAutoRotation(false);
});
$( "#clear" ).click( function () {
    controller.clearData();
});

全部独自メソッドなのでライブラリ知らない人はふーんという感じではあるが一応。この辺はアプリっぽくしたくて入れたところはある。

  • 国を直接クリックした時にセレクトボックスものselectedも変える
controller.onCountryPicked( function( select, relate ) {
    $('#country-select').val(select.ISOCode);
});

これも独自メソッド。


気づきなど

とにかく私にとって難易度が高いポイントが色々あった課題となりました。

  • 外部APIの呼び出し
  • LaravelとJSの連携
  • 非同期通信
  • jsonの取り扱い

特にjsonは他の人の取り組み方や参考記事を見ているともっとうまく扱えそうなのでリファクタリングしたいです。
データを呼び出したり渡したりする仕組みについてもちゃんと学び直すきっかけになりました。GETとPOSTを雰囲気で使っていたので。。。m(_ _)m
そして何より、外部APIを使うことで開発の幅がぐっと広がる可能性のようなものを身をもって実感できました。 今回はデータを取ってくる形でしたが、今度はデータを送ることができる(SNS投稿とか!)アプリも作ってみたい(*・ω・)ノ

やっと記録かけて満足です。


miyupacaの学習記録ブログです。