ブログパーツ


foursquare API+Google Mapでマッシュアップ その4


Googleマップ上foursquareのチェックイン情報を表示するページを作ってみましたが、今回は機能追加したvenue追加機能用の情報を検索する部分を解説してみたいと思います。

4sq map : http://labs.s-koichi.info/4sqmap/

venueの項目入力を補助するために、ぐるなびホットペッパーホットペッパービューティ楽天トラベル施設検索の各APIから店舗名と住所を取得する機能を用意しましたが、各APIごとに取れる情報の形式が違ったりXMLで取得できなかったりで、クロスサイトスクリプティングの問題があったりってのがあったので、出力形式を統一化するために、PHPでラッパークラスを作って必要な情報だけを取得するようにしました。さらに、全てjsonp形式で取得出来るようにしています。

まずは、出力用に2つのクラスを用意しています。GeoSearchResultSetと、GetSearchResultです。

GetSearchResultSetは、検索結果の集合と検索時の中心位置情報などを表しているクラスで、GetSearchResultは、店舗情報1件づつを表すクラスで、店舗名や住所、位置情報を収納しています。

GeoSearchResultSetクラス

<?php
/**
 * 検索結果のセットを表すクラス
 *
 * @author $Author:$
 * @version $Id:$
 *
 */
class GeoSearchResultSet
{
	/**
	 * 検索結果識別子
	 */
	protected $name = "";
	
	public function setName($name)
	{
		$this->name = $name;
	}
	public function getName()
	{
		return $this->name;
	}

	/**
     * 計測中心緯度
     */
	public $lat;
	
	/**
	 * 計測中心経度
	 */
	public $lng;
	
	/**
	 * 総件数
	 */
	public $count = 0; 

	/**
	 * 検索結果
	 */ 
	public $results = array();

}
?>

GeoSearchResultクラス

<?php

/**
 * 場所の情報を表すクラスです。
 *
 * @author $Author:$
 * @version $Id:$
 *
 */
class GeoSearchResult
{
	/**
	 * 一意のID
	 */
	public $id;
	
	/**
	 * 店名/施設名
	 */
	public $name;
	
	/**
	 * 住所
	 */
	public $address;
	
	/**
	 * 郵便番号
	 */
	public $zip;
	
	/**
	 * 電話番号
	 */ 
	public $tel;
	
	/**
	 * 緯度
     */
	public $lat;
	
	/**
	 * 経度
	 */
	public $lng;
	
	/**
	 * 店舗情報URL
	 */
	public $url;
}
?>

GetSearchResultSetの形式で、場所情報を検索して返すクラスのベースクラスが、GeoSearchクラスです。このクラスから、それぞれぐるなびやHotPaperの情報を検索するクラスを派生させています。

GeoSearchクラス

<?php
require_once 'Cache/Lite.php';

/**
 * 場所情報を検索するクラスのベースクラスです。
 *
 * @author $Author:$
 * @version $Id:$
 *
 */
class GeoSearch {
	/**
	 * キャッシュを使用するかどうか。
	 */
	protected $useCache = true;
	/**
	 * キャッシュを使用するかどうかを設定する。
	 */
	public function setUseCache($flag) {
		$this->useCache = $flag;
	}

	/**
	 * キャッシュ用Prefix
	 */
	protected $cachePrefix = "";
	/**
	 * キャッシュ用Prefixを設定する。
	 */
	public function setCachePrefix($prefix) {
		$this->cachePrefix = $prefix;
	}
	public function getCachePrefix() {
		return $this->cachePrefix;
	}

	/**
	 * キャッシュのディレクトリ
	 */
	protected $cacheDir = "/tmp/cache";
	
	/**
	 * 検索エンジン名
	 */
	protected $name;
	/**
	 * 検索エンジン名
	 */
	public function getName()
	{
		return $this->name;
	}
	
	// キャッシュキー
	protected $cacheKey;
	
	// キャッシュオブジェクト
	protected $cache;
	
	// キャッシュされたデータ
	protected $data;
	
	// 検索キーワード
	protected $keyword;
	
	// 緯度
	protected $geolat;
	
	// 経度
	protected $getlong;
	
	// 検索件数
	protected $count;
	
	// APIからの検索結果の生データ
	protected $rawdata;
	
	
	/**
	 * コンストラクタ
	 */
	function __construct($cacheDir = NULL)
	{
		if($cacheDir) {
			$this->cacheDir = $cacheDir;
		}
	}

	/**
	 * 検索を行なう。
	 */
	public function search($keyword, $geolat, $geolong, $count, $render) {
		
		$this->keyword = $keyword;
		$this->geolat = $geolat;
		$this->geolong = $geolong;
		$this->count = $count;
		
		// キャッシュを使用するかどうか
		if($this->useCache) {
			// キャッシュの設定
			$options = array(
				'cacheDir' => $this->cacheDir,
				'lifeTime' => 600
			);

			// キャッシュオブジェクト生成
			$this->cache = new Cache_Lite($options);
			if(!$this->cache) {
				print ("キャッシュが生成できていない\n");
				return null;
			}
			
			// キャッシュ用のキーを生成
			$keyword = md5($query);
			$this->cacheKey = $keyword . "_" . get_class($this) . "_" . $offset . "_" . $count. "_". $this->cachePrefix;
			$this->data = $this->cache->get($this->cacheKey);
		}
		
		if(!$this->data) {
			// キャッシュされてなかったら、検索する。
			$this->data = $this->searchCore($keyword, $geolat, $geolong, $count);
			
			// キャッシュを行なう場合は、キャッシュに結果を収納する。
			if($this->useCache && $this->cache) {
				$this->cacheData();
			}
		}
		
		$this->rawdata = $this->data;
		// 検索結果を整形して返す。
		$resultSet = $this->getResult($this->data);
		$resultSet->setName($this->name);
		$resultSet->lat = (float)$geolat;
		$resultSet->lng = (float)$geolong;
		
		if($render == 'xml') {
			return $resultSet;
		}
		else {
			return json_encode($resultSet);
		}
	}
	
	/**
	 * 検索処理
	 */
	protected function searchCore($keyword, $getlat, $getlong, $count)
	{
	}
	
	/**
	 * 検索結果をキャッシュに設定する。
	 */
	protected function cacheData()
	{
		$this->cache->save($this->data, $this->cacheKey);
	}
	
	/**
	 * 検索結果を整形して返す
	 *
	 * @param data
	 */ 
	protected function getResult($data)
	{
	}
	
	/**
	 * APIからデータをそのまま返す。
	 *
	 * @param render
	 */
	public function getRawResult($render)
	{
		if($render == 'xml') {
			return $this->data;
		}
		else if($render == 'json') {
			$json = json_encode(simplexml_load_string($this->data));
			
			return $json;
		}
	}
	
}
?>

各APIの検索は、GeoSearchクラスを継承して作成してます。例えば、ぐるなびの検索だとこんな感じです。

GNaviSearchクラス

<?php
require_once 'GeoSearch.php';
require_once 'GeoSearchResultSet.php';
require_once 'GeoSearchResult.php';

/**
 *
 * @author $Author:$
 * @version $Id:$
 *
 */
class GNaviSearch extends GeoSearch {
	/**
	 * ぐるなびのキャッシュPrefix
	 */
	protected $cachePrefix = "gnavi";

	/**
	 * ぐるなび
	 */
	protected $name = "GNavi";
	
	// キャッシュしない
	protected $useCache = false;

	/**
	 * 検索処理
	 */
	protected function searchCore($keyword, $geolat, $geolong, $count)
	{
		// WebAPIで検索する
		$url ='http://api.gnavi.co.jp/ver1/RestSearchAPI/?keyid=yourkey';
		if($keyword) {
			$url .= '&name=' . $keyword;
		}
		$url .= '&input_coordinates_mode=2&range=2&coordinates_mode=2';
		$url .= '&latitude=' . $geolat;
		$url .= '&longitude=' . $geolong;
		$url .= '&hit_per_page=' . $count;

		// DOMDocumentを作る
		$html = file_get_contents($url);
		
		return $html;
	}
	
	
	/**
	 * 検索結果を整形して返す
	 *
	 * @param data
	 */ 
	protected function getResult($data)
	{
		$resultSet = new GeoSearchResultSet(); // 検索結果用のGeoSearchResultSetクラスを生成
		
		$xml = simplexml_load_string($data); // SimpleXMLに変換する。

		if($xml->rest) { // データがあったら。
			// ヒット件数をセット
			$resultSet->count = (int)$xml->total_hit_count;
			
			// データを GeoSearchResultクラスにセットしていきます。
			foreach($xml->rest as $nodelist) {
				$result = new GeoSearchResult(); // GeoSearchResultクラスを生成する。
				
				$result->id = (string)$nodelist->id; // 一意のID
				$result->name = (string)$nodelist->name; // 店舗名
				$result->tel = (string)$nodelist->tel; // 電話番号
				$regex = array();
				if( mbereg("(\d{3})\-(\d{4})\s(.*)$", $nodelist->address, $regex)) { // 住所に郵便番号が入ってる場合
					$result->zip = $regex[1] . "-" . $regex[2]; // 郵便番号
					$result->address = $regex[3]; // 住所
				}
				
				$result->url = (string)$nodelist->url; // 店舗のURL
				$result->lat = (float)$nodelist->latitude; // 店舗の位置情報・緯度
				$result->lng= (float)$nodelist->longitude; // 店舗の位置情報・経度
				
				$resultSet->results[] =  $result; // 配列に追加する。
			}
		}
		
		return $resultSet;
	}
}
?>

今回は、Ajaxで、JQueryから扱う為に、これらをラッピングしたAPIを用意しています。基本的に、クラスの数を増やせばいくらでも拡張できる感じにしてあります。
戻り値の形式がいっしょなので、javascript側の処理は全部いっしょに出来ます。

geosearch.php

<?php
/**
 * 位置情報から店舗名・スポット名を検索する
 *
 * $Id: $
 */
 
if(isset($_GET['vender'])) { // venderから検索するAPIを振り分ける
	if($_GET['vender'] == "gnavi") { // ぐるなび
		require_once('GeoSearch/GNaviSearch.php');
		$geosearch = new GNaviSearch('/home/hogehoge/tmp/cache');
	}
	else if($_GET['vender'] == "hotpaper") { // ホットペッパー
		require_once('GeoSearch/HotPaperSearch.php');
		$geosearch = new HotPaperSearch('/home/hogehoge/tmp/cache');
	}
	else if($_GET['vender'] == "hotpaperbeauty") { // ホットペッパービューティ
		require_once('GeoSearch/HotBeautySearch.php');
		$geosearch = new HotBeautySearch('/home/hogehoge/tmp/cache');
	}
	else if($_GET['vender'] == "rakutentravel") { // 楽天トラベル
		require_once('GeoSearch/RakutenTravelSearch.php');
		$geosearch = new RakutenTravelSearch('/home/hogehoge/tmp/cache');
	}
	else if($_GET['vender'] == "mapion") { // Mapionローカル検索
		require_once('GeoSearch/MapionLocalSearch.php');
		$geosearch = new MapionLocalSearch('/home/hogehoge/tmp/cache');
	}
	else if($_GET['vender'] == "calil") { // カーリル図書館検索
		require_once('GeoSearch/CalilLibrarySearch.php');
		$geosearch = new CalilLibrarySearch('/home/hogehoge/tmp/cache');
	}
	else if($_GET['vender'] == "tabelog") { // 食べログ
		require_once('GeoSearch/TabelogSearch.php');
		$geosearch = new TabelogSearch('/home/hogehoge/tmp/cache');
	}
}

// その他のパラメータ処理
$keyword = $_GET['keyword']; // 検索キー
$geolat = $_GET['geolat']; // 緯度
$geolong = $_GET['geolong']; // 経度
$render = "";
if(isset($_GET['_render'])) { // render
	$render = $_GET['_render'];
}
else if(isset($_GET['format'])) { // render
	$render = $_GET['format'];
}

$callback = ""; // Jsonp用のコールバック
if(isset($_GET['_callback'])) { // callback付きかどうか
	$callback = $_GET['_callback'];
}
else if(isset($_GET['callback'])) { // callback付きかどうか
	$callback = $_GET['callback'];
}

// renderが空だったらjsonで返す
if($render == "") {
	$render = 'json';
}

// 実際の検索を行う。
$res = $geosearch->search($keyword, $geolat, $geolong, 30, $render);

if($render == 'xml') { // XMLで返す
	header('Content-type: text/xml; charset=utf-8');
	print $res;
}
else if($render == "json") { // JSONで返す
	if($callback) { // JSONP
		header('Content-type: text/javascript; charset=utf-8');
		print $callback . '(' . $res . ');';
	}
	else { // JSON
		header('Content-type: text/json; charset=utf-8');
		print $res;
	}
}
?>

javasciptからの呼び出し例も載せておきます。大体こんな感じというところです。

/**
 * 位置情報から検索する
 *
 * @param vender
 * @param lat 緯度
 * @param lng 経度
 */
function geoSearch(vender, lat, lng)
{
	var url = "geosearch?vender=" + vender;
	url += "&geolat=" + lat + "&geolong=" + lng + "&format=json&callback={callback}";

	$("#result").empty();
	$("#result").append('<div class="loading"><img src="images/loading.gif" /> 読み込み中です...</div>');
	
	$.getJSONP(url, function(result) {
		shops = [];
		if(result.count > 0) {
			shops = result.results;
			
			writeShop(shops, vender);
		}
		
		if(shops != undefined) {
			if(shops.length == 0) {
				$("#Pagination").empty();
				$("#result").empty();
			}
			else {
				var pageropts = initPaginationOpts;
				pageropts.callback = pagenationShopsCallback;
				$("#Pagination").pagination(shops.length, pageropts);
			}
		}
		else {
			$("#Pagination").empty();
			$("#result").empty();
		}
	
		var lat =  map.getCenter().lat(); 
		var lng = map.getCenter().lng();
		var zoom = map.getZoom();
		
		location.hash = "#!/" + round(lat, 8) + "," + round(lng, 8) + "/" + zoom + "/" + vender
			
		setLinkUrl();
	});
}

とりあえず、今回はこんなところで終わります。

にほんブログ村 IT技術ブログ プログラム・プログラマーへ
にほんブログ村

, , ,

  1. コメントはありません
(公開されません)


  1. トラックバックがありません。