C++ で電源・Wi-Fi利用可能店舗を検索

(1/1)
>C++で電源・Wi-Fi利用可能店舗を検索
インターネット経由で モバイラーズオアシスAPI にアクセスし、地図や住所、ランドマーク、郵便番号等から電源・Wi-Fi利用可能店舗を検索し、一覧表示する。一覧情報をクリップボードにコピーしたり、CSV形式ファイルに保存することができる。また、ユーザーがAPIキーを取得することでGoogleマップやGoogle住所検索も利用できるようになる。
PHPで電源・WiFi利用可能店舗を検索する」で作ったPHPプログラムをC++に移植したものである。

(2025年3月22日)ネット接続チェック強化,使用ライブラリ更新
(2024年11月30日)使用ライブラリ更新
(2024年8月24日)使用ライブラリ更新
(2024年5月3日)API入力処理を改良
(2024年4月27日)Edgeブラウザ対応,64ビット対応
(2024年4月7日)使用ライブラリ更新

目次

サンプル・プログラム

圧縮ファイルの内容
searchoutletwin.msiインストーラ
bin/searchoutletwin.exe実行プログラム本体
bin/cwebpage.dll
bin/libcurl.dll
実行時に必要になるDLL
bin/etc/help.chmヘルプ・ファイル
sour/searchoutletwin.cppソース・プログラム
sour/resource.hリソース・ヘッダ
sour/resource.rcリソース・ファイル
sour/application.icoアプリケーション・アイコン
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/makefileビルド
searchoutletwin.cpp 更新履歴
バージョン 更新日 内容
2.2.0 2025/03/22 Leafletアクセス可否チェック追加,使用ライブラリ更新
2.1.2 2024/11/30 使用ライブラリ更新
2.1.1 2024/08/24 使用ライブラリ更新
2.1.0 2024/05/03 API入力処理を改良
2.0.0 2024/04/28 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()メソッド追加
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 初版

使用ライブラリ

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

リソースの準備

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

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

解説:定数など

searchoutletwin.cpp

  44: // 定数など ==================================================================
  45: #define MAKER       "pahoo.org"         //作成者
  46: #define APPNAME     "searchoutletwin"   //アプリケーション名
  47: #define APPNAMEJP   "電源・Wi-Fi利用可能\店舗を検索" //アプリケーション名(日本語)
  48: #define APPVERSION  "2.2.0"             //バージョン
  49: #define APPYEAR     "2020-25"           //作成年
  50: #define REFERENCE   "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-17-01.shtm"   //参考サイト
  51: 
  52: //ListViewItemの最大文字長:変更不可
  53: #define MAX_LISTVIEWITEM    259
  54: 
  55: //ヘルプ・ファイル
  56: #define HELPFILE    ".\\etc\\help.chm"
  57: 
  58: //デフォルト保存ファイル名
  59: #define SAVEFILE    "stationsearchwin.csv"
  60: 
  61: //マップID
  62: #define MAP_ID          "map_id"
  63: //地図の大きさ
  64: #define MAP_WIDTH       550     //地図の幅(ピクセル)
  65: #define MAP_HEIGHT      320     //地図の高さ(ピクセル)
  66: //経度・緯度(初期値)
  67: #define DEF_LONGITUDE   139.766667
  68: double Longitude = DEF_LONGITUDE;
  69: #define DEF_LATITUDE    35.681111
  70: double Latitude  = DEF_LATITUDE;
  71: //地図拡大率(初期値)
  72: #define DEF_ZOOM    13
  73: int Zoom = DEF_ZOOM;
  74: //地図の種類(初期値)
  75: #define DEF_MAPTYPE     "GSISTD"
  76: string Maptype = DEF_MAPTYPE;

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

解説:検索の流れ

本アプリケーションの肝は、検索ボタンをクリックしたときのフローである。
>C++で電源・Wi-Fi利用可能店舗を検索

searchoutletwin.cpp

1276:         //検索
1277:         case IDM_EXEC:
1278:         case IDC_BUTTON_EXEC:
1279:             //カーソルを砂時計に
1280:             SetCursor(LoadCursor(NULL, IDC_WAIT));
1281:             //検索キーがあればジオコードAPI呼び出し
1282:             Query = getStrEditBox(hDlg, IDC_EDIT_QUERY);
1283:             if (Query.length() > 0) {
1284:                 if (pGC->searchPoints(_SW(Query), UserAgent, 0> 0) {
1285:                     Longitude = pGC->Ppoints[0].longitude;
1286:                     Latitude  = pGC->Ppoints[0].latitude;
1287:                 }
1288:                 //ブラウザ表示と一覧表示
1289:                 selectAction = eAction::GetZoomType;
1290:                 execScriptAndAction();
1291:             } else {
1292:                 //ブラウザ表示と一覧表示
1293:                 selectAction = eAction::GetLatLongZoomType;
1294:                 execScriptAndAction();
1295:             }
1296:             //エラー・リセット
1297:             ErrorMessage = "";
1298:             pGC->resetError();
1299:             break;

モバイラーズオアシスAPI

緯度・経度から電源・Wi-Fi利用可能店舗を検索するのに、「モバイラーズオアシスAPI 」を利用する。
WebAPIのURL
URL
https://oasis.mogya.com/api/v1/spots.json

入力パラメータ
フィールド名 要否 内  容
n 必須 検索範囲の北端の緯度(世界測地系)
s 必須 検索範囲の南端の緯度(世界測地系)
e 必須 検索範囲の東端の経度(世界測地系)
w 必須 検索範囲の西端の経度(世界測地系)
lat 任意 検索中心の緯度(世界測地系)
lng 任意 検索中心の経度(世界測地系)
応答データ(json) result id ユニークID title 店名 address 住所 tel 電話番号 latitude 緯度 longitude 経度 url 店舗公式サイトのURL wireless 無線LAN(配列) categories name カテゴリ名 image イメージアイコンのURL importance カテゴリの重要度 parent_id 親カテゴリID tag 電源、Qi、禁煙喫煙などの情報(配列) other その他情報(HTML) distance latitude,longitudeで指定した緯度経度からのおおよその距離 mo_url モバイラーズオアシスでの情報ページUR status レスポンス

解説:モバイラーズオアシスAPIの呼び出し

searchoutletwin.cpp

 383: /**
 384:  * モバイラーズオアシス電源情報API から必要な情報を配列に格納する
 385:  * @param   double latitude  緯度(世界測地系)
 386:  * @param   double longitude 経度(世界測地系)
 387:  * @param   double distance  範囲(メートル)
 388:  * @return  int ヒット数/(-1):エラー発生
 389: */
 390: int pahooOasisMogya::getResults_OasisMogya(double latitude, double longitude, double distance) {
 391:     //モバイラーズオアシス電源情報API呼び出し
 392:     char sn[SIZE_BUFF + 1], ss[SIZE_BUFF + 1];
 393:     char se[SIZE_BUFF + 1], sw[SIZE_BUFF + 1];
 394:     double n, w, s, e;
 395: 
 396:     pGC->getPointDistance(longitude, latitude, distance, 0 - distance, &w, &n);
 397:     pGC->getPointDistance(longitude, latitude, 0 - distance, distance, &e, &s);
 398: 
 399:     snprintf(sn, SIZE_BUFF, "%.5f", n);
 400:     snprintf(ss, SIZE_BUFF, "%.5f", s);
 401:     snprintf(se, SIZE_BUFF, "%.5f", e);
 402:     snprintf(sw, SIZE_BUFF, "%.5f", w);
 403:     this->webapi = "https://oasis.mogya.com/api/v1/spots.json?n=" + (string)sn + "&w=" + (string)sw + "&s=" + (string)ss + "&e=" + (string)se;
 404: 
 405:     //モバイラーズオアシス電源情報API応答
 406:     static string contents = "";
 407:     static wstring ucontents = L"";
 408: 
 409:     int httpStatus = 0;
 410:     bool res = readWebContents(this->webapi, UserAgent, &contents, &httpStatus);
 411:     if (res == FALSE) {
 412:         this->errmsg = _SW("モバイラーズオアシス電源情報APIの接続エラーが発生しました");
 413:         return (-1);
 414:     } else if ((httpStatus < 200|| (httpStatus > 299)) {
 415:         ErrorMessage = "モバイラーズオアシス電源情報APIの接続エラーが発生しました";
 416:         return (-1);
 417:     }
 418: 
 419:     //配列の初期化
 420:     for (int i = 0i < __SIZE_PPOINTSi++) {
 421:         this->Poutlets[i].id = 0;
 422:         this->Poutlets[i].title = this->Poutlets[i].address = L"";
 423:         this->Poutlets[i].phone = L"";
 424:         this->Poutlets[i].url = this->Poutlets[i].mo_url = "";
 425:         this->Poutlets[i].latitude = this->Poutlets[i].longitude = 0.0;
 426:     }
 427: 
 428:     wregex re1(_SW("用途\\:充電"));
 429:     wregex re2(_SW("電源\\:"));
 430:     wregex re3(_SW("用途\\:"));
 431:     wregex re4(_SW("NG|ng"));
 432:     wsmatch mt1;
 433: 
 434:     //JSON読み込み
 435:     int cnt = 0;
 436:     try {
 437:         std::stringstream ss;
 438:         ss << contents;
 439:         ptree pt;
 440:         json_parser::read_json(ss, pt);
 441: 
 442:         //応答チェック
 443:         if (optional<string>str = pt.get_optional<string>("status")) {
 444:             if (str.get() == "OK") {
 445:             } else {
 446:                 this->errmsg = _SW("モバイラーズオアシス電源情報APIの応答エラーが発生しました");
 447:                 return (-1);
 448:             }
 449:         } else {
 450:             this->errmsg = _SW("モバイラーズオアシス電源情報APIの応答エラーが発生しました");
 451:             return (-1);
 452:         }
 453: 
 454:         //JSON解釈
 455:         try {
 456:             for (auto it : pt.get_child("results")) {
 457:                 if (cnt >__SIZE_PPOINTS) {
 458:                     break;
 459:                 }
 460:                 //店名
 461:                 if (optional<string>title = it.second.get_optional<string>("title")) {
 462:                     this->Poutlets[cnt].title = _UW(title.get());
 463:                 }
 464:                 //住所
 465:                 if (optional<string>address = it.second.get_optional<string>("address")) {
 466:                     this->Poutlets[cnt].address = _UW(address.get());
 467:                 }
 468:                 //緯度
 469:                 if (optional<string>lat = it.second.get_optional<string>("latitude")) {
 470:                     this->Poutlets[cnt].latitude = stod(lat.get());
 471:                 }
 472:                 //経度
 473:                 if (optional<string>lng = it.second.get_optional<string>("longitude")) {
 474:                     this->Poutlets[cnt].longitude = stod(lng.get());
 475:                 }
 476:                 //URL
 477:                 if (optional<string>url = it.second.get_optional<string>("url")) {
 478:                     this->Poutlets[cnt].url = url.get();
 479:                 }
 480:                 //moURL
 481:                 if (optional<string>mo_url = it.second.get_optional<string>("mo_url")) {
 482:                     this->Poutlets[cnt].mo_url = mo_url.get();
 483:                 }
 484:                 //充電情報
 485:                 this->Poutlets[cnt].charge = L"";
 486:                 for (auto it2 : it.second.get_child("tags")) {
 487:                     if (optional<string>name = it2.second.get_optional<string>("name")) {
 488:                         wstring ws = _UW(name.get());
 489:                         if (regex_search(ws, mt1, re1)) {
 490:                             this->Poutlets[cnt].charge = _SW("可能\");
 491:                     }
 492:                     }
 493:                 }
 494:                 //電源情報
 495:                 this->Poutlets[cnt].powersupply = L"";
 496:                 int key = 0;
 497:                 for (auto it2 : it.second.get_child("tags")) {
 498:                     if (optional<string>name = it2.second.get_optional<string>("name")) {
 499:                         wstring ws = _UW(name.get());
 500:                         if (regex_search(ws, mt1, re4)) {
 501:                             this->Poutlets[cnt].powersupply = L"";
 502:                             break;
 503:                         }
 504:                         if (regex_search(ws, mt1, re2)) {
 505:                             wstring wstr;
 506:                             if (key > 0) {
 507:                                 wstr = _SW(",");
 508:                             } else {
 509:                                 wstr = L"";
 510:                             }
 511:                             wstr +regex_replace(ws, re2, L"");
 512:                             this->Poutlets[cnt].powersupply +wstr;
 513:                             key++;
 514:                         }
 515:                     }
 516:                 }
 517:                 //Wi-Fi情報
 518:                 this->Poutlets[cnt].wireless = L"";
 519:                 for (auto it2 : it.second.get_child("wireless")) {
 520:                     if (optional<string>name = it2.second.get_optional<string>("name")) {
 521:                         wstring ws = _UW(name.get());
 522:                         wstring wstr;
 523:                         if (key > 0) {
 524:                             wstr = _SW(",");
 525:                         } else {
 526:                             wstr = L"";
 527:                         }
 528:                         wstr +regex_replace(ws, re3, L"");
 529:                         this->Poutlets[cnt].wireless +wstr;
 530:                         key++;
 531:                     }
 532:                 }
 533:                 //識別子
 534:                 this->Poutlets[cnt].id = {(char)(65 + cnt)};
 535:                 cnt++;
 536:             }
 537:         //JSON解釈エラー
 538:         } catch(ptree_bad_path& e) {
 539:             this->errmsg = _SW("モバイラーズオアシス電源情報APIの検索エラーが発生しました");
 540:             return (-1);
 541:         }
 542: 
 543:     //読み込みエラー
 544:     } catch(json_parser_error& e) {
 545:         this->errmsg = _SW("モバイラーズオアシス電源情報APIの接続エラーが発生しました");
 546:         return (-1);
 547:     }
 548:     contents.clear();
 549: 
 550:     return cnt;
 551: }

これまでのWebAPIと異なり、応答は json形式である。
C++で json形式データを扱うことは希だが、幸いなことに、Boost C++ライブラリproperty_tree にある json_parserxml_parser とほぼ同じ使い方ができる。
今回も、cURL を使って応答情報を変数に代入し、ストリーミングを使ってjsonパーサーに流し込んでやる。

充電情報、電源情報、Wi-Fi情報は配列になっているため、これらをシリアライズしつつ、余計な文字列を正規表現の置換関数 regex_replace を使って省いている。

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

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

参考サイト

(この項おわり)
header