PHPで最近の花粉飛散量をグラフ表示する

(1/1)
ウェザーニューズは、独自に開発した花粉観測機「ポールンロボ」によって観測・解析された花粉飛散数データを無償提供する API「花粉飛散数データ」を用意している。そこで今回は、「PHPで最寄駅の混雑度予想をグラフ表示する」の混雑度予想棒グラフの代わりに、指定した場所の花粉飛散数を棒グラフにして表示するPHPプログラムを作ってみる。

目次

サンプル・プログラムの実行例

PHPで最近の花粉飛散量をグラフ表示する
Googleマップ表示
ホームページ内から呼び出すことを想定し、花粉飛散量グラフだけを描くことができるパラメータ mode や、花粉飛散数データの件数(エラー発生時や花粉飛散期間以外は0)を表示することができる。URLから次のようなパラメータ指定ができる。
(例1)キーワードから場所を検索し,地図とグラフを表示する.
  pollenGraph.php?query=%E5%90%8D%E5%8F%A4%E5%B1%8B%E9%A7%85
    query = 住所またはランドマーク
(例2)緯度・経度から場所を検索し,グラフのみを表示する.
  pollenGraph.php?latitude=35.7&longitude=139.7&mode=1
    latitude = 緯度(世界測地系;日本国内のみ有効)
    longitude = 経度(世界測地系;日本国内のみ有効)
    mode = 1:グラフのみを表示
(例3)市区町村コードから場所を検索し,グラフのみを表示する.
  pollenGraph.php?cityCode=13104&mode=1
    cityCode = 市区町村コード
    mode = 1:グラフのみを表示
(例4)市区町村コードから場所を検索し,花粉飛散数データの数を表示する.
  pollenGraph.php?cityCode=13104&count
  cityCode = 市区町村コード
  count:花粉飛散数データの数を表示

サンプル・プログラム

圧縮ファイルの内容
pollenGraph.phpサンプル・プログラム本体。
.pahooEnvクラウドサービスを利用するためのアカウント情報などを記入する .env ファイル。
使い方は「各種クラウド連携サービス(WebAPI)の登録方法」を参照。include_path が通ったディレクトリに配置すること。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
pahooGeoCode.php住所・緯度・経度に関わるクラス pahooGeoCode。
使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。
pahooWeather.php気象情報に関わるクラス pahooWeather。
気象情報に関わるクラスの使い方は「PHPで天気予報を求める(その3)」「PHPで暑さ指数予測を地図上に表示する」などを参照。include_path が通ったディレクトリに配置すること。
pahooCache.phpキャッシュ処理に関わるクラス pahooCache。
キャッシュ処理に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。
pollenGraph.php 更新履歴
バージョン 更新日 内容
1.0.1 2026/03/10 「地図検索」が動作しない不具合を修正
1.0.0 2026/02/19 初版
pahooInputData.php 更新履歴
バージョン 更新日 内容
2.0.1 2025/08/11 getParam() bug-fix
2.0.0 2025/08/11 pahooLoadEnv() 追加
1.9.0 2025/07/26 getParam() 引数に$trim追加
1.8.1 2025/03/15 validRegexPattern() debug
1.8.0 2024/11/12 validRegexPattern() 追加
pahooGeoCode.php 更新履歴
バージョン 更新日 内容
6.10.0 2026/02/19 getLocalGovernmentCode, address2LocalGovernmentCode, localGovernmentCode2addresss メソッド追加
6.9.1 2025/11/25 PHP8.5対応:double→float
6.9.0 2025/09/21 jsPolygon, jsPolygon_Gmap, jsPolygon_Leaflet, loadGeoJSON, getPrefBorderList を追加
6.8.0 2025/08/10 アクセスキーなどを ".pahooEnd" に分離
6.7.1 2025/07/26 jsLine_Gmap() - bug-fix
pahooWeather.php 更新履歴
バージョン 更新日 内容
5.9.0 2026/03/07 currentWeatherOpenWeather, utc2jst, kelvin2celsius, addOpenWeatherCounterメソッド追加
5.8.0 2026/02/15 getPollenメソッド追加
5.7.0 2025/08/09 readEnvWBGTinfoSpots(), readEnvWBGTforecast(), getWBGTcolor() 追加
5.6.2 2025/04/10 readJmaSpots() -- bug-fix
5.6.1 2025/04/08 getMyscriptPathURL() -- bug-fix
pahooCache.php 更新履歴
バージョン 更新日 内容
1.3.0 2025/12/06 PHP8.5対応:curl_closeを使わない
1.2.0 2025/09/06 cLoad() HTTPヘッダを送信できるようにした
1.1.3 2025/08/10 var→public
1.1.2 2023/07/22 bug-fix
1.1.1 2023/02/11 コメント追記

サンプル・プログラムの流れ(メイン)

PHPで最近の花粉飛散量をグラフ表示する

準備:PHP の https対応

クラウド連携や相手先サイトのデータを読み込むのに https通信を使うため、PHPに OpenSSLモジュールが組み込まれている必要がある。関数  phpinfo  を使って、下図のように表示されればOKだ。
OpenSSL - PHP
そうでない場合は、次の手順に従ってOpenSSLを有効化し、PHPを再起動させる必要がある。

Windowsでは、"php.ini" の下記の行を有効化する。
extension=php_openssl.dll
Linuxでは --with-openssl=/usr オプションを付けて再ビルドする。→OpenSSLインストール手順

これで準備は完了だ。

準備:pahooInputData 関数群

PHPのバージョンや入力データのバリデーションなど、汎用的に使う関数群を収めたファイル "pahooInputData.php" が同梱されているが、include_path が通ったディレクトリに配置してほしい。他のプログラムでも "pahooInputData.php" を利用するが、常に最新のファイルを1つ配置すればよい。

また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。

準備:pahooGeoCode クラス

pahooGeoCode.php

  41: class pahooGeoCode {
  42:     public $items;      // 検索結果格納用
  43:     public $error;      // エラー・フラグ
  44:     public $errmsg;     // エラー・メッセージ
  45:     public $hits;       // 検索ヒット件数
  46:     public $webapi; // 直前に呼び出したWebAPI URL
  47: 
  48:     // 都道府県境界線データ
  49:     // SimpleMaps.com is a product of Pareto Software, LLC. © 2010-2025
  50:     // https://simplemaps.com/gis/country/jp
  51:     // ※各自の環境に合わせて設定すること
  52:     public $GeoJsonJP = __DIR__ . '/jp.json';
  53: 
  54:     // -- 以下のデータは .env ファイルに記述可能
  55:     // Google Cloud Platform APIキー
  56:     // https://cloud.google.com/maps-platform/
  57:     // ※Google Maps APIを利用しないのなら登録不要
  58:     public $GOOGLE_API_KEY_1 = '';      // HTTPリファラ用
  59:     public $GOOGLE_API_KEY_2 = '';      // IP制限用
  60:     public $GOOGLE_MAP_ID    = '';      // GoogleMaps ID
  61: 
  62:     // Yahoo! JAPAN Webサービス アプリケーションID
  63:     // https://e.developer.yahoo.co.jp/register
  64:     // ※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
  65:     public $YAHOO_APPLICATION_ID = '';
  66: 
  67:     // OSM Nominatim Search API利用時に知らせるメールアドレス
  68:     // https://wiki.openstreetmap.org/wiki/JA:Nominatim#.E6.A4.9C.E7.B4.A2
  69:     // ※OSM Nominatim Search APIを利用しないのなら登録不要
  70:     public $NOMINATIM_EMAIL = '';
  71: 
  72:     // IP2Location.io APIキー
  73:     // https://www.ip2location.io/
  74:     // ※IP2Location.ioを利用しないのなら登録不要
  75:     public $IP2LOCATION_API_KEY = '';

GoogleマップやLeafletなどによる地図描画や住所検索を行うためのクラスが pahooGeoCode である。同梱のクラス・ファイル "pahooGeoCode.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooGeoCodeクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。

地図や住所検索として Google を利用するのであれば Google Cloud Platform APIキーマップID が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を、Yahoo!JAPAN を利用するのであれば Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を、IP2Location.ioを利用するのであれば「PHPでIPアドレスやホスト名から住所を求める」を、それぞれ参照されたい。

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

準備:pahooCache クラス

pahooCache.php

  13: class pahooCache {
  14:     const LIFE_CACHE = (2 * 60);        // キャッシュ保持時間(デフォルト;分)
  15:     const DEF_DIRCACHE = './pcache/';   // キャッシュ・ディレクトリ(デフォルト)
  16: 
  17:     public $lifeCache;      // キャッシュ保持時間(分)(0:キャッシュしない)
  18:     public $dirCache;       // キャッシュ用ディレクトリ
  19:     public $httpHeader;     // HTTPヘッダ(空文字の時は何も送らない)
  20:     public $error;          // エラーフラグ
  21:     public $errmsg;         // エラーメッセージ
  22:     public $debug;          // デバッグ用ファイル名
  23: 
  24: /**
  25:  * コンストラクタ
  26:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
  27:  * @param   int    $life キャッシュ保持時間(分)(省略可能)
  28:  * @param   string $dir キャッシュ・ディレクトリ(省略可能)
  29:  * @param   array  $httpHeader httpヘッダに渡す配列(省略可能)
  30:  *          USER AGENT偽装に用いることを想定
  31:  *          (例)
  32:  *           array(
  33:  *              'User-Agent: Mozilla/5.0(Windows NT 10.0; Win64; x64) pahooAppy/pahoo.org AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
  34:  *              'Accept-Language: ja-JP'
  35:  *          );
  36:  * @return  なし
  37: */
  38: function __construct($life=self::LIFE_CACHE, $dir=self::DEF_DIRCACHE, $httpHeader=NULL) {
  39:     if ($life < 0) {
  40:         $life = 0;
  41:     }
  42:     if (preg_match('/\/$/ui', $dir) == 0) {
  43:         $dir = $dir . '/';
  44:     }
  45:     $this->error      = FALSE;
  46:     $this->errmsg     = '';
  47:     $this->debug      = '';
  48:     $this->lifeCache  = $life;
  49:     $this->dirCache   = $dir;
  50:     $this->httpHeader = $httpHeader;
  51: 
  52:     // PHP5以上であることを調べる.
  53:     if (! $this->isphp5over()) {
  54:         $this->error   = TRUE;
  55:         $this->errmsg  = '動作にはPHP5以上が必要です';
  56:         return;
  57:     }
  58: 
  59:     // キャッシュ・ディレクトリが無ければ作成する.
  60:     if (! is_dir($this->dirCache)) {
  61:         $res = mkdir($this->dirCache, 0744);
  62:         if ($res == FALSE) {
  63:             $this->error   = TRUE;
  64:             $this->errmsg  = 'キャッシュ・ディレクトリ "' . $this->$dirCache . '" の作成に失敗しました';
  65:             return;
  66:         }
  67:     }
  68: }

クラウド連携や相手先サイトの公開データを利用する際、こちらのアプリを起動する都度、APIを呼び出したりデータをダウンロードするのでは、相手サーバに負荷をかけてしまう。
そこで、頻繁に変更がないデータについては、一度取り込んだら、こちら側のサーバのローカルストレージにキャッシュしておく仕組みを用意した。それが pahooCacheクラス である。同梱のクラス・ファイル "pahooCache.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooCacheクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。

pahooCacheクラス の注意ポイントは、キャッシュ時間(単位:分)とキャッシュを保存するディレクトリをコンストラクタで指定している点だ。これらはプログラムによって変わるものである。インスタンス化するときの値は、pahooCacheクラス を利用するメイン・プログラムの方で解説する。

サイトによっては、User-Agent などを必要とすることがあるだろう。そこで、第3引数に HTTPヘッダ として送信するデータを配列で渡すことができるようにした。配列の構造はコメントを参照していただきたい。

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

準備:jqPlotプラグイン

pollenGraph.php

 167: <!-- jqPlot 本体 -->
 168: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/jquery.jqplot.min.css">
 169: <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
 170: <script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/jquery.jqplot.min.js"></script>
 171: 
 172: <!-- プラグイン -->
 173: <script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/plugins/jqplot.barRenderer.min.js"></script>
 174: <script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/plugins/jqplot.canvasTextRenderer.min.js"></script>
 175: <script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/plugins/jqplot.canvasAxisLabelRenderer.min.js"></script>
 176: <script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/plugins/jqplot.highlighter.min.js"></script>
 177: <style type="text/css">

サーバに負荷をかけないよう、グラフ描画はクライアント側で行う。今回は、JavaScriptの jQueryプラグイン「jqPlot Charts and Graphs for jQuery」を利用する。公式サイトからダウンロードしてサーバに展開してもいいし、今回のように、無料の JavaScript / CSS ライブラリ配信サービス「Cloudflare CDNJS」を利用してもいい。
HTMLの <head> タグに、ライブラリ「jQuery」と、必要な jPlotプラグインを記述する。

各種定数

pollenGraph.php

  79: // 各種定数(START) ===========================================================
  80: 
  81: // 地図描画サービスの選択
  82: //    0:Google
  83: //    2:地理院地図・OSM
  84: define('MAPSERVICE', 2);
  85: 
  86: // 住所検索サービスの選択
  87: //    0:Google
  88: //    1:Yahoo!JAPAN
  89: //   11:HeartRails Geo API
  90: //   12:OSM Nominatim Search API
  91: //   13:国土地理院ジオコーディングAPI
  92: define('GEOSERVICE', 1);
  93: 
  94: // 逆ジオコーディングサービスの選択
  95: //    0:Google
  96: //    1:Yahoo!JAPAN
  97: //   11:HeartRails Geo API
  98: //   21:簡易ジオコーディングサービス
  99: define('REVGEOSERVICE', 1);
 100: 
 101: // マップID
 102: define('MAP_ID', 'map_id');
 103: // マップの表示サイズ(単位:ピクセル)
 104: define('MAP_WIDTH',  600);
 105: define('MAP_HEIGHT', 400);
 106: // 初期値
 107: define('DEF_LATITUDE',  35.7);          // 緯度
 108: define('DEF_LONGITUDE', 139.7);         // 経度
 109: define('DEF_TYPE',      'roadmap');     // マップタイプ
 110: define('DEF_ZOOM',      13);            // ズーム
 111: define('DEF_CATEGORY', 'address');      // カテゴリ
 112: 
 113: // マップ中心マーカーのURL;表示しなくなければNULLにする
 114: define('CENTER_MARKER', 'https://www.google.com/mapfiles/arrow.png');
 115: 
 116: // 検索キーの最小文字長
 117: define('QUERY_MIN_LEN', 3);
 118: // 検索キーの最大文字長
 119: define('QUERY_MAX_LEN', 99);
 120: 
 121: // グラフのHTMLオブジェクトID
 122: define('GRAPH_ID', 'jqGraph');
 123: // グラフの表示幅・高さ(単位:ピクセル)
 124: define('GRAPH_WIDTH',  600);
 125: define('GRAPH_HEIGHT', 400);
 126: // 棒グラフの色
 127: define('GRAPH_COLOR', '#0000FF');
 128: // グラフの背景色
 129: define('GRAPH_BGCOLOR', '#FFFFFF');
 130: 
 131: // キャッシュ保持時間(分) 0:キャッシュしない
 132: // weathernews API へのアクセス負荷軽減
 133: define('LIFE_CACHE_DATA', (60 * 8));
 134: 
 135: // キャッシュ・ディレクトリ
 136: // 書き込み可能で,外部からアクセスされないディレクトリを指定してください.
 137: define('DIR_CACHE_DATA',   './pcache_pollen/');
 138: 
 139: // キャッシュ保持時間(分) 0:キャッシュしない
 140: // 総務省(標準地域コード取得)へのアクセス不可軽減
 141: define('LIFE_CACHE_DATA_CITYCODE', (60 * 24 * 180));
 142: 
 143: // 各種定数(END) =============================================================

表示する地図は、Googleマップ地理院地図・オープンストリートマップ(OSM)から選べる。あらかじめ、定数 MAPSERVIC に値を設定すること。
住所検索サービスは、GoogleYahoo!JAPANHeartRails Geo APIから選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
逆ジオコーディングサービスは、GoogleYahoo!JAPANHeartRails Geo API簡易ジオコーディングサービスから選べる。あらかじめ、定数 REVGEOSERVICE に値を設定すること。

その他、とくに【変更不可】の指定のない定数については、自由に変更できる。

WxTech Data API:花粉飛散数データ

ウェザーニューズは、独自に開発した花粉観測機「ポールンロボ」によって観測・解析された花粉飛散数データを無償提供する API「花粉飛散数データ」を用意している。市区町村コード、取得開始年月日、取得終了年月日を与えると、1時間毎の花粉飛散数データをCSV形式で応答する REST API である。
リクエストURL
URL
https://wxtech.weathernews.com/opendata/v1/pollen

入力パラメータ
フィールド名 要否 内  容
citycode 必須 市区町村コード(総務省:標準地域コード)
start 必須 取得開始年月日(例:20260208)
end 必須 取得終了年月日(例:20260214;取得期間は31日以内)
応答データ

カンマ区切りのCSV形式。
date は1時間ごとで、ISO 8601 形式。
pollen は花粉飛散量で、単位は「個/cm2」。
citycode,date,pollen
13104,2026-02-11T00:00:00+09:00,0
13104,2026-02-11T01:00:00+09:00,0
13104,2026-02-11T02:00:00+09:00,0
13104,2026-02-11T03:00:00+09:00,0
13104,2026-02-11T04:00:00+09:00,0
13104,2026-02-11T05:00:00+09:00,0
13104,2026-02-11T06:00:00+09:00,0
13104,2026-02-11T07:00:00+09:00,0
13104,2026-02-11T08:00:00+09:00,0
13104,2026-02-11T09:00:00+09:00,0
13104,2026-02-11T10:00:00+09:00,0
13104,2026-02-11T11:00:00+09:00,0
13104,2026-02-11T12:00:00+09:00,0
13104,2026-02-11T13:00:00+09:00,0
13104,2026-02-11T14:00:00+09:00,0
13104,2026-02-11T15:00:00+09:00,0
13104,2026-02-11T16:00:00+09:00,0
13104,2026-02-11T17:00:00+09:00,0
13104,2026-02-11T18:00:00+09:00,0
13104,2026-02-11T19:00:00+09:00,0
13104,2026-02-11T20:00:00+09:00,1
13104,2026-02-11T21:00:00+09:00,0
13104,2026-02-11T22:00:00+09:00,0
13104,2026-02-11T23:00:00+09:00,0
13104,2026-02-12T00:00:00+09:00,1
13104,2026-02-12T01:00:00+09:00,0
13104,2026-02-12T02:00:00+09:00,0
13104,2026-02-12T03:00:00+09:00,0
――以下略――

花粉飛散数データ取得

pahooWeather.php

 969: /**
 970:  * weathernewsの「WxTech Data API」を使って花粉飛散数データを取得する
 971:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-56-01.shtm
 972:  * @param   string $cityCode 市区町村コード(総務省:標準地域コード)
 973:  *          string $start    取得開始年月日(例:20260208)
 974:  *          string $end      取得終了年月日(例:20260214;取得期間は31日以内)
 975:  *          array  $items    花粉飛散数データを格納する配列
 976:  *  データ構造 $items[cityCode][日時] = 花粉飛散数の単位:個数/cm^2 (欠測値-9999)
 977:  * @return  int 取得データ件数/FALSE:取得失敗
 978: */
 979: function getPollen($cityCode, $start, $end, &$items) {
 980:     // 入力値のバリデーション
 981:     if (preg_match('/^\d{5}$/iu', $cityCode) === 0) {
 982:         $this->seterror('花粉飛散数データAPI:市区町村コードが間違っている');
 983:         return FALSE;
 984:     }
 985:     if (preg_match('/^^\d{8}$/iu', $start) === 0) {
 986:         $this->seterror('花粉飛散数データAPI:取得開始年月日が間違っている');
 987:         return FALSE;
 988:     }
 989:     if (preg_match('/^^\d{8}$/iu', $end) === 0) {
 990:         $this->seterror('花粉飛散数データAPI:取得終了年月日が間違っている');
 991:         return FALSE;
 992:     }
 993: 
 994:     // リクエストURL生成
 995:     $url = 'https://wxtech.weathernews.com/opendata/v1/pollen?citycode=' . $cityCode . '&start=' . $start . '&end=' . $end;
 996:     $this->webapi = $url;
 997: 
 998:     // 花粉飛散数データを取得する;pahooCacheクラスを利用
 999:     $csv = $this->pcc->load($url);
1000: 
1001:     // 行分割
1002:     $lines = explode("\n", trim($csv));
1003: 
1004:     // 1行目をヘッダとして取得
1005:     $headers = str_getcsv(array_shift($lines), ',', '"', '\\');
1006: 
1007:     // 花粉飛散数データを変数 $items へ格納する
1008:     $cnt = 0;
1009:     foreach ($lines as $line) {
1010:         if ($line === '')   continue;
1011: 
1012:         // CSVとして安全に分解
1013:         $values = str_getcsv($line, ',', '"', '\\');
1014: 
1015:         // 列数チェック
1016:         if (count($values!== count($headers)) {
1017:             continue;       // 壊れた行はスキップ
1018:         }
1019: 
1020:         // 列に分解
1021:         $row = array_combine($headers, $values);
1022: 
1023:         // データを格納
1024:         $citycode = $row['citycode'];
1025:         $date     = $row['date'];
1026:         $pollen   = (int)$row['pollen'];
1027:         $items[$citycode][$date] = $pollen;
1028:         $cnt++;
1029:     }
1030:     return $cnt;
1031: }

上述の REST API をメソッドとして実装したものが pahooWeather.getPollen である。API へ負荷をかけないよう、キャッシュシステムを利用し、一度取得した CSV データは定数 LIFE_CACHE_DATA で指定する期間だけローカルキャッシュに保存するようにしている。

応答は CSV形式データで1行目はラベル名になっている。そこで、まず組み込み関数  explode  を使って、1行ずつ配列 $lines に格納する。
次に、組み込み関数  str_getcsv  を使って、1行目をヘッダとして取得・分解する。
2行目以降も、組み込み関数  str_getcsv  を使って分解して、配列 [$items] へ代入していく。データ構造は
$items[cityCode][日時] = 花粉飛散数の単位:個数/cm2 (欠測値-9999)
ここで、日時は ISO 8601 形式である。

総務省:標準地域コード

上述の REST API に渡す市区町村コードは、数字5桁からなる総務省の標準地域コードである。
総務省のページ「統計に用いる標準地域コード」に、全国の標準地域コード(CSVファイル)のリンク先が掲げられている。そこで、このページをスクレイピングして、目的のCSVファイルをダウンどーする。
CSVファイルの構造

カンマ区切りのCSV形式。文字エンコードはシフトJIS。
tiiki-code が市区町村コードに該当する。
ken-code,sityouson-code,tiiki-code,ken-name,sityouson-name1,sityouson-name2,sityouson-name3,yomigana
01,000,01000,北海道,,,,ほっかいどう
01,100,01100,北海道,札幌市,,,さっぽろし
01,101,01101,北海道,札幌市,,中央区,ちゅうおうく
01,102,01102,北海道,札幌市,,北区,きたく
01,103,01103,北海道,札幌市,,東区,ひがしく
01,104,01104,北海道,札幌市,,白石区,しろいしく
01,105,01105,北海道,札幌市,,豊平区,とよひらく
01,106,01106,北海道,札幌市,,南区,みなみく
01,107,01107,北海道,札幌市,,西区,にしく
01,108,01108,北海道,札幌市,,厚別区,あつべつく
01,109,01109,北海道,札幌市,,手稲区,ていねく
01,110,01110,北海道,札幌市,,清田区,きよたく
01,202,01202,北海道,,,函館市,はこだてし
01,203,01203,北海道,,,小樽市,おたるし
01,204,01204,北海道,,,旭川市,あさひかわし
01,205,01205,北海道,,,室蘭市,むろらんし
01,206,01206,北海道,,,釧路市,くしろし
――以下略――

総務省:標準地域コード一覧を取得

pahooGeoCode.php

2914: /**
2915:  * 総務省の標準地域コード一覧を取得する
2916:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-56-01.shtm#localGovernmentCodeList
2917:  * @param   object $pcc pahooCacheオブジェクト
2918:  * @param   array  $cityCodeList 標準地域コードを格納する配列
2919:  *              データ構造 $items[cityCode] = 市区町村名(UTF-8)
2920:  * @param   string $encode 「統計に用いる標準地域コード」サイトのエンコード
2921:  *              省略時:SJIS
2922:  * @return  int 格納したデータ数 / FALSE:読み込み失敗
2923: */
2924: function getLocalGovernmentCodeList($pcc, &$cityCodeList, $encode='SJIS') {
2925:     static $errmsg = '標準地域コードを取得できない';
2926:     $domain     = 'https://www.soumu.go.jp';
2927:     $contentURL = 'https://www.soumu.go.jp/toukei_toukatsu/index/seido/9-5.htm';
2928: 
2929:     // 「統計に用いる標準地域コード」サイトにアクセスする.
2930:     $contents = $pcc->load($contentURL);
2931:     if ($contents === FALSE) {
2932:         $this->seterror("{$errmsg}({$contentURL} にアクセスできない)");
2933:         return FALSE;
2934:     }
2935:     $contents = mb_convert_encoding($contents, INTERNAL_ENCODING, $encode);
2936: 
2937:     // 「統計に用いる標準地域コード」のURLを取得する.
2938:     $rep = '/<a\s+href\=\"(\/main_content\/[0-9]+\.csv)\">/i';
2939:     if (preg_match($rep, $contents, $arr) === FALSE) {
2940:         $this->seterror("{$errmsg}(CSVファイルが見つからない)");
2941:         return FALSE;
2942:     }
2943: 
2944:     // 「統計に用いる標準地域コード」のCSVファイルを取得する.
2945:     $url = $domain . $arr[1];
2946:     $csv = $pcc->load($url);
2947:     if ($csv === FALSE) {
2948:         $this->seterror("{$errmsg}({$url} にアクセスできない)");
2949:         return FALSE;
2950:     }
2951:     $csv = mb_convert_encoding($csv, INTERNAL_ENCODING, $encode);
2952: 
2953:     // 行分割
2954:     $lines = explode("\n", trim($csv));
2955: 
2956:     // ヘッダ取得
2957:     $headers = str_getcsv(array_shift($lines), ',', '"', '\\');
2958: 
2959:     // 標準地域コードを配列 $cityCodeList に格納する
2960:     $cnt = 0;
2961:     foreach ($lines as $line) {
2962:         if (trim($line) === '')     continue;
2963: 
2964:         // CSVとして分解
2965:         $cols = str_getcsv($line, ',', '"', '\\');
2966: 
2967:         // 列数不一致ガード
2968:         if (count($cols!== count($headers)) continue;
2969: 
2970:         // ヘッダ+値  連想配列
2971:         $row = array_combine($headers, $cols);
2972: 
2973:         // 標準地域コード
2974:         $cityCode = $row['tiiki-code'];
2975: 
2976:         // 市区町村名を連結(空は除外)
2977:         $nameParts = array_filter([
2978:             $row['ken-name'],
2979:             $row['sityouson-name1'],
2980:             $row['sityouson-name2'],
2981:             $row['sityouson-name3'],
2982:         ]);
2983:         $cityName = implode('', $nameParts);
2984: 
2985:         // 配列に格納する.
2986:         if ($cityCode !== '') {
2987:             $cityCodeList[$cityCode] = $cityName;
2988:             $cnt++;
2989:         }
2990:     }
2991: 
2992:     return $cnt;
2993: }

ユーザー定義メソッド getLocalGovernmentCodeList は、上述のCSVファイルを読み込んで、標準地域コード一覧を配列に格納する。
まず、総務省のページ「統計に用いる標準地域コード」から、目的の CSV ファイルへのハイパーリンクをスクレイピングで捜し出し、ダウンロードする。
このCSVファイルは、市区町村の合併・廃止がないかぎり変更はないはずだが、念のため、キャッシュシステムを介して一定期間ごと(定数 LIFE_CACHE_DATA_CITYCODE で指定)に再ダウンロードするようにした。

CSVファイルの分解と配列への格納については、上述の getPollen メソッドとほぼ同じである。
データ構造は、
$items[cityCode]= 市区町村名(UTF-8)
ここで、市区町村名は、CSVファイルのラベル ken-name, sityouson-name1, sityouson-name2, sityouson-name3 を結合した文字列である。

住所から標準地域コードを取得する

pahooGeoCode.php

2995: /**
2996:  * 住所から標準地域コードを取得する.
2997:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-56-01.shtm#php_address2LocalGovernmentCode
2998:  * @param   object $pcc pahooCacheオブジェクト
2999:  * @param   string 住所
3000:  * @return  array(標準地域コード, マッチした住所) / FALSE:変換失敗失敗
3001: */
3002: function address2LocalGovernmentCode($pcc, $address) {
3003:     // 総務省:標準地域コードを取得する.
3004:     $cityCodeList = array();
3005:     $cnt = $this->getLocalGovernmentCodeList($pcc, $cityCodeList, 'SJIS');
3006:     if ($cnt === FALSE) {
3007:         $errmsg = $this->geterror();
3008:         return FALSE;
3009:     }
3010: 
3011:     // 住所から標準地域コードを取得する.
3012:     $cityCode = '00000';
3013:     $shortAddress = '';
3014:     foreach ($cityCodeList as $code=>$str) {
3015:         // 前方一致検索
3016:         if (mb_strpos($address, $str) === 0) {
3017:             $cityCode = $code;
3018:             $shortAddress = $str;
3019:         }
3020:     }
3021: 
3022:     return array($cityCode, $shortAddress);
3023: }

ユーザー定義メソッド getLocalGovernmentCodeList は、逆ジオコーディングで得られた住所(丁目、番地まで含む)を引数として渡すと、標準地域コードを返す。

前述のメソッド getLocalGovernmentCodeList を使って得られた一覧配列から、1つ1つ住所が一致するものを捜していく。
ここで、引数で渡した住所 $address の方が丁目、番地まで含む長い文字列なので、一覧配列から取り出した住所と先頭一致するかどうかで判定する。先頭一致には、組み込み関数  mb_strpos  を使っている。

標準地域コードから住所を取得する

pahooGeoCode.php

3025: /**
3026:  * 標準地域コードから住所を取得する.
3027:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-56-01.shtm#php_localGovernmentCode2address
3028:  * @param   object $pcc pahooCacheオブジェクト
3029:  * @param   string $cityCode 標準地域コード
3030:  * @return  string 住所 / FALSE:該当する住所がない
3031: */
3032: function localGovernmentCode2address($pcc, $cityCode) {
3033:     // 総務省:標準地域コードを取得する.
3034:     $cityCodeList = array();
3035:     $cnt = $this->getLocalGovernmentCodeList($pcc, $cityCodeList, 'SJIS');
3036:     if ($cnt === FALSE) {
3037:         $errmsg = $this->geterror();
3038:         return FALSE;
3039:     }
3040: 
3041:     // 住所を標準地域コードに変換する.
3042:     $address = FALSE;
3043:     if (array_key_exists($cityCode, $cityCodeList)) {
3044:         $address = $cityCodeList[$cityCode];
3045:     } else {
3046:          $this->seterror('該当する住所がありません');
3047:     }
3048: 
3049:     return $address;
3050: }

ユーザー定義メソッド localGovernmentCode2address は、逆に、標準地域コードから住所を取得する。

前述のメソッド getLocalGovernmentCodeList を使って得られた一覧配列から、標準地域コードに合致する添字があれば、その値(つまり住所)を返す。

花粉飛散量の棒グラフを描画

pollenGraph.php

 242: /**
 243:  * jqPlot用のスクリプト
 244:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-56-01.shtm#jqPlot
 245:  * @param   string $graphId グラフのHTMLオブジェクト名
 246:  * @param   array  $items データ配列
 247:  * @param   string $title グラフのタイトル
 248:  * @param   string $address 住所
 249:  * @return  string スクリプト
 250: */
 251: function plot($graphId, $items, $title='', $address='') {
 252:     // 棒グラフの色
 253:     $color = GRAPH_COLOR;
 254:     // 棒グラフの幅
 255:     $barwidth = (int)(GRAPH_WIDTH / count($items* 0.8);
 256:     // グラフの背景色
 257:     $bgcolor = GRAPH_BGCOLOR;
 258: 
 259:     $ticks  = array();
 260:     $seen   = array();
 261:     $values = array();
 262: 
 263:     $cnt = 0;
 264:     $prev = '';
 265:     foreach ($items as $datetime=>$value) {
 266:         // 花粉飛散量
 267:         $values[] = (int)$value;
 268: 
 269:         // 表示用ラベル(例: 02/10)
 270:         $date = substr($datetime, 0, 10);
 271:         $label = date('m/d', strtotime($date));
 272:         // 同じ日付は1回だけ表示
 273:         if (! isset($seen[$label])) {
 274:             $ticks[] = [$cnt, $label];
 275:             $seen[$label] = TRUE;
 276:         }
 277:         $cnt++;
 278:     }
 279: 
 280:     // プロットデータをJSON化
 281:     $jsValues = json_encode($values);
 282:     $jsTicks  = json_encode($ticks);
 283: 
 284:     $js =<<< EOT
 285: jQuery(function() {
 286:     const values = {$jsValues};
 287:     const ticks  = {$jsTicks};
 288:     jQuery.jqplot('{$graphId}', [values], {
 289:         // タイトル
 290:         title: {
 291:             text: '{$title}&nbsp;<span style="font-size:14px;">({$address})</span>',
 292:             show: true,
 293:             fontFamily: 'sans-serif',
 294:             fontSize:   '18px',
 295:             textAlign:  'center',
 296:             textColor:  'black',
 297:         },
 298:         // データ(系列)
 299:         seriesDefaults: {
 300:             renderer: jQuery.jqplot.BarRenderer,
 301:             rendererOptions: {
 302:                 barWidth: {$barwidth},
 303:                 shadowOffset: 0
 304:             },
 305:             color: '{$color}',
 306:         },
 307:         // 軸
 308:         axes: {
 309:             xaxis: {
 310:                 label: '月&nbsp;日',
 311:                 ticks: ticks,
 312:                 tickRenderer: jQuery.jqplot.CanvasAxisTickRenderer,
 313:                 labelOptions:{
 314:                     fontFamily: 'sans-serif',
 315:                     fontSize:   '16px',
 316:                     textColor:  'black',
 317:                 },
 318:             },
 319:             yaxis: {
 320:                 labelRenderer: jQuery.jqplot.CanvasAxisLabelRenderer,
 321:                 label: '花粉飛散量(個/㎠)',
 322:                 min: 0,
 323:                 tickOptions: {
 324:                     formatString: '%d',
 325:                 },
 326:                 labelOptions:{
 327:                     fontFamily: 'sans-serif',
 328:                     fontSize:   '16px',
 329:                     textColor:  'black',
 330:                 },
 331:             }
 332:         },
 333:         // ハイライター
 334:         highlighter: {
 335:             show: true,
 336:             showMarker: false,
 337:             tooltipLocation: 'n',
 338:             fadeTooltip: false,
 339:             bringSeriesToFront: true,
 340:             tooltipAxes: 'y',
 341:             formatString: '%d'
 342:         },
 343:         // グラフの背景
 344:         grid: {
 345:             drawBorder: true,
 346:             shadow: false,
 347:             background: '{$bgcolor}',
 348:         },
 349:     })
 350: });
 351: 
 352: EOT;
 353: 
 354:     return $js;
 355: }

花粉飛散量の棒グラフを描画するユーザー定義関数は plot だが、クライアント側で JavaScriptライブラリ jqPlot を使って棒グラフを描いている。jqPlot の使い方は、「PHPでNHK政治意識月例調査をグラフ表示」をご覧いただきたい。

PHP側では、前述の getPollen メソッドを使って取得した花粉飛散量データ配列 $itemsjqPlot で扱いやすいように、JSON形式に変換する。
まず、プロットするデータは配列 $value に格納する。
X軸のラベルだが、1時間おきにデータがあるので、1日につき24個のデータがプロットされる。この1つ1つに日時ラベルを表示するとX軸がまっ黒になってしまうので、月日までを表示する工夫を施した。すなわち、配列 $ticks に "[$cnt, $label]" というデータセットを代入していく。ここで、変数 $cnt は先頭データを 0 としたときの順番、$label は表示したい月日である。もし直前の $label と違う月日であれば配列 $ticks に格納するが、違えば格納しない。こうするこことで、配列 $ticks$cnt が24ずつ離れた形の配列となる。
これを JSON 化して、jqPlotaxes オブジェクトに渡すことで、月日が切り替わるタイミング(午前0時)でX軸にラベルを表示するようになる。

メイン・プログラム

pollenGraph.php

 487: // メイン・プログラム =======================================================
 488: 
 489: // インスタンスを生成する.
 490: $pcc = new pahooCache(LIFE_CACHE_DATA, DIR_CACHE_DATA);
 491: $pccCityCode = new pahooCache(LIFE_CACHE_DATA_CITYCODE, DIR_CACHE_DATA);
 492: $pwt = new pahooWeather($pcc);
 493: $pgc = new pahooGeoCode();
 494: 
 495: // 各種パラメータ取得と初期値
 496: $errmsg = $address = $jsGraph = '';
 497: $count  = 0;
 498: $items    = array();
 499: // 動作モード
 500: $mode = (int)getValidNumber('mode', $errmsg, 0, TRUE, 0, 1);
 501: if ($errmsg !== '') {
 502:     $errmsg = '表示モードの値が ' . $errmsg;
 503: }
 504: // 市区町村コード
 505: $cityCode = (string)getValidString('cityCode', $errmsg, '', 5, 5, TRUE, ['/^\d{5}$/ui']);
 506: if ($errmsg !== '') {
 507:     $errmsg = '市区町村コードが ' . $errmsg;
 508: }
 509: // 各種パラメータ
 510: $query = (string)getValidString('query', $errmsg, '', QUERY_MIN_LEN, QUERY_MAX_LEN);
 511: $countOnly = isButton('count');
 512: $latitude  = getParam('latitude',  FALSE, DEF_LATITUDE);    // 緯度
 513: $longitude = getParam('longitude', FALSE, DEF_LONGITUDE);   // 経度
 514: $zoom      = getParam('zoom',      FALSE, DEF_ZOOM);        // ズーム
 515: $type      = getParam('type',      FALSE, DEF_TYPE);        // マップタイプ
 516: $category  = getParam('category',  FALSE, DEF_CATEGORY);    // カテゴリ
 517: $pgc->setYOLP_GeoSelectCategory($category);
 518: $start    = date('Ymd', strtotime('-7 days'));
 519: $end      = date('Ymd');
 520: 
 521: // 検索キーをクリアする.
 522: if (isButton('clear')) {
 523:     $query     = '';
 524:     $latitude  = DEF_LATITUDE;
 525:     $longitude = DEF_LONGITUDE;
 526:     $zoom      = DEF_ZOOM;
 527:     $type      = DEF_TYPE;
 528:     $category  = DEF_CATEGORY;
 529:     $errmsg = $address = $jsGraph = '';
 530: 
 531: // 緯度・経度を取得する.
 532: else if (($errmsg === ''&& ! isButton('map'&& ($query !== '')) {
 533:     list($n, $url) = $pgc->searchPoint3($query, GEOSERVICE, $category);
 534:     // エラーがなければ最初の住所を対象にする
 535:     if (! $pgc->iserror()) {
 536:         list($latitude, $longitude, $address) = $pgc->getPoint(1);
 537:         if ($address === '') {
 538:             $address = (($arr = $pgc->getAddress3($latitude, $longitude, REVGEOSERVICE)) == FALSE? '' : $arr['address'];
 539:         }
 540:     } else {
 541:         $errmsg = $pgc->geterror();
 542:     }
 543: 
 544: // 住所を取得する.
 545: else if (($errmsg === ''&& isButton('map')) {
 546:     $query = $address = (($arr = $pgc->getAddress3($latitude, $longitude, REVGEOSERVICE)) == FALSE? '' : $arr['address'];
 547:     
 548: 
 549: // 市区町村コードから住所を取得する.
 550: else if ($cityCode !== '') {
 551:     $address = $pgc->localGovernmentCode2address($pccCityCode, $cityCode);
 552:     $errmsg = $pgc->geterror();
 553: 
 554: // 緯度・経度から住所を取得
 555: else {
 556:     $address = (($arr = $pgc->getAddress3($latitude, $longitude, REVGEOSERVICE)) == FALSE? '' : $arr['address'];
 557: }
 558: 
 559: $arr = $pgc->address2LocalGovernmentCode($pccCityCode, $address);
 560: if ($arr === FALSE) {
 561:     $errmsg = '該当市区町村が見当たりません';
 562:     $cityCode = '00000';
 563:     $shortAddress = '';
 564: else {
 565:     $cityCode = (string)$arr[0];
 566:     $shortAddress = $arr[1];
 567: }
 568: 
 569: if ($errmsg === '') {
 570:     $count = $pwt->getPollen($cityCode, $start, $end, $items);
 571:     $title'花粉飛散量';
 572:     $jsGraph = plot(GRAPH_ID, $items[$cityCode], $title, $shortAddress);
 573: }
 574: 
 575: // マップ描画スクリプト
 576: $jsmap = $pgc->drawJSMap(MAP_ID, $latitude, $longitude, $type, $zoom, NULL, NULL, MAPSERVICE, NULL, NULL, NULL, NULL, CENTER_MARKER);
 577: 
 578: // 場所情報を配列へ
 579: $spot['query']     = $query;
 580: $spot['address']   = $address;
 581: $spot['cityCode']  = $cityCode;
 582: $spot['longitude'] = $longitude;
 583: $spot['latitude']  = $latitude;
 584: $spot['address']   = $address;
 585: $spot['zoom']      = $zoom;
 586: $spot['type']      = $type;
 587: 
 588: // 表示用HTMLを作成する.
 589: $HtmlBody = makeCommonBody($mode, $errmsg, $count, $jsmap, $jsGraph, $spot, $pwt->webapi, $pgc);
 590: 
 591: // 花粉飛散数データの件数だけ表示
 592: if ($countOnly) {
 593:     echo $count;
 594: 
 595: // 画面に表示する.
 596: else {
 597:     echo $HtmlHeader;
 598:     echo $HtmlBody;
 599:     echo $HtmlFooter;
 600: }
 601: 
 602: // インスタンスを解放する.
 603: $pgc = $pwt = $pcc = $pccCityCode = NULL;
 604: 
 605: /*

メイン・プログラムは、冒頭のサンプル・プログラムの流れ(メイン)をそのままコードにした形である。

参考サイト

(この項おわり)
header