ブログパーツ


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


ここしばらく、foursquare APIの勉強がてら、Google Maps APIと組み合わせごにょごにょやってました。やはり、foursquareは地図と組み合わせると楽しいですね。

とりあえず、今後もう少しデザイン的な部分を見直したり、機能を増やしていってちょっとしたサービスに出来たらなと思っています。

サイト名は仮ですが。
4sq map : http://labs.s-koichi.info/mashup/4sq/checkinmap.html

こんな感じで、フレンドのチェックイン情報がGoogleマップの地図上に表示されます。

4sq map

4sq map

まず、foursquare APIの参考にしたのがこちらの日本語訳です。


まだまだ使ってないAPIがあるので、いろいろ試してみたいですね。あと、登録系のAPIは今回つかってないです。
ちなみに、foursquareのOAuthアプリ申請はこちらから出来ます。ちょっと、最初探すのに手間取ってしまいました。

あと、Google Maps APIはこちらの日本語訳と本家のドキュメントを参照しました。

foursquqreのAPIを使っている部分は、phpでOAuth認証をさせています。先日のiTunes2Twitterで使ったクラスをfoursqueare用のサブクラスを作っています。スーパークラスは、先日のエントリーと同じです。

<?php
require_once 'OAuthProxy.class.php';

/**
 * fourSquare用のクラス
 *
 */
class FourSquareOAuthProxy extends OAuthProxy
{
	protected $request_token_url = 'http://foursquare.com/oauth/request_token';
	protected $access_token_url = 'http://foursquare.com/oauth/access_token';
	protected $oauth_url = 'http://foursquare.com/oauth/authorize';
	
	public function requestMethod($action, $param, $callback)
	{
		try {
			$this->init();
			if($action == "friends") {
				
				$response = $this->consumer->sendRequest("http://api.foursquare.com/v1/friends.json", 
						array(), "GET");
				
				if($response->getStatus() != 200) {
					$this->error(new Exception($response->getBody(), $response->getStatus()), $callback);
				}
				else {
					$this->printResult($response->getBody(), $callback);
				}
			}
			else if($action == "user") {
				$response = $this->consumer->sendRequest("http://api.foursquare.com/v1/user.json", 
						array('uid' => $param["id"]), "GET");
				$this->printResult($response->getBody(), $callback);
			}
			else if($action == "checkins") {
				$response = $this->consumer->sendRequest("http://api.foursquare.com/v1/checkins.json", 
						$param, "GET");
				$this->printResult($response->getBody(), $callback);
			}
			else if($action == "venues") {
				$response = $this->consumer->sendRequest("http://api.foursquare.com/v1/venues.json", 
						$param, "GET");
				$this->printResult($response->getBody(), $callback);
			}
			else if($action == "tips") {
				$response = $this->consumer->sendRequest("http://api.foursquare.com/v1/tips.json", 
						$param, "GET");
				$this->printResult($response->getBody(), $callback);
			}
			else if($action == "findfriends_bytwitter") {
				$response = $this->consumer->sendRequest("http://api.foursquare.com/v1/findfriends/bytwitter.json", 
						$param, "GET");
				$this->printResult($response->getBody(), $callback);
			}
		}
		catch (Exception $e) {
			$this->error($e, $callback);
		}
	}

	private function init()
	{
		//アクセストークンを設定する。
		$this->consumer->setToken($_SESSION['access_token']);
		$this->consumer->setTokenSecret($_SESSION['access_token_secret']);
	}

	protected function checkAuth($e)
	{
		$msg = $e->getMessage();
		return ($msg == "unauthorized");
	}
}
?>

このクラスをつかったproxyのソースです。
4sqproxy.php

<?php
	require_once 'FourSquareOAuthProxy.php';
	
	$consumer_key = 'あなたのCONSUMER_KEY';
	$consumer_secret = 'あなたのCONSUMER_SECRET';

	session_name('4sq_proxy');
	session_start();
	if(isset($_REQUEST['pushback'])) {
		$_SESSION['pushback'] = $_REQUEST['pushback'];
	}
	
	$callback = NULL;
	if(isset($_GET['callback'])) {
		$callback = $_GET['callback'];
	}

	$oauthproxy = new FourSquareOAuthProxy($consumer_key, $consumer_secret, 
							'http://labs.s-koichi.info/oauthproxy/4sqproxy.php');
	
	if(isset($_GET['oauth_verifier'])) { // 認証後に、callback urlできたとき
		$oauthproxy->receiveCallback($_GET['oauth_verifier'], $callback);
	}
	else if(isset($_SESSION['access_token']) &&
		isset($_SESSION['access_token_secret'])) { // access_tokenがもうあるとき。

		$action = NULL;
		if(isset($_REQUEST['action'])) {
			$action = $_REQUEST['action'];
		}
		
		$param;
		foreach ($_REQUEST as $key => $value) {
			$param[$key] = $value;
		}
		
		$oauthproxy->requestMethod($action, $param, $callback);
	}
	else { // これから認証。
		unset($_SESSION['access_token']);
		unset($_SESSION['access_token_secret']);
		$oauthproxy->requestAuth($callback);
	}
?>

このproxyを使ってjavascript経由で、いろいろな情報を取得してます。主なところだけ載せると、こんな感じです。

// proxyのURL
var baseurl = "http://labs.s-koichi.info/oauthproxy/4sqproxy.php";
// google mapの地図オブジェクト
var map;
// 地図のオプション情報
var mapOptions;
// マーカーの情報
var markers = {};
// ログインユーザーの情報
var owner;

/**
 * チェックイン情報を取得します。
 */
function refresh()
{
	// 表示中のマーカーの数を残しておく
	var len = getLength(markers);
	// マーカーをクリアします。
	clearMarkers();
	
	// 画面表示クリア
	$("#result").empty();
	
	// まず自分のユーザー情報を取得します。
	var myurl = baseurl;
	myurl += "?action=user&pushback=refresh&callback={callback}";
	
	$.getJSONP(myurl, 
		function (result) {
			if(result.js == undefined) { // 認証済みの場合
				// 情報が取得出来てる場合
				if(result.user && result.user.checkin) {
					owner = result.user;
					
					var venue = result.user.checkin.venue;
				
					// 最初だけ地図の中心を変更します。
					if(len == 0) {
						var latlng = new google.maps.LatLng(venue.geolat, venue.geolong);
						mapOptions.center = latlng;
						
						map.setOptions(mapOptions);
					}
					// マーカーを作成します。
					createMarker(result.user, venue, result.user.checkin,
						"http://chart.apis.google.com/chart?cht=mm&chs=32x32&chco=FFFFFF,0000FF,000000&ext=.png");
				}
				
				// フレンドのチェックイン情報を取得します。
				getCheckins();
			}
			else {
				// OAuth認証します。
				eval(result.js);
			}
		});
}

/**
 * 連想配列の要素数を取得します。
 */
function getLength(hash)
{
	var len = 0;
	for (var key in hash) {
		len++;
	}
	
	return len;
}

/**
 * フレンドのチェックイン情報を取得します。
 */
function getCheckins()
{
	var url = baseurl;
	
	// url作成
	var ownervenue = owner.checkin.venue;
	url += "?pushback=refresh&action=checkins&geolat=" + ownervenue.geolat + "&geolong=" + ownervenue.geolong + "&callback={callback}";

	$.getJSONP(url, function(result) {
			if(result.js == undefined) {
				if(result.checkins) {
					// チェックイン情報があったら
					$( result.checkins ).each(
						function() {
							var user;
							var venue;
							
							if(this.user) {
								user = this.user;
							}
							if(this.venue) { // 公開チェックインの場合
							
								venue = this.venue;
								// 日時情報の整形
								var createat =  getDateString(this.created);
								var markerurl;
								var now = new Date();
								// 時間の比較用
								var diffdate = compareDate(now , createat);
								// 経過時間によってマーカーの色を変えます。
								if(diffdate >= 1) { // 1日以上経過している
									markerurl = "http://chart.apis.google.com/chart?cht=mm&chs=32x32&chco=FFFFFF,FFCCCC,000000&ext=.png";
								}
								else if(diffdate <= (3600000/86400000)) { // 1時間以内にチェックインしている
									markerurl = "http://chart.apis.google.com/chart?cht=mm&chs=32x32&chco=FFFFFF,FF0000,000000&ext=.png";
								}
								else {
									markerurl = "http://chart.apis.google.com/chart?cht=mm&chs=32x32&chco=FFFFFF,FF6666,000000&ext=.png";
								}
								// マーカー作成
								createMarker(user, venue, this, markerurl);
							}
							
							// 詳細情報表示
							writeCheckinDetail(user, venue, this);
						});
				}
			}
			else {
				// OAuth認証 or エラー時用
				eval(result.js);
			}
		});
}

/**
 * マーカーを作成します
 */
function createMarker(user, venue, checkin, iconurl)
{
	// マーカー情報の生成。
	var latlng = new google.maps.LatLng(venue.geolat, venue.geolong);
	var icon = new google.maps.MarkerImage(iconurl,
			 new google.maps.Size( 32, 32 ),
			 new google.maps.Point(0,0),
			 new google.maps.Point(14,18));
	var markerOptions = {
		position : latlng,
		map : map,
		title : venue.name + " (" + user.firstname + ")",
		icon: icon
	};
	// マーカー生成
	var marker = new google.maps.Marker(markerOptions);
	
	
	var createat = getDateString(checkin.created);
	// 日時の整形
	var createdate = getDisplayDate(createat);
	
	// 情報ウインドウ用のhtml作成
	var contentString = '<div id="user_' + user.id + '">' +
			'<div class="user>' +
			'<div class="phote"><img src="' + user.photo + '" /></div>';
	contentString += '<div class="userinfo">name: ';
	if(user.twitter != undefined) {
		contentString += '<a href="http://foursquare.com/user/' + user.twitter + '" target="4sq">' + user.firstname + '</a>';
	}
	else {
		contentString += user.firstname;
	}
	contentString += '</div>';
	
	contentString += '<div >last checkin: ' + createdate + '</div>';
	contentString += '</div>';
	contentString += '<div class="venue">';
	contentString += '<div class="venuename"><a href="http://foursquare.com/venue/' + venue.id + '" target="4sq">';
	contentString += venue.name + '</a></div>';
	contentString += '<div class="address">' + venue.address + ' ' + venue.city + '</div>';
	contentString += '</div></div>';
	
	// 情報ウインドウ作成
	var infowindow = new google.maps.InfoWindow({
		content: contentString
	});
	
	// マーカーがクリックしたら表示されるようにする。
	google.maps.event.addListener(marker, 'click', function(event) {
			infowindow.open(map,marker);
		});
	
	// マーカーを連想配列に収納
	markers[checkin.id] = marker;
}

/**
 * 全てのマーカーをクリアする。
 */
function clearMarkers()
{
	for (var key in markers) {
		var marker = markers[key];
		if(marker) {
			marker.setMap(null);
		}
		
		delete markers[key];
	}
}

/**
 * 情報ウインドウを表示する。
 */
function showInfo(checkinId)
{
	var marker = markers[checkinId];
	if(marker) {
		marker.setZIndex = 99999;
		google.maps.event.trigger(marker, 'click');
	}
}

/**
 * 詳細情報を表示する。
 */ 
function writeCheckinDetail(user, venue, checkin)
{
	// htmlの整形
	var detail = '<div id="checkin_' + checkin.id + '" class="checkin">';
	detail += '<div class="checkininfo">';
	
	if(venue) {
		detail += '<a href="#" id="checkin_link_' + checkin.id + '" ';
		if(checkin.shout) {
			detail += 'title="' + checkin.shout + '" ';
		}
		detail += '>';
	}
	else {
		detail += '<span ';
		if(checkin.shout) {
			detail += 'title="' + checkin.shout + '" ';
		}
		detail += '>';
	}
	detail += checkin.display;
	
	if(venue) {
		detail+= '</a>';
	}
	else {
		detail+= '</span>';
	}

	var createat = getDateString(checkin.created);
	var createdate = getDisplayDate(createat);
	
	detail += ' (' + createdate + ')</div>';
	
	// 整形したhtmlを表示する。
	$("#result").append(detail);
	
	// クリックしたら、情報ウインドウが表示されるようにする。
	$("#checkin_link_" + checkin.id).click(
		function(event) {
			showInfo(checkin.id);
		});
			
}

この他に、右クリックメニューからべニューを検索するようにもしています。べニュー(venue)検索すると、かなり位置がずれているのがあって面白いですね。
この後は、Tips情報の表示とか、地図上からべニューを登録したりとか出来る用にしたいなと思ってます。

, , ,

  1. #1 by Hattori - 2010年8月28日 at 15:11

    上記コードをまねしてやってみましたが、うまく行きませんでした。
    thrown in /usr/share/php5-pear/HTTP/OAuth/Consumer.php on line 168
    OAUTHがうまく行かないようです。
    そもそもhttp://foursquare.com/apps/でアプリケーション登録したのですが、
    KEYやSECRETも得られませんでした。
    なにかヒントをいただければと思います。

  2. #2 by Koichi - 2010年8月28日 at 15:34

    エラー自体については、わかりませんが、自分でOAuthアプリケーション登録したものは、
    http://foursquare.com/oauth/
    より確認できます。
    KEYとSECRETもここで参照できます。

  3. #3 by Hattori - 2010年8月28日 at 15:55

    「自分でOAuthアプリケーション登録したものは、
    http://foursquare.com/oauth/
    より確認できます。
    KEYとSECRETもここで参照できます。」

    ありがとうございます。

    確認するには、Click here to register a new consumer
    を実施する必要があるのでしょうか?

  4. #4 by Koichi - 2010年8月28日 at 23:15

    それは、新しいアプリを登録するリンクです。
    何も登録されていないんじゃないですかね?
    登録されていれば、

    Registered Consumers

    の下に登録されているアプリの一覧が出ますから。

  5. #5 by Hattori - 2010年8月29日 at 17:13

    http://foursquare.com/apps/add
    上記のページからアプリを登録しました。(カテゴリーはWebsitesを選択)
    それでもやはり、http://foursquare.com/oauth/ こちらには表示されませんでした。

  6. #6 by Koichi - 2010年8月29日 at 18:08

    どうやら、Consumer Keyの申請は、
    http://foursquare.com/oauth/register
    でいいようです。

    http://foursquare.com/apps/add
    はアプリの申請用なので、とりあえず使う分には大丈夫そうです。
    あとで、本文のリンクも変えておきます。

  7. #7 by Hattori - 2010年8月30日 at 12:23

    ありがとうございます。

    http://foursquare.com/oauth/register

    こちらから登録しましたが#1のエラーのままでした。

    OAuthProxy.class.php の
    $this->consumer->getRequestToken($this->request_token_url, $this->callback_url);
    ここでエラーとなるようです。

  8. #8 by Hattori - 2010年8月31日 at 12:25

    上記のエラーについて報告です。
    独自に構築したサーバの問題かもしれません。
    さくらサーバでサンプロを動かしたところ、動作しました。

    とても参考になるサンプルですので、
    続編も期待しています。

  9. #9 by Koichi - 2010年9月3日 at 10:45

    遅くなりましたが。
    無事に動いてよかったです。

(公開されません)