C++ で最寄駅を検索

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

(2024年5月3日)Edgeブラウザ対応,64ビット対応.API入力処理を改良.
(2024年3月30日)使用ライラリ更新
(2023年11月23日)使用ライラリ更新
(2023年7月8日)検索に国土地理院ジオコーディングAPIを追加,使用ライラリ更新
(2023年4月1日)使用ライラリ更新

目次

サンプル・プログラム

圧縮ファイルの内容
stationsearchwin.msiインストーラ
bin/stationsearchwin.exe実行プログラム本体
bin/cwebpage.dll
bin/libcurl.dll
実行時に必要になるDLL
bin/etc/help.chmヘルプ・ファイル
sour/stationsearchwin.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ビルド
stationsearchwin.cpp 更新履歴
バージョン 更新日 内容
2.1.0 2024/05/03 API入力処理を改良
2.0.0 2024/04/28 Edgeブラウザ対応,64ビット対応
1.1.4 2024/03/30 使用ライラリ更新
1.1.3 2023/11/23 使用ライラリ更新
1.1.2 2023/07/08 使用ライラリ更新
pahooGeocode.cpp 更新履歴
バージョン 更新日 内容
1.8.0 2024/05/03 getMyPath()をapikey.appのgetMyPath()関数に変更
1.7.0 2024/04/27 Edge対応に伴いデバッグ用コードを廃棄
1.6.0 2023/07/02 getPointsGSI()メソッド追加
1.5 2022/09/03 デバッグコード埋め込み
1.4 2021/05/01 makeMapLeaflet() 引数追加
mystrings.cpp 更新履歴
バージョン 更新日 内容
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 初版
pahooWebView2.cpp 更新履歴
バージョン 更新日 内容
1.0.0 2024/04/27 初版

使用ライブラリ

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

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

リソースの準備

今回は32ビット版の開発環境を用いる。
Eclipse を起動し、新規プロジェクト stationsearchwin を用意する。
ResEdit を起動し、resource.rc を用意する。
Eclipse に戻り、ソース・プログラム "stationsearchwin.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" を利用してほしい。

解説:定数など

  44: // 定数など ==================================================================
  45: #define MAKER       "pahoo.org"             //作成者
  46: #define APPNAME     "stationsearchwin"      //アプリケーション名
  47: #define APPNAMEJP   "最寄駅検索"            //アプリケーション名(日本語)
  48: #define APPVERSION  "2.1.0"                 //バージョン
  49: #define APPYEAR     "2020-24"               //作成年
  50: #define REFERENCE   "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-15-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       530     //地図の幅(ピクセル)
  65: #define MAP_HEIGHT      300     //地図の高さ(ピクセル)
  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++で最寄駅を検索

1076:         //検索
1077:         case IDM_EXEC:
1078:         case IDC_BUTTON_EXEC:
1079:             //カーソルを砂時計に
1080:             SetCursor(LoadCursor(NULL, IDC_WAIT));
1081:             //検索キーがあればジオコードAPI呼び出し
1082:             Query = getStrEditBox(hDlg, IDC_EDIT_QUERY);
1083:             if (Query.length() > 0) {
1084:                 if (pGC->searchPoints(_SW(Query), UserAgent, 0> 0) {
1085:                     Longitude = pGC->Ppoints[0].longitude;
1086:                     Latitude  = pGC->Ppoints[0].latitude;
1087:                 }
1088:                 //ブラウザ表示と一覧表示
1089:                 selectAction = eAction::GetZoomType;
1090:                 execScriptAndAction();
1091:             } else {
1092:                 //ブラウザ表示と一覧表示
1093:                 selectAction = eAction::GetLatLongZoomType;
1094:                 execScriptAndAction();
1095:             }
1096:             //エラー・リセット
1097:             ErrorMessage = "";
1098:             pGC->resetError();
1099:             break;

HeartRails Geo API 最寄駅検索

緯度・経度から最寄駅検索するのに、「HeartRails Express API - 最寄駅情報取得 API 」を利用する。
WebAPIのURL
URL
https://express.heartrails.com/api/xml

入力パラメータ
フィールド名 要否 内  容
method 必須 メソッド名:getStation(固定)
x 必須 最寄駅を取得したい場所の経度(世界測地系)。
y 必須 最寄駅を取得したい場所の緯度(世界測地系)。
応答データ(xml) response station name 駅名 line 路線名 distance 検索地点からの距離 x 経度 y 緯度 prefecture 都道府県 postal 郵便番号 next 次の駅 prev 前の駅

解説:HeartRails Geo APIの呼び出し

 376: /**
 377:  * HeartRails Express API から必要な情報を配列に格納する
 378:  * @param   double latitude  緯度(世界測地系)
 379:  * @param   double longitude 経度(世界測地系)
 380:  * @return  int ヒット数/(-1):エラー発生
 381: */
 382: int pahooHeartRailsExpress::getResults_Heartrails(double latitude, double longitude) {
 383:     char lng[SIZE_BUFF + 1], lat[SIZE_BUFF + 1];
 384: 
 385:     snprintf(lng, SIZE_BUFF, "%.5f", longitude);
 386:     snprintf(lat, SIZE_BUFF, "%.5f", latitude);
 387:     this->webapi = "http://express.heartrails.com/api/xml?method=getStations&x=" + (string)lng + "&y=" + (string)lat;
 388:     static string contents = "";
 389:     bool res = readWebContents(this->webapi, UserAgent, &contents);
 390:     if (res == FALSE) {
 391:         this->errmsg = _SW("HeartRails Express APIの接続エラーが発生しました");
 392:         return (-1);
 393:     }
 394: 
 395:     //配列の初期化
 396:     for (int i = 0i < __SIZE_PPOINTSi++) {
 397:         this->Pstations[i].id = 0;
 398:         this->Pstations[i].title = this->Pstations[i].line = this->Pstations[i].distance = L"";
 399:         this->Pstations[i].latitude = this->Pstations[i].longitude = 0.0;
 400:     }
 401: 
 402:     //XML読み込み
 403:     int cnt = 0;
 404:     try {
 405:         std::stringstream ss;
 406:         ss << contents;
 407:         ptree pt;
 408:         xml_parser::read_xml(ss, pt);
 409: 
 410:         //応答チェック
 411:         if (optional<string>str = pt.get_optional<string>("response.station")) {
 412:         } else {
 413:             this->errmsg = _SW("HeartRails Geo APIの応答エラーが発生しました");
 414:             return (-1);
 415:         }
 416: 
 417:         //XML解釈
 418:         try {
 419:             for (auto it : pt.get_child("response")) {
 420:                 if (cnt >__SIZE_PPOINTS) {
 421:                     break;
 422:                 }
 423:                 //読み飛ばし
 424:                 if (it.first !"station") {
 425:                     continue;
 426:                 }
 427:                 //最寄駅名
 428:                 if (optional<string>name = it.second.get_optional<string>("name")) {
 429:                     this->Pstations[cnt].title = _UW(name.value());
 430:                 }
 431:                 //緯度
 432:                 if (optional<string>lat = it.second.get_optional<string>("y")) {
 433:                     this->Pstations[cnt].latitude = stod(lat.value());
 434:                 }
 435:                 //経度
 436:                 if (optional<string>lng = it.second.get_optional<string>("x")) {
 437:                     this->Pstations[cnt].longitude = stod(lng.value());
 438:                 }
 439:                 //路線
 440:                 if (optional<string>line = it.second.get_optional<string>("line")) {
 441:                     this->Pstations[cnt].line = _UW(line.value());
 442:                 }
 443:                 //距離
 444:                 if (optional<string>distance = it.second.get_optional<string>("distance")) {
 445:                     this->Pstations[cnt].distance = _UW(distance.value());
 446:                 }
 447:                 //識別子
 448:                 this->Pstations[cnt].id = {(char)(65 + cnt)};
 449:                 cnt++;
 450:             }
 451:         //XML解釈エラー
 452:         } catch(ptree_bad_path& e) {
 453:             this->errmsg = _SW("HeartRails Geo APIの検索エラーが発生しました");
 454:             return (-1);
 455:         }
 456: 
 457:     //読み込みエラー
 458:     } catch(xml_parser_error& e) {
 459:         this->errmsg = _SW("HeartRails Geo APIの接続エラーが発生しました");
 460:         return (-1);
 461:     }
 462:     contents.clear();
 463: 
 464:     return cnt;
 465: }

解説:パラメータの保存と読み込み

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

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

前回状態の保持を目的として、地図の中心点(緯度・経度)、表示倍率、マップタイプ、アプリケーションウィンドウの位置、検索キーをXMLファイルに保存し、次回起動するときにそれを読み込むようにした。

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

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

参考サイト

(この項おわり)
header