
目次
サンプル・プログラム
earthquakewin.msi | インストーラ |
bin/earthquakewin.exe | 実行プログラム本体 |
bin/cwebpage.dll bin/libcurl.dll | 実行時に必要になるDLL |
bin/etc/help.chm | ヘルプ・ファイル |
sour/earthquakewin.cpp | ソース・プログラム |
sour/resource.h | リソース・ヘッダ |
sour/resource.rc | リソース・ファイル |
sour/mystrings.cpp | 汎用文字列処理関数など(ソース) |
sour/mystrings.h | 汎用文字列処理関数など(ヘッダ) |
sour/pahooGeocode.cpp | 住所・緯度・経度に関わるクラス(ソース) |
sour/pahooGeocode.hpp | 住所・緯度・経度に関わるクラス(ヘッダ) |
sour/apikey.cpp | APIキーの管理(ソース) |
sour/apikey.hpp | APIキーの管理(ヘッダ) |
sour/webbrowser.hpp | Webブラウザ・クラス(ヘッダ) |
sour/pahooCache.cpp | キャッシュ処理に関わるクラス(ソース) |
sour/pahooCache.hpp | キャッシュ処理に関わるクラス(ヘッダ) |
sour/makefile | ビルド |
使用ライブラリ
また、地図表示にWebブラウザ・コントロールを利用するため "cwebpage.dll" を利用する。codeproject からダウンロードできる。
リソースの準備
Eclipse を起動したら、新規プロジェクト earthquakewin を用意する。
ResEdit を起動したら、resource.rc を用意する。
Eclipse に戻り、ソース・プログラム "earthquakewin.cpp" を追加する。
リンカー・フラグを -Wl,--enable-stdcall-fixup -mwindows -lgdiplus -lole32 -static -lstdc++ -lgcc -lwinpthread -lcurl -lssl "C:\(libcurl.dllのフォルダ)\libcurl.dll" "C:\(cwebpage.dllのフォルダ)\cwebpage.dll" に設定する。
また、コマンド行パターンをアレンジし "${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS} -luuid -loleaut32 -lole32" とする。
MSYS2 コマンドラインからビルドするのであれば、"makefile" を利用してほしい。

MSYS2 コマンドラインからビルドするのであれば、"makefile" を利用してほしい。
解説:定数など
0040: #define MAKER "pahoo.org" //作成者
0041: #define APPNAME "earthquakewin" //アプリケーション名
0042: #define APPNAMEJP "最近の地震情報" //アプリケーション名(日本語)
0043: #define APPVERSION "3.4.1" //バージョン
0044: #define APPYEAR "2020-22" //作成年
0045: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-12-01.shtm" // 参考サイト
0046:
0047: //char*バッファサイズ
0048: #define SIZE_BUFF 512
0049:
0050: //現在のインターフェイス
0051: HINSTANCE hInst;
0052:
0053: //アプリケーション・ウィンドウ
0054: HWND hParent;
0055:
0056: //アプリケーション・ウィンドウ位置
0057: unsigned hParent_X, hParent_Y;
0058:
0059: //エラー・メッセージ格納用【変更不可】
0060: string ErrorMessage;
0061:
0062: //ListViewItemの最大文字長【変更不可】
0063: #define MAX_LISTVIEWITEM 259
0064:
0065: //ヘルプ・ファイル
0066: #define HELPFILE ".\\etc\\help.chm"
0067:
0068: //デフォルト保存ファイル名
0069: #define SAVEFILE "eq_%04d%02d%02d_%02d%02d.csv"
0096: //マップID
0097: #define MAP_ID "map_id"
0098: //地図の大きさ
0099: #define MAP_WIDTH 600 //地図の幅(ピクセル)
0100: #define MAP_HEIGHT 400 //地図の高さ(ピクセル)
0101: //経度・緯度(初期値)
0102: #define DEF_LONGITUDE 139.766667
0103: double Longitude = DEF_LONGITUDE;
0104: #define DEF_LATITUDE 35.681111
0105: double Latitude = DEF_LATITUDE;
0106: //地図拡大率(初期値)
0107: #define DEF_ZOOM 6
0108: int Zoom = DEF_ZOOM;
0109: //地図の種類(初期値)
0110: #define DEF_MAPTYPE "GSISTD"
0111: string Maptype = DEF_MAPTYPE;
0112: #define INFO_WIDTH (int)(MAP_WIDTH * 0.75) //情報ウィンドウの最大幅
0113: #define INFO_OFFSET_X 0 //情報ウィンドウのオフセット位置(X)
0114: #define INFO_OFFSET_Y -10 //情報ウィンドウのオフセット位置(Y)
0115:
0116: //地震情報の最大格納数
0117: #define MAX_EARTHQUAKE 100
解説:データ構造
0122: //地震情報を格納する構造体
0123: struct _Earthquake {
0124: int id = 0; //マッピングID
0125: int year = 0; //西暦年
0126: int month = 0; //月
0127: int day = 0; //日
0128: int hour = 0; //時
0129: int minuite = 0; //分
0130: wstring location = L""; //震源地
0131: double latitude = 0.0; //緯度
0132: double longitude = 0.0; //経度
0133: double depth = 0.0; //深さ
0134: double magnitude = -1.0; //マグニチュード
0135: int maxintensity = -1; //最大震度
0136: } Earthquake[MAX_EARTHQUAKE];
解説:キャッシュ・システム
- Atomフィード:高頻度フィード:地震火山
- Atomフィード:長期フィード:地震火山
- VXSE53
0071: //キャッシュ・ディレクトリ
0072: #define DIR_CACHE_FEED "pcache1\\"
0073: #define DIR_CACHE_FEED_L "pcache2\\"
0074: #define DIR_CACHE_DATA "pcache3\\"
0075:
0076: //キャッシュ保持時間(デフォルト;分)(0:キャッシュしない)
0077: #define LIFE_CACHE_FEED 5 //高頻度フィードに対して
0078: #define LIFE_CACHE_FEED_L 120 //長期フィードに対して
0079: #define LIFE_CACHE_DATA 720 //地震情報に対して
配布ファイルは、新しい地震情報が入ってくることを考え、フィードの方を短く、地震情報の方は長くキャッシュ保持時間を設定してある。
解説:ワイド文字列中の改行を他の文字列に置換
0315: /**
0316: * ワイド文字列中の改行を他の文字列に置換する
0317: * @param wstring str 置換対象の文字列
0318: * @param wstring rep 置換文字列
0319: * @return wstring 置換後の文字列
0320: */
0321: wstring wrepNL(wstring str, wstring rep) {
0322: wstring strRet;
0323: wstring::iterator ite = str.begin();
0324: wstring::iterator iteEnd = str.end();
0325:
0326: if (0 < str.size()) {
0327: wchar_t bNextChar = *ite++;
0328: while (1) {
0329: if (L'\r' == bNextChar) {
0330: // 改行確定
0331: strRet += rep;
0332: // EOF判定
0333: if (ite == iteEnd) {
0334: break;
0335: }
0336: // 1文字取得
0337: bNextChar = *ite++;
0338: if (L'\n' == bNextChar) {
0339: // EOF判定
0340: if (ite == iteEnd) {
0341: break;
0342: }
0343: // 1文字取得
0344: bNextChar = *ite++;
0345: }
0346: } else if (L'\n' == bNextChar) {
0347: // 改行確定
0348: strRet += rep;
0349: // EOF判定
0350: if (ite == iteEnd) {
0351: break;
0352: }
0353: // 1文字取得
0354: bNextChar = *ite++;
0355: if (L'\r' == bNextChar) {
0356: // EOF判定
0357: if (ite == iteEnd) {
0358: break;
0359: }
0360: // 1文字取得
0361: bNextChar = *ite++;
0362: }
0363: } else {
0364: // 改行以外
0365: strRet += bNextChar;
0366: // EOF判定
0367: if (ite == iteEnd) {
0368: break;
0369: }
0370: // 1文字取得
0371: bNextChar = *ite++;
0372: }
0373: };
0374: }
0375: return strRet;
0376: }
解説:地震情報取得
0489: /**
0490: * 地震情報取得(気象庁防災情報XMLから)
0491: * @param なし
0492: * @return bool TRUE:取得成功/FALSE:失敗
0493: */
0494: bool getEarthquake(void) {
0495: //年月日時分
0496: regex re1("([0-9]+)\\-([0-9]+)\\-([0-9]+)T([0-9]+)\\:([0-9]+)");
0497: //緯度・経度・深さ
0498: regex re2("([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\-\\+\\/])([0-9]*)");
0499: smatch mt1, mt2;
0500: double lng, lat;
0501: pahooCache* pCC; //pahooCacheオブジェクト
0502:
0503: //地震情報URLを取得
0504: static string urls[MAX_EARTHQUAKE];
0505: bool res = jma_getLastEarthquakeURLs(urls);
0506: if (res == FALSE) {
0507: return FALSE;
0508: }
0509:
0510: static string contents = "";
0511: pCC = new pahooCache(LIFE_CACHE_DATA, getMyPath(APPNAME) + DIR_CACHE_DATA, UserAgent);
0512: for (int i = 0; i < MAX_EARTHQUAKE; i++) {
0513: if (urls[i] == "") {
0514: // cout << "count = " << i << endl;
0515: break;
0516: }
0517: // readWebContents(urls[i], UserAgent, &contents);
0518: if (pCC->load(urls[i], &contents) == FALSE) {
0519: ErrorMessage = _WS(pCC->getError());
0520: return FALSE;
0521: }
0522: //XML読み込み
0523: try {
0524: std::stringstream ss;
0525: ss << contents;
0526: ptree pt;
0527: xml_parser::read_xml(ss, pt);
0528:
0529: //XML解釈
0530: //地震発生日時の取得
0531: if (optional<string>str = pt.get_optional<string>("Report.Body.Earthquake.OriginTime")) {
0532: // cout << str.value() << " ";
0533: if (regex_search(str.value(), mt1, re1)) {
0534: Earthquake[i].year = stoi(mt1[1].str());
0535: Earthquake[i].month = stoi(mt1[2].str());
0536: Earthquake[i].day = stoi(mt1[3].str());
0537: Earthquake[i].hour = stoi(mt1[4].str());
0538: Earthquake[i].minuite = stoi(mt1[5].str());
0539: }
0540: }
0541: //震源地の取得
0542: if (optional<string>str = pt.get_optional<string>("Report.Body.Earthquake.Hypocenter.Area.Name")) {
0543: Earthquake[i].location = _UW(str.value());
0544: // cout << _WS(Earthquake[i].location) << " ";
0545: }
0546: if (optional<string>str = pt.get_optional<string>("Report.Body.Earthquake.Hypocenter.Area.jmx_eb:Coordinate")) {
0547: if (regex_search(str.value(), mt2, re2)) {
0548: pGC->tokyo_wgs84(stod(mt2[2].str()), stod(mt2[1].str()), &lng, &lat);
0549: Earthquake[i].latitude = lat;
0550: Earthquake[i].longitude = lng;
0551: if (mt2[3].str() == "/") {
0552: Earthquake[i].depth = (-1.0);
0553: } else {
0554: Earthquake[i].depth = stod(mt2[4].str());
0555: }
0556: // cout << "lat=" << lat << " lng=" << lng << " ";
0557: }
0558: }
0559: //マグニチュードの取得
0560: if (optional<string>str = pt.get_optional<string>("Report.Body.Earthquake.jmx_eb:Magnitude")) {
0561: Earthquake[i].magnitude = stod(str.value());
0562: // cout << "M=" << Earthquake[i].magnitude << " ";
0563: }
0564: //最大震度の取得
0565: if (optional<string>str = pt.get_optional<string>("Report.Body.Intensity.Observation.MaxInt")) {
0566: Earthquake[i].maxintensity = stoi(str.value());
0567: // cout << "max=" << Earthquake[i].maxintensity << endl;
0568: }
0569: //読み込みエラー
0570: } catch(xml_parser_error& e) {
0571: ErrorMessage = "気象庁防災情報XMLにアクセスできません";
0572: return FALSE;
0573: }
0574: contents.clear();
0575: }
0576: delete pCC;
0577:
0578: return TRUE;
0579: }
XMLファイルの構造については、「PHPで直近の地震情報を表示する」をご覧いただきたい。

今回も、ワイド文字列に対する正規表現を使うことにした。ソースはSJISで書いているので、ユーザーマクロ関数 _SW を使ってワイド文字列に変換し、これを使って正規表現によるパターンマッチングを行う。
解説:マップを生成する
0801: /**
0802: * 地図描画スクリプトを生成する
0803: * @param string id マップID
0804: * @param double longitude 中心座標:経度(世界測地系)
0805: * @param double latitude 中心座標:緯度(世界測地系)
0806: * @param int zoom 拡大率
0807: * @param string type マップタイプ
0808: * GSISTD:地理院地図(標準):省略時
0809: * GSIPALE:地理院地図(淡色地図)
0810: * GSIBLANK:地理院地図(白地図)
0811: * GSIPHOTO:地理院地図(写真)
0812: * OSM:OpenStreetMap
0813: * GMRD:Googleマップ(ROADMAP);APIキー有効時
0814: * @param ppoints_t* items 地点情報配列(省略可能)
0815: * @param size_t size 地点情報配列の数(省略可能)
0816: * @param string call1 イベント発生時にコールする関数(省略可)
0817: * @param string call2 追加スクリプト(省略可)
0818: * @param int max_width 情報ウィンドウの最大幅(省略時:200)
0819: * @param int ofst_x 情報ウィンドウのオフセット位置(X)(省略時:0)
0820: * @param int ofst_y 情報ウィンドウのオフセット位置(Y)(省略時:0)
0821: * @return string 生成したスクリプト
0822: */
0823: string pahooGeocode::makeMapLeaflet(string id, double longitude, double latitude, int zoom, string type, ppoints_t* items, size_t size, string call1, string call2, int max_width, int ofst_x, int ofst_y) {
0824: //地点情報スクリプトの生成
0825: char lat[SIZE_BUFF + 1], lng[SIZE_BUFF + 1];
0826: char buff[SIZE_BUFF + 1];
0827: string icode = "";
0828: if (items != NULL) {
0829: string icon = "";
0830: string info = "";
0831: for (size_t i = 0; i < size; i++) {
0832: if ((items[i].icon) == "" && (i > 25)) break;
0833: //アイコンURLなく 'Z'を超えたら打ち止め
0834: string mark = {(char)(65 + i)};
0835: if (items[i].icon == "") {
0836: icon = "https://www.google.com/mapfiles/marker" + mark + ".png";
0837: } else {
0838: icon = items[i].icon;
0839: }
0840: info = "";
0841: if (items[i].description != L"") {
0842: snprintf(buff, SIZE_BUFF, "', {maxWidth: %d, offset: [%d, %d] });", max_width, ofst_x, ofst_y);
0843: info = "marker_" + mark + ".bindPopup('" + _WS(items[i].description) + buff;
0844: }
0845: snprintf(lat, SIZE_BUFF, "%.5f", items[i].latitude);
0846: snprintf(lng, SIZE_BUFF, "%.5f", items[i].longitude);
0847: icode += (boost::format(R"(
0848: var icon_%1% = new L.icon({
0849: iconUrl: '%2%',
0850: iconAnchor: [10, 10] //暫定
0851: });
0852: var marker_%1% = new L.Marker([%3%, %4%], {icon: icon_%1%}).addTo(map);
0853: %5%
0854: )")
0855: % mark //マーカー識別子
0856: % icon //マーカーURL
0857: % lat //緯度
0858: % lng //経度
0859: % info //情報
0860: ).str();
0861: }
0862: }
0863:
0864: //地図描画スクリプトの生成
0865: string script = (boost::format(R"(
0866: %6%
0867: <link rel="stylesheet" href="https://unpkg.com/leaflet@latest/dist/leaflet.css" />
0868: <script src="https://unpkg.com/leaflet@latest/dist/leaflet.js"></script>
0869: %7%
0870: <script>
0871: window.onload = function() {
0872: var map = L.map('%1%',{zoomControl:false});
0873: map.setView([%2%, %3%], %4%);
0874: L.control.scale({
0875: maxWidth: 250,
0876: position: 'bottomright',
0877: imperial: false
0878: }).addTo(map);
0879: L.control.zoom({position:'topleft'}).addTo(map);
0880:
0881: //地理院地図:標準地図
0882: var GSISTD = new L.tileLayer(
0883: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
0884: {
0885: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
0886: minZoom: 0,
0887: maxZoom: 18,
0888: name: 'GSISTD'
0889: });
0890: //地理院地図:淡色地図
0891: var GSIPALE = new L.tileLayer(
0892: 'https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png',
0893: {
0894: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
0895: minZoom: 2,
0896: maxZoom: 18,
0897: name: 'GSIPALE'
0898: });
0899: //地理院地図:白地図
0900: var GSIBLANK = new L.tileLayer(
0901: 'https://cyberjapandata.gsi.go.jp/xyz/blank/{z}/{x}/{y}.png',
0902: {
0903: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
0904: minZoom: 5,
0905: maxZoom: 14,
0906: name: 'GSIBLANK'
0907: });
0908: //地理院地図:写真
0909: var GSIPHOTO = new L.tileLayer(
0910: 'https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg',
0911: {
0912: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
0913: minZoom: 2,
0914: maxZoom: 18,
0915: name: 'GSIPHOTO'
0916: });
0917: //OpenStreetMap
0918: var OSM = new L.tileLayer(
0919: 'https://tile.openstreetmap.jp/{z}/{x}/{y}.png',
0920: {
0921: attribution: "<a href='https://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors",
0922: minZoom: 0,
0923: maxZoom: 18,
0924: name: 'OSM'
0925: });
0926: %8%
0927:
0928: //baseMapsオブジェクトにタイル設定
0929: var baseMaps = {
0930: "地理院地図" : GSISTD,
0931: "淡色地図" : GSIPALE,
0932: "白地図" : GSIBLANK,
0933: "写真地図" : GSIPHOTO,
0934: "オープンストリートマップ" : OSM
0935: %9%
0936: };
0937:
0938: //layersコントロールにbaseMapsオブジェクトを設定して地図に追加
0939: L.control.layers(baseMaps).addTo(map);
0940: %5%.addTo(map);
0941:
0942: //イベント追加
0943: map.on('moveend', getPointData);
0944: map.on('zoomend', getPointData);
0945: map.on('baselayerchange', getPointData);
0946:
0947: //イベント発生時の地図情報を取得・格納
0948: function getPointData() {
0949: var pos = map.getCenter();
0950: //経度
0951: if (document.getElementById('longitude') != null) {
0952: document.getElementById('longitude').value = pos.lng;
0953: }
0954: //緯度
0955: if (document.getElementById('latitude') != null) {
0956: document.getElementById('latitude').value = pos.lat;
0957: }
0958: //ズーム
0959: if (document.getElementById('zoom') != null) {
0960: document.getElementById('zoom').value = map.getZoom();
0961: }
0962: //タイプ
0963: if (document.getElementById('maptype') != null) {
0964: for (var k in baseMaps) {
0965: if (map.hasLayer(baseMaps[k])) {
0966: document.getElementById('maptype').value = baseMaps[k].options.name;
0967: }
0968: }
0969: }
0970: %11%
0971: }
0972: %10%
0973: %12%
0974: }
0975: </script>
0976: )")
0977: % id //地図ID
0978: % latitude //緯度
0979: % longitude //経度
0980: % zoom //地図拡大率
0981: % type //地図タイプ
0982: % this->GoogleMap1 //Googleマップ描画用スクリプトURL
0983: % this->GoogleMap2 //Leaftet:Googleマップ・アドオンURL
0984: % this->GoogleMap3 //Leaftet:Googleマップ用レイヤ
0985: % this->GoogleMap4 //Leaftet:Googleマップ選択肢
0986: % icode
0987: % call1
0988: % call2
0989: ).str();
0990:
0991: return script;
0992: }
このメソッドは、「地理院地図・OSM描画 -PHPで住所・ランドマークから最寄り駅を求める」で紹介した手法をそのまま移植した。無償のJavaScriptライブラリLeafletを利用している。
後述するように、Googleマップが利用できるときには、必要なスクリプトを変数 GoogleMap1~GoogleMap4 から追加するようにした。
解説:Googleマップを利用する
そこで本プログラムでは、ユーザーが APIキーを取得した場合、それをプログラムから入力・保存できるようにするダイアログを用意した。
0093: /**
0094: * AppDataのパスを取得
0095: * @param char* appname アプリケーション名
0096: * @return TCHAR* パス
0097: */
0098: TCHAR* pahooGeocode::getMyPath(const char* appname) {
0099: static TCHAR myPath[MAX_PATH] = "";
0100:
0101: if (strlen(myPath) == 0) {
0102: if (SHGetSpecialFolderPath(NULL, myPath, CSIDL_APPDATA, 0)) {
0103: TCHAR *ptmp = _tcsrchr(myPath, _T('\\'));
0104: if (ptmp != NULL) {
0105: ptmp = _tcsinc(ptmp);
0106: *ptmp = _T('\0');
0107: }
0108: strcat(myPath, _T("Roaming"));
0109: CreateDirectory((LPCTSTR)myPath, NULL);
0110: strcat(myPath, _T("\\pahoo.org"));
0111: CreateDirectory((LPCTSTR)myPath, NULL);
0112: strcat(myPath, _T("\\"));
0113: strcat(myPath, _T(appname));
0114: CreateDirectory((LPCTSTR)myPath, NULL);
0115: strcat(myPath, _T("\\"));
0116: } else {
0117: }
0118: }
0119: return myPath;
0120: }
なお、WiX を使って作ったアンインストーラーを実行すると、このフォルダを消去するようにしてある。
0179: /**
0180: * Google Cloud Platform APIキーを書き込む
0181: * @param string key 書き込むAPIキー
0182: * @return bool TRUE:書込成功/FALSE:失敗
0183: */
0184: bool pahooGeocode::writeGoogleApiKey(std::string key) {
0185: bool ret = TRUE;
0186: ofstream ofs;
0187:
0188: ofs.open((string)this->getMyPath(NULL) + FNAME_GOOGLE_API);
0189: ofs << key;
0190: if(ofs.bad()) {
0191: this->errmsg = _SW("Google Cloud Platform APIキーの保存に失敗しました");
0192: ret = FALSE;
0193: }
0194: ofs.close();
0195: this->readGoogleApiKey();
0196:
0197: return ret;
0198: }
0123: /**
0124: * Google Cloud Platform APIキーを読み込む
0125: * @param なし
0126: * @return bool TRUE:読込成功/FALSE:ファイルがない
0127: */
0128: bool pahooGeocode::readGoogleApiKey(void) {
0129: string key;
0130: bool ret = FALSE;
0131:
0132: ifstream ifs((string)this->getMyPath(NULL) + this->FNAME_GOOGLE_API);
0133: //APIキー・ファイルが無ければ初期化
0134: if (!ifs) {
0135: this->GoogleAPIkey = "";
0136: this->GoogleMap1 = "";
0137: this->GoogleMap2 = "";
0138: this->GoogleMap3 = "";
0139: this->GoogleMap4 = "";
0140: return ret;
0141: }
0142:
0143: //APIキー読み込み
0144: ifs >> key;
0145: if(ifs.bad()) {
0146: this->errmsg = _SW("Google Cloud Platform APIキーの読み込みに失敗しました");
0147: ifs.close();
0148: this->GoogleAPIkey = "";
0149: this->GoogleMap1 = "";
0150: this->GoogleMap2 = "";
0151: this->GoogleMap3 = "";
0152: this->GoogleMap4 = "";
0153: return FALSE;
0154: }
0155: ifs.close();
0156:
0157: //APIキーなどの設定
0158: if (key.length() > 0) {
0159: this->GoogleAPIkey = key;
0160: this->GoogleMap1 = "<script src='https://maps.googleapis.com/maps/api/js?key="
0161: + key + "' async defer></script>";
0162: this->GoogleMap2 =
0163: "<script src='https://unpkg.com/leaflet.gridlayer.googlemutant@0.10.2/Leaflet.GoogleMutant.js'></script>";
0164: this->GoogleMap3 =
0165: "var GMRD = L.gridLayer.googleMutant({type:'roadmap', name:'GMRD'});\nvar GMST = L.gridLayer.googleMutant({type:'satellite', name:'GMST'});\nvar GMHB = L.gridLayer.googleMutant({type:'hybrid', name:'GMHB'});";
0166: this->GoogleMap4 = ",'Googleマップ(標準)' : GMRD,\n'Googleマップ(写真)' : GMST,\n'Googleマップ(混合)' : GMHB";
0167: ret = TRUE;
0168: //APIキーが無ければ初期化
0169: } else {
0170: this->GoogleAPIkey = "";
0171: this->GoogleMap1 = "";
0172: this->GoogleMap2 = "";
0173: this->GoogleMap3 = "";
0174: this->GoogleMap4 = "";
0175: }
0176: return ret;
0177: }
有効なキーがあれば、変数 GoogleAPIkey に代入する。また、前述の makeMapLeaflet メソッドにGoogleマップを追加するためのスクリプトを変数 GoogleMap1~GoogleMap4 に代入する。
解説:マップ表示用HTML生成
0717: /**
0718: * 地図表示用HTML生成
0719: * @param string 表示するインフォメーション
0720: * @return string 生成したHTML文
0721: */
0722: string makeMapHTML(wstring info) {
0723: // if (pGC->GoogleAPIkey.length() == 0) {
0724: // Maptype = DEF_MAPTYPE;
0725: // }
0726:
0727: int cnt = info2points();
0728: string script = pGC->makeMapLeaflet(MAP_ID, Earthquake[0].longitude, Earthquake[0].latitude, Zoom, Maptype, pGC->Ppoints, cnt, "", "", INFO_WIDTH, INFO_OFFSET_X, INFO_OFFSET_Y);
0729:
0730: string html = (boost::format(R"(<!DOCTYPE html>
0731: <html lang="ja">
0732: <head>
0733: <meta charset="SJIS">
0734: <title>%9%</title>
0735: <meta name="author" content="studio pahoo" />
0736: <meta name="copyright" content="studio pahoo" />
0737: <meta name="ROBOTS" content="NOINDEX,NOFOLLOW" />
0738: <meta http-equiv="pragma" content="no-cache">
0739: <meta http-equiv="cache-control" content="no-cache">
0740: <meta http-equiv="X-UA-Compatible" content="IE=edge">
0741: %8%
0742: </head>
0743: <body>
0744: <div id="%1%" style="width:%2%px; height:%3%px;"></div>
0745: <form>
0746: <input id="latitude" type="hidden" value="%4%" />
0747: <input id="longitude" type="hidden" value="%5%" />
0748: <input id="zoom" type="hidden" value="%6%" />
0749: <input id="maptype" type="hidden" value="%7%" />
0750: </form>
0751: </body>
0752: </html>
0753: )")
0754: % MAP_ID //地図ID
0755: % MAP_WIDTH //地図の幅
0756: % MAP_HEIGHT //地図の高さ
0757: % Earthquake[0].latitude //緯度
0758: % Earthquake[0].longitude //経度
0759: % Zoom //地図拡大率
0760: % Maptype //地図タイプ
0761: % script //地図描画スクリプト
0762: % APPNAMEJP //アプリケーション名
0763: ).str();
0764:
0765: return html;
0766: }
解説:ブラウザ・コントロール表示
0796: /**
0797: * ブラウザ・コントロールを表示
0798: * @param wstring info 情報ウィンドウに表示するテキスト
0799: * @param char* tmpname 読み込むHTMLファイル名
0800: * @return なし
0801: */
0802: void viewBrowser(wstring info, const char* tmpname) {
0803: string html;
0804: string fname;
0805: ofstream ofs;
0806:
0807: //表示用HTMLファイル作成
0808: if ((ErrorMessage == "") && (! pGC->isError())) {
0809: html = makeMapHTML(info);
0810: } else {
0811: html = makeErrorHTML();
0812: }
0813: ofs.open(tmpname);
0814: ofs << html;
0815: ofs.close();
0816:
0817: fname = (string)tmpname;
0818: std::replace(fname.begin(), fname.end(), '\\', '/');
0819: wBrowser.loadPage((char*)fname.c_str());
0820: wBrowser.fitToParent();
0821: }
テンポラリディレクトリにHTML文を保存し、これを表示するようにしている。
1118: //ブラウザ・コントロール作成
1119: hHTML = CreateWindowEx(0, WC_STATIC, "WebBrowser", WS_CHILD | WS_VISIBLE | ES_LEFT, 10, 10, MAP_WIDTH + 20, MAP_HEIGHT + 20, hDlg, NULL, hInst, NULL);
1120: wBrowser.create((char*)"", 0, 0, MAP_WIDTH + 20, MAP_HEIGHT + 20, hHTML);
WebBrowserコントロール・クラス WebBrowse は、Digital Point の "webbrowser.h" を参考にアレンジし、"webbrowser.hpp" とした。
解説:地図描画パラメータ
0139: /**
0140: * パラメータの初期化
0141: * @param なし
0142: * @return なし
0143: */
0144: void initParameter(void) {
0145: Longitude = DEF_LONGITUDE;
0146: Latitude = DEF_LATITUDE;
0147: Zoom = DEF_ZOOM;
0148: Maptype = DEF_MAPTYPE;
0149: hParent_X = 0;
0150: hParent_Y = 0;
0151: }
0216: /**
0217: * パラメータの保存
0218: * @param なし
0219: * @return なし
0220: */
0221: void saveParameter(void) {
0222: #ifndef CMDAPP
0223: //アプリケーション・ウィンドウの位置取得
0224: WINDOWINFO windowInfo;
0225: windowInfo.cbSize = sizeof(WINDOWINFO);
0226: GetWindowInfo(hParent, &windowInfo);
0227: hParent_X = (unsigned)windowInfo.rcWindow.left;
0228: hParent_Y = (unsigned)windowInfo.rcWindow.top;
0229: if (hParent_X >= (unsigned)windowInfo.rcWindow.right) {
0230: hParent_X = 0;
0231: }
0232: if (hParent_Y >= (unsigned)windowInfo.rcWindow.bottom) {
0233: hParent_Y = 0;
0234: }
0235: #endif
0236:
0237: //ブラウザ・コントロールからパラメータを取り出す
0238: wstring val;
0239: if (wBrowser.getInputById(L"longitude", &val)) {
0240: Longitude = stod(val);
0241: }
0242: if (wBrowser.getInputById(L"latitude", &val)) {
0243: Latitude = stod(val);
0244: }
0245: if (wBrowser.getInputById(L"zoom", &val)) {
0246: Zoom = stoi(val);
0247: }
0248: if (wBrowser.getInputById(L"maptype", &val)) {
0249: Maptype = _WS(val);
0250: }
0251:
0252: char lng[SIZE_BUFF + 1], lat[SIZE_BUFF + 1];
0253: snprintf(lng, SIZE_BUFF, "%.5f", Longitude);
0254: snprintf(lat, SIZE_BUFF, "%.5f", Latitude);
0255:
0256: //XMLファイルへ書き込む
0257: ptree pt;
0258: ptree& child1 = pt.add("parameter.param", lat);
0259: child1.add("<xmlattr>.type", "latitude");
0260: ptree& child2 = pt.add("parameter.param", lng);
0261: child2.add("<xmlattr>.type", "longitude");
0262: ptree& child3 = pt.add("parameter.param", to_string(Zoom));
0263: child3.add("<xmlattr>.type", "zoom");
0264: ptree& child4 = pt.add("parameter.param", Maptype);
0265: child4.add("<xmlattr>.type", "maptype");
0266: ptree& child5 = pt.add("parameter.param", (string)to_string(hParent_X));
0267: child5.add("<xmlattr>.type", "wx");
0268: ptree& child6 = pt.add("parameter.param", (string)to_string(hParent_Y));
0269: child6.add("<xmlattr>.type", "wy");
0270:
0271: const int indent = 4;
0272: write_xml(getMyPath(APPNAME) + APPNAME + ".xml", pt, std::locale(),
0273: xml_writer_make_settings<std::string>(' ', indent));
0274: }
ブラウザ・コントロールから、これらの値を取り出し、所定のXMLファイルへ保存するのが saveParameter である。
保存場所は、"[C:\Users\(ユーザー名)\AppData\Roaming\pahoo.org\(アプリケーション名)" である。
なお、INPUT要素を取得する方法は、「INPUT要素の取得 - C++で最寄駅を検索」で紹介したとおりだ。
0153: /**
0154: * パラメータの読み込み
0155: * @param なし
0156: * @return なし
0157: */
0158: void loadParameter(void) {
0159: ptree pt;
0160:
0161: //初期値設定
0162: initParameter();
0163:
0164: //XMLファイル読み込み
0165: try {
0166: xml_parser::read_xml(getMyPath(APPNAME) + APPNAME + ".xml", pt);
0167:
0168: //XML解釈
0169: try {
0170: //形式チェック
0171: if (optional<string>str = pt.get_optional<string>("parameter")) {
0172: } else {
0173: return;
0174: }
0175: //パラメータ読み込み
0176: for (auto it : pt.get_child("parameter")) {
0177: string type= it.second.get_optional<string>("<xmlattr>.type").value();
0178: if (type == "latitude") {
0179: Latitude = stod(it.second.data());
0180: } else if (type == "longitude") {
0181: Longitude = stod(it.second.data());
0182: } else if (type == "zoom") {
0183: Zoom = stoi(it.second.data());
0184: } else if (type == "maptype") {
0185: Maptype = (string)it.second.data();
0186: } else if (type == "wx") {
0187: hParent_X = (unsigned)stoi(it.second.data());
0188: } else if (type == "wy") {
0189: hParent_Y = (unsigned)stoi(it.second.data());
0190: }
0191: }
0192: //解釈失敗したら初期値設定
0193: } catch (xml_parser_error& e) {
0194: initParameter();
0195: return;
0196: }
0197: //読み込み失敗したら初期値設定
0198: } catch (xml_parser_error& e) {
0199: initParameter();
0200: return;
0201: }
0202:
0203: //アプリケーション・ウィンドウの位置(デスクトップ範囲外なら原点移動)
0204: HWND hDesktop = GetDesktopWindow();
0205: WINDOWINFO windowInfo;
0206: windowInfo.cbSize = sizeof(WINDOWINFO);
0207: GetWindowInfo(hDesktop, &windowInfo);
0208: if (hParent_X >= (unsigned)windowInfo.rcWindow.right) {
0209: hParent_X = 0;
0210: }
0211: if (hParent_Y >= (unsigned)windowInfo.rcWindow.bottom) {
0212: hParent_Y = 0;
0213: }
0214: }
参考サイト
- PHPで最近の地震情報を表示する:ぱふぅ家のホームページ
- WiX によるWindowsインストーラー作成:ぱふぅ家のホームページ
- C++ 開発環境の準備:ぱふぅ家のホームページ
- C++ 開発環境の準備 -MSYS2編-
(2022年11月12日)使用ライブラリをバージョンアップ
(2022年9月3日)各種パラメータの保存に対応,最新版ライブラリに更新.
(2022年3月10日)気象庁防災情報XMLのhttps化に対応。新しいプログラムを実行するとき、一度だけ「キャッシュ・クリア」を実行するといいだろう。