blob: 8beda606648ee5b1f85aba3a5adc7667326deffc [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 = {};
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700136 map_cells = [];
137 map_tweet_markers = [];
138 map_info_windows = {};
139 review_mode_tweetbooks = [];
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700140
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700141 getAllDataverseTweetbooks();
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700142 initDemoUIButtonControls();
143});
144
145function initDemoUIButtonControls() {
146
147 // Explore Mode - Update Sliders
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700148 var updateSliderDisplay = function(event, ui) {
149 if (event.target.id == "grid-lat-slider") {
150 $("#gridlat").text(""+ui.value);
151 } else {
152 $("#gridlng").text(""+ui.value);
153 }
154 };
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700155 sliderOptions = {
156 max: 10,
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700157 min: 2.0,
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700158 step: .1,
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700159 value: 3.0,
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700160 slidechange: updateSliderDisplay,
161 slide: updateSliderDisplay,
162 start: updateSliderDisplay,
163 stop: updateSliderDisplay
164 };
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700165 $("#gridlat").text(""+sliderOptions.value);
166 $("#gridlng").text(""+sliderOptions.value);
167 $(".grid-slider").slider(sliderOptions);
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700168
169 // Explore Mode - Query Builder Date Pickers
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700170 var dateOptions = {
171 dateFormat: "yy-mm-dd",
172 defaultDate: "2012-01-02",
173 navigationAsDateFormat: true,
174 constrainInput: true
175 };
176 var start_dp = $("#start-date").datepicker(dateOptions);
177 start_dp.val(dateOptions.defaultDate);
178 dateOptions['defaultDate'] = "2012-12-31";
179 var end_dp= $("#end-date").datepicker(dateOptions);
180 end_dp.val(dateOptions.defaultDate);
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700181
182 // Explore Mode: Toggle Selection/Location Search
Eugenia Gabrielova3d7bfe52013-10-28 02:36:44 -0700183 $('#selection-button').on('change', function (e) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700184 $("#location-text-box").attr("disabled", "disabled");
185 if (selectionRect) {
186 selectionRect.setMap(map);
187 }
188 });
Eugenia Gabrielova3d7bfe52013-10-28 02:36:44 -0700189 $('#location-button').on('change', function (e) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700190 $("#location-text-box").removeAttr("disabled");
191 if (selectionRect) {
192 selectionRect.setMap(null);
193 }
194 });
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700195 $("#selection-button").trigger("click");
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700196
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700197 // Review Mode: New Tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700198 $('#new-tweetbook-button').on('click', function (e) {
199 onCreateNewTweetBook($('#new-tweetbook-entry').val());
200
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700201 $('#new-tweetbook-entry').val("");
202 $('#new-tweetbook-entry').attr("placeholder", "Name a new tweetbook");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700203 });
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700204
205 // Explore Mode - Clear Button
206 $("#clear-button").click(mapWidgetResetMap);
207
208 // Explore Mode: Query Submission
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700209 $("#submit-button").on("click", function () {
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700210
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700211 $("#report-message").html('');
212 $("#submit-button").attr("disabled", true);
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -0700213 $("body").css("cursor", "progress");
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700214
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700215 var kwterm = $("#keyword-textbox").val();
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700216 var startdp = $("#start-date").datepicker("getDate");
217 var enddp = $("#end-date").datepicker("getDate");
218 var startdt = $.datepicker.formatDate("yy-mm-dd", startdp)+"T00:00:00Z";
219 var enddt = $.datepicker.formatDate("yy-mm-dd", enddp)+"T23:59:59Z";
220
221 var formData = {
222 "keyword": kwterm,
223 "startdt": startdt,
224 "enddt": enddt,
225 "gridlat": $("#grid-lat-slider").slider("value"),
226 "gridlng": $("#grid-lng-slider").slider("value")
227 };
228
229 // Get Map Bounds
230 var bounds;
231 if ($('#selection-button').hasClass("active") && selectionRect) {
232 bounds = selectionRect.getBounds();
Eugenia Gabrielova3d7bfe52013-10-28 02:36:44 -0700233 alert("using rectangle");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700234 } else {
Eugenia Gabrielova3d7bfe52013-10-28 02:36:44 -0700235 alert("Using map");
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700236 bounds = map.getBounds();
237 }
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700238
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700239 var swLat = Math.abs(bounds.getSouthWest().lat());
240 var swLng = Math.abs(bounds.getSouthWest().lng());
241 var neLat = Math.abs(bounds.getNorthEast().lat());
242 var neLng = Math.abs(bounds.getNorthEast().lng());
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700243
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700244 formData["swLat"] = Math.min(swLat, neLat);
245 formData["swLng"] = Math.max(swLng, neLng);
246 formData["neLat"] = Math.max(swLat, neLat);
247 formData["neLng"] = Math.min(swLng, neLng);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700248
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700249 var build_cherry_mode = "synchronous";
250 if ($('#asbox').is(":checked")) {
251 build_cherry_mode = "asynchronous";
252 //$('#show-query-button').attr("disabled", false);
253 } else {
254 //$('#show-query-button').attr("disabled", true);
255 }
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700256
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700257 var f = buildAQLQueryFromForm(formData);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700258
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700259 APIqueryTracker = {
260 "query" : "use dataverse twitter;\n" + f.val(),
261 "data" : formData
262 };
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700263
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700264 // TODO Make dialog work correctly.
265 //$('#dialog').html(APIqueryTracker["query"]);
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700266
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700267 if (build_cherry_mode == "synchronous") {
268 A.query(f.val(), cherryQuerySyncCallback, build_cherry_mode);
269 } else {
270 A.query(f.val(), cherryQueryAsyncCallback, build_cherry_mode);
271 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700272
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700273 // Clears selection rectangle on query execution, rather than waiting for another clear call.
274 if (selectionRect) {
275 selectionRect.setMap(null);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700276 }
277 });
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700278}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700279
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700280/**
281* Builds AsterixDB REST Query from explore mode form.
282*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700283function buildAQLQueryFromForm(parameters) {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700284
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700285 var bounds = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700286 "ne" : { "lat" : parameters["neLat"], "lng" : -1*parameters["neLng"]},
287 "sw" : { "lat" : parameters["swLat"], "lng" : -1*parameters["swLng"]}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700288 };
289
290 var rectangle =
291 new FunctionExpression("create-rectangle",
292 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
293 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
294
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700295 // You can chain these all together, but let's take them one at a time.
296 // Let's start with a ForClause. Here we go through each tweet $t in the
297 // dataset TweetMessageShifted.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700298 var aql = new FLWOGRExpression()
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700299 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"));
300
301 // We know we have bounds for our region, so we can add that LetClause next.
302 aql = aql.LetClause("$region", rectangle);
303
304 // Now, let's change it up. The keyword term doesn't always show up, so it might be blank.
305 // We'll attach a new let clause for it, and then a WhereClause.
306 if (parameters["keyword"].length > 0) {
307 aql = aql
308 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
309 .WhereClause().and(
310 new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
311 new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
312 new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")'),
313 new FunctionExpression("contains", "$t.message-text", "$keyword")
314 );
315 } else {
316 aql = aql
317 .WhereClause().and(
318 new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
319 new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
320 new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")')
321 );
322 }
323
324 // Finally, we'll group our results into spatial cells.
325 aql = aql.GroupClause(
326 "$c",
327 new FunctionExpression("spatial-cell", "$t.sender-location",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700328 new FunctionExpression("create-point", "24.5", "-125.5"),
329 parameters["gridlat"].toFixed(1), parameters["gridlng"].toFixed(1)),
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700330 "with",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700331 "$t"
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700332 );
333
334 // ...and return a resulting cell and a count of results in that cell.
335 aql = aql.ReturnClause({ "cell" : "$c", "count" : "count($t)" });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700336
337 return aql;
338}
339
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700340/**
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700341* getAllDataverseTweetbooks
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700342*
343* Returns all datasets of type TweetbookEntry, populates review_mode_tweetbooks
344*/
345function getAllDataverseTweetbooks(fn_tweetbooks) {
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700346
347 // This creates a query to the Metadata for datasets of type
348 // TweetBookEntry. Note that if we throw in a WhereClause (commented out below)
349 // there is an odd error. This is being fixed and will be removed from this demo.
350 var getTweetbooksQuery = new FLWOGRExpression()
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700351 .ForClause("$ds", new AExpression("dataset Metadata.Dataset"))
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700352 //.WhereClause(new AExpression('$ds.DataTypeName = "TweetbookEntry"'))
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700353 .ReturnClause({
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700354 "DataTypeName" : "$ds.DataTypeName",
355 "DatasetName" : "$ds.DatasetName"
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700356 });
357
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700358 // Now create a function that will be called when tweetbooks succeed.
359 // In this case, we want to parse out the results object from the Asterix
360 // REST API response.
361 var tweetbooksSuccess = function(r) {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700362 // Parse tweetbook metadata results
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700363 $.each(r.results, function(i, data) {
364 if ($.parseJSON(data)["DataTypeName"] == "TweetbookEntry") {
365 review_mode_tweetbooks.push($.parseJSON(data)["DatasetName"]);
366 }
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700367 });
368
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700369 // Now, if any tweetbooks already exist, opulate review screen.
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700370 $('#review-tweetbook-titles').html('');
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700371 $.each(review_mode_tweetbooks, function(i, tweetbook) {
372 addTweetBookDropdownItem(tweetbook);
373 });
374 };
375
376 // Now, we are ready to run a query.
377 A.meta(getTweetbooksQuery.val(), tweetbooksSuccess);
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700378}
379
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700380/**
381* Checks through each asynchronous query to see if they are ready yet
382*/
383function asynchronousQueryIntervalUpdate() {
384 for (var handle_key in asyncQueryManager) {
385 if (!asyncQueryManager[handle_key].hasOwnProperty("ready")) {
386 asynchronousQueryGetAPIQueryStatus( asyncQueryManager[handle_key]["handle"], handle_key );
387 }
388 }
389}
390
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700391/**
392* Returns current time interval to check for asynchronous query readiness
393* @returns {number} milliseconds between asychronous query checks
394*/
395function asynchronousQueryGetInterval() {
396 var seconds = 10;
397 return seconds * 1000;
398}
399
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700400/**
401* Retrieves status of an asynchronous query, using an opaque result handle from API
402* @param {Object} handle, an object previously returned from an async call
403* @param {number} handle_id, the integer ID parsed from the handle object
404*/
405function asynchronousQueryGetAPIQueryStatus (handle, handle_id) {
406
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700407 A.query_status(
408 {
409 "handle" : JSON.stringify(handle)
410 },
411 function (res) {
412 if (res["status"] == "SUCCESS") {
413 // We don't need to check if this one is ready again, it's not going anywhere...
414 // Unless the life cycle of handles has changed drastically
415 asyncQueryManager[handle_id]["ready"] = true;
416
417 // Indicate success.
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700418 $('#handle_' + handle_id).removeClass("btn-disabled").prop('disabled', false).addClass("btn-success");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700419 }
420 }
421 );
422}
423
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700424/**
425* On-success callback after async API query
426* @param {object} res, a result object containing an opaque result handle to Asterix
427*/
428function cherryQueryAsyncCallback(res) {
429
430 // Parse handle, handle id and query from async call result
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700431 var handle_query = APIqueryTracker["query"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700432 var handle = res;
433 var handle_id = res["handle"].toString().split(',')[0];
434
435 // Add to stored map of existing handles
436 asyncQueryManager[handle_id] = {
437 "handle" : handle,
438 "query" : handle_query,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700439 "data" : APIqueryTracker["data"]
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700440 };
441
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700442 // Create a container for this async query handle
443 $('<div/>')
444 .css("margin-left", "1em")
445 .css("margin-bottom", "1em")
446 .css("display", "block")
447 .attr({
448 "class" : "btn-group",
449 "id" : "async_container_" + handle_id
450 })
451 .appendTo("#async-handle-controls");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700452
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700453 // Adds the main button for this async handle
454 var handle_action_button = '<button class="btn btn-disabled" id="handle_' + handle_id + '">Handle ' + handle_id + '</button>';
455 $('#async_container_' + handle_id).append(handle_action_button);
456 $('#handle_' + handle_id).prop('disabled', true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700457 $('#handle_' + handle_id).on('click', function (e) {
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700458
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700459 // make sure query is ready to be run
460 if (asyncQueryManager[handle_id]["ready"]) {
461
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700462 APIqueryTracker = {
463 "query" : asyncQueryManager[handle_id]["query"],
464 "data" : asyncQueryManager[handle_id]["data"]
465 };
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700466 // TODO
467 //$('#dialog').html(APIqueryTracker["query"]);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700468
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700469 if (!asyncQueryManager[handle_id].hasOwnProperty("result")) {
470 // Generate new Asterix Core API Query
471 A.query_result(
472 { "handle" : JSON.stringify(asyncQueryManager[handle_id]["handle"]) },
473 function(res) {
474 asyncQueryManager[handle_id]["result"] = res;
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700475
476 var resultTransform = {
477 "results" : res.results[0]
478 };
479
480 cherryQuerySyncCallback(resultTransform);
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700481 }
482 );
483 } else {
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700484
485 var resultTransform = {
486 "results" : asyncQueryManager[handle_id]["result"].results[0]
487 };
488
489 cherryQuerySyncCallback(resultTransform);
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700490 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700491 }
492 });
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700493
494 // Adds a removal button for this async handle
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700495 var asyncDeleteButton = addDeleteButton(
496 "trashhandle_" + handle_id,
497 "async_container_" + handle_id,
498 function (e) {
499 $('#async_container_' + handle_id).remove();
500 delete asyncQueryManager[handle_id];
501 }
502 );
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700503
504 $('#async_container_' + handle_id).append('<br/>');
505
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -0700506 $("body").css("cursor", "default");
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700507 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700508}
509
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700510/**
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700511* A spatial data cleaning and mapping call
512* @param {Object} res, a result object from a cherry geospatial query
513*/
514function cherryQuerySyncCallback(res) {
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700515
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700516 // Initialize coordinates and weights, to store
517 // coordinates of map cells and their weights
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700518 var coordinates = [];
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700519 var maxWeight = 0;
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700520 var minWeight = Number.MAX_VALUE;
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700521
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700522 // Parse resulting JSON objects. Here is an example record:
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700523 // { "cell": rectangle("21.5,-98.5 24.5,-95.5"), "count": 78i64 }
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700524 $.each(res.results, function(i, data) {
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700525
526 // We need to clean the JSON a bit to parse it properly in javascript
527 var cleanRecord = $.parseJSON(data
528 .replace('rectangle(', '')
529 .replace(')', '')
530 .replace('i64', ''));
531
532 var recordCount = cleanRecord["count"];
533 var rectangle = cleanRecord["cell"]
534 .replace(' ', ',')
535 .split(',')
536 .map( parseFloat );
537
538 // Now, using the record count and coordinates, we can create a
539 // coordinate system for this spatial cell.
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700540 var coordinate = {
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700541 "latSW" : rectangle[0],
542 "lngSW" : rectangle[1],
543 "latNE" : rectangle[2],
544 "lngNE" : rectangle[3],
545 "weight" : recordCount
546 };
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700547
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700548 // We track the minimum and maximum weight to support our legend.
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700549 maxWeight = Math.max(coordinate["weight"], maxWeight);
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700550 minWeight = Math.min(coordinate["weight"], minWeight);
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700551
552 // Save completed coordinate and move to next one.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700553 coordinates.push(coordinate);
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700554 });
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700555
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700556 triggerUIUpdate(coordinates, maxWeight, minWeight);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700557}
558
559/**
560* Triggers a map update based on a set of spatial query result cells
561* @param [Array] mapPlotData, an array of coordinate and weight objects
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700562* @param [Array] plotWeights, a list of weights of the spatial cells - e.g., number of tweets
563*/
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700564function triggerUIUpdate(mapPlotData, maxWeight, minWeight) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700565 /** Clear anything currently on the map **/
566 mapWidgetClearMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700567
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700568 // Initialize info windows.
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700569 map_info_windows = {};
570
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700571 $.each(mapPlotData, function (m) {
572
573 var point_center = new google.maps.LatLng(
574 (mapPlotData[m].latSW + mapPlotData[m].latNE)/2.0,
575 (mapPlotData[m].lngSW + mapPlotData[m].lngNE)/2.0);
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700576
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700577 var map_circle_options = {
578 center: point_center,
579 anchorPoint: point_center,
580 radius: mapWidgetComputeCircleRadius(mapPlotData[m], maxWeight),
581 map: map,
582 fillOpacity: 0.85,
583 fillColor: rainbow.colourAt(Math.ceil(100 * (mapPlotData[m].weight / maxWeight))),
584 clickable: true
585 };
586 var map_circle = new google.maps.Circle(map_circle_options);
587 map_circle.val = mapPlotData[m];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700588
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700589 map_info_windows[m] = new google.maps.InfoWindow({
590 content: mapPlotData[m].weight + " tweets",
591 position: point_center
592 });
593
594 // Clicking on a circle drills down map to that value, hovering over it displays a count
595 // of tweets at that location.
596 google.maps.event.addListener(map_circle, 'click', function (event) {
597 $.each(map_info_windows, function(i) {
598 map_info_windows[i].close();
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700599 });
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700600 onMapPointDrillDown(map_circle.val);
601 });
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700602
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700603 google.maps.event.addListener(map_circle, 'mouseover', function(event) {
604 if (!map_info_windows[m].getMap()) {
605 map_info_windows[m].setPosition(map_circle.center);
606 map_info_windows[m].open(map);
607 }
608 });
609
610 // Add this marker to global marker cells
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700611 map_cells.push(map_circle);
612
613 // Show legend
614 $("#legend-min").html(minWeight);
615 $("#legend-max").html(maxWeight);
616 $("#rainbow-legend-container").show();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700617 });
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -0700618
619 $("body").css("cursor", "default");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700620}
621
622/**
623* prepares an Asterix API query to drill down in a rectangular spatial zone
624*
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700625* @params {object} marker_borders a set of bounds for a region from a previous api result
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700626*/
627function onMapPointDrillDown(marker_borders) {
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -0700628
629 $("body").css("cursor", "progress");
630
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700631 var zoneData = APIqueryTracker["data"];
632
633 var zswBounds = new google.maps.LatLng(marker_borders.latSW, marker_borders.lngSW);
634 var zneBounds = new google.maps.LatLng(marker_borders.latNE, marker_borders.lngNE);
635
636 var zoneBounds = new google.maps.LatLngBounds(zswBounds, zneBounds);
637 zoneData["swLat"] = zoneBounds.getSouthWest().lat();
638 zoneData["swLng"] = zoneBounds.getSouthWest().lng();
639 zoneData["neLat"] = zoneBounds.getNorthEast().lat();
640 zoneData["neLng"] = zoneBounds.getNorthEast().lng();
641 var zB = {
642 "sw" : {
643 "lat" : zoneBounds.getSouthWest().lat(),
644 "lng" : zoneBounds.getSouthWest().lng()
645 },
646 "ne" : {
647 "lat" : zoneBounds.getNorthEast().lat(),
648 "lng" : zoneBounds.getNorthEast().lng()
649 }
650 };
651
652 mapWidgetClearMap();
653
654 var customBounds = new google.maps.LatLngBounds();
655 var zoomSWBounds = new google.maps.LatLng(zoneData["swLat"], zoneData["swLng"]);
656 var zoomNEBounds = new google.maps.LatLng(zoneData["neLat"], zoneData["neLng"]);
657 customBounds.extend(zoomSWBounds);
658 customBounds.extend(zoomNEBounds);
659 map.fitBounds(customBounds);
660
661 var df = getDrillDownQuery(zoneData, zB);
662
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700663 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700664 "query_string" : "use dataverse twitter;\n" + df.val(),
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700665 "marker_path" : "static/img/mobile2.png"
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700666 };
667
668 A.query(df.val(), onTweetbookQuerySuccessPlot);
669}
670
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700671/**
672* Generates an aql query for zooming on a spatial cell and obtaining tweets contained therein.
673* @param parameters, the original query parameters
674* @param bounds, the bounds of the zone to zoom in on.
675*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700676function getDrillDownQuery(parameters, bounds) {
677
678 var zoomRectangle = new FunctionExpression("create-rectangle",
679 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
680 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
681
682 var drillDown = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700683 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700684 .LetClause("$region", zoomRectangle);
685
686 if (parameters["keyword"].length == 0) {
687 drillDown = drillDown
688 .WhereClause().and(
689 new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
690 new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
691 new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")')
692 );
693 } else {
694 drillDown = drillDown
695 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
696 .WhereClause().and(
697 new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
698 new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
699 new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")'),
700 new FunctionExpression('contains', '$t.message-text', '$keyword')
701 );
702 }
703
704 drillDown = drillDown
705 .ReturnClause({
706 "tweetId" : "$t.tweetid",
707 "tweetText" : "$t.message-text",
708 "tweetLoc" : "$t.sender-location"
709 });
710
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700711 return drillDown;
712}
713
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700714/**
715* Given a location where a tweet exists, opens a modal to examine or update a tweet's content.
716* @param t0, a tweetobject that has a location, text, id, and optionally a comment.
717*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700718function onDrillDownAtLocation(tO) {
719
720 var tweetId = tO["tweetEntryId"];
721 var tweetText = tO["tweetText"];
722
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700723 // First, set tweet in drilldown modal to be this tweet's text
724 $('#modal-body-tweet').html('Tweet #' + tweetId + ": " + tweetText);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700725
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700726 // Next, empty any leftover tweetbook comments or error/success messages
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700727 $("#modal-body-add-to").val('');
728 $("#modal-body-add-note").val('');
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700729 $("#modal-body-message-holder").html("");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700730
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700731 // Next, if there is an existing tweetcomment reported, show it.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700732 if (tO.hasOwnProperty("tweetComment")) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700733
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700734 // Show correct panel
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700735 $("#modal-existing-note").show();
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700736 $("#modal-save-tweet-panel").hide();
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700737
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700738 // Fill in existing tweet comment
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700739 $("#modal-body-tweet-note").val(tO["tweetComment"]);
740
741 // Change Tweetbook Badge
742 $("#modal-current-tweetbook").val(APIqueryTracker["active_tweetbook"]);
743
744 // Add deletion functionality
745 $("#modal-body-trash-icon").on('click', function () {
746 // Send comment deletion to asterix
747 var deleteTweetCommentOnId = '"' + tweetId + '"';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700748 var toDelete = new DeleteStatement(
749 "$mt",
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700750 APIqueryTracker["active_tweetbook"],
751 new AExpression("$mt.tweetid = " + deleteTweetCommentOnId.toString())
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700752 );
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700753 A.update(
754 toDelete.val()
755 );
756
757 // Hide comment from map
758 $('#drilldown_modal').modal('hide');
759
760 // Replot tweetbook
761 onPlotTweetbook(APIqueryTracker["active_tweetbook"]);
762 });
763
764 } else {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700765 // Show correct panel
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700766 $("#modal-existing-note").hide();
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700767 $("#modal-save-tweet-panel").show();
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700768
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700769 // Now, when adding a comment on an available tweet to a tweetbook
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700770 $('#save-comment-tweetbook-modal').unbind('click');
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700771 $("#save-comment-tweetbook-modal").on('click', function(e) {
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700772
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700773 // Stuff to save about new comment
774 var save_metacomment_target_tweetbook = $("#modal-body-add-to").val();
775 var save_metacomment_target_comment = '"' + $("#modal-body-add-note").val() + '"';
776 var save_metacomment_target_tweet = '"' + tweetId + '"';
777
778 // Make sure content is entered, and then save this comment.
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700779 if ($("#modal-body-add-note").val() == "") {
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700780
781 reportUserMessage("Please enter a comment about the tweet", false, "report-message");
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700782
783 } else if ($("#modal-body-add-to").val() == "") {
784
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700785 reportUserMessage("Please enter a tweetbook.", false, "report-message");
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700786
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700787 } else {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700788
789 // Check if tweetbook exists. If not, create it.
790 if (!(existsTweetbook(save_metacomment_target_tweetbook))) {
791 onCreateNewTweetBook(save_metacomment_target_tweetbook);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700792 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700793
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700794 var toInsert = new InsertStatement(
795 save_metacomment_target_tweetbook,
796 {
797 "tweetid" : save_metacomment_target_tweet.toString(),
798 "comment-text" : save_metacomment_target_comment
799 }
800 );
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700801
802 A.update(toInsert.val(), function () {
803 var successMessage = "Saved comment on <b>Tweet #" + tweetId +
804 "</b> in dataset <b>" + save_metacomment_target_tweetbook + "</b>.";
805 reportUserMessage(successMessage, true, "report-message");
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700806
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700807 $("#modal-body-add-to").val('');
808 $("#modal-body-add-note").val('');
809 $('#save-comment-tweetbook-modal').unbind('click');
810
811 // Close modal
812 $('#drilldown_modal').modal('hide');
813 });
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700814 }
815 });
816 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700817}
818
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700819/**
820* Adds a new tweetbook entry to the menu and creates a dataset of type TweetbookEntry.
821*/
822function onCreateNewTweetBook(tweetbook_title) {
823
824 var tweetbook_title = tweetbook_title.split(' ').join('_');
825
826 A.ddl(
827 "create dataset " + tweetbook_title + "(TweetbookEntry) primary key tweetid;",
828 function () {}
829 );
830
831 if (!(existsTweetbook(tweetbook_title))) {
832 review_mode_tweetbooks.push(tweetbook_title);
833 addTweetBookDropdownItem(tweetbook_title);
834 }
835}
836
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700837/**
838* Removes a tweetbook from both demo and from
839* dataverse metadata.
840*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700841function onDropTweetBook(tweetbook_title) {
842
843 // AQL Call
844 A.ddl(
845 "drop dataset " + tweetbook_title + " if exists;",
846 function () {}
847 );
848
849 // Removes tweetbook from review_mode_tweetbooks
850 var remove_position = $.inArray(tweetbook_title, review_mode_tweetbooks);
851 if (remove_position >= 0) review_mode_tweetbooks.splice(remove_position, 1);
852
853 // Clear UI with review tweetbook titles
854 $('#review-tweetbook-titles').html('');
855 for (r in review_mode_tweetbooks) {
856 addTweetBookDropdownItem(review_mode_tweetbooks[r]);
857 }
858}
859
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700860/**
861* Adds a tweetbook action button to the dropdown box in review mode.
862* @param tweetbook, a string representing a tweetbook
863*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700864function addTweetBookDropdownItem(tweetbook) {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700865 // Add placeholder for this tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700866 $('<div/>')
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700867 .attr({
868 "class" : "btn-group",
869 "id" : "rm_holder_" + tweetbook
870 }).appendTo("#review-tweetbook-titles");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700871
872 // Add plotting button for this tweetbook
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700873 var plot_button = '<button class="btn btn-default" id="rm_plotbook_' + tweetbook + '">' + tweetbook + '</button>';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700874 $("#rm_holder_" + tweetbook).append(plot_button);
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700875 $("#rm_plotbook_" + tweetbook).width("200px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700876 $("#rm_plotbook_" + tweetbook).on('click', function(e) {
877 onPlotTweetbook(tweetbook);
878 });
879
880 // Add trash button for this tweetbook
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700881 var onTrashTweetbookButton = addDeleteButton(
882 "rm_trashbook_" + tweetbook,
883 "rm_holder_" + tweetbook,
884 function(e) {
885 onDropTweetBook(tweetbook);
886 }
887 );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700888}
889
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700890/**
891* Generates AsterixDB query to plot existing tweetbook commnets
892* @param tweetbook, a string representing a tweetbook
893*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700894function onPlotTweetbook(tweetbook) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700895
896 // Clear map for this one
897 mapWidgetResetMap();
898
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700899 var plotTweetQuery = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700900 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700901 .ForClause("$m", new AExpression("dataset " + tweetbook))
902 .WhereClause(new AExpression("$m.tweetid = $t.tweetid"))
903 .ReturnClause({
904 "tweetId" : "$m.tweetid",
905 "tweetText" : "$t.message-text",
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700906 "tweetCom" : "$m.comment-text",
907 "tweetLoc" : "$t.sender-location"
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700908 });
909
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700910 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700911 "query_string" : "use dataverse twitter;\n" + plotTweetQuery.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700912 "marker_path" : "static/img/mobile_green2.png",
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700913 "active_tweetbook" : tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700914 };
915
916 A.query(plotTweetQuery.val(), onTweetbookQuerySuccessPlot);
917}
918
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700919/**
920* Given an output response set of tweet data,
921* prepares markers on map to represent individual tweets.
922* @param res, a JSON Object
923*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700924function onTweetbookQuerySuccessPlot (res) {
925
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700926 // Parse out tweet Ids, texts, and locations
927 var tweets = [];
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700928 var al = 1;
929
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700930 $.each(res.results, function(i, data) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700931
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700932 // First, clean up the data
933 //{ "tweetId": "100293", "tweetText": " like at&t the touch-screen is amazing", "tweetLoc": point("31.59,-84.23") }
934 // We need to turn the point object at the end into a string
935 var json = $.parseJSON(data
936 .replace(': point(',': ')
937 .replace(') }', ' }'));
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700938
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700939 // Now, we construct a tweet object
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700940 var tweetData = {
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700941 "tweetEntryId" : parseInt(json.tweetId),
942 "tweetText" : json.tweetText,
943 "tweetLat" : json.tweetLoc.split(",")[0],
944 "tweetLng" : json.tweetLoc.split(",")[1]
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700945 };
946
947 // If we are parsing out tweetbook data with comments, we need to check
948 // for those here as well.
949 if (json.hasOwnProperty("tweetCom")) {
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700950 tweetData["tweetComment"] = json.tweetCom;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700951 }
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700952
953 tweets.push(tweetData)
954 });
955
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700956 // Create a marker for each tweet
957 $.each(tweets, function(i, t) {
958 // Create a phone marker at tweet's position
959 var map_tweet_m = new google.maps.Marker({
960 position: new google.maps.LatLng(tweets[i]["tweetLat"], tweets[i]["tweetLng"]),
961 map: map,
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700962 icon: APIqueryTracker["marker_path"],
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700963 clickable: true,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700964 });
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700965 map_tweet_m["test"] = t;
966
967 // Open Tweet exploration window on click
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700968 google.maps.event.addListener(map_tweet_m, 'click', function (event) {
969 onClickTweetbookMapMarker(map_tweet_markers[i]["test"]);
970 });
971
972 // Add marker to index of tweets
973 map_tweet_markers.push(map_tweet_m);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700974 });
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -0700975
976 $("body").css("cursor", "default");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700977}
978
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700979/**
980* Checks if a tweetbook exists
981* @param tweetbook, a String
982*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700983function existsTweetbook(tweetbook) {
984 if (parseInt($.inArray(tweetbook, review_mode_tweetbooks)) == -1) {
985 return false;
986 } else {
987 return true;
988 }
989}
990
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700991/**
992* When a marker is clicked on in the tweetbook, it will launch a modal
993* view to examine or edit the appropriate tweet
994*/
995function onClickTweetbookMapMarker(t) {
996 onDrillDownAtLocation(t)
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700997 $('#drilldown_modal').modal();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700998}
999
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001000/**
1001* Explore mode: Initial map creation and screen alignment
1002*/
1003function onOpenExploreMap () {
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -07001004 var explore_column_height = $('#explore-well').height();
1005 var right_column_width = $('#right-col').width();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001006 $('#map_canvas').height(explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -07001007 $('#map_canvas').width(right_column_width + "px");
1008
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001009 $('#review-well').height(explore_column_height + "px");
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001010 $('#review-well').css('max-height', explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -07001011 $('#right-col').height(explore_column_height + "px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001012}
1013
1014/**
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001015* initializes demo - adds some extra events when review/explore
1016* mode are clicked, initializes tabs, aligns map box, moves
1017* active tab to about tab
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001018*/
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001019function initDemoPrepareTabs() {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001020
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001021 // Tab behavior for About, Explore, and Demo
1022 $('#mode-tabs a').click(function (e) {
1023 e.preventDefault()
1024 $(this).tab('show')
1025 })
1026
1027 // Explore mode should show explore-mode query-builder UI
1028 $('#explore-mode').click(function(e) {
1029 $('#review-well').hide();
1030 $('#explore-well').show();
1031 mapWidgetResetMap();
1032 });
1033
1034 // Review mode should show review well and hide explore well
1035 $('#review-mode').click(function(e) {
1036 $('#explore-well').hide();
1037 $('#review-well').show();
1038 mapWidgetResetMap();
1039 });
1040
1041 // Does some alignment necessary for the map canvas
1042 onOpenExploreMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001043}
1044
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001045/**
1046* Creates a delete icon button using default trash icon
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001047* @param {String} id, id for this element
1048* @param {String} attachTo, id string of an element to which I can attach this button.
1049* @param {Function} onClick, a function to fire when this icon is clicked
1050*/
1051function addDeleteButton(iconId, attachTo, onClick) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001052
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001053 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 -07001054 $('#' + attachTo).append(trashIcon);
1055
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001056 // When this trash button is clicked, the function is called.
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001057 $('#' + iconId).on('click', onClick);
1058}
1059
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001060/**
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001061* Creates a message and attaches it to data management area.
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001062* @param {String} message, a message to post
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001063* @param {Boolean} isPositiveMessage, whether or not this is a positive message.
1064* @param {String} target, the target div to attach this message.
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001065*/
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001066function reportUserMessage(message, isPositiveMessage, target) {
1067 // Clear out any existing messages
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001068 $('#' + target).html('');
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001069
1070 // Select appropriate alert-type
1071 var alertType = "alert-success";
1072 if (!isPositiveMessage) {
1073 alertType = "alert-danger";
1074 }
1075
1076 // Append the appropriate message
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001077 $('<div/>')
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001078 .attr("class", "alert " + alertType)
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001079 .html('<button type="button" class="close" data-dismiss="alert">&times;</button>' + message)
1080 .appendTo('#' + target);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001081}
1082
1083/**
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001084* mapWidgetResetMap
1085*
1086* [No Parameters]
1087*
1088* Clears ALL map elements - plotted items, overlays, then resets position
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001089*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001090function mapWidgetResetMap() {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001091
1092 if (selectionRect) {
1093 selectionRect.setMap(null);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001094 }
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001095
1096 mapWidgetClearMap();
1097
1098 // Reset map center and zoom
1099 map.setCenter(new google.maps.LatLng(38.89, -77.03));
1100 map.setZoom(4);
1101}
1102
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001103/**
1104* mapWidgetClearMap
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001105* Removes data/markers
1106*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001107function mapWidgetClearMap() {
1108
1109 // Remove previously plotted data/markers
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001110 for (c in map_cells) {
1111 map_cells[c].setMap(null);
1112 }
1113 map_cells = [];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -07001114
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001115 $.each(map_info_windows, function(i) {
1116 map_info_windows[i].close();
1117 });
1118 map_info_windows = {};
1119
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001120 for (m in map_tweet_markers) {
1121 map_tweet_markers[m].setMap(null);
1122 }
1123 map_tweet_markers = [];
Eugenia Gabrielova12de0302013-10-18 02:25:39 -07001124
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -07001125 $("#rainbow-legend-container").hide();
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -07001126
Eugenia Gabrielova12de0302013-10-18 02:25:39 -07001127 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001128}
1129
1130/**
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001131* buildLegend
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001132*
1133* Generates gradient, button action for legend bar
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001134*/
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001135function buildLegend() {
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001136
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001137 // Fill in legend area with colors
1138 var gradientColor;
1139
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -07001140 for (i = 0; i<100; i++) {
1141 //$("#rainbow-legend-container").append("" + rainbow.colourAt(i));
1142 $("#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 -07001143 }
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001144
1145 // Window clear button closes all info count windows
1146 $("#windows-off-btn").on("click", function(e) {
1147 $.each(map_info_windows, function(i) {
1148 map_info_windows[i].close();
1149 });
1150 });
1151}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001152
1153/**
1154* Computes radius for a given data point from a spatial cell
1155* @param {Object} keys => ["latSW" "lngSW" "latNE" "lngNE" "weight"]
1156* @returns {number} radius between 2 points in metres
1157*/
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001158function mapWidgetComputeCircleRadius(spatialCell, wLimit) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001159
1160 // Define Boundary Points
1161 var point_center = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1162 var point_left = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, spatialCell.lngSW);
1163 var point_top = new google.maps.LatLng(spatialCell.latNE, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1164
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001165 // Circle scale modifier =
1166 var scale = 500 + 500*(spatialCell.weight / wLimit);
1167
1168 // Return proportionate value so that circles mostly line up.
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -07001169 return scale * Math.min(distanceBetweenPoints(point_center, point_left), distanceBetweenPoints(point_center, point_top));
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001170}
1171
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001172/**
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -07001173* Calculates the distance between two latlng locations in km, using Google Geometry API.
1174* @param p1, a LatLng
1175* @param p2, a LatLng
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001176*/
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -07001177function distanceBetweenPoints (p1, p2) {
1178 return 0.001 * google.maps.geometry.spherical.computeDistanceBetween(p1, p2);
Eugenia Gabrielova3d7bfe52013-10-28 02:36:44 -07001179};