サンプル・プログラム
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.cpp | APIキーの管理(ソース) |
sour/apikey.hpp | APIキーの管理(ヘッダ) |
sour/webbrowser.hpp | Webブラウザ・クラス(ヘッダ) |
sour/makefile | ビルド |
バージョン | 更新日 | 内容 |
---|---|---|
1.1.4 | 2024/04/07 | 使用ライブラリ更新 |
1.1.3 | 2023/12/17 | 使用ライブラリ更新 |
1.1.2 | 2023/07/23 | 使用ライブラリ更新 |
1.1.1 | 2023/04/01 | 使用ライブラリ更新 |
1.1.0 | 2022/09/23 | ウィンドウ位置保存,ライブラリ更新 |
バージョン | 更新日 | 内容 |
---|---|---|
1.6.0 | 2023/07/02 | getPointsGSI()メソッド追加 |
1.5 | 2022/09/03 | デバッグコード埋め込み |
1.4 | 2021/05/01 | makeMapLeaflet() 引数追加 |
1.3 | 2021/03/07 | tokyo_wgs84(), wgs84_tokyo()メソッド追加 |
1.2 | 2020/12/20 | leafletスクリプトの参照URL変更 |
バージョン | 更新日 | 内容 |
---|---|---|
1.12 | 2021/01/31 | readWebContents() 引数post追加 |
1.11 | 2020/10/17 | htmlspecialchars() 追加 |
1.1 | 2020/10/17 | GetVersion2()追加,readWebContents() 引数ua追加 |
1.01 | 2020/10/03 | setClipboardData() bug-fix |
1.0 | 2020/09/22 | 初版 |
使用ライブラリ
リソースの準備
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 にすること。
MSYS2 コマンドラインからビルドするのであれば、"makefile" を利用してほしい。
解説:定数など
38: // 定数など ==================================================================
39: #define MAKER "pahoo.org" //作成者
40: #define APPNAME "searchoutletwin" //アプリケーション名
41: #define APPNAMEJP "電源・Wi-Fi利用可能\店舗を検索" //アプリケーション名(日本語)
42: #define APPVERSION "1.1.4" //バージョン
43: #define APPYEAR "2020-24" //作成年
44: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-17-01.shtm" //参考サイト
45:
46: //ListViewItemの最大文字長:変更不可
47: #define MAX_LISTVIEWITEM 259
48:
49: //ヘルプ・ファイル
50: #define HELPFILE ".\\etc\\help.chm"
51:
52: //デフォルト保存ファイル名
53: #define SAVEFILE "stationsearchwin.csv"
54:
55: //現在のインターフェイス
56: HINSTANCE hInst;
57:
58: //アプリケーション・ウィンドウ
59: HWND hParent;
60:
61: //アプリケーション・ウィンドウ位置
62: unsigned hParent_X, hParent_Y;
63:
64: //検索キー格納用
65: string Query;
66:
67: //エラー・メッセージ格納用【変更不可】
68: string ErrorMessage;
69:
70: //ブラウザ・コントロール
71: WebBrowser wBrowser;
72:
73: //UserAgent
74: string UserAgent;
75:
76: //pahooGeocodeオブジェクト
77: pahooGeocode* pGC;
78:
79: //マップID
80: #define MAP_ID "map_id"
81: //地図の大きさ
82: #define MAP_WIDTH 530 //地図の幅(ピクセル)
83: #define MAP_HEIGHT 300 //地図の高さ(ピクセル)
84: //経度・緯度(初期値)
85: #define DEF_LONGITUDE 139.766667
86: double Longitude = DEF_LONGITUDE;
87: #define DEF_LATITUDE 35.681111
88: double Latitude = DEF_LATITUDE;
89: //地図拡大率(初期値)
90: #define DEF_ZOOM 13
91: int Zoom = DEF_ZOOM;
92: //地図の種類(初期値)
93: #define DEF_MAPTYPE "GSISTD"
94: string Maptype = DEF_MAPTYPE;
解説:検索の流れ
1092: //検索
1093: case IDM_EXEC:
1094: case IDC_BUTTON_EXEC:
1095: //検索キーがあればジオコードAPI呼び出し
1096: Query = getStrEditBox(hDlg, IDC_EDIT_QUERY);
1097: if (Query.length() > 0) {
1098: if (pGC->searchPoints(_SW(Query), UserAgent, 0) > 0) {
1099: Longitude = pGC->Ppoints[0].longitude;
1100: Latitude = pGC->Ppoints[0].latitude;
1101: }
1102: //検索キーがなければ地図中心座標を取り出す
1103: } else {
1104: if (wBrowser.getInputById(L"longitude", &val)) {
1105: Longitude = stod(val);
1106: }
1107: if (wBrowser.getInputById(L"latitude", &val)) {
1108: Latitude = stod(val);
1109: }
1110: }
1111: if (wBrowser.getInputById(L"zoom", &val)) {
1112: Zoom = stoi(val);
1113: }
1114: if (wBrowser.getInputById(L"maptype", &val)) {
1115: Maptype = _WS(val);
1116: }
1117: //カーソルを砂時計に
1118: SetCursor(LoadCursor(NULL, IDC_WAIT));
1119: //最寄駅検索
1120: cnt = pOM->getResults_OasisMogya(Latitude, Longitude, 100);
1121: if (cnt > 0) {
1122: cnt = makeInformation(cnt);
1123: }
1124: //ブラウザ・コントロールに表示
1125: viewBrowser(tmpname, cnt);
1126: //最寄駅の一覧表示
1127: makeListView(GetDlgItem(hDlg, IDC_LISTVIEW_OUTLETS));
1128: //エラー・リセット
1129: ErrorMessage = "";
1130: pGC->resetError();
1131: break;
モバイラーズオアシスAPI
URL |
---|
https://oasis.mogya.com/api/v1/spots.json |
フィールド名 | 要否 | 内 容 |
---|---|---|
n | 必須 | 検索範囲の北端の緯度(世界測地系) |
s | 必須 | 検索範囲の南端の緯度(世界測地系) |
e | 必須 | 検索範囲の東端の経度(世界測地系) |
w | 必須 | 検索範囲の西端の経度(世界測地系) |
lat | 任意 | 検索中心の緯度(世界測地系) |
lng | 任意 | 検索中心の経度(世界測地系) |
解説:モバイラーズオアシスAPIの呼び出し
375: /**
376: * モバイラーズオアシス電源情報API から必要な情報を配列に格納する
377: * @param double latitude 緯度(世界測地系)
378: * @param double longitude 経度(世界測地系)
379: * @param double distance 範囲(メートル)
380: * @return int ヒット数/(-1):エラー発生
381: */
382: int pahooOasisMogya::getResults_OasisMogya(double latitude, double longitude, double distance) {
383: //モバイラーズオアシス電源情報API呼び出し
384: char sn[SIZE_BUFF + 1], ss[SIZE_BUFF + 1];
385: char se[SIZE_BUFF + 1], sw[SIZE_BUFF + 1];
386: double n, w, s, e;
387:
388: pGC->getPointDistance(longitude, latitude, distance, 0 - distance, &w, &n);
389: pGC->getPointDistance(longitude, latitude, 0 - distance, distance, &e, &s);
390:
391: snprintf(sn, SIZE_BUFF, "%.5f", n);
392: snprintf(ss, SIZE_BUFF, "%.5f", s);
393: snprintf(se, SIZE_BUFF, "%.5f", e);
394: snprintf(sw, SIZE_BUFF, "%.5f", w);
395: this->webapi = "https://oasis.mogya.com/api/v1/spots.json?n=" + (string)sn + "&w=" + (string)sw + "&s=" + (string)ss + "&e=" + (string)se;
396:
397: //モバイラーズオアシス電源情報API応答
398: static string contents = "";
399: static wstring ucontents = L"";
400:
401: bool res = readWebContents(this->webapi, UserAgent, &contents);
402: if (res == FALSE) {
403: this->errmsg = _SW("モバイラーズオアシス電源情報APIの接続エラーが発生しました");
404: return (-1);
405: }
406:
407: //配列の初期化
408: for (int i = 0; i < __SIZE_PPOINTS; i++) {
409: this->Poutlets[i].id = 0;
410: this->Poutlets[i].title = this->Poutlets[i].address = L"";
411: this->Poutlets[i].phone = L"";
412: this->Poutlets[i].url = this->Poutlets[i].mo_url = "";
413: this->Poutlets[i].latitude = this->Poutlets[i].longitude = 0.0;
414: }
415:
416: wregex re1(_SW("用途\\:充電"));
417: wregex re2(_SW("電源\\:"));
418: wregex re3(_SW("用途\\:"));
419: wregex re4(_SW("NG|ng"));
420: wsmatch mt1;
421:
422: //JSON読み込み
423: int cnt = 0;
424: try {
425: std::stringstream ss;
426: ss << contents;
427: ptree pt;
428: json_parser::read_json(ss, pt);
429:
430: //応答チェック
431: if (optional<string>str = pt.get_optional<string>("status")) {
432: if (str.get() == "OK") {
433: } else {
434: this->errmsg = _SW("モバイラーズオアシス電源情報APIの応答エラーが発生しました");
435: return (-1);
436: }
437: } else {
438: this->errmsg = _SW("モバイラーズオアシス電源情報APIの応答エラーが発生しました");
439: return (-1);
440: }
441:
442: //JSON解釈
443: try {
444: for (auto it : pt.get_child("results")) {
445: if (cnt >= __SIZE_PPOINTS) {
446: break;
447: }
448: //店名
449: if (optional<string>title = it.second.get_optional<string>("title")) {
450: this->Poutlets[cnt].title = _UW(title.get());
451: }
452: //住所
453: if (optional<string>address = it.second.get_optional<string>("address")) {
454: this->Poutlets[cnt].address = _UW(address.get());
455: }
456: //緯度
457: if (optional<string>lat = it.second.get_optional<string>("latitude")) {
458: this->Poutlets[cnt].latitude = stod(lat.get());
459: }
460: //経度
461: if (optional<string>lng = it.second.get_optional<string>("longitude")) {
462: this->Poutlets[cnt].longitude = stod(lng.get());
463: }
464: //URL
465: if (optional<string>url = it.second.get_optional<string>("url")) {
466: this->Poutlets[cnt].url = url.get();
467: }
468: //moURL
469: if (optional<string>mo_url = it.second.get_optional<string>("mo_url")) {
470: this->Poutlets[cnt].mo_url = mo_url.get();
471: }
472: //充電情報
473: this->Poutlets[cnt].charge = L"";
474: for (auto it2 : it.second.get_child("tags")) {
475: if (optional<string>name = it2.second.get_optional<string>("name")) {
476: wstring ws = _UW(name.get());
477: if (regex_search(ws, mt1, re1)) {
478: this->Poutlets[cnt].charge = _SW("可能\");
479: }
480: }
481: }
482: //電源情報
483: this->Poutlets[cnt].powersupply = L"";
484: int key = 0;
485: for (auto it2 : it.second.get_child("tags")) {
486: if (optional<string>name = it2.second.get_optional<string>("name")) {
487: wstring ws = _UW(name.get());
488: if (regex_search(ws, mt1, re4)) {
489: this->Poutlets[cnt].powersupply = L"";
490: break;
491: }
492: if (regex_search(ws, mt1, re2)) {
493: wstring wstr;
494: if (key > 0) {
495: wstr = _SW(",");
496: } else {
497: wstr = L"";
498: }
499: wstr += regex_replace(ws, re2, L"");
500: this->Poutlets[cnt].powersupply += wstr;
501: key++;
502: }
503: }
504: }
505: //Wi-Fi情報
506: this->Poutlets[cnt].wireless = L"";
507: for (auto it2 : it.second.get_child("wireless")) {
508: if (optional<string>name = it2.second.get_optional<string>("name")) {
509: wstring ws = _UW(name.get());
510: wstring wstr;
511: if (key > 0) {
512: wstr = _SW(",");
513: } else {
514: wstr = L"";
515: }
516: wstr += regex_replace(ws, re3, L"");
517: this->Poutlets[cnt].wireless += wstr;
518: key++;
519: }
520: }
521: //識別子
522: this->Poutlets[cnt].id = {(char)(65 + cnt)};
523: cnt++;
524: }
525: //JSON解釈エラー
526: } catch(ptree_bad_path& e) {
527: this->errmsg = _SW("モバイラーズオアシス電源情報APIの検索エラーが発生しました");
528: return (-1);
529: }
530:
531: //読み込みエラー
532: } catch(json_parser_error& e) {
533: this->errmsg = _SW("モバイラーズオアシス電源情報APIの接続エラーが発生しました");
534: return (-1);
535: }
536: contents.clear();
537:
538: return cnt;
539: }
C++で json形式データを扱うことは希だが、幸いなことに、Boost C++ライブラリ の property_tree にある json_parser は xml_parser とほぼ同じ使い方ができる。
今回も、cURL を使って応答情報を変数に代入し、ストリーミングを使ってjsonパーサーに流し込んでやる。
充電情報、電源情報、Wi-Fi情報は配列になっているため、これらをシリアライズしつつ、余計な文字列を正規表現の置換関数 regex_replace を使って省いている。
参考サイト
- モバイラーズオアシスAPI
- PHPで電源・WiFi利用可能店舗を検索する:ぱふぅ家のホームページ
- WiX によるWindowsインストーラー作成:ぱふぅ家のホームページ
- C++ 開発環境の準備:ぱふぅ家のホームページ
「PHPで電源・WiFi利用可能店舗を検索する」で作ったPHPプログラムをC++に移植したものである。
(2024年4月7日)使用ライブラリ更新
(2023年12月17日)使用ライブラリ更新
(2023年7月23日)使用ライブラリ更新