
サンプル・プログラム
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/makefile | ビルド |
バージョン | 更新日 | 内容 |
---|---|---|
2.5.2 | 2023/03/18 | 予報地点情報ファイルおよび使用ライブラリを更新 |
2.5.1 | 2023/02/11 | 予報地点情報ファイルおよび使用ライブラリを更新 |
2.5.0 | 2022/09/24 | ウィンドウ位置保存,ライブラリ更新 |
2.4.0 | 2022/08/20 | 設定クリア機能を追加 |
2.3.1 | 2022/08/20 | 予報地点情報ファイルおよび使用ライブラリを更新 |
バージョン | 更新日 | 内容 |
---|---|---|
2.2 | 2022/03/12 | 気象庁防災情報XMLのhttps化に対応 |
2.1 | 2021/04/14 | キャッシュ・システム導入:pahooCacheクラス |
2.0 | 2021/03/16 | 気象庁防災情報XMLから情報取得に変更 |
1.2 | 2020/12/20 | bug-fix |
1.1 | 2020/12/05 | __jma_readWeeklyWeather() 月日取得方式変更 |
バージョン | 更新日 | 内容 |
---|---|---|
1.5 | 2022/09/03 | デバッグコード埋め込み |
1.4 | 2021/05/01 | makeMapLeaflet() 引数追加 |
1.3 | 2021/03/07 | tokyo_wgs84(), wgs84_tokyo()メソッド追加 |
1.2 | 2020/12/20 | leafletスクリプトの参照URL変更 |
1.1 | 2020/09/27 | makeMapLeaflet()メソッド追加 |
バージョン | 更新日 | 内容 |
---|---|---|
1.0 | 2021/04/14 |
バージョン | 更新日 | 内容 |
---|---|---|
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 | 2020/09/22 |
使用ライブラリ
リソースの準備
Eclipse を起動し、新規プロジェクト weeklyweather を用意する。
ResEdit を起動し、resource.rc を用意する。
Eclipse に戻り、ソース・プログラム "weeklyweather.cpp" を追加する。
リンカー・フラグを -Wl,--enable-stdcall-fixup -mwindows -lgdiplus -static -lstdc++ -lgcc -lwinpthread -lcurl -lssl "(任意のパス)\libcurl.dll" "(任意のパス)\cwebpage.dll" "C:\Windows\system32\GdiPlus.dll" に設定する。
また、コマンド行パターンを ${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS} -lole32 -loleaut32 -luuid にすること。

MSYS2 コマンドラインからビルドするのであれば、"makefile" を利用してほしい。
解説:定数など
38: // 定数など ==================================================================
39: #define MAKER "pahoo.org" //作成者
40: #define APPNAME "weeklyweather" //アプリケーション名
41: #define APPNAMEJP "週間天気予\報" //アプリケーション名(日本語)
42: #define APPVERSION "2.5.2" //バージョン
43: #define APPYEAR "2020-23" //作成年
44: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-16-01.shtm" //参考サイト
45:
46: //ヘルプ・ファイル
47: #define HELPFILE ".\\etc\\help.chm"
48:
49: //デフォルト保存ファイル名
50: #define SAVEFILE "weeklyweather.csv"
51:
52: //キャッシュ・ディレクトリ
53: #define CACHEDIR_WEATHER "pcache\\"
54:
55: //キャッシュ保持時間(デフォルト;分)(0:キャッシュしない)
56: #define LIFE_CACHE_WEATHER (2 * 60)
57:
58: //現在のインターフェイス
59: HINSTANCE hInst;
60:
61: //アプリケーション・ウィンドウ
62: HWND hParent;
63:
64: //アプリケーション・ウィンドウ位置
65: unsigned hParent_X, hParent_Y;
66:
67: //検索キー格納用
68: string Query;
69:
70: //エラー・メッセージ格納用【変更不可】
71: string ErrorMessage;
72:
73: //ブラウザ・コントロール
74: WebBrowser wBrowser;
75:
76: //UserAgent
77: string UserAgent;
78:
79: //pahooWeatherオブジェクト
80: pahooWeather *pWT;
81:
82: //pahooGeocodeオブジェクト
83: pahooGeocode *pGC;
84:
85: //pahooCacheオブジェクト
86: pahooCache *pCC;
87:
88: //予報表のID
89: #define IDC_CAL_LABEL 1501 //日付
90: #define IDC_RES_LABEL 1601 //天気予報
91: #define IDC_RES_IMAGE 1701 //天気アイコン
92: #define IDC_TEMP_LABEL 1801 //降水確率・最低・最高気温
93:
94: //予報表の座標
95: #define IDC_RES_X 10
96: #define IDC_RES_Y 430
97: #define IDC_RES_WIDTH 80
98:
99: #define FORECAST_HEIGHT 140 //予報表の高さ
100:
101: //マップに表示するマーカー画像URL
102: #define URL_MARKER "https://maps.google.co.jp/mapfiles/ms/icons/yellow-dot.png"
103: #define MAP_WIDTH 530 //地図の幅(ピクセル)
104: #define MAP_HEIGHT 300 //地図の高さ(ピクセル)
105:
106: //マップID
107: #define MAP_ID "map_id"
108: //経度・緯度(初期値)
109: #define DEF_LONGITUDE 139.766667
110: double Longitude = DEF_LONGITUDE;
111: #define DEF_LATITUDE 35.681111
112: double Latitude = DEF_LATITUDE;
113: //地図拡大率(初期値)
114: #define DEF_ZOOM 6
115: int Zoom = DEF_ZOOM;
116: //地図の種類(初期値)
117: #define DEF_MAPTYPE "GSISTD"
118: string Maptype = DEF_MAPTYPE;
解説:pahooWeatherクラスとデータ構造
25: //天気予報情報
26: typedef struct _jmaWeeklyWeather {
27: int year; //西暦年
28: int month; //月
29: int day; //日
30: std::wstring day_of_week; //曜日
31: std::wstring weather; //天気予報
32: std::string image; //天気予報アイコンURL
33: std::string rainy; //降水確率
34: std::string temp_max; //最高気温
35: std::string temp_min; //最低気温
36: std::string stationCode; //予報地点コード
37: std::wstring stationName; //予報地点名
38: std::wstring location; //所在地
39: } jmaWeeklyWeather_t;
39: //予報地点情報格納用クラス ===================================================
40: #define FILE_VERSION "2.2" //予報値地点情報ファイルのバージョン
41: #define SIZE_SPOTS 500 //格納上限
42:
43: class _Spots {
44: public:
45: string code = ""; //電文コード
46: string page = ""; //ページ番号
47: string regionCode = ""; //地方コード
48: wstring regionName = L""; //地方名
49: string prefCode = ""; //都道府県コード
50: wstring prefName = L""; //都道府県名
51: string areaCode = ""; //地域コード
52: wstring areaName = L""; //地域名
53: string stationCode = ""; //予報地点コード
54: wstring stationName = L""; //予報地点名
55: wstring location = L""; //所在地
56: double latitude = 0.0; //緯度
57: double longitude = 0.0; //経度
58: };
59: static unique_ptr<_Spots> Spots[SIZE_SPOTS] = {};
115: /**
116: * 予報地点情報を読み込む
117: * @param なし
118: * @return int 読み込んだ地点情報数/(-1):読み込み失敗
119: */
120: int pahooWeather::readJmaSpots(void) {
121: int cnt = -1;
122: try {
123: //XMLファイル読み込み
124: ptree pt;
125: xml_parser::read_xml(FILE_SPOTS, pt);
126: //XML解釈
127: ptree tree;
128: for (auto it : pt.get_child("jmaweatherspots")) {
129: cnt++;
130: Spots[cnt] = make_unique<_Spots>();
131: //電文コード
132: if (optional<string>code = it.second.get_optional<string>("code")) {
133: Spots[cnt]->code = code.value();
134: }
135: //ページ番号
136: if (optional<string>page = it.second.get_optional<string>("page")) {
137: Spots[cnt]->page = page.value();
138: }
139: //地方コード
140: if (optional<string>regionCode = it.second.get_optional<string>("regionCode")) {
141: Spots[cnt]->regionCode = regionCode.value();
142: }
143: //地方名
144: if (optional<string>regionName = it.second.get_optional<string>("regionName")) {
145: Spots[cnt]->regionName = _UW(regionName.value());
146: }
147: //都道府県コード
148: if (optional<string>prefCode = it.second.get_optional<string>("prefCode")) {
149: Spots[cnt]->prefCode = prefCode.value();
150: }
151: //都道府県名
152: if (optional<string>prefName = it.second.get_optional<string>("prefName")) {
153: Spots[cnt]->prefName = _UW(prefName.value());
154: }
155: //地域コード
156: if (optional<string>areaCode = it.second.get_optional<string>("areaCode")) {
157: Spots[cnt]->areaCode = areaCode.value();
158: }
159: //地域名
160: if (optional<string>areaName = it.second.get_optional<string>("areaName")) {
161: Spots[cnt]->areaName = _UW(areaName.value());
162: }
163: //予報地点コード
164: if (optional<string>stationCode = it.second.get_optional<string>("stationCode")) {
165: Spots[cnt]->stationCode = stationCode.value();
166: }
167: //予報地点名
168: if (optional<string>stationName = it.second.get_optional<string>("stationName")) {
169: Spots[cnt]->stationName = _UW(stationName.value());
170: }
171: //所在地
172: if (optional<string>location = it.second.get_optional<string>("location")) {
173: Spots[cnt]->location = _UW(location.value());
174: }
175: //
176: //経度
177: if (optional<string>longitude = it.second.get_optional<string>("longitude")) {
178: Spots[cnt]->longitude = stod(longitude.value());
179: }
180: //緯度
181: if (optional<string>latitude = it.second.get_optional<string>("latitude")) {
182: Spots[cnt]->latitude = stod(latitude.value());
183: }
184: }
185: //読み込みエラー
186: } catch(xml_parser_error& e) {
187: errmsg = _SW("予\報地点情報ファイルを読み込めません");
188: return (-1);
189: }
190:
191: return cnt;
192: }
235: /**
236: * 指定した緯度・経度に最も近い予報地点コードを返す
237: * @param double latitude 緯度(世界測地系)
238: * @param double longitude 経度(世界測地系)
239: * @param int forecast 0:天気予報,1:週間天気予報(省略時:1)
240: * @return string stationCode 予報地点コードを格納
241: */
242: string pahooWeather::getJmaNearSpot(double longitude, double latitude, int forecast=1) {
243: string stationCode = "";
244: double d0 = 999999999.9;
245: //電文コード
246: string code = "";
247: if (forecast == 0) {
248: code = "VPFD51";
249: } else {
250: code = "VPFW50";
251: }
252:
253: for (int i = 0; i < SIZE_SPOTS; i++) {
254: if (Spots[i] == NULL) {
255: break;
256: }
257: if (Spots[i]->code == code) {
258: double d1 = this->distance(Spots[i]->longitude, Spots[i]->latitude, longitude, latitude);
259: if (d1 < d0) {
260: stationCode = Spots[i]->stationCode;
261: d0 = d1;
262: }
263: }
264: }
265: return stationCode;
266: }
524: /**
525: * 気象庁防災情報XMLから天気予報情報を読み込む
526: * @param string station 予報地点コード
527: * @param int forecast 0:天気予報,1:週間天気予報(省略時:1)
528: * 0のとき‥‥jmaWeeklyWeather[0](本日)〜[6](6日後)に代入
529: * 1のとき‥‥jmaWeeklyWeather[0](本日)〜[2](2日後)に代入
530: * @return bool TRUE:成功/FALSE:失敗
531: */
532: bool pahooWeather::jmaGetWeatherForecast(string station, int forecast) {
533: //曜日
534: static wstring week_name[] = {_SW("日"), _SW("月"), _SW("火"), _SW("水"), _SW("木"), _SW("金"), _SW("土")};
535:
536: //マッチングパターン
537: regex re11("([0-9]+)\\-([0-9]+)\\-([0-9]+)"); //年月日
538: regex re12("([0-9]+)\\-([0-9]+)\\-([0-9]+)T([0-9]+)\\:"); //年月日時
539: wregex re21(_SW("から")); //降水確率
540: wregex re31(_SW("最低気温")); //最低気温
541: wregex re32(_SW("^日中の最高気温")); //最高気温
542: regex re41("^[0-9]+$"); //正数
543: smatch mt1;
544: wsmatch mt2;
545: int n;
546:
547: //電文コード
548: string code;
549: if (forecast == 0) {
550: code = "VPFD51";
551: } else {
552: code = "VPFW50";
553: }
554:
555: //予報地点コードの取得
556: bool res = FALSE;
557: int id;
558: for (id = 0; id < SIZE_SPOTS; id++) {
559: if (Spots[id] == NULL) {
560: break;
561: }
562: if ((Spots[id]->code == code) && (Spots[id]->stationCode == station)) {
563: res = TRUE;
564: break;
565: }
566: }
567: if (res == FALSE) {
568: errmsg = _SW("予\報地点コードが見つかりません");
569: return FALSE;
570: }
571:
572: //初日のみ初期化
573: time_t now = time(NULL);
574: struct tm* pnow = localtime(&now);
575: namespace gr = boost::gregorian;
576: gr::date dt(pnow->tm_year + 1900, pnow->tm_mon + 1, pnow->tm_mday);
577: int i = 0, dd = 0, d2 = 0;
578: //天気予報アイコンを夜間にするかどうか
579: int mode = 0;
580: if (pnow->tm_hour >= 18) {
581: mode = 1;
582: }
583:
584: this->jmaWeeklyWeather[i].year = dt.year();
585: this->jmaWeeklyWeather[i].month = dt.month();
586: this->jmaWeeklyWeather[i].day = dt.day();
587: this->jmaWeeklyWeather[i].day_of_week = week_name[dt.day_of_week()];
588: this->jmaWeeklyWeather[i].stationName = Spots[id]->stationName;
589: this->jmaWeeklyWeather[i].weather = L"";
590: this->jmaWeeklyWeather[i].image = "";
591: this->jmaWeeklyWeather[i].rainy = "";
592: this->jmaWeeklyWeather[i].temp_max = "";
593: this->jmaWeeklyWeather[i].temp_min = "";
594:
595: //最新の週間天気予報情報URLを取得
596: long page = stol(Spots[id]->page);
597: string areaCode = Spots[id]->areaCode;
598: string stationCode = Spots[id]->stationCode;
599: wstring stationName = Spots[id]->stationName;
600: wstring location = Spots[id]->location;
601:
602: string vpfd51="", vpfw50="";
603: jmaGetWeatherForecastURL(page, &vpfd51, &vpfw50);
604: if (errmsg != L"") {
605: return FALSE;
606: }
607:
608: //VPFW51(府県週間天気予報)の解析
609: string contents = "";
610: if (forecast == 1) {
611: //XML読み込み
612: // readWebContents(vpfw50, UserAgent, &contents);
613: if (pCC->load(vpfw50, &contents) == FALSE) {
614: errmsg = pCC->getError();
615: return FALSE;
616: }
617: try {
618: std::stringstream ss;
619: ss << contents;
620: ptree pt;
621: xml_parser::read_xml(ss, pt);
622:
623: //XML解釈
624: try {
625: //年月日取得
626: for (auto it : pt.get_child("Report.Body.MeteorologicalInfos.TimeSeriesInfo.TimeDefines")) {
627: if (optional<string>dti = it.second.get_optional<string>("DateTime")) {
628: if (regex_search(dti.value(), mt1, re11)) {
629: optional<string>tid = it.second.get_optional<string>("<xmlattr>.timeId");
630: // cout << tid.value() << " : " << mt1[0].str() << endl;
631: //最初の情報は何時か
632: i = stoi(tid.value());
633: gr::date dt2(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
634:
635: if (i == 1) {
636: if (dt < dt2) {
637: dd = 0; //明日
638: } else if (dt == dt2) {
639: dd = (-1); //今日
640: } else {
641: dd = (-2); //明日
642: }
643: }
644: i += dd;
645: if (i < 0) {
646: continue;
647: }
648: //予報1日分の初期化
649: gr::date dt(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
650: this->jmaWeeklyWeather[i].year = stoi(mt1[1].str());
651: this->jmaWeeklyWeather[i].month = stoi(mt1[2].str());
652: this->jmaWeeklyWeather[i].day = stoi(mt1[3].str());
653: this->jmaWeeklyWeather[i].day_of_week = week_name[dt.day_of_week()];
654: this->jmaWeeklyWeather[i].stationName = Spots[id]->stationName;
655: this->jmaWeeklyWeather[i].weather = L"";
656: this->jmaWeeklyWeather[i].image = "";
657: this->jmaWeeklyWeather[i].rainy = "";
658: this->jmaWeeklyWeather[i].temp_max = "";
659: this->jmaWeeklyWeather[i].temp_min = "";
660: }
661: }
662: }
663:
664: for (auto it : pt.get_child("Report.Body")) {
665: //天気・降水確率の取得
666: if (optional<string>type = it.second.get_optional<string>("<xmlattr>.type")) {
667: if (_UW(type.value()) == _SW("区域予\報")) {
668: for (auto it2 : it.second.get_child("TimeSeriesInfo")) {
669: if (optional<string>acode = it2.second.get_optional<string>("Area.Code")) {
670: if (acode.value() != areaCode) {
671: continue;
672: }
673: }
674: for (auto it3 : it2.second.get_child("")) {
675: if (optional<string>type = it3.second.get_optional<string>("Property.Type")) {
676: if (_UW(type.value()) == _SW("天気")) {
677: //天気予報の取得
678: for (auto it4 : it3.second.get_child("Property.WeatherPart")) {
679: if (optional<string>we = it4.second.get_optional<string>("")) {
680: i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
681: if (i < 0) {
682: continue;
683: }
684: this->jmaWeeklyWeather[i].weather = jmaShortWeather(_UW(we.value()));
685: }
686: }
687: //天気予報用テロップ番号の取得
688: for (auto it4 : it3.second.get_child("Property.WeatherCodePart")) {
689: if (optional<string>we = it4.second.get_optional<string>("")) {
690: i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
691: if (i < 0) {
692: continue;
693: }
694: if (i == 0) {
695: this->jmaWeeklyWeather[i].image = jma_telop2url(stoi(we.value()), mode);
696: } else {
697: this->jmaWeeklyWeather[i].image = jma_telop2url(stoi(we.value()), 0);
698: }
699: }
700: }
701: } else if (_UW(type.value()) == _SW("降水確率")) {
702: //降水確率の取得
703: for (auto it4 : it3.second.get_child("Property.ProbabilityOfPrecipitationPart")) {
704: if (optional<string>we = it4.second.get_optional<string>("")) {
705: i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
706: if (i < 0) {
707: continue;
708: }
709: this->jmaWeeklyWeather[i].rainy = we.value();
710: }
711: }
712: }
713: }
714: }
715: }
716: } else if (_UW(type.value()) == _SW("地点予\報")) {
717: for (auto it2 : it.second.get_child("TimeSeriesInfo")) {
718: if (optional<string>scode = it2.second.get_optional<string>("Station.Code")) {
719: if (scode.value() != stationCode) {
720: continue;
721: }
722: }
723: for (auto it3 : it2.second.get_child("")) {
724: for (auto it4 : it3.second.get_child("")) {
725: if (optional<string>type = it4.second.get_optional<string>("Type")) {
726: if (_UW(type.value()) == _SW("最低気温")) {
727: //最低気温の取得
728: for (auto it5 : it4.second.get_child("TemperaturePart")) {
729: if (optional<string>we = it5.second.get_optional<string>("")) {
730: i = stoi(it5.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
731: if (i < 0) {
732: continue;
733: }
734: this->jmaWeeklyWeather[i].temp_min = we.value();
735: }
736: }
737: } else if (_UW(type.value()) == _SW("最高気温")) {
738: //最高気温の取得
739: for (auto it5 : it4.second.get_child("TemperaturePart")) {
740: if (optional<string>we = it5.second.get_optional<string>("")) {
741: i = stoi(it5.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
742: if (i < 0) {
743: continue;
744: }
745: this->jmaWeeklyWeather[i].temp_max = we.value();
746: }
747: }
748: }
749: }
750: }
751: }
752: }
753: }
754: }
755: }
756:
757: //XML解釈エラー
758: } catch(ptree_bad_path& e) {
759: errmsg = _SW("府県週間天気予\報情報を取得できません");
760: return FALSE;
761: }
762: //読み込みエラー
763: } catch(xml_parser_error& e) {
764: errmsg = _SW("府県週間天気予\報情報を取得できません");
765: return FALSE;
766: }
767: }
768:
769: //VPFD51(府県天気予報 R1)の解析
770: code = "VPFD51";
771: //予報地点コードの取得
772: res = FALSE;
773: for (id = 0; id < SIZE_SPOTS; id++) {
774: if (Spots[id] == NULL) {
775: break;
776: }
777: if ((Spots[id]->code == code) && (Spots[id]->stationCode == station)) {
778: res = TRUE;
779: break;
780: }
781: }
782: if (res == FALSE) {
783: errmsg = _SW("予\報地点コードが見つかりません");
784: return FALSE;
785: }
786: areaCode = Spots[id]->areaCode;
787:
788: string rain_table[8] = { "-", "-", "-", "-", "-", "-", "-", "-", };
789: int temp_table[5] = { 0, 0, 0, 0, 0 };
790: //XML読み込み
791: string contents2 = "";
792: // readWebContents(vpfd51, UserAgent, &contents2);
793: if (pCC->load(vpfd51, &contents2) == FALSE) {
794: errmsg = pCC->getError();
795: return FALSE;
796: }
797: try {
798: std::stringstream ss;
799: ss << contents2;
800: ptree pt;
801: xml_parser::read_xml(ss, pt);
802:
803: //XML解釈
804: try {
805: //年月日取得
806: for (auto it : pt.get_child("Report.Body")) {
807: if (optional<string>type = it.second.get_optional<string>("<xmlattr>.type")) {
808: if (_UW(type.value()) == _SW("区域予\報")) {
809: //日時
810: for (auto it2 : it.second.get_child("")) {
811: for (auto it3 : it2.second.get_child("")) {
812: for (auto it4 : it3.second.get_child("")) {
813: if (optional<string>name = it4.second.get_optional<string>("Name")) {
814: wstring ws = _UW(name.value());
815:
816: if (regex_search(ws, mt2, re21)) {
817: string ss = it4.second.get_optional<string>("DateTime").value();
818: regex_search(ss, mt1, re12);
819: optional<string>tid = it4.second.get_optional<string>("<xmlattr>.timeId");
820: // cout << tid.value() << " : " << mt1[0].str() << endl;
821: //最初の情報は何時か
822: i = stoi(tid.value());
823: gr::date dt2(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
824: if (i == 1) {
825: if (dt < dt2) {
826: dd = 0; //明日
827: } else if (dt == dt2) {
828: dd = (-1); //今日
829: } else {
830: dd = (-2); //明日
831: }
832: d2 = stoi(mt1[4].str()) / 6; //6時間毎
833: if (dd == (-2)) {
834: d2 = d2 - 4 + 1;
835: }
836: }
837: } else if (optional<string>s2 = it4.second.get_optional<string>("DateTime")) {
838: string ss = s2.value();
839: if (regex_search(ss, mt1, re11)) {
840: //最初の情報は何時か
841: optional<string>tid = it4.second.get_optional<string>("<xmlattr>.timeId");
842: i = stoi(tid.value());
843: gr::date dt2(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
844: if (i == 1) {
845: if (dt < dt2) {
846: dd = 0; //明日
847: } else if (dt == dt2) {
848: dd = (-1); //今日
849: } else {
850: dd = (-2); //明日
851: }
852: }
853: }
854: }
855: }
856: }
857: }
858: //地方コードは上何桁で判定するか
859: bool flag = FALSE;
860: for (n = 5; n >= 1; n--) {
861: for (auto it3 : it2.second.get_child("")) {
862: if (optional<string>code = it3.second.get_optional<string>("Area.Code")) {
863: if (code.value().substr(0, n) != areaCode.substr(0, n)) {
864: continue;
865: }
866: flag = TRUE;
867: }
868: }
869: if (flag) {
870: break;
871: }
872: }
873: // cout << "N = " << n << endl;
874: // cout << "areaCode = " << areaCode << endl;
875:
876: //天気予報
877: for (auto it3 : it2.second.get_child("")) {
878: if (optional<string>code = it3.second.get_optional<string>("Area.Code")) {
879:
880: if (code.value().substr(0, n) != areaCode.substr(0, n)) {
881: continue;
882: }
883: }
884: // cout << it3.first << endl;
885: if (optional<string>type = it3.second.get_optional<string>("Kind.Property.Type")) {
886: if (_UW(type.value()) == _SW("天気")) {
887: //天気予報の取得
888: for (auto it4 : it3.second.get_child("Kind.Property.WeatherPart")) {
889: i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
890: if (i < 0) {
891: continue;
892: }
893: this->jmaWeeklyWeather[i].weather = jmaShortWeather(_UW(it4.second.get_optional<string>("").value()));
894: }
895: //天気予報用テロップ番号の取得
896: for (auto it4 : it3.second.get_child("Kind.Property.WeatherCodePart")) {
897: i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd;
898: if (i < 0) {
899: continue;
900: }
901: if (i == 0) {
902: this->jmaWeeklyWeather[i].image = jma_telop2url(stoi(it4.second.get_optional<string>("").value()), mode);
903: } else {
904: this->jmaWeeklyWeather[i].image = jma_telop2url(stoi(it4.second.get_optional<string>("").value()), 0);
905: }
906: }
907:
908: } else if (_UW(type.value()) == _SW("降水確率")) {
909: //降水確率の取得
910: for (auto it4 : it3.second.get_child("Kind.Property.ProbabilityOfPrecipitationPart")) {
911: i = stoi(it4.second.get_optional<string>("<xmlattr>.refID").value()) + dd + d2;
912: if (i < 0) {
913: continue;
914: }
915: rain_table[i] = it4.second.get_optional<string>("").value();
916: }
917: }
918: }
919: }
920: }
921: } else if (_UW(type.value()) == _SW("地点予\報")) {
922: //日時
923: int day0 = 0;
924: for (auto it2 : it.second.get_child("")) {
925: for (auto it3 : it2.second.get_child("")) {
926: for (auto it4 : it3.second.get_child("")) {
927: if (optional<string>s2 = it4.second.get_optional<string>("DateTime")) {
928: string ss = s2.value();
929: if (regex_search(ss, mt1, re11)) {
930: //最初の情報は何時か
931: optional<string>tid = it4.second.get_optional<string>("<xmlattr>.timeId");
932: i = stoi(tid.value());
933: gr::date dt2(stoi(mt1[1].str()), stoi(mt1[2].str()), stoi(mt1[3].str()));
934: if (i == 1) {
935: if (dt < dt2) {
936: dd = 0; //明日
937: } else if (dt == dt2) {
938: dd = (-1); //今日
939: } else {
940: dd = (-2); //明日
941: }
942: temp_table[i] = 0;
943: day0 = stoi(mt1[3]);
944: } else {
945: if (day0 == stoi(mt1[3])) {
946: temp_table[i] = 0;
947: } else {
948: temp_table[i] = 1;
949: }
950: day0 = stoi(mt1[3]);
951: }
952: }
953: }
954: }
955: }
956: //最低気温・最高気温
957: i = 1 + dd;
958: for (auto it3 : it2.second.get_child("")) {
959: if (optional<string>code = it3.second.get_optional<string>("Station.Code")) {
960:
961: if (code.value() != stationCode) {
962: continue;
963: }
964: }
965: //最高気温・最低気温
966: for (auto it4 : it3.second.get_child("")) {
967: for (auto it5 : it4.second.get_child("")) {
968: if (optional<string>ts = it5.second.get_optional<string>("Type")) {
969: wstring ws = _UW(ts.value());
970: // cout << _WS(ws) << endl;
971: if (regex_search(ws, mt2, re31)) {
972: int i2 = stoi(it5.second.get_optional<string>("TemperaturePart.jmx_eb:Temperature.<xmlattr>.refID").value());
973: i += temp_table[i2];
974: if (i < 0) {
975: continue;
976: }
977: this->jmaWeeklyWeather[i].temp_min = it5.second.get_optional<string>("TemperaturePart.jmx_eb:Temperature").value();
978: } else if (regex_search(ws, mt2, re32)) {
979: int i2 = stoi(it5.second.get_optional<string>("TemperaturePart.jmx_eb:Temperature.<xmlattr>.refID").value());
980: i += temp_table[i2];
981: if (i < 0) {
982: continue;
983: }
984: this->jmaWeeklyWeather[i].temp_max = it5.second.get_optional<string>("TemperaturePart.jmx_eb:Temperature").value();
985: }
986: }
987: }
988: }
989: }
990: }
991: }
992: }
993: }
994:
995: //XML解釈エラー
996: } catch(ptree_bad_path& e) {
997: errmsg = _SW("府県天気予\報情報を取得できません");
998: return FALSE;
999: }
1000: //読み込みエラー
1001: } catch(xml_parser_error& e) {
1002: errmsg = _SW("府県天気予\報情報を取得できません");
1003: return FALSE;
1004: }
1005:
1006: //降水確率を天気予報情報へ
1007: for (int j = 0; j < 8; j++) {
1008: if ((dd == (-2)) && (j >= 4)) break;
1009: i = floor(j / 4.0);
1010: if (regex_search(jmaWeeklyWeather[i].rainy, mt1, re41)) {
1011: this->jmaWeeklyWeather[i].rainy = "";
1012: }
1013: this->jmaWeeklyWeather[i].rainy += rain_table[j];
1014: if (j % 4 != 3) {
1015: this->jmaWeeklyWeather[i].rainy += "/";
1016: }
1017: }
1018:
1019: /** debug
1020: for (int j = 0; j <= 7; j++) {
1021: cout << j << ":"
1022: << _WS(this->jmaWeeklyWeather[j].stationName) << ":"
1023: << this->jmaWeeklyWeather[j].year << "/"
1024: << this->jmaWeeklyWeather[j].month << "/"
1025: << this->jmaWeeklyWeather[j].day << "("
1026: << _WS(this->jmaWeeklyWeather[j].day_of_week) << ") "
1027: << _WS(this->jmaWeeklyWeather[j].weather) << " "
1028: << this->jmaWeeklyWeather[j].image << " "
1029: << this->jmaWeeklyWeather[j].rainy << "% "
1030: << this->jmaWeeklyWeather[j].temp_max << "/"
1031: << this->jmaWeeklyWeather[j].temp_min << "℃ "
1032: << endl;
1033: }
1034: **/
1035:
1036: contents.clear();
1037: contents2.clear();
1038:
1039: return TRUE;
1040: }
解説:pahooCacheクラスとデータ構造
- Atomフィード(長期フィード:定時)
- VPFW50
- VPFD51
52: //キャッシュ・ディレクトリ
53: #define CACHEDIR_WEATHER "pcache\\"
54:
55: //キャッシュ保持時間(デフォルト;分)(0:キャッシュしない)
56: #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関数を使って読み込んでいる。
参考サイト
- PHPで天気予報を求める:ぱふぅ家のホームページ
- 気象庁防災情報XMLフォーマット 情報提供ページ
- WiX によるWindowsインストーラー作成:ぱふぅ家のホームページ
- C++ 開発環境の準備:ぱふぅ家のホームページ
- PHPで地図で指定した場所の週間カレンダーを表示:ぱふぅ家のホームページ
インターネット経由で気象庁防災情報XMLにアクセスし、地図や住所、ランドマーク,郵便番号等で指定した地点の週間天気予報を一覧表示するアプリケーションを作る。一覧情報をクリップボードにコピーしたり、CSV形式ファイルに保存することができる。また、ユーザーがAPIキーを取得することでGoogleマップやGoogle住所検索も利用できるようになる。
(2023年3月18日)予報地点情報ファイルおよび使用ライブラリを更新.
(2023年2月11日)予報地点情報ファイルおよび使用ライブラリを更新.
(2022年9月24日)ウィンドウ位置保存,ライブラリ更新.
(2022年8月20日)予報地点情報ファイルおよび使用ライブラリを更新.設定クリア機能を追加.