blob: 61ecedbf61fc5076577cd4a47c68f270e06aea9b [file] [log] [blame]
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001$(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
Zachary Heilbrond98423c2014-03-06 17:37:28 -08005 // code but does not store information about any individual API call.
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07006 A = new AsterixDBConnection({
Zachary Heilbrond98423c2014-03-06 17:37:28 -08007
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07008 // 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",
Zachary Heilbrond98423c2014-03-06 17:37:28 -080012
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -070013 // 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" : "/",
Zachary Heilbrond98423c2014-03-06 17:37:28 -080017
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -070018 // 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
Zachary Heilbrond98423c2014-03-06 17:37:28 -080029 // an error message to display, a positivity value (false in this case, errors are bad), and a
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -070030 // 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";
Zachary Heilbrond98423c2014-03-06 17:37:28 -080034
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -070035 reportUserMessage(showErrorMessage, isPositive, showReportAt);
36 }
37 });
Zachary Heilbrond98423c2014-03-06 17:37:28 -080038
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.
Zachary Heilbrond98423c2014-03-06 17:37:28 -080042 var intervalID = setInterval(
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -070043 function() {
44 asynchronousQueryIntervalUpdate();
Zachary Heilbrond98423c2014-03-06 17:37:28 -080045 },
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -070046 asynchronousQueryGetInterval()
47 );
Zachary Heilbrond98423c2014-03-06 17:37:28 -080048
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -070049 // Legend Container
Zachary Heilbrond98423c2014-03-06 17:37:28 -080050 // Create a rainbow from a pretty color scheme.
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -070051 // http://www.colourlovers.com/palette/292482/Terra
52 rainbow = new Rainbow();
53 rainbow.setSpectrum("#E8DDCB", "#CDB380", "#036564", "#033649", "#031634");
54 buildLegend();
Zachary Heilbrond98423c2014-03-06 17:37:28 -080055
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,
Eugenia Gabrielovae11965b2013-10-29 19:07:42 -070065 draggable : false,
66 mapTypeControl: false
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070067 };
68 map = new google.maps.Map(document.getElementById('map_canvas'), mapOptions);
69
70 var input = document.getElementById('location-text-box');
71 var autocomplete = new google.maps.places.Autocomplete(input);
72 autocomplete.bindTo('bounds', map);
Zachary Heilbrond98423c2014-03-06 17:37:28 -080073
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070074 google.maps.event.addListener(autocomplete, 'place_changed', function() {
75 var place = autocomplete.getPlace();
76 if (place.geometry.viewport) {
77 map.fitBounds(place.geometry.viewport);
78 } else {
79 map.setCenter(place.geometry.location);
80 map.setZoom(17); // Why 17? Because it looks good.
81 }
82 var address = '';
83 if (place.address_components) {
84 address = [(place.address_components[0] && place.address_components[0].short_name || ''),
85 (place.address_components[1] && place.address_components[1].short_name || ''),
86 (place.address_components[2] && place.address_components[2].short_name || '') ].join(' ');
87 }
88 });
Zachary Heilbrond98423c2014-03-06 17:37:28 -080089
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -070090 // Drawing Manager for selecting map regions. See documentation here:
91 // https://developers.google.com/maps/documentation/javascript/reference#DrawingManager
92 rectangleManager = new google.maps.drawing.DrawingManager({
93 drawingMode : google.maps.drawing.OverlayType.RECTANGLE,
94 drawingControl : false,
95 rectangleOptions : {
96 strokeWeight: 1,
97 clickable: false,
98 editable: true,
Zachary Heilbron1eab8c92014-03-10 13:57:00 -070099 strokeColor: "#2b3f8c",
100 fillColor: "#2b3f8c",
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700101 zIndex: 1
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700102 }
103 });
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700104 rectangleManager.setMap(map);
105 selectionRectangle = null;
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800106
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700107 // Drawing Manager: Just one editable rectangle!
108 google.maps.event.addListener(rectangleManager, 'rectanglecomplete', function(rectangle) {
109 selectionRectangle = rectangle;
110 rectangleManager.setDrawingMode(null);
111 });
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700112
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700113 // Initialize data structures
114 APIqueryTracker = {};
115 asyncQueryManager = {};
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700116 map_cells = [];
117 map_tweet_markers = [];
118 map_info_windows = {};
119 review_mode_tweetbooks = [];
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700120
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700121 getAllDataverseTweetbooks();
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700122 initDemoUIButtonControls();
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800123
Eugenia Gabrielovae11965b2013-10-29 19:07:42 -0700124 google.maps.event.addListenerOnce(map, 'idle', function(){
125 // Show tutorial tab only the first time the map is loaded
126 $('#mode-tabs a:first').tab('show');
127 });
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800128
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700129});
130
131function initDemoUIButtonControls() {
132
133 // Explore Mode - Update Sliders
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700134 var updateSliderDisplay = function(event, ui) {
135 if (event.target.id == "grid-lat-slider") {
136 $("#gridlat").text(""+ui.value);
137 } else {
138 $("#gridlng").text(""+ui.value);
139 }
140 };
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700141 sliderOptions = {
142 max: 10,
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700143 min: 2.0,
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700144 step: .1,
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700145 value: 3.0,
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700146 slidechange: updateSliderDisplay,
147 slide: updateSliderDisplay,
148 start: updateSliderDisplay,
149 stop: updateSliderDisplay
150 };
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700151 $("#gridlat").text(""+sliderOptions.value);
152 $("#gridlng").text(""+sliderOptions.value);
153 $(".grid-slider").slider(sliderOptions);
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700154
155 // Explore Mode - Query Builder Date Pickers
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700156 var dateOptions = {
157 dateFormat: "yy-mm-dd",
158 defaultDate: "2012-01-02",
159 navigationAsDateFormat: true,
160 constrainInput: true
161 };
162 var start_dp = $("#start-date").datepicker(dateOptions);
163 start_dp.val(dateOptions.defaultDate);
164 dateOptions['defaultDate'] = "2012-12-31";
165 var end_dp= $("#end-date").datepicker(dateOptions);
166 end_dp.val(dateOptions.defaultDate);
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800167
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700168 // Explore Mode: Toggle Selection/Location Search
Eugenia Gabrielova3d7bfe52013-10-28 02:36:44 -0700169 $('#selection-button').on('change', function (e) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700170 $("#location-text-box").attr("disabled", "disabled");
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700171 rectangleManager.setMap(map);
172 if (selectionRectangle) {
173 selectionRectangle.setMap(null);
174 selectionRectangle = null;
175 } else {
176 rectangleManager.setDrawingMode(google.maps.drawing.OverlayType.RECTANGLE);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700177 }
178 });
Eugenia Gabrielova3d7bfe52013-10-28 02:36:44 -0700179 $('#location-button').on('change', function (e) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700180 $("#location-text-box").removeAttr("disabled");
Zachary Heilbronf3e45752014-03-10 14:47:55 -0700181 selectionRectangle.setMap(null);
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700182 rectangleManager.setMap(null);
Eugenia Gabrielovae11965b2013-10-29 19:07:42 -0700183 rectangleManager.setDrawingMode(google.maps.drawing.OverlayType.RECTANGLE);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700184 });
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700185 $("#selection-button").trigger("click");
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700186
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700187 // Review Mode: New Tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700188 $('#new-tweetbook-button').on('click', function (e) {
189 onCreateNewTweetBook($('#new-tweetbook-entry').val());
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800190
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700191 $('#new-tweetbook-entry').val("");
192 $('#new-tweetbook-entry').attr("placeholder", "Name a new tweetbook");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700193 });
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700194
195 // Explore Mode - Clear Button
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800196 $("#clear-button").click(mapWidgetResetMap);
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700197
198 // Explore Mode: Query Submission
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700199 $("#submit-button").on("click", function () {
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700200
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700201 $("#report-message").html('');
202 $("#submit-button").attr("disabled", true);
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700203 rectangleManager.setDrawingMode(null);
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800204
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700205 var kwterm = $("#keyword-textbox").val();
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700206 var startdp = $("#start-date").datepicker("getDate");
207 var enddp = $("#end-date").datepicker("getDate");
208 var startdt = $.datepicker.formatDate("yy-mm-dd", startdp)+"T00:00:00Z";
209 var enddt = $.datepicker.formatDate("yy-mm-dd", enddp)+"T23:59:59Z";
210
211 var formData = {
212 "keyword": kwterm,
213 "startdt": startdt,
214 "enddt": enddt,
215 "gridlat": $("#grid-lat-slider").slider("value"),
216 "gridlng": $("#grid-lng-slider").slider("value")
217 };
218
219 // Get Map Bounds
220 var bounds;
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700221 if ($('#selection-label').hasClass("active") && selectionRectangle) {
222 bounds = selectionRectangle.getBounds();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700223 } else {
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700224 bounds = map.getBounds();
225 }
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800226
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700227 var swLat = Math.abs(bounds.getSouthWest().lat());
228 var swLng = Math.abs(bounds.getSouthWest().lng());
229 var neLat = Math.abs(bounds.getNorthEast().lat());
230 var neLng = Math.abs(bounds.getNorthEast().lng());
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800231
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700232 formData["swLat"] = Math.min(swLat, neLat);
233 formData["swLng"] = Math.max(swLng, neLng);
234 formData["neLat"] = Math.max(swLat, neLat);
235 formData["neLng"] = Math.min(swLng, neLng);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700236
zheilbronbebb2202014-03-28 14:36:52 -0700237 var build_tweetbook_mode = "synchronous";
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700238 if ($('#asbox').is(":checked")) {
zheilbronbebb2202014-03-28 14:36:52 -0700239 build_tweetbook_mode = "asynchronous";
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700240 }
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800241
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700242 var f = buildAQLQueryFromForm(formData);
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800243
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700244 APIqueryTracker = {
245 "query" : "use dataverse twitter;\n" + f.val(),
246 "data" : formData
247 };
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800248
zheilbronbebb2202014-03-28 14:36:52 -0700249 if (build_tweetbook_mode == "synchronous") {
250 A.query(f.val(), tweetbookQuerySyncCallback, build_tweetbook_mode);
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700251 } else {
zheilbronbebb2202014-03-28 14:36:52 -0700252 A.query(f.val(), tweetbookQueryAsyncCallback, build_tweetbook_mode);
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700253 }
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800254
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700255 // Clears selection rectangle on query execution, rather than waiting for another clear call.
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -0700256 if (selectionRectangle) {
257 selectionRectangle.setMap(null);
258 selectionRectangle = null;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700259 }
260 });
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700261}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700262
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -0700263/**
264* Builds AsterixDB REST Query from explore mode form.
265*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700266function buildAQLQueryFromForm(parameters) {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700267
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700268 var bounds = {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800269 "ne" : { "lat" : parameters["neLat"], "lng" : -1*parameters["neLng"]},
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700270 "sw" : { "lat" : parameters["swLat"], "lng" : -1*parameters["swLng"]}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700271 };
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800272
273 var rectangle =
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700274 new FunctionExpression("create-rectangle",
275 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
276 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800277
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700278 // You can chain these all together, but let's take them one at a time.
279 // Let's start with a ForClause. Here we go through each tweet $t in the
280 // dataset TweetMessageShifted.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700281 var aql = new FLWOGRExpression()
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700282 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"));
283
284 // We know we have bounds for our region, so we can add that LetClause next.
285 aql = aql.LetClause("$region", rectangle);
286
287 // Now, let's change it up. The keyword term doesn't always show up, so it might be blank.
288 // We'll attach a new let clause for it, and then a WhereClause.
289 if (parameters["keyword"].length > 0) {
290 aql = aql
291 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
292 .WhereClause().and(
293 new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
294 new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
295 new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")'),
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800296 new FunctionExpression("contains", "$t.message-text", "$keyword")
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700297 );
298 } else {
299 aql = aql
300 .WhereClause().and(
301 new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
302 new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
303 new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")')
304 );
305 }
306
307 // Finally, we'll group our results into spatial cells.
308 aql = aql.GroupClause(
309 "$c",
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800310 new FunctionExpression("spatial-cell", "$t.sender-location",
311 new FunctionExpression("create-point", "24.5", "-125.5"),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700312 parameters["gridlat"].toFixed(1), parameters["gridlng"].toFixed(1)),
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800313 "with",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700314 "$t"
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700315 );
316
317 // ...and return a resulting cell and a count of results in that cell.
318 aql = aql.ReturnClause({ "cell" : "$c", "count" : "count($t)" });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700319
320 return aql;
321}
322
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700323/**
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700324* getAllDataverseTweetbooks
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800325*
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700326* Returns all datasets of type TweetbookEntry, populates review_mode_tweetbooks
327*/
328function getAllDataverseTweetbooks(fn_tweetbooks) {
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700329
330 // This creates a query to the Metadata for datasets of type
331 // TweetBookEntry. Note that if we throw in a WhereClause (commented out below)
332 // there is an odd error. This is being fixed and will be removed from this demo.
333 var getTweetbooksQuery = new FLWOGRExpression()
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700334 .ForClause("$ds", new AExpression("dataset Metadata.Dataset"))
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700335 //.WhereClause(new AExpression('$ds.DataTypeName = "TweetbookEntry"'))
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700336 .ReturnClause({
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700337 "DataTypeName" : "$ds.DataTypeName",
338 "DatasetName" : "$ds.DatasetName"
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700339 });
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800340
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700341 // Now create a function that will be called when tweetbooks succeed.
342 // In this case, we want to parse out the results object from the Asterix
343 // REST API response.
344 var tweetbooksSuccess = function(r) {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700345 // Parse tweetbook metadata results
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700346 $.each(r.results, function(i, data) {
347 if ($.parseJSON(data)["DataTypeName"] == "TweetbookEntry") {
348 review_mode_tweetbooks.push($.parseJSON(data)["DatasetName"]);
349 }
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700350 });
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800351
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700352 // Now, if any tweetbooks already exist, opulate review screen.
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700353 $('#review-tweetbook-titles').html('');
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700354 $.each(review_mode_tweetbooks, function(i, tweetbook) {
355 addTweetBookDropdownItem(tweetbook);
356 });
357 };
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800358
359 // Now, we are ready to run a query.
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700360 A.meta(getTweetbooksQuery.val(), tweetbooksSuccess);
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700361}
362
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700363/**
364* Checks through each asynchronous query to see if they are ready yet
365*/
366function asynchronousQueryIntervalUpdate() {
367 for (var handle_key in asyncQueryManager) {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800368 if (!asyncQueryManager[handle_key].hasOwnProperty("ready")) {
369 asynchronousQueryGetAPIQueryStatus( asyncQueryManager[handle_key]["handle"], handle_key );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700370 }
371 }
372}
373
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700374/**
375* Returns current time interval to check for asynchronous query readiness
376* @returns {number} milliseconds between asychronous query checks
377*/
378function asynchronousQueryGetInterval() {
379 var seconds = 10;
380 return seconds * 1000;
381}
382
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700383/**
384* Retrieves status of an asynchronous query, using an opaque result handle from API
385* @param {Object} handle, an object previously returned from an async call
386* @param {number} handle_id, the integer ID parsed from the handle object
387*/
388function asynchronousQueryGetAPIQueryStatus (handle, handle_id) {
389
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800390 A.query_status(
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700391 {
392 "handle" : JSON.stringify(handle)
393 },
394 function (res) {
395 if (res["status"] == "SUCCESS") {
396 // We don't need to check if this one is ready again, it's not going anywhere...
397 // Unless the life cycle of handles has changed drastically
398 asyncQueryManager[handle_id]["ready"] = true;
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800399
400 // Indicate success.
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700401 $('#handle_' + handle_id).removeClass("btn-disabled").prop('disabled', false).addClass("btn-success");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700402 }
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800403 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700404 );
405}
406
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700407/**
408* On-success callback after async API query
409* @param {object} res, a result object containing an opaque result handle to Asterix
410*/
zheilbronbebb2202014-03-28 14:36:52 -0700411function tweetbookQueryAsyncCallback(res) {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800412
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700413 // Parse handle, handle id and query from async call result
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700414 var handle_query = APIqueryTracker["query"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700415 var handle = res;
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800416 var handle_id = res["handle"].toString().split(',')[0];
417
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700418 // Add to stored map of existing handles
419 asyncQueryManager[handle_id] = {
420 "handle" : handle,
Eugenia Gabrielovae11965b2013-10-29 19:07:42 -0700421 "query" : handle_query, // This will show up when query control button is clicked.
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700422 "data" : APIqueryTracker["data"]
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700423 };
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800424
425 // Create a container for this async query handle
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700426 $('<div/>')
427 .css("margin-left", "1em")
428 .css("margin-bottom", "1em")
429 .css("display", "block")
430 .attr({
431 "class" : "btn-group",
432 "id" : "async_container_" + handle_id
433 })
434 .appendTo("#async-handle-controls");
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800435
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700436 // Adds the main button for this async handle
437 var handle_action_button = '<button class="btn btn-disabled" id="handle_' + handle_id + '">Handle ' + handle_id + '</button>';
438 $('#async_container_' + handle_id).append(handle_action_button);
439 $('#handle_' + handle_id).prop('disabled', true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700440 $('#handle_' + handle_id).on('click', function (e) {
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700441
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700442 // make sure query is ready to be run
443 if (asyncQueryManager[handle_id]["ready"]) {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800444
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700445 APIqueryTracker = {
446 "query" : asyncQueryManager[handle_id]["query"],
447 "data" : asyncQueryManager[handle_id]["data"]
448 };
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800449
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700450 if (!asyncQueryManager[handle_id].hasOwnProperty("result")) {
451 // Generate new Asterix Core API Query
452 A.query_result(
453 { "handle" : JSON.stringify(asyncQueryManager[handle_id]["handle"]) },
454 function(res) {
455 asyncQueryManager[handle_id]["result"] = res;
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800456
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700457 var resultTransform = {
Zachary Heilbron87251352014-03-10 14:41:17 -0700458 "results" : res.results
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700459 };
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800460
zheilbronbebb2202014-03-28 14:36:52 -0700461 tweetbookQuerySyncCallback(resultTransform);
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700462 }
463 );
464 } else {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800465
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700466 var resultTransform = {
Zachary Heilbron87251352014-03-10 14:41:17 -0700467 "results" : asyncQueryManager[handle_id]["result"].results
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700468 };
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800469
zheilbronbebb2202014-03-28 14:36:52 -0700470 tweetbookQuerySyncCallback(resultTransform);
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700471 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700472 }
473 });
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800474
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700475 // Adds a removal button for this async handle
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700476 var asyncDeleteButton = addDeleteButton(
477 "trashhandle_" + handle_id,
478 "async_container_" + handle_id,
479 function (e) {
480 $('#async_container_' + handle_id).remove();
481 delete asyncQueryManager[handle_id];
482 }
483 );
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800484
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700485 $('#async_container_' + handle_id).append('<br/>');
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800486
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700487 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700488}
489
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700490/**
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700491* A spatial data cleaning and mapping call
zheilbronbebb2202014-03-28 14:36:52 -0700492* @param {Object} res, a result object from a tweetbook geospatial query
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700493*/
zheilbronbebb2202014-03-28 14:36:52 -0700494function tweetbookQuerySyncCallback(res) {
Eugenia Gabrielovae11965b2013-10-29 19:07:42 -0700495 // First, we check if any results came back in.
496 // If they didn't, return.
497 if (!res.hasOwnProperty("results")) {
498 reportUserMessage("Oops, no results found for those parameters.", false, "report-message");
499 return;
500 }
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800501
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700502 // Initialize coordinates and weights, to store
503 // coordinates of map cells and their weights
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700504 var coordinates = [];
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700505 var maxWeight = 0;
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700506 var minWeight = Number.MAX_VALUE;
Eugenia Gabrielovaf9fcd712013-10-20 02:37:35 -0700507
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700508 // Parse resulting JSON objects. Here is an example record:
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700509 // { "cell": rectangle("21.5,-98.5 24.5,-95.5"), "count": 78i64 }
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700510 $.each(res.results, function(i, data) {
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700511
512 // We need to clean the JSON a bit to parse it properly in javascript
513 var cleanRecord = $.parseJSON(data
514 .replace('rectangle(', '')
515 .replace(')', '')
516 .replace('i64', ''));
517
518 var recordCount = cleanRecord["count"];
519 var rectangle = cleanRecord["cell"]
520 .replace(' ', ',')
521 .split(',')
522 .map( parseFloat );
523
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800524 // Now, using the record count and coordinates, we can create a
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700525 // coordinate system for this spatial cell.
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700526 var coordinate = {
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700527 "latSW" : rectangle[0],
528 "lngSW" : rectangle[1],
529 "latNE" : rectangle[2],
530 "lngNE" : rectangle[3],
531 "weight" : recordCount
532 };
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800533
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700534 // We track the minimum and maximum weight to support our legend.
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700535 maxWeight = Math.max(coordinate["weight"], maxWeight);
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700536 minWeight = Math.min(coordinate["weight"], minWeight);
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700537
538 // Save completed coordinate and move to next one.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700539 coordinates.push(coordinate);
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700540 });
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700541
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700542 triggerUIUpdate(coordinates, maxWeight, minWeight);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700543}
544
545/**
546* Triggers a map update based on a set of spatial query result cells
547* @param [Array] mapPlotData, an array of coordinate and weight objects
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700548* @param [Array] plotWeights, a list of weights of the spatial cells - e.g., number of tweets
549*/
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700550function triggerUIUpdate(mapPlotData, maxWeight, minWeight) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700551 /** Clear anything currently on the map **/
552 mapWidgetClearMap();
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800553
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700554 // Initialize info windows.
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700555 map_info_windows = {};
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800556
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700557 $.each(mapPlotData, function (m) {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800558
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700559 var point_center = new google.maps.LatLng(
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800560 (mapPlotData[m].latSW + mapPlotData[m].latNE)/2.0,
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700561 (mapPlotData[m].lngSW + mapPlotData[m].lngNE)/2.0);
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700562
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700563 var map_circle_options = {
564 center: point_center,
565 anchorPoint: point_center,
566 radius: mapWidgetComputeCircleRadius(mapPlotData[m], maxWeight),
567 map: map,
568 fillOpacity: 0.85,
Zachary Heilbron046724c2014-03-10 13:42:23 -0700569 fillColor: "#" + rainbow.colourAt(Math.ceil(100 * (mapPlotData[m].weight / maxWeight))),
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700570 clickable: true
571 };
572 var map_circle = new google.maps.Circle(map_circle_options);
573 map_circle.val = mapPlotData[m];
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800574
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700575 map_info_windows[m] = new google.maps.InfoWindow({
576 content: mapPlotData[m].weight + " tweets",
577 position: point_center
578 });
579
580 // Clicking on a circle drills down map to that value, hovering over it displays a count
581 // of tweets at that location.
582 google.maps.event.addListener(map_circle, 'click', function (event) {
583 $.each(map_info_windows, function(i) {
584 map_info_windows[i].close();
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700585 });
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700586 onMapPointDrillDown(map_circle.val);
587 });
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800588
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700589 google.maps.event.addListener(map_circle, 'mouseover', function(event) {
590 if (!map_info_windows[m].getMap()) {
591 map_info_windows[m].setPosition(map_circle.center);
592 map_info_windows[m].open(map);
593 }
594 });
Zachary Heilbron566ba062014-03-11 13:22:54 -0700595 google.maps.event.addListener(map, 'mousemove', function(event) {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800596 map_info_windows[m].close();
Zachary Heilbron046724c2014-03-10 13:42:23 -0700597
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800598 });
599
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -0700600 // Add this marker to global marker cells
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700601 map_cells.push(map_circle);
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800602
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -0700603 // Show legend
604 $("#legend-min").html(minWeight);
605 $("#legend-max").html(maxWeight);
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800606 $("#rainbow-legend-container").show();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700607 });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700608}
609
610/**
611* prepares an Asterix API query to drill down in a rectangular spatial zone
612*
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700613* @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 -0700614*/
615function onMapPointDrillDown(marker_borders) {
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -0700616
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700617 var zoneData = APIqueryTracker["data"];
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800618
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700619 var zswBounds = new google.maps.LatLng(marker_borders.latSW, marker_borders.lngSW);
620 var zneBounds = new google.maps.LatLng(marker_borders.latNE, marker_borders.lngNE);
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800621
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700622 var zoneBounds = new google.maps.LatLngBounds(zswBounds, zneBounds);
623 zoneData["swLat"] = zoneBounds.getSouthWest().lat();
624 zoneData["swLng"] = zoneBounds.getSouthWest().lng();
625 zoneData["neLat"] = zoneBounds.getNorthEast().lat();
626 zoneData["neLng"] = zoneBounds.getNorthEast().lng();
627 var zB = {
628 "sw" : {
629 "lat" : zoneBounds.getSouthWest().lat(),
630 "lng" : zoneBounds.getSouthWest().lng()
631 },
632 "ne" : {
633 "lat" : zoneBounds.getNorthEast().lat(),
634 "lng" : zoneBounds.getNorthEast().lng()
635 }
636 };
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800637
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700638 mapWidgetClearMap();
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800639
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700640 var customBounds = new google.maps.LatLngBounds();
641 var zoomSWBounds = new google.maps.LatLng(zoneData["swLat"], zoneData["swLng"]);
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800642 var zoomNEBounds = new google.maps.LatLng(zoneData["neLat"], zoneData["neLng"]);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700643 customBounds.extend(zoomSWBounds);
644 customBounds.extend(zoomNEBounds);
645 map.fitBounds(customBounds);
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800646
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700647 var df = getDrillDownQuery(zoneData, zB);
648
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700649 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700650 "query_string" : "use dataverse twitter;\n" + df.val(),
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700651 "marker_path" : "static/img/mobile2.png"
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700652 };
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800653
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700654 A.query(df.val(), onTweetbookQuerySuccessPlot);
655}
656
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700657/**
658* Generates an aql query for zooming on a spatial cell and obtaining tweets contained therein.
659* @param parameters, the original query parameters
660* @param bounds, the bounds of the zone to zoom in on.
661*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700662function getDrillDownQuery(parameters, bounds) {
663
664 var zoomRectangle = new FunctionExpression("create-rectangle",
665 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
666 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800667
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700668 var drillDown = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700669 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700670 .LetClause("$region", zoomRectangle);
671
672 if (parameters["keyword"].length == 0) {
673 drillDown = drillDown
674 .WhereClause().and(
675 new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
676 new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
677 new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")')
678 );
679 } else {
680 drillDown = drillDown
681 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
682 .WhereClause().and(
683 new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
684 new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
685 new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")'),
686 new FunctionExpression('contains', '$t.message-text', '$keyword')
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800687 );
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700688 }
689
690 drillDown = drillDown
691 .ReturnClause({
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800692 "tweetId" : "$t.tweetid",
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700693 "tweetText" : "$t.message-text",
694 "tweetLoc" : "$t.sender-location"
695 });
696
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700697 return drillDown;
698}
699
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700700/**
701* Given a location where a tweet exists, opens a modal to examine or update a tweet's content.
702* @param t0, a tweetobject that has a location, text, id, and optionally a comment.
703*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700704function onDrillDownAtLocation(tO) {
705
706 var tweetId = tO["tweetEntryId"];
707 var tweetText = tO["tweetText"];
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800708
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700709 // First, set tweet in drilldown modal to be this tweet's text
710 $('#modal-body-tweet').html('Tweet #' + tweetId + ": " + tweetText);
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800711
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700712 // Next, empty any leftover tweetbook comments or error/success messages
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700713 $("#modal-body-add-to").val('');
714 $("#modal-body-add-note").val('');
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700715 $("#modal-body-message-holder").html("");
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800716
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700717 // Next, if there is an existing tweetcomment reported, show it.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700718 if (tO.hasOwnProperty("tweetComment")) {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800719
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700720 // Show correct panel
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700721 $("#modal-existing-note").show();
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700722 $("#modal-save-tweet-panel").hide();
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800723
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700724 // Fill in existing tweet comment
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700725 $("#modal-body-tweet-note").val(tO["tweetComment"]);
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800726
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700727 // Change Tweetbook Badge
728 $("#modal-current-tweetbook").val(APIqueryTracker["active_tweetbook"]);
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800729
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700730 // Add deletion functionality
731 $("#modal-body-trash-icon").on('click', function () {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800732 // Send comment deletion to asterix
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700733 var deleteTweetCommentOnId = '"' + tweetId + '"';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700734 var toDelete = new DeleteStatement(
735 "$mt",
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700736 APIqueryTracker["active_tweetbook"],
737 new AExpression("$mt.tweetid = " + deleteTweetCommentOnId.toString())
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700738 );
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700739 A.update(
740 toDelete.val()
741 );
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800742
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700743 // Hide comment from map
744 $('#drilldown_modal').modal('hide');
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800745
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700746 // Replot tweetbook
747 onPlotTweetbook(APIqueryTracker["active_tweetbook"]);
748 });
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800749
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700750 } else {
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700751 // Show correct panel
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700752 $("#modal-existing-note").hide();
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700753 $("#modal-save-tweet-panel").show();
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800754
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700755 // Now, when adding a comment on an available tweet to a tweetbook
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700756 $('#save-comment-tweetbook-modal').unbind('click');
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700757 $("#save-comment-tweetbook-modal").on('click', function(e) {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800758
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700759 // Stuff to save about new comment
760 var save_metacomment_target_tweetbook = $("#modal-body-add-to").val();
761 var save_metacomment_target_comment = '"' + $("#modal-body-add-note").val() + '"';
762 var save_metacomment_target_tweet = '"' + tweetId + '"';
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800763
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700764 // Make sure content is entered, and then save this comment.
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700765 if ($("#modal-body-add-note").val() == "") {
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700766
767 reportUserMessage("Please enter a comment about the tweet", false, "report-message");
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800768
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700769 } else if ($("#modal-body-add-to").val() == "") {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800770
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700771 reportUserMessage("Please enter a tweetbook.", false, "report-message");
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800772
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700773 } else {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800774
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700775 // Check if tweetbook exists. If not, create it.
776 if (!(existsTweetbook(save_metacomment_target_tweetbook))) {
777 onCreateNewTweetBook(save_metacomment_target_tweetbook);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700778 }
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800779
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700780 var toInsert = new InsertStatement(
781 save_metacomment_target_tweetbook,
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800782 {
783 "tweetid" : save_metacomment_target_tweet.toString(),
784 "comment-text" : save_metacomment_target_comment
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700785 }
786 );
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800787
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700788 A.update(toInsert.val(), function () {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800789 var successMessage = "Saved comment on <b>Tweet #" + tweetId +
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700790 "</b> in dataset <b>" + save_metacomment_target_tweetbook + "</b>.";
791 reportUserMessage(successMessage, true, "report-message");
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800792
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700793 $("#modal-body-add-to").val('');
794 $("#modal-body-add-note").val('');
795 $('#save-comment-tweetbook-modal').unbind('click');
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800796
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700797 // Close modal
798 $('#drilldown_modal').modal('hide');
799 });
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800800 }
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -0700801 });
802 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700803}
804
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700805/**
806* Adds a new tweetbook entry to the menu and creates a dataset of type TweetbookEntry.
807*/
808function onCreateNewTweetBook(tweetbook_title) {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800809
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700810 var tweetbook_title = tweetbook_title.split(' ').join('_');
811
812 A.ddl(
813 "create dataset " + tweetbook_title + "(TweetbookEntry) primary key tweetid;",
814 function () {}
815 );
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800816
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700817 if (!(existsTweetbook(tweetbook_title))) {
818 review_mode_tweetbooks.push(tweetbook_title);
819 addTweetBookDropdownItem(tweetbook_title);
820 }
821}
822
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700823/**
824* Removes a tweetbook from both demo and from
825* dataverse metadata.
826*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700827function onDropTweetBook(tweetbook_title) {
828
829 // AQL Call
830 A.ddl(
831 "drop dataset " + tweetbook_title + " if exists;",
832 function () {}
833 );
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800834
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700835 // Removes tweetbook from review_mode_tweetbooks
836 var remove_position = $.inArray(tweetbook_title, review_mode_tweetbooks);
837 if (remove_position >= 0) review_mode_tweetbooks.splice(remove_position, 1);
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800838
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700839 // Clear UI with review tweetbook titles
840 $('#review-tweetbook-titles').html('');
841 for (r in review_mode_tweetbooks) {
842 addTweetBookDropdownItem(review_mode_tweetbooks[r]);
843 }
844}
845
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700846/**
847* Adds a tweetbook action button to the dropdown box in review mode.
848* @param tweetbook, a string representing a tweetbook
849*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700850function addTweetBookDropdownItem(tweetbook) {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700851 // Add placeholder for this tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700852 $('<div/>')
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700853 .attr({
854 "class" : "btn-group",
855 "id" : "rm_holder_" + tweetbook
856 }).appendTo("#review-tweetbook-titles");
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800857
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700858 // Add plotting button for this tweetbook
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700859 var plot_button = '<button class="btn btn-default" id="rm_plotbook_' + tweetbook + '">' + tweetbook + '</button>';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700860 $("#rm_holder_" + tweetbook).append(plot_button);
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700861 $("#rm_plotbook_" + tweetbook).width("200px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700862 $("#rm_plotbook_" + tweetbook).on('click', function(e) {
863 onPlotTweetbook(tweetbook);
864 });
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800865
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700866 // Add trash button for this tweetbook
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700867 var onTrashTweetbookButton = addDeleteButton(
868 "rm_trashbook_" + tweetbook,
869 "rm_holder_" + tweetbook,
870 function(e) {
871 onDropTweetBook(tweetbook);
872 }
873 );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700874}
875
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700876/**
877* Generates AsterixDB query to plot existing tweetbook commnets
878* @param tweetbook, a string representing a tweetbook
879*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700880function onPlotTweetbook(tweetbook) {
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800881
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700882 // Clear map for this one
Eugenia Gabrielovae11965b2013-10-29 19:07:42 -0700883 mapWidgetClearMap();
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700884
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700885 var plotTweetQuery = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700886 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700887 .ForClause("$m", new AExpression("dataset " + tweetbook))
888 .WhereClause(new AExpression("$m.tweetid = $t.tweetid"))
889 .ReturnClause({
890 "tweetId" : "$m.tweetid",
891 "tweetText" : "$t.message-text",
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700892 "tweetCom" : "$m.comment-text",
893 "tweetLoc" : "$t.sender-location"
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700894 });
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800895
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700896 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700897 "query_string" : "use dataverse twitter;\n" + plotTweetQuery.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700898 "marker_path" : "static/img/mobile_green2.png",
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700899 "active_tweetbook" : tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700900 };
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800901
902 A.query(plotTweetQuery.val(), onTweetbookQuerySuccessPlot);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700903}
904
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700905/**
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800906* Given an output response set of tweet data,
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700907* prepares markers on map to represent individual tweets.
908* @param res, a JSON Object
909*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700910function onTweetbookQuerySuccessPlot (res) {
911
Eugenia Gabrielovae11965b2013-10-29 19:07:42 -0700912 // First, we check if any results came back in.
913 // If they didn't, return.
914 if (!res.hasOwnProperty("results")) {
915 reportUserMessage("Oops, no data matches this query.", false, "report-message");
916 return;
917 }
918
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700919 // Parse out tweet Ids, texts, and locations
920 var tweets = [];
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700921 var al = 1;
922
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700923 $.each(res.results, function(i, data) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700924
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700925 // First, clean up the data
926 //{ "tweetId": "100293", "tweetText": " like at&t the touch-screen is amazing", "tweetLoc": point("31.59,-84.23") }
927 // We need to turn the point object at the end into a string
928 var json = $.parseJSON(data
929 .replace(': point(',': ')
930 .replace(') }', ' }'));
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700931
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700932 // Now, we construct a tweet object
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700933 var tweetData = {
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700934 "tweetEntryId" : parseInt(json.tweetId),
935 "tweetText" : json.tweetText,
936 "tweetLat" : json.tweetLoc.split(",")[0],
937 "tweetLng" : json.tweetLoc.split(",")[1]
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700938 };
939
940 // If we are parsing out tweetbook data with comments, we need to check
941 // for those here as well.
942 if (json.hasOwnProperty("tweetCom")) {
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700943 tweetData["tweetComment"] = json.tweetCom;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700944 }
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700945
946 tweets.push(tweetData)
947 });
948
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700949 // Create a marker for each tweet
950 $.each(tweets, function(i, t) {
951 // Create a phone marker at tweet's position
952 var map_tweet_m = new google.maps.Marker({
953 position: new google.maps.LatLng(tweets[i]["tweetLat"], tweets[i]["tweetLng"]),
954 map: map,
Eugenia Gabrielovacacc9f82013-10-21 14:10:21 -0700955 icon: APIqueryTracker["marker_path"],
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700956 clickable: true,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700957 });
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700958 map_tweet_m["test"] = t;
959
960 // Open Tweet exploration window on click
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700961 google.maps.event.addListener(map_tweet_m, 'click', function (event) {
962 onClickTweetbookMapMarker(map_tweet_markers[i]["test"]);
963 });
964
965 // Add marker to index of tweets
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800966 map_tweet_markers.push(map_tweet_m);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700967 });
968}
969
Eugenia Gabrielovab2457982013-10-21 14:36:09 -0700970/**
971* Checks if a tweetbook exists
972* @param tweetbook, a String
973*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700974function existsTweetbook(tweetbook) {
975 if (parseInt($.inArray(tweetbook, review_mode_tweetbooks)) == -1) {
976 return false;
977 } else {
978 return true;
979 }
980}
981
Eugenia Gabrielovafcd28682013-10-20 05:36:36 -0700982/**
983* When a marker is clicked on in the tweetbook, it will launch a modal
984* view to examine or edit the appropriate tweet
985*/
986function onClickTweetbookMapMarker(t) {
987 onDrillDownAtLocation(t)
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700988 $('#drilldown_modal').modal();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700989}
990
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700991/**
992* Explore mode: Initial map creation and screen alignment
993*/
994function onOpenExploreMap () {
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700995 var explore_column_height = $('#explore-well').height();
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800996 var right_column_width = $('#right-col').width();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700997 $('#map_canvas').height(explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700998 $('#map_canvas').width(right_column_width + "px");
Zachary Heilbrond98423c2014-03-06 17:37:28 -0800999
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001000 $('#review-well').height(explore_column_height + "px");
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001001 $('#review-well').css('max-height', explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -07001002 $('#right-col').height(explore_column_height + "px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001003}
1004
1005/**
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001006* initializes demo - adds some extra events when review/explore
1007* mode are clicked, initializes tabs, aligns map box, moves
1008* active tab to about tab
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001009*/
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001010function initDemoPrepareTabs() {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001011
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001012 // Tab behavior for About, Explore, and Demo
1013 $('#mode-tabs a').click(function (e) {
1014 e.preventDefault()
1015 $(this).tab('show')
1016 })
1017
1018 // Explore mode should show explore-mode query-builder UI
1019 $('#explore-mode').click(function(e) {
1020 $('#review-well').hide();
1021 $('#explore-well').show();
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -07001022 rectangleManager.setMap(map);
1023 rectangleManager.setDrawingMode(google.maps.drawing.OverlayType.RECTANGLE);
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001024 mapWidgetResetMap();
1025 });
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001026
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001027 // Review mode should show review well and hide explore well
1028 $('#review-mode').click(function(e) {
1029 $('#explore-well').hide();
1030 $('#review-well').show();
1031 mapWidgetResetMap();
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -07001032 rectangleManager.setMap(null);
Eugenia Gabrielovae11965b2013-10-29 19:07:42 -07001033 rectangleManager.setDrawingMode(null);
Eugenia Gabrielovaab1ae552013-10-20 01:12:19 -07001034 });
1035
1036 // Does some alignment necessary for the map canvas
1037 onOpenExploreMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001038}
1039
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001040/**
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001041* Creates a delete icon button using default trash icon
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001042* @param {String} id, id for this element
1043* @param {String} attachTo, id string of an element to which I can attach this button.
1044* @param {Function} onClick, a function to fire when this icon is clicked
1045*/
1046function addDeleteButton(iconId, attachTo, onClick) {
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001047
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001048 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 -07001049 $('#' + attachTo).append(trashIcon);
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001050
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001051 // When this trash button is clicked, the function is called.
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001052 $('#' + iconId).on('click', onClick);
1053}
1054
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001055/**
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001056* Creates a message and attaches it to data management area.
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001057* @param {String} message, a message to post
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001058* @param {Boolean} isPositiveMessage, whether or not this is a positive message.
1059* @param {String} target, the target div to attach this message.
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001060*/
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001061function reportUserMessage(message, isPositiveMessage, target) {
1062 // Clear out any existing messages
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001063 $('#' + target).html('');
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001064
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001065 // Select appropriate alert-type
1066 var alertType = "alert-success";
1067 if (!isPositiveMessage) {
1068 alertType = "alert-danger";
1069 }
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001070
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001071 // Append the appropriate message
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001072 $('<div/>')
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -07001073 .attr("class", "alert " + alertType)
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001074 .html('<button type="button" class="close" data-dismiss="alert">&times;</button>' + message)
1075 .appendTo('#' + target);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001076}
1077
1078/**
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001079* mapWidgetResetMap
1080*
1081* [No Parameters]
1082*
1083* Clears ALL map elements - plotted items, overlays, then resets position
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001084*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001085function mapWidgetResetMap() {
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001086
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001087 mapWidgetClearMap();
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001088
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001089 // Reset map center and zoom
1090 map.setCenter(new google.maps.LatLng(38.89, -77.03));
1091 map.setZoom(4);
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001092
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -07001093 // Selection button
1094 $("#selection-button").trigger("click");
1095 rectangleManager.setMap(map);
1096 rectangleManager.setDrawingMode(google.maps.drawing.OverlayType.RECTANGLE);
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001097}
1098
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001099/**
1100* mapWidgetClearMap
Eugenia Gabrielovad07556a2013-10-11 03:45:06 -07001101* Removes data/markers
1102*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001103function mapWidgetClearMap() {
1104
1105 // Remove previously plotted data/markers
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001106 for (c in map_cells) {
1107 map_cells[c].setMap(null);
1108 }
1109 map_cells = [];
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001110
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001111 $.each(map_info_windows, function(i) {
1112 map_info_windows[i].close();
1113 });
1114 map_info_windows = {};
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001115
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001116 for (m in map_tweet_markers) {
1117 map_tweet_markers[m].setMap(null);
1118 }
1119 map_tweet_markers = [];
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001120
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -07001121 // Hide legend
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -07001122 $("#rainbow-legend-container").hide();
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001123
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -07001124 // Reenable submit button
Eugenia Gabrielova12de0302013-10-18 02:25:39 -07001125 $("#submit-button").attr("disabled", false);
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001126
Eugenia Gabrielovaea8f9482013-10-28 04:49:18 -07001127 // Hide selection rectangle
1128 if (selectionRectangle) {
1129 selectionRectangle.setMap(null);
1130 selectionRectangle = null;
1131 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001132}
1133
1134/**
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001135* buildLegend
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001136*
1137* Generates gradient, button action for legend bar
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001138*/
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001139function buildLegend() {
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001140
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001141 // Fill in legend area with colors
1142 var gradientColor;
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001143
Eugenia Gabrielovad6e88e02013-10-19 08:26:54 -07001144 for (i = 0; i<100; i++) {
1145 //$("#rainbow-legend-container").append("" + rainbow.colourAt(i));
1146 $("#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 -07001147 }
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001148}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001149
1150/**
1151* Computes radius for a given data point from a spatial cell
1152* @param {Object} keys => ["latSW" "lngSW" "latNE" "lngNE" "weight"]
1153* @returns {number} radius between 2 points in metres
1154*/
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001155function mapWidgetComputeCircleRadius(spatialCell, wLimit) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001156
1157 // Define Boundary Points
1158 var point_center = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1159 var point_left = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, spatialCell.lngSW);
1160 var point_top = new google.maps.LatLng(spatialCell.latNE, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001161
1162 // Circle scale modifier =
Zachary Heilbron566ba062014-03-11 13:22:54 -07001163 var scale = 425 + 425*(spatialCell.weight / wLimit);
Zachary Heilbrond98423c2014-03-06 17:37:28 -08001164
Eugenia Gabrielova8b34c652013-10-19 06:53:27 -07001165 // Return proportionate value so that circles mostly line up.
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -07001166 return scale * Math.min(distanceBetweenPoints(point_center, point_left), distanceBetweenPoints(point_center, point_top));
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001167}
1168
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001169/**
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -07001170* Calculates the distance between two latlng locations in km, using Google Geometry API.
1171* @param p1, a LatLng
1172* @param p2, a LatLng
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001173*/
Eugenia Gabrielovaf29a55f2013-10-28 03:18:18 -07001174function distanceBetweenPoints (p1, p2) {
1175 return 0.001 * google.maps.geometry.spherical.computeDistanceBetween(p1, p2);
Eugenia Gabrielova3d7bfe52013-10-28 02:36:44 -07001176};