C++ で喫茶店を検索する

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

目次

サンプル・プログラム

圧縮ファイルの内容
searchcafewin.msiインストーラ
bin/searchcafewin.exe実行プログラム本体
bin/cwebpage.dll
bin/libcurl.dll
実行時に必要になるDLL
bin/etc/help.chmヘルプ・ファイル
sour/searchcafewin.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ビルド
searchcafewin.cpp 更新履歴
バージョン 更新日 内容
1.0.0 2024/04/29 初版
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
pahooWebView2.cpp 更新履歴
バージョン 更新日 内容
1.0.0 2024/04/27 初版
apikey.cpp 更新履歴
バージョン 更新日 内容
2.0.0 2024/04/29 createSetAPIkey, processSetAPIkey に統合
1.0 2020/09/30 初版

使用ライブラリ

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

リソースの準備

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

解説:定数など

// 定数など ==================================================================
#define MAKER		"pahoo.org"			//作成者
#define APPNAME		"searchcafewin"		//アプリケーション名
#define APPNAMEJP	"喫茶店を検索"		//アプリケーション名(日本語)
#define APPVERSION	"1.0.0"				//バージョン
#define APPYEAR		"2024"				//作成年
#define REFERENCE	"/e-soul/webtech/cpp01/cpp01-17-01.shtm"	//参考サイト

//ListViewItemの最大文字長:変更不可
#define MAX_LISTVIEWITEM	259

//ヘルプ・ファイル
#define HELPFILE	".\\etc\\help.chm"

//デフォルト保存ファイル名
#define SAVEFILE	"stationsearchwin.csv"

//ホットペッパーグルメWebサービス APIキー名称
#define LABEL_HOTPEPPER_API	"ホットペッパーAPIキー"
//ホットペッパーグルメWebサービス APIキー保存ファイル名
#define FNAME_HOTPEPPER_API	"HotpepperAPIkey"
//ホットペッパーグルメWebサービス APIキー取得ページ
#define URL_HOTPEPPER_API	"https://webservice.recruit.co.jp/register"
//ホットペッパーグルメWebサービス 検索ジャンルID:カフェ・スイーツ
#define HOTPEPPER_GENRE		"G014"

//マップID
#define MAP_ID			"map_id"
//地図の大きさ
#define MAP_WIDTH		550		//地図の幅(ピクセル)
#define MAP_HEIGHT		320		//地図の高さ(ピクセル)
//経度・緯度(初期値)
#define DEF_LONGITUDE	139.766667
double Longitude = DEF_LONGITUDE;
#define DEF_LATITUDE	35.681111
double Latitude  = DEF_LATITUDE;
//地図拡大率(初期値)
#define DEF_ZOOM	13
int Zoom = DEF_ZOOM;
//地図の種類(初期値)
#define DEF_MAPTYPE		"GSISTD"
string Maptype = DEF_MAPTYPE;
とくに注意記載が無い限り、定数は自由に変更できる。

ホットペッパー グルメサーチAPI

ホットペッパー グルメサーチAPIは、入力パラメータ(IN)として GET方式を、出力結果(OUT)がXMLまたはJSONで戻るというAPIである。
WebAPIのURL
URL
https://webservice.recruit.co.jp/hotpepper/gourmet/v1/

入力パラメータ
フィールド名 要否 内  容
key 必須 APIキー
lat 必須 緯度
lng 必須 経度
range 必須 検索範囲
1: 300m
2: 500m
3: 1000m (初期値)
4: 2000m
5: 3000m
datum 任意 world: 世界測地系、tokyo: 旧日本測地系。初期値は world。
genre 任意 お店ジャンルコード。本プログラムでは定数HOTPEPPER_GENREで指定。
count 任意 検索結果の最大出力データ数を指定します。初期値:10、宰相:1、最大100。
format 任意 レスポンス形式。初期値:xml。xml または json または jsonp。
応答データ構造(json) results shop id お店ID name 掲載店名 address 住所 lat 緯度(世界測地系) lng 経度(世界測地系) open 営業時間 close 定休日 genre catch お店ジャンルキャッチ urls pc PC向けURL photo pc l PC用店舗トップ写真(大)画像URL m PC用店舗トップ写真(中)画像URL s PC用店舗トップ写真(小)画像URL wifi Wi-Fi有無 shop id お店ID name 掲載店名 address 住所 lat 緯度(世界測地系) lng 経度(世界測地系) open 営業時間 close 定休日 genre catch お店ジャンルキャッチ urls pc PC向けURL photo pc l PC用店舗トップ写真(大)画像URL m PC用店舗トップ写真(中)画像URL s PC用店舗トップ写真(小)画像URL wifi Wi-Fi有無

解説:ホットペッパーグルメWebサービスの呼び出し

/**
 * ホットペッパー グルメサーチAPI から必要な情報を配列に格納する
 * @param	double latitude  緯度(世界測地系)
 * @param	double longitude 経度(世界測地系)
 * @param	int    range     検索範囲(1:300m,2:500m,3:1000m,4:2000m,5:3000m)
 * @param	string genre     ジャンルID
 * @return	int ヒット数/(-1):エラー発生
*/
int pahooHotpepper::getResults_Hotpepper(double latitude, double longitude, int range, string genre) {
	//ホットペッパー グルメサーチAPI呼び出し
	char lat[SIZE_BUFF + 1], lng[SIZE_BUFF + 1], rng[SIZE_BUFF + 1];;

	if (range <= 0 || range > 5) {
		setError(_SW("検索範囲が間違っています"));
		return (-1);
	}

	snprintf(lat, SIZE_BUFF, "%.5f", latitude);
	snprintf(lng, SIZE_BUFF, "%.5f", longitude);
	snprintf(rng, SIZE_BUFF, "%d", range);
	this->webapi = "http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=" + HotpepperAPIkey + "&lat=" + (string)lat + "&lng=" + (string)lng + "&range=" + (string)rng + "&genre=" + (string)genre;

	//ホットペッパー グルメサーチAPI 応答
	string contents = "";
	bool res = readWebContents(this->webapi, UserAgent, &contents);
	if (res == FALSE) {
		setError(_SW("ホットペッパー グルメサーチAPI の接続エラーが発生しました"));
		return (-1);
	}

	//配列の初期化
	for (int i = 0; i < __SIZE_PPOINTS; i++) {
		this->Cafes[i].id = 0;
		this->Cafes[i].name = this->Cafes[i].address = L"";
		this->Cafes[i].latitude = this->Cafes[i].longitude = 0.0;
		this->Cafes[i].url  = this->Cafes[i].photo = "";
		this->Cafes[i].open = this->Cafes[i].close = L"";
		this->Cafes[i].wifi = this->Cafes[i].budget = L"";
	}

	//XML読み込み
	int cnt = 0;
	try {
		std::stringstream ss;
		ss.str("");
		ss << contents;
		ptree pt;
		xml_parser::read_xml(ss, pt);

		//応答チェック
		if (optionalstr = pt.get_optional("results.results_returned")) {
			int n = stoi(str.get());
			if (n <= 0) {
				return 0;
			}
		} else {
			setError(_SW("ホットペッパー グルメサーチAPI の応答エラーが発生しました"));
			return (-1);
		}

		//XML解釈
		try {
			for (auto it : pt.get_child("results")) {
				if (cnt >= __SIZE_PPOINTS) {
					break;
				}
				//読み飛ばし
				if (it.first != "shop") {
					continue;
				}
				//識別子
				this->Cafes[cnt].id = cnt + 1;
				//店名
				if (optionalname = it.second.get_optional("name")) {
					this->Cafes[cnt].name = _UW(name.value());
				}
				//住所
				if (optionaladdress = it.second.get_optional("address")) {
					this->Cafes[cnt].address = _UW(address.value());
				}
				//緯度
				if (optionallat = it.second.get_optional("lat")) {
					this->Cafes[cnt].latitude = stod(lat.value());
				}
				//経度
				if (optionallng = it.second.get_optional("lng")) {
					this->Cafes[cnt].longitude = stod(lng.value());
				}
				//営業時間
				if (optionalopen = it.second.get_optional("open")) {
					this->Cafes[cnt].open = _UW(open.value());
				}
				//定休日
				if (optionalclose = it.second.get_optional("close")) {
					this->Cafes[cnt].close = _UW(close.value());
				}
				//PC向けURL
				if (optionalurl = it.second.get_optional("urls.pc")) {
					this->Cafes[cnt].url = url.value();
				}
				//写真URL
				if (optionalphoto = it.second.get_optional("photo.pc.s")) {
					this->Cafes[cnt].photo = photo.value();
				}
				//ディナー予算
				if (optionalbudget = it.second.get_optional("budget.average")) {
					this->Cafes[cnt].budget = _UW(budget.value());
				}
				//Wi-Fi有無
				if (optionalwifi = it.second.get_optional("wifi")) {
					this->Cafes[cnt].wifi = _UW(wifi.value());
				}
				cnt++;
			}
		//XML解釈エラー
		} catch(ptree_bad_path& e) {
			setError(_SW("ホットペッパー グルメサーチAPI の検索エラーが発生しました"));
			return (-1);
		}

	//読み込みエラー
	} catch(xml_parser_error& e) {
		setError(_SW("ホットペッパー グルメサーチAPI の接続エラーが発生しました"));
		return (-1);
	}
	contents.clear();

/** debug
	cout << "cnt = " << cnt << endl;
	for (int i = 0; i < cnt; i++) {
		cout << _WS(this->Cafes[cnt].name) << endl;
	}
**/

	return cnt;
}
応答は XML形式である。
Boost C++ライブラリproperty_tree にある xml_parser を利用する。
今回も、cURL を使って応答情報を変数に代入し、ストリーミングを使ってxmlパーサーに流し込んでやる。

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

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

参考サイト

(この項おわり)
header