blob: 791864826759866a4a6e17d1dddacae7c06a5ad5 [file] [log] [blame]
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001$(function() {
2
3 // Connection to AsterixDB - Just one needed!
4 A = new AsterixDBConnection().dataverse("twitter");
5
6 // Following this is some stuff specific to the Black Cherry demo
7 // This is not necessary for working with AsterixDB
8 APIqueryTracker = {};
9 drilldown_data_map = {};
10 drilldown_data_map_vals = {};
11 asyncQueryManager = {};
12
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -070013 // Populate review mode tweetbooks
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070014 review_mode_tweetbooks = [];
15 review_mode_handles = [];
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -070016 getAllDataverseTweetbooks();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070017
18 map_cells = [];
19 map_tweet_markers = [];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -070020 map_info_windows = {};
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070021
22 // UI Elements - Modals & perspective tabs
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -070023 $('#drilldown_modal').modal('hide');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070024 $('#explore-mode').click( onLaunchExploreMode );
25 $('#review-mode').click( onLaunchReviewMode );
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -070026 $('#about-mode').click(onLaunchAboutMode);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070027
28 // UI Elements - A button to clear current map and query data
29 $("#clear-button").button().click(function () {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -070030 mapWidgetResetMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070031
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -070032 $('#explore-report-message').html('');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070033 $('#query-preview-window').html('');
34 $("#metatweetzone").html('');
35 });
36
37 // UI Elements - Query setup
38 $("#selection-button").button('toggle');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070039
40 // UI Element - Grid sliders
41 var updateSliderDisplay = function(event, ui) {
42 if (event.target.id == "grid-lat-slider") {
43 $("#gridlat").text(""+ui.value);
44 } else {
45 $("#gridlng").text(""+ui.value);
46 }
47 };
48
49 sliderOptions = {
50 max: 10,
51 min: 1.5,
52 step: .1,
53 value: 2.0,
54 slidechange: updateSliderDisplay,
55 slide: updateSliderDisplay,
56 start: updateSliderDisplay,
57 stop: updateSliderDisplay
58 };
59
60 $("#gridlat").text(""+sliderOptions.value);
61 $("#gridlng").text(""+sliderOptions.value);
62 $(".grid-slider").slider(sliderOptions);
63
64 // UI Elements - Date Pickers
65 var dateOptions = {
66 dateFormat: "yy-mm-dd",
67 defaultDate: "2012-01-02",
68 navigationAsDateFormat: true,
69 constrainInput: true
70 };
71 var start_dp = $("#start-date").datepicker(dateOptions);
72 start_dp.val(dateOptions.defaultDate);
73 dateOptions['defaultDate'] = "2012-12-31";
74 var end_dp= $("#end-date").datepicker(dateOptions);
75 end_dp.val(dateOptions.defaultDate);
76
77 // This little bit of code manages period checks of the asynchronous query manager,
78 // which holds onto handles asynchornously received. We can set the handle update
79 // frequency using seconds, and it will let us know when it is ready.
80 var intervalID = setInterval(
81 function() {
82 asynchronousQueryIntervalUpdate();
83 },
84 asynchronousQueryGetInterval()
85 );
86
87 // UI Elements - Creates map and location auto-complete
88 onOpenExploreMap();
89 var mapOptions = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -070090 center: new google.maps.LatLng(38.89, -77.03),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070091 zoom: 4,
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -070092 mapTypeId: google.maps.MapTypeId.ROADMAP,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070093 streetViewControl: false,
94 draggable : false
95 };
96 map = new google.maps.Map(document.getElementById('map_canvas'), mapOptions);
97
98 var input = document.getElementById('location-text-box');
99 var autocomplete = new google.maps.places.Autocomplete(input);
100 autocomplete.bindTo('bounds', map);
101
102 google.maps.event.addListener(autocomplete, 'place_changed', function() {
103 var place = autocomplete.getPlace();
104 if (place.geometry.viewport) {
105 map.fitBounds(place.geometry.viewport);
106 } else {
107 map.setCenter(place.geometry.location);
108 map.setZoom(17); // Why 17? Because it looks good.
109 }
110 var address = '';
111 if (place.address_components) {
112 address = [(place.address_components[0] && place.address_components[0].short_name || ''),
113 (place.address_components[1] && place.address_components[1].short_name || ''),
114 (place.address_components[2] && place.address_components[2].short_name || '') ].join(' ');
115 }
116 });
117
118 // UI Elements - Selection Rectangle Drawing
119 shouldDraw = false;
120 var startLatLng;
121 selectionRect = null;
122 var selectionRadio = $("#selection-button");
123 var firstClick = true;
124
125 google.maps.event.addListener(map, 'mousedown', function (event) {
126 // only allow drawing if selection is selected
127 if (selectionRadio.hasClass("active")) {
128 startLatLng = event.latLng;
129 shouldDraw = true;
130 }
131 });
132
133 google.maps.event.addListener(map, 'mousemove', drawRect);
134 function drawRect (event) {
135 if (shouldDraw) {
136 if (!selectionRect) {
137 var selectionRectOpts = {
138 bounds: new google.maps.LatLngBounds(startLatLng, event.latLng),
139 map: map,
140 strokeWeight: 1,
141 strokeColor: "2b3f8c",
142 fillColor: "2b3f8c"
143 };
144 selectionRect = new google.maps.Rectangle(selectionRectOpts);
145 google.maps.event.addListener(selectionRect, 'mouseup', function () {
146 shouldDraw = false;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700147 });
148 } else {
genia.likes.science@gmail.com4ada78c2013-09-07 13:53:48 -0700149
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700150 if (startLatLng.lng() < event.latLng.lng()) {
151 selectionRect.setBounds(new google.maps.LatLngBounds(startLatLng, event.latLng));
152 } else {
153 selectionRect.setBounds(new google.maps.LatLngBounds(event.latLng, startLatLng));
154 }
155 }
156 }
157 };
158
159 // UI Elements - Toggle location search style by location or by map selection
160 $('#selection-button').on('click', function (e) {
161 $("#location-text-box").attr("disabled", "disabled");
162 if (selectionRect) {
163 selectionRect.setMap(map);
164 }
165 });
166 $('#location-button').on('click', function (e) {
167 $("#location-text-box").removeAttr("disabled");
168 if (selectionRect) {
169 selectionRect.setMap(null);
170 }
171 });
172
173 // UI Elements - Tweetbook Management
174 $('.dropdown-menu a.holdmenu').click(function(e) {
175 e.stopPropagation();
176 });
177
178 $('#new-tweetbook-button').on('click', function (e) {
179 onCreateNewTweetBook($('#new-tweetbook-entry').val());
180
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700181 $('#new-tweetbook-entry').val("");
182 $('#new-tweetbook-entry').attr("placeholder", "Name a new tweetbook");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700183 });
184
185 // UI Element - Query Submission
186 $("#submit-button").button().click(function () {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700187
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700188 var kwterm = $("#keyword-textbox").val();
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700189 if (kwterm == "") {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700190 addFailureBlock("Please enter a search term!", "explore-report-message");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700191 } else {
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700192
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700193 $("#explore-report-message").html('');
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700194 $("#submit-button").attr("disabled", true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700195
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700196 var startdp = $("#start-date").datepicker("getDate");
197 var enddp = $("#end-date").datepicker("getDate");
198 var startdt = $.datepicker.formatDate("yy-mm-dd", startdp)+"T00:00:00Z";
199 var enddt = $.datepicker.formatDate("yy-mm-dd", enddp)+"T23:59:59Z";
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700200
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700201 var formData = {
202 "keyword": kwterm,
203 "startdt": startdt,
204 "enddt": enddt,
205 "gridlat": $("#grid-lat-slider").slider("value"),
206 "gridlng": $("#grid-lng-slider").slider("value")
207 };
208
209 // Get Map Bounds
210 var bounds;
211 if ($('#selection-button').hasClass("active") && selectionRect) {
212 bounds = selectionRect.getBounds();
213 } else {
214 bounds = map.getBounds();
215 }
216
217 var swLat = Math.abs(bounds.getSouthWest().lat());
218 var swLng = Math.abs(bounds.getSouthWest().lng());
219 var neLat = Math.abs(bounds.getNorthEast().lat());
220 var neLng = Math.abs(bounds.getNorthEast().lng());
221
222 formData["swLat"] = Math.min(swLat, neLat);
223 formData["swLng"] = Math.max(swLng, neLng);
224 formData["neLat"] = Math.max(swLat, neLat);
225 formData["neLng"] = Math.min(swLng, neLng);
226
227 var build_cherry_mode = "synchronous";
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700228
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700229 if ($('#asbox').is(":checked")) {
230 build_cherry_mode = "asynchronous";
231 $('#show-query-button').attr("disabled", false);
232 } else {
233 $('#show-query-button').attr("disabled", true);
234 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700235
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700236 var f = buildAQLQueryFromForm(formData);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700237
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700238 APIqueryTracker = {
239 "query" : "use dataverse twitter;\n" + f.val(),
240 "data" : formData
241 };
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700242
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700243 // TODO
244 //$('#dialog').html(APIqueryTracker["query"]);
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700245
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700246 if (build_cherry_mode == "synchronous") {
247 A.query(f.val(), cherryQuerySyncCallback, build_cherry_mode);
248 } else {
249 A.query(f.val(), cherryQueryAsyncCallback, build_cherry_mode);
250 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700251
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700252 // Clears selection rectangle on query execution, rather than waiting for another clear call.
253 if (selectionRect) {
254 selectionRect.setMap(null);
255 selectionRect = null;
256 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700257 }
258 });
259});
260
261
262function buildAQLQueryFromForm(parameters) {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700263
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700264 var bounds = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700265 "ne" : { "lat" : parameters["neLat"], "lng" : -1*parameters["neLng"]},
266 "sw" : { "lat" : parameters["swLat"], "lng" : -1*parameters["swLng"]}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700267 };
268
269 var rectangle =
270 new FunctionExpression("create-rectangle",
271 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
272 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
273
274
275 var aql = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700276 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700277 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
278 .LetClause("$region", rectangle)
279 .WhereClause().and(
280 new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
281 new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
282 new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")'),
283 new FunctionExpression("contains", "$t.message-text", "$keyword")
284 )
285 .GroupClause(
286 "$c",
287 new FunctionExpression("spatial-cell", "$t.sender-location",
288 new FunctionExpression("create-point", "24.5", "-125.5"),
289 parameters["gridlat"].toFixed(1), parameters["gridlng"].toFixed(1)),
290 "with",
291 "$t"
292 )
293 .ReturnClause({ "cell" : "$c", "count" : "count($t)" });
294
295 return aql;
296}
297
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700298
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700299/**
300*
301*/
302
303
304/**
305* getAllDataverseTweetbooks
306*
307* no params
308*
309* Returns all datasets of type TweetbookEntry, populates review_mode_tweetbooks
310*/
311function getAllDataverseTweetbooks(fn_tweetbooks) {
312 // Tweetbook Metadata Query
313 var getTweetbooks = new FLWOGRExpression()
314 .ForClause("$ds", new AExpression("dataset Metadata.Dataset"))
315 .WhereClause(new AExpression('$ds.DataTypeName = "TweetbookEntry"'))
316 .ReturnClause({
317 "tweetbookTitle" : "$ds.DatasetName",
318 });
319
320 // Run query
321 A.query(getTweetbooks.val(), function(r) {
322 // Parse tweetbook metadata results
323 var tweetbookMetadata = r["results"];
324
325 // Add existing tweetbooks
326 review_mode_tweetbooks = [];
327 $.each(tweetbookMetadata, function (i, v) {
328 review_mode_tweetbooks.push(tweetbookMetadata[i].split(": \"")[1].split("\"")[0]);
329 });
330
331 // Populate review screen, if possible.
332 $('#review-tweetbook-titles').html('');
333 for (tb in review_mode_tweetbooks) {
334 addTweetBookDropdownItem(review_mode_tweetbooks[tb]);
335 }
336 });
337
338}
339
340
341/** Asynchronous Query Management **/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700342
343/**
344* Checks through each asynchronous query to see if they are ready yet
345*/
346function asynchronousQueryIntervalUpdate() {
347 for (var handle_key in asyncQueryManager) {
348 if (!asyncQueryManager[handle_key].hasOwnProperty("ready")) {
349 asynchronousQueryGetAPIQueryStatus( asyncQueryManager[handle_key]["handle"], handle_key );
350 }
351 }
352}
353
354
355/**
356* Returns current time interval to check for asynchronous query readiness
357* @returns {number} milliseconds between asychronous query checks
358*/
359function asynchronousQueryGetInterval() {
360 var seconds = 10;
361 return seconds * 1000;
362}
363
364
365/**
366* Retrieves status of an asynchronous query, using an opaque result handle from API
367* @param {Object} handle, an object previously returned from an async call
368* @param {number} handle_id, the integer ID parsed from the handle object
369*/
370function asynchronousQueryGetAPIQueryStatus (handle, handle_id) {
371
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700372 A.query_status(
373 {
374 "handle" : JSON.stringify(handle)
375 },
376 function (res) {
377 if (res["status"] == "SUCCESS") {
378 // We don't need to check if this one is ready again, it's not going anywhere...
379 // Unless the life cycle of handles has changed drastically
380 asyncQueryManager[handle_id]["ready"] = true;
381
382 // Indicate success.
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700383 $('#handle_' + handle_id).removeClass("btn-disabled").prop('disabled', false).addClass("btn-success");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700384 }
385 }
386 );
387}
388
389
390/**
391* On-success callback after async API query
392* @param {object} res, a result object containing an opaque result handle to Asterix
393*/
394function cherryQueryAsyncCallback(res) {
395
396 // Parse handle, handle id and query from async call result
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700397 var handle_query = APIqueryTracker["query"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700398 var handle = res;
399 var handle_id = res["handle"].toString().split(',')[0];
400
401 // Add to stored map of existing handles
402 asyncQueryManager[handle_id] = {
403 "handle" : handle,
404 "query" : handle_query,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700405 "data" : APIqueryTracker["data"]
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700406 };
407
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700408 // Create a container for this async query handle
409 $('<div/>')
410 .css("margin-left", "1em")
411 .css("margin-bottom", "1em")
412 .css("display", "block")
413 .attr({
414 "class" : "btn-group",
415 "id" : "async_container_" + handle_id
416 })
417 .appendTo("#async-handle-controls");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700418
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700419 // Adds the main button for this async handle
420 var handle_action_button = '<button class="btn btn-disabled" id="handle_' + handle_id + '">Handle ' + handle_id + '</button>';
421 $('#async_container_' + handle_id).append(handle_action_button);
422 $('#handle_' + handle_id).prop('disabled', true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700423 $('#handle_' + handle_id).on('click', function (e) {
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700424
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700425 // make sure query is ready to be run
426 if (asyncQueryManager[handle_id]["ready"]) {
427
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700428 APIqueryTracker = {
429 "query" : asyncQueryManager[handle_id]["query"],
430 "data" : asyncQueryManager[handle_id]["data"]
431 };
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700432 // TODO
433 //$('#dialog').html(APIqueryTracker["query"]);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700434
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700435 if (!asyncQueryManager[handle_id].hasOwnProperty("result")) {
436 // Generate new Asterix Core API Query
437 A.query_result(
438 { "handle" : JSON.stringify(asyncQueryManager[handle_id]["handle"]) },
439 function(res) {
440 asyncQueryManager[handle_id]["result"] = res;
441 cherryQuerySyncCallback(res);
442 }
443 );
444 } else {
445 cherryQuerySyncCallback(asyncQueryManager[handle_id]["result"]);
446 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700447 }
448 });
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700449
450 // Adds a removal button for this async handle
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700451 var asyncDeleteButton = addDeleteButton(
452 "trashhandle_" + handle_id,
453 "async_container_" + handle_id,
454 function (e) {
455 $('#async_container_' + handle_id).remove();
456 delete asyncQueryManager[handle_id];
457 }
458 );
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700459
460 $('#async_container_' + handle_id).append('<br/>');
461
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700462 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700463}
464
465
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700466/**
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700467* returns a json object with keys: weight, latSW, lngSW, latNE, lngNE
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700468*
469* { "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 -0700470*/
471function getRecord(cell_count_record) {
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700472 // This is a really hacky way to pull out the digits, but it works for now.
473 var values = cell_count_record.replace("int64","").match(/[-+]?[0-9]*\.?[0-9]+/g);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700474 var record_representation = {};
475
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700476 record_representation["latSW"] = parseFloat(values[0]);
477 record_representation["lngSW"] = parseFloat(values[1]);
478 record_representation["latNE"] = parseFloat(values[2]);
479 record_representation["lngNE"] = parseFloat(values[3]);
480 record_representation["weight"] = parseInt(values[4]);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700481
482 return record_representation;
483}
484
485/**
486* A spatial data cleaning and mapping call
487* @param {Object} res, a result object from a cherry geospatial query
488*/
489function cherryQuerySyncCallback(res) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700490 records = res["results"];
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700491
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700492 if (typeof res["results"][0] == "object") {
493 records = res["results"][0];
494 }
495
496 var coordinates = [];
497 var weights = [];
498
499 for (var subrecord in records) {
500 var coordinate = getRecord(records[subrecord]);
501 weights.push(coordinate["weight"]);
502 coordinates.push(coordinate);
503 }
504
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700505 triggerUIUpdate(coordinates, weights);
506 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700507}
508
509/**
510* Triggers a map update based on a set of spatial query result cells
511* @param [Array] mapPlotData, an array of coordinate and weight objects
512* @param [Array] params, an object containing original query parameters [LEGACY]
513* @param [Array] plotWeights, a list of weights of the spatial cells - e.g., number of tweets
514*/
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700515function triggerUIUpdate(mapPlotData, plotWeights) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700516 /** Clear anything currently on the map **/
517 mapWidgetClearMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700518
519 // Compute data point spread
520 var dataBreakpoints = mapWidgetLegendComputeNaturalBreaks(plotWeights);
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700521
522 map_info_windows = {};
523
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700524 $.each(mapPlotData, function (m, val) {
525
526 // Only map points in data range of top 4 natural breaks
527 if (mapPlotData[m].weight > dataBreakpoints[0]) {
528
529 // Get color value of legend
530 var mapColor = mapWidgetLegendGetHeatValue(mapPlotData[m].weight, dataBreakpoints);
531 var markerRadius = mapWidgetComputeCircleRadius(mapPlotData[m], dataBreakpoints);
532 var point_opacity = 1.0;
533
534 var point_center = new google.maps.LatLng(
535 (mapPlotData[m].latSW + mapPlotData[m].latNE)/2.0,
536 (mapPlotData[m].lngSW + mapPlotData[m].lngNE)/2.0);
537
538 // Create and plot marker
539 var map_circle_options = {
540 center: point_center,
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700541 anchorPoint: point_center,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700542 radius: markerRadius,
543 map: map,
544 fillOpacity: point_opacity,
545 fillColor: mapColor,
546 clickable: true
547 };
548 var map_circle = new google.maps.Circle(map_circle_options);
549 map_circle.val = mapPlotData[m];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700550 map_circle.ind = m;
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700551
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700552 map_info_windows[m] = new google.maps.InfoWindow({
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700553 content: mapPlotData[m].weight + "",
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700554 position: point_center
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700555 });
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700556
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700557 // Clicking on a circle drills down map to that value, hovering over it displays a count
558 // of tweets at that location.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700559 google.maps.event.addListener(map_circle, 'click', function (event) {
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700560 map_info_windows[m].close();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700561 onMapPointDrillDown(map_circle.val);
562 });
563
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700564 google.maps.event.addListener(map_circle, 'mouseover', function(event) {
565 if (!map_info_windows[m].getMap()) {
566 map_info_windows[m].setPosition(map_circle.center);
567 map_info_windows[m].open(map);
568 }
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700569 });
570
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700571 // Add this marker to global marker cells
572 map_cells.push(map_circle);
573 }
574 });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700575}
576
577/**
578* prepares an Asterix API query to drill down in a rectangular spatial zone
579*
580* @params {object} marker_borders [LEGACY] a set of bounds for a region from a previous api result
581*/
582function onMapPointDrillDown(marker_borders) {
583 var zoneData = APIqueryTracker["data"];
584
585 var zswBounds = new google.maps.LatLng(marker_borders.latSW, marker_borders.lngSW);
586 var zneBounds = new google.maps.LatLng(marker_borders.latNE, marker_borders.lngNE);
587
588 var zoneBounds = new google.maps.LatLngBounds(zswBounds, zneBounds);
589 zoneData["swLat"] = zoneBounds.getSouthWest().lat();
590 zoneData["swLng"] = zoneBounds.getSouthWest().lng();
591 zoneData["neLat"] = zoneBounds.getNorthEast().lat();
592 zoneData["neLng"] = zoneBounds.getNorthEast().lng();
593 var zB = {
594 "sw" : {
595 "lat" : zoneBounds.getSouthWest().lat(),
596 "lng" : zoneBounds.getSouthWest().lng()
597 },
598 "ne" : {
599 "lat" : zoneBounds.getNorthEast().lat(),
600 "lng" : zoneBounds.getNorthEast().lng()
601 }
602 };
603
604 mapWidgetClearMap();
605
606 var customBounds = new google.maps.LatLngBounds();
607 var zoomSWBounds = new google.maps.LatLng(zoneData["swLat"], zoneData["swLng"]);
608 var zoomNEBounds = new google.maps.LatLng(zoneData["neLat"], zoneData["neLng"]);
609 customBounds.extend(zoomSWBounds);
610 customBounds.extend(zoomNEBounds);
611 map.fitBounds(customBounds);
612
613 var df = getDrillDownQuery(zoneData, zB);
614
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700615 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700616 "query_string" : "use dataverse twitter;\n" + df.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700617 "marker_path" : "static/img/mobile2.png",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700618 "on_clean_result" : onCleanTweetbookDrilldown,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700619 };
620
621 A.query(df.val(), onTweetbookQuerySuccessPlot);
622}
623
624function getDrillDownQuery(parameters, bounds) {
625
626 var zoomRectangle = new FunctionExpression("create-rectangle",
627 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
628 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
629
630 var drillDown = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700631 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700632 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
633 .LetClause("$region", zoomRectangle)
634 .WhereClause().and(
635 new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
636 new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
637 new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")'),
638 new FunctionExpression('contains', '$t.message-text', '$keyword')
639 )
640 .ReturnClause({
641 "tweetId" : "$t.tweetid",
642 "tweetText" : "$t.message-text",
643 "tweetLoc" : "$t.sender-location"
644 });
645
646 return drillDown;
647}
648
649
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700650function onDrillDownAtLocation(tO) {
651
652 var tweetId = tO["tweetEntryId"];
653 var tweetText = tO["tweetText"];
654
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700655 // First, set tweet in drilldown modal to be this tweet's text
656 $('#modal-body-tweet').html('Tweet #' + tweetId + ": " + tweetText);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700657
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700658 // Next, empty any leftover tweetbook comments or error/success messages
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700659 $("#modal-body-add-to").val('');
660 $("#modal-body-add-note").val('');
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700661 $("#modal-body-message-holder").html("");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700662
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700663 // Next, if there is an existing tweetcomment reported, show it.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700664 if (tO.hasOwnProperty("tweetComment")) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700665
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700666 // Show correct panel
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700667 $("#modal-existing-note").show();
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700668 $("#modal-save-tweet-panel").hide();
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700669
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700670 // Fill in existing tweet comment
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700671 $("#modal-body-tweet-note").val(tO["tweetComment"]);
672
673 // Change Tweetbook Badge
674 $("#modal-current-tweetbook").val(APIqueryTracker["active_tweetbook"]);
675
676 // Add deletion functionality
677 $("#modal-body-trash-icon").on('click', function () {
678 // Send comment deletion to asterix
679 var deleteTweetCommentOnId = '"' + tweetId + '"';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700680 var toDelete = new DeleteStatement(
681 "$mt",
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700682 APIqueryTracker["active_tweetbook"],
683 new AExpression("$mt.tweetid = " + deleteTweetCommentOnId.toString())
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700684 );
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700685 A.update(
686 toDelete.val()
687 );
688
689 // Hide comment from map
690 $('#drilldown_modal').modal('hide');
691
692 // Replot tweetbook
693 onPlotTweetbook(APIqueryTracker["active_tweetbook"]);
694 });
695
696 } else {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700697 // Show correct panel
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700698 $("#modal-existing-note").hide();
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700699 $("#modal-save-tweet-panel").show();
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700700
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700701 // Now, when adding a comment on an available tweet to a tweetbook
702 $("#save-comment-tweetbook-modal").on('click', function(e) {
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700703
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700704 // Stuff to save about new comment
705 var save_metacomment_target_tweetbook = $("#modal-body-add-to").val();
706 var save_metacomment_target_comment = '"' + $("#modal-body-add-note").val() + '"';
707 var save_metacomment_target_tweet = '"' + tweetId + '"';
708
709 // Make sure content is entered, and then save this comment.
710 if (save_metacomment_target_tweetbook.length == 0) {
711 alert("Please enter a tweetbook.");
712 } else if ($("#modal-body-add-note").val() == "") {
713 alert("Please enter a comment.");
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700714 } else {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700715
716 // Check if tweetbook exists. If not, create it.
717 if (!(existsTweetbook(save_metacomment_target_tweetbook))) {
718 onCreateNewTweetBook(save_metacomment_target_tweetbook);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700719 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700720
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700721 var toInsert = new InsertStatement(
722 save_metacomment_target_tweetbook,
723 {
724 "tweetid" : save_metacomment_target_tweet.toString(),
725 "comment-text" : save_metacomment_target_comment
726 }
727 );
728 A.update(toInsert.val(), function () {});
729
730 var successMessage = "Saved comment on <b>Tweet #" + tweetId +
731 "</b> in dataset <b>" + save_metacomment_target_tweetbook + "</b>.";
732 addSuccessBlock(successMessage, "modal-body-message-holder");
733
734 $("#modal-body-add-to").val('');
735 $("#modal-body-add-note").val('');
736 }
737 });
738 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700739}
740
741
742/**
743* Adds a new tweetbook entry to the menu and creates a dataset of type TweetbookEntry.
744*/
745function onCreateNewTweetBook(tweetbook_title) {
746
747 var tweetbook_title = tweetbook_title.split(' ').join('_');
748
749 A.ddl(
750 "create dataset " + tweetbook_title + "(TweetbookEntry) primary key tweetid;",
751 function () {}
752 );
753
754 if (!(existsTweetbook(tweetbook_title))) {
755 review_mode_tweetbooks.push(tweetbook_title);
756 addTweetBookDropdownItem(tweetbook_title);
757 }
758}
759
760
761function onDropTweetBook(tweetbook_title) {
762
763 // AQL Call
764 A.ddl(
765 "drop dataset " + tweetbook_title + " if exists;",
766 function () {}
767 );
768
769 // Removes tweetbook from review_mode_tweetbooks
770 var remove_position = $.inArray(tweetbook_title, review_mode_tweetbooks);
771 if (remove_position >= 0) review_mode_tweetbooks.splice(remove_position, 1);
772
773 // Clear UI with review tweetbook titles
774 $('#review-tweetbook-titles').html('');
775 for (r in review_mode_tweetbooks) {
776 addTweetBookDropdownItem(review_mode_tweetbooks[r]);
777 }
778}
779
780
781function addTweetBookDropdownItem(tweetbook) {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700782 // Add placeholder for this tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700783 $('<div/>')
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700784 .attr({
785 "class" : "btn-group",
786 "id" : "rm_holder_" + tweetbook
787 }).appendTo("#review-tweetbook-titles");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700788
789 // Add plotting button for this tweetbook
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700790 var plot_button = '<button class="btn btn-default" id="rm_plotbook_' + tweetbook + '">' + tweetbook + '</button>';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700791 $("#rm_holder_" + tweetbook).append(plot_button);
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700792 $("#rm_plotbook_" + tweetbook).width("200px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700793 $("#rm_plotbook_" + tweetbook).on('click', function(e) {
794 onPlotTweetbook(tweetbook);
795 });
796
797 // Add trash button for this tweetbook
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700798 var onTrashTweetbookButton = addDeleteButton(
799 "rm_trashbook_" + tweetbook,
800 "rm_holder_" + tweetbook,
801 function(e) {
802 onDropTweetBook(tweetbook);
803 }
804 );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700805}
806
807
808function onPlotTweetbook(tweetbook) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700809
810 // Clear map for this one
811 mapWidgetResetMap();
812
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700813 var plotTweetQuery = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700814 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700815 .ForClause("$m", new AExpression("dataset " + tweetbook))
816 .WhereClause(new AExpression("$m.tweetid = $t.tweetid"))
817 .ReturnClause({
818 "tweetId" : "$m.tweetid",
819 "tweetText" : "$t.message-text",
820 "tweetLoc" : "$t.sender-location",
821 "tweetCom" : "$m.comment-text"
822 });
823
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700824 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700825 "query_string" : "use dataverse twitter;\n" + plotTweetQuery.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700826 "marker_path" : "static/img/mobile_green2.png",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700827 "on_clean_result" : onCleanPlotTweetbook,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700828 "active_tweetbook" : tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700829 };
830
831 A.query(plotTweetQuery.val(), onTweetbookQuerySuccessPlot);
832}
833
834
835function onTweetbookQuerySuccessPlot (res) {
836
837 var records = res["results"];
838
839 var coordinates = [];
840 map_tweet_markers = [];
841 map_tweet_overlays = [];
842 drilldown_data_map = {};
843 drilldown_data_map_vals = {};
844
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700845 var micon = APIqueryTracker["marker_path"];
846 var marker_click_function = onClickTweetbookMapMarker;
847 var clean_result_function = APIqueryTracker["on_clean_result"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700848
849 coordinates = clean_result_function(records);
850
851 for (var dm in coordinates) {
852 var keyLat = coordinates[dm].tweetLat.toString();
853 var keyLng = coordinates[dm].tweetLng.toString();
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700854
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700855 if (!drilldown_data_map.hasOwnProperty(keyLat)) {
856 drilldown_data_map[keyLat] = {};
857 }
858 if (!drilldown_data_map[keyLat].hasOwnProperty(keyLng)) {
859 drilldown_data_map[keyLat][keyLng] = [];
860 }
861 drilldown_data_map[keyLat][keyLng].push(coordinates[dm]);
862 drilldown_data_map_vals[coordinates[dm].tweetEntryId.toString()] = coordinates[dm];
863 }
864
865 $.each(drilldown_data_map, function(drillKeyLat, valuesAtLat) {
866 $.each(drilldown_data_map[drillKeyLat], function (drillKeyLng, valueAtLng) {
867
868 // Get subset of drilldown position on map
869 var cposition = new google.maps.LatLng(parseFloat(drillKeyLat), parseFloat(drillKeyLng));
870
871 // Create a marker using the snazzy phone icon
872 var map_tweet_m = new google.maps.Marker({
873 position: cposition,
874 map: map,
875 icon: micon,
876 clickable: true,
877 });
878
879 // Open Tweet exploration window on click
880 google.maps.event.addListener(map_tweet_m, 'click', function (event) {
881 marker_click_function(drilldown_data_map[drillKeyLat][drillKeyLng]);
882 });
883
884 // Add marker to index of tweets
885 map_tweet_markers.push(map_tweet_m);
886
887 });
888 });
889}
890
891
892function existsTweetbook(tweetbook) {
893 if (parseInt($.inArray(tweetbook, review_mode_tweetbooks)) == -1) {
894 return false;
895 } else {
896 return true;
897 }
898}
899
900
901function onCleanPlotTweetbook(records) {
902 var toPlot = [];
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700903
904 // An entry looks like this:
905 // { "tweetId": "273589", "tweetText": " like verizon the network is amazing", "tweetLoc": { point: [37.78, 82.27]}, "tweetCom": "hooray comments" }
906
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700907 for (var entry in records) {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700908
909 var points = records[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
910
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700911 var tweetbook_element = {
912 "tweetEntryId" : parseInt(records[entry].split(",")[0].split(":")[1].split('"')[1]),
913 "tweetText" : records[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700914 "tweetLat" : parseFloat(points[0]),
915 "tweetLng" : parseFloat(points[1]),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700916 "tweetComment" : records[entry].split("tweetCom\": \"")[1].split("\"")[0]
917 };
918 toPlot.push(tweetbook_element);
919 }
920
921 return toPlot;
922}
923
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700924
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700925function onCleanTweetbookDrilldown (rec) {
926
927 var drilldown_cleaned = [];
928
929 for (var entry = 0; entry < rec.length; entry++) {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700930
931 // An entry looks like this:
932 // { "tweetId": "105491", "tweetText": " hate verizon its platform is OMG", "tweetLoc": { point: [30.55, 71.44]} }
933 var points = rec[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
934
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700935 var drill_element = {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700936 "tweetEntryId" : parseInt(rec[entry].split(",")[0].split(":")[1].replace('"', '')),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700937 "tweetText" : rec[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700938 "tweetLat" : parseFloat(points[0]),
939 "tweetLng" : parseFloat(points[1])
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700940 };
941 drilldown_cleaned.push(drill_element);
942 }
943 return drilldown_cleaned;
944}
945
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700946
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700947function onClickTweetbookMapMarker(tweet_arr) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700948 // Clear existing display
949 $.each(tweet_arr, function (t, valueT) {
950 var tweet_obj = tweet_arr[t];
951 onDrillDownAtLocation(tweet_obj);
952 });
953
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700954 $('#drilldown_modal').modal();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700955}
956
957/** Toggling Review and Explore Modes **/
958
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700959
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700960/**
961* Explore mode: Initial map creation and screen alignment
962*/
963function onOpenExploreMap () {
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700964 var explore_column_height = $('#explore-well').height();
965 var right_column_width = $('#right-col').width();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700966 $('#map_canvas').height(explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700967 $('#map_canvas').width(right_column_width + "px");
968
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700969 $('#review-well').height(explore_column_height + "px");
970 $('#review-well').css('max-height', explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700971
972 $('#right-col').height(explore_column_height + "px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700973}
974
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700975
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700976/**
977* Launching explore mode: clear windows/variables, show correct sidebar
978*/
979function onLaunchExploreMode() {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700980 $('#aboutr').hide();
981 $('#r1').show();
982 $('#about-active').removeClass('active');
983
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700984 $('#review-active').removeClass('active');
985 $('#review-well').hide();
986
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700987
988 $('#explore-active').addClass('active');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700989 $('#explore-well').show();
990
991 $("#clear-button").trigger("click");
992}
993
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700994
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700995/**
996* Launching review mode: clear windows/variables, show correct sidebar
997*/
998function onLaunchReviewMode() {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700999 $('#aboutr').hide();
1000 $('#r1').show();
1001 $('#about-active').removeClass('active');
1002
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001003 $('#explore-active').removeClass('active');
1004 $('#explore-well').hide();
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001005
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001006 $('#review-active').addClass('active');
1007 $('#review-well').show();
1008
1009 $("#clear-button").trigger("click");
1010}
1011
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001012
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001013/**
1014* Lauching about mode: hides all windows, shows row containing about info
1015*/
1016function onLaunchAboutMode() {
1017 $('#explore-active').removeClass('active');
1018 $('#review-active').removeClass('active');
1019 $('#about-active').addClass('active');
1020 $('#r1').hide();
1021 $('#aboutr').show();
1022}
1023
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001024/** Icon / Interface Utility Methods **/
1025
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001026/**
1027* Creates a delete icon button using default trash icon
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001028* @param {String} id, id for this element
1029* @param {String} attachTo, id string of an element to which I can attach this button.
1030* @param {Function} onClick, a function to fire when this icon is clicked
1031*/
1032function addDeleteButton(iconId, attachTo, onClick) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001033
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001034 var trashIcon = '<button class="btn btn-default" id="' + iconId + '"><span class="glyphicon glyphicon-trash"></span></button>';
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001035 $('#' + attachTo).append(trashIcon);
1036
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001037 // When this trash button is clicked, the function is called.
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001038 $('#' + iconId).on('click', onClick);
1039}
1040
1041
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001042/**
1043* Creates a success message and attaches it to a div with provided ID.
1044* @param {String} message, a message to post
1045* @param {String} appendTarget, a target div to which to append the alert
1046*/
1047function addSuccessBlock(message, appendTarget) {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001048 $('#' + appendTarget).html('');
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001049 $('<div/>')
1050 .attr("class", "alert alert-success")
1051 .html('<button type="button" class="close" data-dismiss="alert">&times;</button>' + message)
1052 .appendTo('#' + appendTarget);
1053}
1054
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001055/**
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001056* Creates a failure mesage and attaches it to a div with provided id.
1057* @param {String} message, a message to post
1058* @param {String} target, a target div to append the message
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001059*/
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001060function addFailureBlock(message, target) {
1061 $('#' + target).html('');
1062 $('<div/>')
1063 .attr("class", "alert alert-danger")
1064 .html('<button type="button" class="close" data-dismiss="alert">&times;</button>' + message)
1065 .appendTo('#' + target);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001066}
1067
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001068
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001069/**
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001070* mapWidgetResetMap
1071*
1072* [No Parameters]
1073*
1074* Clears ALL map elements - plotted items, overlays, then resets position
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001075*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001076function mapWidgetResetMap() {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001077
1078 if (selectionRect) {
1079 selectionRect.setMap(null);
1080 selectionRect = null;
1081 }
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001082
1083 mapWidgetClearMap();
1084
1085 // Reset map center and zoom
1086 map.setCenter(new google.maps.LatLng(38.89, -77.03));
1087 map.setZoom(4);
1088}
1089
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001090/**
1091* mapWidgetClearMap
1092*
1093* No parameters
1094*
1095* Removes data/markers
1096*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001097function mapWidgetClearMap() {
1098
1099 // Remove previously plotted data/markers
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001100 for (c in map_cells) {
1101 map_cells[c].setMap(null);
1102 }
1103 map_cells = [];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -07001104
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001105 for (m in map_tweet_markers) {
1106 map_tweet_markers[m].setMap(null);
1107 }
1108 map_tweet_markers = [];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001109}
1110
1111/**
1112* Uses jenks algorithm in geostats library to find natural breaks in numeric data
1113* @param {number Array} weights of points to plot
1114* @returns {number Array} array of natural breakpoints, of which the top 4 subsets will be plotted
1115*/
1116function mapWidgetLegendComputeNaturalBreaks(weights) {
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001117
1118 if (weights.length < 10) {
1119 return [0];
1120 }
1121
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001122 var plotDataWeights = new geostats(weights.sort());
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001123 return plotDataWeights.getJenks(6).slice(2,7);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001124}
1125
1126/**
1127* Computes values for map legend given a value and an array of jenks breakpoints
1128* @param {number} weight of point to plot on map
1129* @param {number Array} breakpoints, an array of 5 points corresponding to bounds of 4 natural ranges
1130* @returns {String} an RGB value corresponding to a subset of data
1131*/
1132function mapWidgetLegendGetHeatValue(weight, breakpoints) {
1133
1134 // Determine into which range the weight falls
1135 var weightColor = 0;
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001136
1137 if (breakpoints.length == 1) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001138 weightColor = 2;
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001139 } else {
1140 if (weight >= breakpoints[3]) {
1141 weightColor = 3;
1142 } else if (weight >= breakpoints[2]) {
1143 weightColor = 2;
1144 } else if (weight >= breakpoints[1]) {
1145 weightColor = 1;
1146 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001147 }
1148
1149 // Get default map color palette
1150 var colorValues = mapWidgetGetColorPalette();
1151 return colorValues[weightColor];
1152}
1153
1154/**
1155* Returns an array containing a 4-color palette, lightest to darkest
1156* External palette source: http://www.colourlovers.com/palette/2763366/s_i_l_e_n_c_e_r
1157* @returns {Array} [colors]
1158*/
1159function mapWidgetGetColorPalette() {
1160 return [
1161 "rgb(115,189,158)",
1162 "rgb(74,142,145)",
1163 "rgb(19,93,96)",
1164 "rgb(7,51,46)"
1165 ];
1166}
1167
1168/**
1169* Computes radius for a given data point from a spatial cell
1170* @param {Object} keys => ["latSW" "lngSW" "latNE" "lngNE" "weight"]
1171* @returns {number} radius between 2 points in metres
1172*/
1173function mapWidgetComputeCircleRadius(spatialCell, breakpoints) {
1174
1175 var weight = spatialCell.weight;
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001176
1177 if (breakpoints.length == 1) {
1178 var weightColor = 0.25;
1179 } else {
1180 // Compute weight color
1181 var weightColor = 0.25;
1182 if (weight >= breakpoints[3]) {
1183 weightColor = 1.0;
1184 } else if (weight >= breakpoints[2]) {
1185 weightColor = 0.75;
1186 } else if (weight >= breakpoints[1]) {
1187 weightColor = 0.5;
1188 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001189 }
1190
1191 // Define Boundary Points
1192 var point_center = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1193 var point_left = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, spatialCell.lngSW);
1194 var point_top = new google.maps.LatLng(spatialCell.latNE, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1195
1196 // TODO not actually a weight color :)
1197 return weightColor * 1000 * Math.min(distanceBetweenPoints_(point_center, point_left), distanceBetweenPoints_(point_center, point_top));
1198}
1199
1200/** External Utility Methods **/
1201
1202/**
1203 * Calculates the distance between two latlng locations in km.
1204 * @see http://www.movable-type.co.uk/scripts/latlong.html
1205 *
1206 * @param {google.maps.LatLng} p1 The first lat lng point.
1207 * @param {google.maps.LatLng} p2 The second lat lng point.
1208 * @return {number} The distance between the two points in km.
1209 * @private
1210*/
1211function distanceBetweenPoints_(p1, p2) {
1212 if (!p1 || !p2) {
1213 return 0;
1214 }
1215
1216 var R = 6371; // Radius of the Earth in km
1217 var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
1218 var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
1219 var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
1220 Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
1221 Math.sin(dLon / 2) * Math.sin(dLon / 2);
1222 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1223 var d = R * c;
1224 return d;
1225};