
目次
サンプル・プログラム
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.h | WebView2に関わるヘッダ |
sour/event.h | WebView2用インターフェース(ヘッダ) |
sour/event.cpp | WebView2用インターフェース(ソース) |
sour/pahooWebView2.cpp | WebView2に関わる関数(ソース) |
sour/pahooWebView2.hpp | WebView2に関わる関数(ヘッダ) |
sour/makefile | ビルド |
バージョン | 更新日 | 内容 |
---|---|---|
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 | 起動時に予報地点情報ファイルを自動更新 |
バージョン | 更新日 | 内容 |
---|---|---|
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クラス |
バージョン | 更新日 | 内容 |
---|---|---|
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()メソッド追加 |
バージョン | 更新日 | 内容 |
---|---|---|
1.1.0 | 2025/03/16 | 不具合修正 |
1.0 | 2021/04/14 | 初版 |
バージョン | 更新日 | 内容 |
---|---|---|
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() 追加 |
バージョン | 更新日 | 内容 |
---|---|---|
1.0.0 | 2024/04/27 | 初版 |
バージョン | 更新日 | 内容 |
---|---|---|
2.0.0 | 2024/04/29 | createSetAPIkey, processSetAPIkey に統合 |
1.0 | 2020/09/30 | 初版 |
使用ライブラリ
また、地図表示にWebブラウザ・コントロールを利用するため "WebView2Loader.dll" を利用する。jchv / webview2-in-mingw からダウンロードできる。
リソースの準備
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クラスとデータ構造
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;
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] = {};
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: }
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 = 0; i < SIZE_SPOTS; i++) {
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: }
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 = 0; id < SIZE_SPOTS; id++) {
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 = 0; id < SIZE_SPOTS; id++) {
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 = 5; n >= 1; n--) {
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 = 0; j < 8; j++) {
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: }
解説:pahooCacheクラスとデータ構造
- Atomフィード(長期フィード:定時)
- VPFW50
- VPFD51
weeklyweather.cpp
55: // キャッシュ・ディレクトリ
56: #define CACHEDIR_WEATHER "pcache\\"
57:
58: // キャッシュ保持時間(デフォルト;分)(0:キャッシュしない)
59: #define LIFE_CACHE_WEATHER (2 * 60)

たとえば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: }
キャッシュが有効の時は、まず、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: }

まず、ローカル側のファイルの配置場所だが、本プログラムの起動しレク取りと同じところにある。そこで、起動プログラムの絶対パスを取得するユーザー関数 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: }
それぞれのファイルの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: }
共通手順、モジュールなど
参考サイト
- PHPで天気予報を求める:ぱふぅ家のホームページ
- PHPで地図で指定した場所の週間カレンダーを表示:ぱふぅ家のホームページ
- 気象庁防災情報XMLフォーマット 情報提供ページ
インターネット経由で気象庁防災情報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入力処理を改良.起動時に予報地点情報ファイルを自動更新.