前回、Googleマップ上にfoursquareのチェックイン情報を表示するページを作りましたが、今回はそこに少し機能を追加してみました。
URL自体は前回といっしょで、こちらです。実装自体は、かなり前にやっていたんですが、地図上に右クリックメニューから、べニュー(venue)情報を検索する機能を付けてみました。
4sq map : http://labs.s-koichi.info/mashup/4sq/checkinmap.htmlまず、Googleマップ上に、右クリックメニューを出すのに手間取りました。当初、JQueryのContextMenuyプラグインを使おうと思ってたのですが、どうもGoogleマップ上に表示させようとうまくいかずに頓挫してしまいました。
ちょっと、反則なんですが、ContextMenuプラグインのソースを流用して、Googleマップのイベントに反応して、右クリックメニューを表示するクラスを別途作ることにしました。ソースは、こんな感じです。
Google Maps APIのカスタムOverlayViewとして右クリックメニューを表示させるような感じにしてみました。カスタムOverlayViewの使い方はこちらを参考にしました。
drawメソッドの実装部分に、JQueryのContextMenuプラグインのソースを流用しています。
/** * 右クリックメニュー用のクラス * * @param map GoogleMap APIのmapオブジェクト * @param latlng GoogleMap APIのLatLngオブジェクト * @param opt オプション * @param callback メニュークリック時のコールバック */ function MapContextMenu(map, latlng, opt, callback) { this.latlng = latlng; this.opt = opt; this.callback = callback; this.setMap(map); // Defaults if( this.opt.menu == undefined ) return false; if( this.opt.inSpeed == undefined ) this.opt.inSpeed = 150; if( this.opt.outSpeed == undefined ) this.opt.outSpeed = 75; // 0 needs to be -1 for expected results (no fade) if( this.opt.inSpeed == 0 ) this.opt.inSpeed = -1; if( this.opt.outSpeed == 0 ) this.opt.outSpeed = -1; $("#" + opt.menu).addClass('contextMenu'); } /** * google.maps.OverlayViewを継承します */ MapContextMenu.prototype = new google.maps.OverlayView(); /** * drawの実装 */ MapContextMenu.prototype.draw = function () { if(!this.div_) { this.div_ = document.getElementById(this.opt.menu); this.div_.style.position = "absolute"; var panes = this.getPanes(); var point = this.getProjection().fromLatLngToDivPixel( this.latlng); this.div_.style.left = point.x + 'px'; this.div_.style.top = point.y + 'px'; panes.overlayLayer.appendChild( this.div_ ); var menu = $('#' + this.opt.menu); $(menu).css({ top: point.y , left: point.x }).fadeIn(this.opt.inSpeed); // Hover events $(menu).find('A').mouseover( function() { $(menu).find('LI.hover').removeClass('hover'); $(this).parent().addClass('hover'); }).mouseout( function() { $(menu).find('LI.hover').removeClass('hover'); }); var parent = this; // When items are selected $(menu).find('A').unbind('click'); $(menu).find('LI:not(.disabled) A').click( function() { $(document).unbind('click').unbind('keypress'); $(".contextMenu").hide(); // Callback var latLng = parent.latlng; if(parent.callback) { parent.callback( this.hash, latLng ); } return false; }); // Hide bindings setTimeout( function() { // Delay for Mozilla $(document).click( function() { $(document).unbind('click').unbind('keypress'); $(menu).fadeOut(parent.opt.outSpeed); return false; }); }, 0); } } /** * showを実装 */ MapContextMenu.prototype.show = function () { $("#" + this.opt.menu).show(); } /** * hideを実装 */ MapContextMenu.prototype.hide = function () { $("#" + this.opt.menu).hide(); } /** * remove実装 */ MapContextMenu.prototype.remove = function() { $("#" + this.opt.menu).hide(); } /** * 位置情報を設定します。 */ MapContextMenu.prototype.setLatLng = function(latlng) { this.latlng = latlng; // projectionを取得 var projection = this.getProjection(); if(projection) { // 位置情報からピクセルに変換 var point = projection.fromLatLngToDivPixel( this.latlng); this.div_.style.left = point.x + 'px'; this.div_.style.top = point.y + 'px'; } }
このクラスを使って、Google Map APIの地図オブジェクトの右クリックイベントとクリックイベントに、メニューの表示・非表示の処理を割り当てます。(この処理は、初期化時に行っています。)
// 右クリックのイベントリスナー google.maps.event.addListener(map, 'rightclick', function(event) { lastRightClickLatLng = event.latLng; // コンテキストメニューがなかったら初期化 if(contextMenu == undefined) { contextMenu = new MapContextMenu(map, event.latLng, { menu: "mapMenu" }, onMenuClick); } // クリックした位置を設定する。 contextMenu.setLatLng( event.latLng ); // メニューを表示する。 contextMenu.show(); }); // 左クリックのイベントリスナー google.maps.event.addListener(map, 'click', function(event) { if(contextMenu) { // メニューを閉じる contextMenu.hide(); } });
右クリックメニューをクリックしたときのfunction
は、こんな感じです。なお、べニューキーワード検索の入力欄は、JQueryのjQuery Alert Dialogsプラグインを使っています。
/** * 右クリックメニューをクリックしたときの処理 */ function onMenuClick( hash, latlng ) { if(hash == "#searchvenue") { // べニュー検索 searchVenues(latlng); } else if(hash == "#searchvenuekeyword") { // べニューキーワード検索 jPrompt('べニュー検索キーワードを入力してください', '', 'キーワード入力', function(result) { if(result){ searchVenues(latlng, result); } }); } else if(hash == "#searchvenueclear") { // べニュー表示クリア clearVenueMarkers(); } else if(hash == "#centermap") { // ここを中心にする centerMap(latlng); } }
右クリックメニューからの、動作をするメソッドがこちらです。べニュー検索部分が主です。
// proxyのURL var baseurl = "http://labs.s-koichi.info/oauthproxy/4sqproxy.php"; // google mapの地図オブジェクト var map; // 地図のオプション情報 var mapOptions; // マーカーの情報 var markers = {}; // ログインユーザーの情報 var owner; // べニュー情報 var venues = {}; // var contextMenu; // var lastRightClickLatLng; // ...途中省略 /** * べニュー検索 */ function searchVenues(latlng, keyword) { // 表示中のマーカーを消します。 clearVenueMarkers(); if(latlng == undefined) { // 最後に右クリックした場所を使います。 latlng = lastRightClickLatLng; } var venuesearch = baseurl; // 認証時の戻り先 venuesearch += "?pushback=searchVenues"; // べニュー検索 venuesearch += "&action=venues"; // 位置情報指定 venuesearch += "&geolat=" + latlng.lat() + "&geolong=" + latlng.lng(); if(keyword) { // キーワード検索時 venuesearch += "&q=" + keyword; } // 30件まで表示します。 venuesearch += "&l=30&callback={callback}"; $.getJSONP(venuesearch, function (result) { if(result.js == undefined) { // 中心マークの表示 createCenterMarker(latlng); $( result.groups ).each(function() { // 今のところ、どれも同じ表示にしてます。 // トレンディスポットと、お気に入りスポット if(this.type == "Trending Now" || this.type == "My Favorites") { writeVenues(this.venues); } else if(this.type == "Nearby") { // 近くのスポット writeVenues(this.venues); } else { // それ以外?! writeVenues(this.venues); } }); } else { eval(result.js); } }); } /** * べニュー検索の中央マーカー(+マーク)の表示 */ function createCenterMarker(latlng) { var iconurl = "http://labs.s-koichi.info/mashup/4sq/images/cross.png"; var latlng = new google.maps.LatLng(latlng.lat(), latlng.lng()); var icon = new google.maps.MarkerImage(iconurl, new google.maps.Size( 64, 64 ), new google.maps.Point(0,0), new google.maps.Point(14,18)); var markerOptions = { position : latlng, map : map, icon: icon }; var marker = new google.maps.Marker(markerOptions); venues["center"] = marker; var latlng = new google.maps.LatLng(latlng.lat(), latlng.lng()); mapOptions.center = latlng; mapOptions.zoom = map.getZoom(); map.setOptions(mapOptions); } /** * べニューのマーカーを作成する */ function writeVenues(venues) { $( venues ).each(function() { createVenueMarker(this); }); } /** * べニューのマーカーを作成する */ function createVenueMarker(venue) { // カテゴリーを割り当ていないときのアイコン var iconurl = "http://foursquare.com/img/categories/question.png"; if(venue.primarycategory) { iconurl = venue.primarycategory.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 , icon: icon }; // べニューマーカーの作成 var marker = new google.maps.Marker(markerOptions); // 情報ウインドウに表示する内容を作成 var contentString = '<div id="venue_' + venue.id + '" style="width:95%;">'; contentString += '<div class="venueinfo"><div class="venueicon"><img src="' + iconurl + '" /></div>'; contentString += '<div class="venuename"><a href="http://foursquare.com/venue/' + venue.id + '" target="4sq">' + venue.name + '</a></div></div>'; contentString += '<div class="addressinfo">'; if(venue.address) { contentString += '<div class="address">' + venue.address + '</div>'; } if(venue.city && venue.state) { contentString += '<div class="city">' + venue.city + ', ' + venue.state; if(venue.zip) { contentString += ', ' + venue.zip; } contentString += '</div>'; } contentString += '</div>'; if(venue.stats && venue.stats.herenow && venue.stats.herenow > 0) { contentString += '<div class="herenow">' + venue.stats.herenow + '人がいます</div>'; } contentString += '<div class="distance">' + venue.distance + 'm</div>'; contentString += '</div>'; // 情報ウインドウ作成 var infowindow = new google.maps.InfoWindow({ content: contentString }); // マーカークリック時に情報ウインドウ表示 google.maps.event.addListener(marker, 'click', function(event) { infowindow.open(map,marker); }); // ハッシュに保持 venues[venue.id] = marker; } /** * べニューのマーカーを消去する */ function clearVenueMarkers() { for (var key in venues) { var marker = venues[key]; if(marker) { marker.setMap(null); } delete venues[key]; } } /** * 指定した場所を地図の中央にする */ function centerMap(latlng) { var latlng = new google.maps.LatLng(latlng.lat(), latlng.lng()); mapOptions.center = latlng; mapOptions.zoom = map.getZoom(); map.setOptions(mapOptions); }
最後に、秋葉原駅付近でべニュー検索をしてみたときのスクリーンショットです。なお、前回から、ページデザインも少しいじってます。