C++ で週間天気予報を表示する

(1/1)
>C++で週間天気予報を表示する
本プログラムは、「PHPで天気予報を求める」「PHPで地図で指定した場所の天気予報を求める」で作ったPHPプログラムをC++に移植したものである。
インターネット経由で気象庁防災情報XMLにアクセスし、地図や住所、ランドマーク,郵便番号等で指定した地点の週間天気予報を一覧表示するアプリケーションを作る。一覧情報をクリップボードにコピーしたり、CSV形式ファイルに保存することができる。また、ユーザーがAPIキーを取得することでGoogleマップやGoogle住所検索も利用できるようになる。

(2025年3月22日)ネット接続チェック強化,キャッシュシステム不具合修正,使用ライブラリ更新
(2025年2月23日)pahooGeocode.cppの不具合修正,指定した場所が予報地点外の時にエラー表示,予報地点情報ファイルおよび使用ライブラリを更新
(2024年11月9日)予報地点情報ファイルおよび使用ライブラリを更新.
(2024年8月24日)使用ライブラリを更新.
(2024年5月6日)Edgeブラウザ対応,64ビット対応.予報地点情報ファイルを更新.API入力処理を改良.起動時に予報地点情報ファイルを自動更新.

目次

サンプル・プログラム

圧縮ファイルの内容
weeklyweather.msiインストーラ
bin/weeklyweather.exe実行プログラム本体
bin/jmaweatherspots.xml予報地点情報ファイル
bin/cwebpage.dll
bin/libcurl.dll
実行時に必要になるDLL
bin/etc/help.chmヘルプ・ファイル
sour/weeklyweather.cppソース・プログラム
sour/resource.hリソース・ヘッダ
sour/resource.rcリソース・ファイル
sour/application.icoアプリケーション・アイコン
sour/mystrings.cpp汎用文字列処理関数など(ソース)
sour/mystrings.h汎用文字列処理関数など(ヘッダ)
sour/pahooGeocode.cpp住所・緯度・経度に関わるクラス(ソース)
sour/pahooGeocode.hpp住所・緯度・経度に関わるクラス(ヘッダ)
sour/pahooWeather.cpp気象情報に関わるクラス(ソース)
sour/pahooWeather.hpp気象情報に関わるクラス(ヘッダ)
sour/pahooCache.cppキャッシュ処理に関わるクラス(ソース)
sour/pahooCache.hppキャッシュ処理に関わるクラス(ヘッダ)
sour/WebView2.hWebView2に関わるヘッダ
sour/event.hWebView2用インターフェース(ヘッダ)
sour/event.cppWebView2用インターフェース(ソース)
sour/pahooWebView2.cppWebView2に関わる関数(ソース)
sour/pahooWebView2.hppWebView2に関わる関数(ヘッダ)
sour/makefileビルド
weeklyweather.cpp 更新履歴
バージョン 更新日 内容
3.4.0 2025/03/22 Leafletアクセス可否チェック追加,キャッシュシステム不具合修正,使用ライブラリ更新
3.3.0 2025/02/23 指定した場所が予報地点外の時にエラー表示
3.2.2 2024/11/09 予報地点情報ファイルおよび使用ライブラリを更新
3.2.1 2024/08/24 使用ライブラリを更新
3.2.0 2024/05/06 起動時に予報地点情報ファイルを自動更新
pahooWeather.cpp 更新履歴
バージョン 更新日 内容
2.4.0 2025/02/23 getJmaNearSpot() -- 引数 distanceMax 追加
2.3.0 2024/05/06 isExistsNewerFileSpots(), updateFileSpots()追加
2.2.1 2024/04/29 64ビット対応
2.2 2022/03/12 気象庁防災情報XMLのhttps化に対応
2.1 2021/04/14 キャッシュ・システム導入:pahooCacheクラス
pahooGeocode.cpp 更新履歴
バージョン 更新日 内容
1.9.0 2025/03/16 HTTPステータス・エラーをキャッチアップ
1.8.1 2025/02/23 setError() -- bug-fix
1.8.0 2024/05/03 getMyPath()をapikey.appのgetMyPath()関数に変更
1.7.0 2024/04/27 Edge対応に伴いデバッグ用コードを廃棄
1.6.0 2023/07/02 getPointsGSI()メソッド追加
pahooCache.cpp 更新履歴
バージョン 更新日 内容
1.1.0 2025/03/16 不具合修正
1.0 2021/04/14 初版
mystrings.cpp 更新履歴
バージョン 更新日 内容
1.3.1 2025/03/16 readWebContents() リダイレクト有効に
1.3.0 2025/03/16 readWebContents() 引数httpStatus追加
1.2.0 2024/05/06 getModulePath() 追加
1.12 2021/01/31 readWebContents() 引数post追加
1.11 2020/10/17 htmlspecialchars() 追加
pahooWebView2.cpp 更新履歴
バージョン 更新日 内容
1.0.0 2024/04/27 初版
apikey.cpp 更新履歴
バージョン 更新日 内容
2.0.0 2024/04/29 createSetAPIkey, processSetAPIkey に統合
1.0 2020/09/30 初版

使用ライブラリ

気象庁週間予報サイトにアクセスするために、オープンソースのライブラリ Boost C++ライブラリcURL (カール)  および OpenSSL が必要になる。導入方法等については、「C++ 開発環境の準備」をご覧いただきたい。
また、地図表示にWebブラウザ・コントロールを利用するため "WebView2Loader.dll" を利用する。jchv / webview2-in-mingw からダウンロードできる。

リソースの準備

64ビット版の開発環境を用いる。
Eclipse を起動し、新規プロジェクト weeklyweather を用意する。
ResEdit を起動し、resource.rc を用意する。
Eclipse に戻り、ソース・プログラム "weeklyweather.cpp" を追加する。
リンカー・フラグを -Wl,--enable-stdcall-fixup -mwindows -lgdiplus -static -lstdc++ -lgcc -lwinpthread -lcurl -lssl "C:\(libcurl-x64.dllのフォルダ)\libcurl-x64.dll" "C:\(WebView2Loader.dllのフォルダ)\WebView2Loader.dll" "C:\Windows\system32\GdiPlus.dll" に設定する。
また、コマンド行パターンを ${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS} -lole32 -loleaut32 -luuid にすること。

MSYS2 コマンドラインからビルドするのであれば、"makefile" を利用してほしい。

解説:定数など

weeklyweather.cpp

  41: // 定数など ==================================================================
  42: #define MAKER       "pahoo.org"         // 作成者
  43: #define APPNAME     "weeklyweather"     // アプリケーション名
  44: #define APPNAMEJP   "週間天気予\報"      // アプリケーション名(日本語)
  45: #define APPVERSION  "3.3.0"             // バージョン
  46: #define APPYEAR     "2020-25"           // 作成年
  47: #define REFERENCE   "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-16-01.shtm"   // 参考サイト
  48: 
  49: // ヘルプ・ファイル
  50: #define HELPFILE    ".\\etc\\help.chm"
  51: 
  52: // デフォルト保存ファイル名
  53: #define SAVEFILE    "weeklyweather.csv"
  54: 
  55: // キャッシュ・ディレクトリ
  56: #define CACHEDIR_WEATHER    "pcache\\"
  57: 
  58: // キャッシュ保持時間(デフォルト;分)(0:キャッシュしない)
  59: #define LIFE_CACHE_WEATHER  (2 * 60)
  60: 
  61: // 予報表のID
  62: #define IDC_CAL_LABEL   1501        // 日付
  63: #define IDC_RES_LABEL   1601        // 天気予報
  64: #define IDC_RES_IMAGE   1701        // 天気アイコン
  65: #define IDC_TEMP_LABEL  1801        // 降水確率・最低・最高気温
  66: 
  67: // 予報表の座標
  68: #define IDC_RES_X       10
  69: #define IDC_RES_Y       430
  70: #define IDC_RES_WIDTH   80
  71: 
  72: #define FORECAST_HEIGHT 140     // 予報表の高さ
  73: 
  74: // マップに表示するマーカー画像URL
  75: #define URL_MARKER "https://maps.google.co.jp/mapfiles/ms/icons/yellow-dot.png"
  76: #define MAP_WIDTH       530     // 地図の幅(ピクセル)
  77: #define MAP_HEIGHT      300     // 地図の高さ(ピクセル)
  78: 
  79: // マップID
  80: #define MAP_ID          "map_id"
  81: // 経度・緯度(初期値)
  82: #define DEF_LONGITUDE   139.766667
  83: double Longitude = DEF_LONGITUDE;
  84: #define DEF_LATITUDE    35.681111
  85: double Latitude  = DEF_LATITUDE;
  86: // 地図拡大率(初期値)
  87: #define DEF_ZOOM    6
  88: int Zoom = DEF_ZOOM;
  89: // 地図の種類(初期値)
  90: #define DEF_MAPTYPE     "GSISTD"
  91: string Maptype = DEF_MAPTYPE;
  92: 
  93: // 予報地点までの最大距離(km)
  94: #define DISTANCE_MAX    400

とくに注意記載が無い限り、定数は自由に変更できる。

解説:pahooWeatherクラスとデータ構造

今回は、気象庁防災情報XMLを解析することで天気予報情報を取り出すのだが、必要なメソッドとデータ構造をPHPプログラムから移植し、クラス pahooWeather クラス としてコーディングした。ソースは "pahooWeather.cpp" ヘッダは "pahooWeather.hpp" である。

pahooWeather.hpp

  26: //天気予報情報
  27: typedef struct _jmaWeeklyWeather {
  28:     int year;                       //西暦年
  29:     int month;                      //月
  30:     int day;                        //日
  31:     std::wstring day_of_week;       //曜日
  32:     std::wstring weather;           //天気予報
  33:     std::string  image;             //天気予報アイコンURL
  34:     std::string  rainy;             //降水確率
  35:     std::string  temp_max;          //最高気温
  36:     std::string  temp_min;          //最低気温
  37:     std::string  stationCode;       //予報地点コード
  38:     std::wstring stationName;       //予報地点名
  39:     std::wstring location;          //所在地
  40: jmaWeeklyWeather_t;

1日分の予報情報は、構造体 forecast_t に格納する。これを7日分、配列として管理する。

pahooWeather.cpp

  52: // 予報地点情報格納用クラス ===================================================
  53: #define FILE_VERSION "2.2"          // 予報値地点情報ファイルのバージョン
  54: #define SIZE_SPOTS  500             // 格納上限
  55: 
  56: class _Spots {
  57: public:
  58:     string  code        = "";       // 電文コード
  59:     string  page        = "";       // ページ番号
  60:     string  regionCode  = "";       // 地方コード
  61:     wstring regionName  = L"";      // 地方名
  62:     string  prefCode    = "";       // 都道府県コード
  63:     wstring prefName    = L"";      // 都道府県名
  64:     string  areaCode    = "";       // 地域コード
  65:     wstring areaName    = L"";      // 地域名
  66:     string  stationCode = "";       // 予報地点コード
  67:     wstring stationName = L"";      // 予報地点名
  68:     wstring location    = L"";      // 所在地
  69:     double  latitude    = 0.0;      // 緯度
  70:     double  longitude   = 0.0;      // 経度
  71: };
  72: static unique_ptr<_Spots> Spots[SIZE_SPOTS] = {};

予報地点の情報は、あらかじめ FILE_SPOTS_LOCAL で示されるXMLファイルに格納しておく(起動時に自動更新;後述)。このファイルは、「PHPで天気予報を求める」で紹介したプログラムを使って作成することができる。

pahooWeather.cpp

 221: /**
 222:  * 予報地点情報を読み込む
 223:  * @param   なし
 224:  * @return  int 読み込んだ地点情報数/(-1):読み込み失敗
 225:  */
 226: int pahooWeather::readJmaSpots(void) {
 227:     int cnt = -1;
 228:     try {
 229:         // XMLファイル読み込み
 230:         string path = getModulePath();
 231:         ptree pt;
 232:         xml_parser::read_xml(path + "\\" + FILE_SPOTS_LOCAL, pt);
 233:         // XML解釈
 234:         ptree tree;
 235:         for (auto it : pt.get_child("jmaweatherspots")) {
 236:             cnt++;
 237:             Spots[cnt] = make_unique<_Spots>();
 238:             // 電文コード
 239:             if (optional<string>code = it.second.get_optional<string>("code")) {
 240:                 Spots[cnt]->code = code.value();
 241:             }
 242:             // ページ番号
 243:             if (optional<string>page = it.second.get_optional<string>("page")) {
 244:                 Spots[cnt]->page = page.value();
 245:             }
 246:             // 地方コード
 247:             if (optional<string>regionCode = it.second.get_optional<string>("regionCode")) {
 248:                 Spots[cnt]->regionCode = regionCode.value();
 249:             }
 250:             // 地方名
 251:             if (optional<string>regionName = it.second.get_optional<string>("regionName")) {
 252:                 Spots[cnt]->regionName = _UW(regionName.value());
 253:             }
 254:             // 都道府県コード
 255:             if (optional<string>prefCode = it.second.get_optional<string>("prefCode")) {
 256:                 Spots[cnt]->prefCode = prefCode.value();
 257:             }
 258:             // 都道府県名
 259:             if (optional<string>prefName = it.second.get_optional<string>("prefName")) {
 260:                 Spots[cnt]->prefName = _UW(prefName.value());
 261:             }
 262:             // 地域コード
 263:             if (optional<string>areaCode = it.second.get_optional<string>("areaCode")) {
 264:                 Spots[cnt]->areaCode = areaCode.value();
 265:             }
 266:             // 地域名
 267:             if (optional<string>areaName = it.second.get_optional<string>("areaName")) {
 268:                 Spots[cnt]->areaName = _UW(areaName.value());
 269:             }
 270:             // 予報地点コード
 271:             if (optional<string>stationCode = it.second.get_optional<string>("stationCode")) {
 272:                 Spots[cnt]->stationCode = stationCode.value();
 273:             }
 274:             // 予報地点名
 275:             if (optional<string>stationName = it.second.get_optional<string>("stationName")) {
 276:                 Spots[cnt]->stationName = _UW(stationName.value());
 277:             }
 278:             // 所在地
 279:             if (optional<string>location = it.second.get_optional<string>("location")) {
 280:                 Spots[cnt]->location = _UW(location.value());
 281:             }
 282:             //
 283:             // 経度
 284:             if (optional<string>longitude = it.second.get_optional<string>("longitude")) {
 285:                 Spots[cnt]->longitude = stod(longitude.value());
 286:             }
 287:             // 緯度
 288:             if (optional<string>latitude = it.second.get_optional<string>("latitude")) {
 289:                 Spots[cnt]->latitude = stod(latitude.value());
 290:             }
 291:         }
 292:     // 読み込みエラー
 293:     } catch(xml_parser_error& e) {
 294:         errmsg = _SW("予\報地点情報ファイルを読み込めません");
 295:         return (-1);
 296:     }
 297: 
 298:     return cnt;
 299: }

予報地点の情報は、メソッド readJmaSpots を使い、前述の構造体配列 Spots へ格納する。

pahooWeather.cpp

 342: /**
 343:  * 指定した緯度・経度に最も近い予報地点コードを返す
 344:  * @param   double latitude  緯度(世界測地系)
 345:  * @param   double longitude 経度(世界測地系)
 346:  * @param   int    forecast  0:天気予報,1:週間天気予報(省略時:1)
 347:  * @param   float  distanceMax 予報地点からの最大距離(km)
 348:  *                      これより近い予報地点が見つからなければ空文字を返す
 349:  * @return  string stationCode 予報地点コードを格納
 350:  */
 351: string pahooWeather::getJmaNearSpot(double longitude, double latitude, int forecast=1, double distanceMax=99999.9) {
 352:     string stationCode = "";
 353:     double d0  = 999999999.9;
 354:     // 電文コード
 355:     string code = "";
 356:     if (forecast == 0) {
 357:         code = "VPFD51";
 358:     } else {
 359:         code = "VPFW50";
 360:     }
 361: 
 362:     // 最も近い予報地点を探す
 363:     for (int i = 0i < SIZE_SPOTSi++) {
 364:         if (Spots[i] == NULL) {
 365:             break;
 366:         }
 367:         if (Spots[i]->code == code) {
 368:             double d1 = this->distance(Spots[i]->longitude, Spots[i]->latitude, longitude, latitude);
 369:             if (d1 < d0) {
 370:                 stationCode = Spots[i]->stationCode;
 371:                 d0 = d1;
 372:             }
 373:         }
 374:     }
 375: 
 376:     // 戻り値
 377:     if (d0 > distanceMax * 1000.0) {
 378:         this->setError(_SW("予\報地点が見つかりません"));
 379:         return "";
 380:     } else {
 381:         return stationCode;
 382:     }
 383: }

メソッド getJmaNearSpot は、緯度・経度を引数として、前述の予報地点情報を収めた構造体配列 Spots を参照し、一番近い予報地点コードを返す。

pahooWeather.cpp

 643: /**
 644:  * 気象庁防災情報XMLから天気予報情報を読み込む
 645:  * @param   string station  予報地点コード
 646:  * @param   int    forecast 0:天気予報,1:週間天気予報(省略時:1)
 647:  *                  0のとき‥‥jmaWeeklyWeather[0](本日)〜[6](6日後)に代入
 648:  *                  1のとき‥‥jmaWeeklyWeather[0](本日)〜[2](2日後)に代入
 649:  * @return  bool TRUE:成功/FALSE:失敗
 650: */
 651: bool pahooWeather::jmaGetWeatherForecast(string station, int forecast) {
 652:     // 曜日
 653:     static wstring week_name[] = {_SW("日"), _SW("月"), _SW("火"), _SW("水"), _SW("木"), _SW("金"), _SW("土")};
 654: 
 655:     // マッチングパターン
 656:     static regex re11("([0-9]+)\\-([0-9]+)\\-([0-9]+)");                // 年月日
 657:     static regex re12("([0-9]+)\\-([0-9]+)\\-([0-9]+)T([0-9]+)\\:");    // 年月日時
 658:     static wregex re21(_SW("から"));                                // 降水確率
 659:     static wregex re31(_SW("最低気温"));                            // 最低気温
 660:     static wregex re32(_SW("^日中の最高気温"));                     // 最高気温
 661:     static regex re41("^[0-9]+$");                                  // 正数
 662:     static smatch  mt1;
 663:     static wsmatch mt2;
 664:     int n;
 665: 
 666:     // 電文コード
 667:     string code;
 668:     if (forecast == 0) {
 669:         code = "VPFD51";
 670:     } else {
 671:         code = "VPFW50";
 672:     }
 673: 
 674:     // 予報地点コードの取得
 675:     bool res = FALSE;
 676:     int id;
 677:     for (id = 0id < SIZE_SPOTSid++) {
 678:         if (Spots[id] == NULL) {
 679:             break;
 680:         }
 681:         if ((Spots[id]->code == code&& (Spots[id]->stationCode == station)) {
 682:             res = TRUE;
 683:             break;
 684:         }
 685:     }
 686:     if (res == FALSE) {
 687:         errmsg = _SW("予\報地点コードが見つかりません");
 688:         return FALSE;
 689:     }
 690: 
 691:     // 初日のみ初期化
 692:     time_t now = time(NULL);
 693:     struct tm* pnow = localtime(&now);
 694:     namespace gr = boost::gregorian;
 695:     gr::date dt(pnow->tm_year + 1900, pnow->tm_mon + 1, pnow->tm_mday);
 696:     int i = 0, dd = 0, d2 = 0;
 697:     // 天気予報アイコンを夜間にするかどうか
 698:     int mode = 0;
 699:     if (pnow->tm_hour >18) {
 700:         mode = 1;
 701:     }
 702: 
 703:     this->jmaWeeklyWeather[i].year  = dt.year();
 704:     this->jmaWeeklyWeather[i].month = dt.month();
 705:     this->jmaWeeklyWeather[i].day   = dt.day();
 706:     this->jmaWeeklyWeather[i].day_of_week = week_name[dt.day_of_week()];
 707:     this->jmaWeeklyWeather[i].stationName = Spots[id]->stationName;
 708:     this->jmaWeeklyWeather[i].weather  = L"";
 709:     this->jmaWeeklyWeather[i].image    = "";
 710:     this->jmaWeeklyWeather[i].rainy    = "";
 711:     this->jmaWeeklyWeather[i].temp_max = "";
 712:     this->jmaWeeklyWeather[i].temp_min = "";
 713: 
 714:     // 最新の週間天気予報情報URLを取得
 715:     long page           = stol(Spots[id]->page);
 716:     string areaCode     = Spots[id]->areaCode;
 717:     string stationCode  = Spots[id]->stationCode;
 718:     wstring stationName = Spots[id]->stationName;
 719:     wstring location    = Spots[id]->location;
 720: 
 721:     string vpfd51="", vpfw50="";
 722:     jmaGetWeatherForecastURL(page, &vpfd51, &vpfw50);
 723:     if (errmsg !L"") {
 724:         return FALSE;
 725:     }
 726: 
 727:     // VPFW51(府県週間天気予報)の解析
 728:     string contents = "";
 729:     if (forecast == 1) {
 730:         // XML読み込み
 731:         if (pCC->load(vpfw50, &contents) == FALSE) {
 732:             errmsg = pCC->getError();
 733:             return FALSE;
 734:         }
 735:         try {
 736:             std::stringstream ss;
 737:             ss << contents;
 738:             ptree pt;
 739:             xml_parser::read_xml(ss, pt);
 740: 
 741:             // XML解釈
 742:             try {
 743:                 // 年月日取得
 744:                 for (auto it : pt.get_child("Report.Body.MeteorologicalInfos.TimeSeriesInfo.TimeDefines")) {
 745:                     if (optional<string>dti = it.second.get_optional<string>("DateTime")) {
 746:                         if (regex_search(dti.value(), mt1, re11)) {
 747:                             optional<string>tid = it.second.get_optional<string>("<xmlattr>.timeId");
 748: //                          cout << tid.value() << " : " << mt1[0].str() << endl;
 749:                             // 最初の情報は何時か
 750:                             i = stoi(tid.value());
 751:                             gr::date dt2(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
 752: 
 753:                             if (i == 1) {
 754:                                 if (dt < dt2) {
 755:                                     dd = 0;         // 明日
 756:                                 } else if (dt == dt2) {
 757:                                     dd = (-1);      // 今日
 758:                                 } else {
 759:                                     dd = (-2);      // 明日
 760:                                 }
 761:                             }
 762:                             i +dd;
 763:                             if (i < 0) {
 764:                                 continue;
 765:                             }
 766:                             // 予報1日分の初期化
 767:                             gr::date dt(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
 768:                             this->jmaWeeklyWeather[i].year  = stoi(mt1[1].str());
 769:                             this->jmaWeeklyWeather[i].month = stoi(mt1[2].str());
 770:                             this->jmaWeeklyWeather[i].day   = stoi(mt1[3].str());
 771:                             this->jmaWeeklyWeather[i].day_of_week = week_name[dt.day_of_week()];
 772:                             this->jmaWeeklyWeather[i].stationName = Spots[id]->stationName;
 773:                             this->jmaWeeklyWeather[i].weather  = L"";
 774:                             this->jmaWeeklyWeather[i].image    = "";
 775:                             this->jmaWeeklyWeather[i].rainy    = "";
 776:                             this->jmaWeeklyWeather[i].temp_max = "";
 777:                             this->jmaWeeklyWeather[i].temp_min = "";
 778:                         }
 779:                     }
 780:                 }
 781: 
 782:                 for (auto it : pt.get_child("Report.Body")) {
 783:                     // 天気・降水確率の取得
 784:                     if (optional<string>type = it.second.get_optional<string>("<xmlattr>.type")) {
 785:                         if (_UW(type.value()) == _SW("区域予\報")) {
 786:                             for (auto it2 : it.second.get_child("TimeSeriesInfo")) {
 787:                                 if (optional<string>acode = it2.second.get_optional<string>("Area.Code")) {
 788:                                     if (acode.value() !areaCode) {
 789:                                         continue;
 790:                                     }
 791:                                 }
 792:                                 for (auto it3 : it2.second.get_child("")) {
 793:                                     if (optional<string>type = it3.second.get_optional<string>("Property.Type")) {
 794:                                         if (_UW(type.value()) == _SW("天気")) {
 795:                                             // 天気予報の取得
 796:                                             for (auto it4 : it3.second.get_child("Property.WeatherPart")) {
 797:                                                 if (optional<string>we = it4.second.get_optional<string>("")) {
 798:                                                     i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
 799:                                                     if (i < 0) {
 800:                                                         continue;
 801:                                                     }
 802:                                                     this->jmaWeeklyWeather[i].weather = jmaShortWeather(_UW(we.value()));
 803:                                                 }
 804:                                             }
 805:                                             // 天気予報用テロップ番号の取得
 806:                                             for (auto it4 : it3.second.get_child("Property.WeatherCodePart")) {
 807:                                                 if (optional<string>we = it4.second.get_optional<string>("")) {
 808:                                                     i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
 809:                                                     if (i < 0) {
 810:                                                         continue;
 811:                                                     }
 812:                                                     if (i == 0) {
 813:                                                         this->jmaWeeklyWeather[i].image = jma_telop2url(stoi(we.value()), mode);
 814:                                                     } else {
 815:                                                         this->jmaWeeklyWeather[i].image = jma_telop2url(stoi(we.value()), 0);
 816:                                                     }
 817:                                                 }
 818:                                             }
 819:                                         } else if (_UW(type.value()) == _SW("降水確率")) {
 820:                                             // 降水確率の取得
 821:                                             for (auto it4 : it3.second.get_child("Property.ProbabilityOfPrecipitationPart")) {
 822:                                                 if (optional<string>we = it4.second.get_optional<string>("")) {
 823:                                                     i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
 824:                                                     if (i < 0) {
 825:                                                         continue;
 826:                                                     }
 827:                                                     this->jmaWeeklyWeather[i].rainy = we.value();
 828:                                                 }
 829:                                             }
 830:                                         }
 831:                                     }
 832:                                 }
 833:                             }
 834:                         } else if (_UW(type.value()) == _SW("地点予\報")) {
 835:                             for (auto it2 : it.second.get_child("TimeSeriesInfo")) {
 836:                                 if (optional<string>scode = it2.second.get_optional<string>("Station.Code")) {
 837:                                     if (scode.value() !stationCode) {
 838:                                         continue;
 839:                                     }
 840:                                 }
 841:                                 for (auto it3 : it2.second.get_child("")) {
 842:                                     for (auto it4 : it3.second.get_child("")) {
 843:                                         if (optional<string>type = it4.second.get_optional<string>("Type")) {
 844:                                             if (_UW(type.value()) == _SW("最低気温")) {
 845:                                                 // 最低気温の取得
 846:                                                 for (auto it5 : it4.second.get_child("TemperaturePart")) {
 847:                                                     if (optional<string>we = it5.second.get_optional<string>("")) {
 848:                                                         i = stoi(it5.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
 849:                                                         if (i < 0) {
 850:                                                             continue;
 851:                                                         }
 852:                                                         this->jmaWeeklyWeather[i].temp_min = we.value();
 853:                                                     }
 854:                                                 }
 855:                                             } else if (_UW(type.value()) == _SW("最高気温")) {
 856:                                                 // 最高気温の取得
 857:                                                 for (auto it5 : it4.second.get_child("TemperaturePart")) {
 858:                                                     if (optional<string>we = it5.second.get_optional<string>("")) {
 859:                                                         i = stoi(it5.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
 860:                                                         if (i < 0) {
 861:                                                             continue;
 862:                                                         }
 863:                                                         this->jmaWeeklyWeather[i].temp_max = we.value();
 864:                                                     }
 865:                                                 }
 866:                                             }
 867:                                         }
 868:                                     }
 869:                                 }
 870:                             }
 871:                         }
 872:                     }
 873:                 }
 874: 
 875:             // XML解釈エラー
 876:             } catch(ptree_bad_path& e) {
 877:                 errmsg = _SW("府県週間天気予\報情報を取得できません");
 878:                 return FALSE;
 879:             }
 880:         // 読み込みエラー
 881:         } catch(xml_parser_error& e) {
 882:             errmsg = _SW("府県週間天気予\報情報を取得できません");
 883:             return FALSE;
 884:         }
 885:     }
 886: 
 887:     // VPFD51(府県天気予報 R1)の解析
 888:     code = "VPFD51";
 889:     // 予報地点コードの取得
 890:     res = FALSE;
 891:     for (id = 0id < SIZE_SPOTSid++) {
 892:         if (Spots[id] == NULL) {
 893:             break;
 894:         }
 895:         if ((Spots[id]->code == code&& (Spots[id]->stationCode == station)) {
 896:             res = TRUE;
 897:             break;
 898:         }
 899:     }
 900:     if (res == FALSE) {
 901:         errmsg = _SW("予\報地点コードが見つかりません");
 902:         return FALSE;
 903:     }
 904:     areaCode = Spots[id]->areaCode;
 905: 
 906:     string rain_table[8] = { "-", "-", "-", "-", "-", "-", "-", "-", };
 907:     int temp_table[5] = { 0, 0, 0, 0, 0 };
 908:     // XML読み込み
 909:     string contents2 = "";
 910:     if (pCC->load(vpfd51, &contents2) == FALSE) {
 911:         errmsg = pCC->getError();
 912:         return FALSE;
 913:     }
 914:     try {
 915:         std::stringstream ss;
 916:         ss << contents2;
 917:         ptree pt;
 918:         xml_parser::read_xml(ss, pt);
 919: 
 920:         // XML解釈
 921:         try {
 922:             // 年月日取得
 923:             for (auto it : pt.get_child("Report.Body")) {
 924:                 if (optional<string>type = it.second.get_optional<string>("<xmlattr>.type")) {
 925:                     if (_UW(type.value()) == _SW("区域予\報")) {
 926:                         // 日時
 927:                         for (auto it2 : it.second.get_child("")) {
 928:                             for (auto it3 : it2.second.get_child("")) {
 929:                                 for (auto it4 : it3.second.get_child("")) {
 930:                                     if (optional<string>name = it4.second.get_optional<string>("Name")) {
 931:                                         wstring ws = _UW(name.value());
 932: 
 933:                                         if (regex_search(ws, mt2, re21)) {
 934:                                             string ss = it4.second.get_optional<string>("DateTime").value();
 935:                                             regex_search(ss, mt1, re12);
 936:                                             optional<string>tid = it4.second.get_optional<string>("<xmlattr>.timeId");
 937: //                                          cout << tid.value() << " : " << mt1[0].str() << endl;
 938:                                             // 最初の情報は何時か
 939:                                             i = stoi(tid.value());
 940:                                             gr::date dt2(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
 941:                                             if (i == 1) {
 942:                                                 if (dt < dt2) {
 943:                                                     dd = 0;         // 明日
 944:                                                 } else if (dt == dt2) {
 945:                                                     dd = (-1);      // 今日
 946:                                                 } else {
 947:                                                     dd = (-2);      // 明日
 948:                                                 }
 949:                                                 d2 = stoi(mt1[4].str()) / 6;        // 6時間毎
 950:                                                 if (dd == (-2)) {
 951:                                                     d2 = d2 - 4 + 1;
 952:                                                 }
 953:                                             }
 954:                                         } else if (optional<string>s2 = it4.second.get_optional<string>("DateTime")) {
 955:                                             string ss = s2.value();
 956:                                             if (regex_search(ss, mt1, re11)) {
 957:                                                 // 最初の情報は何時か
 958:                                                 optional<string>tid = it4.second.get_optional<string>("<xmlattr>.timeId");
 959:                                                 i = stoi(tid.value());
 960:                                                 gr::date dt2(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
 961:                                                 if (i == 1) {
 962:                                                     if (dt < dt2) {
 963:                                                         dd = 0;         // 明日
 964:                                                     } else if (dt == dt2) {
 965:                                                         dd = (-1);      // 今日
 966:                                                     } else {
 967:                                                         dd = (-2);      // 明日
 968:                                                     }
 969:                                                 }
 970:                                             }
 971:                                         }
 972:                                     }
 973:                                 }
 974:                             }
 975:                             // 地方コードは上何桁で判定するか
 976:                             bool flag = FALSE;
 977:                             for (n = 5n >1n--) {
 978:                                 for (auto it3 : it2.second.get_child("")) {
 979:                                     if (optional<string>code = it3.second.get_optional<string>("Area.Code")) {
 980:                                         if (code.value().substr(0, n!areaCode.substr(0, n)) {
 981:                                             continue;
 982:                                         }
 983:                                         flag = TRUE;
 984:                                     }
 985:                                 }
 986:                                 if (flag) {
 987:                                     break;
 988:                                 }
 989:                             }
 990: //                          cout <<  "N = " << n << endl;
 991: //                          cout <<  "areaCode = " << areaCode << endl;
 992: 
 993:                             // 天気予報
 994:                             for (auto it3 : it2.second.get_child("")) {
 995:                                 if (optional<string>code = it3.second.get_optional<string>("Area.Code")) {
 996: 
 997:                                     if (code.value().substr(0, n!areaCode.substr(0, n)) {
 998:                                         continue;
 999:                                     }
1000:                                 }
1001: //                              cout << it3.first << endl;
1002:                                 if (optional<string>type = it3.second.get_optional<string>("Kind.Property.Type")) {
1003:                                     if (_UW(type.value()) == _SW("天気")) {
1004:                                         // 天気予報の取得
1005:                                         for (auto it4 : it3.second.get_child("Kind.Property.WeatherPart")) {
1006:                                             i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
1007:                                             if (i < 0) {
1008:                                                 continue;
1009:                                             }
1010:                                             this->jmaWeeklyWeather[i].weather = jmaShortWeather(_UW(it4.second.get_optional<string>("").value()));
1011:                                         }
1012:                                         // 天気予報用テロップ番号の取得
1013:                                         for (auto it4 : it3.second.get_child("Kind.Property.WeatherCodePart")) {
1014:                                             i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
1015:                                             if (i < 0) {
1016:                                                 continue;
1017:                                             }
1018:                                             if (i == 0) {
1019:                                                 this->jmaWeeklyWeather[i].image = jma_telop2url(stoi(it4.second.get_optional<string>("").value()), mode);
1020:                                             } else {
1021:                                                 this->jmaWeeklyWeather[i].image = jma_telop2url(stoi(it4.second.get_optional<string>("").value()), 0);
1022:                                             }
1023:                                         }
1024: 
1025:                                     } else if (_UW(type.value()) == _SW("降水確率")) {
1026:                                         // 降水確率の取得
1027:                                         for (auto it4 : it3.second.get_child("Kind.Property.ProbabilityOfPrecipitationPart")) {
1028:                                             i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd + d2;
1029:                                             if (i < 0) {
1030:                                                 continue;
1031:                                             }
1032:                                             rain_table[i] = it4.second.get_optional<string>("").value();
1033:                                         }
1034:                                     }
1035:                                 }
1036:                             }
1037:                         }
1038:                     } else if (_UW(type.value()) == _SW("地点予\報")) {
1039:                         // 日時
1040:                         int day0 = 0;
1041:                         for (auto it2 : it.second.get_child("")) {
1042:                             for (auto it3 : it2.second.get_child("")) {
1043:                                 for (auto it4 : it3.second.get_child("")) {
1044:                                     if (optional<string>s2 = it4.second.get_optional<string>("DateTime")) {
1045:                                         string ss = s2.value();
1046:                                         if (regex_search(ss, mt1, re11)) {
1047:                                             // 最初の情報は何時か
1048:                                             optional<string>tid = it4.second.get_optional<string>("<xmlattr>.timeId");
1049:                                             i = stoi(tid.value());
1050:                                             gr::date dt2(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
1051:                                             if (i == 1) {
1052:                                                 if (dt < dt2) {
1053:                                                     dd = 0;         // 明日
1054:                                                 } else if (dt == dt2) {
1055:                                                     dd = (-1);      // 今日
1056:                                                 } else {
1057:                                                     dd = (-2);      // 明日
1058:                                                 }
1059:                                                 temp_table[i] = 0;
1060:                                                 day0 = stoi(mt1[3]);
1061:                                             } else {
1062:                                                 if (day0 == stoi(mt1[3])) {
1063:                                                     temp_table[i] = 0;
1064:                                                 } else {
1065:                                                     temp_table[i] = 1;
1066:                                                 }
1067:                                                 day0 = stoi(mt1[3]);
1068:                                             }
1069:                                         }
1070:                                     }
1071:                                 }
1072:                             }
1073:                             // 最低気温・最高気温
1074:                             i = 1 + dd;
1075:                             for (auto it3 : it2.second.get_child("")) {
1076:                                 if (optional<string>code = it3.second.get_optional<string>("Station.Code")) {
1077: 
1078:                                     if (code.value() !stationCode) {
1079:                                         continue;
1080:                                     }
1081:                                 }
1082:                                 // 最高気温・最低気温
1083:                                 for (auto it4 : it3.second.get_child("")) {
1084:                                     for (auto it5 : it4.second.get_child("")) {
1085:                                         if (optional<string>ts = it5.second.get_optional<string>("Type")) {
1086:                                             wstring ws = _UW(ts.value());
1087: //                                          cout << _WS(ws) << endl;
1088:                                             if (regex_search(ws, mt2, re31)) {
1089:                                                 int i2 = stoi(it5.second.get_optional<string>("TemperaturePart.jmx_eb:Temperature.<xmlattr>.refID").value());
1090:                                                 i +temp_table[i2];
1091:                                                 if (i < 0) {
1092:                                                     continue;
1093:                                                 }
1094:                                                 this->jmaWeeklyWeather[i].temp_min = it5.second.get_optional<string>("TemperaturePart.jmx_eb:Temperature").value();
1095:                                             } else if (regex_search(ws, mt2, re32)) {
1096:                                                 int i2 = stoi(it5.second.get_optional<string>("TemperaturePart.jmx_eb:Temperature.<xmlattr>.refID").value());
1097:                                                 i +temp_table[i2];
1098:                                                 if (i < 0) {
1099:                                                     continue;
1100:                                                 }
1101:                                                 this->jmaWeeklyWeather[i].temp_max = it5.second.get_optional<string>("TemperaturePart.jmx_eb:Temperature").value();
1102:                                             }
1103:                                         }
1104:                                     }
1105:                                 }
1106:                             }
1107:                         }
1108:                     }
1109:                 }
1110:             }
1111: 
1112:         // XML解釈エラー
1113:         } catch(ptree_bad_path& e) {
1114:             errmsg = _SW("府県天気予\報情報を取得できません");
1115:             return FALSE;
1116:         }
1117:     // 読み込みエラー
1118:     } catch(xml_parser_error& e) {
1119:         errmsg = _SW("府県天気予\報情報を取得できません");
1120:         return FALSE;
1121:     }
1122: 
1123:     // 降水確率を天気予報情報へ
1124:     for (int j = 0j < 8j++) {
1125:         if ((dd == (-2)) && (j >4))   break;
1126:         i = floor(j / 4.0);
1127:         if (regex_search(jmaWeeklyWeather[i].rainy, mt1, re41)) {
1128:             this->jmaWeeklyWeather[i].rainy = "";
1129:         }
1130:         this->jmaWeeklyWeather[i].rainy +rain_table[j];
1131:         if (j % 4 !3) {
1132:             this->jmaWeeklyWeather[i].rainy +"/";
1133:         }
1134:     }
1135: 
1136: /** debug code
1137:     for (int j = 0; j <= 7; j++) {
1138:         cout << j << ":"
1139:                 << _WS(this->jmaWeeklyWeather[j].stationName) << ":"
1140:                 << this->jmaWeeklyWeather[j].year << "/"
1141:                 << this->jmaWeeklyWeather[j].month << "/"
1142:                 << this->jmaWeeklyWeather[j].day << "("
1143:                 << _WS(this->jmaWeeklyWeather[j].day_of_week) << ") "
1144:                 << _WS(this->jmaWeeklyWeather[j].weather) << " "
1145:                 << this->jmaWeeklyWeather[j].image << " "
1146:                 << this->jmaWeeklyWeather[j].rainy << "% "
1147:                 << this->jmaWeeklyWeather[j].temp_max << "/"
1148:                 << this->jmaWeeklyWeather[j].temp_min << "℃ "
1149:         << endl;
1150:     }
1151: */
1152: 
1153:     contents.clear();
1154:     contents2.clear();
1155: 
1156:     return TRUE;
1157: }

メソッド jmaGetWeatherForecast は、予報地点コードを引数として、その週間天気予報情報を読み込む。

解説:pahooCacheクラスとデータ構造

1週間分の予報を表示するには、気象庁防災情報XMLから、
  1. Atomフィード(長期フィード:定時)
  2. VPFW50
  3. VPFD51
の3つのXMLファイルを読み込む必要がある。このうちAtomフィード(長期フィード:定時)は約4Mバイトと大きく、毎回、ロードすることは気象庁サイトへ負荷を掛けることになる。そこで、PHPプログラムの場合と同様、一定時間、自サイトにXMLファイルを保持しておきキャッシュ・システムを導入することにする。

weeklyweather.cpp

  55: // キャッシュ・ディレクトリ
  56: #define CACHEDIR_WEATHER    "pcache\\"
  57: 
  58: // キャッシュ保持時間(デフォルト;分)(0:キャッシュしない)
  59: #define LIFE_CACHE_WEATHER  (2 * 60)

キャッシュ保持時間は定数 LIFE_CACHE_WEATHER に分で指定する。キャッシュ・ファイルを保存するディレクトリは、定数 CACHEDIR_WEATHER で指定する。このディレクトリは、各種パラメータの保存と同様、ユーザーのAppData配下に作成する。

たとえば120分(=2時間)を指定した場合、気象庁防災情報XMLへのアクセスは
 1日24時間÷2=12回
だけ行えばいい。VPFW50やVPFD51のXMLファイルのサイズは約16Kバイト。VPFW50の対象地点が70箇所、VPFD51の対象地点が170箇所であることから、1日あたりのアクセス量の最大値は
 4.0M×12+16K×70×12+16×170×12=141Mバイト
となる。

pahooCache.cpp

   1: /** pahooCache.cpp
   2:  * キャッシュ処理に関わるクラス:C++ソース
   3:  *
   4:  * @copyright   (c)studio pahoo
   5:  * @author      パパぱふぅ
   6:  * @動作環境    MinGW C++ + cURL + OpenSSL + Boost C++ Libraries
   7:  * @参考URL     https://www.pahoo.org/e-soul/webtech/cpp01-16-01.shtm
   8:  */
   9: #include <iostream>
  10: #include <fstream>
  11: #include <stdio.h>
  12: #include <stdlib.h>
  13: #include <tchar.h>
  14: #include <time.h>
  15: #include <direct.h>
  16: #include <sys/stat.h>
  17: #include <dirent.h>
  18: #include <sstream>
  19: #include <string>
  20: #include <regex>
  21: #include <locale>
  22: #include <math.h>
  23: #include <boost/property_tree/xml_parser.hpp>
  24: #include <boost/date_time/gregorian/gregorian.hpp>
  25: #include <boost/format.hpp>
  26: #include <boost/algorithm/hex.hpp>
  27: #include <boost/uuid/detail/md5.hpp>
  28: #include "mystrings.h"
  29: #include "pahooCache.hpp"
  30: 
  31: using namespace std;
  32: using namespace boost;
  33: using namespace boost::property_tree;
  34: using namespace boost::uuids::detail;
  35: 
  36: /**
  37:  * コンストラクタ
  38:  * @param   unsigned int life キャッシュ保持時間(分)
  39:  * @param   string dir        キャッシュ・ディレクトリ
  40:  * @param   string ua         UserAgent
  41:  * @return  なし
  42:  */
  43: pahooCache::pahooCache(unsigned int life, string dir, const string ua) {
  44:     // dirの最後にディレクトリ区切り文字がなければ追加
  45:     smatch mt;
  46:     regex re("\\\\$");
  47:     if (regex_search(dir, mt, re) == FALSE) {
  48:         dir = dir + "\\";
  49:     }
  50:     this->errmsg = L"";
  51:     this->lifeCache = life;
  52:     this->dirCache  = dir;
  53:     this->userAgent = ua;
  54: 
  55:     // キャッシュ・ディレクトリが無ければ作成
  56:     struct stat statBuf;
  57:     if (stat(this->dirCache.c_str(), &statBuf!0) {
  58:         if (_mkdir(this->dirCache.c_str()) !0) {
  59:             errmsg = _SW("キャッシュ・ディレクトリ \""+ _SW(this->dirCache+ _SW("\" の作成に失敗しました");
  60:         }
  61:     }
  62: }

キャッシュ保持時間とキャッシュ・ファイルを保存するディレクトリはコンストラクタで指定する。ディレクトリが無い場合、コンストラクタ内で生成するようにしてある。

pahooCache.cpp

 105: /**
 106:  * キャッシュの削除
 107:  * @param   string pat 削除ファイル名(正規表現指定可能)
 108:  * @param   unsigned int life  キャッシュ保持時間(分)
 109:  * @return  unsigned int 削除したファイル数
 110: */
 111: unsigned int pahooCache::delCache(string pat, unsigned int life) {
 112:     int cnt = 0;
 113:     string fullname;
 114:     struct stat st;
 115:     smatch mt;
 116:     regex re(pat);
 117: 
 118:     // 現在時刻(UTC)
 119:     time_t t;
 120:     time(&t);
 121:     std::tm* localTime = std::localtime(&t); 
 122:     // 現在時刻(ローカル時間)
 123:     std::time_t localEpoch = std::mktime(localTime);
 124: 
 125:     // ディレクトリ検索
 126:     DIR *dp;
 127:     dp = opendir(this->dirCache.c_str());
 128:     if (dp == NULL) {
 129:         return 0;
 130:     }
 131:     dirent* entry = readdir(dp);
 132:     while (entry !NULL){
 133:         if (entry !NULL) {
 134:             string fname = (string)entry->d_name;
 135:             // patに一致するファイル
 136:             if (regex_search(fname, mt, re)) {
 137:                 fullname = this->dirCache + fname;
 138:                 if (stat(fullname.c_str(), &st) == 0) {
 139:                     // 保持時間を過ぎたキャッシュ・ファイルは削除
 140:                     if ((long unsigned int)localEpoch - (long unsigned int)st.st_mtime > (life * 60)) {
 141:                         unlink(fullname.c_str());
 142:                         cnt++;
 143:                     }
 144:                 }
 145:             }
 146:         }
 147:         entry = readdir(dp);
 148:     }
 149:     closedir(dp);
 150: 
 151:     return cnt;
 152: }

pahooCache.cpp

 154: /**
 155:  * cURLによるコンテンツ取得
 156:  * @param   string url URL
 157:  * @param   string* contents コンテンツを格納
 158:  * @return  bool TRUE:読み込み成功/FALSE:失敗
 159: */
 160: bool pahooCache::cLoad(string url, string* contents) {
 161:     int httpStatus = 0;
 162:     bool res = readWebContents(url, (const string)this->userAgent, contents, &httpStatus);
 163:     if (res == FALSE) {
 164:         this->errmsg  = _SW("\""+ _SW(url+ _SW("\" が取得できません");
 165:         return FALSE;
 166:     } else if ((httpStatus < 200|| (httpStatus > 299)) {
 167:         this->errmsg = _SW("国土地理院ジオコーディングAPIの接続エラーが発生しました(HTTPステータスコード" + to_string(httpStatus+ ")");
 168:         return FALSE;
 169:     }
 170:     return TRUE;
 171: }

pahooCache.cpp

 168:         return FALSE;
 169:     }
 170:     return TRUE;
 171: }
 172: 
 173: /**
 174:  * MD5ハッシュ値を返す
 175:  * @param   string str 文字列
 176:  * @return  string MD5ハッシュ値
 177: */
 178: string pahooCache::md5(string str) {
 179:     boost::uuids::detail::md5 hash;
 180:     md5::digest_type digest;
 181:     hash.process_bytes(str.data(), str.size());
 182:     hash.get_digest(digest);
 183: 
 184:     const auto intDigest = reinterpret_cast<const int *>(&digest);

pahooCache.cpp

 191: /**
 192:  * ネットからコンテンツを読み込む
 193:  * @param   string url   URL
 194:  * @param   string* contents コンテンツを格納
 195:  * @return  bool TRUE:読み込み成功/FALSE:失敗
 196: */
 197: bool pahooCache::forceLoad(string url, string* contents) {
 198:     // キャッシュするファイル名
 199:     string fname = this->md5(url);
 200:     string fullname = this->dirCache + fname;
 201: 
 202:     bool ret = this->cLoad(url, contents);
 203:     ofstream fout;
 204:     fout.open(fullname, ios::out);
 205:     fout << *contents;
 206: 
 207:     if (ret == FALSE) {
 208:         return FALSE;
 209:     }
 210:     this->debug +url + "\n";
 211: 
 212:     return TRUE;
 213: }

pahooCache.cpp

 215: /**
 216:  * コンテンツを読み込む
 217:  * @param   string url   URL
 218:  * @param   string* contents コンテンツを格納
 219:  * @return  bool TRUE:読み込み成功/FALSE:失敗
 220: */
 221: bool pahooCache::load(string url, string* contents) {
 222:     bool res = FALSE;
 223:     string fname, fullname;
 224:     string buff;
 225:     ifstream fin;
 226: 
 227:     // キャッシュ有効
 228:     if (this->lifeCache > 0) {
 229:         this->delCache("[0-9a-f]+", this->lifeCache);   // 古いキャッシュを削除
 230:         fname = this->md5(url);                     // キャッシュするファイル名
 231:         fullname = this->dirCache + fname;
 232:         // キャッシュが存在する
 233:         *contents = "";
 234:         fin.open(fullname, ios::binary);
 235:         if (fin) {
 236: //          cout << "from file: " << fname << endl;
 237:             getline(fin, *contents, '\0');
 238:             if (contents->length() <0) {
 239:                 this->errmsg = _SW("キャッシュファイル \""+ _SW(fullname+ _SW("\" の読み込みに失敗しました");
 240: //              cout << _WS(this->errmsg) << endl;
 241:                 return FALSE;
 242:             }
 243:             this->debug +fname + "\n";
 244:             res = TRUE;
 245:         // ネットから取得
 246:         } else {
 247: //          cout << "from URL: " << url << endl;
 248:             res = this->forceLoad(url, contents);
 249:         }
 250: 
 251:     // キャッシュ無効
 252:     } else {
 253: //      cout << "uncache from URL: " << url << endl;
 254:         res = this->forceLoad(url, contents);
 255:     }
 256: 
 257:     return res;
 258: }

コンテンツを読み込むメソッドは load である。
キャッシュが有効の時は、まず、delCache メソッドを使って古いキャッシュを削除する。stat関数を使ってファイルのタイムスタンプを取得し、それを現在日時と比較することで削除するかどうかを判定している。
ッシュへ書き込む。

キャッシュ・ファイル名は、URLからMD5ハッシュを使って生成する。
キャッシュが存在すれば、getline関数を使って読み込む。
無ければ、forceLoad メソッドを使ってネットから読み込み、キャッシュへ書き込む。ネットからの読み込みは cLoad メソッドで、readWebContents関数を使って読み込んでいる。

予報地点情報ファイルの自動更新

mystrings.cpp

  60: /**
  61:  * 実行プログラムのパスを取得する.
  62:  * @param   なし
  63:  * @return  string パス/"":取得失敗
  64:  */
  65: string getModulePath(void) {
  66:     // バッファを確保して実行可能ファイルのパスを取得する.
  67:     TCHAR buffer[MAX_PATH];
  68:     GetModuleFileName(NULL, buffer, MAX_PATH);
  69: 
  70:     // パスからディレクトリを取得する.
  71:     std::string _fullPath(buffer);
  72:     std::string::size_type pos = _fullPath.find_last_of("\\/");
  73:     if (pos !std::string::npos) {
  74:         std::string directory = _fullPath.substr(0, pos);
  75:         return directory;
  76:     } else {
  77:         return "";
  78:     }
  79: }

定数のところで紹介した予報地点情報ファイル FILE_SPOTS_LOCALだが、不定期に更新される。そこで、本プログラム起動時に、最新ファイルをぱふぅ家のホームページから自動ダウンロードするようにした。更新の要否は、ファイルの中のupdateタグを比較して決定する。

まず、ローカル側のファイルの配置場所だが、本プログラムの起動しレク取りと同じところにある。そこで、起動プログラムの絶対パスを取得するユーザー関数 getModulePath を用意した。

pahooWeather.cpp

 137: /**
 138:  * ローカルの予報地点情報ファイルとpahoo.org上のファイルを比べる.
 139:  * @param   なし
 140:  * @return  bool TRUE:pahoo.org上でのファイルの方が新しい
 141:  */
 142: bool pahooWeather::isExistsNewerFileSpots(void) {
 143:     string xml;
 144:     string ssPahoo, ssLocal;
 145: 
 146:     // XML読み込み:pahoo.org
 147:     if (readWebContents(FILE_SPOTS_PAHOO, UserAgent, &xml, nullptr) == FALSE) {
 148:         return FALSE;
 149:     }
 150:     try {
 151:         ptree ptPahoo;
 152:         ptPahoo = readXmlFromString(xml);
 153:         // XML解釈
 154:         if (optional<string>updatePahoo = ptPahoo.get_optional<string>("jmaweatherspots.update")) {
 155:             ssPahoo = (string)updatePahoo.get();
 156: //          cout << "pahoo.org = " << ssPahoo << endl;
 157:         // XML解釈失敗→更新しない
 158:         } else {
 159:             return FALSE;
 160:         }
 161:     // 読み込みエラー→更新しない
 162:     } catch(xml_parser_error& e) {
 163:         return FALSE;
 164:     }
 165: 
 166:     // XML読み込み:ローカル
 167:     string path = getModulePath();
 168:     if (path == "") {
 169:         return FALSE;
 170:     }
 171:     try {
 172:         ptree ptLocal;
 173:         xml_parser::read_xml(path + "\\" + FILE_SPOTS_LOCAL, ptLocal);
 174:         // XML解釈
 175:         if (optional<string>updateLocal = ptLocal.get_optional<string>("jmaweatherspots.update")) {
 176:             ssLocal = (string)updateLocal.get();
 177: //          cout << "local = " << ssLocal << endl;
 178:         // XML解釈失敗→更新する
 179:         } else {
 180:             return FALSE;
 181:         }
 182:     // 読み込みエラー→更新する
 183:     } catch(xml_parser_error& e) {
 184:         return TRUE;
 185:     }
 186: 
 187:     return (bool)(ssPahoo.compare(ssLocal> 0);
 188: }

次に、"pahooWeather.cpp" に、ローカルの予報地点情報ファイルとpahoo.org上のファイルを比べるメソッド isExistsNewerFileSpots を用意した。
それぞれのファイルのupdateタグの内容を比較し、pahoo.org上のファイルが新しければTRUEを返す。
ただし、ローカル上のファイルの読み込みに失敗したり、updateタグが見つからないようなときはファイルが存在しないか壊れていると判断し、FALSEを返して強制的に更新する。
逆に、pahoo.org上のファイルの読み込みに失敗したり、updateタグが見つからないようなときはTRUEを返して更新しないようにする。

pahooWeather.cpp

 190: /**
 191:  * ローカルの予報地点情報ファイルが古ければ更新する.
 192:  * pahoo.org上のファイルと比べて,updateが古ければダウンロードして差し替える.
 193:  * @param   なし
 194:  * @return  bool TRUE:更新した/FALSE:更新しないまたはエラー
 195:  */
 196: bool pahooWeather::updateFileSpots(void) {
 197:     // 更新実行
 198:     if (this->isExistsNewerFileSpots()) {
 199:         string contents;
 200:         if (readWebContents(FILE_SPOTS_PAHOO, UserAgent, &contents, nullptr) == FALSE) {
 201:             return FALSE;
 202:         }
 203: 
 204:         string path = getModulePath();
 205:         if (path == "") {
 206:             return FALSE;
 207:         }
 208:         ofstream outputfile(path + "\\" + FILE_SPOTS_LOCAL);
 209:         outputfile << contents;
 210:         outputfile.close();
 211: 
 212:         return TRUE;
 213: 
 214:     // 更新しない
 215:     } else {
 216:         return FALSE;
 217:     }
 218: }

最後に、ローカルの予報地点情報ファイルが古ければ、とpahoo.org上のファイルをダウンロードして刺し返す updateFileSpots を用意した。

共通手順、モジュールなど

参考サイト

(この項おわり)
header