blob: 84fc8c983a5535c25c35ffa20680d659d82287d1 [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
39 // Following this is some stuff specific to the Black Cherry demo
40 // This is not necessary for working with AsterixDB
41 APIqueryTracker = {};
42 drilldown_data_map = {};
43 drilldown_data_map_vals = {};
44 asyncQueryManager = {};
45
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -070046 // Populate review mode tweetbooks
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070047 review_mode_tweetbooks = [];
48 review_mode_handles = [];
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -070049 getAllDataverseTweetbooks();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070050
51 map_cells = [];
52 map_tweet_markers = [];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -070053 map_info_windows = {};
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070054
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -070055 // Legend Container
56 // Create a rainbow from a pretty color scheme.
57 // http://www.colourlovers.com/palette/292482/Terra
58 rainbow = new Rainbow();
59 rainbow.setSpectrum("#E8DDCB", "#CDB380", "#036564", "#033649", "#031634");
60 buildLegend();
61
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070062 // UI Elements - Modals & perspective tabs
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -070063 $('#drilldown_modal').modal('hide');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070064 $('#explore-mode').click( onLaunchExploreMode );
65 $('#review-mode').click( onLaunchReviewMode );
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -070066 $('#about-mode').click(onLaunchAboutMode);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070067
68 // UI Elements - A button to clear current map and query data
69 $("#clear-button").button().click(function () {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -070070 mapWidgetResetMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070071
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -070072 $('#report-message').html('');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070073 $('#query-preview-window').html('');
74 $("#metatweetzone").html('');
75 });
76
77 // UI Elements - Query setup
78 $("#selection-button").button('toggle');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070079
80 // UI Element - Grid sliders
81 var updateSliderDisplay = function(event, ui) {
82 if (event.target.id == "grid-lat-slider") {
83 $("#gridlat").text(""+ui.value);
84 } else {
85 $("#gridlng").text(""+ui.value);
86 }
87 };
88
89 sliderOptions = {
90 max: 10,
91 min: 1.5,
92 step: .1,
93 value: 2.0,
94 slidechange: updateSliderDisplay,
95 slide: updateSliderDisplay,
96 start: updateSliderDisplay,
97 stop: updateSliderDisplay
98 };
99
100 $("#gridlat").text(""+sliderOptions.value);
101 $("#gridlng").text(""+sliderOptions.value);
102 $(".grid-slider").slider(sliderOptions);
103
104 // UI Elements - Date Pickers
105 var dateOptions = {
106 dateFormat: "yy-mm-dd",
107 defaultDate: "2012-01-02",
108 navigationAsDateFormat: true,
109 constrainInput: true
110 };
111 var start_dp = $("#start-date").datepicker(dateOptions);
112 start_dp.val(dateOptions.defaultDate);
113 dateOptions['defaultDate'] = "2012-12-31";
114 var end_dp= $("#end-date").datepicker(dateOptions);
115 end_dp.val(dateOptions.defaultDate);
116
117 // This little bit of code manages period checks of the asynchronous query manager,
118 // which holds onto handles asynchornously received. We can set the handle update
119 // frequency using seconds, and it will let us know when it is ready.
120 var intervalID = setInterval(
121 function() {
122 asynchronousQueryIntervalUpdate();
123 },
124 asynchronousQueryGetInterval()
125 );
126
127 // UI Elements - Creates map and location auto-complete
128 onOpenExploreMap();
129 var mapOptions = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700130 center: new google.maps.LatLng(38.89, -77.03),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700131 zoom: 4,
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700132 mapTypeId: google.maps.MapTypeId.ROADMAP,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700133 streetViewControl: false,
134 draggable : false
135 };
136 map = new google.maps.Map(document.getElementById('map_canvas'), mapOptions);
137
138 var input = document.getElementById('location-text-box');
139 var autocomplete = new google.maps.places.Autocomplete(input);
140 autocomplete.bindTo('bounds', map);
141
142 google.maps.event.addListener(autocomplete, 'place_changed', function() {
143 var place = autocomplete.getPlace();
144 if (place.geometry.viewport) {
145 map.fitBounds(place.geometry.viewport);
146 } else {
147 map.setCenter(place.geometry.location);
148 map.setZoom(17); // Why 17? Because it looks good.
149 }
150 var address = '';
151 if (place.address_components) {
152 address = [(place.address_components[0] && place.address_components[0].short_name || ''),
153 (place.address_components[1] && place.address_components[1].short_name || ''),
154 (place.address_components[2] && place.address_components[2].short_name || '') ].join(' ');
155 }
156 });
157
158 // UI Elements - Selection Rectangle Drawing
159 shouldDraw = false;
160 var startLatLng;
161 selectionRect = null;
162 var selectionRadio = $("#selection-button");
163 var firstClick = true;
164
165 google.maps.event.addListener(map, 'mousedown', function (event) {
166 // only allow drawing if selection is selected
167 if (selectionRadio.hasClass("active")) {
168 startLatLng = event.latLng;
169 shouldDraw = true;
170 }
171 });
172
173 google.maps.event.addListener(map, 'mousemove', drawRect);
174 function drawRect (event) {
175 if (shouldDraw) {
176 if (!selectionRect) {
177 var selectionRectOpts = {
178 bounds: new google.maps.LatLngBounds(startLatLng, event.latLng),
179 map: map,
180 strokeWeight: 1,
181 strokeColor: "2b3f8c",
182 fillColor: "2b3f8c"
183 };
184 selectionRect = new google.maps.Rectangle(selectionRectOpts);
185 google.maps.event.addListener(selectionRect, 'mouseup', function () {
186 shouldDraw = false;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700187 });
188 } else {
genia.likes.science@gmail.com4ada78c2013-09-07 13:53:48 -0700189
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700190 if (startLatLng.lng() < event.latLng.lng()) {
191 selectionRect.setBounds(new google.maps.LatLngBounds(startLatLng, event.latLng));
192 } else {
193 selectionRect.setBounds(new google.maps.LatLngBounds(event.latLng, startLatLng));
194 }
195 }
196 }
197 };
198
199 // UI Elements - Toggle location search style by location or by map selection
200 $('#selection-button').on('click', function (e) {
201 $("#location-text-box").attr("disabled", "disabled");
202 if (selectionRect) {
203 selectionRect.setMap(map);
204 }
205 });
206 $('#location-button').on('click', function (e) {
207 $("#location-text-box").removeAttr("disabled");
208 if (selectionRect) {
209 selectionRect.setMap(null);
210 }
211 });
212
213 // UI Elements - Tweetbook Management
214 $('.dropdown-menu a.holdmenu').click(function(e) {
215 e.stopPropagation();
216 });
217
218 $('#new-tweetbook-button').on('click', function (e) {
219 onCreateNewTweetBook($('#new-tweetbook-entry').val());
220
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700221 $('#new-tweetbook-entry').val("");
222 $('#new-tweetbook-entry').attr("placeholder", "Name a new tweetbook");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700223 });
224
225 // UI Element - Query Submission
226 $("#submit-button").button().click(function () {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700227
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700228 var kwterm = $("#keyword-textbox").val();
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700229 if (kwterm == "") {
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700230 reportUserMessage("Please enter a search term!", false, "report-message");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700231 } else {
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700232
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700233 $("#report-message").html('');
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700234 $("#submit-button").attr("disabled", true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700235
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700236 var startdp = $("#start-date").datepicker("getDate");
237 var enddp = $("#end-date").datepicker("getDate");
238 var startdt = $.datepicker.formatDate("yy-mm-dd", startdp)+"T00:00:00Z";
239 var enddt = $.datepicker.formatDate("yy-mm-dd", enddp)+"T23:59:59Z";
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700240
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700241 var formData = {
242 "keyword": kwterm,
243 "startdt": startdt,
244 "enddt": enddt,
245 "gridlat": $("#grid-lat-slider").slider("value"),
246 "gridlng": $("#grid-lng-slider").slider("value")
247 };
248
249 // Get Map Bounds
250 var bounds;
251 if ($('#selection-button').hasClass("active") && selectionRect) {
252 bounds = selectionRect.getBounds();
253 } else {
254 bounds = map.getBounds();
255 }
256
257 var swLat = Math.abs(bounds.getSouthWest().lat());
258 var swLng = Math.abs(bounds.getSouthWest().lng());
259 var neLat = Math.abs(bounds.getNorthEast().lat());
260 var neLng = Math.abs(bounds.getNorthEast().lng());
261
262 formData["swLat"] = Math.min(swLat, neLat);
263 formData["swLng"] = Math.max(swLng, neLng);
264 formData["neLat"] = Math.max(swLat, neLat);
265 formData["neLng"] = Math.min(swLng, neLng);
266
267 var build_cherry_mode = "synchronous";
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700268
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700269 if ($('#asbox').is(":checked")) {
270 build_cherry_mode = "asynchronous";
271 $('#show-query-button').attr("disabled", false);
272 } else {
273 $('#show-query-button').attr("disabled", true);
274 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700275
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700276 var f = buildAQLQueryFromForm(formData);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700277
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700278 APIqueryTracker = {
279 "query" : "use dataverse twitter;\n" + f.val(),
280 "data" : formData
281 };
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700282
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700283 // TODO Make dialog work correctly.
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700284 //$('#dialog').html(APIqueryTracker["query"]);
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700285
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700286 if (build_cherry_mode == "synchronous") {
287 A.query(f.val(), cherryQuerySyncCallback, build_cherry_mode);
288 } else {
289 A.query(f.val(), cherryQueryAsyncCallback, build_cherry_mode);
290 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700291
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700292 // Clears selection rectangle on query execution, rather than waiting for another clear call.
293 if (selectionRect) {
294 selectionRect.setMap(null);
295 selectionRect = null;
296 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700297 }
298 });
299});
300
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700301function buildAQLQueryFromForm(parameters) {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700302
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700303 var bounds = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700304 "ne" : { "lat" : parameters["neLat"], "lng" : -1*parameters["neLng"]},
305 "sw" : { "lat" : parameters["swLat"], "lng" : -1*parameters["swLng"]}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700306 };
307
308 var rectangle =
309 new FunctionExpression("create-rectangle",
310 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
311 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
312
313
314 var aql = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700315 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700316 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
317 .LetClause("$region", rectangle)
318 .WhereClause().and(
319 new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
320 new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
321 new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")'),
322 new FunctionExpression("contains", "$t.message-text", "$keyword")
323 )
324 .GroupClause(
325 "$c",
326 new FunctionExpression("spatial-cell", "$t.sender-location",
327 new FunctionExpression("create-point", "24.5", "-125.5"),
328 parameters["gridlat"].toFixed(1), parameters["gridlng"].toFixed(1)),
329 "with",
330 "$t"
331 )
332 .ReturnClause({ "cell" : "$c", "count" : "count($t)" });
333
334 return aql;
335}
336
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700337/**
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700338* getAllDataverseTweetbooks
339*
340* no params
341*
342* Returns all datasets of type TweetbookEntry, populates review_mode_tweetbooks
343*/
344function getAllDataverseTweetbooks(fn_tweetbooks) {
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700345
346 // This creates a query to the Metadata for datasets of type
347 // TweetBookEntry. Note that if we throw in a WhereClause (commented out below)
348 // there is an odd error. This is being fixed and will be removed from this demo.
349 var getTweetbooksQuery = new FLWOGRExpression()
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700350 .ForClause("$ds", new AExpression("dataset Metadata.Dataset"))
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700351 //.WhereClause(new AExpression('$ds.DataTypeName = "TweetbookEntry"'))
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700352 .ReturnClause({
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700353 "DataTypeName" : "$ds.DataTypeName",
354 "DatasetName" : "$ds.DatasetName"
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700355 });
356
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700357 // Now create a function that will be called when tweetbooks succeed.
358 // In this case, we want to parse out the results object from the Asterix
359 // REST API response.
360 var tweetbooksSuccess = function(r) {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700361 // Parse tweetbook metadata results
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700362 $.each(r.results, function(i, data) {
363 if ($.parseJSON(data)["DataTypeName"] == "TweetbookEntry") {
364 review_mode_tweetbooks.push($.parseJSON(data)["DatasetName"]);
365 }
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700366 });
367
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700368 // Now, if any tweetbooks already exist, opulate review screen.
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700369 $('#review-tweetbook-titles').html('');
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700370 $.each(review_mode_tweetbooks, function(i, tweetbook) {
371 addTweetBookDropdownItem(tweetbook);
372 });
373 };
374
375 // Now, we are ready to run a query.
376 A.meta(getTweetbooksQuery.val(), tweetbooksSuccess);
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700377
378}
379
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700380/** Asynchronous Query Management **/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700381
382/**
383* Checks through each asynchronous query to see if they are ready yet
384*/
385function asynchronousQueryIntervalUpdate() {
386 for (var handle_key in asyncQueryManager) {
387 if (!asyncQueryManager[handle_key].hasOwnProperty("ready")) {
388 asynchronousQueryGetAPIQueryStatus( asyncQueryManager[handle_key]["handle"], handle_key );
389 }
390 }
391}
392
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700393/**
394* Returns current time interval to check for asynchronous query readiness
395* @returns {number} milliseconds between asychronous query checks
396*/
397function asynchronousQueryGetInterval() {
398 var seconds = 10;
399 return seconds * 1000;
400}
401
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700402/**
403* Retrieves status of an asynchronous query, using an opaque result handle from API
404* @param {Object} handle, an object previously returned from an async call
405* @param {number} handle_id, the integer ID parsed from the handle object
406*/
407function asynchronousQueryGetAPIQueryStatus (handle, handle_id) {
408
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700409 A.query_status(
410 {
411 "handle" : JSON.stringify(handle)
412 },
413 function (res) {
414 if (res["status"] == "SUCCESS") {
415 // We don't need to check if this one is ready again, it's not going anywhere...
416 // Unless the life cycle of handles has changed drastically
417 asyncQueryManager[handle_id]["ready"] = true;
418
419 // Indicate success.
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700420 $('#handle_' + handle_id).removeClass("btn-disabled").prop('disabled', false).addClass("btn-success");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700421 }
422 }
423 );
424}
425
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700426/**
427* On-success callback after async API query
428* @param {object} res, a result object containing an opaque result handle to Asterix
429*/
430function cherryQueryAsyncCallback(res) {
431
432 // Parse handle, handle id and query from async call result
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700433 var handle_query = APIqueryTracker["query"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700434 var handle = res;
435 var handle_id = res["handle"].toString().split(',')[0];
436
437 // Add to stored map of existing handles
438 asyncQueryManager[handle_id] = {
439 "handle" : handle,
440 "query" : handle_query,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700441 "data" : APIqueryTracker["data"]
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700442 };
443
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700444 // Create a container for this async query handle
445 $('<div/>')
446 .css("margin-left", "1em")
447 .css("margin-bottom", "1em")
448 .css("display", "block")
449 .attr({
450 "class" : "btn-group",
451 "id" : "async_container_" + handle_id
452 })
453 .appendTo("#async-handle-controls");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700454
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700455 // Adds the main button for this async handle
456 var handle_action_button = '<button class="btn btn-disabled" id="handle_' + handle_id + '">Handle ' + handle_id + '</button>';
457 $('#async_container_' + handle_id).append(handle_action_button);
458 $('#handle_' + handle_id).prop('disabled', true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700459 $('#handle_' + handle_id).on('click', function (e) {
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700460
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700461 // make sure query is ready to be run
462 if (asyncQueryManager[handle_id]["ready"]) {
463
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700464 APIqueryTracker = {
465 "query" : asyncQueryManager[handle_id]["query"],
466 "data" : asyncQueryManager[handle_id]["data"]
467 };
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700468 // TODO
469 //$('#dialog').html(APIqueryTracker["query"]);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700470
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700471 if (!asyncQueryManager[handle_id].hasOwnProperty("result")) {
472 // Generate new Asterix Core API Query
473 A.query_result(
474 { "handle" : JSON.stringify(asyncQueryManager[handle_id]["handle"]) },
475 function(res) {
476 asyncQueryManager[handle_id]["result"] = res;
477 cherryQuerySyncCallback(res);
478 }
479 );
480 } else {
481 cherryQuerySyncCallback(asyncQueryManager[handle_id]["result"]);
482 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700483 }
484 });
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700485
486 // Adds a removal button for this async handle
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700487 var asyncDeleteButton = addDeleteButton(
488 "trashhandle_" + handle_id,
489 "async_container_" + handle_id,
490 function (e) {
491 $('#async_container_' + handle_id).remove();
492 delete asyncQueryManager[handle_id];
493 }
494 );
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700495
496 $('#async_container_' + handle_id).append('<br/>');
497
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700498 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700499}
500
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700501/**
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700502* returns a json object with keys: weight, latSW, lngSW, latNE, lngNE
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700503*
504* { "cell": { rectangle: [{ point: [22.5, 64.5]}, { point: [24.5, 66.5]}]}, "count": { int64: 5 }}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700505*/
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700506
507/**
508* cleanJSON
509*
510* @param json, a JSON string that is not correctly formatted.
511*
512* Quick and dirty little function to clean up an Asterix JSON quirk.
513*/
514function cleanJSON(json) {
515 return json
516 .replace("rectangle", '"rectangle"')
517 .replace("point:", '"point":')
518 .replace("point:", '"point":')
519 .replace("int64", '"int64"');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700520}
521
522/**
523* A spatial data cleaning and mapping call
524* @param {Object} res, a result object from a cherry geospatial query
525*/
526function cherryQuerySyncCallback(res) {
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700527
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700528 // Initialize coordinates and weights, to store
529 // coordinates of map cells and their weights
530 // TODO these are all included in coordinates already...
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700531 var coordinates = [];
532 var weights = [];
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700533 var maxWeight = 0;
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700534
535 // Parse resulting JSON objects. Here is an example record:
536 // { "cell": { rectangle: [{ point: [22.5, 64.5]}, { point: [24.5, 66.5]}]}, "count": { int64: 5 }}
537 $.each(res.results, function(i, data) {
538
539 // First, parse a JSON object from a cleaned up string.
540 var record = $.parseJSON(cleanJSON(data));
541
542 // Parse Coordinates and Weights into a record
543 var sw = record.cell.rectangle[0].point;
544 var ne = record.cell.rectangle[1].point;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700545
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700546 var coordinate = {
547 "latSW" : sw[0],
548 "lngSW" : sw[1],
549 "latNE" : ne[0],
550 "lngNE" : ne[1],
551 "weight" : record.count.int64
552 }
553
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700554 maxWeight = Math.max(coordinate["weight"], maxWeight);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700555 coordinates.push(coordinate);
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700556 });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700557
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700558 triggerUIUpdate(coordinates, maxWeight);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700559}
560
561/**
562* Triggers a map update based on a set of spatial query result cells
563* @param [Array] mapPlotData, an array of coordinate and weight objects
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700564* @param [Array] plotWeights, a list of weights of the spatial cells - e.g., number of tweets
565*/
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700566function triggerUIUpdate(mapPlotData, maxWeight) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700567 /** Clear anything currently on the map **/
568 mapWidgetClearMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700569
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700570 // Initialize info windows.
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700571 map_info_windows = {};
572
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700573 $.each(mapPlotData, function (m) {
574
575 var point_center = new google.maps.LatLng(
576 (mapPlotData[m].latSW + mapPlotData[m].latNE)/2.0,
577 (mapPlotData[m].lngSW + mapPlotData[m].lngNE)/2.0);
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700578
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700579 var map_circle_options = {
580 center: point_center,
581 anchorPoint: point_center,
582 radius: mapWidgetComputeCircleRadius(mapPlotData[m], maxWeight),
583 map: map,
584 fillOpacity: 0.85,
585 fillColor: rainbow.colourAt(Math.ceil(100 * (mapPlotData[m].weight / maxWeight))),
586 clickable: true
587 };
588 var map_circle = new google.maps.Circle(map_circle_options);
589 map_circle.val = mapPlotData[m];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700590
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700591 map_info_windows[m] = new google.maps.InfoWindow({
592 content: mapPlotData[m].weight + " tweets",
593 position: point_center
594 });
595
596 // Clicking on a circle drills down map to that value, hovering over it displays a count
597 // of tweets at that location.
598 google.maps.event.addListener(map_circle, 'click', function (event) {
599 $.each(map_info_windows, function(i) {
600 map_info_windows[i].close();
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700601 });
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700602 onMapPointDrillDown(map_circle.val);
603 });
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700604
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700605 google.maps.event.addListener(map_circle, 'mouseover', function(event) {
606 if (!map_info_windows[m].getMap()) {
607 map_info_windows[m].setPosition(map_circle.center);
608 map_info_windows[m].open(map);
609 }
610 });
611
612 // Add this marker to global marker cells
613 map_cells.push(map_circle);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700614 });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700615}
616
617/**
618* prepares an Asterix API query to drill down in a rectangular spatial zone
619*
620* @params {object} marker_borders [LEGACY] a set of bounds for a region from a previous api result
621*/
622function onMapPointDrillDown(marker_borders) {
623 var zoneData = APIqueryTracker["data"];
624
625 var zswBounds = new google.maps.LatLng(marker_borders.latSW, marker_borders.lngSW);
626 var zneBounds = new google.maps.LatLng(marker_borders.latNE, marker_borders.lngNE);
627
628 var zoneBounds = new google.maps.LatLngBounds(zswBounds, zneBounds);
629 zoneData["swLat"] = zoneBounds.getSouthWest().lat();
630 zoneData["swLng"] = zoneBounds.getSouthWest().lng();
631 zoneData["neLat"] = zoneBounds.getNorthEast().lat();
632 zoneData["neLng"] = zoneBounds.getNorthEast().lng();
633 var zB = {
634 "sw" : {
635 "lat" : zoneBounds.getSouthWest().lat(),
636 "lng" : zoneBounds.getSouthWest().lng()
637 },
638 "ne" : {
639 "lat" : zoneBounds.getNorthEast().lat(),
640 "lng" : zoneBounds.getNorthEast().lng()
641 }
642 };
643
644 mapWidgetClearMap();
645
646 var customBounds = new google.maps.LatLngBounds();
647 var zoomSWBounds = new google.maps.LatLng(zoneData["swLat"], zoneData["swLng"]);
648 var zoomNEBounds = new google.maps.LatLng(zoneData["neLat"], zoneData["neLng"]);
649 customBounds.extend(zoomSWBounds);
650 customBounds.extend(zoomNEBounds);
651 map.fitBounds(customBounds);
652
653 var df = getDrillDownQuery(zoneData, zB);
654
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700655 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700656 "query_string" : "use dataverse twitter;\n" + df.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700657 "marker_path" : "static/img/mobile2.png",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700658 "on_clean_result" : onCleanTweetbookDrilldown,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700659 };
660
661 A.query(df.val(), onTweetbookQuerySuccessPlot);
662}
663
664function getDrillDownQuery(parameters, bounds) {
665
666 var zoomRectangle = new FunctionExpression("create-rectangle",
667 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
668 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
669
670 var drillDown = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700671 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700672 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
673 .LetClause("$region", zoomRectangle)
674 .WhereClause().and(
675 new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
676 new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
677 new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")'),
678 new FunctionExpression('contains', '$t.message-text', '$keyword')
679 )
680 .ReturnClause({
681 "tweetId" : "$t.tweetid",
682 "tweetText" : "$t.message-text",
683 "tweetLoc" : "$t.sender-location"
684 });
685
686 return drillDown;
687}
688
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700689function onDrillDownAtLocation(tO) {
690
691 var tweetId = tO["tweetEntryId"];
692 var tweetText = tO["tweetText"];
693
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700694 // First, set tweet in drilldown modal to be this tweet's text
695 $('#modal-body-tweet').html('Tweet #' + tweetId + ": " + tweetText);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700696
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700697 // Next, empty any leftover tweetbook comments or error/success messages
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700698 $("#modal-body-add-to").val('');
699 $("#modal-body-add-note").val('');
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700700 $("#modal-body-message-holder").html("");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700701
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700702 // Next, if there is an existing tweetcomment reported, show it.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700703 if (tO.hasOwnProperty("tweetComment")) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700704
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700705 // Show correct panel
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700706 $("#modal-existing-note").show();
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700707 $("#modal-save-tweet-panel").hide();
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700708
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700709 // Fill in existing tweet comment
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700710 $("#modal-body-tweet-note").val(tO["tweetComment"]);
711
712 // Change Tweetbook Badge
713 $("#modal-current-tweetbook").val(APIqueryTracker["active_tweetbook"]);
714
715 // Add deletion functionality
716 $("#modal-body-trash-icon").on('click', function () {
717 // Send comment deletion to asterix
718 var deleteTweetCommentOnId = '"' + tweetId + '"';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700719 var toDelete = new DeleteStatement(
720 "$mt",
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700721 APIqueryTracker["active_tweetbook"],
722 new AExpression("$mt.tweetid = " + deleteTweetCommentOnId.toString())
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700723 );
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700724 A.update(
725 toDelete.val()
726 );
727
728 // Hide comment from map
729 $('#drilldown_modal').modal('hide');
730
731 // Replot tweetbook
732 onPlotTweetbook(APIqueryTracker["active_tweetbook"]);
733 });
734
735 } else {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700736 // Show correct panel
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700737 $("#modal-existing-note").hide();
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700738 $("#modal-save-tweet-panel").show();
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700739
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700740 // Now, when adding a comment on an available tweet to a tweetbook
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700741 $('#save-comment-tweetbook-modal').unbind('click');
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700742 $("#save-comment-tweetbook-modal").on('click', function(e) {
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700743
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700744 // Stuff to save about new comment
745 var save_metacomment_target_tweetbook = $("#modal-body-add-to").val();
746 var save_metacomment_target_comment = '"' + $("#modal-body-add-note").val() + '"';
747 var save_metacomment_target_tweet = '"' + tweetId + '"';
748
749 // Make sure content is entered, and then save this comment.
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700750 if ($("#modal-body-add-note").val() == "") {
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700751
752 reportUserMessage("Please enter a comment about the tweet", false, "report-message");
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700753
754 } else if ($("#modal-body-add-to").val() == "") {
755
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700756 reportUserMessage("Please enter a tweetbook.", false, "report-message");
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700757
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700758 } else {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700759
760 // Check if tweetbook exists. If not, create it.
761 if (!(existsTweetbook(save_metacomment_target_tweetbook))) {
762 onCreateNewTweetBook(save_metacomment_target_tweetbook);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700763 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700764
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700765 var toInsert = new InsertStatement(
766 save_metacomment_target_tweetbook,
767 {
768 "tweetid" : save_metacomment_target_tweet.toString(),
769 "comment-text" : save_metacomment_target_comment
770 }
771 );
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700772
773 A.update(toInsert.val(), function () {
774 var successMessage = "Saved comment on <b>Tweet #" + tweetId +
775 "</b> in dataset <b>" + save_metacomment_target_tweetbook + "</b>.";
776 reportUserMessage(successMessage, true, "report-message");
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700777
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700778 $("#modal-body-add-to").val('');
779 $("#modal-body-add-note").val('');
780 $('#save-comment-tweetbook-modal').unbind('click');
781
782 // Close modal
783 $('#drilldown_modal').modal('hide');
784 });
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700785 }
786 });
787 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700788}
789
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700790/**
791* Adds a new tweetbook entry to the menu and creates a dataset of type TweetbookEntry.
792*/
793function onCreateNewTweetBook(tweetbook_title) {
794
795 var tweetbook_title = tweetbook_title.split(' ').join('_');
796
797 A.ddl(
798 "create dataset " + tweetbook_title + "(TweetbookEntry) primary key tweetid;",
799 function () {}
800 );
801
802 if (!(existsTweetbook(tweetbook_title))) {
803 review_mode_tweetbooks.push(tweetbook_title);
804 addTweetBookDropdownItem(tweetbook_title);
805 }
806}
807
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700808function onDropTweetBook(tweetbook_title) {
809
810 // AQL Call
811 A.ddl(
812 "drop dataset " + tweetbook_title + " if exists;",
813 function () {}
814 );
815
816 // Removes tweetbook from review_mode_tweetbooks
817 var remove_position = $.inArray(tweetbook_title, review_mode_tweetbooks);
818 if (remove_position >= 0) review_mode_tweetbooks.splice(remove_position, 1);
819
820 // Clear UI with review tweetbook titles
821 $('#review-tweetbook-titles').html('');
822 for (r in review_mode_tweetbooks) {
823 addTweetBookDropdownItem(review_mode_tweetbooks[r]);
824 }
825}
826
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700827function addTweetBookDropdownItem(tweetbook) {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700828 // Add placeholder for this tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700829 $('<div/>')
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700830 .attr({
831 "class" : "btn-group",
832 "id" : "rm_holder_" + tweetbook
833 }).appendTo("#review-tweetbook-titles");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700834
835 // Add plotting button for this tweetbook
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700836 var plot_button = '<button class="btn btn-default" id="rm_plotbook_' + tweetbook + '">' + tweetbook + '</button>';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700837 $("#rm_holder_" + tweetbook).append(plot_button);
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700838 $("#rm_plotbook_" + tweetbook).width("200px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700839 $("#rm_plotbook_" + tweetbook).on('click', function(e) {
840 onPlotTweetbook(tweetbook);
841 });
842
843 // Add trash button for this tweetbook
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700844 var onTrashTweetbookButton = addDeleteButton(
845 "rm_trashbook_" + tweetbook,
846 "rm_holder_" + tweetbook,
847 function(e) {
848 onDropTweetBook(tweetbook);
849 }
850 );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700851}
852
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700853function onPlotTweetbook(tweetbook) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700854
855 // Clear map for this one
856 mapWidgetResetMap();
857
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700858 var plotTweetQuery = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700859 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700860 .ForClause("$m", new AExpression("dataset " + tweetbook))
861 .WhereClause(new AExpression("$m.tweetid = $t.tweetid"))
862 .ReturnClause({
863 "tweetId" : "$m.tweetid",
864 "tweetText" : "$t.message-text",
865 "tweetLoc" : "$t.sender-location",
866 "tweetCom" : "$m.comment-text"
867 });
868
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700869 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700870 "query_string" : "use dataverse twitter;\n" + plotTweetQuery.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700871 "marker_path" : "static/img/mobile_green2.png",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700872 "on_clean_result" : onCleanPlotTweetbook,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700873 "active_tweetbook" : tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700874 };
875
876 A.query(plotTweetQuery.val(), onTweetbookQuerySuccessPlot);
877}
878
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700879function onTweetbookQuerySuccessPlot (res) {
880
881 var records = res["results"];
882
883 var coordinates = [];
884 map_tweet_markers = [];
885 map_tweet_overlays = [];
886 drilldown_data_map = {};
887 drilldown_data_map_vals = {};
888
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700889 var micon = APIqueryTracker["marker_path"];
890 var marker_click_function = onClickTweetbookMapMarker;
891 var clean_result_function = APIqueryTracker["on_clean_result"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700892
893 coordinates = clean_result_function(records);
894
895 for (var dm in coordinates) {
896 var keyLat = coordinates[dm].tweetLat.toString();
897 var keyLng = coordinates[dm].tweetLng.toString();
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700898
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700899 if (!drilldown_data_map.hasOwnProperty(keyLat)) {
900 drilldown_data_map[keyLat] = {};
901 }
902 if (!drilldown_data_map[keyLat].hasOwnProperty(keyLng)) {
903 drilldown_data_map[keyLat][keyLng] = [];
904 }
905 drilldown_data_map[keyLat][keyLng].push(coordinates[dm]);
906 drilldown_data_map_vals[coordinates[dm].tweetEntryId.toString()] = coordinates[dm];
907 }
908
909 $.each(drilldown_data_map, function(drillKeyLat, valuesAtLat) {
910 $.each(drilldown_data_map[drillKeyLat], function (drillKeyLng, valueAtLng) {
911
912 // Get subset of drilldown position on map
913 var cposition = new google.maps.LatLng(parseFloat(drillKeyLat), parseFloat(drillKeyLng));
914
915 // Create a marker using the snazzy phone icon
916 var map_tweet_m = new google.maps.Marker({
917 position: cposition,
918 map: map,
919 icon: micon,
920 clickable: true,
921 });
922
923 // Open Tweet exploration window on click
924 google.maps.event.addListener(map_tweet_m, 'click', function (event) {
925 marker_click_function(drilldown_data_map[drillKeyLat][drillKeyLng]);
926 });
927
928 // Add marker to index of tweets
929 map_tweet_markers.push(map_tweet_m);
930
931 });
932 });
933}
934
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700935function existsTweetbook(tweetbook) {
936 if (parseInt($.inArray(tweetbook, review_mode_tweetbooks)) == -1) {
937 return false;
938 } else {
939 return true;
940 }
941}
942
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700943function onCleanPlotTweetbook(records) {
944 var toPlot = [];
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700945
946 // An entry looks like this:
947 // { "tweetId": "273589", "tweetText": " like verizon the network is amazing", "tweetLoc": { point: [37.78, 82.27]}, "tweetCom": "hooray comments" }
948
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700949 for (var entry in records) {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700950
951 var points = records[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
952
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700953 var tweetbook_element = {
954 "tweetEntryId" : parseInt(records[entry].split(",")[0].split(":")[1].split('"')[1]),
955 "tweetText" : records[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700956 "tweetLat" : parseFloat(points[0]),
957 "tweetLng" : parseFloat(points[1]),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700958 "tweetComment" : records[entry].split("tweetCom\": \"")[1].split("\"")[0]
959 };
960 toPlot.push(tweetbook_element);
961 }
962
963 return toPlot;
964}
965
966function onCleanTweetbookDrilldown (rec) {
967
968 var drilldown_cleaned = [];
969
970 for (var entry = 0; entry < rec.length; entry++) {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700971
972 // An entry looks like this:
973 // { "tweetId": "105491", "tweetText": " hate verizon its platform is OMG", "tweetLoc": { point: [30.55, 71.44]} }
974 var points = rec[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
975
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700976 var drill_element = {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700977 "tweetEntryId" : parseInt(rec[entry].split(",")[0].split(":")[1].replace('"', '')),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700978 "tweetText" : rec[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700979 "tweetLat" : parseFloat(points[0]),
980 "tweetLng" : parseFloat(points[1])
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700981 };
982 drilldown_cleaned.push(drill_element);
983 }
984 return drilldown_cleaned;
985}
986
987function onClickTweetbookMapMarker(tweet_arr) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700988 // Clear existing display
989 $.each(tweet_arr, function (t, valueT) {
990 var tweet_obj = tweet_arr[t];
991 onDrillDownAtLocation(tweet_obj);
992 });
993
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700994 $('#drilldown_modal').modal();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700995}
996
997/** Toggling Review and Explore Modes **/
998
999/**
1000* Explore mode: Initial map creation and screen alignment
1001*/
1002function onOpenExploreMap () {
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -07001003 var explore_column_height = $('#explore-well').height();
1004 var right_column_width = $('#right-col').width();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001005 $('#map_canvas').height(explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -07001006 $('#map_canvas').width(right_column_width + "px");
1007
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001008 $('#review-well').height(explore_column_height + "px");
1009 $('#review-well').css('max-height', explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -07001010
1011 $('#right-col').height(explore_column_height + "px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001012}
1013
1014/**
1015* Launching explore mode: clear windows/variables, show correct sidebar
1016*/
1017function onLaunchExploreMode() {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001018 $('#aboutr').hide();
1019 $('#r1').show();
1020 $('#about-active').removeClass('active');
1021
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001022 $('#review-active').removeClass('active');
1023 $('#review-well').hide();
1024
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001025 $('#explore-active').addClass('active');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001026 $('#explore-well').show();
1027
1028 $("#clear-button").trigger("click");
1029}
1030
1031/**
1032* Launching review mode: clear windows/variables, show correct sidebar
1033*/
1034function onLaunchReviewMode() {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001035 $('#aboutr').hide();
1036 $('#r1').show();
1037 $('#about-active').removeClass('active');
1038
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001039 $('#explore-active').removeClass('active');
1040 $('#explore-well').hide();
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001041
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001042 $('#review-active').addClass('active');
1043 $('#review-well').show();
1044
1045 $("#clear-button").trigger("click");
1046}
1047
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001048/**
1049* Lauching about mode: hides all windows, shows row containing about info
1050*/
1051function onLaunchAboutMode() {
1052 $('#explore-active').removeClass('active');
1053 $('#review-active').removeClass('active');
1054 $('#about-active').addClass('active');
1055 $('#r1').hide();
1056 $('#aboutr').show();
1057}
1058
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001059/** Icon / Interface Utility Methods **/
1060
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001061/**
1062* Creates a delete icon button using default trash icon
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001063* @param {String} id, id for this element
1064* @param {String} attachTo, id string of an element to which I can attach this button.
1065* @param {Function} onClick, a function to fire when this icon is clicked
1066*/
1067function addDeleteButton(iconId, attachTo, onClick) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001068
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001069 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 -07001070 $('#' + attachTo).append(trashIcon);
1071
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001072 // When this trash button is clicked, the function is called.
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001073 $('#' + iconId).on('click', onClick);
1074}
1075
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001076/**
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001077* Creates a message and attaches it to data management area.
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001078* @param {String} message, a message to post
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001079* @param {Boolean} isPositiveMessage, whether or not this is a positive message.
1080* @param {String} target, the target div to attach this message.
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001081*/
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001082function reportUserMessage(message, isPositiveMessage, target) {
1083 // Clear out any existing messages
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001084 $('#' + target).html('');
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001085
1086 // Select appropriate alert-type
1087 var alertType = "alert-success";
1088 if (!isPositiveMessage) {
1089 alertType = "alert-danger";
1090 }
1091
1092 // Append the appropriate message
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001093 $('<div/>')
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001094 .attr("class", "alert " + alertType)
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001095 .html('<button type="button" class="close" data-dismiss="alert">&times;</button>' + message)
1096 .appendTo('#' + target);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001097}
1098
1099/**
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001100* mapWidgetResetMap
1101*
1102* [No Parameters]
1103*
1104* Clears ALL map elements - plotted items, overlays, then resets position
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001105*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001106function mapWidgetResetMap() {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001107
1108 if (selectionRect) {
1109 selectionRect.setMap(null);
1110 selectionRect = null;
1111 }
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001112
1113 mapWidgetClearMap();
1114
1115 // Reset map center and zoom
1116 map.setCenter(new google.maps.LatLng(38.89, -77.03));
1117 map.setZoom(4);
1118}
1119
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001120/**
1121* mapWidgetClearMap
1122*
1123* No parameters
1124*
1125* Removes data/markers
1126*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001127function mapWidgetClearMap() {
1128
1129 // Remove previously plotted data/markers
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001130 for (c in map_cells) {
1131 map_cells[c].setMap(null);
1132 }
1133 map_cells = [];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -07001134
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001135 $.each(map_info_windows, function(i) {
1136 map_info_windows[i].close();
1137 });
1138 map_info_windows = {};
1139
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001140 for (m in map_tweet_markers) {
1141 map_tweet_markers[m].setMap(null);
1142 }
1143 map_tweet_markers = [];
Eugenia Gabrielova12de0302013-10-18 02:25:39 -07001144
1145 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001146}
1147
1148/**
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001149* buildLegend
1150*
1151* no params
1152*
1153* Generates gradient, button action for legend bar
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001154*/
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001155function buildLegend() {
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001156
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001157 // Fill in legend area with colors
1158 var gradientColor;
1159
1160 for (i = 0; i=100; i++) {
1161 $("#rainbow-legend-container").append("" + rainbow.colourAt(i));
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001162 }
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001163
1164 // Window clear button closes all info count windows
1165 $("#windows-off-btn").on("click", function(e) {
1166 $.each(map_info_windows, function(i) {
1167 map_info_windows[i].close();
1168 });
1169 });
1170}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001171
1172/**
1173* Computes radius for a given data point from a spatial cell
1174* @param {Object} keys => ["latSW" "lngSW" "latNE" "lngNE" "weight"]
1175* @returns {number} radius between 2 points in metres
1176*/
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001177function mapWidgetComputeCircleRadius(spatialCell, wLimit) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001178
1179 // Define Boundary Points
1180 var point_center = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1181 var point_left = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, spatialCell.lngSW);
1182 var point_top = new google.maps.LatLng(spatialCell.latNE, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1183
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001184 // Circle scale modifier =
1185 var scale = 500 + 500*(spatialCell.weight / wLimit);
1186
1187 // Return proportionate value so that circles mostly line up.
1188 return scale * Math.min(distanceBetweenPoints_(point_center, point_left), distanceBetweenPoints_(point_center, point_top));
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001189}
1190
1191/** External Utility Methods **/
1192
1193/**
1194 * Calculates the distance between two latlng locations in km.
1195 * @see http://www.movable-type.co.uk/scripts/latlong.html
1196 *
1197 * @param {google.maps.LatLng} p1 The first lat lng point.
1198 * @param {google.maps.LatLng} p2 The second lat lng point.
1199 * @return {number} The distance between the two points in km.
1200 * @private
1201*/
1202function distanceBetweenPoints_(p1, p2) {
1203 if (!p1 || !p2) {
1204 return 0;
1205 }
1206
1207 var R = 6371; // Radius of the Earth in km
1208 var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
1209 var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
1210 var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
1211 Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
1212 Math.sin(dLon / 2) * Math.sin(dLon / 2);
1213 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1214 var d = R * c;
1215 return d;
1216};