C++ で住所などから緯度・経度を求める

(1/1)
>C++で住所などから緯度・経度を求める
インターネット上のクラウドサービスを利用し、住所やランドマークなどから緯度・経度を検索するアプリケーションである。検索結果をクリプボードにコピーしたり、テキストファイルに保存することができる。また、ユーザーがAPIキーを取得することで、GoogleやYahoo!JAPANのサービスを利用することができる。
PHPで住所・ランドマークから緯度・経度を求める」で作ったPHPプログラムをC++に移植したものである。

(2024年8月17日)使用ライブラリ更新
(2024年3月23日)使用ライブラリ更新
(2023年11月11日)使用ライブラリ更新

目次

サンプル・プログラム

圧縮ファイルの内容
address2geowin.msiインストーラ
bin/address2geowin.exe実行プログラム本体
bin/libcurl-x64.dll実行時に必要になるDLL
bin/etc/help.chmヘルプ・ファイル
sour/address2geowin.cppソース・プログラム
sour/resource.hリソース・ヘッダ
sour/resource.rcリソース・ファイル
sour/application.icoアプリケーション・アイコン
sour/mystrings.cpp汎用文字列処理関数など(ソース)
sour/mystrings.h汎用文字列処理関数など(ヘッダ)
sour/pahooGeocode.cpp住所・緯度・経度に関わるクラス(ソース)
sour/pahooGeocode.cpp住所・緯度・経度に関わるクラス(ヘッダ)
sour/apikey.cppAPIキーの管理(ソース)
sour/apikey.hppAPIキーの管理(ヘッダ)
sour/makefileビルド
address2geowin.cpp 更新履歴
バージョン 更新日 内容
1.2.3 2024/08/17 使用ライブラリ更新
1.2.2 2024/03/23 使用ライブラリ更新
1.2.1 2023/11/11 使用ライブラリ更新
1.2.0 2023/07/02 国土地理院ジオコーディングAPIを使用出来るように
1.1.2 2023/04/01 使用ライブラリ更新
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.2.0 2024/05/06 getModulePath() 追加
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

使用ライブラリ

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

リソースの準備

今回は32ビット版の開発環境を用いる。
Eclipse を起動し、新規プロジェクト address2geowin を用意する。
ResEdit を起動し、resource.rc を用意する。
Eclipse に戻り、ソース・プログラム "address2geowin.cpp" を追加する。
リンカー・フラグを -mwindows -static -lstdc++ -lgcc -lwinpthread -lcurl -lssl -lole32 "C:\pleiades\eclipse\mingw\mysys2\mingw32\bin\libcurl.dll" に設定する。

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

解説:定数など

  36: // 定数など ==================================================================
  37: #define MAKER       "pahoo.org"             //作成者
  38: #define APPNAME     "address2geowin"        //アプリケーション名
  39: #define APPNAMEJP   "住所などから緯度・経度を求める"
  40:                                             //アプリケーション名(日本語)
  41: #define APPVERSION  "1.2.3"                 //バージョン
  42: #define APPYEAR     "2020-24"               //作成年
  43: #define REFERENCE   "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-14-01.shtm"   // 参考サイト
  44: 
  45: //ヘルプ・ファイル
  46: #define HELPFILE    ".\\etc\\help.chm"
  47: 
  48: //デフォルト保存ファイル名
  49: #define SAVEFILE    "address2geowin.txt"
  50: 
  51: //現在のインターフェイス
  52: HINSTANCE hInst;
  53: 
  54: //アプリケーション・ウィンドウ
  55: HWND hParent;
  56: 
  57: //アプリケーション・ウィンドウ位置
  58: unsigned hParent_X, hParent_Y;
  59: 
  60: //検索キー格納用
  61: string Query;
  62: 
  63: //エラー・メッセージ格納用【変更不可】
  64: string ErrorMessage;
  65: 
  66: //UserAgent
  67: string UserAgent;
  68: 
  69: //pahooGeocodeオブジェクト
  70: pahooGeocode *pGC;

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

解説:クラス

C++には、オブジェクト指向プログラムミングを行うための仕組みとしてクラスが用意されている。ある程度まとまった処理を別プログラムで再利用したり、複雑なデータ構造を扱う際、クラスを使うと効果がある。
今回は、クラウドサービスにアクセする処理を pahooGeocode クラス としてコーディングした。ソースは "pahooGeocode.cpp" ヘッダは "pahooGeocode.hpp" である。

  31: using namespace std;
  32: using namespace boost;
  33: using namespace boost::property_tree;
  34: 
  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);
  44: }
  45: 
  46: //デストラクタ
  47: pahooGeocode::~pahooGeocode() {
  48: }
  49: 
  50: // 各種処理 ==================================================================

クラス名と同じ名前を持つメソッドはコンストラクタと呼び、クラスからオブジェクトを生成するときに最初に呼ばれる。
ここでは、GoogleAPIキーやYahoo!アプリケーションIDの読み込みを行う。
一方、クラス名に "~" を付けたメソッドはデストラクタと呼び、オブジェクトを解放するときに呼ばれる。
このデストラクタは何もしない。

解説:住所検索サービスAPI

pahooGeocode クラスは、下表の住所検索サービスAPIを選択して呼び出すことが可能である。各々のAPIの仕様については、「PHPで住所・ランドマークから緯度・経度を求める」をご覧いただきたい。
住所検索サービス
サービス名 メソッド 制 約
1 Google getPointsGoogle 有料(決められた無料枠あり)。全世界の住所、ランドマークの検索可能。
2 Yahoo!JAPAN getPointsYOLP 無料(?)。住所、ランドマーク、海外のどれを検索するか指定。ランドマークや海外地名検索はGoogleに劣る。
11 HeartRails Geo API getPointsHRG 無料。住所、または住所の一部のみ検索可能。
12 OSM Nominatim Search API getPointsNominatim 無料。全世界の住所、ランドマークの検索可能。精度はGoogleに劣る。
13 国土地理院ジオコーディングAPI getPointsGSI 無料。日本国内の住所、ランドマークの検索可能。精度はGoogleに劣る。

 652: /**
 653:  * ジオコーダAPI を用いて検索キーワードから緯度・経度を求める
 654:  *
 655:  * @param   wstring query 検索キーワード
 656:  *                         郵便番号表記 ###-####または#######(半角数字)
 657:  *                         緯度・経度表記 E##.##.##.#N###.##.##.#(半角数字)
 658:  *                         緯度・経度表記 ##.##,###.##(緯度,経度;半角数字)
 659:  * @param   string ua  UserAgent
 660:  * @param   int     api    0:APIを自動選定(省略時)
 661:  *                         1:Google Geocoding API
 662:  *                         2:Yahoo!ジオコーダAPI
 663:  *                        11:HeartRails Geo API
 664:  *                        12:OSM Nominatim Search API
 665:  *                        13:国土地理院ジオコーディングAPI
 666:  * @return  int ヒットした地点数
 667: */
 668: int pahooGeocode::searchPoints(wstring query, string ua, int api=0) {
 669:     static int apilist[] = { 1, 2, 11, 12, 13 };
 670:     int cnt;
 671:     wsmatch mt1;
 672:     //郵便番号パターン
 673:     wregex re01(_SW("([0-9]{3})\\-?([0-9]{4})"));
 674:     //緯度・経度パターン
 675:     wregex re11(_SW("(E|e|W|w|N|n|S|s)([0-9]+)\\.([0-9]+)\\.([0-9]+\\.?[0-9]*)(E|e|W|w|N|n|S|s)([0-9]+)\\.([0-9]+)\\.([0-9]+\\.?[0-9]*)"));
 676:     wregex re12(_SW("(E|e|W|w|N|n|S|s)([0-9]+\\.?[0-9]*)(E|e|W|w|N|n|S|s)([0-9]+\\.?[0-9]*)"));
 677:     wregex re13(_SW("(\\+|\\-)*([0-9]+\\.?[0-9]*)\\s*,\\s*(\\+|\\-)*([0-9]+\\.?[0-9]*)"));
 678: 
 679:     //配列の初期化
 680:     for (int i = 0i < __SIZE_PPOINTSi++) {
 681:         this->Ppoints[i].longitude = 0;
 682:         this->Ppoints[i].latitude  = 0;
 683:         this->Ppoints[i].address   = L"";
 684:     }
 685: 
 686:     //郵便番号かどうか
 687:     if (regex_search(query, mt1, re01)) {
 688:         cnt = this->getPointsHRG_postal(_WS(mt1[1].str() + mt1[2].str()), ua);
 689: 
 690:     //緯度・経度表記かどうか
 691:     } else if (regex_search(query, mt1, re11)) {
 692:         cnt = 0;
 693:         string s1 = _WS(mt1[1].str());
 694:         string s2 = _WS(mt1[5].str());
 695:         double d1 = stod(mt1[2].str()) + stod(mt1[3].str()) / 60 + stod(mt1[4].str()) / (60 * 60);
 696:         double d2 = stod(mt1[6].str()) + stod(mt1[7].str()) / 60 + stod(mt1[8].str()) / (60 * 60);
 697:         if ((s1 == "E"|| (s1 == "e")) {
 698:             this->Ppoints[cnt].longitude = d1;
 699:         } else if ((s1 == "S"|| (s1 == "s")) {
 700:             this->Ppoints[cnt].longitude = -d1;
 701:         } else if ((s1 == "N"|| (s1 == "n")) {
 702:             this->Ppoints[cnt].latitude = d1;
 703:         } else if ((s1 == "S"|| (s1 == "s")) {
 704:             this->Ppoints[cnt].latitude = -d1;
 705:         }
 706:         if ((s2 == "E"|| (s2 == "e")) {
 707:             this->Ppoints[cnt].longitude = d2;
 708:         } else if ((s2 == "S"|| (s2 == "s")) {
 709:             this->Ppoints[cnt].longitude = -d2;
 710:         } else if ((s2 == "N"|| (s2 == "n")) {
 711:             this->Ppoints[cnt].latitude = d2;
 712:         } else if ((s2 == "S"|| (s2 == "s")) {
 713:             this->Ppoints[cnt].latitude = -d2;
 714:         }
 715:         cnt++;
 716:     } else if (regex_search(query, mt1, re12)) {
 717:         cnt = 0;
 718:         string s1 = _WS(mt1[1].str());
 719:         string s2 = _WS(mt1[3].str());
 720:         double d1 = stod(mt1[2].str());
 721:         double d2 = stod(mt1[4].str());
 722:         if ((s1 == "E"|| (s1 == "e")) {
 723:             this->Ppoints[cnt].longitude = d1;
 724:         } else if ((s1 == "S"|| (s1 == "s")) {
 725:             this->Ppoints[cnt].longitude = -d1;
 726:         } else if ((s1 == "N"|| (s1 == "n")) {
 727:             this->Ppoints[cnt].latitude = d1;
 728:         } else if ((s1 == "S"|| (s1 == "s")) {
 729:             this->Ppoints[cnt].latitude = -d1;
 730:         }
 731:         if ((s2 == "E"|| (s2 == "e")) {
 732:             this->Ppoints[cnt].longitude = d2;
 733:         } else if ((s2 == "S"|| (s2 == "s")) {
 734:             this->Ppoints[cnt].longitude = -d2;
 735:         } else if ((s2 == "N"|| (s2 == "n")) {
 736:             this->Ppoints[cnt].latitude = d2;
 737:         } else if ((s2 == "S"|| (s2 == "s")) {
 738:             this->Ppoints[cnt].latitude = -d2;
 739:         }
 740:         cnt++;
 741:     } else if (regex_search(query, mt1, re13)) {
 742:         cnt = 0;
 743:         string s1 = _WS(mt1[1].str());
 744:         string s2 = _WS(mt1[3].str());
 745:         double d1 = stod(mt1[2].str());
 746:         double d2 = stod(mt1[4].str());
 747:         if ((s1 == ""|| (s1 == "+")) {
 748:             this->Ppoints[cnt].latitude = d1;
 749:         } else if (s1 == "-") {
 750:             this->Ppoints[cnt].latitude = -d1;
 751:         }
 752:         if ((s2 == ""|| (s2 == "+")) {
 753:             this->Ppoints[cnt].longitude = d2;
 754:         } else if (s2 == "-") {
 755:             this->Ppoints[cnt].longitude = -d2;
 756:         }
 757:         cnt++;
 758: 
 759:     //APIを自動選定
 760:     } else if (api == 0) {
 761:         for (int api : apilist) {
 762:             this->resetError();
 763:             cnt = this->__searchPoints(query, ua, api);
 764:             if (cnt > 0)    break;
 765:         }
 766:     //API指定
 767:     } else {
 768:         cnt = this->__searchPoints(query, ua, api);
 769:     }
 770: 
 771:     return cnt;
 772: }

メソッド searchPoints は、前述の住所検索サービスを指定して呼び出すか、利用できるサービスを自動選定して呼び出す。
自動選定の順序は、上表にしたがう。GoogleAPIキーやYahoo!アプリケーションIDが未登録の場合は、これらの呼び出しをスキップする。また、そのクラウドサービスを利用することができなかったり、検索結果がゼロだった場合は、次点のサービスを自動的に呼び出す。
その他の関数、マップ描画、ヘルプファイルやインストーラー作成方法については、これまでの連載で説明してきたとおりである。

参考サイト

(この項おわり)
header