C++ で直近の地震情報を取得する

(1/1)
>C++で直近の地震情報を取得する
インターネット経由で気象庁防災情報XMLにアクセスし、最近の地震情報(発生日時、震源の位置・深さ、地震の規模)を地図上にマッピングしたり、その情報をファイル保存するアプリケーションを作る。「PHPで最近の地震情報を表示する」で作ったPHPプログラムをC++に移植したものである。
地図としては、地理院地図、オープンストリートマップのほか、Edgeブラウザに対応し64ビットアプリ化したことで、Googleマップが再び利用できるようになった。Googleマップ利用には、ユーザーにおいてGoogle Cloud APIキーを取得してほしい。

(2025年3月16日)ネット接続チェック強化,キャッシュシステム不具合修正,使用ライブラリ更新
(2024年11月23日)使用ライブラリ更新
(2024年8月17日)使用ライブラリ更新
(2024年5月3日)API入力処理を改良
(2024年4月27日)Edgeブラウザ対応,64ビット対応

目次

サンプル・プログラム

圧縮ファイルの内容
earthquakewin.msiインストーラ
bin/earthquakewin.exe実行プログラム本体
bin/WebView2Loader.dll
bin/libcurl-x64.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.cppAPIキーの管理(ソース)
sour/apikey.hppAPIキーの管理(ヘッダ)
sour/WebView2.hWebView2に関わるヘッダ
sour/event.hWebView2用インターフェース(ヘッダ)
sour/event.cppWebView2用インターフェース(ソース)
sour/pahooWebView2.cppWebView2に関わる関数(ソース)
sour/pahooWebView2.hppWebView2に関わる関数(ヘッダ)
sour/pahooCache.cppキャッシュ処理に関わるクラス(ソース)
sour/pahooCache.hppキャッシュ処理に関わるクラス(ヘッダ)
sour/makefileビルド
earthquakewin.cpp 更新履歴
バージョン 更新日 内容
4.2.0 2025/03/15 Leafletアクセス可否チェック追加,使用ライブラリ更新
4.1.2 2024/11/23 使用ライブラリ更新
4.1.1 2024/08/17 使用ライブラリ更新
4.1.0 2024/05/03 enum eActionクラス導入,API入力処理を改良
4.0.0 2024/04/27 Edgeブラウザ対応,64ビット対応
pahooGeocode.cpp 更新履歴
バージョン 更新日 内容
1.9.0 2025/03/16 HTTPステータス・エラーをキャッチアップ
1.8.1 2025/02/23 setError() -- bug-fix
1.8.0 2024/05/03 getMyPath()をapikey.appのgetMyPath()関数に変更
1.7.0 2024/04/27 Edge対応に伴いデバッグ用コードを廃棄
1.6.0 2023/07/02 getPointsGSI()メソッド追加
pahooCache.cpp 更新履歴
バージョン 更新日 内容
1.1.0 2025/03/16 不具合修正
1.0 2021/04/14 初版
mystrings.cpp 更新履歴
バージョン 更新日 内容
1.3.1 2025/03/16 readWebContents() リダイレクト有効に
1.3.0 2025/03/16 readWebContents() 引数httpStatus追加
1.2.0 2024/05/06 getModulePath() 追加
1.12 2021/01/31 readWebContents() 引数post追加
1.11 2020/10/17 htmlspecialchars() 追加
pahooWebView2.cpp 更新履歴
バージョン 更新日 内容
1.0.0 2024/04/27 初版
apikey.cpp 更新履歴
バージョン 更新日 内容
2.0.0 2024/04/29 createSetAPIkey, processSetAPIkey に統合
1.0 2020/09/30 初版

使用ライブラリ

気象庁防災情報XMLにアクセスするために、オープンソースのライブラリ Boost C++ライブラリcURL (カール)  および OpenSSL が必要になる。導入方法等については、「C++ 開発環境の準備」をご覧いただきたい。
また、地図表示にWebブラウザ・コントロールを利用するため "WebView2Loader.dll" を利用する。jchv / webview2-in-mingw からダウンロードできる。

リソースの準備

64ビット版の開発環境を用いる。
Eclipse を起動したら、新規プロジェクト earthquakewin を用意する。
ResEdit を起動したら、resource.rc を用意する。
Eclipse に戻り、ソース・プログラム "earthquakewin.cpp" を追加する。
リンカー・フラグを -s -mwindows -static -lstdc++ -lgcc -lwinpthread -lcurl -lssl -llzma -lz -lws2_32 "C:\(libcurl-x64.dllのフォルダ)\libcurl-x64.dll" "C:\(WebView2Loader.dllのフォルダ)\WebView2Loader.dll" に設定する。
また、コマンド行パターンをアレンジし "${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS} -luuid -loleaut32 -lole32" とする。

MSYS2 コマンドラインからビルドするのであれば、"makefile" を利用してほしい。

解説:定数など

earthquakewin.cpp

  45: // 定数など ==================================================================
  46: #define MAKER       "pahoo.org"             // 作成者
  47: #define APPNAME     "earthquakewin"         // アプリケーション名
  48: #define APPNAMEJP   "最近の地震情報"        // アプリケーション名(日本語)
  49: #define APPVERSION  "4.2.0"                 // バージョン
  50: #define APPYEAR     "2020-25"               // 作成年
  51: #define REFERENCE   "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-12-01.shtm"   // 参考サイト
  52: 
  53: // ListViewItemの最大文字長【変更不可】
  54: #define MAX_LISTVIEWITEM    259
  55: 
  56: // ヘルプ・ファイル
  57: #define HELPFILE    ".\\etc\\help.chm"
  58: 
  59: // デフォルト保存ファイル名
  60: #define SAVEFILE    "eq_%04d%02d%02d_%02d%02d.csv"
  61: 
  62: // キャッシュ・ディレクトリ
  63: #define DIR_CACHE_FEED      "pcache1\\"
  64: #define DIR_CACHE_FEED_L    "pcache2\\"
  65: #define DIR_CACHE_DATA      "pcache3\\"
  66: 
  67: // キャッシュ保持時間(デフォルト;分)(0:キャッシュしない)
  68: #define LIFE_CACHE_FEED     5       // 高頻度フィードに対して
  69: #define LIFE_CACHE_FEED_L   120     // 長期フィードに対して
  70: #define LIFE_CACHE_DATA     720     // 地震情報に対して
  71: 
  72: // 気象庁防災情報XML:高頻度フィード - 地震火山【変更不可】
  73: #define FEED_EVOL   "https://www.data.jma.go.jp/developer/xml/feed/eqvol.xml"
  74: 
  75: // 気象庁防災情報XML:長期フィード - 地震火山【変更不可】
  76: #define FEED_EVOL_L "https://www.data.jma.go.jp/developer/xml/feed/eqvol_l.xml"
  77: 
  78: // マップID
  79: #define MAP_ID          "map_id"
  80: // 地図の大きさ
  81: #define MAP_WIDTH       600     // 地図の幅(ピクセル)
  82: #define MAP_HEIGHT      400     // 地図の高さ(ピクセル)
  83: // 経度・緯度(初期値)
  84: #define DEF_LONGITUDE   139.766667
  85: double Longitude = DEF_LONGITUDE;
  86: #define DEF_LATITUDE    35.681111
  87: double Latitude  = DEF_LATITUDE;
  88: // 地図拡大率(初期値)
  89: #define DEF_ZOOM        6
  90: int Zoom = DEF_ZOOM;
  91: // 地図の種類(初期値)
  92: #define DEF_MAPTYPE     "GSISTD"
  93: string Maptype = DEF_MAPTYPE;
  94: #define INFO_WIDTH  (int)(MAP_WIDTH * 0.75// 情報ウィンドウの最大幅
  95: #define INFO_OFFSET_X   0               // 情報ウィンドウのオフセット位置(X)
  96: #define INFO_OFFSET_Y   -10             // 情報ウィンドウのオフセット位置(Y)
  97: 
  98: // 地震情報の最大格納数
  99: #define MAX_EARTHQUAKE  100

とくに注意記載が無い限り、定数は自由に変更できる。

解説:データ構造

earthquakewin.cpp

 144: // 地震情報を格納する構造体
 145: struct _Earthquake {
 146:     int id              = 0;            // マッピングID
 147:     int year            = 0;            // 西暦年
 148:     int month           = 0;            // 月
 149:     int day             = 0;            // 日
 150:     int hour            = 0;            // 時
 151:     int minuite         = 0;            // 分
 152:     wstring location    = L"";          // 震源地
 153:     double latitude     = 0.0;          // 緯度
 154:     double longitude    = 0.0;          // 経度
 155:     double depth        = 0.0;          // 深さ
 156:     double magnitude    = -1.0;         // マグニチュード
 157:     int maxintensity    = -1;           // 最大震度
 158: Earthquake[MAX_EARTHQUAKE];

取得した地震情報は構造体 Earthquake に格納する。

解説:キャッシュ・システム

最近の地震情報を取得するには、気象庁防災情報XMLから
  1. Atomフィード:高頻度フィード:地震火山
  2. Atomフィード:長期フィード:地震火山
  3. VXSE53
の3つのXMLファイルを読み込む必要がある。毎回、ロードすることは気象庁サイトへ負荷を掛けることになる。そこで、PHPプログラムの場合と同様、一定時間、ローカルドライブにXMLファイルを保持しておくキャッシュ・システムを導入した。
このキャッシュ・システムの仕組みについては、「解説:pahooCacheクラスとデータ構造 - C++ で週間天気予報を表示する」をご覧いただきたい。

earthquakewin.cpp

  62: // キャッシュ・ディレクトリ
  63: #define DIR_CACHE_FEED      "pcache1\\"
  64: #define DIR_CACHE_FEED_L    "pcache2\\"
  65: #define DIR_CACHE_DATA      "pcache3\\"
  66: 
  67: // キャッシュ保持時間(デフォルト;分)(0:キャッシュしない)
  68: #define LIFE_CACHE_FEED     5       // 高頻度フィードに対して
  69: #define LIFE_CACHE_FEED_L   120     // 長期フィードに対して
  70: #define LIFE_CACHE_DATA     720     // 地震情報に対して

キャッシュ保持時間、キャッシュ・ディレクトリともに、ローカル環境に応じて変更してほしい。天気予報系プログラムと別のキャッシュ・ディレクトリにした方が、お互いのキャッシュ保持時間の干渉を受けなくなる。
配布ファイルは、新しい地震情報が入ってくることを考え、フィードの方を短く、地震情報の方は長くキャッシュ保持時間を設定してある。

解説:ワイド文字列中の改行を他の文字列に置換

mystrings.cpp

 336: /**
 337:  * ワイド文字列中の改行を他の文字列に置換する
 338:  * @param  wstring str 置換対象の文字列
 339:  * @param  wstring rep 置換文字列
 340:  * @return wstring 置換後の文字列
 341:  */
 342: wstring wrepNL(wstring str, wstring rep) {
 343:     wstring strRet;
 344:     wstring::iterator ite = str.begin();
 345:     wstring::iterator iteEnd = str.end();
 346: 
 347:     if (0 < str.size()) {
 348:         wchar_t bNextChar = *ite++;
 349:         while (1) {
 350:             if (L'\r' == bNextChar) {
 351:                 // 改行確定
 352:                 strRet +rep;
 353:                 // EOF判定
 354:                 if (ite == iteEnd) {
 355:                     break;
 356:                 }
 357:                 // 1文字取得
 358:                 bNextChar = *ite++;
 359:                 if (L'\n' == bNextChar) {
 360:                     // EOF判定
 361:                     if (ite == iteEnd) {
 362:                         break;
 363:                     }
 364:                     // 1文字取得
 365:                     bNextChar = *ite++;
 366:                 }
 367:             } else if (L'\n' == bNextChar) {
 368:                 // 改行確定
 369:                 strRet +rep;
 370:                 // EOF判定
 371:                 if (ite == iteEnd) {
 372:                     break;
 373:                 }
 374:                 // 1文字取得
 375:                 bNextChar = *ite++;
 376:                 if (L'\r' == bNextChar) {
 377:                     // EOF判定
 378:                     if (ite == iteEnd) {
 379:                         break;
 380:                     }
 381:                     // 1文字取得
 382:                     bNextChar = *ite++;
 383:                 }
 384:             } else {
 385:                 // 改行以外
 386:                 strRet +bNextChar;
 387:                 // EOF判定
 388:                 if (ite == iteEnd) {
 389:                     break;
 390:                 }
 391:                 // 1文字取得
 392:                 bNextChar = *ite++;
 393:             }
 394:         };
 395:     }
 396:     return strRet;
 397: }

よく使う文字列処理関数は "mystrings.cpp" に分離した。
PHPの組み込み関数  nl2br  に相当する機能をワイド文字列用に拡張したのが wrepNL である。iterator を使ってワイド文字列を総なめにしている。

解説:地震情報取得

earthquakewin.cpp

 499: /**
 500:  * 地震情報取得(気象庁防災情報XMLから)
 501:  * @param   なし
 502:  * @return  bool TRUE:取得成功/FALSE:失敗
 503: */
 504: bool getEarthquake(void) {
 505:     // 年月日時分
 506:     regex re1("([0-9]+)\\-([0-9]+)\\-([0-9]+)T([0-9]+)\\:([0-9]+)");
 507:     // 緯度・経度・深さ
 508:     regex re2("([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\-\\+\\/])([0-9]*)");
 509:     smatch mt1, mt2;
 510:     double lng, lat;
 511:     pahooCache* pCC;    // pahooCacheオブジェクト
 512: 
 513:     // 地震情報URLを取得
 514:     static string urls[MAX_EARTHQUAKE];
 515:     bool res = jma_getLastEarthquakeURLs(urls);
 516:     if (res == FALSE) {
 517:         return FALSE;
 518:     }
 519: 
 520:     static string contents = "";
 521:     pCC = new pahooCache(LIFE_CACHE_DATA, getMyPath(APPNAME+ DIR_CACHE_DATA, UserAgent);
 522:     for (int i = 0i < MAX_EARTHQUAKEi++) {
 523:         if (urls[i] == "") {
 524: //          cout << "count = " << i << endl;
 525:             break;
 526:         }
 527:         int httpStatus = 0;
 528:         readWebContents(urls[i], UserAgent, &contents, &httpStatus);
 529:         if (pCC->load(urls[i], &contents) == FALSE) {
 530:             ErrorMessage = _WS(pCC->getError());
 531:             return FALSE;
 532:         }
 533:         // XML読み込み
 534:         try {
 535:             std::stringstream ss;
 536:             ss << contents;
 537:             ptree pt;
 538:             xml_parser::read_xml(ss, pt);
 539: 
 540:             // XML解釈
 541:             // 地震発生日時の取得
 542:             if (optional<string>str = pt.get_optional<string>("Report.Body.Earthquake.OriginTime")) {
 543: //              cout << str.value() << " ";
 544:                 if (regex_search(str.value(), mt1, re1)) {
 545:                     Earthquake[i].year    = stoi(mt1[1].str());
 546:                     Earthquake[i].month   = stoi(mt1[2].str());
 547:                     Earthquake[i].day     = stoi(mt1[3].str());
 548:                     Earthquake[i].hour    = stoi(mt1[4].str());
 549:                     Earthquake[i].minuite = stoi(mt1[5].str());
 550:                 }
 551:             }
 552:             // 震源地の取得
 553:             if (optional<string>str = pt.get_optional<string>("Report.Body.Earthquake.Hypocenter.Area.Name")) {
 554:                 Earthquake[i].location = _UW(str.value());
 555: //              cout << _WS(Earthquake[i].location) << "  ";
 556:             }
 557:             if (optional<string>str = pt.get_optional<string>("Report.Body.Earthquake.Hypocenter.Area.jmx_eb:Coordinate")) {
 558:                 if (regex_search(str.value(), mt2, re2)) {
 559:                     pGC->tokyo_wgs84(stod(mt2[2].str()), stod(mt2[1].str()), &lng, &lat);
 560:                     Earthquake[i].latitude  = lat;
 561:                     Earthquake[i].longitude = lng;
 562:                     if (mt2[3].str() == "/") {
 563:                         Earthquake[i].depth = (-1.0);
 564:                     } else {
 565:                         Earthquake[i].depth = stod(mt2[4].str());
 566:                     }
 567: //                  cout << "lat=" << lat << " lng=" << lng << " ";
 568:                 }
 569:             }
 570:             // マグニチュードの取得
 571:             if (optional<string>str = pt.get_optional<string>("Report.Body.Earthquake.jmx_eb:Magnitude")) {
 572:                 Earthquake[i].magnitude = stod(str.value());
 573: //              cout << "M=" << Earthquake[i].magnitude << " ";
 574:             }
 575:             // 最大震度の取得
 576:             if (optional<string>str = pt.get_optional<string>("Report.Body.Intensity.Observation.MaxInt")) {
 577:                 Earthquake[i].maxintensity = stoi(str.value());
 578: //              cout << "max=" << Earthquake[i].maxintensity << endl;
 579:             }
 580:         // 読み込みエラー
 581:         } catch(xml_parser_error& e) {
 582:             ErrorMessage = "気象庁防災情報XMLにアクセスできません";
 583:             return FALSE;
 584:         }
 585:         contents.clear();
 586:     }
 587:     delete pCC;
 588: 
 589:     return TRUE;
 590: }

気象庁防災情報XMLからコンテンツを取り込むには、readWebContents で読み込んだXMLファイルを解釈していく。
XMLファイルの構造については、「PHPで直近の地震情報を表示する」をご覧いただきたい。

今回も、ワイド文字列に対する正規表現を使うことにした。ソースはSJISで書いているので、ユーザーマクロ関数 _SW を使ってワイド文字列に変換し、これを使って正規表現によるパターンマッチングを行う。

解説:マップを生成する

pahooGeocode.cpp

 800: /**
 801:  * 地図描画スクリプトを生成する
 802:  * @param   string id        マップID
 803:  * @param   double longitude 中心座標:経度(世界測地系)
 804:  * @param   double latitude  中心座標:緯度(世界測地系)
 805:  * @param   int    zoom      拡大率
 806:  * @param   string type      マップタイプ
 807:  *                              GSISTD:地理院地図(標準):省略時
 808:  *                              GSIPALE:地理院地図(淡色地図)
 809:  *                              GSIBLANK:地理院地図(白地図)
 810:  *                              GSIPHOTO:地理院地図(写真)
 811:  *                              OSM:OpenStreetMap
 812:  *                              GMRD:Googleマップ(ROADMAP);APIキー有効時
 813:  * @param   ppoints_t* items  地点情報配列(省略可能)
 814:  * @param   size_t size       地点情報配列の数(省略可能)
 815:  * @param   string call1      イベント発生時にコールする関数(省略可)
 816:  * @param   string call2      追加スクリプト(省略可)
 817:  * @param   int    max_width  情報ウィンドウの最大幅(省略時:200)
 818:  * @param   int    ofst_x     情報ウィンドウのオフセット位置(X)(省略時:0)
 819:  * @param   int    ofst_y     情報ウィンドウのオフセット位置(Y)(省略時:0)
 820:  * @return  string 生成したスクリプト
 821:  */
 822: string pahooGeocode::makeMapLeaflet(
 823:     string id, double longitude, double latitude, int zoom,
 824:     string type, ppoints_t* items, size_t size, string call1, string call2,
 825:     int max_width, int ofst_x, int ofst_y) {
 826: 
 827:     // 地点情報スクリプトの生成
 828:     char lat[SIZE_BUFF + 1], lng[SIZE_BUFF + 1];
 829:     char buff[SIZE_BUFF + 1];
 830:     string icode = "";
 831:     if (items !NULL) {
 832:         string icon = "";
 833:         string info = "";
 834:         for (size_t i = 0i < sizei++) {
 835:             if ((items[i].icon) == "" && (i > 25))  break;
 836:                                     // アイコンURLなく 'Z'を超えたら打ち止め
 837:             string mark = {(char)(65 + i)};
 838:             if (items[i].icon == "") {
 839:                 icon = "https://www.google.com/mapfiles/marker" + mark + ".png";
 840:             } else {
 841:                 icon = items[i].icon;
 842:             }
 843:             info = "";
 844:             if (items[i].description !L"") {
 845:                 snprintf(buff, SIZE_BUFF, "', {maxWidth: %d, offset: [%d, %d] });", max_width, ofst_x, ofst_y);
 846:                 info = "marker_" + mark + ".bindPopup('" + _WS(items[i].description+ buff;
 847:             }
 848:             snprintf(lat, SIZE_BUFF, "%.5f", items[i].latitude);
 849:             snprintf(lng, SIZE_BUFF, "%.5f", items[i].longitude);
 850:             icode += (boost::format(R"(
 851:                 var icon_%1% =  new L.icon({
 852:                     iconUrl: '%2%',
 853:                     iconAnchor: [10, 10]    // 暫定
 854:                 });
 855:                 var marker_%1% = new L.Marker([%3%, %4%], {icon: icon_%1%}).addTo(map);
 856:                 %5%
 857: )")
 858:  %mark           // マーカー識別子
 859: % icon          // マーカーURL
 860: % lat           // 緯度
 861: % lng           // 経度
 862: % info          // 情報
 863: ).str();
 864:         }
 865:     }
 866: 
 867:     // 地図描画スクリプトの生成
 868:     string script = (boost::format(R"(
 869: %6%
 870: <link rel="stylesheet" href="https://unpkg.com/leaflet@latest/dist/leaflet.css" />
 871: <script src="https://unpkg.com/leaflet@latest/dist/leaflet.js"></script>
 872: %7%
 873: <script>
 874: window.onload = function() {
 875:     var map = L.map('%1%',{zoomControl:false});
 876:     map.setView([%2%, %3%], %4%);
 877:     L.control.scale({
 878:         maxWidth: 250,
 879:         position: 'bottomright',
 880:         imperial: false
 881:     }).addTo(map);
 882:     L.control.zoom({position:'topleft'}).addTo(map);
 883: 
 884:     // 地理院地図:標準地図
 885:     var GSISTD = new L.tileLayer(
 886:         'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
 887:         {
 888:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
 889:             minZoom: 0,
 890:             maxZoom: 18,
 891:             name: 'GSISTD'
 892:         });
 893:     // 地理院地図:淡色地図
 894:     var GSIPALE = new L.tileLayer(
 895:         'https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png',
 896:         {
 897:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
 898:             minZoom: 2,
 899:             maxZoom: 18,
 900:             name: 'GSIPALE'
 901:         });
 902:     // 地理院地図:白地図
 903:     var GSIBLANK = new L.tileLayer(
 904:         'https://cyberjapandata.gsi.go.jp/xyz/blank/{z}/{x}/{y}.png',
 905:         {
 906:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
 907:             minZoom: 5,
 908:             maxZoom: 14,
 909:             name: 'GSIBLANK'
 910:         });
 911:     // 地理院地図:写真
 912:     var GSIPHOTO = new L.tileLayer(
 913:         'https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg',
 914:         {
 915:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
 916:             minZoom: 2,
 917:             maxZoom: 18,
 918:             name: 'GSIPHOTO'
 919:         });
 920:     // OpenStreetMap
 921:     var OSM = new L.tileLayer(
 922:         'https://tile.openstreetmap.jp/{z}/{x}/{y}.png',
 923:         {
 924:             attribution: "<a href='https://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors",
 925:             minZoom: 0,
 926:             maxZoom: 18,
 927:             name: 'OSM'
 928:         });
 929: %8%
 930: 
 931:     // baseMapsオブジェクトにタイル設定
 932:     var baseMaps = {
 933:         "地理院地図" : GSISTD,
 934:         "淡色地図" : GSIPALE,
 935:         "白地図" : GSIBLANK,
 936:         "写真地図" : GSIPHOTO,
 937:         "オープンストリートマップ" : OSM
 938:         %9%
 939:     };
 940: 
 941:     // layersコントロールにbaseMapsオブジェクトを設定して地図に追加
 942:     L.control.layers(baseMaps).addTo(map);
 943:     %5%.addTo(map);
 944: 
 945:     // イベント追加
 946:     map.on('moveend', getPointData);
 947:     map.on('zoomend', getPointData);
 948:     map.on('baselayerchange', getPointData);
 949: 
 950:     // イベント発生時の地図情報を取得・格納
 951:     function getPointData() {
 952:         var pos = map.getCenter();
 953:         // 経度
 954:         if (document.getElementById('longitude'!null) {
 955:             document.getElementById('longitude').value = pos.lng;
 956:         }
 957:         // 緯度
 958:         if (document.getElementById('latitude'!null) {
 959:             document.getElementById('latitude').value = pos.lat;
 960:         }
 961:         // ズーム
 962:         if (document.getElementById('zoom'!null) {
 963:             document.getElementById('zoom').value = map.getZoom();
 964:         }
 965:         // タイプ
 966:         if (document.getElementById('maptype'!null) {
 967:             for (var k in baseMaps) {
 968:                 if (map.hasLayer(baseMaps[k])) {
 969:                     document.getElementById('maptype').value = baseMaps[k].options.name;
 970:                 }
 971:             }
 972:         }
 973:         %11%
 974:     }
 975:     %10%
 976: }
 977: </script>
 978: )")
 979:  %id                 // 地図ID
 980: % latitude              // 緯度
 981: % longitude             // 経度
 982: % zoom                  // 地図拡大率
 983: % type                  // 地図タイプ
 984: % this->GoogleMap1      // Googleマップ描画用スクリプトURL
 985: % this->GoogleMap2      // Leaftet:Googleマップ・アドオンURL
 986: % this->GoogleMap3      // Leaftet:Googleマップ用レイヤ
 987: % this->GoogleMap4      // Leaftet:Googleマップ選択肢
 988: % icode
 989: % call1
 990: ).str();
 991: 
 992:     return script;
 993: }

地図描画は "pahooGeocode.cpp" に分離し、クラス pahooGeocode のメソッド makeMapLeaflet としている。
このメソッドは、「地理院地図・OSM描画 -PHPで住所・ランドマークから最寄り駅を求める」で紹介した手法をそのまま移植した。無償のJavaScriptライブラリLeafletを利用している。
後述するように、Googleマップが利用できるときには、必要なスクリプトを変数 GoogleMap1GoogleMap4 から追加するようにした。

pahooGeocode.cpp

  35: /**
  36:  * コンストラクタ
  37:  * @param   string appname アプリケーション名
  38:  */
  39: pahooGeocode::pahooGeocode(std::string appname) {
  40:     this->appname = appname;
  41:     this->readGoogleApiKey();
  42:     // ホットペッパーグルメWebサービス APIキー読み込み
  43:     readApiKey(FNAME_YAHOO_API, &this->YahooAPIkey);

クラス pahooGeocode のコンストラクタは、後述する Google Cloud Platform のAPIキーと、今回は使用しないが [Yahoo!JAPANデベロッパーネットワーク] のAPIキーを読み込み、キーが存在していればプロパティに代入し、クラウドサービスで利用できるようにする。

解説:地図描画パラメータ

地図描画のパラメータとして、緯度は Latitude、経度は Longitude、拡大率は Zoom、地図形式は Maptype のグローバル変数に、それぞれ代入している。

earthquakewin.cpp

 161: /**
 162:  * パラメータの初期化
 163:  * @param   なし
 164:  * @return  なし
 165:  */
 166: void initParameter(void) {
 167:     Longitude = DEF_LONGITUDE;
 168:     Latitude  = DEF_LATITUDE;
 169:     Zoom      = DEF_ZOOM;
 170:     Maptype   = DEF_MAPTYPE;
 171:     hParent_X = 0;
 172:     hParent_Y = 0;
 173: }

パラメータは、initParamete によって初期化する。

earthquakewin.cpp

 238: /**
 239:  * パラメータの保存
 240:  * @param   なし
 241:  * @return  なし
 242:  */
 243: void saveParameter(void) {
 244: #ifndef CMDAPP
 245:     // アプリケーション・ウィンドウの位置取得
 246:     WINDOWINFO windowInfo;
 247:     windowInfo.cbSize = sizeof(WINDOWINFO);
 248:     GetWindowInfo(hParent, &windowInfo);
 249:     hParent_X = (unsigned)windowInfo.rcWindow.left;
 250:     hParent_Y = (unsigned)windowInfo.rcWindow.top;
 251:     if (hParent_X >= (unsigned)windowInfo.rcWindow.right) {
 252:         hParent_X = 0;
 253:     }
 254:     if (hParent_Y >= (unsigned)windowInfo.rcWindow.bottom) {
 255:         hParent_Y = 0;
 256:     }
 257: #endif
 258: 
 259:     char lng[SIZE_BUFF + 1], lat[SIZE_BUFF + 1];
 260:     snprintf(lng, SIZE_BUFF, "%.5f", Longitude);
 261:     snprintf(lat, SIZE_BUFF, "%.5f", Latitude);
 262: 
 263:     // XMLファイルへ書き込む
 264:     ptree pt;
 265:     ptree& child1 = pt.add("parameter.param", lat);
 266:     child1.add("<xmlattr>.type", "latitude");
 267:     ptree& child2 = pt.add("parameter.param", lng);
 268:     child2.add("<xmlattr>.type", "longitude");
 269:     ptree& child3 = pt.add("parameter.param", to_string(Zoom));
 270:     child3.add("<xmlattr>.type", "zoom");
 271:     ptree& child4 = pt.add("parameter.param", Maptype);
 272:     child4.add("<xmlattr>.type", "maptype");
 273:     ptree& child5 = pt.add("parameter.param", (string)to_string(hParent_X));
 274:     child5.add("<xmlattr>.type", "wx");
 275:     ptree& child6 = pt.add("parameter.param", (string)to_string(hParent_Y));
 276:     child6.add("<xmlattr>.type", "wy");
 277: 
 278:     const int indent = 4;
 279:     write_xml(getMyPath(APPNAME+ APPNAME + ".xml", pt, std::locale(),
 280:         xml_writer_make_settings<std::string>(' ', indent));
 281: }

地図描画のパラメータは、地図に対する操作で随時変化する。この変化は、上述の地図描画JavaScriptによって、HTMLのINPUT要素の値として代入されている。
ブラウザ・コントロールから、これらの値を取り出し、所定のXMLファイルへ保存するのが saveParameter である。
保存場所は、"C:\Users\(ユーザー名)\AppData\Roaming\pahoo.org\(アプリケーション名)" である。
なお、INPUT要素を取得する方法は、「INPUT要素の取得 - C++で最寄駅を検索」で紹介したとおりだ。

earthquakewin.cpp

 175: /**
 176:  * パラメータの読み込み
 177:  * @param   なし
 178:  * @return  なし
 179:  */
 180: void loadParameter(void) {
 181:     ptree pt;
 182: 
 183:     // 初期値設定
 184:     initParameter();
 185: 
 186:     // XMLファイル読み込み
 187:     try {
 188:         xml_parser::read_xml(getMyPath(APPNAME+ APPNAME + ".xml", pt);
 189: 
 190:         // XML解釈
 191:         try {
 192:             // 形式チェック
 193:             if (optional<string>str = pt.get_optional<string>("parameter")) {
 194:             } else {
 195:                 return;
 196:             }
 197:             // パラメータ読み込み
 198:             for (auto it : pt.get_child("parameter")) {
 199:                 string typeit.second.get_optional<string>("<xmlattr>.type").value();
 200:                 if (type == "latitude") {
 201:                     Latitude = stod(it.second.data());
 202:                 } else if (type == "longitude") {
 203:                     Longitude = stod(it.second.data());
 204:                 } else if (type == "zoom") {
 205:                     Zoom = stoi(it.second.data());
 206:                 } else if (type == "maptype") {
 207:                     Maptype = (string)it.second.data();
 208:                 } else if (type == "wx") {
 209:                     hParent_X = (unsigned)stoi(it.second.data());
 210:                 } else if (type == "wy") {
 211:                     hParent_Y = (unsigned)stoi(it.second.data());
 212:                 }
 213:             }
 214:         // 解釈失敗したら初期値設定
 215:         } catch (xml_parser_error& e) {
 216:             initParameter();
 217:             return;
 218:         }
 219:     // 読み込み失敗したら初期値設定
 220:     } catch (xml_parser_error& e) {
 221:         initParameter();
 222:         return;
 223:     }
 224: 
 225:     // アプリケーション・ウィンドウの位置(デスクトップ範囲外なら原点移動)
 226:     HWND hDesktop = GetDesktopWindow();
 227:     WINDOWINFO windowInfo;
 228:     windowInfo.cbSize = sizeof(WINDOWINFO);
 229:     GetWindowInfo(hDesktop, &windowInfo);
 230:     if (hParent_X >= (unsigned)windowInfo.rcWindow.right) {
 231:         hParent_X = 0;
 232:     }
 233:     if (hParent_Y >= (unsigned)windowInfo.rcWindow.bottom) {
 234:         hParent_Y = 0;
 235:     }
 236: }

アプリケーション起動時に地図描画のパラメータを読み出す関数が loadParameter である。saveParameter によって保存されたXMLファイルがあれば、その値を読み込む。無ければ、initParamete によって初期化する。

解説:マップ表示用HTML生成

earthquakewin.cpp

 727: /**
 728:  * 地図表示用HTML生成
 729:  * @param   string 表示するインフォメーション
 730:  * @return  string 生成したHTML文
 731:  */
 732: string makeMapHTML(wstring info) {
 733:     int cnt = info2points();
 734:     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);
 735: 
 736:     string html = (boost::format(R"(<!DOCTYPE html>
 737: <html lang="ja">
 738: <head>
 739: <meta charset="SJIS">
 740: <title>%9%</title>
 741: <meta name="author" content="studio pahoo" />
 742: <meta name="copyright" content="studio pahoo" />
 743: <meta name="ROBOTS" content="NOINDEX,NOFOLLOW" />
 744: <meta http-equiv="pragma" content="no-cache">
 745: <meta http-equiv="cache-control" content="no-cache">
 746: <meta http-equiv="X-UA-Compatible" content="IE=edge">
 747: %8%
 748: </head>
 749: <body>
 750: <div id="%1%" style="width:%2%px; height:%3%px;"></div>
 751: <form>
 752: <input id="latitude"  type="hidden" value="%4%" />
 753: <input id="longitude" type="hidden" value="%5%" />
 754: <input id="zoom"      type="hidden" value="%6%" />
 755: <input id="maptype"   type="hidden" value="%7%" />
 756: </form>
 757: </body>
 758: </html>
 759: )")
 760:  %MAP_ID                     // 地図ID
 761: % MAP_WIDTH                     // 地図の幅
 762: % MAP_HEIGHT                    // 地図の高さ
 763: % Earthquake[0].latitude        // 緯度
 764: % Earthquake[0].longitude       // 経度
 765: % Zoom                          // 地図拡大率
 766: % Maptype                       // 地図タイプ
 767: % script                        // 地図描画スクリプト
 768: % APPNAMEJP                     // アプリケーション名
 769: ).str();
 770: 
 771:     return html;
 772: }

マップ表示用HTML文を生成するのがユーザー関数 makeMapHTML である。
地震情報を引数にして、前述のメソッド makeMapLeaflet を呼び出してHTML文を生成する。

解説:WebView2

これまで、MinGW C++でWebコンテンツを利用するために、Windowsの標準ブラウザだった Internet Explorer(以下、IEと記す) のコンポーネントを利用してきた。このため、アプリは32ビット用であり、また、2022年(令和4年)6月16日に IE のサポートが終了したことを受けて、Googleマップなどが表示できなくなってしまった。

IE サポート終了に備え、マイクロソフトは64ビット環境でデスクトップアプリケーションでWebコンテンツを利用するために「Microsoft Edge WebView2」(以下、WebView2と記す)と呼ぶ仕組みを用意している。Windowsの標準ブラウザ Edgeのモジュールを利用し、Webコンテンツ(HTML,CSS,JavaScriptなど)をアプリに埋め込むことができる。
ところが、WebView2Visual Studio での開発を前提にした仕組みであるため、MinGW C++ で扱うには困難を極めた。とくに、WebView2 のWebコンテンツ上のパラメータを読み書きするのに、Visual C++/C# 固有インターフェースを使わなければならず、これを代替するのに手間取った。

最終的に、MinGW C++ を使って WebView2 を操作できるようになり、アプリを64ビット環境に対応させることができたのだが、後述するように、CからWindows APIを呼び出さなければならないモジュールが必要になり、WebView2操作処理をクラス化することができなかった。このため、いくつかのグローバル変数を追加せざるを得なかった。

解説:WebView2コンポーネントを扱う

WebView2コンポーネントを扱うためのファイルとして、上述の jchv / webview2-in-mingw から入手した "event.cpp", "event.h", "WebView2.h" および実行時に "WebView2Loader.dll" を使用する。
また、jchv / webview2-in-mingw のサンプル・プログラムを参考に、"pahooWebView2.cpp", "pahooWebView2.hpp" を用意した。ここでは、"pahooWebView2.cpp" の内容について解説する。

pahooWebView2.cpp

 129: /**
 130:  * WebView2を生成する.
 131:  * @param   HINSTANCE hInst     現在のインターフェイス
 132:  * @param   HWND hDlg           親ウィンドウ・ハンドラ
 133:  * @paramm  int x, y            WebView2の左上座標
 134:  * @paramm  int width, height   WebView2の幅、高さ
 135:  * @param   LPCWSTR uri         表示するURI
 136:  * @return  HWND                WebView2へのハンドラ
 137: */
 138: HWND createWebView2(HINSTANCE hInst, HWND hDlg, int x, int y, int width, int height, LPCWSTR uri) {
 139:     //ウィンドウ クラス情報
 140:     static WNDCLASSEX wc{};
 141:     wc.cbSize = sizeof(WNDCLASSEX);
 142:     wc.hInstance = hInst;
 143:     wc.lpszClassName = TEXT("webview");
 144:     wc.lpfnWndProc = WndProc;
 145:     RegisterClassEx(&wc);
 146: 
 147:     //ウィンドウ生成
 148:     static HWND hWnd = CreateWindowEx(
 149:         0,
 150:         TEXT("webview"),
 151:         TEXT("MinGW WebView2"),
 152:         WS_CHILD | WS_VISIBLE | ES_LEFT,
 153:         x, y, width, height,
 154:         hDlg,
 155:         nullptr,
 156:         hInst,
 157:         nullptr
 158:     );
 159:     ShowWindow(hWnd, SW_SHOW);
 160:     UpdateWindow(hWnd);
 161:     SetFocus(hWnd);
 162: 
 163:     //データ・パス取得
 164:     TCHAR szDataPath[MAX_PATH + 1];
 165:     GetDataPath(szDataPath, MAX_PATH);
 166:     //イベントハンドラ
 167:     static EventHandler handler{};
 168: 
 169:     handler.EnvironmentCompleted = [&](HRESULT result, ICoreWebView2Environment* created_environment) {
 170: //      cout << "EnvironmentCompleted" << endl;
 171:         if (FAILED(result)) {
 172:             FatalError(TEXT("Failed to create environment?"));
 173:         }
 174:         created_environment->lpVtbl->CreateCoreWebView2Controller(created_environment, hWnd, &handler);
 175:         return S_OK;
 176:     };
 177: 
 178:     handler.ControllerCompleted = [&](HRESULT result, ICoreWebView2Controller* new_controller) {
 179: //      cout << "ControllerCompleted" << endl;
 180:         if (FAILED(result)) {
 181:             FatalError(TEXT("Failed to create controller?"));
 182:         }
 183:         controller = new_controller;
 184:         controller->lpVtbl->AddRef(controller);
 185:         controller->lpVtbl->get_CoreWebView2(controller, &webView2);
 186:         webView2->lpVtbl->AddRef(webView2);
 187:         webView2->lpVtbl->Navigate(webView2, uri);
 188:         ResizeBrowser(hWnd);
 189:         webView2Ready = true;
 190:         return S_OK;
 191:     };
 192: 
 193:     HRESULT result = CreateCoreWebView2EnvironmentWithOptions(
 194:         nullptr,
 195:         TStrToWStr(szDataPath).c_str(),
 196:         nullptr,
 197:         &handler
 198:     );
 199: 
 200:     if (FAILED(result)) {
 201:         FatalError(TEXT("Call to CreateCoreWebView2EnvironmentWithOptions failed!"));
 202:     }
 203: 
 204:     return hWnd;
 205: }

ユーザー関数 createWebView2 は、プログラムの冒頭で呼び出すもので、WebView2を利用できるように準備を整える。
まず、ICoreWebView2Environment インターフェースを使って、WebView2を初期化し、WebView2ランタイムの状態管理やイベントハンドリングができるようにする。
次に、ICoreWebView2Controller インターフェースを使って、アプリケーション内に WebView2コントロールを生成する。
いずれも非同期で処理されるため、これらの処理が終了したことをグローバル変数 webView2Ready に代入しておく。
また、これらのインターフェースなどがCライブラリであるため、"pahooWebView2.cpp" は "CINTERFACE" とせざる得ず、上述のようにクラス化することができなかった。

earthquakewin.cpp

1099: /**
1100:  * WebView2:ダイアログを初期化する
1101:  * @param   HWND hDlg   親ウィンドウ・ハンドラ
1102:  * @return  なし
1103: */
1104: void initDialog(HWND hDlg) {
1105:     HICON hIcon;
1106:     hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
1107:     SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
1108:     ErrorMessage = "";
1109: 
1110:     // カーソルを砂時計に
1111:     SetCursor(LoadCursor(NULL, IDC_WAIT));
1112:     // オプション読み込み
1113:     loadParameter();
1114:     // アプリケーション・ウィンドウ移動
1115:     SetWindowPos(hDlg, NULL, hParent_X, hParent_Y, 0, 0, (SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER));
1116: 
1117:     // テンポラリ・ファイル名取得
1118:     getTempFname(tmpFname);
1119:     size_t wLen = 0;
1120:     mbstowcs_s(&wLen, wUri, MAX_PATH * 2, tmpFname, MAX_PATH);
1121:     // WebView2生成
1122:     hWebView2 = createWebView2(hInst, hDlg, 10, 10, MAP_WIDTH + 20, MAP_HEIGHT + 20, wUri);
1123: 
1124:     // Leafletが利用可能かどうか調べる
1125:     const string url = "https://unpkg.com/leaflet@latest/";
1126:     string contents = "";
1127:     int httpStatus = 0;
1128:     bool res = readWebContents(url, UserAgent, &contents, &httpStatus);
1129:     if (res == FALSE) {
1130:         ErrorMessage = "地図描画ライブラリ(Leaflet)が利用できません";
1131:     } else if ((httpStatus < 200|| (httpStatus > 299)) {
1132:         ErrorMessage = "地図描画ライブラリ(Leaflet)が利用できません";
1133:     }
1134: 
1135:     // メッセージ・ループ
1136:     MSG msg;
1137:     static bool flagWebView2 = false;
1138:     while (GetMessage(&msg, nullptr, 0, 0)) {
1139:         if (msg.message == WM_QUIT) {
1140:             // オプション保存
1141:             // saveOption();
1142:             DestroyWindow(hDlg);
1143:         }
1144:         TranslateMessage(&msg);
1145:         DispatchMessage(&msg);
1146:         if (!flagWebView2 && isWeb2Ready()) {
1147:             // カーソルを砂時計に
1148:             SetCursor(LoadCursor(NULL, IDC_WAIT));
1149:             // 地震情報取得
1150:             getEarthquake();
1151:             // ブラウザ・コントロールに表示
1152:             viewBrowser(infoEarthquake, tmpFname);
1153:             webView2->Reload();
1154:             flagWebView2 = true;
1155:             // 地震情報一覧フレーム作成
1156:             makeListViewFrame(GetDlgItem(hDlg, IDC_LISTVIEW_EARTHQUAKE));
1157:             // 地震情報一覧表示
1158:             makeListView(GetDlgItem(hDlg, IDC_LISTVIEW_EARTHQUAKE));
1159:         }
1160:     }
1161: }

次にメイン・プログラム "earthquakewin.cpp" 側だが、まず、WebView2コントロールを含むダイアログを初期化する関数 initDialog を用意する。
WebView2コントロールで表示するためのHTMLファイルをローカルに用意するため、ユーザー関数 getTempFname を使って、Windowsユーザーの AppDataフォルダにテンポラリファイルを作る。
次に、上述のユーザー関数 createWebView2 を呼び出して WebView2コントロールを用意するのだが、これが非同期処理であるため、メッセージループを用意しなければならない。

earthquakewin.cpp

 802: /**
 803:  * ブラウザ・コントロールを表示
 804:  * @param   wstring info 情報ウィンドウに表示するテキスト
 805:  * @param   char* tmpname 読み込むHTMLファイル名
 806:  * @return  なし
 807: */
 808: void viewBrowser(wstring info, const char* tmpname) {
 809:     string html;
 810:     string fname;
 811:     ofstream ofs;
 812: 
 813:     // 表示用HTMLファイル作成
 814:     if ((ErrorMessage == ""&& (! pGC->isError())) {
 815:         html = makeMapHTML(info);
 816:     } else {
 817:         html = makeErrorHTML();
 818:     }
 819:     ofs.open(tmpname);
 820:     ofs << html;
 821:     ofs.close();
 822: 
 823:     fname = (string)tmpname;
 824:     std::replace(fname.begin(), fname.end(), '\\', '/');
 825: 
 826:     if (isWeb2Ready()) {
 827:         webView2->Reload();
 828:     }
 829: }

WebView2コントロールを、実際に画面に表示するのがユーザー関数 viewBrowser である。

earthquakewin.cpp

1163: /**
1164:  * WebView2のパラメータを取り出す.
1165:  * selectActionの値によって動作を変える。
1166:  * @param   string result   JavaScript終了ハンドラから渡るデータ
1167:  * @return  なし
1168: */
1169: void execScriptCompleted(string result) {
1170:     // パラメータを読み込む
1171:     vector<string> tokens;
1172:     split(tokens, result, is_any_of(",\""));
1173:     int cnt = 0;
1174:     for (string ss: tokens) {
1175:         switch (cnt) {
1176:             case 1:
1177:                 Longitude = stod(ss);
1178:                 break;
1179:             case 2:
1180:                 Latitude = stod(ss);
1181:                 break;
1182:             case 3:
1183:                 Zoom = stoi(ss);
1184:                 break;
1185:             case 4:
1186:                 Maptype = ss;
1187:                 break;
1188:         }
1189:         cnt++;
1190:     }
1191: 
1192:     switch (selectAction) {
1193:         // プログラムを終了する.
1194:         case eAction::Finish:
1195:             // テンポラリ・ファイルを削除する.
1196:             remove(tmpFname);
1197:             // パラメータを保存する.
1198:             saveParameter();
1199:             // プログラムを終了する
1200:             EndDialog(hParent, 0);
1201:             DestroyWindow(hParent);
1202:             break;
1203:         // ブラウザ・コントロールに表示
1204:         default:
1205:             viewBrowser(infoEarthquake, tmpFname);
1206:             break;
1207:     }
1208: //  cout << "Longitude: " << Longitude << endl;
1209: //  cout << "Latitude: " << Latitude << endl;
1210: //  cout << "Zoom: " << Zoom << endl;
1211: //  cout << "Maptype: " << Maptype << endl;
1212: }

解説:地図描画パラメータ」で紹介した、緯度、経度、拡大率、地図形式はマップ上で時々刻々と変化する。これを取得するには、WebView2コントロールと通信しなければならない。IE コントールであればCOM通信(ActiveX)が利用できたのだが、マイクロソフトは脆弱性があるActiveXを廃止してしまったため、WebView2コントロールでは JavaScript を送信し、応答データを非同期で受信しなければならない。
このためのJavaScriptを渡す関数が execScriptCompleted で、ハンドラは scriptCompletedHandler である。取得したいパラメータをカンマ区切りで受け取るスクリプトを渡している。(JSONを使うほどのパラメータ量ではないので😓)

earthquakewin.cpp

1253: /**
1254:  * WebView2のパラメータを保存する.
1255:  * selectActionの値によって動作を変える。
1256:  * @param   なし
1257:  * @return  なし
1258: */
1259: void execScriptAndAction(void) {
1260:     // プログラム終了時に実行するJavaScript
1261:     LPCWSTR js = L"document.getElementById('longitude').value + ',' + document.getElementById('latitude').value + ',' + document.getElementById('zoom').value + ',' + document.getElementById('maptype').value;";
1262: 
1263:     webView2->ExecuteScript(js, scriptCompletedHandler);
1264: }

ハンドラ scriptCompletedHandler が呼び出されるのはプログラム終了時(パラメータを保存する)、もしくは再描画(前回パラメータを参照する)の2種類であるから、グローバル変数 flagFinishProgram で識別する。
カンマ区切りで受け取ったパラメータは Boost C++ の split関数を使って分解し、変数に代入する。
その他の関数、ヘルプファイルやインストーラー作成方法については、これまでの連載で説明してきたとおりである。
なお、WebView2 対応にしたことで64ビット化し、それ以前の32ビット・アプリとインストール場所が変更になることから、インストーラーで MinumumVersion を指定し、メジャー・アップグレード扱いにしてそれ以前のバージョンを削除できるようになっている。

解説:APIキーの管理

Google Cloud Platform - 各種WebAPIの登録方法」で紹介したように、Googleマップを利用するには、Google Cloud PlatformAPIキーをプログラム利用者が取得する必要がある。Google Cloud Platform は利用量によって課金される。2024年(令和6年)5月現在、Googleマップ関連サービスは毎月200ドルまでは無料だが、それ以上の利用量があると課金対象となり、登録したクレジットカードに請求される。
そこで本プログラムでは、ユーザーが APIキーを取得した場合、それをプログラムから入力・保存できるようにするダイアログを用意した。

mystrings.cpp

  31: /**
  32:  * AppDataのパスを取得
  33:  * @param   char* appname アプリケーション名
  34:  * @return  string パス
  35:  */
  36: string getMyPath(const char* appname) {
  37:     static TCHAR myPath[MAX_PATH] = "";
  38: 
  39:     if (strlen(myPath) == 0) {
  40:         if (SHGetSpecialFolderPath(NULL, myPath, CSIDL_APPDATA, 0)) {
  41:             TCHAR *ptmp = _tcsrchr(myPath, _T('\\'));
  42:             if (ptmp !NULL) {
  43:                 ptmp = _tcsinc(ptmp);
  44:                 *ptmp = _T('\0');
  45:             }
  46:             strcat(myPath, _T("Roaming"));
  47:             CreateDirectory((LPCTSTR)myPath, NULL);
  48:             strcat(myPath, _T("\\pahoo.org"));
  49:             CreateDirectory((LPCTSTR)myPath, NULL);
  50:             strcat(myPath, _T("\\"));
  51:             strcat(myPath, _T(appname));
  52:             CreateDirectory((LPCTSTR)myPath, NULL);
  53:             strcat(myPath, _T("\\"));
  54:         } else {
  55:         }
  56:     }
  57:     return (string)myPath;
  58: }

ユーザー関数 getMyPath は、APIキーを保存するフォルダを取得する。ログインユーザーのUserフォルダの下、"\Roaming\pahoo.org" に格納する。この関数は "mystrings.cpp" にある。

apikey.cpp

  66: /**
  67:  * APIキーを書き込む
  68:  * @param   string fname    書き込むファイル名(パスを除く)
  69:  * @param   string key      書き込むAPIキー
  70:  * @return  bool TRUE:書込成功/FALSE:失敗
  71:  */
  72: bool writeApiKey(std::string fname, std::string key) {
  73:     bool ret = TRUE;
  74:     ofstream ofs;
  75: 
  76:     ofs.open((string)getMyPath(NULL+ fname);
  77:     ofs << key;
  78:     if(ofs.bad()) {
  79:         ret = FALSE;
  80:     }
  81:     ofs.close();
  82: 
  83:     return ret;
  84: }

APIキーにかかわる関数群は "apikey.cpp" にある。
ユーザー関数 writeApiKey は、ユーザー関数 getMyPath で指定するフォルダに、変数 key に格納したAPIキーを、ファイル名 "fname" で保存する。保存したAPIキーを、valKey で指定する変数に格納する。

apikey.cpp

  34: /**
  35:  * APIキーを読み込む
  36:  * @param   string fname 読み込むファイル名(パスを除く)
  37:  * @param   string *key  APIキーを格納する変数
  38:  * @return  bool TRUE:読込成功/FALSE:ファイルがない
  39:  */
  40: bool readApiKey(std::string fname, std::string* key) {
  41:     string readKey;
  42:     bool ret = FALSE;
  43: 
  44:     ifstream ifs((string)getMyPath(NULL+ fname);
  45:     if (!ifs)   return ret;
  46: 
  47:     ifs >> readKey;
  48:     if(ifs.bad()) {
  49:         ifs.close();
  50:         return FALSE;
  51:     }
  52:     ifs.close();
  53: 
  54:     //APIキーを格納する
  55:     if (readKey.length() > 0) {
  56:         *key = readKey;
  57:         ret = TRUE;
  58:     //APIキーを削除する
  59:     } else {
  60:         *key = "";
  61:     }
  62: 
  63:     return ret;
  64: }

ユーザー関数 readApiKey は、ユーザー関数 getMyPath で指定するフォルダから、ファイル名 "fname" で保存されたAPIキーを読み出し、key で指定する変数に格納する。

pahooGeocode.cpp

 106: /**
 107:  * Google Cloud Platform APIキーを読み込む
 108:  * @param   なし
 109:  * @return  bool TRUE:読込成功/FALSE:ファイルがない
 110:  */
 111: bool pahooGeocode::readGoogleApiKey(void) {
 112:     string key;
 113:     bool ret = FALSE;
 114: 
 115:     ifstream ifs((string)getMyPath(this->appname.c_str()) + FNAME_GOOGLE_API);
 116:     // APIキー・ファイルが無ければ初期化
 117:     if (!ifs) {
 118:         this->GoogleAPIkey = "";
 119:         this->GoogleMap1 = "";
 120:         this->GoogleMap2 = "";
 121:         this->GoogleMap3 = "";
 122:         this->GoogleMap4 = "";
 123:         return ret;
 124:     }
 125: 
 126:     // APIキー読み込み
 127:     ifs >> key;
 128:     if(ifs.bad()) {
 129:         this->errmsg = _SW("Google Cloud Platform APIキーの読み込みに失敗しました");
 130:         ifs.close();
 131:         this->GoogleAPIkey = "";
 132:         this->GoogleMap1 = "";
 133:         this->GoogleMap2 = "";
 134:         this->GoogleMap3 = "";
 135:         this->GoogleMap4 = "";
 136:         return FALSE;
 137:     }
 138:     ifs.close();
 139: 
 140:     // APIキーなどの設定
 141:     if (key.length() > 0) {
 142:         this->GoogleAPIkey = key;
 143:         this->GoogleMap1 = "<script src='https://maps.googleapis.com/maps/api/js?key="
 144:                 + key + "' async defer></script>";
 145:         this->GoogleMap2 =
 146:                 "<script src='https://unpkg.com/leaflet.gridlayer.googlemutant@0.10.2/Leaflet.GoogleMutant.js'></script>";
 147:         this->GoogleMap3 =
 148:                 "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'});";
 149:         this->GoogleMap4 = ",'Googleマップ(標準)' : GMRD,\n'Googleマップ(写真)' : GMST,\n'Googleマップ(混合)' : GMHB";
 150:         ret = TRUE;
 151:         // APIキーが無ければ初期化
 152:     } else {
 153:         this->GoogleAPIkey = "";
 154:         this->GoogleMap1 = "";
 155:         this->GoogleMap2 = "";
 156:         this->GoogleMap3 = "";
 157:         this->GoogleMap4 = "";
 158:     }
 159:     return ret;
 160: }

ただし、本プログラムで Google Cloud Platform を読み込む処理は、"pahooGeocode.cpp" にメソッド readGoogleApiKey として用意した。
これは、前述の makeMapLeaflet メソッドにGoogleマップを追加するためのスクリプトを変数 GoogleMap1GoogleMap4 に代入する必要があるためである。

共通手順、モジュールなど

その他の関数、ヘルプファイルやインストーラー作成方法については、これまでの連載で説明してきたとおりである。

参考サイト

(この項おわり)
header