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

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

目次

サンプル・プログラム

圧縮ファイルの内容
searchoutletwin.msiインストーラ
bin/searchoutletwin.exe実行プログラム本体
bin/cwebpage.dll
bin/libcrypto-1_1.dll
bin/libcurl.dll
bin/libssl-1_1.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/webbrowser.hppWebブラウザ・クラス(ヘッダ)

使用ライブラリ

WebAPI にアクセスするために、オープンソースのライブラリ Boost C++ライブラリcURL (カール)  および OpenSSL が必要になる。導入方法等については、「C++ 開発環境の準備」をご覧いただきたい。

リソースの準備

今回は 32 ビット版の開発環境を用いる。
Eclipse を起動し、新規プロジェクト searchoutletwin を用意する。
ResEdit を起動し、resource.rc を用意する。

Eclipse に戻り、ソース・プログラム "searchoutletwin.cpp" を追加する。
リンカー・フラグを -Wl,--enable-stdcall-fixup -mwindows -lgdiplus -static -lstdc++ -lgcc -lwinpthread -lcurl -lssl "(任意のパス)\libcurl.dll" "(任意のパス)\cwebpage.dll" "C:\Windows\system32\GdiPlus.dll" に設定する。
また、コマンド行パターンを ${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS} -lole32 -loleaut32 -luuid にすること。

解説:定数など

0038: // 定数など ==================================================================
0039: #define MAKER     "pahoo.org"                //作成者
0040: #define APPNAME     "searchoutletwin"        //アプリケーション名
0041: #define APPNAMEJP "電源・Wi-Fi利用可能\店舗を検索"  //アプリケーション名(日本語)
0042: #define APPVERSION "1.0"                //バージョン
0043: #define APPYEAR     "2020"                //作成年
0044: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-17-01.shtm"  //参考サイト
0045: 
0046: //ListViewItemの最大文字長:変更不可
0047: #define MAX_LISTVIEWITEM 259
0048: 
0049: //ヘルプ・ファイル
0050: #define HELPFILE ".\\etc\\help.chm"
0051: 
0052: //デフォルト保存ファイル名
0053: #define SAVEFILE "stationsearchwin.csv"
0054: 
0055: //エラー・メッセージ格納用:変更不可
0056: string ErrorMessage;
0057: 
0058: //現在のインターフェイス
0059: HINSTANCE hInst;
0060: 
0061: //親ウィンドウ
0062: HWND hParent;
0063: 
0064: //ブラウザ・コントロール
0065: WebBrowser wBrowser;
0066: 
0067: //UserAgent
0068: string UserAgent;
0069: 
0070: //pahooGeocodeオブジェクト
0071: pahooGeocodepGC;
0072: 
0073: //マップID
0074: #define MAP_ID         "map_id"
0075: //地図の大きさ
0076: #define MAP_WIDTH     530        //地図の幅(ピクセル)
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 13
0085: int Zoom = DEF_ZOOM;
0086: //地図の種類(初期値)
0087: #define DEF_MAPTYPE     "GSISTD"
0088: string Maptype = DEF_MAPTYPE;

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

解説:検索の流れ

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

1028:         //検索
1029:         case IDM_EXEC:
1030:         case IDC_BUTTON_EXEC:
1031:             //検索キーがあればジオコードAPI呼び出し
1032:             query = _SW(getStrEditBox(hDlg, IDC_EDIT_QUERY));
1033:             if (query.length() > 0) {
1034:                 if (pGC->searchPoints(query, UserAgent, 0) > 0) {
1035:                     Longitude = pGC->Ppoints[0].longitude;
1036:                     Latitude  = pGC->Ppoints[0].latitude;
1037:                 }
1038:             //検索キーがなければ地図中心座標を取り出す
1039:             } else {
1040:                 if (wBrowser.getInputById(L"longitude", &val)) {
1041:                     Longitude = stod(val);
1042:                 }
1043:                 if (wBrowser.getInputById(L
"latitude", &val)) {
1044:                     Latitude = stod(val);
1045:                 }
1046:             }
1047:             if (wBrowser.getInputById(L
"zoom", &val)) {
1048:                 Zoom = stoi(val);
1049:             }
1050:             if (wBrowser.getInputById(L
"maptype", &val)) {
1051:                 Maptype = _WS(val);
1052:             }
1053:             //カーソルを砂時計に
1054:             SetCursor(LoadCursor(NULL, IDC_WAIT));
1055:             //最寄駅検索
1056:             cnt = pOM->getResults_OasisMogya(Latitude, Longitude, 100);
1057:             if (cnt > 0) {
1058:                 cnt = makeInformation(cnt);
1059:             }
1060:             //ブラウザ・コントロールに表示
1061:             viewBrowser(tmpname, cnt);
1062:             //最寄駅の一覧表示
1063:             makeListView(GetDlgItem(hDlg, IDC_LISTVIEW_OUTLETS));
1064:             //エラー・リセット
1065:             ErrorMessage = 
"";
1066:             pGC->resetError();
1067:             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 tags 電源、Qi、禁煙喫煙などの情報(配列) other その他情報(HTML) distance latitude,longitudeで指定した緯度経度からのおおよその距離 mo_url モバイラーズオアシスでの情報ページUR status レスポンス

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

0291: /**
0292:  * モバイラーズオアシス電源情報API から必要な情報を配列に格納する
0293:  * @param double latitude  緯度(世界測地系)
0294:  * @param double longitude 経度(世界測地系)
0295:  * @param double distance  範囲(メートル)
0296:  * @return int ヒット数/(-1):エラー発生
0297: */
0298: int pahooOasisMogya::getResults_OasisMogya(double latitudedouble longitudedouble distance) {
0299:     //モバイラーズオアシス電源情報API呼び出し
0300:     char sn[SIZE_BUFF + 1], ss[SIZE_BUFF + 1];
0301:     char se[SIZE_BUFF + 1], sw[SIZE_BUFF + 1];
0302:     double nwse;
0303: 
0304:     pGC->getPointDistance(longitudelatitudedistance, 0 - distance, &w, &n);
0305:     pGC->getPointDistance(longitudelatitude, 0 - distancedistance, &e, &s);
0306: 
0307:     snprintf(snSIZE_BUFF, "%.5f", n);
0308:     snprintf(ssSIZE_BUFF, "%.5f", s);
0309:     snprintf(seSIZE_BUFF, "%.5f", e);
0310:     snprintf(swSIZE_BUFF, "%.5f", w);
0311:     this->webapi = "https://oasis.mogya.com/api/v1/spots.json?n=" + (string)sn + "&w=" + (string)sw + "&s=" + (string)ss + "&e=" + (string)se;
0312: 
0313:     //モバイラーズオアシス電源情報API応答
0314:     static string contents = "";
0315:     static wstring ucontents = L"";
0316: 
0317:     bool res = readWebContents(this->webapiUserAgent, &contents);
0318:     if (res == FALSE) {
0319:         this->errmsg = _SW("モバイラーズオアシス電源情報APIの接続エラーが発生しました");
0320:         return (-1);
0321:     }
0322: 
0323:     //配列の初期化
0324:     for (int i = 0; i < __SIZE_PPOINTSi++) {
0325:         this->Poutlets[i].id = 0;
0326:         this->Poutlets[i].title = this->Poutlets[i].address = L"";
0327:         this->Poutlets[i].phone = L"";
0328:         this->Poutlets[i].url = this->Poutlets[i].mo_url = "";
0329:         this->Poutlets[i].latitude = this->Poutlets[i].longitude = 0.0;
0330:     }
0331: 
0332:     wregex re1(_SW("用途\\:充電"));
0333:     wregex re2(_SW("電源\\:"));
0334:     wregex re3(_SW("用途\\:"));
0335:     wregex re4(_SW("NG|ng"));
0336:     wsmatch mt1;
0337: 
0338:     //JSON読み込み
0339:     int cnt = 0;
0340:     try {
0341:         std::stringstream ss;
0342:         ss << contents;
0343:         ptree pt;
0344:         json_parser::read_json(sspt);
0345: 
0346:         //応答チェック
0347:         if (optional<string>str = pt.get_optional<string>("status")) {
0348:             if (str.get() == "OK") {
0349:             } else {
0350:                 this->errmsg = _SW("モバイラーズオアシス電源情報APIの応答エラーが発生しました");
0351:                 return (-1);
0352:             }
0353:         } else {
0354:             this->errmsg = _SW("モバイラーズオアシス電源情報APIの応答エラーが発生しました");
0355:             return (-1);
0356:         }
0357: 
0358:         //JSON解釈
0359:         try {
0360:             for (auto it : pt.get_child("results")) {
0361:                 if (cnt >= __SIZE_PPOINTS) {
0362:                     break;
0363:                 }
0364:                 //店名
0365:                 if (optional<string>title = it.second.get_optional<string>("title")) {
0366:                     this->Poutlets[cnt].title = _UW(title.get());
0367:                 }
0368:                 //住所
0369:                 if (optional<string>address = it.second.get_optional<string>("address")) {
0370:                     this->Poutlets[cnt].address = _UW(address.get());
0371:                 }
0372:                 //緯度
0373:                 if (optional<string>lat = it.second.get_optional<string>("latitude")) {
0374:                     this->Poutlets[cnt].latitude = stod(lat.get());
0375:                 }
0376:                 //経度
0377:                 if (optional<string>lng = it.second.get_optional<string>("longitude")) {
0378:                     this->Poutlets[cnt].longitude = stod(lng.get());
0379:                 }
0380:                 //URL
0381:                 if (optional<string>url = it.second.get_optional<string>("url")) {
0382:                     this->Poutlets[cnt].url = url.get();
0383:                 }
0384:                 //moURL
0385:                 if (optional<string>mo_url = it.second.get_optional<string>("mo_url")) {
0386:                     this->Poutlets[cnt].mo_url = mo_url.get();
0387:                 }
0388:                 //充電情報
0389:                 this->Poutlets[cnt].charge = L"";
0390:                 for (auto it2 : it.second.get_child("tags")) {
0391:                     if (optional<string>name = it2.second.get_optional<string>("name")) {
0392:                         wstring ws = _UW(name.get());
0393:                         if (regex_search(wsmt1re1)) {
0394:                             this->Poutlets[cnt].charge = _SW("可能\");
0395:                         }
0396:                     }
0397:                 }
0398:                 //電源情報
0399:                 this->Poutlets[cnt].powersupply = L
"";
0400:                 int key = 0;
0401:                 for (auto it2 : it.second.get_child(
"tags")) {
0402:                     if (optional<string>name = it2.second.get_optional<string>(
"name")) {
0403:                         wstring ws = _UW(name.get());
0404:                         if (regex_search(ws, mt1, re4)) {
0405:                             this->Poutlets[cnt].powersupply = L
"";
0406:                             break;
0407:                         }
0408:                         if (regex_search(ws, mt1, re2)) {
0409:                             wstring wstr;
0410:                             if (key > 0) {
0411:                                 wstr = _SW(
",");
0412:                             } else {
0413:                                 wstr = L
"";
0414:                             }
0415:                             wstr += regex_replace(ws, re2, L
"");
0416:                             this->Poutlets[cnt].powersupply += wstr;
0417:                             key++;
0418:                         }
0419:                     }
0420:                 }
0421:                 //Wi-Fi情報
0422:                 this->Poutlets[cnt].wireless = L
"";
0423:                 for (auto it2 : it.second.get_child(
"wireless")) {
0424:                     if (optional<string>name = it2.second.get_optional<string>(
"name")) {
0425:                         wstring ws = _UW(name.get());
0426:                         wstring wstr;
0427:                         if (key > 0) {
0428:                             wstr = _SW(
",");
0429:                         } else {
0430:                             wstr = L
"";
0431:                         }
0432:                         wstr += regex_replace(ws, re3, L
"");
0433:                         this->Poutlets[cnt].wireless += wstr;
0434:                         key++;
0435:                     }
0436:                 }
0437:                 //識別子
0438:                 this->Poutlets[cnt].id = {(char)(65 + cnt)};
0439:                 cnt++;
0440:             }
0441:         //JSON解釈エラー
0442:         } catch(ptree_bad_path& e) {
0443:             this->errmsg = _SW(
"モバイラーズオアシス電源情報APIの検索エラーが発生しました");
0444:             return (-1);
0445:         }
0446: 
0447:     //読み込みエラー
0448:     } catch(json_parser_error& e) {
0449:         this->errmsg = _SW(
"モバイラーズオアシス電源情報APIの接続エラーが発生しました");
0450:         return (-1);
0451:     }
0452:     contents.clear();
0453: 
0454:     return cnt;
0455: }

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

充電情報、電源情報、Wi-Fi 情報は配列になっているため、これらをシリアライズしつつ、余計な文字列を正規表現の置換関数 regex_replace を使って省いている。
その他の関数、マップ描画、ヘルプファイルやインストーラー作成方法については、これまでの連載で説明してきたとおりである。

参考サイト

(この項おわり)
header