
目次
サンプル・プログラム
earthquakewin.msi | インストーラ |
bin/earthquakewin.exe | 実行プログラム本体 |
bin/cwebpage.dll bin/libcrypto-1_1.dll bin/libcurl.dll bin/libssl-1_1.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ブラウザ・クラス(ヘッダ) |
使用ライブラリ
また、地図表示に 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" とする。
解説:定数など
0039: #define MAKER "pahoo.org" //作成者
0040: #define APPNAME "earthquakewin" //アプリケーション名
0041: #define APPNAMEJP "直近の地震情報" //アプリケーション名日本語)
0042: #define APPVERSION "1.1" //バージョン
0043: #define APPYEAR "2020" //作成年
0044: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-12-01.shtm" // 参考サイト
0045:
0046: //char*バッファサイズ
0047: #define SIZE_BUFF 512
0048:
0049: //現在のインターフェイス
0050: HINSTANCE hInst;
0051:
0052: //親ウィンドウ
0053: HWND hParent;
0054:
0055: //エラー・メッセージ格納用
0056: string ErrorMessage;
0057:
0058: //ヘルプ・ファイル
0059: #define HELPFILE ".\\etc\\help.chm"
0060:
0061: //デフォルト保存ファイル名
0062: #define SAVEFILE "eq_%04d%02d%02d_%02d%02d.txt"
0063:
0064: //ブラウザ・コントロール
0065: WebBrowser wBrowser;
0066:
0067: //UserAgent
0068: string UserAgent;
0069:
0070: //pahooGeocodeオブジェクト
0071: pahooGeocode* pGC;
0072:
0073: //マップID
0074: #define MAP_ID "map_id"
0075: //地図の大きさ
0076: #define MAP_WIDTH 500 //地図の幅(ピクセル)
0077: #define MAP_HEIGHT 300 //地図の高さ(ピクセル)
0078: //経度・緯度(初期値)
0079: #define DEF_LONGITUDE 139.766667
0080: double Longitude = DEF_LONGITUDE;
0081: #define DEF_LATITUDE 35.681111
0082: double Latitude = DEF_LATITUDE;
0083: //地図拡大率(初期値)
0084: #define DEF_ZOOM 6
0085: int Zoom = DEF_ZOOM;
0086: //地図の種類(初期値)
0087: #define DEF_MAPTYPE "GSISTD"
0088: string Maptype = DEF_MAPTYPE;
解説:データ構造
0090: //地震情報
0091: wstring infoEarthquake;
0092:
0093: //地震情報URL(気象庁):変更不可
0094: #define URL_EARTHQUAKE "https://www.jma.go.jp/jp/quake/"
0095:
0096: //地震情報を格納する構造体
0097: struct _Earthquake {
0098: int year = 0; //西暦年
0099: int month = 0; //月
0100: int day = 0; //日
0101: int hour = 0; //時
0102: int minuite = 0; //分
0103: double latitude = 0.0; //緯度
0104: double longitude = 0.0; //経度
0105: double depth = 0.0; //深さ
0106: double magnitude = 0.0; //マグニチュード
0107: } Earthquake;
解説:ワイド文字列中の改行を他の文字列に置換
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: }
解説:日本語テキスト変換
0249: /**
0250: * 日本語テキスト変換(Win32 API利用)
0251: * @param wstring sour 変換元テキスト
0252: * @param DWORD method 変換方式
0253: * @return wstring 変換後テキスト
0254: */
0255: wstring wconvString(wstring wsour, DWORD method) {
0256: //濁点・半濁点は全角→半角で2文字に増えるので変換後領域は余裕を持たせる
0257: wchar_t wdest[wsour.length() * 2 + 1];
0258: for (size_t i = 0; i < wsour.length() * 2 + 1; i++) {
0259: wdest[i] = 0L;
0260: }
0261: //変換実行
0262: LCMapStringW(LOCALE_SYSTEM_DEFAULT, method, (LPWSTR) wsour.c_str(),
0263: wsour.length(), (LPWSTR) wdest, wsour.length() * 2);
0264:
0265: return (wstring) wdest;
0266: }
解説:数字(wstring)を数値(double)に変換
0268: /**
0269: * 数字(wstring)を数値(double)に変換
0270: * @param wstring sour 変換元テキスト
0271: * @return double 変換後数値
0272: */
0273: double w2f(wstring sour) {
0274: return atof(_WS(wconvString(sour, LCMAP_HALFWIDTH)).c_str());
0275: }
解説:Webコンテンツ取得
0446: /**
0447: * cURLによるWebコンテンツ取得
0448: * @param string url アクセスURL
0449: * @param string ua UserAgent
0450: * @param string *contents コンテンツを格納
0451: * @return bool TRUE:読み込み成功/FALSE:失敗
0452: */
0453: bool readWebContents(const string url, const string ua, string *contents) {
0454: //cURLによる読み込み
0455: bool ret = FALSE;
0456: CURL *curl;
0457: CURLcode res = (CURLcode)0;
0458: curl = curl_easy_init();
0459: if (curl) {
0460: struct curl_slist *hs=NULL;
0461: hs = curl_slist_append(hs, "Content-Type: application/x-www-form-urlencoded");
0462: curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
0463: curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
0464: curl_easy_setopt(curl, CURLOPT_USERAGENT, ua.c_str());
0465: curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs);
0466: curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callBackFuncCURL);
0467: curl_easy_setopt(curl, CURLOPT_WRITEDATA, contents);
0468: res = curl_easy_perform(curl);
0469: curl_easy_cleanup(curl);
0470: }
0471: //エラー・チェック
0472: if (res == CURLE_OK) ret = TRUE;
0473:
0474: return ret;
0475: }

なお、アクセス時に HTTP ユーザーエージェントを指定できるようにしている。
HTTP ユーザーエージェントは、メインプログラムの花器で発生させている。
ヘルプの末尾にも記載したように、Web サイトに対して悪意のあるアクセスでないことを示す意味で、"Mozilla/5.0 ([アプリケーション名/バージョン/pahoo.org], Windows NT [バージョン])" を送信することを WeAPI管理者に申し送りしている。
0666: //UserAgent生成
0667: static OSVERSIONINFOEX os;
0668: GetVersion2(&os);
0669: UserAgent = (string)"Mozilla/5.0 (" + APPNAME + "/"
0670: + APPVERSION + "/" + MAKER
0671: + ", Windows NT " + to_string(os.dwMajorVersion) + "."
0672: + to_string(os.dwMinorVersion) + ")";
解説:地震情報取得
0327: /**
0328: * 地震情報取得(気象庁から)
0329: * @param なし
0330: * @return int (-1) : 取得失敗(ネットワーク接続異常)
0331: * 0 : 情報無し
0332: * (+1) : 取得成功
0333: */
0334: int getEarthquake(void) {
0335: string url = URL_EARTHQUAKE;
0336: static string contents = "";
0337:
0338: //気象庁地震情報読み込み
0339: bool res = readWebContents(url, UserAgent, &contents);
0340: if (res == FALSE) {
0341: ErrorMessage = "気象庁地震情報サイトへの接続エラーが発生しました";
0342: return (-1);
0343: }
0344:
0345: //コンテンツの解釈
0346: setlocale(LC_ALL, "Japanese");
0347: int ofst = 0;
0348: stringstream ss;
0349: string ss0;
0350: wstring ws;
0351: wsmatch mt1, mt2;
0352: wregex re1(_SW("震源・震度に関する情報(<br>)?(.+[0-9\\.0-9.]+と推定されます。)"));
0353: wregex re2(_SW("(昭和|平成|令和)\\s*([0-90-9]+)年\\s*([0-90-9]+)月\\s*([0-90-9]+)日"));
0354: wregex re3(_SW("([0-90-9]+)日([0-90-9]+)時([0-90-9]+)分ころ、地震がありました"));
0355: wregex re4(_SW("北緯([0-9\\.0-9.]+)度、東経([0-9\\.0-9.]+)度"));
0356: wregex re5(_SW("震源の深さは約([0-9\\.0-9.]+)km"));
0357: wregex re6(_SW("地震の規模(マグニチュード)は([0-9\\.0-9.]+)と"));
0358:
0359: ss << contents;
0360: while (ss && getline(ss, ss0)) {
0361: //1行をwstring変換
0362: ws = _UW(ss0);
0363: if (regex_search(ws, mt1, re1)) {
0364: wstring sour = mt1[2].str();
0365: sour = wstrip_tags(sour);
0366: if (sour == L"") {
0367: return 0;
0368: }
0369: //年月
0370: if (regex_search(sour, mt2, re2)) {
0371: if (mt2[1].str() == _SW("昭和")) {
0372: ofst = 1925;
0373: } else if (mt2[1].str() == _SW("平成")) {
0374: ofst = 1988;
0375: } else if (mt2[1].str() == _SW("令和")) {
0376: ofst = 2018;
0377: }
0378: Earthquake.year = (int)w2f(mt2[2].str()) + ofst;
0379: Earthquake.month = (int)w2f(mt2[3].str());
0380: } else {
0381: return 0;
0382: }
0383: //日時分
0384: if (regex_search(sour, mt2, re3)) {
0385: Earthquake.day = (int)w2f(mt2[1].str());
0386: Earthquake.hour = (int)w2f(mt2[2].str());
0387: Earthquake.minuite = (int)w2f(mt2[3].str());
0388: } else {
0389: return 0;
0390: }
0391: //緯度・経度
0392: if (regex_search(sour, mt2, re4)) {
0393: Earthquake.latitude = w2f(mt2[1].str());
0394: Earthquake.longitude = w2f(mt2[2].str());
0395: } else {
0396: return 0;
0397: }
0398: //深さ
0399: if (regex_search(sour, mt2, re5)) {
0400: Earthquake.depth = w2f(mt2[1].str());
0401: } else {
0402: return 0;
0403: }
0404: //マグニチュード
0405: if (regex_search(sour, mt2, re6)) {
0406: Earthquake.magnitude = w2f(mt2[1].str());
0407: } else {
0408: return 0;
0409: }
0410: }
0411: }
0412:
0413: return 1;
0414: }

地震情報が記述されているのは 1行で、これにマッチするのが re1 である。あとは、この行に対してマッチングをかけていく。
パターン名 | 内容 |
---|---|
re1 | 地震情報が記述されている行 |
re2 | 発表年月 |
re3 | 発生日時分 |
re4 | 震源の緯度・経度 |
re5 | 震源の深さ |
re6 | 地震の規模 |
解説:マップを生成する
0775: /**
0776: * 地図描画スクリプトを生成する
0777: * @param string id マップID
0778: * @param double longitude 中心座標:経度(世界測地系)
0779: * @param double latitude 中心座標:緯度(世界測地系)
0780: * @param int zoom 拡大率
0781: * @param string type マップタイプ
0782: * GSISTD:地理院地図(標準):省略時
0783: * GSIPALE:地理院地図(淡色地図)
0784: * GSIBLANK:地理院地図(白地図)
0785: * GSIPHOTO:地理院地図(写真)
0786: * OSM:OpenStreetMap
0787: * GMRD:Googleマップ(ROADMAP);APIキー有効時
0788: * @param ppoints_t* items 地点情報配列(省略可能)
0789: * @param size_t size 地点情報配列の数(省略可能)
0790: * @param string call1 イベント発生時にコールする関数(省略可)
0791: * @param string call2 追加スクリプト(省略可)
0792: * @return string 生成したスクリプト
0793: */
0794: string pahooGeocode::makeMapLeaflet(
0795: string id, double longitude, double latitude, int zoom,
0796: string type, ppoints_t* items, size_t size, string call1, string call2) {
0797:
0798: //地点情報スクリプトの生成
0799: char lat[SIZE_BUFF + 1], lng[SIZE_BUFF + 1];
0800: string icode = "";
0801: if (items != NULL) {
0802: string icon = "";
0803: string info = "";
0804: for (size_t i = 0; i < size; i++) {
0805: if ((items[i].icon) == "" && (i > 25)) break;
0806: //アイコンURLなく 'Z'を超えたら打ち止め
0807: string mark = {(char)(65 + i)};
0808: if (items[i].icon == "") {
0809: icon = "https://www.google.com/mapfiles/marker" + mark + ".png";
0810: } else {
0811: icon = items[i].icon;
0812: }
0813: info = "";
0814: if (items[i].description != L"") {
0815: info = "marker_" + mark + ".bindPopup('" + _WS(items[i].description) +"', {maxWidth: 200});";
0816: }
0817: snprintf(lat, SIZE_BUFF, "%.5f", items[i].latitude);
0818: snprintf(lng, SIZE_BUFF, "%.5f", items[i].longitude);
0819: icode += (boost::format(R"(
0820: var icon_%1% = new L.icon({
0821: iconUrl: '%2%',
0822: iconAnchor: [10, 10] //暫定
0823: });
0824: var marker_%1% = new L.Marker([%3%, %4%], {icon: icon_%1%}).addTo(map);
0825: %5%
0826: )")
0827: % mark //マーカー識別子
0828: % icon //マーカーURL
0829: % lat //緯度
0830: % lng //経度
0831: % info //情報
0832: ).str();
0833: }
0834: }
0835:
0836: //地図描画スクリプトの生成
0837: string script = (boost::format(R"(
0838: %6%
0839: <link rel="stylesheet" href="https://unpkg.com/leaflet@latest/dist/leaflet.css" />
0840: <script src="https://unpkg.com/leaflet@latest/dist/leaflet.js"></script>
0841: %7%
0842: <script>
0843: window.onload = function() {
0844: var map = L.map('%1%',{zoomControl:false});
0845: map.setView([%2%, %3%], %4%);
0846: L.control.scale({
0847: maxWidth: 250,
0848: position: 'bottomright',
0849: imperial: false
0850: }).addTo(map);
0851: L.control.zoom({position:'topleft'}).addTo(map);
0852:
0853: //地理院地図:標準地図
0854: var GSISTD = new L.tileLayer(
0855: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
0856: {
0857: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
0858: minZoom: 0,
0859: maxZoom: 18,
0860: name: 'GSISTD'
0861: });
0862: //地理院地図:淡色地図
0863: var GSIPALE = new L.tileLayer(
0864: 'https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png',
0865: {
0866: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
0867: minZoom: 2,
0868: maxZoom: 18,
0869: name: 'GSIPALE'
0870: });
0871: //地理院地図:白地図
0872: var GSIBLANK = new L.tileLayer(
0873: 'https://cyberjapandata.gsi.go.jp/xyz/blank/{z}/{x}/{y}.png',
0874: {
0875: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
0876: minZoom: 5,
0877: maxZoom: 14,
0878: name: 'GSIBLANK'
0879: });
0880: //地理院地図:写真
0881: var GSIPHOTO = new L.tileLayer(
0882: 'https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg',
0883: {
0884: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
0885: minZoom: 2,
0886: maxZoom: 18,
0887: name: 'GSIPHOTO'
0888: });
0889: //OpenStreetMap
0890: var OSM = new L.tileLayer(
0891: 'https://tile.openstreetmap.jp/{z}/{x}/{y}.png',
0892: {
0893: attribution: "<a href='https://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors",
0894: minZoom: 0,
0895: maxZoom: 18,
0896: name: 'OSM'
0897: });
0898: %8%
0899:
0900: //baseMapsオブジェクトにタイル設定
0901: var baseMaps = {
0902: "地理院地図" : GSISTD,
0903: "淡色地図" : GSIPALE,
0904: "白地図" : GSIBLANK,
0905: "写真地図" : GSIPHOTO,
0906: "オープンストリートマップ" : OSM
0907: %9%
0908: };
0909:
0910: //layersコントロールにbaseMapsオブジェクトを設定して地図に追加
0911: L.control.layers(baseMaps).addTo(map);
0912: %5%.addTo(map);
0913:
0914: //イベント追加
0915: map.on('moveend', getPointData);
0916: map.on('zoomend', getPointData);
0917: map.on('baselayerchange', getPointData);
0918:
0919: //イベント発生時の地図情報を取得・格納
0920: function getPointData() {
0921: var pos = map.getCenter();
0922: //経度
0923: if (document.getElementById('longitude') != null) {
0924: document.getElementById('longitude').value = pos.lng;
0925: }
0926: //緯度
0927: if (document.getElementById('latitude') != null) {
0928: document.getElementById('latitude').value = pos.lat;
0929: }
0930: //ズーム
0931: if (document.getElementById('zoom') != null) {
0932: document.getElementById('zoom').value = map.getZoom();
0933: }
0934: //タイプ
0935: if (document.getElementById('maptype') != null) {
0936: for (var k in baseMaps) {
0937: if (map.hasLayer(baseMaps[k])) {
0938: document.getElementById('maptype').value = baseMaps[k].options.name;
0939: }
0940: }
0941: }
0942: %11%
0943: }
0944: %10%
0945: }
0946: </script>
0947: )")
0948: % id //地図ID
0949: % latitude //緯度
0950: % longitude //経度
0951: % zoom //地図拡大率
0952: % type //地図タイプ
0953: % this->GoogleMap1 //Googleマップ描画用スクリプトURL
0954: % this->GoogleMap2 //Leaftet:Googleマップ・アドオンURL
0955: % this->GoogleMap3 //Leaftet:Googleマップ用レイヤ
0956: % this->GoogleMap4 //Leaftet:Googleマップ選択肢
0957: % icode
0958: % call1
0959: ).str();
0960:
0961: return script;
0962: }
0963:
0964: /*
このメソッドは、「地理院地図・ 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生成
0429: /**
0430: * 地図表示用HTML生成
0431: * @param string 表示するインフォメーション
0432: * @return string 生成したHTML文
0433: */
0434: string makeMapHTML(wstring info) {
0435: if (pGC->GoogleAPIkey.length() == 0) {
0436: Maptype = DEF_MAPTYPE;
0437: }
0438:
0439: //infoウィンドウの表示テキスト
0440: pGC->Ppoints[0].latitude = Earthquake.latitude;
0441: pGC->Ppoints[0].longitude = Earthquake.longitude;
0442: pGC->Ppoints[0].description = wrepNL(info, _SW("<br />"));
0443: pGC->Ppoints[0].title = pGC->Ppoints[0].address = L"";
0444: pGC->Ppoints[0].icon = "";
0445:
0446: string script = pGC->makeMapLeaflet(MAP_ID, Earthquake.longitude, Earthquake.latitude, Zoom, Maptype, pGC->Ppoints, 1);
0447: string html = (boost::format(R"(<!DOCTYPE html>
0448: <html lang="ja">
0449: <head>
0450: <meta charset="SJIS">
0451: <title>フォトマップ</title>
0452: <meta name="author" content="studio pahoo" />
0453: <meta name="copyright" content="studio pahoo" />
0454: <meta name="ROBOTS" content="NOINDEX,NOFOLLOW" />
0455: <meta http-equiv="pragma" content="no-cache">
0456: <meta http-equiv="cache-control" content="no-cache">
0457: <meta http-equiv="X-UA-Compatible" content="IE=edge">
0458: %8%
0459: </head>
0460: <body>
0461: <div id="%1%" style="width:%2%px; height:%3%px;"></div>
0462: <form>
0463: <input id="latitude" type="hidden" value="%4%" />
0464: <input id="longitude" type="hidden" value="%5%" />
0465: <input id="zoom" type="hidden" value="%6%" />
0466: <input id="maptype" type="hidden" value="%7%" />
0467: </form>
0468: </body>
0469: </html>
0470: )")
0471: % MAP_ID //地図ID
0472: % MAP_WIDTH //地図の幅
0473: % MAP_HEIGHT //地図の高さ
0474: % Earthquake.latitude //緯度
0475: % Earthquake.longitude //経度
0476: % Zoom //地図拡大率
0477: % Maptype //地図タイプ
0478: % script //地図描画スクリプト
0479: ).str();
0480:
0481: return html;
0482: }
解説:ブラウザ・コントロール表示
0511: /**
0512: * ブラウザ・コントロールを表示
0513: * @param wstring info 情報ウィンドウに表示するテキスト
0514: * @param char* tmpname 読み込むHTMLファイル名
0515: * @return なし
0516: */
0517: void viewBrowser(wstring info, const char* tmpname) {
0518: string html;
0519: string fname;
0520: ofstream ofs;
0521:
0522: //表示用HTMLファイル作成
0523: if ((ErrorMessage == "") && (! pGC->isError())) {
0524: html = makeMapHTML(info);
0525: } else {
0526: html = makeErrorHTML();
0527: }
0528: ofs.open(tmpname);
0529: ofs << html;
0530: ofs.close();
0531:
0532: fname = (string)tmpname;
0533: std::replace(fname.begin(), fname.end(), '\\', '/');
0534: wBrowser.loadPage((char*)fname.c_str());
0535: wBrowser.fitToParent();
0536: }
テンポラリディレクトリに HTML 文を保存し、これを表示するようにしている。
0560: //ブラウザ・コントロール作成
0561: hHTML = CreateWindowEx(0, WC_STATIC, "WebBrowser", WS_CHILD | WS_VISIBLE | ES_LEFT, 10, 115, MAP_WIDTH + 40, MAP_HEIGHT + 40, hDlg, NULL, hInst, NULL);
0562: wBrowser.create((char*)"", 0, 0, MAP_WIDTH + 40, MAP_HEIGHT + 40, hHTML);
WebBrowser コントロール・クラス WebBrowse は、Digital Point の "webbrowser.h" を参考にアレンジし、"webbrowser.hpp" とした。
解説:地図描画パラメータ
0110: /**
0111: * パラメータの初期化
0112: * @param なし
0113: * @return なし
0114: */
0115: void initParameter(void) {
0116: Longitude = DEF_LONGITUDE;
0117: Latitude = DEF_LATITUDE;
0118: Zoom = DEF_ZOOM;
0119: Maptype = DEF_MAPTYPE;
0120: }
0169: /**
0170: * パラメータの保存
0171: * @param なし
0172: * @return なし
0173: */
0174: void saveParameter(void) {
0175: //ブラウザ・コントロールからパラメータを取り出す
0176: wstring val;
0177: if (wBrowser.getInputById(L"longitude", &val)) {
0178: Longitude = stod(val);
0179: }
0180: if (wBrowser.getInputById(L"latitude", &val)) {
0181: Latitude = stod(val);
0182: }
0183: if (wBrowser.getInputById(L"zoom", &val)) {
0184: Zoom = stoi(val);
0185: }
0186: if (wBrowser.getInputById(L"maptype", &val)) {
0187: Maptype = _WS(val);
0188: }
0189:
0190: char lng[SIZE_BUFF + 1], lat[SIZE_BUFF + 1];
0191: snprintf(lng, SIZE_BUFF, "%.5f", Longitude);
0192: snprintf(lat, SIZE_BUFF, "%.5f", Latitude);
0193:
0194: //XMLファイルへ書き込む
0195: ptree pt;
0196: ptree& child1 = pt.add("parameter.param", lat);
0197: child1.add("<xmlattr>.type", "latitude");
0198: ptree& child2 = pt.add("parameter.param", lng);
0199: child2.add("<xmlattr>.type", "longitude");
0200: ptree& child3 = pt.add("parameter.param", to_string(Zoom));
0201: child3.add("<xmlattr>.type", "zoom");
0202: ptree& child4 = pt.add("parameter.param", Maptype);
0203: child4.add("<xmlattr>.type", "maptype");
0204:
0205: const int indent = 4;
0206: write_xml(getMyPath(APPNAME) + APPNAME + ".xml", pt, std::locale(),
0207: xml_writer_make_settings<std::string>(' ', indent));
0208: }
ブラウザ・コントロールから、これらの値を取り出し、所定の XML ファイルへ保存するのが saveParameter である。
保存場所は、"[C:\Users\(ユーザー名)\AppData\Roaming\pahoo.org\(アプリケーション名)" である。
なお、INPUT要素を取得する方法は、「INPUT要素の取得 - C++で最寄駅を検索」で紹介したとおりだ。
0122: /**
0123: * パラメータの読み込み
0124: * @param なし
0125: * @return なし
0126: */
0127: void loadParameter(void) {
0128: ptree pt;
0129:
0130: //初期値設定
0131: initParameter();
0132:
0133: //XMLファイル読み込み
0134: try {
0135: xml_parser::read_xml(getMyPath(APPNAME) + APPNAME + ".xml", pt);
0136:
0137: //XML解釈
0138: try {
0139: //形式チェック
0140: if (optional<string>str = pt.get_optional<string>("parameter")) {
0141: } else {
0142: return;
0143: }
0144: //パラメータ読み込み
0145: for (auto it : pt.get_child("parameter")) {
0146: string type= it.second.get_optional<string>("<xmlattr>.type").value();
0147: if (type == "latitude") {
0148: Latitude = stod(it.second.data());
0149: } else if (type == "longitude") {
0150: Longitude = stod(it.second.data());
0151: } else if (type == "zoom") {
0152: Zoom = stoi(it.second.data());
0153: } else if (type == "maptype") {
0154: Maptype = (string)it.second.data();
0155: }
0156: }
0157: //解釈失敗したら初期値設定
0158: } catch (xml_parser_error& e) {
0159: initParameter();
0160: return;
0161: }
0162: //読み込み失敗したら初期値設定
0163: } catch (xml_parser_error& e) {
0164: initParameter();
0165: return;
0166: }
0167: }
参考サイト
- PHP で直近の地震情報を表示する(Windows アプリ版):ぱふぅ家のホームページ
- PHP で直近の地震情報を表示する:ぱふぅ家のホームページ
- WiX による Windows インストーラー作成:ぱふぅ家のホームページ
- C++ 開発環境の準備:ぱふぅ家のホームページ
(2021 年 1 月 2 日)pahooGeocode クラスを使用するなど、ソースを全面改訂。