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

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

(2022年11月20日)使用ライブラリをバージョンアップ
(2022年9月19日)UserAgent追加,各種パラメータの保存.

目次

サンプル・プログラム

圧縮ファイルの内容
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ビルド

使用ライブラリ

WebAPIにアクセスするために、オープンソースのライブラリ 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" を利用してほしい。

解説:定数など

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

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

解説:クラス

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

  33: /**
  34:  * コンストラクタ
  35:  * @param  string appname アプリケーション名
  36:  */
  37: pahooGeocode::pahooGeocode(std::string appname) {
  38:     pahooGeocode::getMyPath(appname.c_str());
  39:     pahooGeocode::readGoogleApiKey();
  40:     pahooGeocode::readYahooApiKey();
  41: }
  42: 
  43: //デストラクタ
  44: pahooGeocode::~pahooGeocode() {
  45: }

クラス名と同じ名前を持つメソッドはコンストラクタと呼び、クラスからオブジェクトを生成するときに最初に呼ばれる。
ここでは、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に劣る。

 679: /**
 680:  * ジオコーダAPI を用いて検索キーワードから緯度・経度を求める
 681:  *
 682:  * @param  wstring query 検索キーワード
 683:  *                         郵便番号表記 ###-####または#######(半角数字)
 684:  *                         緯度・経度表記 E##.##.##.#N###.##.##.#(半角数字)
 685:  *                         緯度・経度表記 ##.##,###.##(緯度,経度;半角数字)
 686:  * @param  string ua  UserAgent
 687:  * @param  int     api    0:APIを自動選定(省略時)
 688:  *                         1:Google Geocoding API
 689:  *                         2:Yahoo!ジオコーダAPI
 690:  *                        11:HeartRails Geo API
 691:  *                        12:OSM Nominatim Search API
 692:  * @return    int ヒットした地点数
 693: */
 694: int pahooGeocode::searchPoints(wstring query, string ua, int api=0) {
 695:     static int apilist[] = { 1, 2, 11, 12 };
 696:     int cnt;
 697:     wsmatch mt1;
 698:     //郵便番号パターン
 699:     wregex re01(_SW("([0-9]{3})\\-?([0-9]{4})"));
 700:     //緯度・経度パターン
 701:     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]*)"));
 702:     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]*)"));
 703:     wregex re13(_SW("(\\+|\\-)*([0-9]+\\.?[0-9]*)\\s*,\\s*(\\+|\\-)*([0-9]+\\.?[0-9]*)"));
 704: 
 705:     //配列の初期化
 706:     for (int i = 0i  <__SIZE_PPOINTSi++) {
 707:         this->Ppoints[i].longitude = 0;
 708:         this->Ppoints[i].latitude  = 0;
 709:         this->Ppoints[i].address   = L"";
 710:     }
 711: 
 712:     //郵便番号かどうか
 713:     if (regex_search(query, mt1, re01)) {
 714:         cnt = this->getPointsHRG_postal(_WS(mt1[1].str() + mt1[2].str()), ua);
 715: 
 716:     //緯度・経度表記かどうか
 717:     } else if (regex_search(query, mt1, re11)) {
 718:         cnt = 0;
 719:         string s1 = _WS(mt1[1].str());
 720:         string s2 = _WS(mt1[5].str());
 721:         double d1 = stod(mt1[2].str()) + stod(mt1[3].str()) / 60 + stod(mt1[4].str()) / (60 * 60);
 722:         double d2 = stod(mt1[6].str()) + stod(mt1[7].str()) / 60 + stod(mt1[8].str()) / (60 * 60);
 723:         if ((s1 == "E"|| (s1 == "e")) {
 724:             this->Ppoints[cnt].longitude = d1;
 725:         } else if ((s1 == "S"|| (s1 == "s")) {
 726:             this->Ppoints[cnt].longitude = -d1;
 727:         } else if ((s1 == "N"|| (s1 == "n")) {
 728:             this->Ppoints[cnt].latitude = d1;
 729:         } else if ((s1 == "S"|| (s1 == "s")) {
 730:             this->Ppoints[cnt].latitude = -d1;
 731:         }
 732:         if ((s2 == "E"|| (s2 == "e")) {
 733:             this->Ppoints[cnt].longitude = d2;
 734:         } else if ((s2 == "S"|| (s2 == "s")) {
 735:             this->Ppoints[cnt].longitude = -d2;
 736:         } else if ((s2 == "N"|| (s2 == "n")) {
 737:             this->Ppoints[cnt].latitude = d2;
 738:         } else if ((s2 == "S"|| (s2 == "s")) {
 739:             this->Ppoints[cnt].latitude = -d2;
 740:         }
 741:         cnt++;
 742:     } else if (regex_search(query, mt1, re12)) {
 743:         cnt = 0;
 744:         string s1 = _WS(mt1[1].str());
 745:         string s2 = _WS(mt1[3].str());
 746:         double d1 = stod(mt1[2].str());
 747:         double d2 = stod(mt1[4].str());
 748:         if ((s1 == "E"|| (s1 == "e")) {
 749:             this->Ppoints[cnt].longitude = d1;
 750:         } else if ((s1 == "S"|| (s1 == "s")) {
 751:             this->Ppoints[cnt].longitude = -d1;
 752:         } else if ((s1 == "N"|| (s1 == "n")) {
 753:             this->Ppoints[cnt].latitude = d1;
 754:         } else if ((s1 == "S"|| (s1 == "s")) {
 755:             this->Ppoints[cnt].latitude = -d1;
 756:         }
 757:         if ((s2 == "E"|| (s2 == "e")) {
 758:             this->Ppoints[cnt].longitude = d2;
 759:         } else if ((s2 == "S"|| (s2 == "s")) {
 760:             this->Ppoints[cnt].longitude = -d2;
 761:         } else if ((s2 == "N"|| (s2 == "n")) {
 762:             this->Ppoints[cnt].latitude = d2;
 763:         } else if ((s2 == "S"|| (s2 == "s")) {
 764:             this->Ppoints[cnt].latitude = -d2;
 765:         }
 766:         cnt++;
 767:     } else if (regex_search(query, mt1, re13)) {
 768:         cnt = 0;
 769:         string s1 = _WS(mt1[1].str());
 770:         string s2 = _WS(mt1[3].str());
 771:         double d1 = stod(mt1[2].str());
 772:         double d2 = stod(mt1[4].str());
 773:         if ((s1 == ""|| (s1 == "+")) {
 774:             this->Ppoints[cnt].latitude = d1;
 775:         } else if (s1 == "-") {
 776:             this->Ppoints[cnt].latitude = -d1;
 777:         }
 778:         if ((s2 == ""|| (s2 == "+")) {
 779:             this->Ppoints[cnt].longitude = d2;
 780:         } else if (s2 == "-") {
 781:             this->Ppoints[cnt].longitude = -d2;
 782:         }
 783:         cnt++;
 784: 
 785:     //APIを自動選定
 786:     } else if (api == 0) {
 787:         for (int api : apilist) {
 788:             this->resetError();
 789:             cnt = this->__searchPoints(query, ua, api);
 790:             if (cnt > 0)    break;
 791:         }
 792:     //API指定
 793:     } else {
 794:         cnt = this->__searchPoints(query, ua, api);
 795:     }
 796: 
 797:     return cnt;
 798: }

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

参考サイト

(この項おわり)
header