目次
サンプル・プログラム
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.2.1 | 2024/08/24 | 使用ライブラリを更新 |
3.2.0 | 2024/05/06 | 起動時に予報地点情報ファイルを自動更新 |
3.1.0 | 2024/05/03 | API入力処理を改良 |
3.0.0 | 2024/04/29 | Edgeブラウザ対応,64ビット対応,予報地点情報ファイル更新 |
2.5.7 | 2024/03/30 | 予報地点情報ファイルおよび使用ライブラリを更新 |
バージョン | 更新日 | 内容 |
---|---|---|
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クラス |
2.0 | 2021/03/16 | 気象庁防災情報XMLから情報取得に変更 |
バージョン | 更新日 | 内容 |
---|---|---|
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.5 | 2022/09/03 | デバッグコード埋め込み |
1.4 | 2021/05/01 | makeMapLeaflet() 引数追加 |
バージョン | 更新日 | 内容 |
---|---|---|
1.0 | 2021/04/14 |
バージョン | 更新日 | 内容 |
---|---|---|
1.2.0 | 2024/05/06 | getModulePath() 追加 |
1.12 | 2021/01/31 | readWebContents() 引数post追加 |
1.11 | 2020/10/17 | htmlspecialchars() 追加 |
1.1 | 2020/10/17 | GetVersion2()追加,readWebContents() 引数ua追加 |
1.01 | 2020/10/03 | setClipboardData() bug-fix |
バージョン | 更新日 | 内容 |
---|---|---|
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" を利用してほしい。
解説:定数など
41: // 定数など ==================================================================
42: #define MAKER "pahoo.org" //作成者
43: #define APPNAME "weeklyweather" //アプリケーション名
44: #define APPNAMEJP "週間天気予\報" //アプリケーション名(日本語)
45: #define APPVERSION "3.2.1" //バージョン
46: #define APPYEAR "2020-24" //作成年
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;
解説:pahooWeatherクラスとデータ構造
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;
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] = {};
212: /**
213: * 予報地点情報を読み込む
214: * @param なし
215: * @return int 読み込んだ地点情報数/(-1):読み込み失敗
216: */
217: int pahooWeather::readJmaSpots(void) {
218: int cnt = -1;
219: try {
220: //XMLファイル読み込み
221: ptree pt;
222: xml_parser::read_xml(FILE_SPOTS_LOCAL, pt);
223: //XML解釈
224: ptree tree;
225: for (auto it : pt.get_child("jmaweatherspots")) {
226: cnt++;
227: Spots[cnt] = make_unique<_Spots>();
228: //電文コード
229: if (optional<string>code = it.second.get_optional<string>("code")) {
230: Spots[cnt]->code = code.value();
231: }
232: //ページ番号
233: if (optional<string>page = it.second.get_optional<string>("page")) {
234: Spots[cnt]->page = page.value();
235: }
236: //地方コード
237: if (optional<string>regionCode = it.second.get_optional<string>("regionCode")) {
238: Spots[cnt]->regionCode = regionCode.value();
239: }
240: //地方名
241: if (optional<string>regionName = it.second.get_optional<string>("regionName")) {
242: Spots[cnt]->regionName = _UW(regionName.value());
243: }
244: //都道府県コード
245: if (optional<string>prefCode = it.second.get_optional<string>("prefCode")) {
246: Spots[cnt]->prefCode = prefCode.value();
247: }
248: //都道府県名
249: if (optional<string>prefName = it.second.get_optional<string>("prefName")) {
250: Spots[cnt]->prefName = _UW(prefName.value());
251: }
252: //地域コード
253: if (optional<string>areaCode = it.second.get_optional<string>("areaCode")) {
254: Spots[cnt]->areaCode = areaCode.value();
255: }
256: //地域名
257: if (optional<string>areaName = it.second.get_optional<string>("areaName")) {
258: Spots[cnt]->areaName = _UW(areaName.value());
259: }
260: //予報地点コード
261: if (optional<string>stationCode = it.second.get_optional<string>("stationCode")) {
262: Spots[cnt]->stationCode = stationCode.value();
263: }
264: //予報地点名
265: if (optional<string>stationName = it.second.get_optional<string>("stationName")) {
266: Spots[cnt]->stationName = _UW(stationName.value());
267: }
268: //所在地
269: if (optional<string>location = it.second.get_optional<string>("location")) {
270: Spots[cnt]->location = _UW(location.value());
271: }
272: //
273: //経度
274: if (optional<string>longitude = it.second.get_optional<string>("longitude")) {
275: Spots[cnt]->longitude = stod(longitude.value());
276: }
277: //緯度
278: if (optional<string>latitude = it.second.get_optional<string>("latitude")) {
279: Spots[cnt]->latitude = stod(latitude.value());
280: }
281: }
282: //読み込みエラー
283: } catch(xml_parser_error& e) {
284: errmsg = _SW("予\報地点情報ファイルを読み込めません");
285: return (-1);
286: }
287:
288: return cnt;
289: }
332: /**
333: * 指定した緯度・経度に最も近い予報地点コードを返す
334: * @param double latitude 緯度(世界測地系)
335: * @param double longitude 経度(世界測地系)
336: * @param int forecast 0:天気予報,1:週間天気予報(省略時:1)
337: * @return string stationCode 予報地点コードを格納
338: */
339: string pahooWeather::getJmaNearSpot(double longitude, double latitude, int forecast=1) {
340: string stationCode = "";
341: double d0 = 999999999.9;
342: //電文コード
343: string code = "";
344: if (forecast == 0) {
345: code = "VPFD51";
346: } else {
347: code = "VPFW50";
348: }
349:
350: for (int i = 0; i < SIZE_SPOTS; i++) {
351: if (Spots[i] == NULL) {
352: break;
353: }
354: if (Spots[i]->code == code) {
355: double d1 = this->distance(Spots[i]->longitude, Spots[i]->latitude, longitude, latitude);
356: if (d1 < d0) {
357: stationCode = Spots[i]->stationCode;
358: d0 = d1;
359: }
360: }
361: }
362: return stationCode;
363: }
623: /**
624: * 気象庁防災情報XMLから天気予報情報を読み込む
625: * @param string station 予報地点コード
626: * @param int forecast 0:天気予報,1:週間天気予報(省略時:1)
627: * 0のとき‥‥jmaWeeklyWeather[0](本日)〜[6](6日後)に代入
628: * 1のとき‥‥jmaWeeklyWeather[0](本日)〜[2](2日後)に代入
629: * @return bool TRUE:成功/FALSE:失敗
630: */
631: bool pahooWeather::jmaGetWeatherForecast(string station, int forecast) {
632: //曜日
633: static wstring week_name[] = {_SW("日"), _SW("月"), _SW("火"), _SW("水"), _SW("木"), _SW("金"), _SW("土")};
634:
635: //マッチングパターン
636: static regex re11("([0-9]+)\\-([0-9]+)\\-([0-9]+)"); //年月日
637: static regex re12("([0-9]+)\\-([0-9]+)\\-([0-9]+)T([0-9]+)\\:"); //年月日時
638: static wregex re21(_SW("から")); //降水確率
639: static wregex re31(_SW("最低気温")); //最低気温
640: static wregex re32(_SW("^日中の最高気温")); //最高気温
641: static regex re41("^[0-9]+$"); //正数
642: static smatch mt1;
643: static wsmatch mt2;
644: int n;
645:
646: //電文コード
647: string code;
648: if (forecast == 0) {
649: code = "VPFD51";
650: } else {
651: code = "VPFW50";
652: }
653:
654: //予報地点コードの取得
655: bool res = FALSE;
656: int id;
657: for (id = 0; id < SIZE_SPOTS; id++) {
658: if (Spots[id] == NULL) {
659: break;
660: }
661: if ((Spots[id]->code == code) && (Spots[id]->stationCode == station)) {
662: res = TRUE;
663: break;
664: }
665: }
666: if (res == FALSE) {
667: errmsg = _SW("予\報地点コードが見つかりません");
668: return FALSE;
669: }
670:
671: //初日のみ初期化
672: time_t now = time(NULL);
673: struct tm* pnow = localtime(&now);
674: namespace gr = boost::gregorian;
675: gr::date dt(pnow->tm_year + 1900, pnow->tm_mon + 1, pnow->tm_mday);
676: int i = 0, dd = 0, d2 = 0;
677: //天気予報アイコンを夜間にするかどうか
678: int mode = 0;
679: if (pnow->tm_hour >= 18) {
680: mode = 1;
681: }
682:
683: this->jmaWeeklyWeather[i].year = dt.year();
684: this->jmaWeeklyWeather[i].month = dt.month();
685: this->jmaWeeklyWeather[i].day = dt.day();
686: this->jmaWeeklyWeather[i].day_of_week = week_name[dt.day_of_week()];
687: this->jmaWeeklyWeather[i].stationName = Spots[id]->stationName;
688: this->jmaWeeklyWeather[i].weather = L"";
689: this->jmaWeeklyWeather[i].image = "";
690: this->jmaWeeklyWeather[i].rainy = "";
691: this->jmaWeeklyWeather[i].temp_max = "";
692: this->jmaWeeklyWeather[i].temp_min = "";
693:
694: //最新の週間天気予報情報URLを取得
695: long page = stol(Spots[id]->page);
696: string areaCode = Spots[id]->areaCode;
697: string stationCode = Spots[id]->stationCode;
698: wstring stationName = Spots[id]->stationName;
699: wstring location = Spots[id]->location;
700:
701: string vpfd51="", vpfw50="";
702: jmaGetWeatherForecastURL(page, &vpfd51, &vpfw50);
703: if (errmsg != L"") {
704: return FALSE;
705: }
706:
707: //VPFW51(府県週間天気予報)の解析
708: string contents = "";
709: if (forecast == 1) {
710: //XML読み込み
711: if (pCC->load(vpfw50, &contents) == FALSE) {
712: errmsg = pCC->getError();
713: return FALSE;
714: }
715: try {
716: std::stringstream ss;
717: ss << contents;
718: ptree pt;
719: xml_parser::read_xml(ss, pt);
720:
721: //XML解釈
722: try {
723: //年月日取得
724: for (auto it : pt.get_child("Report.Body.MeteorologicalInfos.TimeSeriesInfo.TimeDefines")) {
725: if (optional<string>dti = it.second.get_optional<string>("DateTime")) {
726: if (regex_search(dti.value(), mt1, re11)) {
727: optional<string>tid = it.second.get_optional<string>("<xmlattr>.timeId");
728: // cout << tid.value() << " : " << mt1[0].str() << endl;
729: //最初の情報は何時か
730: i = stoi(tid.value());
731: gr::date dt2(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
732:
733: if (i == 1) {
734: if (dt < dt2) {
735: dd = 0; //明日
736: } else if (dt == dt2) {
737: dd = (-1); //今日
738: } else {
739: dd = (-2); //明日
740: }
741: }
742: i += dd;
743: if (i < 0) {
744: continue;
745: }
746: //予報1日分の初期化
747: gr::date dt(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
748: this->jmaWeeklyWeather[i].year = stoi(mt1[1].str());
749: this->jmaWeeklyWeather[i].month = stoi(mt1[2].str());
750: this->jmaWeeklyWeather[i].day = stoi(mt1[3].str());
751: this->jmaWeeklyWeather[i].day_of_week = week_name[dt.day_of_week()];
752: this->jmaWeeklyWeather[i].stationName = Spots[id]->stationName;
753: this->jmaWeeklyWeather[i].weather = L"";
754: this->jmaWeeklyWeather[i].image = "";
755: this->jmaWeeklyWeather[i].rainy = "";
756: this->jmaWeeklyWeather[i].temp_max = "";
757: this->jmaWeeklyWeather[i].temp_min = "";
758: }
759: }
760: }
761:
762: for (auto it : pt.get_child("Report.Body")) {
763: //天気・降水確率の取得
764: if (optional<string>type = it.second.get_optional<string>("<xmlattr>.type")) {
765: if (_UW(type.value()) == _SW("区域予\報")) {
766: for (auto it2 : it.second.get_child("TimeSeriesInfo")) {
767: if (optional<string>acode = it2.second.get_optional<string>("Area.Code")) {
768: if (acode.value() != areaCode) {
769: continue;
770: }
771: }
772: for (auto it3 : it2.second.get_child("")) {
773: if (optional<string>type = it3.second.get_optional<string>("Property.Type")) {
774: if (_UW(type.value()) == _SW("天気")) {
775: //天気予報の取得
776: for (auto it4 : it3.second.get_child("Property.WeatherPart")) {
777: if (optional<string>we = it4.second.get_optional<string>("")) {
778: i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
779: if (i < 0) {
780: continue;
781: }
782: this->jmaWeeklyWeather[i].weather = jmaShortWeather(_UW(we.value()));
783: }
784: }
785: //天気予報用テロップ番号の取得
786: for (auto it4 : it3.second.get_child("Property.WeatherCodePart")) {
787: if (optional<string>we = it4.second.get_optional<string>("")) {
788: i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
789: if (i < 0) {
790: continue;
791: }
792: if (i == 0) {
793: this->jmaWeeklyWeather[i].image = jma_telop2url(stoi(we.value()), mode);
794: } else {
795: this->jmaWeeklyWeather[i].image = jma_telop2url(stoi(we.value()), 0);
796: }
797: }
798: }
799: } else if (_UW(type.value()) == _SW("降水確率")) {
800: //降水確率の取得
801: for (auto it4 : it3.second.get_child("Property.ProbabilityOfPrecipitationPart")) {
802: if (optional<string>we = it4.second.get_optional<string>("")) {
803: i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
804: if (i < 0) {
805: continue;
806: }
807: this->jmaWeeklyWeather[i].rainy = we.value();
808: }
809: }
810: }
811: }
812: }
813: }
814: } else if (_UW(type.value()) == _SW("地点予\報")) {
815: for (auto it2 : it.second.get_child("TimeSeriesInfo")) {
816: if (optional<string>scode = it2.second.get_optional<string>("Station.Code")) {
817: if (scode.value() != stationCode) {
818: continue;
819: }
820: }
821: for (auto it3 : it2.second.get_child("")) {
822: for (auto it4 : it3.second.get_child("")) {
823: if (optional<string>type = it4.second.get_optional<string>("Type")) {
824: if (_UW(type.value()) == _SW("最低気温")) {
825: //最低気温の取得
826: for (auto it5 : it4.second.get_child("TemperaturePart")) {
827: if (optional<string>we = it5.second.get_optional<string>("")) {
828: i = stoi(it5.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
829: if (i < 0) {
830: continue;
831: }
832: this->jmaWeeklyWeather[i].temp_min = we.value();
833: }
834: }
835: } else if (_UW(type.value()) == _SW("最高気温")) {
836: //最高気温の取得
837: for (auto it5 : it4.second.get_child("TemperaturePart")) {
838: if (optional<string>we = it5.second.get_optional<string>("")) {
839: i = stoi(it5.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
840: if (i < 0) {
841: continue;
842: }
843: this->jmaWeeklyWeather[i].temp_max = we.value();
844: }
845: }
846: }
847: }
848: }
849: }
850: }
851: }
852: }
853: }
854:
855: //XML解釈エラー
856: } catch(ptree_bad_path& e) {
857: errmsg = _SW("府県週間天気予\報情報を取得できません");
858: return FALSE;
859: }
860: //読み込みエラー
861: } catch(xml_parser_error& e) {
862: errmsg = _SW("府県週間天気予\報情報を取得できません");
863: return FALSE;
864: }
865: }
866:
867: //VPFD51(府県天気予報 R1)の解析
868: code = "VPFD51";
869: //予報地点コードの取得
870: res = FALSE;
871: for (id = 0; id < SIZE_SPOTS; id++) {
872: if (Spots[id] == NULL) {
873: break;
874: }
875: if ((Spots[id]->code == code) && (Spots[id]->stationCode == station)) {
876: res = TRUE;
877: break;
878: }
879: }
880: if (res == FALSE) {
881: errmsg = _SW("予\報地点コードが見つかりません");
882: return FALSE;
883: }
884: areaCode = Spots[id]->areaCode;
885:
886: string rain_table[8] = { "-", "-", "-", "-", "-", "-", "-", "-", };
887: int temp_table[5] = { 0, 0, 0, 0, 0 };
888: //XML読み込み
889: string contents2 = "";
890: if (pCC->load(vpfd51, &contents2) == FALSE) {
891: errmsg = pCC->getError();
892: return FALSE;
893: }
894: try {
895: std::stringstream ss;
896: ss << contents2;
897: ptree pt;
898: xml_parser::read_xml(ss, pt);
899:
900: //XML解釈
901: try {
902: //年月日取得
903: for (auto it : pt.get_child("Report.Body")) {
904: if (optional<string>type = it.second.get_optional<string>("<xmlattr>.type")) {
905: if (_UW(type.value()) == _SW("区域予\報")) {
906: //日時
907: for (auto it2 : it.second.get_child("")) {
908: for (auto it3 : it2.second.get_child("")) {
909: for (auto it4 : it3.second.get_child("")) {
910: if (optional<string>name = it4.second.get_optional<string>("Name")) {
911: wstring ws = _UW(name.value());
912:
913: if (regex_search(ws, mt2, re21)) {
914: string ss = it4.second.get_optional<string>("DateTime").value();
915: regex_search(ss, mt1, re12);
916: optional<string>tid = it4.second.get_optional<string>("<xmlattr>.timeId");
917: // cout << tid.value() << " : " << mt1[0].str() << endl;
918: //最初の情報は何時か
919: i = stoi(tid.value());
920: gr::date dt2(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
921: if (i == 1) {
922: if (dt < dt2) {
923: dd = 0; //明日
924: } else if (dt == dt2) {
925: dd = (-1); //今日
926: } else {
927: dd = (-2); //明日
928: }
929: d2 = stoi(mt1[4].str()) / 6; //6時間毎
930: if (dd == (-2)) {
931: d2 = d2 - 4 + 1;
932: }
933: }
934: } else if (optional<string>s2 = it4.second.get_optional<string>("DateTime")) {
935: string ss = s2.value();
936: if (regex_search(ss, mt1, re11)) {
937: //最初の情報は何時か
938: optional<string>tid = it4.second.get_optional<string>("<xmlattr>.timeId");
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: }
950: }
951: }
952: }
953: }
954: }
955: //地方コードは上何桁で判定するか
956: bool flag = FALSE;
957: for (n = 5; n >= 1; n--) {
958: for (auto it3 : it2.second.get_child("")) {
959: if (optional<string>code = it3.second.get_optional<string>("Area.Code")) {
960: if (code.value().substr(0, n) != areaCode.substr(0, n)) {
961: continue;
962: }
963: flag = TRUE;
964: }
965: }
966: if (flag) {
967: break;
968: }
969: }
970: // cout << "N = " << n << endl;
971: // cout << "areaCode = " << areaCode << endl;
972:
973: //天気予報
974: for (auto it3 : it2.second.get_child("")) {
975: if (optional<string>code = it3.second.get_optional<string>("Area.Code")) {
976:
977: if (code.value().substr(0, n) != areaCode.substr(0, n)) {
978: continue;
979: }
980: }
981: // cout << it3.first << endl;
982: if (optional<string>type = it3.second.get_optional<string>("Kind.Property.Type")) {
983: if (_UW(type.value()) == _SW("天気")) {
984: //天気予報の取得
985: for (auto it4 : it3.second.get_child("Kind.Property.WeatherPart")) {
986: i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
987: if (i < 0) {
988: continue;
989: }
990: this->jmaWeeklyWeather[i].weather = jmaShortWeather(_UW(it4.second.get_optional<string>("").value()));
991: }
992: //天気予報用テロップ番号の取得
993: for (auto it4 : it3.second.get_child("Kind.Property.WeatherCodePart")) {
994: i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
995: if (i < 0) {
996: continue;
997: }
998: if (i == 0) {
999: this->jmaWeeklyWeather[i].image = jma_telop2url(stoi(it4.second.get_optional<string>("").value()), mode);
1000: } else {
1001: this->jmaWeeklyWeather[i].image = jma_telop2url(stoi(it4.second.get_optional<string>("").value()), 0);
1002: }
1003: }
1004:
1005: } else if (_UW(type.value()) == _SW("降水確率")) {
1006: //降水確率の取得
1007: for (auto it4 : it3.second.get_child("Kind.Property.ProbabilityOfPrecipitationPart")) {
1008: i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd + d2;
1009: if (i < 0) {
1010: continue;
1011: }
1012: rain_table[i] = it4.second.get_optional<string>("").value();
1013: }
1014: }
1015: }
1016: }
1017: }
1018: } else if (_UW(type.value()) == _SW("地点予\報")) {
1019: //日時
1020: int day0 = 0;
1021: for (auto it2 : it.second.get_child("")) {
1022: for (auto it3 : it2.second.get_child("")) {
1023: for (auto it4 : it3.second.get_child("")) {
1024: if (optional<string>s2 = it4.second.get_optional<string>("DateTime")) {
1025: string ss = s2.value();
1026: if (regex_search(ss, mt1, re11)) {
1027: //最初の情報は何時か
1028: optional<string>tid = it4.second.get_optional<string>("<xmlattr>.timeId");
1029: i = stoi(tid.value());
1030: gr::date dt2(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
1031: if (i == 1) {
1032: if (dt < dt2) {
1033: dd = 0; //明日
1034: } else if (dt == dt2) {
1035: dd = (-1); //今日
1036: } else {
1037: dd = (-2); //明日
1038: }
1039: temp_table[i] = 0;
1040: day0 = stoi(mt1[3]);
1041: } else {
1042: if (day0 == stoi(mt1[3])) {
1043: temp_table[i] = 0;
1044: } else {
1045: temp_table[i] = 1;
1046: }
1047: day0 = stoi(mt1[3]);
1048: }
1049: }
1050: }
1051: }
1052: }
1053: //最低気温・最高気温
1054: i = 1 + dd;
1055: for (auto it3 : it2.second.get_child("")) {
1056: if (optional<string>code = it3.second.get_optional<string>("Station.Code")) {
1057:
1058: if (code.value() != stationCode) {
1059: continue;
1060: }
1061: }
1062: //最高気温・最低気温
1063: for (auto it4 : it3.second.get_child("")) {
1064: for (auto it5 : it4.second.get_child("")) {
1065: if (optional<string>ts = it5.second.get_optional<string>("Type")) {
1066: wstring ws = _UW(ts.value());
1067: // cout << _WS(ws) << endl;
1068: if (regex_search(ws, mt2, re31)) {
1069: int i2 = stoi(it5.second.get_optional<string>("TemperaturePart.jmx_eb:Temperature.<xmlattr>.refID").value());
1070: i += temp_table[i2];
1071: if (i < 0) {
1072: continue;
1073: }
1074: this->jmaWeeklyWeather[i].temp_min = it5.second.get_optional<string>("TemperaturePart.jmx_eb:Temperature").value();
1075: } else if (regex_search(ws, mt2, re32)) {
1076: int i2 = stoi(it5.second.get_optional<string>("TemperaturePart.jmx_eb:Temperature.<xmlattr>.refID").value());
1077: i += temp_table[i2];
1078: if (i < 0) {
1079: continue;
1080: }
1081: this->jmaWeeklyWeather[i].temp_max = it5.second.get_optional<string>("TemperaturePart.jmx_eb:Temperature").value();
1082: }
1083: }
1084: }
1085: }
1086: }
1087: }
1088: }
1089: }
1090: }
1091:
1092: //XML解釈エラー
1093: } catch(ptree_bad_path& e) {
1094: errmsg = _SW("府県天気予\報情報を取得できません");
1095: return FALSE;
1096: }
1097: //読み込みエラー
1098: } catch(xml_parser_error& e) {
1099: errmsg = _SW("府県天気予\報情報を取得できません");
1100: return FALSE;
1101: }
1102:
1103: //降水確率を天気予報情報へ
1104: for (int j = 0; j < 8; j++) {
1105: if ((dd == (-2)) && (j >= 4)) break;
1106: i = floor(j / 4.0);
1107: if (regex_search(jmaWeeklyWeather[i].rainy, mt1, re41)) {
1108: this->jmaWeeklyWeather[i].rainy = "";
1109: }
1110: this->jmaWeeklyWeather[i].rainy += rain_table[j];
1111: if (j % 4 != 3) {
1112: this->jmaWeeklyWeather[i].rainy += "/";
1113: }
1114: }
1115:
1116: /** debug
1117: for (int j = 0; j <= 7; j++) {
1118: cout << j << ":"
1119: << _WS(this->jmaWeeklyWeather[j].stationName) << ":"
1120: << this->jmaWeeklyWeather[j].year << "/"
1121: << this->jmaWeeklyWeather[j].month << "/"
1122: << this->jmaWeeklyWeather[j].day << "("
1123: << _WS(this->jmaWeeklyWeather[j].day_of_week) << ") "
1124: << _WS(this->jmaWeeklyWeather[j].weather) << " "
1125: << this->jmaWeeklyWeather[j].image << " "
1126: << this->jmaWeeklyWeather[j].rainy << "% "
1127: << this->jmaWeeklyWeather[j].temp_max << "/"
1128: << this->jmaWeeklyWeather[j].temp_min << "℃ "
1129: << endl;
1130: }
1131: */
1132:
1133: contents.clear();
1134: contents2.clear();
1135:
1136: return TRUE;
1137: }
解説:pahooCacheクラスとデータ構造
- Atomフィード(長期フィード:定時)
- VPFW50
- VPFD51
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バイト
となる。
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: lifeCache = life;
45:
46: //dirの最後にディレクトリ区切り文字がなければ追加
47: smatch mt;
48: regex re("\\\\$");
49: if (regex_search(dir, mt, re) == FALSE) {
50: dir = dir + "\\";
51: }
52: errmsg = L"";
53: lifeCache = life;
54: dirCache = dir;
55: userAgent = ua;
56:
57: //キャッシュ・ディレクトリが無ければ作成
58: struct stat statBuf;
59: if (stat(dirCache.c_str(), &statBuf) != 0) {
60: if (_mkdir(dirCache.c_str()) != 0) {
61: errmsg = _SW("キャッシュ・ディレクトリ \"") + _SW(dirCache) + _SW("\" の作成に失敗しました");
62: }
63: }
64: }
107: /**
108: * キャッシュの削除
109: * @param string pat 削除ファイル名(正規表現指定可能)
110: * @param unsigned int life キャッシュ保持時間(分)
111: * @return unsigned int 削除したファイル数
112: */
113: unsigned int pahooCache::delCache(string pat, unsigned int life) {
114: int cnt = 0;
115: string fullname;
116: struct stat st;
117: smatch mt;
118: regex re(pat);
119:
120: //現在時刻
121: time_t t;
122: time(&t);
123:
124: //ディレクトリ検索
125: DIR *dp;
126: dp = opendir(dirCache.c_str());
127: if (dp == NULL) {
128: return 0;
129: }
130: dirent* entry = readdir(dp);
131: while (entry != NULL){
132: if (entry != NULL) {
133: string fname = (string)entry->d_name;
134: //patに一致するファイル
135: if (regex_search(fname, mt, re)) {
136: fullname = dirCache + fname;
137: if (stat(fullname.c_str(), &st) == 0) {
138: //保持時間を過ぎたキャッシュ・ファイルは削除
139: if ((long unsigned int)t - (long unsigned int)st.st_ctime > (life * 60)) {
140: unlink(fullname.c_str());
141: cnt++;
142: }
143: }
144: }
145: }
146: entry = readdir(dp);
147: }
148: closedir(dp);
149:
150: return cnt;
151: }
153: /**
154: * cURLによるコンテンツ取得
155: * @param string url URL
156: * @param string* contents コンテンツを格納
157: * @return bool TRUE:読み込み成功/FALSE:失敗
158: */
159: bool pahooCache::cLoad(string url, string* contents) {
160: bool res = readWebContents(url, (const string)userAgent, contents);
161: if (res == FALSE) {
162: errmsg = _SW("\"") + _SW(url) + _SW("\" が取得できません");
163: return FALSE;
164: }
165: return TRUE;
166: }
168: /**
169: * MD5ハッシュ値を返す
170: * @param string str 文字列
171: * @return string MD5ハッシュ値
172: */
173: string pahooCache::md5(string str) {
174: boost::uuids::detail::md5 hash;
175: md5::digest_type digest;
176: hash.process_bytes(str.data(), str.size());
177: hash.get_digest(digest);
178:
179: const auto intDigest = reinterpret_cast<const int *>(&digest);
180: std::string result;
181: boost::algorithm::hex(intDigest, intDigest + (sizeof(md5::digest_type) / sizeof(int)), std::back_inserter(result));
182:
183: return result;
184: }
186: /**
187: * ネットからコンテンツを読み込む
188: * @param string url URL
189: * @param string* contents コンテンツを格納
190: * @return bool TRUE:読み込み成功/FALSE:失敗
191: */
192: bool pahooCache::forceLoad(string url, string* contents) {
193: //キャッシュするファイル名
194: string fname = this->md5(url);
195: string fullname = dirCache + fname;
196:
197: bool ret = this->cLoad(url, contents);
198: ofstream fout;
199: fout.open(fullname, ios::out);
200: fout << *contents;
201:
202: if (ret == FALSE) {
203: errmsg = _SW("キャッシュファイル \"") + _SW(fullname) + _SW("\" の書き込み失敗しました");
204: return FALSE;
205: }
206: debug += url + "\n";
207:
208: return TRUE;
209: }
211: /**
212: * コンテンツを読み込む
213: * @param string url URL
214: * @param string* contents コンテンツを格納
215: * @return bool TRUE:読み込み成功/FALSE:失敗
216: */
217: bool pahooCache::load(string url, string* contents) {
218: bool res = FALSE;
219: string fname, fullname;
220: string buff;
221: ifstream fin;
222:
223: //キャッシュ有効
224: if (lifeCache > 0) {
225: this->delCache("[0-9a-f]+", lifeCache); //古いキャッシュを削除
226: fname = this->md5(url); //キャッシュするファイル名
227: fullname = dirCache + fname;
228: //キャッシュが存在する
229: *contents = "";
230: fin.open(fullname, ios::binary);
231: if (fin) {
232: // cout << "from file: " << fname << endl;
233: getline(fin, *contents, '\0');
234: if (contents->length() <= 0) {
235: errmsg = _SW("キャッシュファイル \"") + _SW(fullname) + _SW("\" の読み込みに失敗しました");
236: // cout << _WS(errmsg) << endl;
237: return FALSE;
238: }
239: debug += fname + "\n";
240: res = TRUE;
241: //ネットから取得
242: } else {
243: // cout << "from URL: " << url << endl;
244: res = this->forceLoad(url, contents);
245: }
246:
247: //キャッシュ無効
248: } else {
249: // cout << "uncache from URL: " << url << endl;
250: res = this->forceLoad(url, contents);
251: }
252:
253: return res;
254: }
キャッシュが有効の時は、まず、delCache メソッドを使って古いキャッシュを削除する。stat関数を使ってファイルのタイムスタンプを取得し、それを現在日時と比較することで削除するかどうかを判定している。
ッシュへ書き込む。
キャッシュ・ファイル名は、URLからMD5ハッシュを使って生成する。
キャッシュが存在すれば、getline関数を使って読み込む。
無ければ、forceLoad メソッドを使ってネットから読み込み、キャッシュへ書き込む。ネットからの読み込みは cLoad メソッドで、readWebContents関数を使って読み込んでいる。
予報地点情報ファイルの自動更新
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 を用意した。
128: /**
129: * ローカルの予報地点情報ファイルとpahoo.org上のファイルを比べる.
130: * @param なし
131: * @return bool TRUE:pahoo.org上でのファイルの方が新しい
132: */
133: bool pahooWeather::isExistsNewerFileSpots(void) {
134: string xml;
135: string ssPahoo, ssLocal;
136:
137: //XML読み込み:pahoo.org
138: if (readWebContents(FILE_SPOTS_PAHOO, UserAgent, &xml, nullptr) == FALSE) {
139: return FALSE;
140: }
141: try {
142: ptree ptPahoo;
143: ptPahoo = readXmlFromString(xml);
144: //XML解釈
145: if (optional<string>updatePahoo = ptPahoo.get_optional<string>("jmaweatherspots.update")) {
146: ssPahoo = (string)updatePahoo.get();
147: // cout << "pahoo.org = " << ssPahoo << endl;
148: //XML解釈失敗→更新しない
149: } else {
150: return FALSE;
151: }
152: //読み込みエラー→更新しない
153: } catch(xml_parser_error& e) {
154: return FALSE;
155: }
156:
157: //XML読み込み:ローカル
158: string path = getModulePath();
159: if (path == "") {
160: return FALSE;
161: }
162: try {
163: ptree ptLocal;
164: xml_parser::read_xml(path + "\\" + FILE_SPOTS_LOCAL, ptLocal);
165: //XML解釈
166: if (optional<string>updateLocal = ptLocal.get_optional<string>("jmaweatherspots.update")) {
167: ssLocal = (string)updateLocal.get();
168: // cout << "local = " << ssLocal << endl;
169: //XML解釈失敗→更新する
170: } else {
171: return FALSE;
172: }
173: //読み込みエラー→更新する
174: } catch(xml_parser_error& e) {
175: return TRUE;
176: }
177:
178: return (bool)(ssPahoo.compare(ssLocal) > 0);
179: }
それぞれのファイルのupdateタグの内容を比較し、pahoo.org上のファイルが新しければTRUEを返す。
ただし、ローカル上のファイルの読み込みに失敗したり、updateタグが見つからないようなときはファイルが存在しないか壊れていると判断し、FALSEを返して強制的に更新する。
逆に、pahoo.org上のファイルの読み込みに失敗したり、updateタグが見つからないようなときはTRUEを返して更新しないようにする。
181: /**
182: * ローカルの予報地点情報ファイルが古ければ更新する.
183: * pahoo.org上のファイルと比べて,updateが古ければダウンロード医て差し替える.
184: * @param なし
185: * @return bool TRUE:更新した/FALSE:更新しないまたはエラー
186: */
187: bool pahooWeather::updateFileSpots(void) {
188: //更新実行
189: if (this->isExistsNewerFileSpots()) {
190: string contents;
191: if (readWebContents(FILE_SPOTS_PAHOO, UserAgent, &contents, nullptr) == FALSE) {
192: return FALSE;
193: }
194:
195: string path = getModulePath();
196: if (path == "") {
197: return FALSE;
198: }
199: ofstream outputfile(path + "\\" + FILE_SPOTS_LOCAL);
200: outputfile << contents;
201: outputfile.close();
202:
203: return TRUE;
204:
205: //更新しない
206: } else {
207: return FALSE;
208: }
209: }
共通手順、モジュールなど
- C++ 開発環境の準備:ぱふぅ家のホームページ
- C++ 開発環境の準備 -MSYS2編-
- WiX によるWindowsインストーラー作成:ぱふぅ家のホームページ
- C++ でダイアログボックスを使う
- C++ でイベント駆動型アプリを作る
- 解説:ニュース一覧作成
C++ で Googleニュース検索 - 解説:検索結果をCSVファイルに保存
C++ で Googleニュース検索 - 解説:クリップボード
C++ でパスワード生成機を作る - 解説:APIキーの管理
C++ で直近の地震情報を取得する - 解説:WebView2
C++ で直近の地震情報を取得する - 解説:解説:マップ表示用HTML生成
C++ で直近の地震情報を取得する 参考サイト
- PHPで天気予報を求める:ぱふぅ家のホームページ
- PHPで地図で指定した場所の週間カレンダーを表示:ぱふぅ家のホームページ
- 気象庁防災情報XMLフォーマット 情報提供ページ
(この項おわり)
インターネット経由で気象庁防災情報XMLにアクセスし、地図や住所、ランドマーク,郵便番号等で指定した地点の週間天気予報を一覧表示するアプリケーションを作る。一覧情報をクリップボードにコピーしたり、CSV形式ファイルに保存することができる。また、ユーザーがAPIキーを取得することでGoogleマップやGoogle住所検索も利用できるようになる。
(2024年8月24日)使用ライブラリを更新.
(2024年5月6日)Edgeブラウザ対応,64ビット対応.予報地点情報ファイルを更新.API入力処理を改良.起動時に予報地点情報ファイルを自動更新.
(2024年3月30日)予報地点情報ファイルおよび使用ライブラリを更新.
(2023年12月30日)予報地点情報ファイルおよび使用ライブラリを更新.
(2023年11月23日)使用ライブラリを更新.
を追加.