6 <meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1">
7 <meta name="description" content="Search Twitter based on location and draw the results as a heat map">
8 <meta name="author" content="Ryan Westphal">
9 <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/blitzer/jquery-ui.css" />
10 <style type="text/css">
13 font:13px/1.231 Calibri,Arial,sans-serif; *font-size:small;
23 background: url(img/$.geo-logo-small.png) left no-repeat;
29 text-decoration: none;
36 box-shadow: -4px 4px #444;
39 position: absolute !important;
49 @media only screen and (min-width: 800px)
78 border: solid 1px #444;
90 border-top: 2px solid #444;
101 border: 2px solid #444;
105 display: inline-block;
109 display: inline-block;
120 display: inline-block;
141 <input type="text" name="l" autofocus />
143 <button type="submit">Zoom</button>
147 <span>Search Twitter for</span>
148 <input type="text" name="q" />
150 <button type="submit">Search</button>
152 <div id="appendedCount">no tweets mapped yet :(</div>
153 <div id="tweetButton"></div>
155 <div class="info not-mobile">
157 <a href="http://jquerygeo.com/" class="docLink">jQuery Geo ></a>
160 <h2>A Twitter Heat Map</h2>
163 <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
164 <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"></script>
165 <script src="http://code.jquerygeo.com/jquery.geo-1.0a4.min.js"></script>
169 checkedIds = [], //< list of tweet ids we've checked
170 appendedCount = 0, //< number of tweets we successfully appended
171 searchTerm = "", //< last search term
173 currentXhr = null, //< an ajax request reference if we need to cancel
175 twitterButtonHtml = '<a href="https://twitter.com/share" class="twitter-share-button" data-count="vertical" data-via="ryanttb">Tweet</a><script src="//platform.twitter.com/widgets.js">\x3C/script>';
177 function initMap(center, zoom) {
178 // create a map using an optional center and zoom
179 map = $("#map").geomap({
180 center: center || [-71.0597732, 42.3584308],
183 // set the shapeStyle to a largish solid but translucent circle
184 // to give the tweets a heat map effect
190 borderRadius: "16px",
194 move: function (e, geo) {
195 // when the user moves, search for appended tweets
199 $("#popup").hide().html("");
202 // spatial query, geo has the cursor location as a map point
203 // this will find appended tweets within 3 pixels
204 var features = map.geomap("find", geo, 3),
208 // for each tweet found, add some html to the popup
209 for (; i < features.length; i++) {
210 popupHtml += "<p>" + features[i].properties.tweet + "</p>";
214 // if any tweets found, show the popup
215 var $popup = $("#popup").append(popupHtml).css({
220 var widthOver = $(window).width() - ( $popup.width() + e.pageX ),
221 heightOver = ($(window).height() - 32) - ( $popup.height() + e.pageY ),
225 if ( widthOver < 0 ) {
229 if ( heightOver < 0 ) {
242 if ( searchTerm && !searching ) {
247 $("#loc").submit(function (e) {
250 // when the user clicks the location search button,
251 // send a request to nominatim for an OpenStreatMap data search
253 url: "http://open.mapquestapi.com/nominatim/v1/search",
256 q: $("#loc input").val()
259 jsonp: "json_callback",
260 success: function (results) {
261 if (results && results.length > 0) {
262 // if we get a result, relaunch the app to the new location with the old search
263 // this will allow users to tweet their map
264 window.location.search =
265 "q=" + encodeURIComponent($("#twit input").val()) +
266 "&l=" + encodeURIComponent($("#loc input").val()) +
267 "¢er=" + results[0].lon + "," + results[0].lat +
268 "&zoom=" + map.geomap("option", "zoom");
275 $("#twit").submit(function (e) {
278 // when the user clicks the tweet search button,
279 // send a request to twitter
282 // if there's a search pending, cancel it
287 $("#popup").hide().html("");
289 // save our search term
290 searchTerm = $("#twit input").val();
293 // if we have a new search term, relaunch the app to the same location with the new search
294 // this will allow users to tweet their map
295 window.location.search =
296 "q=" + encodeURIComponent(searchTerm) +
297 "&l=" + encodeURIComponent($("#loc input").val()) +
298 "¢er=" + map.geomap("option", "center").toString() +
299 "&zoom=" + map.geomap("option", "zoom");
305 // jQueryUI for pretty buttons
306 $( "button" ).button();
309 // called by autoSearch, this function actually searches Twitter for geo-enabled tweets
311 if ( map !== null ) {
312 var center = map.geomap("option", "center"), //< the center of the map in lon, lat
313 // the geocode argument to Twitter search,
314 // it's an array with [ lat, lon, radius in km ]
315 // geomap's center is lon, lat so we have to switch
316 // for radius we'll use document width * pixelSize converted to km (from meters)
317 // Twitter search has a max of 2500km
321 Math.min(2500, map.geomap("option", "pixelSize") * $(document).width() / 2 / 1000) + "km"
323 lastSearchTerm = searchTerm;
325 // actually send the request to Twitter
326 currentXhr = $.ajax({
327 url: "http://search.twitter.com/search.json",
331 geocode: geocode.join(",")
334 complete: function () {
337 success: function (tweets) {
338 if (searchTerm == lastSearchTerm && tweets.results) {
339 // if we have results, search each of them for the geo or location property
340 $.each(tweets.results, function () {
345 $("#appendedCount").text(appendedCount + " tweets mapped!");
352 function appendTweet( tweet ) {
353 // attempt to append a tweet if we haven't already
354 if ( $.inArray( tweet.id_str, checkedIds ) >= 0 ) {
358 // we don't want to attempt more than once for a given tweet
359 checkedIds.push( tweet.id_str );
361 // store some tweet html as a property on the feature
366 coordinates: [ 0, 0 ]
369 tweet: "<b>" + tweet.from_user + "</b>: " + tweet.text
374 // if we have a geo property, flip the coordinates because Twitter search isn't
375 // in proper GeoJSON spec order
376 // Twitter uses [lat, lon] instead of [lon, lat]
377 feature.geometry.coordinates = [
378 tweet.geo.coordinates[1],
379 tweet.geo.coordinates[0]
382 map.geomap("append", feature);
384 } else if ( tweet.location ) {
385 // otherwise, attempt to geocode the location property
387 url: "http://open.mapquestapi.com/nominatim/v1/search",
393 jsonp: "json_callback",
394 success: function (results) {
395 if (results && results.length > 0) {
396 // if found, grab the location's lon & lat
397 if (results[0].lon && results[0].lat) {
398 feature.geometry.coordinates = [
403 // finally append the tweet
404 map.geomap( "append", feature );
406 $("#appendedCount").text(appendedCount + " tweets mapped!");
414 function autoSearch() {
418 setTimeout(autoSearch, 5000);
422 // for fun, we support sending center, zoom and a tweet query in the query string
423 var queryString = window.location.search.substring(1),
424 params = queryString.split("&"),
427 $.each(params, function() {
428 var idx = this.indexOf("=");
430 options[this.substring(0, idx)] = this.substring(idx + 1);
434 if (options.center) {
436 initMap($.parseJSON("[" + options.center + "]"), parseInt(options.zoom));
438 initMap($.parseJSON("[" + options.center + "]"));
441 // if there's no center in the query string, try to use geolocation
442 if (navigator.geolocation) {
443 navigator.geolocation.getCurrentPosition(function (p) {
444 initMap([p.coords.longitude, p.coords.latitude]);
445 }, function (error) {
449 // if all else fails, use defaults
454 var title = "Twheat !";
457 searchTerm = decodeURIComponent(options.q);
458 $("#twit input").val(searchTerm);
459 title += " " + searchTerm;
461 if ( map !== null && !searching ) {
467 var loc = decodeURIComponent(options.l);
468 $("#loc input").val(loc);
472 $("title").html(title);
473 $("#tweetButton").append(twitterButtonHtml);
478 var _gaq = [['_setAccount', 'UA-26084853-1'], ['_trackPageview']];
480 var g = d.createElement(t), s = d.getElementsByTagName(t)[0]; g.async = 1;
481 g.src = ('https:' == location.protocol ? '//ssl' : '//www') + '.google-analytics.com/ga.js';
482 s.parentNode.insertBefore(g, s);
483 } (document, 'script'));