GPS機能とTwitterを連携させて指定エリア周辺のつぶやきを表示する

TwitterのSearch APIを利用して「池袋駅から半径3km」のつぶやきを自動で連続取得・表示します。

用意するファイル
  • index.html
  • style.css
  • js/modernizr.js
  • js/geo.js
ファイルの内容

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<title>GPS機能とTwitterの連携</title>
<link rel="stylesheet" href="style.css">
<style>

body {
	background: #75B98C;
}
header {
	height: 40px;
	background:#508368;
	display: block;
}
h1 {
	color: #fff;
	font-size: 1.2em;
	font-family:Verdana, Geneva, sans-serif;
	font-weight: bold;
	text-align: center;
	padding: 6px 0 0 10px;
}

/* ツイッターモジュール */
#twitterModule {
	width: 300px;
	height: 300px;
	overflow: hidden;
	border: 10px solid #75B98C;
	position: absolute;
	left: 0;
	top: 40px;
	background: #fff;
}
#twitterModule .title {
	position: relative;
	padding:0 2px 6px 6px;
	background: #75B98C;
	color: #F2F2F2;
	font-size: 18px;

}

/* ツイートブロック */
#area-tweets p{
	padding: 1em;
	font-size:14px;
	line-height:1.2em;
}
#area-tweets p a{
	color: #3774ed;
	text-decoration:none;
}
#area-tweets p a.reply {
	color: #666;


}

#area-tweets .user,
#area-tweets .date {
	padding-right: 1em;
	font-style: normal;
	font-weight:noral;
	color: #999;
	text-decoration: none;
}

/* 住所入力エリア */
#twitterModule .area-input{
	position: absolute;
	bottom: 0;
	width: 100%;
	padding-top: 4px;
	background: #75B98C;
}
#twitterModule .area-input input {
	padding: 5px 12px 2px;
	width: 275px;
	height: 16px;
	font-size: 12px;
	border: 0;
	border-radius: 12px;
	-moz-border-radius: 12px;
	-webkit-border-radius:12px;
}

#area-tweets .address {
	padding: 1em;
	color: #279572;
	font-weight: bold;
}

</style>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script src="js/modernizr.js"></script>
<script src="js/geo.js"></script>
</head>
<body>
<div id="container">
<header>
<h1>Twitter×Geocode</h1>
</header>
<div id="wrapper">
<div id="twitterModule">
<p class="title">Twitter Module</p>
<div id="area-tweets">
</div>
<form class="area-input"><input type="text" value=""></form>
</div>
</div>
<footer></footer>
</div>
</body>
</html>



style.css

article, aside, canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,
section, summary{display:block;}

html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,
address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,
dt,dd,ol,ul,li,fieldset,form,label, legend,table,caption,tbody,tfoot,thead,tr,th,td,
article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0; padding:0; border:0; outline:0;
font-size:100%; font-style:normal; font-weight:normal; vertical-align:baseline; background:tranparent;}

body{font:16px/1.5em sans-serif;}

li{list-style:none;}


img{vertical-align:bottom;}

:focus{outline:0;}



js/geo.js

(function($){

/*設定*/
var geo={
  twitter:"http://search.twitter.com/search.json",
  lat:35.724442, //緯度
  lng:139.715447, //経度
  rad:3,  //半径(km)
  addr:"", //表示されている住所・施設名
  timerGetTweet: 0, //getTweet関数のループを止めるためのタイマーID格納用
  google: new google.maps.Geocoder() //このgeo.googleを通じてMaps APIを利用します。
  };
  
  /* 実行 */
  $(function(){
  
  getTweet();
  slideTweet();
  
  $("#area-tweets").hover(function(){
	  $(this).find("p").first().stop();
  }, function() {
	  slideTweet();
  });
  
  //form要素のsubmit時
  $("#twitterModule .area-input").submit(function(){
	  getAddress();
  
  //sumitiイベントのデフォルト動作(action属性への遷移。この場合は同じページの再読込み)をキャンセルし、リロードしないようにします。
  return false;
  });
  
  // input要素のblur時
  $("#twitterModule input").blur(function() {
	  getAddress();
  });

  });
  
  /* 入力 */
  function getAddress(){
	  
// var value = $("#twitterModule input").attr("value");
   var value = $("#twitterModule input").val();	  
  
//表示内容と入力内容の間に変更がなかった場合は何もしません。
   if (geo.addr==value){return false; } 
   
//入力内容を表示内容として保存します。
  geo.addr=value;

//getGeocode関数を実行します。
   getGeocode();
  }
  
  /* 取得位置情報 */
  function getGeocode(){
	  if(!geo.google){
		  return false;
	  }
	  
	  geo.google.geocode({"address": geo.addr},function(results, status) {
		  if(status==google.maps.GeocoderStatus.OK){
			  geo.lat=results[0].geometry.location.lat();
			  geo.lng=results[0].geometry.location.lng();
		  }
		  
//すでに登録されたツイートを削除するためresetTweetを実行します。
  resetTweet();
  
//getTweetは30秒ごとに実行するようになっていますので、タイマーIDを使って、いったんそのタイマーを中断します。
  clearTimeout(geo.timerGetTweet);
  
//新たにgetTweetを実行します。
  getTweet();
	  });
  }
  
 /* 消去 */
   function resetTweet() {
	   var mod = $("#twitterModule"),
	   
//ツイートエリアの高さを取得します。
//モジュールの高さ−タイトルエリアの高さ-インプットエリアの高さ

areaHeight=mod.height()-mod.children(".title").height()-mod.chirdren(".area-input").height();
  h=0,     //ツイートブロックの高さ集計用
  index=0;  //削除基点となるツイートブロックのインデックス番号用
  
//eachメソッドでツイートブロック(p)の高さを順に加算して、ツイートエリアの高さを越えた時点で、そのインデックス番号を取得して終了する。
  $("#area-tweets p").each(function(i) {
	  h += $(this).height();
	  if(areaHeight < h) {
		  index = i;
		  return false;
	  }
  });
  
  // nextAllメソッドとremoveメソッドでインデックス番号以降のツイートブロックを削除する
  $("#area-tweets p").eq(index).nextAll().remove();
   }
   
/* 取得(ツイート) */
 function getTweet(){
	 var area = $("#area-tweets"),   
		  
// Twitter Search APIのURLにパラメータを付加する。
  url=geo.twitter + "?geocode=" + geo.lat+ "," + geo.lng+ "," + geo.rad +"km"; 

$.getJSON(url + "&callback=?", function(data){
	var results = data.results,
	p=$("<p>", {"class" : "address"});//住所表示用		
	
//住所を表示するブロックを追加する。
 p.text(geo.addr + "周辺").appendTo(area);	  
 
 for(var i=results.length; i--;){
	 var p=$("<p>"),
	 user = $("<a>", {"class" : "user", target: "_blank"}),
	 date = $("<span>", {"class" : "date"}),
	 datetime=new Date(results[i].created_at);
	 
user.text(results[i].from_user).attr("href", "http://twitter.com/" + results[i].from_user);
  date.text(
   	 datetime.getFullYear()+ "/" +
	 (datetime.getMonth()+1) + "/" +
	 datetime.getDate() + " " +
	 datetime.toLocaleTimeString()
);
results[i].text = results[i].text.replace(/(https?:\/\/[\-\/a-z0-9_~.#?&=%]+)/ig,"<a target='_blank' href='$1'>$1</a>");
results[i].text=results[i].text.replace(/@([a-z0-9_]+)/ig, "<a class='reply' target='_blank' href='http://twitter.com/$1'>@$1</a>");
	 
	 p.append(user, date, "<br>", results[i].text).appendTo(area);
 }
 
 //30秒後に再実行させます。
 //ループ中断用にタイマーIDを格納します。
   geo.timerGetTweet = setTimeout(getTweet, 30000);
});
 }
 
  /* 表示 */
 function slideTweet(){
	 var p = $("#area-tweets > p"), h=0;
	 
	 if(!p.length){
		 setTimeout(slideTweet, 500);
		 return false;
	 }
	 
	 h=p.first().innerHeight();

	 p.first().animate({
		 marginTop:-h
	 },{
		 duration:(h+Math.round(p.first().css("marginTop").slice(0,-2)))*50,
		 easing:"linear",
		 complete:function(){
			 $(this).remove();
			 slideTweet();
		 }
	 });
 }
 
})(jQuery);



js/moderneizr.js

// Modernizr v1.7  www.modernizr.com
window.Modernizr=function(a,b,c){function G(){e.input=function(a){for(var b=0,c=a.length;b<c;b++)t[a[b]]=!!(a[b]in l);return t}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),e.inputtypes=function(a){for(var d=0,e,f,h,i=a.length;d<i;d++)l.setAttribute("type",f=a[d]),e=l.type!=="text",e&&(l.value=m,l.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(f)&&l.style.WebkitAppearance!==c?(g.appendChild(l),h=b.defaultView,e=h.getComputedStyle&&h.getComputedStyle(l,null).WebkitAppearance!=="textfield"&&l.offsetHeight!==0,g.removeChild(l)):/^(search|tel)$/.test(f)||(/^(url|email)$/.test(f)?e=l.checkValidity&&l.checkValidity()===!1:/^color$/.test(f)?(g.appendChild(l),g.offsetWidth,e=l.value!=m,g.removeChild(l)):e=l.value!=m)),s[a[d]]=!!e;return s}("search tel url email datetime date month week time datetime-local number range color".split(" "))}function F(a,b){var c=a.charAt(0).toUpperCase()+a.substr(1),d=(a+" "+p.join(c+" ")+c).split(" ");return!!E(d,b)}function E(a,b){for(var d in a)if(k[a[d]]!==c&&(!b||b(a[d],j)))return!0}function D(a,b){return(""+a).indexOf(b)!==-1}function C(a,b){return typeof a===b}function B(a,b){return A(o.join(a+";")+(b||""))}function A(a){k.cssText=a}var d="1.7",e={},f=!0,g=b.documentElement,h=b.head||b.getElementsByTagName("head")[0],i="modernizr",j=b.createElement(i),k=j.style,l=b.createElement("input"),m=":)",n=Object.prototype.toString,o=" -webkit- -moz- -o- -ms- -khtml- ".split(" "),p="Webkit Moz O ms Khtml".split(" "),q={svg:"http://www.w3.org/2000/svg"},r={},s={},t={},u=[],v,w=function(a){var c=b.createElement("style"),d=b.createElement("div"),e;c.textContent=a+"{#modernizr{height:3px}}",h.appendChild(c),d.id="modernizr",g.appendChild(d),e=d.offsetHeight===3,c.parentNode.removeChild(c),d.parentNode.removeChild(d);return!!e},x=function(){function d(d,e){e=e||b.createElement(a[d]||"div");var f=(d="on"+d)in e;f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=C(e[d],"function"),C(e[d],c)||(e[d]=c),e.removeAttribute(d))),e=null;return f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),y=({}).hasOwnProperty,z;C(y,c)||C(y.call,c)?z=function(a,b){return b in a&&C(a.constructor.prototype[b],c)}:z=function(a,b){return y.call(a,b)},r.flexbox=function(){function c(a,b,c,d){a.style.cssText=o.join(b+":"+c+";")+(d||"")}function a(a,b,c,d){b+=":",a.style.cssText=(b+o.join(c+";"+b)).slice(0,-b.length)+(d||"")}var d=b.createElement("div"),e=b.createElement("div");a(d,"display","box","width:42px;padding:0;"),c(e,"box-flex","1","width:10px;"),d.appendChild(e),g.appendChild(d);var f=e.offsetWidth===42;d.removeChild(e),g.removeChild(d);return f},r.canvas=function(){var a=b.createElement("canvas");return a.getContext&&a.getContext("2d")},r.canvastext=function(){return e.canvas&&C(b.createElement("canvas").getContext("2d").fillText,"function")},r.webgl=function(){return!!a.WebGLRenderingContext},r.touch=function(){return"ontouchstart"in a||w("@media ("+o.join("touch-enabled),(")+"modernizr)")},r.geolocation=function(){return!!navigator.geolocation},r.postmessage=function(){return!!a.postMessage},r.websqldatabase=function(){var b=!!a.openDatabase;return b},r.indexedDB=function(){for(var b=-1,c=p.length;++b<c;){var d=p[b].toLowerCase();if(a[d+"_indexedDB"]||a[d+"IndexedDB"])return!0}return!1},r.hashchange=function(){return x("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},r.history=function(){return !!(a.history&&history.pushState)},r.draganddrop=function(){return x("dragstart")&&x("drop")},r.websockets=function(){return"WebSocket"in a},r.rgba=function(){A("background-color:rgba(150,255,150,.5)");return D(k.backgroundColor,"rgba")},r.hsla=function(){A("background-color:hsla(120,40%,100%,.5)");return D(k.backgroundColor,"rgba")||D(k.backgroundColor,"hsla")},r.multiplebgs=function(){A("background:url(//:),url(//:),red url(//:)");return(new RegExp("(url\\s*\\(.*?){3}")).test(k.background)},r.backgroundsize=function(){return F("backgroundSize")},r.borderimage=function(){return F("borderImage")},r.borderradius=function(){return F("borderRadius","",function(a){return D(a,"orderRadius")})},r.boxshadow=function(){return F("boxShadow")},r.textshadow=function(){return b.createElement("div").style.textShadow===""},r.opacity=function(){B("opacity:.55");return/^0.55$/.test(k.opacity)},r.cssanimations=function(){return F("animationName")},r.csscolumns=function(){return F("columnCount")},r.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";A((a+o.join(b+a)+o.join(c+a)).slice(0,-a.length));return D(k.backgroundImage,"gradient")},r.cssreflections=function(){return F("boxReflect")},r.csstransforms=function(){return!!E(["transformProperty","WebkitTransform","MozTransform","OTransform","msTransform"])},r.csstransforms3d=function(){var a=!!E(["perspectiveProperty","WebkitPerspective","MozPerspective","OPerspective","msPerspective"]);a&&"webkitPerspective"in g.style&&(a=w("@media ("+o.join("transform-3d),(")+"modernizr)"));return a},r.csstransitions=function(){return F("transitionProperty")},r.fontface=function(){var a,c,d=h||g,e=b.createElement("style"),f=b.implementation||{hasFeature:function(){return!1}};e.type="text/css",d.insertBefore(e,d.firstChild),a=e.sheet||e.styleSheet;var i=f.hasFeature("CSS2","")?function(b){if(!a||!b)return!1;var c=!1;try{a.insertRule(b,0),c=/src/i.test(a.cssRules[0].cssText),a.deleteRule(a.cssRules.length-1)}catch(d){}return c}:function(b){if(!a||!b)return!1;a.cssText=b;return a.cssText.length!==0&&/src/i.test(a.cssText)&&a.cssText.replace(/\r+|\n+/g,"").indexOf(b.split(" ")[0])===0};c=i('@font-face { font-family: "font"; src: url(data:,); }'),d.removeChild(e);return c},r.video=function(){var a=b.createElement("video"),c=!!a.canPlayType;if(c){c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"');var d='video/mp4; codecs="avc1.42E01E';c.h264=a.canPlayType(d+'"')||a.canPlayType(d+', mp4a.40.2"'),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"')}return c},r.audio=function(){var a=b.createElement("audio"),c=!!a.canPlayType;c&&(c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"'),c.mp3=a.canPlayType("audio/mpeg;"),c.wav=a.canPlayType('audio/wav; codecs="1"'),c.m4a=a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;"));return c},r.localstorage=function(){try{return!!localStorage.getItem}catch(a){return!1}},r.sessionstorage=function(){try{return!!sessionStorage.getItem}catch(a){return!1}},r.webWorkers=function(){return!!a.Worker},r.applicationcache=function(){return!!a.applicationCache},r.svg=function(){return!!b.createElementNS&&!!b.createElementNS(q.svg,"svg").createSVGRect},r.inlinesvg=function(){var a=b.createElement("div");a.innerHTML="<svg/>";return(a.firstChild&&a.firstChild.namespaceURI)==q.svg},r.smil=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"animate")))},r.svgclippaths=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"clipPath")))};for(var H in r)z(r,H)&&(v=H.toLowerCase(),e[v]=r[H](),u.push((e[v]?"":"no-")+v));e.input||G(),e.crosswindowmessaging=e.postmessage,e.historymanagement=e.history,e.addTest=function(a,b){a=a.toLowerCase();if(!e[a]){b=!!b(),g.className+=" "+(b?"":"no-")+a,e[a]=b;return e}},A(""),j=l=null,f&&a.attachEvent&&function(){var a=b.createElement("div");a.innerHTML="<elem></elem>";return a.childNodes.length!==1}()&&function(a,b){function p(a,b){var c=-1,d=a.length,e,f=[];while(++c<d)e=a[c],(b=e.media||b)!="screen"&&f.push(p(e.imports,b),e.cssText);return f.join("")}function o(a){var b=-1;while(++b<e)a.createElement(d[b])}var c="abbr|article|aside|audio|canvas|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",d=c.split("|"),e=d.length,f=new RegExp("(^|\\s)("+c+")","gi"),g=new RegExp("<(/*)("+c+")","gi"),h=new RegExp("(^|[^\\n]*?\\s)("+c+")([^\\n]*)({[\\n\\w\\W]*?})","gi"),i=b.createDocumentFragment(),j=b.documentElement,k=j.firstChild,l=b.createElement("body"),m=b.createElement("style"),n;o(b),o(i),k.insertBefore(m,k.firstChild),m.media="print",a.attachEvent("onbeforeprint",function(){var a=-1,c=p(b.styleSheets,"all"),k=[],o;n=n||b.body;while((o=h.exec(c))!=null)k.push((o[1]+o[2]+o[3]).replace(f,"$1.iepp_$2")+o[4]);m.styleSheet.cssText=k.join("\n");while(++a<e){var q=b.getElementsByTagName(d[a]),r=q.length,s=-1;while(++s<r)q[s].className.indexOf("iepp_")<0&&(q[s].className+=" iepp_"+d[a])}i.appendChild(n),j.appendChild(l),l.className=n.className,l.innerHTML=n.innerHTML.replace(g,"<$1font")}),a.attachEvent("onafterprint",function(){l.innerHTML="",j.removeChild(l),j.appendChild(n),m.styleSheet.cssText=""})}(a,b),e._enableHTML5=f,e._version=d,g.className=g.className.replace(/\bno-js\b/,"")+" js "+u.join(" ");return e}(this,this.document)

今回苦労した点
打ち間違いにより、動作しない・・。
疲れきった目と脳ではなかなか間違い箇所を発見できません。そんな時に強い助けとなるフリーソフトWinMerge」を、今回使ってみました。
あてにならない自分の目を凝らしてDreamweaverテキストエディターをしらみつぶしに見ていても全く発見できない間違いも、相当見つけやすくなります。


2つのドキュメントの差分を検出するソフト「WinMerge


完成形