/** geocode.cpp
 * 住所・緯度・経度に関わるクラス：C++ソース
 *
 * @copyright	(c)studio pahoo
 * @author		パパぱふぅ
 * @動作環境	MinGW C++ + cURL + OpenSSL + Boost C++ Libraries
 * @参考URL		https://www.pahoo.org/e-soul/webtech/cpp01-12-01.shtm
 */
#include <iostream>
#include <iomanip>
#include <fstream>
#include <winsock2.h>
#include <windows.h>
#include <shlobj.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <tchar.h>
#include <sstream>
#include <string>
#include <regex>
#include <locale>
#include <boost/format.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/json_parser.hpp>
#include "apikey.h"
#include "pahooGeocode.hpp"
#include "mystrings.h"
#include "resource.h"

using namespace std;
using namespace boost;
using namespace boost::property_tree;

/**
 * コンストラクタ
 * @param	string appname アプリケーション名
 */
pahooGeocode::pahooGeocode(std::string appname) {
	this->appname = appname;
	this->readGoogleApiKey();
	// ホットペッパーグルメWebサービス APIキー読み込み
	readApiKey(FNAME_YAHOO_API, &this->YahooAPIkey);
}

// デストラクタ
pahooGeocode::~pahooGeocode() {
}

// 各種処理 ==================================================================
/**
 * エラー状況
 * @param	なし
 * @return	bool TRUE:異常／FALSE:正常
*/
bool pahooGeocode::isError(void) {
	return this->errmsg.length() > 0;
}

/**
 * エラーメッセージ取得
 * @param	なし
 * @return	wstring 現在発生しているエラーメッセージ
*/
wstring pahooGeocode::getError(void) {
	return this->errmsg;
}

/**
 * エラーをリセットする
 * @param	なし
 * @return	なし
*/
void pahooGeocode::resetError(void) {
	this->errmsg = L"";
}

/**
 * エラーをセットする
 * @param	wstring msg エラーメッセージ
 * @return	なし
*/
void pahooGeocode::setError(wstring msg) {
	this->errmsg = msg;
}

/**
 * 直近で使用したWebAPIのURL取得
 * @param	なし
 * @return	string URL
*/
string pahooGeocode::getwebapi(void) {
	return this->webapi;
}

/**
 * GoogleクラウドサービスAPIキーが設定されているかどうかを返す．
 * @param	なし
 * @return	bool TRUE:設定済／FALSE:未設定
*/
bool pahooGeocode::isGoogleApiKey(void) {
	return this->GoogleAPIkey.length() > 0;
}

// APIキー処理 =============================================================
/**
 * Google Cloud Platform APIキーを読み込む
 * @param	なし
 * @return	bool TRUE：読込成功／FALSE：ファイルがない
 */
bool pahooGeocode::readGoogleApiKey(void) {
	string key;
	bool ret = FALSE;

	ifstream ifs((string)getMyPath(this->appname.c_str()) + FNAME_GOOGLE_API);
	// APIキー・ファイルが無ければ初期化
	if (!ifs) {
		this->GoogleAPIkey = "";
		this->GoogleMap1 = "";
		this->GoogleMap2 = "";
		this->GoogleMap3 = "";
		this->GoogleMap4 = "";
		return ret;
	}

	// APIキー読み込み
	ifs >> key;
	if(ifs.bad()) {
		this->errmsg = _SW("Google Cloud Platform APIキーの読み込みに失敗しました");
		ifs.close();
		this->GoogleAPIkey = "";
		this->GoogleMap1 = "";
		this->GoogleMap2 = "";
		this->GoogleMap3 = "";
		this->GoogleMap4 = "";
		return FALSE;
	}
	ifs.close();

	// APIキーなどの設定
	if (key.length() > 0) {
		this->GoogleAPIkey = key;
		this->GoogleMap1 = "<script src='https://maps.googleapis.com/maps/api/js?key="
				+ key + "' async defer></script>";
		this->GoogleMap2 =
				"<script src='https://unpkg.com/leaflet.gridlayer.googlemutant@0.10.2/Leaflet.GoogleMutant.js'></script>";
		this->GoogleMap3 =
				"var GMRD = L.gridLayer.googleMutant({type:'roadmap', name:'GMRD'});\nvar GMST = L.gridLayer.googleMutant({type:'satellite', name:'GMST'});\nvar GMHB = L.gridLayer.googleMutant({type:'hybrid', name:'GMHB'});";
		this->GoogleMap4 = ",'Googleマップ(標準)' : GMRD,\n'Googleマップ(写真)' : GMST,\n'Googleマップ(混合)' : GMHB";
		ret = TRUE;
		// APIキーが無ければ初期化
	} else {
		this->GoogleAPIkey = "";
		this->GoogleMap1 = "";
		this->GoogleMap2 = "";
		this->GoogleMap3 = "";
		this->GoogleMap4 = "";
	}
	return ret;
}

// 座標計算 =================================================================
/**
 * 日本測地系を世界測地系に変換する
 * @param	double  lng0  経度（日本測地系）
 * @param	double  lat0  緯度（日本測地系）
 * @param	double* lng1  変換後の経度（世界測地系）
 * @param	double* lat1  変換後の緯度（世界測地系）
 * @return	なし
*/
void pahooGeocode::tokyo_wgs84(double lng0, double lat0, double* lng1, double* lat1) {
	*lng1 = lng0 - lat0 * 0.000046038 - lng0 * 0.000083043 + 0.010040;
	*lat1 = lat0 - lat0 * 0.00010695  + lng0 * 0.000017464 + 0.0046017;
}

/**
 * 世界測地系を日本測地系に変換する
 * @param	double  lng0  経度（世界測地系）
 * @param	duble   lat0  緯度（世界測地系）
 * @param	double* lng1  変換後の経度（日本測地系）
 * @param	double* lat1  変換後の緯度（日本測地系）
 * @return	なし
*/
void pahooGeocode::wgs84_tokyo(double lng0, double lat0, double* lng1, double* lat1) {
	*lng1 = lng0 + lat0 * 0.000046047 + lng0 * 0.000083049 - 0.010041;
	*lat1 = lat0 + lat0 * 0.00010696  - lng0 * 0.000017467 - 0.0046020;
}

/**
 * ある地点から指定距離離れた地点の緯度・経度を求める
 * @param	double sour_lng   起点の経度（世界測地系）
 * @param	double sour_lat   起点の緯度（世界測地系）
 * @param	double y          北への距離（メートル；南ならマイナス）
 * @param	double x          東への距離（メートル；西ならマイナス）
 * @param	double* dest_lng  離れた地点の経度（世界測地系）
 * @param	double* dest_lat  離れた地点の緯度（世界測地系）
 * @return	float array(経度,緯度)
*/
void pahooGeocode::getPointDistance(double sour_lng, double sour_lat,
	double y, double x, double* dest_lng, double* dest_lat) {
	double rad = 6378137;		// 地球の半径（メートル）

	*dest_lat = (y / rad + sour_lat * (M_PI / 180)) * (180 / M_PI);
	*dest_lng = (x / (rad * cos(sour_lat * (M_PI / 180))) + sour_lng * (M_PI / 180)) * (180 / M_PI);
}

// ジオコーディングAPI =====================================================
/**
 * Google Geocoding API を用いて検索キーワードから緯度・経度を求める
 * @param	wstring query 検索キーワード
 * @param	string ua  UserAgent
 * @return	int ヒットした地点数／(-1)：検索エラー
*/
int pahooGeocode::getPointsGoogle(wstring query, string ua) {
	int httpStatus = 0;
	this->webapi = "https://maps.googleapis.com/maps/api/geocode/xml?key=" + this->GoogleAPIkey + "&language=ja&region=JP&address=" + urlencode(query);
	static string contents = "";
	bool res = readWebContents(this->webapi, ua, &contents, &httpStatus);
	if (res == FALSE) {
		this->errmsg = _SW("Google Geocoding APIの接続エラーが発生しました");
		return (-1);
	} else if ((httpStatus < 200) || (httpStatus > 299)) {
		this->errmsg = _SW("Google Geocoding APIの接続エラーが発生しました（HTTPステータスコード" + to_string(httpStatus) + "）");
		return (-1);
	}

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

		// 応答チェック
		if (optional<string>str = pt.get_optional<string>("GeocodeResponse.status")) {
			if (str.get() != "OK") {
				this->errmsg = _SW("Google Geocoding APIの応答エラーが発生しました");
				return (-1);
			}
		} else {
			this->errmsg = _SW("Google Geocoding APIの応答エラーが発生しました");
			return (-1);
		}

		// XML解釈
		wstring co = L"";
		try {
			for (auto it : pt.get_child("GeocodeResponse.result")) {
				if (cnt >= __SIZE_PPOINTS)	break;
				// 緯度
				if (optional<string>lat = it.second.get_optional<string>("location.lat")) {
					this->Ppoints[cnt].latitude = stod(lat.value());
				}
				// 経度
				if (optional<string>lng = it.second.get_optional<string>("location.lng")) {
					this->Ppoints[cnt].longitude = stod(lng.value());
					cnt++;
				}
				// 住所
				if (it.first == "formatted_address") {
					this->Ppoints[cnt].address = _UW(it.second.data());
				}
			}
		// XML解釈エラー
		} catch(ptree_bad_path& e) {
			this->errmsg = _SW("Google Geocoding APIの検索エラーが発生しました");
			return (-1);
		}
	// 読み込みエラー
	} catch(xml_parser_error& e) {
		this->errmsg = _SW("Google Geocoding APIのエラーが発生しました");
		return (-1);
	}
	contents.clear();

	return cnt + 1;
}

/**
 * YOLPコンテンツジオコーダAPI を用いて検索キーワードから緯度・経度を求める
 * @param	wstring query 検索キーワード
 * @param	string ua  UserAgent
 * @return	int ヒットした地点数
*/
int pahooGeocode::getPointsYOLP(wstring query, string ua) {
	int httpStatus = 0;
	const string category[] = { "address", "landmark", "world" };
	this->webapi = "https://map.yahooapis.jp/geocode/cont/V1/contentsGeoCoder?appid=" + this->YahooAPIkey + "&el=UTF-8&output=xml&category=" + category[0] + "&query=" + urlencode(query);

	string contents = "";
	bool res = readWebContents(this->webapi, ua, &contents, &httpStatus);
	if (res == FALSE) {
		this->errmsg = _SW("YOLPコンテンツジオコーダAPIの接続エラーが発生しました");
		return (-1);
	} else if ((httpStatus < 200) || (httpStatus > 299)) {
		this->errmsg = _SW("YOLPコンテンツジオコーダAPIの接続エラーが発生しました（HTTPステータスコード" + to_string(httpStatus) + "）");
		return (-1);
	}

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

		// 応答チェック
		if (optional<string>str = pt.get_optional<string>("YDF.ResultInfo.Status")) {
			if (str.get() != "200") {
				this->errmsg = _SW("YOLPコンテンツジオコーダAPIの応答エラーが発生しました");
				return (-1);
			}
		} else {
			this->errmsg = _SW("YOLPコンテンツジオコーダAPIの応答エラーが発生しました");
			return (-1);
		}

		// XML解釈
		try {
			wstring co = L"";
			for (auto it : pt.get_child("YDF.Feature")) {
				if (cnt >= __SIZE_PPOINTS)	break;
				// 経度，緯度
				if (optional<string>coordinates = it.second.get_optional<string>("Coordinates")) {
					co = _UW(coordinates.value());
					vector<wstring> arr = wsplit(co, L',');
					this->Ppoints[cnt].longitude = stod(arr[0]);
					this->Ppoints[cnt].latitude  = stod(arr[1]);
					co = L"";
				}
				// 住所
				if (optional<string>address = it.second.get_optional<string>("Address")) {
					this->Ppoints[cnt].address = _UW(address.value());
					cnt++;
				}
			}
		// XML解釈エラー
		} catch(ptree_bad_path& e) {
			this->errmsg = _SW("YOLPコンテンツジオコーダAPIの検索エラーが発生しました");
			return (-1);
		}

	// 読み込みエラー
	} catch(xml_parser_error& e) {
		this->errmsg = _SW("YOLPコンテンツジオコーダAPIの接続エラーが発生しました");
		return (-1);
	}
	contents.clear();

	return cnt;
}

/**
 * HeartRails Geo API - 検索キーワードから緯度・経度を求める
 * @param	wstring query 検索キーワード
 * @param	string ua  UserAgent
 * @return	int ヒットした地点数
*/
int pahooGeocode::getPointsHRG(wstring query, string ua) {
	int httpStatus = 0;
	this->webapi = "http://geoapi.heartrails.com/api/xml?method=suggest&matching=like&keyword=" + urlencode(query);
	string contents = "";
	bool res = readWebContents(this->webapi, ua, &contents, &httpStatus);
	if (res == FALSE) {
		this->errmsg = _SW("HeartRails Geo APIの接続エラーが発生しました");
		return (-1);
	} else if ((httpStatus < 200) || (httpStatus > 299)) {
		this->errmsg = _SW("HeartRails Geo APIの接続エラーが発生しました（HTTPステータスコード" + to_string(httpStatus) + "）");
		return (-1);
	}

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

		// 応答チェック
		if (optional<string>str = pt.get_optional<string>("response.location")) {
		} else {
			this->errmsg = _SW("HeartRails Geo APIの応答エラーが発生しました");
			return (-1);
		}

		// XML解釈
		try {
			for (auto it : pt.get_child("response")) {
				if (cnt >= __SIZE_PPOINTS)	break;
				// 緯度
				if (optional<string>lat = it.second.get_optional<string>("y")) {
					this->Ppoints[cnt].latitude = stod(lat.value());
				}
				// 経度
				if (optional<string>lng = it.second.get_optional<string>("x")) {
					this->Ppoints[cnt].longitude = stod(lng.value());
				}
				// 住所
				if (optional<string>town = it.second.get_optional<string>("town")) {
					this->Ppoints[cnt].address = _UW(town.value());
				}
				if (optional<string>city = it.second.get_optional<string>("city")) {
					this->Ppoints[cnt].address = _UW(city.value()) + this->Ppoints[cnt].address;
				}
				if (optional<string>pref = it.second.get_optional<string>("prefecture")) {
					this->Ppoints[cnt].address = _UW(pref.value()) + this->Ppoints[cnt].address;
					cnt++;
				}
			}
		// XML解釈エラー
		} catch(ptree_bad_path& e) {
			this->errmsg = _SW("HeartRails Geo APIの検索エラーが発生しました");
			return (-1);
		}

	// 読み込みエラー
	} catch(xml_parser_error& e) {
		this->errmsg = _SW("HeartRails Geo APIの接続エラーが発生しました");
		return (-1);
	}
	contents.clear();

	return cnt;
}

/**
 * HeartRails Geo API - 郵便番号から緯度・経度・住所を検索
 * @param	string postal 郵便番号（7桁の半角数字）
 * @param	string ua  UserAgent
 * @return	int ヒットした地点数
*/
int pahooGeocode::getPointsHRG_postal(string postal, string ua) {
	int httpStatus = 0;
	this->webapi = "http://geoapi.heartrails.com/api/xml?method=searchByPostal&postal=" + postal;
	string contents = "";
	bool res = readWebContents(this->webapi, ua, &contents, &httpStatus);
	if (res == FALSE) {
		this->errmsg = _SW("HeartRails Geo APIの接続エラーが発生しました");
		return (-1);
	} else if ((httpStatus < 200) || (httpStatus > 299)) {
		this->errmsg = _SW("HeartRails Geo APIの接続エラーが発生しました（HTTPステータスコード" + to_string(httpStatus) + "）");
		return (-1);
	}

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

		// 応答チェック
		if (optional<string>str = pt.get_optional<string>("response.location")) {
		} else {
			this->errmsg = _SW("HeartRails Geo APIの応答エラーが発生しました");
			return (-1);
		}

		// XML解釈
		try {
			for (auto it : pt.get_child("response")) {
				if (cnt >= __SIZE_PPOINTS)	break;
				// 緯度
				if (optional<string>lat = it.second.get_optional<string>("y")) {
					this->Ppoints[cnt].latitude = stod(lat.value());
				}
				// 経度
				if (optional<string>lng = it.second.get_optional<string>("x")) {
					this->Ppoints[cnt].longitude = stod(lng.value());
				}
				// 住所
				if (optional<string>town = it.second.get_optional<string>("town")) {
					this->Ppoints[cnt].address = _UW(town.value());
				}
				if (optional<string>city = it.second.get_optional<string>("city")) {
					this->Ppoints[cnt].address = _UW(city.value()) + this->Ppoints[cnt].address;
				}
				if (optional<string>pref = it.second.get_optional<string>("prefecture")) {
					this->Ppoints[cnt].address = _UW(pref.value()) + this->Ppoints[cnt].address;
					cnt++;
				}
			}
		// XML解釈エラー
		} catch(ptree_bad_path& e) {
			this->errmsg = _SW("HeartRails Geo APIの検索エラーが発生しました");
			return (-1);
		}

	// 読み込みエラー
	} catch(xml_parser_error& e) {
		this->errmsg = _SW("HeartRails Geo APIの接続エラーが発生しました");
		return (-1);
	}
	contents.clear();

	return cnt;
}

/**
 * OSM Nominatim Search API を用いて検索キーワードから緯度・経度を求める
 * @param	wstring query 検索キーワード
 * @param	string ua  UserAgent
 * @return	int ヒットした地点数
*/
int pahooGeocode::getPointsNominatim(wstring query, string ua) {
	int httpStatus = 0;
	this->webapi = "https://nominatim.openstreetmap.org/search?format=xml&q=" + urlencode(query);
	string contents = "";
	bool res = readWebContents(this->webapi, ua, &contents, &httpStatus);
	if (res == FALSE) {
		this->errmsg = _SW("OSM Nominatim Search APIの接続エラーが発生しました");
		return (-1);
	} else if ((httpStatus < 200) || (httpStatus > 299)) {
		this->errmsg = _SW("OSM Nominatim Search APIの接続エラーが発生しました（HTTPステータスコード" + to_string(httpStatus) + "）");
		return (-1);
	}

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

		// 応答チェック
		if (optional<string>str = pt.get_optional<string>("searchresults.place")) {
		} else {
			this->errmsg = _SW("OSM Nominatim Search APIの応答エラーが発生しました");
			return (-1);
		}

		// XML解釈
		try {
			for (auto it : pt.get_child("searchresults")) {
				if (cnt >= __SIZE_PPOINTS)	break;
				// 緯度
				if (optional<string>lat = it.second.get_optional<string>("<xmlattr>.lat")) {
					this->Ppoints[cnt].latitude = stod(lat.value());
				}
				// 経度
				if (optional<string>lng = it.second.get_optional<string>("<xmlattr>.lon")) {
					this->Ppoints[cnt].longitude = stod(lng.value());
				}
				// 住所
				if (optional<string>address = it.second.get_optional<string>("<xmlattr>.display_name")) {
					this->Ppoints[cnt].address = _UW(address.value());
					cnt++;
				}
			}
		// XML解釈エラー
		} catch(ptree_bad_path& e) {
			this->errmsg = _SW("OSM Nominatim Search APIの検索エラーが発生しました");
			return (-1);
		}

	// 読み込みエラー
	} catch(xml_parser_error& e) {
		this->errmsg = _SW("OSM Nominatim Search APIの接続エラーが発生しました");
		return (-1);
	}
	contents.clear();

	return cnt;
}

/**
 * 国土地理院ジオコーディングAPIを用いて検索キーワードから緯度・経度を求める
 * @param	wstring query 検索キーワード
 * @param	string ua  UserAgent
 * @return	int ヒットした地点数
*/
int pahooGeocode::getPointsGSI(wstring query, string ua) {
	int httpStatus = 0;
	this->webapi = "https://msearch.gsi.go.jp/address-search/AddressSearch?q=" + urlencode(query);
	string contents = "";
	bool res = readWebContents(this->webapi, ua, &contents, &httpStatus);
//	cout << httpStatus << httpStatus << endl;
	if (res == FALSE) {
		this->errmsg = _SW("国土地理院ジオコーディングAPIの接続エラーが発生しました");
		return (-1);
	} else if ((httpStatus < 200) || (httpStatus > 299)) {
		this->errmsg = _SW("国土地理院ジオコーディングAPIの接続エラーが発生しました（HTTPステータスコード" + to_string(httpStatus) + "）");
		return (-1);
	}

	// XML読み込み
	int cnt = 0;
	try {
		std::stringstream ss;
		ss << contents;
		ptree pt;
		json_parser::read_json(ss, pt);

		// JSON解釈
		try {
			for (auto it = pt.begin(); it != pt.end(); ++it) {
				if (cnt >= __SIZE_PPOINTS)	break;
				ptree pt2 = it->second.get_child("geometry.coordinates");
				int cnt2 = 0;
				for (auto it2 = pt2.begin(); it2 != pt2.end(); ++it2) {
					// 緯度
					if (cnt2 == 1) {
						this->Ppoints[cnt].latitude = stod(it2->second.data());
					// 経度
					} else {
						this->Ppoints[cnt].longitude = stod(it2->second.data());
					}
					cnt2++;
				}
				// 住所
				if (optional<string>address = it->second.get_optional<string>("properties.title")) {
					this->Ppoints[cnt].address = _UW(address.value());
					cnt++;
				}
			}
		// JSON解釈エラー
		} catch(ptree_bad_path& e) {
			this->errmsg = _SW("国土地理院ジオコーディングAPIの検索エラーが発生しました");
			return (-1);
		}

	// 読み込みエラー
	} catch(json_parser_error &e) {
		this->errmsg = _SW("国土地理院ジオコーディングAPIの接続エラーが発生しました");
		return (-1);
	}
	contents.clear();

	return cnt;
}

/**
 * ジオコーダAPI を用いて検索キーワードから緯度・経度を求める（下請け）
 *
 * @param	wstring query 検索キーワード
 * @param	string ua  UserAgent
 * @param	int     api    1：Google Geocoding API
 *                         2：Yahoo!ジオコーダAPI
 *                        11：HeartRails Geo API
 *                        12：OSM Nominatim Search API
 *                        13：国土地理院ジオコーディングAPI
 * @return	int ヒットした地点数
*/
int pahooGeocode::__searchPoints(wstring query, string ua, int api) {
	int cnt = 0;

	switch (api) {
	case 1:
		if (this->GoogleAPIkey == "")	return (-1);
		cnt = this->getPointsGoogle(query, ua);
		break;
	case 2:
		if (this->YahooAPIkey == "")	return (-1);
		cnt = this->getPointsYOLP(query, ua);
		break;
	case 11:
		cnt = this->getPointsHRG(query, ua);
		break;
	case 12:
		cnt = this->getPointsNominatim(query, ua);
		break;
	case 13:
		cnt = this->getPointsGSI(query, ua);
		break;
	default:
		cnt = (-1);
		break;
	}
	return cnt;
}

/**
 * ジオコーダAPI を用いて検索キーワードから緯度・経度を求める
 *
 * @param	wstring query 検索キーワード
 *                         郵便番号表記　###-####または#######（半角数字）
 *                         緯度・経度表記　E##.##.##.#N###.##.##.#（半角数字）
 *                         緯度・経度表記　##.##,###.##（緯度，経度；半角数字）
 * @param	string ua  UserAgent
 * @param	int     api    0：APIを自動選定（省略時）
 *                         1：Google Geocoding API
 *                         2：Yahoo!ジオコーダAPI
 *                        11：HeartRails Geo API
 *                        12：OSM Nominatim Search API
 *                        13：国土地理院ジオコーディングAPI
 * @return	int ヒットした地点数
*/
int pahooGeocode::searchPoints(wstring query, string ua, int api=0) {
	static int apilist[] = { 1, 2, 11, 12, 13 };
	int cnt;
	wsmatch mt1;
	// 郵便番号パターン
	wregex re01(_SW("([0-9]{3})\\-?([0-9]{4})"));
	// 緯度・経度パターン
	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]*)"));
	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]*)"));
	wregex re13(_SW("(\\+|\\-)*([0-9]+\\.?[0-9]*)\\s*,\\s*(\\+|\\-)*([0-9]+\\.?[0-9]*)"));

	// 配列の初期化
	for (int i = 0; i < __SIZE_PPOINTS; i++) {
		this->Ppoints[i].longitude = 0;
		this->Ppoints[i].latitude  = 0;
		this->Ppoints[i].address   = L"";
	}

	// 郵便番号かどうか
	if (regex_search(query, mt1, re01)) {
		cnt = this->getPointsHRG_postal(_WS(mt1[1].str() + mt1[2].str()), ua);

	// 緯度・経度表記かどうか
	} else if (regex_search(query, mt1, re11)) {
		cnt = 0;
		string s1 = _WS(mt1[1].str());
		string s2 = _WS(mt1[5].str());
		double d1 = stod(mt1[2].str()) + stod(mt1[3].str()) / 60 + stod(mt1[4].str()) / (60 * 60);
		double d2 = stod(mt1[6].str()) + stod(mt1[7].str()) / 60 + stod(mt1[8].str()) / (60 * 60);
		if ((s1 == "E") || (s1 == "e")) {
			this->Ppoints[cnt].longitude = d1;
		} else if ((s1 == "S") || (s1 == "s")) {
			this->Ppoints[cnt].longitude = -d1;
		} else if ((s1 == "N") || (s1 == "n")) {
			this->Ppoints[cnt].latitude = d1;
		} else if ((s1 == "S") || (s1 == "s")) {
			this->Ppoints[cnt].latitude = -d1;
		}
		if ((s2 == "E") || (s2 == "e")) {
			this->Ppoints[cnt].longitude = d2;
		} else if ((s2 == "S") || (s2 == "s")) {
			this->Ppoints[cnt].longitude = -d2;
		} else if ((s2 == "N") || (s2 == "n")) {
			this->Ppoints[cnt].latitude = d2;
		} else if ((s2 == "S") || (s2 == "s")) {
			this->Ppoints[cnt].latitude = -d2;
		}
		cnt++;
	} else if (regex_search(query, mt1, re12)) {
		cnt = 0;
		string s1 = _WS(mt1[1].str());
		string s2 = _WS(mt1[3].str());
		double d1 = stod(mt1[2].str());
		double d2 = stod(mt1[4].str());
		if ((s1 == "E") || (s1 == "e")) {
			this->Ppoints[cnt].longitude = d1;
		} else if ((s1 == "S") || (s1 == "s")) {
			this->Ppoints[cnt].longitude = -d1;
		} else if ((s1 == "N") || (s1 == "n")) {
			this->Ppoints[cnt].latitude = d1;
		} else if ((s1 == "S") || (s1 == "s")) {
			this->Ppoints[cnt].latitude = -d1;
		}
		if ((s2 == "E") || (s2 == "e")) {
			this->Ppoints[cnt].longitude = d2;
		} else if ((s2 == "S") || (s2 == "s")) {
			this->Ppoints[cnt].longitude = -d2;
		} else if ((s2 == "N") || (s2 == "n")) {
			this->Ppoints[cnt].latitude = d2;
		} else if ((s2 == "S") || (s2 == "s")) {
			this->Ppoints[cnt].latitude = -d2;
		}
		cnt++;
	} else if (regex_search(query, mt1, re13)) {
		cnt = 0;
		string s1 = _WS(mt1[1].str());
		string s2 = _WS(mt1[3].str());
		double d1 = stod(mt1[2].str());
		double d2 = stod(mt1[4].str());
		if ((s1 == "") || (s1 == "+")) {
			this->Ppoints[cnt].latitude = d1;
		} else if (s1 == "-") {
			this->Ppoints[cnt].latitude = -d1;
		}
		if ((s2 == "") || (s2 == "+")) {
			this->Ppoints[cnt].longitude = d2;
		} else if (s2 == "-") {
			this->Ppoints[cnt].longitude = -d2;
		}
		cnt++;

	// APIを自動選定
	} else if (api == 0) {
		for (int api : apilist) {
			this->resetError();
			cnt = this->__searchPoints(query, ua, api);
			if (cnt > 0)	break;
		}
	// API指定
	} else {
		cnt = this->__searchPoints(query, ua, api);
	}

	return cnt;
}

// 地図描画 =================================================================
/**
 * 地図描画スクリプトを生成する
 * @param	string id        マップID
 * @param	double longitude 中心座標：経度（世界測地系）
 * @param	double latitude  中心座標：緯度（世界測地系）
 * @param	int    zoom      拡大率
 * @param	string type      マップタイプ
 *								GSISTD：地理院地図（標準）：省略時
 *								GSIPALE：地理院地図（淡色地図）
 *								GSIBLANK：地理院地図（白地図）
 *								GSIPHOTO：地理院地図（写真）
 *								OSM：OpenStreetMap
 *								GMRD：Googleマップ（ROADMAP）；APIキー有効時
 * @param	ppoints_t* items  地点情報配列（省略可能）
 * @param	size_t size       地点情報配列の数（省略可能）
 * @param	string call1      イベント発生時にコールする関数（省略可）
 * @param	string call2      追加スクリプト（省略可）
 * @param	int    max_width  情報ウィンドウの最大幅（省略時：200）
 * @param	int    ofst_x     情報ウィンドウのオフセット位置（X)（省略時：0）
 * @param	int    ofst_y     情報ウィンドウのオフセット位置（Y)（省略時：0）
 * @return	string 生成したスクリプト
 */
string pahooGeocode::makeMapLeaflet(
	string id, double longitude, double latitude, int zoom,
	string type, ppoints_t* items, size_t size, string call1, string call2,
	int max_width, int ofst_x, int ofst_y) {

	// 地点情報スクリプトの生成
	char lat[SIZE_BUFF + 1], lng[SIZE_BUFF + 1];
	char buff[SIZE_BUFF + 1];
	string icode = "";
	if (items != NULL) {
		string icon = "";
		string info = "";
		for (size_t i = 0; i < size; i++) {
			size_t i2;
			if ((items[i].icon) == "" && (i > 999))	break;	// 最大999箇所まで
			i2 = i;
			if (i2 > 26)	i2 = 26;
			string mark = {(char)(65 + i2)};
			if (items[i].icon == "") {
				icon = "https://www.google.com/mapfiles/marker" + mark + ".png";
			} else {
				icon = items[i].icon;
			}
			info = "";
			if (items[i].description != L"") {
				snprintf(buff, SIZE_BUFF, "', {maxWidth: %d, offset: [%d, %d] });", max_width, ofst_x, ofst_y);
				info = "marker_" + mark + ".bindPopup('" + _WS(items[i].description) + buff;
			}
			snprintf(lat, SIZE_BUFF, "%.5f", items[i].latitude);
			snprintf(lng, SIZE_BUFF, "%.5f", items[i].longitude);

			// アイコン・ラベル
			if (items[i].label != L"") {
				icode += (boost::format(R"(
					let icon_%1% =  new L.divIcon({
						html: '<span style="color:%2%; font-size:%3%px; font-weight:%4%; white-space:nowrap;">%5%</span>',
						iconSize: [0, 0],
						iconAnchor: [%3%, %3%],
					});
					let marker_%1% = new L.Marker([%6%, %7%], {icon: icon_%1%}).addTo(map);
					%8%
)")
% mark					// マーカー識別子
% items[i].label_color	// アイコン・ラベルの色
% items[i].label_size	// アイコン・ラベルのサイズ
% items[i].label_weight	// アイコン・ラベルの太さ
% _WS(items[i].label)	// アイコン・ラベル
% lat					// 緯度
% lng					// 経度
% info					// 情報
).str();

			// 通常アイコン
			} else {
				icode += (boost::format(R"(
					let icon_%1% =  new L.icon({
						iconUrl: '%2%',
						iconAnchor: [10, 10]	// 暫定
					});
					let marker_%1% = new L.Marker([%3%, %4%], {icon: icon_%1%}).addTo(map);
					%5%
)")
% mark			// マーカー識別子
% icon			// マーカーURL
% lat			// 緯度
% lng			// 経度
% info			// 情報
).str();
			}
		}
	}

	// 地図描画スクリプトの生成
	string script = (boost::format(R"(
%6%
<link rel="stylesheet" href="https://unpkg.com/leaflet@latest/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@latest/dist/leaflet.js"></script>
%7%
<script>
window.onload = function() {
	var map = L.map('%1%',{zoomControl:false});
	map.setView([%2%, %3%], %4%);
	L.control.scale({
		maxWidth: 250,
		position: 'bottomright',
		imperial: false
	}).addTo(map);
	L.control.zoom({position:'topleft'}).addTo(map);

	// 地理院地図：標準地図
	var GSISTD = new L.tileLayer(
		'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
		{
			attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
			minZoom: 0,
			maxZoom: 18,
			name: 'GSISTD'
		});
	// 地理院地図：淡色地図
	var GSIPALE = new L.tileLayer(
		'https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png',
		{
			attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
			minZoom: 2,
			maxZoom: 18,
			name: 'GSIPALE'
		});
	// 地理院地図：白地図
	var GSIBLANK = new L.tileLayer(
		'https://cyberjapandata.gsi.go.jp/xyz/blank/{z}/{x}/{y}.png',
		{
			attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
			minZoom: 5,
			maxZoom: 14,
			name: 'GSIBLANK'
		});
	// 地理院地図：写真
	var GSIPHOTO = new L.tileLayer(
		'https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg',
		{
			attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
			minZoom: 2,
			maxZoom: 18,
			name: 'GSIPHOTO'
		});
	// OpenStreetMap
	var OSM = new L.tileLayer(
		'https://tile.openstreetmap.jp/{z}/{x}/{y}.png',
		{
			attribution: "<a href='https://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors",
			minZoom: 0,
			maxZoom: 18,
			name: 'OSM'
		});
%8%

	// baseMapsオブジェクトにタイル設定
	var baseMaps = {
		"地理院地図" : GSISTD,
		"淡色地図" : GSIPALE,
		"白地図" : GSIBLANK,
		"写真地図" : GSIPHOTO,
		"オープンストリートマップ" : OSM
		%9%
	};

	// layersコントロールにbaseMapsオブジェクトを設定して地図に追加
	L.control.layers(baseMaps).addTo(map);
	%5%.addTo(map);

	// イベント追加
	map.on('moveend', getPointData);
	map.on('zoomend', getPointData);
	map.on('baselayerchange', getPointData);

	// イベント発生時の地図情報を取得・格納
	function getPointData() {
		var pos = map.getCenter();
		// 経度
		if (document.getElementById('longitude') != null) {
			document.getElementById('longitude').value = pos.lng;
		}
		// 緯度
		if (document.getElementById('latitude') != null) {
			document.getElementById('latitude').value = pos.lat;
		}
		// ズーム
		if (document.getElementById('zoom') != null) {
			document.getElementById('zoom').value = map.getZoom();
		}
		// タイプ
		if (document.getElementById('maptype') != null) {
			for (var k in baseMaps) {
				if (map.hasLayer(baseMaps[k])) {
					document.getElementById('maptype').value = baseMaps[k].options.name;
				}
			}
		}
		%11%
	}
	%10%
}
</script>
)")
% id					// 地図ID
% latitude				// 緯度
% longitude				// 経度
% zoom					// 地図拡大率
% type					// 地図タイプ
% this->GoogleMap1		// Googleマップ描画用スクリプトURL
% this->GoogleMap2		// Leaftet:Googleマップ・アドオンURL
% this->GoogleMap3		// Leaftet:Googleマップ用レイヤ
% this->GoogleMap4		// Leaftet:Googleマップ選択肢
% icode
% call1
).str();

	return script;
}

/*
 ** バージョンアップ履歴 =====================================================
 *
 * @version 2.0.0  2026/05/31 makeMapLeaflet() アイコン・ラベル追加
 * @version 1.9.1  2026/03/28 不要なデバッグ情報を抑制
 * @version 1.9.0  2025/03/16 HTTPステータス・エラーをキャッチアップ
 * @version 1.8.1  2025/02/23 setError() -- bug-fix
 * @version 1.8.0  2024/05/03 getMyPath()をapikey.appのgetMyPath()関数に変更
 * @version 1.7.0  2024/04/27 Edge対応に伴いデバッグ用コードを廃棄
 * @version 1.6.0  2023/07/02 getPointsGSI()メソッド追加
 * @version 1.5    2022/09/03 デバッグコード埋め込み
 * @version 1.4    2021/05/01 makeMapLeaflet() 引数追加
 * @version 1.3    2021/03/07 tokyo_wgs84(), wgs84_tokyo()メソッド追加
 * @version 1.2    2020/12/20 leafletスクリプトの参照URL変更
 * @version 1.1    2020/09/27 makeMapLeaflet()メソッド追加
 * @version 1.0    2020/09/23 初版
 */
