blob: 92a6e602380316a93be9b43e509724a4ac433819 [file] [log] [blame]
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001$(function() {
2
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07003 // Initialize connection to AsterixDB. Just one connection is needed and contains
4 // logic for connecting to each API endpoint. This object A is reused throughout the
5 // code but does not store information about any individual API call.
6 A = new AsterixDBConnection({
7
8 // We will be using the twitter dataverse, which we can configure either like this
9 // or by following our AsterixDBConnection with a dataverse call, like so:
10 // A = new AsterixDBConnection().dataverse("twitter");
11 "dataverse" : "twitter",
12
13 // Due to the setup of this demo using the Bottle server, it is necessary to change the
14 // default endpoint of API calls. The proxy server handles the call to http://localhost:19002
15 // for us, and we reconfigure this connection to connect to the proxy server.
16 "endpoint_root" : "/",
17
18 // Finally, we want to make our error function nicer so that we show errors with a call to the
19 // reportUserMessage function. Update the "error" property to do that.
20 "error" : function(data) {
21 // For an error, data will look like this:
22 // {
23 // "error-code" : [error-number, error-text]
24 // "stacktrace" : ...stack trace...
25 // "summary" : ...summary of error...
26 // }
27 // We will report this as an Asterix REST API Error, an error code, and a reason message.
28 // Note the method signature: reportUserMessage(message, isPositiveMessage, target). We will provide
29 // an error message to display, a positivity value (false in this case, errors are bad), and a
30 // target html element in which to report the message.
31 var showErrorMessage = "Asterix Error #" + data["error-code"][0] + ": " + data["error-code"][1];
32 var isPositive = false;
33 var showReportAt = "report-message";
34
35 reportUserMessage(showErrorMessage, isPositive, showReportAt);
36 }
37 });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070038
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -070039 // This little bit of code manages period checks of the asynchronous query manager,
40 // which holds onto handles asynchornously received. We can set the handle update
41 // frequency using seconds, and it will let us know when it is ready.
42 var intervalID = setInterval(
43 function() {
44 asynchronousQueryIntervalUpdate();
45 },
46 asynchronousQueryGetInterval()
47 );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070048
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -070049 // Legend Container
50 // Create a rainbow from a pretty color scheme.
51 // http://www.colourlovers.com/palette/292482/Terra
52 rainbow = new Rainbow();
53 rainbow.setSpectrum("#E8DDCB", "#CDB380", "#036564", "#033649", "#031634");
54 buildLegend();
55
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -070056 // Initialization of UI Tabas
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -070057 initDemoPrepareTabs();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070058
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -070059 // UI Elements - Creates Map, Location Auto-Complete, Selection Rectangle
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070060 var mapOptions = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -070061 center: new google.maps.LatLng(38.89, -77.03),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070062 zoom: 4,
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -070063 mapTypeId: google.maps.MapTypeId.ROADMAP,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070064 streetViewControl: false,
65 draggable : false
66 };
67 map = new google.maps.Map(document.getElementById('map_canvas'), mapOptions);
68
69 var input = document.getElementById('location-text-box');
70 var autocomplete = new google.maps.places.Autocomplete(input);
71 autocomplete.bindTo('bounds', map);
72
73 google.maps.event.addListener(autocomplete, 'place_changed', function() {
74 var place = autocomplete.getPlace();
75 if (place.geometry.viewport) {
76 map.fitBounds(place.geometry.viewport);
77 } else {
78 map.setCenter(place.geometry.location);
79 map.setZoom(17); // Why 17? Because it looks good.
80 }
81 var address = '';
82 if (place.address_components) {
83 address = [(place.address_components[0] && place.address_components[0].short_name || ''),
84 (place.address_components[1] && place.address_components[1].short_name || ''),
85 (place.address_components[2] && place.address_components[2].short_name || '') ].join(' ');
86 }
87 });
88
89 // UI Elements - Selection Rectangle Drawing
90 shouldDraw = false;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070091 selectionRect = null;
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -070092 var startLatLng;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070093 var selectionRadio = $("#selection-button");
94 var firstClick = true;
95
96 google.maps.event.addListener(map, 'mousedown', function (event) {
97 // only allow drawing if selection is selected
98 if (selectionRadio.hasClass("active")) {
99 startLatLng = event.latLng;
100 shouldDraw = true;
101 }
102 });
103
104 google.maps.event.addListener(map, 'mousemove', drawRect);
105 function drawRect (event) {
106 if (shouldDraw) {
107 if (!selectionRect) {
108 var selectionRectOpts = {
109 bounds: new google.maps.LatLngBounds(startLatLng, event.latLng),
110 map: map,
111 strokeWeight: 1,
112 strokeColor: "2b3f8c",
113 fillColor: "2b3f8c"
114 };
115 selectionRect = new google.maps.Rectangle(selectionRectOpts);
116 google.maps.event.addListener(selectionRect, 'mouseup', function () {
117 shouldDraw = false;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700118 });
119 } else {
genia.likes.science@gmail.com4ada78c2013-09-07 13:53:48 -0700120
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700121 if (startLatLng.lng() < event.latLng.lng()) {
122 selectionRect.setBounds(new google.maps.LatLngBounds(startLatLng, event.latLng));
123 } else {
124 selectionRect.setBounds(new google.maps.LatLngBounds(event.latLng, startLatLng));
125 }
126 }
127 }
128 };
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700129
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700130 // Open about tab to start user on a tutorial
131 //$('#mode-tabs a:first').tab('show') // Select first tab
132
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700133 // Initialize data structures
134 APIqueryTracker = {};
135 asyncQueryManager = {};
136 drilldown_data_map = {};
137 drilldown_data_map_vals = {};
138 map_cells = [];
139 map_tweet_markers = [];
140 map_info_windows = {};
141 review_mode_tweetbooks = [];
142 getAllDataverseTweetbooks();
143
144 initDemoUIButtonControls();
145});
146
147function initDemoUIButtonControls() {
148
149 // Explore Mode - Update Sliders
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700150 var updateSliderDisplay = function(event, ui) {
151 if (event.target.id == "grid-lat-slider") {
152 $("#gridlat").text(""+ui.value);
153 } else {
154 $("#gridlng").text(""+ui.value);
155 }
156 };
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700157 sliderOptions = {
158 max: 10,
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700159 min: 2.0,
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700160 step: .1,
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700161 value: 3.0,
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700162 slidechange: updateSliderDisplay,
163 slide: updateSliderDisplay,
164 start: updateSliderDisplay,
165 stop: updateSliderDisplay
166 };
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700167 $("#gridlat").text(""+sliderOptions.value);
168 $("#gridlng").text(""+sliderOptions.value);
169 $(".grid-slider").slider(sliderOptions);
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700170
171 // Explore Mode - Query Builder Date Pickers
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700172 var dateOptions = {
173 dateFormat: "yy-mm-dd",
174 defaultDate: "2012-01-02",
175 navigationAsDateFormat: true,
176 constrainInput: true
177 };
178 var start_dp = $("#start-date").datepicker(dateOptions);
179 start_dp.val(dateOptions.defaultDate);
180 dateOptions['defaultDate'] = "2012-12-31";
181 var end_dp= $("#end-date").datepicker(dateOptions);
182 end_dp.val(dateOptions.defaultDate);
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700183
184 // Explore Mode: Toggle Selection/Location Search
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700185 $('#selection-button').on('click', function (e) {
186 $("#location-text-box").attr("disabled", "disabled");
187 if (selectionRect) {
188 selectionRect.setMap(map);
189 }
190 });
191 $('#location-button').on('click', function (e) {
192 $("#location-text-box").removeAttr("disabled");
193 if (selectionRect) {
194 selectionRect.setMap(null);
195 }
196 });
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700197 $("#selection-button").trigger("click");
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700198
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700199 // Review Mode: New Tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700200 $('#new-tweetbook-button').on('click', function (e) {
201 onCreateNewTweetBook($('#new-tweetbook-entry').val());
202
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700203 $('#new-tweetbook-entry').val("");
204 $('#new-tweetbook-entry').attr("placeholder", "Name a new tweetbook");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700205 });
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700206
207 // Explore Mode - Clear Button
208 $("#clear-button").click(mapWidgetResetMap);
209
210 // Explore Mode: Query Submission
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700211 $("#submit-button").on("click", function () {
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700212
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700213 var kwterm = $("#keyword-textbox").val();
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700214 if (kwterm == "") {
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700215 reportUserMessage("Please enter a search keyword!", false, "keyword-message");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700216 } else {
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700217
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700218 $("#report-message", "#keyword-message").html('');
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700219 $("#submit-button").attr("disabled", true);
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700220
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700221 var startdp = $("#start-date").datepicker("getDate");
222 var enddp = $("#end-date").datepicker("getDate");
223 var startdt = $.datepicker.formatDate("yy-mm-dd", startdp)+"T00:00:00Z";
224 var enddt = $.datepicker.formatDate("yy-mm-dd", enddp)+"T23:59:59Z";
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700225
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700226 var formData = {
227 "keyword": kwterm,
228 "startdt": startdt,
229 "enddt": enddt,
230 "gridlat": $("#grid-lat-slider").slider("value"),
231 "gridlng": $("#grid-lng-slider").slider("value")
232 };
233
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700234 // Get Map Bounds
235 var bounds;
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700236 if ($('#selection-button').hasClass("active") && selectionRect) {
237 bounds = selectionRect.getBounds();
238 } else {
239 bounds = map.getBounds();
240 }
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700241
242 var swLat = Math.abs(bounds.getSouthWest().lat());
243 var swLng = Math.abs(bounds.getSouthWest().lng());
244 var neLat = Math.abs(bounds.getNorthEast().lat());
245 var neLng = Math.abs(bounds.getNorthEast().lng());
246
247 formData["swLat"] = Math.min(swLat, neLat);
248 formData["swLng"] = Math.max(swLng, neLng);
249 formData["neLat"] = Math.max(swLat, neLat);
250 formData["neLng"] = Math.min(swLng, neLng);
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700251
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700252 var build_cherry_mode = "synchronous";
253
254 if ($('#asbox').is(":checked")) {
255 build_cherry_mode = "asynchronous";
256 $('#show-query-button').attr("disabled", false);
257 } else {
258 $('#show-query-button').attr("disabled", true);
259 }
260
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700261 var f = buildAQLQueryFromForm(formData);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700262
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700263 APIqueryTracker = {
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700264 "query" : "use dataverse twitter;\n" + f.val(),
265 "data" : formData
266 };
267
268 alert(f.val());
269
270 // TODO Make dialog work correctly.
271 //$('#dialog').html(APIqueryTracker["query"]);
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700272
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700273 if (build_cherry_mode == "synchronous") {
274 A.query(f.val(), cherryQuerySyncCallback, build_cherry_mode);
275 } else {
276 A.query(f.val(), cherryQueryAsyncCallback, build_cherry_mode);
277 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700278
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700279 // Clears selection rectangle on query execution, rather than waiting for another clear call.
280 if (selectionRect) {
281 selectionRect.setMap(null);
282 selectionRect = null;
283 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700284 }
285 });
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700286}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700287
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700288/**
289* Builds AsterixDB REST Query from explore mode form.
290*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700291function buildAQLQueryFromForm(parameters) {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700292
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700293 var bounds = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700294 "ne" : { "lat" : parameters["neLat"], "lng" : -1*parameters["neLng"]},
295 "sw" : { "lat" : parameters["swLat"], "lng" : -1*parameters["swLng"]}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700296 };
297
298 var rectangle =
299 new FunctionExpression("create-rectangle",
300 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
301 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
302
303
304 var aql = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700305 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700306 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
307 .LetClause("$region", rectangle)
308 .WhereClause().and(
309 new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
310 new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
311 new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")'),
312 new FunctionExpression("contains", "$t.message-text", "$keyword")
313 )
314 .GroupClause(
315 "$c",
316 new FunctionExpression("spatial-cell", "$t.sender-location",
317 new FunctionExpression("create-point", "24.5", "-125.5"),
318 parameters["gridlat"].toFixed(1), parameters["gridlng"].toFixed(1)),
319 "with",
320 "$t"
321 )
322 .ReturnClause({ "cell" : "$c", "count" : "count($t)" });
323
324 return aql;
325}
326
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700327/**
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700328* getAllDataverseTweetbooks
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700329*
330* Returns all datasets of type TweetbookEntry, populates review_mode_tweetbooks
331*/
332function getAllDataverseTweetbooks(fn_tweetbooks) {
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700333
334 // This creates a query to the Metadata for datasets of type
335 // TweetBookEntry. Note that if we throw in a WhereClause (commented out below)
336 // there is an odd error. This is being fixed and will be removed from this demo.
337 var getTweetbooksQuery = new FLWOGRExpression()
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700338 .ForClause("$ds", new AExpression("dataset Metadata.Dataset"))
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700339 //.WhereClause(new AExpression('$ds.DataTypeName = "TweetbookEntry"'))
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700340 .ReturnClause({
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700341 "DataTypeName" : "$ds.DataTypeName",
342 "DatasetName" : "$ds.DatasetName"
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700343 });
344
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700345 // Now create a function that will be called when tweetbooks succeed.
346 // In this case, we want to parse out the results object from the Asterix
347 // REST API response.
348 var tweetbooksSuccess = function(r) {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700349 // Parse tweetbook metadata results
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700350 $.each(r.results, function(i, data) {
351 if ($.parseJSON(data)["DataTypeName"] == "TweetbookEntry") {
352 review_mode_tweetbooks.push($.parseJSON(data)["DatasetName"]);
353 }
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700354 });
355
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700356 // Now, if any tweetbooks already exist, opulate review screen.
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700357 $('#review-tweetbook-titles').html('');
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700358 $.each(review_mode_tweetbooks, function(i, tweetbook) {
359 addTweetBookDropdownItem(tweetbook);
360 });
361 };
362
363 // Now, we are ready to run a query.
364 A.meta(getTweetbooksQuery.val(), tweetbooksSuccess);
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700365}
366
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700367/**
368* Checks through each asynchronous query to see if they are ready yet
369*/
370function asynchronousQueryIntervalUpdate() {
371 for (var handle_key in asyncQueryManager) {
372 if (!asyncQueryManager[handle_key].hasOwnProperty("ready")) {
373 asynchronousQueryGetAPIQueryStatus( asyncQueryManager[handle_key]["handle"], handle_key );
374 }
375 }
376}
377
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700378/**
379* Returns current time interval to check for asynchronous query readiness
380* @returns {number} milliseconds between asychronous query checks
381*/
382function asynchronousQueryGetInterval() {
383 var seconds = 10;
384 return seconds * 1000;
385}
386
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700387/**
388* Retrieves status of an asynchronous query, using an opaque result handle from API
389* @param {Object} handle, an object previously returned from an async call
390* @param {number} handle_id, the integer ID parsed from the handle object
391*/
392function asynchronousQueryGetAPIQueryStatus (handle, handle_id) {
393
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700394 A.query_status(
395 {
396 "handle" : JSON.stringify(handle)
397 },
398 function (res) {
399 if (res["status"] == "SUCCESS") {
400 // We don't need to check if this one is ready again, it's not going anywhere...
401 // Unless the life cycle of handles has changed drastically
402 asyncQueryManager[handle_id]["ready"] = true;
403
404 // Indicate success.
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700405 $('#handle_' + handle_id).removeClass("btn-disabled").prop('disabled', false).addClass("btn-success");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700406 }
407 }
408 );
409}
410
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700411/**
412* On-success callback after async API query
413* @param {object} res, a result object containing an opaque result handle to Asterix
414*/
415function cherryQueryAsyncCallback(res) {
416
417 // Parse handle, handle id and query from async call result
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700418 var handle_query = APIqueryTracker["query"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700419 var handle = res;
420 var handle_id = res["handle"].toString().split(',')[0];
421
422 // Add to stored map of existing handles
423 asyncQueryManager[handle_id] = {
424 "handle" : handle,
425 "query" : handle_query,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700426 "data" : APIqueryTracker["data"]
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700427 };
428
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700429 // Create a container for this async query handle
430 $('<div/>')
431 .css("margin-left", "1em")
432 .css("margin-bottom", "1em")
433 .css("display", "block")
434 .attr({
435 "class" : "btn-group",
436 "id" : "async_container_" + handle_id
437 })
438 .appendTo("#async-handle-controls");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700439
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700440 // Adds the main button for this async handle
441 var handle_action_button = '<button class="btn btn-disabled" id="handle_' + handle_id + '">Handle ' + handle_id + '</button>';
442 $('#async_container_' + handle_id).append(handle_action_button);
443 $('#handle_' + handle_id).prop('disabled', true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700444 $('#handle_' + handle_id).on('click', function (e) {
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700445
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700446 // make sure query is ready to be run
447 if (asyncQueryManager[handle_id]["ready"]) {
448
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700449 APIqueryTracker = {
450 "query" : asyncQueryManager[handle_id]["query"],
451 "data" : asyncQueryManager[handle_id]["data"]
452 };
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700453 // TODO
454 //$('#dialog').html(APIqueryTracker["query"]);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700455
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700456 if (!asyncQueryManager[handle_id].hasOwnProperty("result")) {
457 // Generate new Asterix Core API Query
458 A.query_result(
459 { "handle" : JSON.stringify(asyncQueryManager[handle_id]["handle"]) },
460 function(res) {
461 asyncQueryManager[handle_id]["result"] = res;
462 cherryQuerySyncCallback(res);
463 }
464 );
465 } else {
466 cherryQuerySyncCallback(asyncQueryManager[handle_id]["result"]);
467 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700468 }
469 });
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700470
471 // Adds a removal button for this async handle
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700472 var asyncDeleteButton = addDeleteButton(
473 "trashhandle_" + handle_id,
474 "async_container_" + handle_id,
475 function (e) {
476 $('#async_container_' + handle_id).remove();
477 delete asyncQueryManager[handle_id];
478 }
479 );
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700480
481 $('#async_container_' + handle_id).append('<br/>');
482
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700483 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700484}
485
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700486/**
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700487* cleanJSON
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700488* @param json, a JSON string that is not correctly formatted.
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700489* Quick and dirty little function to clean up an Asterix JSON quirk.
490*/
491function cleanJSON(json) {
492 return json
493 .replace("rectangle", '"rectangle"')
494 .replace("point:", '"point":')
495 .replace("point:", '"point":')
496 .replace("int64", '"int64"');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700497}
498
499/**
500* A spatial data cleaning and mapping call
501* @param {Object} res, a result object from a cherry geospatial query
502*/
503function cherryQuerySyncCallback(res) {
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700504
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700505 // Initialize coordinates and weights, to store
506 // coordinates of map cells and their weights
507 // TODO these are all included in coordinates already...
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700508 var coordinates = [];
509 var weights = [];
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700510 var maxWeight = 0;
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700511 var minWeight = Number.MAX_VALUE;
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700512
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700513 // Parse resulting JSON objects. Here is an example record:
514 // { "cell": { rectangle: [{ point: [22.5, 64.5]}, { point: [24.5, 66.5]}]}, "count": { int64: 5 }}
515 $.each(res.results, function(i, data) {
516
517 // First, parse a JSON object from a cleaned up string.
518 var record = $.parseJSON(cleanJSON(data));
519
520 // Parse Coordinates and Weights into a record
521 var sw = record.cell.rectangle[0].point;
522 var ne = record.cell.rectangle[1].point;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700523
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700524 var coordinate = {
525 "latSW" : sw[0],
526 "lngSW" : sw[1],
527 "latNE" : ne[0],
528 "lngNE" : ne[1],
529 "weight" : record.count.int64
530 }
531
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700532 maxWeight = Math.max(coordinate["weight"], maxWeight);
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700533 minWeight = Math.min(coordinate["weight"], minWeight);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700534 coordinates.push(coordinate);
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700535 });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700536
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700537 triggerUIUpdate(coordinates, maxWeight, minWeight);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700538}
539
540/**
541* Triggers a map update based on a set of spatial query result cells
542* @param [Array] mapPlotData, an array of coordinate and weight objects
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700543* @param [Array] plotWeights, a list of weights of the spatial cells - e.g., number of tweets
544*/
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700545function triggerUIUpdate(mapPlotData, maxWeight, minWeight) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700546 /** Clear anything currently on the map **/
547 mapWidgetClearMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700548
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700549 // Initialize info windows.
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700550 map_info_windows = {};
551
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700552 $.each(mapPlotData, function (m) {
553
554 var point_center = new google.maps.LatLng(
555 (mapPlotData[m].latSW + mapPlotData[m].latNE)/2.0,
556 (mapPlotData[m].lngSW + mapPlotData[m].lngNE)/2.0);
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700557
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700558 var map_circle_options = {
559 center: point_center,
560 anchorPoint: point_center,
561 radius: mapWidgetComputeCircleRadius(mapPlotData[m], maxWeight),
562 map: map,
563 fillOpacity: 0.85,
564 fillColor: rainbow.colourAt(Math.ceil(100 * (mapPlotData[m].weight / maxWeight))),
565 clickable: true
566 };
567 var map_circle = new google.maps.Circle(map_circle_options);
568 map_circle.val = mapPlotData[m];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700569
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700570 map_info_windows[m] = new google.maps.InfoWindow({
571 content: mapPlotData[m].weight + " tweets",
572 position: point_center
573 });
574
575 // Clicking on a circle drills down map to that value, hovering over it displays a count
576 // of tweets at that location.
577 google.maps.event.addListener(map_circle, 'click', function (event) {
578 $.each(map_info_windows, function(i) {
579 map_info_windows[i].close();
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700580 });
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700581 onMapPointDrillDown(map_circle.val);
582 });
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700583
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700584 google.maps.event.addListener(map_circle, 'mouseover', function(event) {
585 if (!map_info_windows[m].getMap()) {
586 map_info_windows[m].setPosition(map_circle.center);
587 map_info_windows[m].open(map);
588 }
589 });
590
591 // Add this marker to global marker cells
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700592 map_cells.push(map_circle);
593
594 // Show legend
595 $("#legend-min").html(minWeight);
596 $("#legend-max").html(maxWeight);
597 $("#rainbow-legend-container").show();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700598 });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700599}
600
601/**
602* prepares an Asterix API query to drill down in a rectangular spatial zone
603*
604* @params {object} marker_borders [LEGACY] a set of bounds for a region from a previous api result
605*/
606function onMapPointDrillDown(marker_borders) {
607 var zoneData = APIqueryTracker["data"];
608
609 var zswBounds = new google.maps.LatLng(marker_borders.latSW, marker_borders.lngSW);
610 var zneBounds = new google.maps.LatLng(marker_borders.latNE, marker_borders.lngNE);
611
612 var zoneBounds = new google.maps.LatLngBounds(zswBounds, zneBounds);
613 zoneData["swLat"] = zoneBounds.getSouthWest().lat();
614 zoneData["swLng"] = zoneBounds.getSouthWest().lng();
615 zoneData["neLat"] = zoneBounds.getNorthEast().lat();
616 zoneData["neLng"] = zoneBounds.getNorthEast().lng();
617 var zB = {
618 "sw" : {
619 "lat" : zoneBounds.getSouthWest().lat(),
620 "lng" : zoneBounds.getSouthWest().lng()
621 },
622 "ne" : {
623 "lat" : zoneBounds.getNorthEast().lat(),
624 "lng" : zoneBounds.getNorthEast().lng()
625 }
626 };
627
628 mapWidgetClearMap();
629
630 var customBounds = new google.maps.LatLngBounds();
631 var zoomSWBounds = new google.maps.LatLng(zoneData["swLat"], zoneData["swLng"]);
632 var zoomNEBounds = new google.maps.LatLng(zoneData["neLat"], zoneData["neLng"]);
633 customBounds.extend(zoomSWBounds);
634 customBounds.extend(zoomNEBounds);
635 map.fitBounds(customBounds);
636
637 var df = getDrillDownQuery(zoneData, zB);
638
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700639 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700640 "query_string" : "use dataverse twitter;\n" + df.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700641 "marker_path" : "static/img/mobile2.png",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700642 "on_clean_result" : onCleanTweetbookDrilldown,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700643 };
644
645 A.query(df.val(), onTweetbookQuerySuccessPlot);
646}
647
648function getDrillDownQuery(parameters, bounds) {
649
650 var zoomRectangle = new FunctionExpression("create-rectangle",
651 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
652 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
653
654 var drillDown = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700655 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700656 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
657 .LetClause("$region", zoomRectangle)
658 .WhereClause().and(
659 new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
660 new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
661 new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")'),
662 new FunctionExpression('contains', '$t.message-text', '$keyword')
663 )
664 .ReturnClause({
665 "tweetId" : "$t.tweetid",
666 "tweetText" : "$t.message-text",
667 "tweetLoc" : "$t.sender-location"
668 });
669
670 return drillDown;
671}
672
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700673function onDrillDownAtLocation(tO) {
674
675 var tweetId = tO["tweetEntryId"];
676 var tweetText = tO["tweetText"];
677
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700678 // First, set tweet in drilldown modal to be this tweet's text
679 $('#modal-body-tweet').html('Tweet #' + tweetId + ": " + tweetText);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700680
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700681 // Next, empty any leftover tweetbook comments or error/success messages
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700682 $("#modal-body-add-to").val('');
683 $("#modal-body-add-note").val('');
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700684 $("#modal-body-message-holder").html("");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700685
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700686 // Next, if there is an existing tweetcomment reported, show it.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700687 if (tO.hasOwnProperty("tweetComment")) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700688
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700689 // Show correct panel
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700690 $("#modal-existing-note").show();
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700691 $("#modal-save-tweet-panel").hide();
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700692
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700693 // Fill in existing tweet comment
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700694 $("#modal-body-tweet-note").val(tO["tweetComment"]);
695
696 // Change Tweetbook Badge
697 $("#modal-current-tweetbook").val(APIqueryTracker["active_tweetbook"]);
698
699 // Add deletion functionality
700 $("#modal-body-trash-icon").on('click', function () {
701 // Send comment deletion to asterix
702 var deleteTweetCommentOnId = '"' + tweetId + '"';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700703 var toDelete = new DeleteStatement(
704 "$mt",
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700705 APIqueryTracker["active_tweetbook"],
706 new AExpression("$mt.tweetid = " + deleteTweetCommentOnId.toString())
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700707 );
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700708 A.update(
709 toDelete.val()
710 );
711
712 // Hide comment from map
713 $('#drilldown_modal').modal('hide');
714
715 // Replot tweetbook
716 onPlotTweetbook(APIqueryTracker["active_tweetbook"]);
717 });
718
719 } else {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700720 // Show correct panel
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700721 $("#modal-existing-note").hide();
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700722 $("#modal-save-tweet-panel").show();
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700723
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700724 // Now, when adding a comment on an available tweet to a tweetbook
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700725 $('#save-comment-tweetbook-modal').unbind('click');
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700726 $("#save-comment-tweetbook-modal").on('click', function(e) {
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700727
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700728 // Stuff to save about new comment
729 var save_metacomment_target_tweetbook = $("#modal-body-add-to").val();
730 var save_metacomment_target_comment = '"' + $("#modal-body-add-note").val() + '"';
731 var save_metacomment_target_tweet = '"' + tweetId + '"';
732
733 // Make sure content is entered, and then save this comment.
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700734 if ($("#modal-body-add-note").val() == "") {
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700735
736 reportUserMessage("Please enter a comment about the tweet", false, "report-message");
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700737
738 } else if ($("#modal-body-add-to").val() == "") {
739
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700740 reportUserMessage("Please enter a tweetbook.", false, "report-message");
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700741
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700742 } else {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700743
744 // Check if tweetbook exists. If not, create it.
745 if (!(existsTweetbook(save_metacomment_target_tweetbook))) {
746 onCreateNewTweetBook(save_metacomment_target_tweetbook);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700747 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700748
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700749 var toInsert = new InsertStatement(
750 save_metacomment_target_tweetbook,
751 {
752 "tweetid" : save_metacomment_target_tweet.toString(),
753 "comment-text" : save_metacomment_target_comment
754 }
755 );
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700756
757 A.update(toInsert.val(), function () {
758 var successMessage = "Saved comment on <b>Tweet #" + tweetId +
759 "</b> in dataset <b>" + save_metacomment_target_tweetbook + "</b>.";
760 reportUserMessage(successMessage, true, "report-message");
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700761
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700762 $("#modal-body-add-to").val('');
763 $("#modal-body-add-note").val('');
764 $('#save-comment-tweetbook-modal').unbind('click');
765
766 // Close modal
767 $('#drilldown_modal').modal('hide');
768 });
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700769 }
770 });
771 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700772}
773
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700774/**
775* Adds a new tweetbook entry to the menu and creates a dataset of type TweetbookEntry.
776*/
777function onCreateNewTweetBook(tweetbook_title) {
778
779 var tweetbook_title = tweetbook_title.split(' ').join('_');
780
781 A.ddl(
782 "create dataset " + tweetbook_title + "(TweetbookEntry) primary key tweetid;",
783 function () {}
784 );
785
786 if (!(existsTweetbook(tweetbook_title))) {
787 review_mode_tweetbooks.push(tweetbook_title);
788 addTweetBookDropdownItem(tweetbook_title);
789 }
790}
791
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700792function onDropTweetBook(tweetbook_title) {
793
794 // AQL Call
795 A.ddl(
796 "drop dataset " + tweetbook_title + " if exists;",
797 function () {}
798 );
799
800 // Removes tweetbook from review_mode_tweetbooks
801 var remove_position = $.inArray(tweetbook_title, review_mode_tweetbooks);
802 if (remove_position >= 0) review_mode_tweetbooks.splice(remove_position, 1);
803
804 // Clear UI with review tweetbook titles
805 $('#review-tweetbook-titles').html('');
806 for (r in review_mode_tweetbooks) {
807 addTweetBookDropdownItem(review_mode_tweetbooks[r]);
808 }
809}
810
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700811function addTweetBookDropdownItem(tweetbook) {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700812 // Add placeholder for this tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700813 $('<div/>')
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700814 .attr({
815 "class" : "btn-group",
816 "id" : "rm_holder_" + tweetbook
817 }).appendTo("#review-tweetbook-titles");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700818
819 // Add plotting button for this tweetbook
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700820 var plot_button = '<button class="btn btn-default" id="rm_plotbook_' + tweetbook + '">' + tweetbook + '</button>';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700821 $("#rm_holder_" + tweetbook).append(plot_button);
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700822 $("#rm_plotbook_" + tweetbook).width("200px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700823 $("#rm_plotbook_" + tweetbook).on('click', function(e) {
824 onPlotTweetbook(tweetbook);
825 });
826
827 // Add trash button for this tweetbook
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700828 var onTrashTweetbookButton = addDeleteButton(
829 "rm_trashbook_" + tweetbook,
830 "rm_holder_" + tweetbook,
831 function(e) {
832 onDropTweetBook(tweetbook);
833 }
834 );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700835}
836
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700837function onPlotTweetbook(tweetbook) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700838
839 // Clear map for this one
840 mapWidgetResetMap();
841
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700842 var plotTweetQuery = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700843 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700844 .ForClause("$m", new AExpression("dataset " + tweetbook))
845 .WhereClause(new AExpression("$m.tweetid = $t.tweetid"))
846 .ReturnClause({
847 "tweetId" : "$m.tweetid",
848 "tweetText" : "$t.message-text",
849 "tweetLoc" : "$t.sender-location",
850 "tweetCom" : "$m.comment-text"
851 });
852
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700853 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700854 "query_string" : "use dataverse twitter;\n" + plotTweetQuery.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700855 "marker_path" : "static/img/mobile_green2.png",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700856 "on_clean_result" : onCleanPlotTweetbook,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700857 "active_tweetbook" : tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700858 };
859
860 A.query(plotTweetQuery.val(), onTweetbookQuerySuccessPlot);
861}
862
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700863function onTweetbookQuerySuccessPlot (res) {
864
865 var records = res["results"];
866
867 var coordinates = [];
868 map_tweet_markers = [];
869 map_tweet_overlays = [];
870 drilldown_data_map = {};
871 drilldown_data_map_vals = {};
872
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700873 var micon = APIqueryTracker["marker_path"];
874 var marker_click_function = onClickTweetbookMapMarker;
875 var clean_result_function = APIqueryTracker["on_clean_result"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700876
877 coordinates = clean_result_function(records);
878
879 for (var dm in coordinates) {
880 var keyLat = coordinates[dm].tweetLat.toString();
881 var keyLng = coordinates[dm].tweetLng.toString();
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700882
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700883 if (!drilldown_data_map.hasOwnProperty(keyLat)) {
884 drilldown_data_map[keyLat] = {};
885 }
886 if (!drilldown_data_map[keyLat].hasOwnProperty(keyLng)) {
887 drilldown_data_map[keyLat][keyLng] = [];
888 }
889 drilldown_data_map[keyLat][keyLng].push(coordinates[dm]);
890 drilldown_data_map_vals[coordinates[dm].tweetEntryId.toString()] = coordinates[dm];
891 }
892
893 $.each(drilldown_data_map, function(drillKeyLat, valuesAtLat) {
894 $.each(drilldown_data_map[drillKeyLat], function (drillKeyLng, valueAtLng) {
895
896 // Get subset of drilldown position on map
897 var cposition = new google.maps.LatLng(parseFloat(drillKeyLat), parseFloat(drillKeyLng));
898
899 // Create a marker using the snazzy phone icon
900 var map_tweet_m = new google.maps.Marker({
901 position: cposition,
902 map: map,
903 icon: micon,
904 clickable: true,
905 });
906
907 // Open Tweet exploration window on click
908 google.maps.event.addListener(map_tweet_m, 'click', function (event) {
909 marker_click_function(drilldown_data_map[drillKeyLat][drillKeyLng]);
910 });
911
912 // Add marker to index of tweets
913 map_tweet_markers.push(map_tweet_m);
914
915 });
916 });
917}
918
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700919function existsTweetbook(tweetbook) {
920 if (parseInt($.inArray(tweetbook, review_mode_tweetbooks)) == -1) {
921 return false;
922 } else {
923 return true;
924 }
925}
926
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700927function onCleanPlotTweetbook(records) {
928 var toPlot = [];
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700929
930 // An entry looks like this:
931 // { "tweetId": "273589", "tweetText": " like verizon the network is amazing", "tweetLoc": { point: [37.78, 82.27]}, "tweetCom": "hooray comments" }
932
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700933 for (var entry in records) {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700934
935 var points = records[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
936
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700937 var tweetbook_element = {
938 "tweetEntryId" : parseInt(records[entry].split(",")[0].split(":")[1].split('"')[1]),
939 "tweetText" : records[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700940 "tweetLat" : parseFloat(points[0]),
941 "tweetLng" : parseFloat(points[1]),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700942 "tweetComment" : records[entry].split("tweetCom\": \"")[1].split("\"")[0]
943 };
944 toPlot.push(tweetbook_element);
945 }
946
947 return toPlot;
948}
949
950function onCleanTweetbookDrilldown (rec) {
951
952 var drilldown_cleaned = [];
953
954 for (var entry = 0; entry < rec.length; entry++) {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700955
956 // An entry looks like this:
957 // { "tweetId": "105491", "tweetText": " hate verizon its platform is OMG", "tweetLoc": { point: [30.55, 71.44]} }
958 var points = rec[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
959
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700960 var drill_element = {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700961 "tweetEntryId" : parseInt(rec[entry].split(",")[0].split(":")[1].replace('"', '')),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700962 "tweetText" : rec[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700963 "tweetLat" : parseFloat(points[0]),
964 "tweetLng" : parseFloat(points[1])
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700965 };
966 drilldown_cleaned.push(drill_element);
967 }
968 return drilldown_cleaned;
969}
970
971function onClickTweetbookMapMarker(tweet_arr) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700972 // Clear existing display
973 $.each(tweet_arr, function (t, valueT) {
974 var tweet_obj = tweet_arr[t];
975 onDrillDownAtLocation(tweet_obj);
976 });
977
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700978 $('#drilldown_modal').modal();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700979}
980
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700981/**
982* Explore mode: Initial map creation and screen alignment
983*/
984function onOpenExploreMap () {
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700985 var explore_column_height = $('#explore-well').height();
986 var right_column_width = $('#right-col').width();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700987 $('#map_canvas').height(explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700988 $('#map_canvas').width(right_column_width + "px");
989
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700990 $('#review-well').height(explore_column_height + "px");
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700991 $('#review-well').css('max-height', explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700992 $('#right-col').height(explore_column_height + "px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700993}
994
995/**
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700996* initializes demo - adds some extra events when review/explore
997* mode are clicked, initializes tabs, aligns map box, moves
998* active tab to about tab
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700999*/
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001000function initDemoPrepareTabs() {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001001
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001002 // Tab behavior for About, Explore, and Demo
1003 $('#mode-tabs a').click(function (e) {
1004 e.preventDefault()
1005 $(this).tab('show')
1006 })
1007
1008 // Explore mode should show explore-mode query-builder UI
1009 $('#explore-mode').click(function(e) {
1010 $('#review-well').hide();
1011 $('#explore-well').show();
1012 mapWidgetResetMap();
1013 });
1014
1015 // Review mode should show review well and hide explore well
1016 $('#review-mode').click(function(e) {
1017 $('#explore-well').hide();
1018 $('#review-well').show();
1019 mapWidgetResetMap();
1020 });
1021
1022 // Does some alignment necessary for the map canvas
1023 onOpenExploreMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001024}
1025
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001026/**
1027* Creates a delete icon button using default trash icon
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001028* @param {String} id, id for this element
1029* @param {String} attachTo, id string of an element to which I can attach this button.
1030* @param {Function} onClick, a function to fire when this icon is clicked
1031*/
1032function addDeleteButton(iconId, attachTo, onClick) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001033
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001034 var trashIcon = '<button class="btn btn-default" id="' + iconId + '"><span class="glyphicon glyphicon-trash"></span></button>';
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001035 $('#' + attachTo).append(trashIcon);
1036
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001037 // When this trash button is clicked, the function is called.
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001038 $('#' + iconId).on('click', onClick);
1039}
1040
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001041/**
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001042* Creates a message and attaches it to data management area.
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001043* @param {String} message, a message to post
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001044* @param {Boolean} isPositiveMessage, whether or not this is a positive message.
1045* @param {String} target, the target div to attach this message.
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001046*/
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001047function reportUserMessage(message, isPositiveMessage, target) {
1048 // Clear out any existing messages
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001049 $('#' + target).html('');
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001050
1051 // Select appropriate alert-type
1052 var alertType = "alert-success";
1053 if (!isPositiveMessage) {
1054 alertType = "alert-danger";
1055 }
1056
1057 // Append the appropriate message
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001058 $('<div/>')
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001059 .attr("class", "alert " + alertType)
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001060 .html('<button type="button" class="close" data-dismiss="alert">&times;</button>' + message)
1061 .appendTo('#' + target);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001062}
1063
1064/**
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001065* mapWidgetResetMap
1066*
1067* [No Parameters]
1068*
1069* Clears ALL map elements - plotted items, overlays, then resets position
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001070*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001071function mapWidgetResetMap() {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001072
1073 if (selectionRect) {
1074 selectionRect.setMap(null);
1075 selectionRect = null;
1076 }
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001077
1078 mapWidgetClearMap();
1079
1080 // Reset map center and zoom
1081 map.setCenter(new google.maps.LatLng(38.89, -77.03));
1082 map.setZoom(4);
1083}
1084
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001085/**
1086* mapWidgetClearMap
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001087* Removes data/markers
1088*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001089function mapWidgetClearMap() {
1090
1091 // Remove previously plotted data/markers
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001092 for (c in map_cells) {
1093 map_cells[c].setMap(null);
1094 }
1095 map_cells = [];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -07001096
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001097 $.each(map_info_windows, function(i) {
1098 map_info_windows[i].close();
1099 });
1100 map_info_windows = {};
1101
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001102 for (m in map_tweet_markers) {
1103 map_tweet_markers[m].setMap(null);
1104 }
1105 map_tweet_markers = [];
Eugenia Gabrielova12de0302013-10-18 02:25:39 -07001106
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -07001107 $("#rainbow-legend-container").hide();
1108
Eugenia Gabrielova12de0302013-10-18 02:25:39 -07001109 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001110}
1111
1112/**
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001113* buildLegend
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001114*
1115* Generates gradient, button action for legend bar
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001116*/
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001117function buildLegend() {
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001118
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001119 // Fill in legend area with colors
1120 var gradientColor;
1121
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -07001122 for (i = 0; i<100; i++) {
1123 //$("#rainbow-legend-container").append("" + rainbow.colourAt(i));
1124 $("#legend-gradient").append('<div style="display:inline-block; max-width:2px; background-color:#' + rainbow.colourAt(i) +';">&nbsp;</div>');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001125 }
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001126
1127 // Window clear button closes all info count windows
1128 $("#windows-off-btn").on("click", function(e) {
1129 $.each(map_info_windows, function(i) {
1130 map_info_windows[i].close();
1131 });
1132 });
1133}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001134
1135/**
1136* Computes radius for a given data point from a spatial cell
1137* @param {Object} keys => ["latSW" "lngSW" "latNE" "lngNE" "weight"]
1138* @returns {number} radius between 2 points in metres
1139*/
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001140function mapWidgetComputeCircleRadius(spatialCell, wLimit) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001141
1142 // Define Boundary Points
1143 var point_center = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1144 var point_left = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, spatialCell.lngSW);
1145 var point_top = new google.maps.LatLng(spatialCell.latNE, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1146
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001147 // Circle scale modifier =
1148 var scale = 500 + 500*(spatialCell.weight / wLimit);
1149
1150 // Return proportionate value so that circles mostly line up.
1151 return scale * Math.min(distanceBetweenPoints_(point_center, point_left), distanceBetweenPoints_(point_center, point_top));
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001152}
1153
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001154/**
1155 * Calculates the distance between two latlng locations in km.
1156 * @see http://www.movable-type.co.uk/scripts/latlong.html
1157 *
1158 * @param {google.maps.LatLng} p1 The first lat lng point.
1159 * @param {google.maps.LatLng} p2 The second lat lng point.
1160 * @return {number} The distance between the two points in km.
1161 * @private
1162*/
1163function distanceBetweenPoints_(p1, p2) {
1164 if (!p1 || !p2) {
1165 return 0;
1166 }
1167
1168 var R = 6371; // Radius of the Earth in km
1169 var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
1170 var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
1171 var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
1172 Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
1173 Math.sin(dLon / 2) * Math.sin(dLon / 2);
1174 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1175 var d = R * c;
1176 return d;
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001177};