
サンプル・プログラム
weeklyweather.msi | インストーラ |
bin/weeklyweather.exe | 実行プログラム本体 |
bin/jmaweatherspots.xml | 予報地点情報ファイル |
bin/cwebpage.dll bin/libcrypto-1_1.dll bin/libcurl.dll bin/libssl-1_1.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.cpp | 住所・緯度・経度に関わるクラス(ヘッダ) |
sour/pahooWeather.cpp | 気象情報に関わるクラス(ソース) |
sour/pahooWeather.cpp | 気象情報に関わるクラス(ヘッダ) |
使用ライブラリ
リソースの準備
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 にすること。
解説:定数など
0038: // 定数など ==================================================================
0039: #define MAKER "pahoo.org" //作成者
0040: #define APPNAME "weeklyweather" //アプリケーション名
0041: #define APPNAMEJP "週間天気予\報" //アプリケーション名(日本語)
0042: #define APPVERSION "1.2" //バージョン
0043: #define APPYEAR "2020" //作成年
0044: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-16-01.shtm" //参考サイト
0045:
0046: //ヘルプ・ファイル
0047: #define HELPFILE ".\\etc\\help.chm"
0048:
0049: //デフォルト保存ファイル名
0050: #define SAVEFILE "weeklyweather.csv"
0051:
0052: //エラー・メッセージ格納用:変更不可
0053: string ErrorMessage;
0054:
0055: //現在のインターフェイス
0056: HINSTANCE hInst;
0057:
0058: //親ウィンドウ
0059: HWND hParent;
0060:
0061: //ブラウザ・コントロール
0062: WebBrowser wBrowser;
0063:
0064: //UserAgent
0065: string UserAgent;
0066:
0067: //pahooWeatherオブジェクト
0068: pahooWeather *pWT;
0069:
0070: //pahooGeocodeオブジェクト
0071: pahooGeocode *pGC;
0072:
0073: //予報表のID
0074: #define IDC_CAL_LABEL 1501 //日付
0075: #define IDC_RES_LABEL 1601 //天気予報
0076: #define IDC_RES_IMAGE 1701 //天気アイコン
0077: #define IDC_TEMP_LABEL 1801 //降水確率・最低・最高気温
0078:
0079: //予報表の座標
0080: #define IDC_RES_X 10
0081: #define IDC_RES_Y 430
0082: #define IDC_RES_WIDTH 80
0083:
0084: //マップに表示するマーカー画像URL
0085: #define URL_MARKER "https://maps.google.co.jp/mapfiles/ms/icons/yellow-dot.png"
0086: #define MAP_WIDTH 530 //地図の幅(ピクセル)
0087: #define MAP_HEIGHT 300 //地図の高さ(ピクセル)
0088:
0089: //マップID
0090: #define MAP_ID "map_id"
0091: //経度・緯度(初期値)
0092: #define DEF_LONGITUDE 139.766667
0093: double Longitude = DEF_LONGITUDE;
0094: #define DEF_LATITUDE 35.681111
0095: double Latitude = DEF_LATITUDE;
0096: //地図拡大率(初期値)
0097: #define DEF_ZOOM 6
0098: int Zoom = DEF_ZOOM;
0099: //地図の種類(初期値)
0100: #define DEF_MAPTYPE "GSISTD"
0101: string Maptype = DEF_MAPTYPE;
解説:クラスとデータ構造
0024: //予報情報
0025: typedef struct _forecast {
0026: int year; //西暦年
0027: int month; //月
0028: int day; //日
0029: std::wstring day_of_week; //曜日
0030: std::wstring weather; //天気予報
0031: std::wstring image; //天気予報アイコンURL
0032: std::wstring rainy; //降水確率
0033: std::wstring temp_max; //最高気温
0034: std::wstring temp_min; //最低気温
0035: std::wstring city; //予報地点名
0036: std::wstring id; //ID
0037: } forecast_t;
0037: //予報地点情報格納用クラス ===================================================
0038: #define SIZE_SPOTS 500 //格納上限
0039: class _Spots {
0040: public:
0041: wstring id = L""; //ID
0042: wstring address = L""; //住所
0043: wstring query = L""; //検索キー
0044: double latitude = 0.0; //緯度
0045: double longitude = 0.0; //経度
0046: };
0047: unique_ptr<_Spots> Spots[SIZE_SPOTS] = {};
0183: /**
0184: * 指定した緯度・経度に最も近い予報地点IDを返す
0185: * @param object $pgc pahooGeoCodeオブジェクト
0186: * @param double latitude 緯度(世界測地系)
0187: * @param double longitude 経度(世界測地系)
0188: * @return wstring 予報地点ID
0189: */
0190: wstring pahooWeather::getJmaNearSpot(double longitude, double latitude) {
0191: wstring id = L"";
0192: double d0 = 999999999.9;
0193: for (int i = 0; i < SIZE_SPOTS; i++) {
0194: if (Spots[i] == NULL) {
0195: break;
0196: }
0197: double d1 = this->distance(Spots[i]->longitude, Spots[i]->latitude, longitude, latitude);
0198: if (d1 < d0) {
0199: id = Spots[i]->id;
0200: d0 = d1;
0201: }
0202: }
0203: return id;
0204: }
0420: /**
0421: * 指定したIDの週間天気予報を読み込む
0422: * @param wstring id 地点ID
0423: * @param int* cnt 都市番号を格納
0424: * @return bool TRUE:読込成功/FALSE:失敗
0425: */
0426: bool pahooWeather::jma_readWeeklyWeatherById(const wstring id, int *cnt) {
0427: wsmatch mt;
0428: wregex re(_SW("^([0-9]{4})([0-9]{2})$"));
0429:
0430: //地点IDのベリフィケーション
0431: if (! regex_search(id, mt, re)) {
0432: this->errmsg = _SW("地点ID(") + id + _SW(")が不正です");
0433: return FALSE;
0434: }
0435:
0436: //読み込みURL
0437: char buff[SIZE_BUFF + 1];
0438: snprintf(buff, SIZE_BUFF, "%03d.html", stoi(_WS(mt[1].str()).c_str()));
0439: string url = JMA_WEEKLY_URL + (string)buff;
0440: *cnt = stoi(mt[2].str());
0441:
0442: this->__jma_readWeeklyWeather(0, 0, url);
0443:
0444: return TRUE;
0445: }
0228: /**
0229: * 指定したURLから週間天気予報を読み込む
0230: * @param int num 都道府県番号(1:北海道〜)
0231: * @param int start 開始都市番号
0232: * @param string url URL
0233: * @return int 読み込んだ最後の都市番号/(-1):読込失敗
0234: */
0235: int pahooWeather::__jma_readWeeklyWeather(int num, int start, const string url) {
0236: static wstring week_name[] = {_SW("日"), _SW("月"), _SW("火"), _SW("水"), _SW("木"), _SW("金"), _SW("土")};
0237:
0238: //cURLによる結果取得
0239: string contents = "";
0240: bool res = readWebContents(url, UserAgent, &contents);
0241: if (! res) {
0242: this->errmsg = _SW("気象庁サイトへの接続エラー");
0243: return (-1);
0244: }
0245:
0246: //コンテンツの解釈
0247: setlocale(LC_ALL, "Japanese");
0248: stringstream ss;
0249: string ss0;
0250: wstring ws;
0251: wsmatch mt1, mt2;
0252: wregex re01(_SW("<th\\s+class\\=\"weekday\"\\s+colspan\\=\"2\">"));
0253: wregex re02(_SW("<th\\s+class\\=\"[^\"]+\">([0-9]+)<"));
0254: wregex re11(_SW("<input\\s+class\\=\"linkbtn\"\\s+type\\=\"button\"\\s+title\\=\"府県天気"));
0255: wregex re12(_SW("<td\\s+class\\=\"for\"\\s+nowrap>([^\\>]+)<br><img\\s+src\\=\"([^\"]+)\"\\s+align\\=\"middle\""));
0256: wregex re21(_SW("<td\\s+colspan\\=\"2\"\\s+class\\=\"normal\">"));
0257: wregex re22(_SW("<td\\s+class\\=\"for\"><font\\s+class\\=\"pop\">([^\\>]+)<\\/font><\\/td>"));
0258: wregex re31(_SW("<th\\s+class\\=\"cityname\"\\s+rowspan\\=\"2\">([^\\>]+)<\\/th>"));
0259: wregex re41(_SW("<td\\s+class\\=\"for\"\\s+nowrap><font\\s+class\\=\"maxtemp\">([^\\<]+)<"));
0260: wregex re42(_SW("低\\(℃\\)"));
0261: wregex re43(_SW("<td\\s+class\\=\"for\"\\s+nowrap><font\\s+class\\=\"mintemp\">([^\\<]+)<"));
0262: wregex re44(_SW("<td\\s+class\\=\"for\">/<\\/td>"));
0263: wregex re51(_SW("<caption\\s+style\\=\"text\\-align\\:left\\;\">([0-9]+)月([0-9]+)日[0-9]+時.+の週間天気予\報<\\/caption>"));
0264: wregex re61(_SW("\\/([0-9]+)\\.html$"));
0265:
0266: int flag = 0;
0267: int cnt = start;
0268: int dd = 0;
0269: int year = 0, month = 0, day = 0;
0270: int i = 0;
0271: time_t now = time(NULL);
0272: struct tm* pnow = localtime(&now);
0273: namespace gr = boost::gregorian;
0274: gr::date dt;
0275: static char buff[SIZE_BUFF + 1];
0276: wstring wurl;
0277:
0278: ss << contents;
0279: while (ss && getline(ss, ss0)) {
0280: //1行をwstring変換
0281: ws = _UW(ss0);
0282: switch (flag) {
0283: //日付開始
0284: case 0:
0285: //発表月日取得
0286: if (regex_search(ws, mt1, re51)) {
0287: month = (int)stoi(mt1[1].str().c_str());
0288: day = (int)stoi(mt1[2].str().c_str());
0289: //発表年は内蔵時計から取得
0290: if (month > 12) {
0291: year = pnow->tm_year + 1900 - 1;
0292: } else {
0293: year = pnow->tm_year + 1900;
0294: }
0295: }
0296: if (regex_search(ws, mt1, re01)) {
0297: flag = 1;
0298: }
0299: break;
0300: //日付の解釈
0301: case 1:
0302: if (regex_search(ws, mt1, re02)) {
0303: //月の補正
0304: if (dd == 0) {
0305: //月またぎ
0306: if ((int)stoi(mt1[1].str().c_str()) < day) {
0307: //年またぎ
0308: if (month == 12) {
0309: year++;
0310: month = 1;
0311: day = (int)stoi(mt1[1].str().c_str());
0312: } else {
0313: month++;
0314: day = (int)stoi(mt1[1].str().c_str());
0315: }
0316: } else {
0317: day = (int)stoi(mt1[1].str().c_str());
0318: }
0319: dt = gr::date(year, month, day);
0320: }
0321: this->Forecast[cnt][dd].year = dt.year();
0322: this->Forecast[cnt][dd].month = dt.month();
0323: this->Forecast[cnt][dd].day = dt.day();
0324: this->Forecast[cnt][dd].day_of_week = week_name[dt.day_of_week()];
0325: dd++;
0326: dt = dt + gr::days(1);
0327: } else if (regex_search(ws, mt1, re11)) {
0328: flag = 2;
0329: dd = 0;
0330: }
0331: break;
0332: //天候の解釈
0333: case 2:
0334: if (regex_search(ws, mt1, re12)) {
0335: if (mt1[1].str() == L"") {
0336: this->Forecast[cnt][dd].weather = L"--";
0337: } else {
0338: this->Forecast[cnt][dd].weather = mt1[1].str();
0339: }
0340: if (mt1[2].str() == L"") {
0341: this->Forecast[cnt][dd].image = L"";
0342: } else {
0343: this->Forecast[cnt][dd].image = mt1[2].str();
0344: }
0345: dd++;
0346: } else if (regex_search(ws, mt1, re21)) {
0347: //2番目以降の年の日付代入
0348: if (cnt >= 1) {
0349: for (int i = 0; i < 7; i++) {
0350: this->Forecast[cnt][i].year = this->Forecast[cnt - 1][i].year;
0351: this->Forecast[cnt][i].month = this->Forecast[cnt - 1][i].month;
0352: this->Forecast[cnt][i].day = this->Forecast[cnt - 1][i].day;
0353: this->Forecast[cnt][i].day_of_week = this->Forecast[cnt - 1][i].day_of_week;
0354: }
0355: }
0356: flag = 3;
0357: dd = 0;
0358: }
0359: this->Forecast[cnt][dd].rainy = L"";
0360: break;
0361: //降水確率の解釈
0362: case 3:
0363: if (regex_search(ws, mt1, re22)) {
0364: //複数の数字がある場合は平均値を計算
0365: this->Forecast[cnt][dd].rainy = _SW(to_string((int)waverage(mt1[1].str())));
0366: dd++;
0367: //都市名+ID
0368: } else if (regex_search(ws, mt1, re31)) {
0369: wurl = _SW(url);
0370: if (! regex_search(wurl, mt2, re61)) {
0371: this->errmsg = _SW("気象庁サイトの解析に失敗しました");
0372: return FALSE;
0373: }
0374: //id = URLの数字(4桁)+連番(2桁)
0375: snprintf(buff, SIZE_BUFF, "%04d%02d", stoi(_WS(mt2[1].str())), cnt - start);
0376: for (i = 0; i < 7; i++) {
0377: this->Forecast[cnt][i].city = mt1[1].str();
0378: this->Forecast[cnt][i].id = _SW(buff);
0379: }
0380: flag = 4;
0381: dd = 0;
0382: }
0383: this->Forecast[cnt][dd].temp_max = L"";
0384: this->Forecast[cnt][dd].temp_min = L"";
0385: break;
0386: //最高気温の解釈
0387: case 4:
0388: if (regex_search(ws, mt1, re41)) {
0389: this->Forecast[cnt][dd].temp_max = mt1[1].str();
0390: dd++;
0391: } else if (regex_search(ws, mt1, re42)) {
0392: flag = 5;
0393: dd = 0;
0394: }
0395: break;
0396: //最低気温の解釈
0397: case 5:
0398: if (regex_search(ws, mt1, re43)) {
0399: this->Forecast[cnt][dd].temp_min = mt1[1].str();
0400: dd++;
0401: } else if (regex_search(ws, mt1, re44)) {
0402: this->Forecast[cnt][dd].temp_min = L"";
0403: dd++;
0404: //次の地域
0405: } else if (regex_search(ws, mt1, re11)) {
0406: flag = 2;
0407: cnt++;
0408: dd = 0;
0409: }
0410: break;
0411: default:
0412: break;
0413: }
0414: }
0415: contents.clear();
0416:
0417: return cnt;
0418: }
解説:天気予報画像を表示
0205: /**
0206: * 天気予報画像を表示
0207: * @param HWND hWnd 表示するウィンドウ・ハンドラ
0208: * @param string url 画像アイコンURL
0209: * @return bool TRUE:表示成功/FALSE:失敗
0210: */
0211: bool readURLimage(HWND hWnd, const string url) {
0212: if (pWT->isError()) {
0213: return FALSE;
0214: }
0215:
0216: //cURLによるアイコン画像取得
0217: string contents = "";
0218: bool res = readWebContents(url, UserAgent, &contents);
0219: if (! res) {
0220: ErrorMessage = "気象庁サイトへの接続エラーです";
0221: return FALSE;
0222: }
0223:
0224: //表示領域クリア
0225: IStream *pStream;
0226: Graphics mygraphics(hWnd);
0227: mygraphics.Clear(Gdiplus::Color(255, 255, 255, 255));
0228: Bitmap *image;
0229: //ストリーム読み込み準備
0230: HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, contents.size());
0231: //ロックしてポインタ取得
0232: LPVOID lpBuf = GlobalLock(hGlobal);
0233: //メモリ読み込み
0234: memcpy(lpBuf, contents.c_str(), contents.size());
0235: //アンロック
0236: GlobalUnlock(hGlobal);
0237: //ストリーム変換
0238: CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);
0239: //ストリームから読み込み
0240: image = Bitmap::FromStream(pStream);
0241: //画面表示
0242: mygraphics.DrawImage(image, 10, 10);
0243:
0244: //メモリ解放
0245: GlobalFree(hGlobal);
0246: contents.clear();
0247:
0248: return TRUE;
0249: }
以前紹介した「C++で撮影場所をマッピング -画像ファイルを読み込む]」と同じく GDI+:blue を用いているが、ファイルではなく、cURL を使って気象庁週間予報サイトからメモリへダウンロードし、メモリからロードして画面に表示させるようにしている。
参考サイト
- PHP で天気予報を求める(その 3):ぱふぅ家のホームページ
- 週間天気予報:気象庁
- WiX による Windows インストーラー作成:ぱふぅ家のホームページ
- C++ 開発環境の準備:ぱふぅ家のホームページ
「PHP で天気予報を求める(その 3)」で作った PHP プログラムを C++に移植したものである。
(2020 年 12 月 20 日)月日取得方式変更,leaflet スクリプトの参照URL 変更