blob: 898afb23e1517cbe24c32cbb4258c6cb8af34bed [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);
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -070072
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070073 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
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -070089 // Drawing Manager for selecting map regions. See documentation here:
90 // https://developers.google.com/maps/documentation/javascript/reference#DrawingManager
91 rectangleManager = new google.maps.drawing.DrawingManager({
92 drawingMode : google.maps.drawing.OverlayType.RECTANGLE,
93 drawingControl : false,
94 rectangleOptions : {
95 strokeWeight: 1,
96 clickable: false,
97 editable: true,
98 strokeColor: "2b3f8c",
99 fillColor: "2b3f8c",
100 zIndex: 1
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700101 }
102 });
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700103 rectangleManager.setMap(map);
104 selectionRectangle = null;
105
106 // Drawing Manager: Just one editable rectangle!
107 google.maps.event.addListener(rectangleManager, 'rectanglecomplete', function(rectangle) {
108 selectionRectangle = rectangle;
109 rectangleManager.setDrawingMode(null);
110 });
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700111
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700112 // Open about tab to start user on a tutorial
113 //$('#mode-tabs a:first').tab('show') // Select first tab
114
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700115 // Initialize data structures
116 APIqueryTracker = {};
117 asyncQueryManager = {};
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700118 map_cells = [];
119 map_tweet_markers = [];
120 map_info_windows = {};
121 review_mode_tweetbooks = [];
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700122
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700123 getAllDataverseTweetbooks();
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700124 initDemoUIButtonControls();
125});
126
127function initDemoUIButtonControls() {
128
129 // Explore Mode - Update Sliders
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700130 var updateSliderDisplay = function(event, ui) {
131 if (event.target.id == "grid-lat-slider") {
132 $("#gridlat").text(""+ui.value);
133 } else {
134 $("#gridlng").text(""+ui.value);
135 }
136 };
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700137 sliderOptions = {
138 max: 10,
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700139 min: 2.0,
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700140 step: .1,
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700141 value: 3.0,
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700142 slidechange: updateSliderDisplay,
143 slide: updateSliderDisplay,
144 start: updateSliderDisplay,
145 stop: updateSliderDisplay
146 };
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700147 $("#gridlat").text(""+sliderOptions.value);
148 $("#gridlng").text(""+sliderOptions.value);
149 $(".grid-slider").slider(sliderOptions);
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700150
151 // Explore Mode - Query Builder Date Pickers
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700152 var dateOptions = {
153 dateFormat: "yy-mm-dd",
154 defaultDate: "2012-01-02",
155 navigationAsDateFormat: true,
156 constrainInput: true
157 };
158 var start_dp = $("#start-date").datepicker(dateOptions);
159 start_dp.val(dateOptions.defaultDate);
160 dateOptions['defaultDate'] = "2012-12-31";
161 var end_dp= $("#end-date").datepicker(dateOptions);
162 end_dp.val(dateOptions.defaultDate);
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700163
164 // Explore Mode: Toggle Selection/Location Search
Eugenia Gabrielova3d7bfe52013-10-28 02:36:44 -0700165 $('#selection-button').on('change', function (e) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700166 $("#location-text-box").attr("disabled", "disabled");
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700167 rectangleManager.setMap(map);
168 if (selectionRectangle) {
169 selectionRectangle.setMap(null);
170 selectionRectangle = null;
171 } else {
172 rectangleManager.setDrawingMode(google.maps.drawing.OverlayType.RECTANGLE);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700173 }
174 });
Eugenia Gabrielova3d7bfe52013-10-28 02:36:44 -0700175 $('#location-button').on('change', function (e) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700176 $("#location-text-box").removeAttr("disabled");
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700177 rectangleManager.setMap(null);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700178 });
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700179 $("#selection-button").trigger("click");
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700180
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700181 // Review Mode: New Tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700182 $('#new-tweetbook-button').on('click', function (e) {
183 onCreateNewTweetBook($('#new-tweetbook-entry').val());
184
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700185 $('#new-tweetbook-entry').val("");
186 $('#new-tweetbook-entry').attr("placeholder", "Name a new tweetbook");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700187 });
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700188
189 // Explore Mode - Clear Button
190 $("#clear-button").click(mapWidgetResetMap);
191
192 // Explore Mode: Query Submission
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700193 $("#submit-button").on("click", function () {
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700194
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700195 $("#report-message").html('');
196 $("#submit-button").attr("disabled", true);
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700197 rectangleManager.setDrawingMode(null);
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -0700198 $("body").css("cursor", "progress");
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700199
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700200 var kwterm = $("#keyword-textbox").val();
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700201 var startdp = $("#start-date").datepicker("getDate");
202 var enddp = $("#end-date").datepicker("getDate");
203 var startdt = $.datepicker.formatDate("yy-mm-dd", startdp)+"T00:00:00Z";
204 var enddt = $.datepicker.formatDate("yy-mm-dd", enddp)+"T23:59:59Z";
205
206 var formData = {
207 "keyword": kwterm,
208 "startdt": startdt,
209 "enddt": enddt,
210 "gridlat": $("#grid-lat-slider").slider("value"),
211 "gridlng": $("#grid-lng-slider").slider("value")
212 };
213
214 // Get Map Bounds
215 var bounds;
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700216 if ($('#selection-label').hasClass("active") && selectionRectangle) {
217 bounds = selectionRectangle.getBounds();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700218 } else {
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700219 bounds = map.getBounds();
220 }
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700221
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700222 var swLat = Math.abs(bounds.getSouthWest().lat());
223 var swLng = Math.abs(bounds.getSouthWest().lng());
224 var neLat = Math.abs(bounds.getNorthEast().lat());
225 var neLng = Math.abs(bounds.getNorthEast().lng());
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700226
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700227 formData["swLat"] = Math.min(swLat, neLat);
228 formData["swLng"] = Math.max(swLng, neLng);
229 formData["neLat"] = Math.max(swLat, neLat);
230 formData["neLng"] = Math.min(swLng, neLng);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700231
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700232 var build_cherry_mode = "synchronous";
233 if ($('#asbox').is(":checked")) {
234 build_cherry_mode = "asynchronous";
235 //$('#show-query-button').attr("disabled", false);
236 } else {
237 //$('#show-query-button').attr("disabled", true);
238 }
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700239
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700240 var f = buildAQLQueryFromForm(formData);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700241
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700242 APIqueryTracker = {
243 "query" : "use dataverse twitter;\n" + f.val(),
244 "data" : formData
245 };
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700246
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700247 // TODO Make dialog work correctly.
248 //$('#dialog').html(APIqueryTracker["query"]);
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700249
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700250 if (build_cherry_mode == "synchronous") {
251 A.query(f.val(), cherryQuerySyncCallback, build_cherry_mode);
252 } else {
253 A.query(f.val(), cherryQueryAsyncCallback, build_cherry_mode);
254 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700255
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700256 // Clears selection rectangle on query execution, rather than waiting for another clear call.
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700257 if (selectionRectangle) {
258 selectionRectangle.setMap(null);
259 selectionRectangle = null;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700260 }
261 });
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700262}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700263
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700264/**
265* Builds AsterixDB REST Query from explore mode form.
266*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700267function buildAQLQueryFromForm(parameters) {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700268
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700269 var bounds = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700270 "ne" : { "lat" : parameters["neLat"], "lng" : -1*parameters["neLng"]},
271 "sw" : { "lat" : parameters["swLat"], "lng" : -1*parameters["swLng"]}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700272 };
273
274 var rectangle =
275 new FunctionExpression("create-rectangle",
276 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
277 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
278
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700279 // You can chain these all together, but let's take them one at a time.
280 // Let's start with a ForClause. Here we go through each tweet $t in the
281 // dataset TweetMessageShifted.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700282 var aql = new FLWOGRExpression()
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700283 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"));
284
285 // We know we have bounds for our region, so we can add that LetClause next.
286 aql = aql.LetClause("$region", rectangle);
287
288 // Now, let's change it up. The keyword term doesn't always show up, so it might be blank.
289 // We'll attach a new let clause for it, and then a WhereClause.
290 if (parameters["keyword"].length > 0) {
291 aql = aql
292 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
293 .WhereClause().and(
294 new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
295 new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
296 new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")'),
297 new FunctionExpression("contains", "$t.message-text", "$keyword")
298 );
299 } else {
300 aql = aql
301 .WhereClause().and(
302 new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
303 new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
304 new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")')
305 );
306 }
307
308 // Finally, we'll group our results into spatial cells.
309 aql = aql.GroupClause(
310 "$c",
311 new FunctionExpression("spatial-cell", "$t.sender-location",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700312 new FunctionExpression("create-point", "24.5", "-125.5"),
313 parameters["gridlat"].toFixed(1), parameters["gridlng"].toFixed(1)),
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700314 "with",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700315 "$t"
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700316 );
317
318 // ...and return a resulting cell and a count of results in that cell.
319 aql = aql.ReturnClause({ "cell" : "$c", "count" : "count($t)" });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700320
321 return aql;
322}
323
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700324/**
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700325* getAllDataverseTweetbooks
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700326*
327* Returns all datasets of type TweetbookEntry, populates review_mode_tweetbooks
328*/
329function getAllDataverseTweetbooks(fn_tweetbooks) {
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700330
331 // This creates a query to the Metadata for datasets of type
332 // TweetBookEntry. Note that if we throw in a WhereClause (commented out below)
333 // there is an odd error. This is being fixed and will be removed from this demo.
334 var getTweetbooksQuery = new FLWOGRExpression()
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700335 .ForClause("$ds", new AExpression("dataset Metadata.Dataset"))
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700336 //.WhereClause(new AExpression('$ds.DataTypeName = "TweetbookEntry"'))
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700337 .ReturnClause({
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700338 "DataTypeName" : "$ds.DataTypeName",
339 "DatasetName" : "$ds.DatasetName"
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700340 });
341
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700342 // Now create a function that will be called when tweetbooks succeed.
343 // In this case, we want to parse out the results object from the Asterix
344 // REST API response.
345 var tweetbooksSuccess = function(r) {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700346 // Parse tweetbook metadata results
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700347 $.each(r.results, function(i, data) {
348 if ($.parseJSON(data)["DataTypeName"] == "TweetbookEntry") {
349 review_mode_tweetbooks.push($.parseJSON(data)["DatasetName"]);
350 }
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700351 });
352
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700353 // Now, if any tweetbooks already exist, opulate review screen.
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700354 $('#review-tweetbook-titles').html('');
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700355 $.each(review_mode_tweetbooks, function(i, tweetbook) {
356 addTweetBookDropdownItem(tweetbook);
357 });
358 };
359
360 // Now, we are ready to run a query.
361 A.meta(getTweetbooksQuery.val(), tweetbooksSuccess);
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700362}
363
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700364/**
365* Checks through each asynchronous query to see if they are ready yet
366*/
367function asynchronousQueryIntervalUpdate() {
368 for (var handle_key in asyncQueryManager) {
369 if (!asyncQueryManager[handle_key].hasOwnProperty("ready")) {
370 asynchronousQueryGetAPIQueryStatus( asyncQueryManager[handle_key]["handle"], handle_key );
371 }
372 }
373}
374
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700375/**
376* Returns current time interval to check for asynchronous query readiness
377* @returns {number} milliseconds between asychronous query checks
378*/
379function asynchronousQueryGetInterval() {
380 var seconds = 10;
381 return seconds * 1000;
382}
383
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700384/**
385* Retrieves status of an asynchronous query, using an opaque result handle from API
386* @param {Object} handle, an object previously returned from an async call
387* @param {number} handle_id, the integer ID parsed from the handle object
388*/
389function asynchronousQueryGetAPIQueryStatus (handle, handle_id) {
390
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700391 A.query_status(
392 {
393 "handle" : JSON.stringify(handle)
394 },
395 function (res) {
396 if (res["status"] == "SUCCESS") {
397 // We don't need to check if this one is ready again, it's not going anywhere...
398 // Unless the life cycle of handles has changed drastically
399 asyncQueryManager[handle_id]["ready"] = true;
400
401 // Indicate success.
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700402 $('#handle_' + handle_id).removeClass("btn-disabled").prop('disabled', false).addClass("btn-success");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700403 }
404 }
405 );
406}
407
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700408/**
409* On-success callback after async API query
410* @param {object} res, a result object containing an opaque result handle to Asterix
411*/
412function cherryQueryAsyncCallback(res) {
413
414 // Parse handle, handle id and query from async call result
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700415 var handle_query = APIqueryTracker["query"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700416 var handle = res;
417 var handle_id = res["handle"].toString().split(',')[0];
418
419 // Add to stored map of existing handles
420 asyncQueryManager[handle_id] = {
421 "handle" : handle,
422 "query" : handle_query,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700423 "data" : APIqueryTracker["data"]
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700424 };
425
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700426 // Create a container for this async query handle
427 $('<div/>')
428 .css("margin-left", "1em")
429 .css("margin-bottom", "1em")
430 .css("display", "block")
431 .attr({
432 "class" : "btn-group",
433 "id" : "async_container_" + handle_id
434 })
435 .appendTo("#async-handle-controls");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700436
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700437 // Adds the main button for this async handle
438 var handle_action_button = '<button class="btn btn-disabled" id="handle_' + handle_id + '">Handle ' + handle_id + '</button>';
439 $('#async_container_' + handle_id).append(handle_action_button);
440 $('#handle_' + handle_id).prop('disabled', true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700441 $('#handle_' + handle_id).on('click', function (e) {
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700442
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700443 // make sure query is ready to be run
444 if (asyncQueryManager[handle_id]["ready"]) {
445
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700446 APIqueryTracker = {
447 "query" : asyncQueryManager[handle_id]["query"],
448 "data" : asyncQueryManager[handle_id]["data"]
449 };
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700450 // TODO
451 //$('#dialog').html(APIqueryTracker["query"]);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700452
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700453 if (!asyncQueryManager[handle_id].hasOwnProperty("result")) {
454 // Generate new Asterix Core API Query
455 A.query_result(
456 { "handle" : JSON.stringify(asyncQueryManager[handle_id]["handle"]) },
457 function(res) {
458 asyncQueryManager[handle_id]["result"] = res;
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700459
460 var resultTransform = {
461 "results" : res.results[0]
462 };
463
464 cherryQuerySyncCallback(resultTransform);
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700465 }
466 );
467 } else {
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700468
469 var resultTransform = {
470 "results" : asyncQueryManager[handle_id]["result"].results[0]
471 };
472
473 cherryQuerySyncCallback(resultTransform);
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700474 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700475 }
476 });
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700477
478 // Adds a removal button for this async handle
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700479 var asyncDeleteButton = addDeleteButton(
480 "trashhandle_" + handle_id,
481 "async_container_" + handle_id,
482 function (e) {
483 $('#async_container_' + handle_id).remove();
484 delete asyncQueryManager[handle_id];
485 }
486 );
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700487
488 $('#async_container_' + handle_id).append('<br/>');
489
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -0700490 $("body").css("cursor", "default");
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700491 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700492}
493
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700494/**
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700495* A spatial data cleaning and mapping call
496* @param {Object} res, a result object from a cherry geospatial query
497*/
498function cherryQuerySyncCallback(res) {
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700499
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700500 // Initialize coordinates and weights, to store
501 // coordinates of map cells and their weights
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700502 var coordinates = [];
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700503 var maxWeight = 0;
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700504 var minWeight = Number.MAX_VALUE;
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700505
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700506 // Parse resulting JSON objects. Here is an example record:
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700507 // { "cell": rectangle("21.5,-98.5 24.5,-95.5"), "count": 78i64 }
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700508 $.each(res.results, function(i, data) {
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700509
510 // We need to clean the JSON a bit to parse it properly in javascript
511 var cleanRecord = $.parseJSON(data
512 .replace('rectangle(', '')
513 .replace(')', '')
514 .replace('i64', ''));
515
516 var recordCount = cleanRecord["count"];
517 var rectangle = cleanRecord["cell"]
518 .replace(' ', ',')
519 .split(',')
520 .map( parseFloat );
521
522 // Now, using the record count and coordinates, we can create a
523 // coordinate system for this spatial cell.
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700524 var coordinate = {
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700525 "latSW" : rectangle[0],
526 "lngSW" : rectangle[1],
527 "latNE" : rectangle[2],
528 "lngNE" : rectangle[3],
529 "weight" : recordCount
530 };
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700531
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700532 // We track the minimum and maximum weight to support our legend.
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700533 maxWeight = Math.max(coordinate["weight"], maxWeight);
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700534 minWeight = Math.min(coordinate["weight"], minWeight);
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700535
536 // Save completed coordinate and move to next one.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700537 coordinates.push(coordinate);
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700538 });
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700539
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700540 triggerUIUpdate(coordinates, maxWeight, minWeight);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700541}
542
543/**
544* Triggers a map update based on a set of spatial query result cells
545* @param [Array] mapPlotData, an array of coordinate and weight objects
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700546* @param [Array] plotWeights, a list of weights of the spatial cells - e.g., number of tweets
547*/
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700548function triggerUIUpdate(mapPlotData, maxWeight, minWeight) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700549 /** Clear anything currently on the map **/
550 mapWidgetClearMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700551
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700552 // Initialize info windows.
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700553 map_info_windows = {};
554
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700555 $.each(mapPlotData, function (m) {
556
557 var point_center = new google.maps.LatLng(
558 (mapPlotData[m].latSW + mapPlotData[m].latNE)/2.0,
559 (mapPlotData[m].lngSW + mapPlotData[m].lngNE)/2.0);
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700560
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700561 var map_circle_options = {
562 center: point_center,
563 anchorPoint: point_center,
564 radius: mapWidgetComputeCircleRadius(mapPlotData[m], maxWeight),
565 map: map,
566 fillOpacity: 0.85,
567 fillColor: rainbow.colourAt(Math.ceil(100 * (mapPlotData[m].weight / maxWeight))),
568 clickable: true
569 };
570 var map_circle = new google.maps.Circle(map_circle_options);
571 map_circle.val = mapPlotData[m];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700572
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700573 map_info_windows[m] = new google.maps.InfoWindow({
574 content: mapPlotData[m].weight + " tweets",
575 position: point_center
576 });
577
578 // Clicking on a circle drills down map to that value, hovering over it displays a count
579 // of tweets at that location.
580 google.maps.event.addListener(map_circle, 'click', function (event) {
581 $.each(map_info_windows, function(i) {
582 map_info_windows[i].close();
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700583 });
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700584 onMapPointDrillDown(map_circle.val);
585 });
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700586
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700587 google.maps.event.addListener(map_circle, 'mouseover', function(event) {
588 if (!map_info_windows[m].getMap()) {
589 map_info_windows[m].setPosition(map_circle.center);
590 map_info_windows[m].open(map);
591 }
592 });
593
594 // Add this marker to global marker cells
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700595 map_cells.push(map_circle);
596
597 // Show legend
598 $("#legend-min").html(minWeight);
599 $("#legend-max").html(maxWeight);
600 $("#rainbow-legend-container").show();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700601 });
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -0700602
603 $("body").css("cursor", "default");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700604}
605
606/**
607* prepares an Asterix API query to drill down in a rectangular spatial zone
608*
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700609* @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 -0700610*/
611function onMapPointDrillDown(marker_borders) {
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -0700612
613 $("body").css("cursor", "progress");
614
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700615 var zoneData = APIqueryTracker["data"];
616
617 var zswBounds = new google.maps.LatLng(marker_borders.latSW, marker_borders.lngSW);
618 var zneBounds = new google.maps.LatLng(marker_borders.latNE, marker_borders.lngNE);
619
620 var zoneBounds = new google.maps.LatLngBounds(zswBounds, zneBounds);
621 zoneData["swLat"] = zoneBounds.getSouthWest().lat();
622 zoneData["swLng"] = zoneBounds.getSouthWest().lng();
623 zoneData["neLat"] = zoneBounds.getNorthEast().lat();
624 zoneData["neLng"] = zoneBounds.getNorthEast().lng();
625 var zB = {
626 "sw" : {
627 "lat" : zoneBounds.getSouthWest().lat(),
628 "lng" : zoneBounds.getSouthWest().lng()
629 },
630 "ne" : {
631 "lat" : zoneBounds.getNorthEast().lat(),
632 "lng" : zoneBounds.getNorthEast().lng()
633 }
634 };
635
636 mapWidgetClearMap();
637
638 var customBounds = new google.maps.LatLngBounds();
639 var zoomSWBounds = new google.maps.LatLng(zoneData["swLat"], zoneData["swLng"]);
640 var zoomNEBounds = new google.maps.LatLng(zoneData["neLat"], zoneData["neLng"]);
641 customBounds.extend(zoomSWBounds);
642 customBounds.extend(zoomNEBounds);
643 map.fitBounds(customBounds);
644
645 var df = getDrillDownQuery(zoneData, zB);
646
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700647 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700648 "query_string" : "use dataverse twitter;\n" + df.val(),
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700649 "marker_path" : "static/img/mobile2.png"
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700650 };
651
652 A.query(df.val(), onTweetbookQuerySuccessPlot);
653}
654
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700655/**
656* Generates an aql query for zooming on a spatial cell and obtaining tweets contained therein.
657* @param parameters, the original query parameters
658* @param bounds, the bounds of the zone to zoom in on.
659*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700660function getDrillDownQuery(parameters, bounds) {
661
662 var zoomRectangle = new FunctionExpression("create-rectangle",
663 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
664 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
665
666 var drillDown = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700667 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700668 .LetClause("$region", zoomRectangle);
669
670 if (parameters["keyword"].length == 0) {
671 drillDown = drillDown
672 .WhereClause().and(
673 new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
674 new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
675 new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")')
676 );
677 } else {
678 drillDown = drillDown
679 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
680 .WhereClause().and(
681 new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
682 new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
683 new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")'),
684 new FunctionExpression('contains', '$t.message-text', '$keyword')
685 );
686 }
687
688 drillDown = drillDown
689 .ReturnClause({
690 "tweetId" : "$t.tweetid",
691 "tweetText" : "$t.message-text",
692 "tweetLoc" : "$t.sender-location"
693 });
694
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700695 return drillDown;
696}
697
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700698/**
699* Given a location where a tweet exists, opens a modal to examine or update a tweet's content.
700* @param t0, a tweetobject that has a location, text, id, and optionally a comment.
701*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700702function onDrillDownAtLocation(tO) {
703
704 var tweetId = tO["tweetEntryId"];
705 var tweetText = tO["tweetText"];
706
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700707 // First, set tweet in drilldown modal to be this tweet's text
708 $('#modal-body-tweet').html('Tweet #' + tweetId + ": " + tweetText);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700709
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700710 // Next, empty any leftover tweetbook comments or error/success messages
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700711 $("#modal-body-add-to").val('');
712 $("#modal-body-add-note").val('');
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700713 $("#modal-body-message-holder").html("");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700714
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700715 // Next, if there is an existing tweetcomment reported, show it.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700716 if (tO.hasOwnProperty("tweetComment")) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700717
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700718 // Show correct panel
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700719 $("#modal-existing-note").show();
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700720 $("#modal-save-tweet-panel").hide();
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700721
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700722 // Fill in existing tweet comment
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700723 $("#modal-body-tweet-note").val(tO["tweetComment"]);
724
725 // Change Tweetbook Badge
726 $("#modal-current-tweetbook").val(APIqueryTracker["active_tweetbook"]);
727
728 // Add deletion functionality
729 $("#modal-body-trash-icon").on('click', function () {
730 // Send comment deletion to asterix
731 var deleteTweetCommentOnId = '"' + tweetId + '"';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700732 var toDelete = new DeleteStatement(
733 "$mt",
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700734 APIqueryTracker["active_tweetbook"],
735 new AExpression("$mt.tweetid = " + deleteTweetCommentOnId.toString())
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700736 );
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700737 A.update(
738 toDelete.val()
739 );
740
741 // Hide comment from map
742 $('#drilldown_modal').modal('hide');
743
744 // Replot tweetbook
745 onPlotTweetbook(APIqueryTracker["active_tweetbook"]);
746 });
747
748 } else {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700749 // Show correct panel
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700750 $("#modal-existing-note").hide();
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700751 $("#modal-save-tweet-panel").show();
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700752
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700753 // Now, when adding a comment on an available tweet to a tweetbook
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700754 $('#save-comment-tweetbook-modal').unbind('click');
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700755 $("#save-comment-tweetbook-modal").on('click', function(e) {
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700756
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700757 // Stuff to save about new comment
758 var save_metacomment_target_tweetbook = $("#modal-body-add-to").val();
759 var save_metacomment_target_comment = '"' + $("#modal-body-add-note").val() + '"';
760 var save_metacomment_target_tweet = '"' + tweetId + '"';
761
762 // Make sure content is entered, and then save this comment.
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700763 if ($("#modal-body-add-note").val() == "") {
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700764
765 reportUserMessage("Please enter a comment about the tweet", false, "report-message");
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700766
767 } else if ($("#modal-body-add-to").val() == "") {
768
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700769 reportUserMessage("Please enter a tweetbook.", false, "report-message");
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700770
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700771 } else {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700772
773 // Check if tweetbook exists. If not, create it.
774 if (!(existsTweetbook(save_metacomment_target_tweetbook))) {
775 onCreateNewTweetBook(save_metacomment_target_tweetbook);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700776 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700777
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700778 var toInsert = new InsertStatement(
779 save_metacomment_target_tweetbook,
780 {
781 "tweetid" : save_metacomment_target_tweet.toString(),
782 "comment-text" : save_metacomment_target_comment
783 }
784 );
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700785
786 A.update(toInsert.val(), function () {
787 var successMessage = "Saved comment on <b>Tweet #" + tweetId +
788 "</b> in dataset <b>" + save_metacomment_target_tweetbook + "</b>.";
789 reportUserMessage(successMessage, true, "report-message");
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700790
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700791 $("#modal-body-add-to").val('');
792 $("#modal-body-add-note").val('');
793 $('#save-comment-tweetbook-modal').unbind('click');
794
795 // Close modal
796 $('#drilldown_modal').modal('hide');
797 });
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700798 }
799 });
800 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700801}
802
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700803/**
804* Adds a new tweetbook entry to the menu and creates a dataset of type TweetbookEntry.
805*/
806function onCreateNewTweetBook(tweetbook_title) {
807
808 var tweetbook_title = tweetbook_title.split(' ').join('_');
809
810 A.ddl(
811 "create dataset " + tweetbook_title + "(TweetbookEntry) primary key tweetid;",
812 function () {}
813 );
814
815 if (!(existsTweetbook(tweetbook_title))) {
816 review_mode_tweetbooks.push(tweetbook_title);
817 addTweetBookDropdownItem(tweetbook_title);
818 }
819}
820
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700821/**
822* Removes a tweetbook from both demo and from
823* dataverse metadata.
824*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700825function onDropTweetBook(tweetbook_title) {
826
827 // AQL Call
828 A.ddl(
829 "drop dataset " + tweetbook_title + " if exists;",
830 function () {}
831 );
832
833 // Removes tweetbook from review_mode_tweetbooks
834 var remove_position = $.inArray(tweetbook_title, review_mode_tweetbooks);
835 if (remove_position >= 0) review_mode_tweetbooks.splice(remove_position, 1);
836
837 // Clear UI with review tweetbook titles
838 $('#review-tweetbook-titles').html('');
839 for (r in review_mode_tweetbooks) {
840 addTweetBookDropdownItem(review_mode_tweetbooks[r]);
841 }
842}
843
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700844/**
845* Adds a tweetbook action button to the dropdown box in review mode.
846* @param tweetbook, a string representing a tweetbook
847*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700848function addTweetBookDropdownItem(tweetbook) {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700849 // Add placeholder for this tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700850 $('<div/>')
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700851 .attr({
852 "class" : "btn-group",
853 "id" : "rm_holder_" + tweetbook
854 }).appendTo("#review-tweetbook-titles");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700855
856 // Add plotting button for this tweetbook
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700857 var plot_button = '<button class="btn btn-default" id="rm_plotbook_' + tweetbook + '">' + tweetbook + '</button>';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700858 $("#rm_holder_" + tweetbook).append(plot_button);
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700859 $("#rm_plotbook_" + tweetbook).width("200px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700860 $("#rm_plotbook_" + tweetbook).on('click', function(e) {
861 onPlotTweetbook(tweetbook);
862 });
863
864 // Add trash button for this tweetbook
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700865 var onTrashTweetbookButton = addDeleteButton(
866 "rm_trashbook_" + tweetbook,
867 "rm_holder_" + tweetbook,
868 function(e) {
869 onDropTweetBook(tweetbook);
870 }
871 );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700872}
873
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700874/**
875* Generates AsterixDB query to plot existing tweetbook commnets
876* @param tweetbook, a string representing a tweetbook
877*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700878function onPlotTweetbook(tweetbook) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700879
880 // Clear map for this one
881 mapWidgetResetMap();
882
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700883 var plotTweetQuery = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700884 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700885 .ForClause("$m", new AExpression("dataset " + tweetbook))
886 .WhereClause(new AExpression("$m.tweetid = $t.tweetid"))
887 .ReturnClause({
888 "tweetId" : "$m.tweetid",
889 "tweetText" : "$t.message-text",
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700890 "tweetCom" : "$m.comment-text",
891 "tweetLoc" : "$t.sender-location"
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700892 });
893
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700894 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700895 "query_string" : "use dataverse twitter;\n" + plotTweetQuery.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700896 "marker_path" : "static/img/mobile_green2.png",
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700897 "active_tweetbook" : tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700898 };
899
900 A.query(plotTweetQuery.val(), onTweetbookQuerySuccessPlot);
901}
902
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700903/**
904* Given an output response set of tweet data,
905* prepares markers on map to represent individual tweets.
906* @param res, a JSON Object
907*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700908function onTweetbookQuerySuccessPlot (res) {
909
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700910 // Parse out tweet Ids, texts, and locations
911 var tweets = [];
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700912 var al = 1;
913
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700914 $.each(res.results, function(i, data) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700915
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700916 // First, clean up the data
917 //{ "tweetId": "100293", "tweetText": " like at&t the touch-screen is amazing", "tweetLoc": point("31.59,-84.23") }
918 // We need to turn the point object at the end into a string
919 var json = $.parseJSON(data
920 .replace(': point(',': ')
921 .replace(') }', ' }'));
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700922
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700923 // Now, we construct a tweet object
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700924 var tweetData = {
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700925 "tweetEntryId" : parseInt(json.tweetId),
926 "tweetText" : json.tweetText,
927 "tweetLat" : json.tweetLoc.split(",")[0],
928 "tweetLng" : json.tweetLoc.split(",")[1]
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700929 };
930
931 // If we are parsing out tweetbook data with comments, we need to check
932 // for those here as well.
933 if (json.hasOwnProperty("tweetCom")) {
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700934 tweetData["tweetComment"] = json.tweetCom;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700935 }
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700936
937 tweets.push(tweetData)
938 });
939
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700940 // Create a marker for each tweet
941 $.each(tweets, function(i, t) {
942 // Create a phone marker at tweet's position
943 var map_tweet_m = new google.maps.Marker({
944 position: new google.maps.LatLng(tweets[i]["tweetLat"], tweets[i]["tweetLng"]),
945 map: map,
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700946 icon: APIqueryTracker["marker_path"],
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700947 clickable: true,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700948 });
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700949 map_tweet_m["test"] = t;
950
951 // Open Tweet exploration window on click
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700952 google.maps.event.addListener(map_tweet_m, 'click', function (event) {
953 onClickTweetbookMapMarker(map_tweet_markers[i]["test"]);
954 });
955
956 // Add marker to index of tweets
957 map_tweet_markers.push(map_tweet_m);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700958 });
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -0700959
960 $("body").css("cursor", "default");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700961}
962
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700963/**
964* Checks if a tweetbook exists
965* @param tweetbook, a String
966*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700967function existsTweetbook(tweetbook) {
968 if (parseInt($.inArray(tweetbook, review_mode_tweetbooks)) == -1) {
969 return false;
970 } else {
971 return true;
972 }
973}
974
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700975/**
976* When a marker is clicked on in the tweetbook, it will launch a modal
977* view to examine or edit the appropriate tweet
978*/
979function onClickTweetbookMapMarker(t) {
980 onDrillDownAtLocation(t)
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700981 $('#drilldown_modal').modal();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700982}
983
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700984/**
985* Explore mode: Initial map creation and screen alignment
986*/
987function onOpenExploreMap () {
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700988 var explore_column_height = $('#explore-well').height();
989 var right_column_width = $('#right-col').width();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700990 $('#map_canvas').height(explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700991 $('#map_canvas').width(right_column_width + "px");
992
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700993 $('#review-well').height(explore_column_height + "px");
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700994 $('#review-well').css('max-height', explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700995 $('#right-col').height(explore_column_height + "px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700996}
997
998/**
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700999* initializes demo - adds some extra events when review/explore
1000* mode are clicked, initializes tabs, aligns map box, moves
1001* active tab to about tab
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001002*/
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001003function initDemoPrepareTabs() {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001004
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001005 // Tab behavior for About, Explore, and Demo
1006 $('#mode-tabs a').click(function (e) {
1007 e.preventDefault()
1008 $(this).tab('show')
1009 })
1010
1011 // Explore mode should show explore-mode query-builder UI
1012 $('#explore-mode').click(function(e) {
1013 $('#review-well').hide();
1014 $('#explore-well').show();
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -07001015 rectangleManager.setMap(map);
1016 rectangleManager.setDrawingMode(google.maps.drawing.OverlayType.RECTANGLE);
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001017 mapWidgetResetMap();
1018 });
1019
1020 // Review mode should show review well and hide explore well
1021 $('#review-mode').click(function(e) {
1022 $('#explore-well').hide();
1023 $('#review-well').show();
1024 mapWidgetResetMap();
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -07001025 rectangleManager.setMap(null);
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001026 });
1027
1028 // Does some alignment necessary for the map canvas
1029 onOpenExploreMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001030}
1031
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001032/**
1033* Creates a delete icon button using default trash icon
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001034* @param {String} id, id for this element
1035* @param {String} attachTo, id string of an element to which I can attach this button.
1036* @param {Function} onClick, a function to fire when this icon is clicked
1037*/
1038function addDeleteButton(iconId, attachTo, onClick) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001039
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001040 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 -07001041 $('#' + attachTo).append(trashIcon);
1042
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001043 // When this trash button is clicked, the function is called.
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001044 $('#' + iconId).on('click', onClick);
1045}
1046
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001047/**
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001048* Creates a message and attaches it to data management area.
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001049* @param {String} message, a message to post
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001050* @param {Boolean} isPositiveMessage, whether or not this is a positive message.
1051* @param {String} target, the target div to attach this message.
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001052*/
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001053function reportUserMessage(message, isPositiveMessage, target) {
1054 // Clear out any existing messages
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001055 $('#' + target).html('');
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001056
1057 // Select appropriate alert-type
1058 var alertType = "alert-success";
1059 if (!isPositiveMessage) {
1060 alertType = "alert-danger";
1061 }
1062
1063 // Append the appropriate message
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001064 $('<div/>')
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001065 .attr("class", "alert " + alertType)
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001066 .html('<button type="button" class="close" data-dismiss="alert">&times;</button>' + message)
1067 .appendTo('#' + target);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001068}
1069
1070/**
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001071* mapWidgetResetMap
1072*
1073* [No Parameters]
1074*
1075* Clears ALL map elements - plotted items, overlays, then resets position
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001076*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001077function mapWidgetResetMap() {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001078
1079 mapWidgetClearMap();
1080
1081 // Reset map center and zoom
1082 map.setCenter(new google.maps.LatLng(38.89, -77.03));
1083 map.setZoom(4);
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -07001084
1085 // Selection button
1086 $("#selection-button").trigger("click");
1087 rectangleManager.setMap(map);
1088 rectangleManager.setDrawingMode(google.maps.drawing.OverlayType.RECTANGLE);
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001089}
1090
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001091/**
1092* mapWidgetClearMap
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001093* Removes data/markers
1094*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001095function mapWidgetClearMap() {
1096
1097 // Remove previously plotted data/markers
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001098 for (c in map_cells) {
1099 map_cells[c].setMap(null);
1100 }
1101 map_cells = [];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -07001102
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001103 $.each(map_info_windows, function(i) {
1104 map_info_windows[i].close();
1105 });
1106 map_info_windows = {};
1107
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001108 for (m in map_tweet_markers) {
1109 map_tweet_markers[m].setMap(null);
1110 }
1111 map_tweet_markers = [];
Eugenia Gabrielova12de0302013-10-18 02:25:39 -07001112
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -07001113 // Hide legend
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -07001114 $("#rainbow-legend-container").hide();
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -07001115
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -07001116 // Reenable submit button
Eugenia Gabrielova12de0302013-10-18 02:25:39 -07001117 $("#submit-button").attr("disabled", false);
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -07001118
1119 // Hide selection rectangle
1120 if (selectionRectangle) {
1121 selectionRectangle.setMap(null);
1122 selectionRectangle = null;
1123 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001124}
1125
1126/**
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001127* buildLegend
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001128*
1129* Generates gradient, button action for legend bar
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001130*/
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001131function buildLegend() {
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001132
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001133 // Fill in legend area with colors
1134 var gradientColor;
1135
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -07001136 for (i = 0; i<100; i++) {
1137 //$("#rainbow-legend-container").append("" + rainbow.colourAt(i));
1138 $("#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 -07001139 }
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001140
1141 // Window clear button closes all info count windows
1142 $("#windows-off-btn").on("click", function(e) {
1143 $.each(map_info_windows, function(i) {
1144 map_info_windows[i].close();
1145 });
1146 });
1147}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001148
1149/**
1150* Computes radius for a given data point from a spatial cell
1151* @param {Object} keys => ["latSW" "lngSW" "latNE" "lngNE" "weight"]
1152* @returns {number} radius between 2 points in metres
1153*/
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001154function mapWidgetComputeCircleRadius(spatialCell, wLimit) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001155
1156 // Define Boundary Points
1157 var point_center = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1158 var point_left = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, spatialCell.lngSW);
1159 var point_top = new google.maps.LatLng(spatialCell.latNE, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1160
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001161 // Circle scale modifier =
1162 var scale = 500 + 500*(spatialCell.weight / wLimit);
1163
1164 // Return proportionate value so that circles mostly line up.
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -07001165 return scale * Math.min(distanceBetweenPoints(point_center, point_left), distanceBetweenPoints(point_center, point_top));
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001166}
1167
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001168/**
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -07001169* Calculates the distance between two latlng locations in km, using Google Geometry API.
1170* @param p1, a LatLng
1171* @param p2, a LatLng
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001172*/
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -07001173function distanceBetweenPoints (p1, p2) {
1174 return 0.001 * google.maps.geometry.spherical.computeDistanceBetween(p1, p2);
Eugenia Gabrielova3d7bfe52013-10-28 02:36:44 -07001175};