import repository from arizona
[raven.git] / apps / mapdisplay / js / mapdisplay.js
1 /*
2  * Justin Samuel <jsamuel@cs.arizona.edu>
3  * Date: 2008-03-19
4  *
5  * Periodically checks a url for lat/long data to map using the Google
6  * Maps API.
7  *
8  * JSON data is expected as:
9  *     [data, legend]
10  *        data = [[float lat, float long, string name, int version], ...]
11  *        legend = [[int color, str desc], ...]
12  */
13
14 // Frequency in milliseconds that the client should check for new data.
15 var UPDATE_FREQUENCY = 5000;
16
17 var DEFAULT_DB = "arizona_demo";
18
19 // Where the json data is grabbed from.
20 var DEFAULT_OWL_URL = "/owl"
21 var UPDATE_BASE_URL = "/mapviewer/map/getMapData"
22 var UPDATE_LINE_BASE_URL = "/mapviewer/map/getLineData"
23 var OWL_URL = ""
24 var UPDATE_URL = ""
25 var UPDATE_LINE_URL = ""
26 var OWL_JSON_URL = ""
27 var DB = ""
28 var DBLINES = ""
29 var LINES_MODE = false;
30 var COLORFIELD = ""
31 var POSTPROCESS = ""
32
33
34 // The id of the page element that has the google map.
35 var MAP_ELEMENT_ID = "map_canvas";
36
37 var haveDatabases = false;
38 var haveFields = false;
39
40 // Setup different color markers for different client reported versions.
41
42 // these new icons have a different size than the old ones
43 var newIconSize = new GSize(20, 34)
44 var greyIcon = new GIcon(G_DEFAULT_ICON);
45 greyIcon.image = "http://www.google.com/intl/en_us/mapfiles/marker_grey.png"
46 greyIcon.iconSize = newIconSize;
47
48 var iconSize = new GSize(32,32);
49 var blueIcon = new GIcon(G_DEFAULT_ICON);
50 blueIcon.image = "http://www.google.com/intl/en_us/mapfiles/ms/micons/blue-dot.png";
51 blueIcon.iconSize = iconSize;
52 var redIcon = new GIcon(G_DEFAULT_ICON);
53 redIcon.image = "http://www.google.com/intl/en_us/mapfiles/ms/micons/red-dot.png";
54 redIcon.iconSize = iconSize;
55 var greenIcon = new GIcon(G_DEFAULT_ICON);
56 greenIcon.image = "http://www.google.com/intl/en_us/mapfiles/ms/micons/green-dot.png";
57 greenIcon.iconSize = iconSize;
58 var yellowIcon = new GIcon(G_DEFAULT_ICON);
59 yellowIcon.image = "http://www.google.com/intl/en_us/mapfiles/ms/micons/yellow-dot.png";
60 yellowIcon.iconSize = iconSize;
61 var markerOptions = [ {icon:greyIcon}, {icon:blueIcon}, {icon:redIcon}, {icon:greenIcon}, {icon:yellowIcon} ];
62 var colors = [ "#777777", "#0000FF", "#FF0000", "#00FF00", "#FFFF00" ];
63
64 var legendPanel;
65 var map;
66 var blah;
67 var data = {};
68 var lineData = {};
69 var intervalId;
70
71 function MyPane() {}
72 MyPane.prototype = new GControl;
73 MyPane.prototype.initialize = function(map) {
74     var me = this;
75     me.panel = document.createElement("div");
76     me.panel.style.width = "150px";
77     me.panel.style.height = "100px";
78     me.panel.style.border = "1px solid gray";
79     me.panel.style.background = "white";
80     me.panel.innerHTML = "Hello World!";
81     blah = me.panel;
82     map.getContainer().appendChild(me.panel);
83     return me.panel;
84 };
85 MyPane.prototype.getDefaultPosition = function() {
86     return new GControlPosition(
87         G_ANCHOR_TOP_RIGHT, new GSize(10, 50));
88 };
89 MyPane.prototype.getPanel = function() {
90     return me.panel;
91 };
92 MyPane.prototype.updateLegend = function(legend) {
93     me.panel.innerHTML = "legend";
94 };
95
96 function log(msg) {
97     setTimeout(function() {\r
98         throw new Error(msg);\r
99     }, 0);\r
100 }\r
101
102 function get_query_argument(name, href, defaultval) {
103 \r
104   href = (href) || top.location.href;\r
105   name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");\r
106 \r
107   var regexS = "[\\?&]"+name+"=([^&#]*)";\r
108   var regex = new RegExp( regexS );\r
109   var results = regex.exec(href);\r
110 \r
111   return (results == null ? defaultval : results[1]);\r
112 };
113
114 function initialize() {
115     if (!GBrowserIsCompatible()) {
116         showMessage("Your browser will not work with this demo.");
117         return;
118     }
119     //clearMessage();
120     intervalId = setInterval(getData, UPDATE_FREQUENCY);
121     map = new GMap2(document.getElementById(MAP_ELEMENT_ID));
122     map.setCenter(new GLatLng(20, 0), 3);
123     var mapControl = new GMapTypeControl();
124     map.addControl(mapControl);
125     map.addControl(new GLargeMapControl());
126
127     DB = get_query_argument("db", null, DEFAULT_DB);
128     DBLINES = get_query_argument("dblines", null, "")
129     COLORFIELD = get_query_argument("colorfield", null, "mapviewer.color")
130     POSTPROCESS = get_query_argument("postprocess", null, "none")
131
132     OWL_URL = get_query_argument("owlurl", null, DEFAULT_OWL_URL);
133
134     updatePostProcessCombo();
135     updateOwlCombo();
136     updateUrls();
137 }
138
139 function updateUrls() {
140     OWL_JSON_URL = OWL_URL + "/json";
141     UPDATE_URL = UPDATE_BASE_URL + "?owljsonurl=" + OWL_JSON_URL  +
142                                    "&db=" + DB +
143                                    "&colorfield=" + COLORFIELD +
144                                    "&postprocess=" + POSTPROCESS;
145
146     UPDATE_LINE_URL = UPDATE_LINE_BASE_URL + "?owljsonurl=" + OWL_JSON_URL  +
147                                    "&db=" + DB +
148                                    "&dbLines=" + DBLINES +
149                                    "&colorfield=" + "vini.ping" +
150                                    "&postprocess=" + "percentile-reverse";
151
152     LINES_MODE = (DBLINES != "")
153
154     clearData();
155
156     haveFields = false;
157
158     //showMessage(UPDATE_URL);
159 }
160
161 function showDetail(site) {
162     detailFrame = top.frames[1]
163     detailFrame.location = OWL_URL + "?db=" + DB + "&filterval=" + site + "&showheader=false";
164 }
165
166 function showLegend(msg) {
167     msgDiv = document.getElementById("legend");
168     msgDiv.innerHTML = msg;
169     msgDiv.style.display = "block";
170 }
171
172 function showMessage(msg) {
173     msgDiv = document.getElementById("message");
174     msgDiv.innerHTML = msg;
175     msgDiv.style.display = "block";
176 }
177
178 function clearMessage() {
179     top.frames[0].document.getElementById("message").style.display = "none";
180 }
181
182 function getDatabases() {
183     var req = new XMLHttpRequest();
184     req.open("GET", OWL_JSON_URL+"?what=databases", true);
185     req.onreadystatechange = function (e) {
186         if (req.readyState == 4) {
187             if(req.status == 200) {
188                  updateDatabasesCombo(eval('(' + req.responseText + ')')["databases"]);
189             }
190         }
191     };
192     req.send(null);
193 }
194
195 function getFields() {
196     var req = new XMLHttpRequest();
197     req.open("GET", OWL_JSON_URL+"?db="+DB+"&what=modules", true);
198     req.onreadystatechange = function (e) {
199         if (req.readyState == 4) {
200             if(req.status == 200) {
201                  updateFieldCombo(eval('(' + req.responseText + ')')["modules"]);
202             }
203         }
204     };
205     req.send(null);
206 }
207
208 function getData() {
209     if (!haveDatabases) {
210         getDatabases();
211     }
212
213     // if still waiting for the combo to fill, return
214     if (DB=="(waiting)") {
215         return;
216     }
217
218     var req = new XMLHttpRequest();
219     // keep track of the old URL, so we don't display data after the user changed a combo
220     req.url = UPDATE_URL;
221     req.open("GET", UPDATE_URL, true);
222     req.onreadystatechange = function (e) {
223         if (req.readyState == 4) {
224             if (req.url != UPDATE_URL) {
225                 // the user changed the URL inbetween the time we sent the request
226                 // and received the response. Ignore the response.
227                 log("received old update" + req.url);
228             } else if (req.status == 200) {
229                 updateData(eval(req.responseText)[0]);
230                 if (!LINES_MODE) {
231                     showLegend(eval(req.responseText)[2]);
232                 }
233                 clearMessage();
234             } else {
235                 showMessage("Error retrieving data.");
236             }
237         }
238     };
239     req.send(null);
240
241     if (LINES_MODE) {
242         getLineData();
243     }
244
245     if (!haveFields) {
246         getFields();
247     }
248 }
249
250 function getLineData() {
251     var req = new XMLHttpRequest();
252     req.open("GET", UPDATE_LINE_URL, true);
253     req.onreadystatechange = function (e) {
254         if (req.readyState == 4) {
255             if(req.status == 200) {
256                 updateLineData(eval(req.responseText)[0]);
257                 showLegend(eval(req.responseText)[2]);
258                 //clearMessage();
259             } else {
260                 //showMessage("Error retrieving data.");
261                 // This could be used to halt future updates.
262                 //clearInterval(intervalId);
263             }
264         }
265     };
266     req.send(null);
267 }
268
269 function createMarker(point, version, site) {
270     var options = markerOptions[version];
271     options.title = site;
272     var marker = new GMarker(point, options)
273     GEvent.addListener(marker, "click", function() { var mySite=site; showDetail(mySite); });
274     return marker;
275 }
276
277 function updateDatabasesCombo(databasesList) {
278     var found=false;
279     dbcombo = document.getElementById("databasecombo");
280     dbcombo.options.length=0
281     for (var i=0; i < databasesList.length; i++) {
282         if (DB==databasesList[i]) {
283             found=true;
284         }
285         dbcombo.options[i] = new Option(databasesList[i], databasesList[i], false, (DB==databasesList[i]))
286     }
287     if ((!found) && (databasesList.length>0)) {
288         DB=databasesList[0];
289         log("default db: " + DB);
290         updateUrls();
291     }
292     haveDatabases = true
293 }
294
295 function databasesComboChange(object) {
296     DB = object.options[object.selectedIndex].value;
297     log("new DB: " + DB);
298     updateUrls();
299 }
300
301 function updateFieldCombo(moduleList) {
302     fieldcombo = document.getElementById("fieldcombo");
303     fieldcombo.options.length=0;
304
305     var fieldCount = 0;
306     for (var i=0; i<moduleList.length; i++) {
307         module = moduleList[i];
308         moduleName = module["module"];
309         fieldList = module["fields"].split(",");
310
311         for (var j=0; j<fieldList.length; j++) {
312             fieldName = fieldList[j];
313             s = moduleName + "." + fieldName;
314             fieldcombo.options[fieldCount] = new Option(s, s, false, (COLORFIELD == s));
315             fieldCount++;
316         }
317     }
318     haveFields = true;
319 }
320
321 function fieldComboChange(object) {
322     COLORFIELD = object.options[object.selectedIndex].value;
323     log("new colorfield: " + COLORFIELD);
324     updateUrls()
325 }
326
327 function updatePostProcessCombo() {
328     var postprocessList = ["none", "version", "version-reverse", "percentile", "percentile-reverse", "green", "digdugdemo"];
329
330     postprocesscombo = document.getElementById("postprocesscombo");
331     postprocesscombo.options.length=0;
332
333     if (POSTPROCESS=="") {
334         POSTPROCESS="none";
335     }
336
337     for (var i=0; i<postprocessList.length; i++) {
338         s = postprocessList[i]
339         postprocesscombo.options[i] = new Option(s, s, false, (POSTPROCESS == s));
340     }
341 }
342
343 function postprocessComboChange(object) {
344     POSTPROCESS = object.options[object.selectedIndex].value;
345     log("new postprocess: " + POSTPROCESS);
346     updateUrls();
347 }
348
349 function updateOwlCombo() {
350     var owlList = ["/owl", "/owl_beta", "/owl_alpha"];
351
352     owlcombo = document.getElementById("owlcombo");
353     owlcombo.options.length=0;
354
355     for (var i=0; i<owlList.length; i++) {
356         s = owlList[i]
357         //log(s + " " + OWL_URL);
358         owlcombo.options[i] = new Option(s, s, false, (OWL_URL == s));
359     }
360 }
361
362 function owlComboChange(object) {
363     OWL_URL = object.options[object.selectedIndex].value;
364     log("new owl url: " + OWL_URL);
365     updateDatabasesCombo(["(waiting)"])
366     haveDatabases=false;
367     updateUrls();
368 }
369
370 function clearData(newData) {
371     data = {};
372     lineData = {};
373     map.clearOverlays();
374 }
375
376 /*
377  * Look through the newData to see if we should add or update points.
378  */
379 function updateData(newData) {
380     for (var i = 0; i < newData.length; i++) {
381         var latNum = newData[i][0];
382         var lngNum = newData[i][1];
383         var site = newData[i][2];
384         var version = newData[i][3];
385         var lat = String(latNum);
386         var lng = String(lngNum);
387         if (!data[lat]) {
388             data[lat] = {}
389         }
390         if (!data[lat][lng]) {
391             data[lat][lng] = {}
392         }
393         // If this point already exists, see if we should update the marker
394         // color because the version has changed.
395         if (data[lat][lng][site]) {
396             if (data[lat][lng][site].version == version) {
397                 continue;
398             }
399             map.removeOverlay(data[lat][lng][site].marker);
400         }
401
402         // a -1 indicates a point that we should not display
403         if (version == -1) {
404             continue;
405         }
406
407         // Add the point to the map (either it doesn't exist or we just
408         // removed it because the version has changed).
409         // Randomize the point a bit so that multiple of the same lat/long
410         // don't overlap.
411         var point = new GLatLng(latNum + Math.random() / 4, lngNum + Math.random() / 4);
412         var marker = createMarker(point, version, site);
413         data[lat][lng][site] = {site:site, version:version, marker:marker};
414         map.addOverlay(marker);
415     }
416 }
417
418 function updateLineData(newData) {
419     for (var i = 0; i < newData.length; i++) {
420         var latNum1 = newData[i][0];
421         var lngNum1 = newData[i][1];
422         var lat1 = String(latNum1);
423         var lng1 = String(lngNum1);
424         var latNum2 = newData[i][2];
425         var lngNum2 = newData[i][3];
426         var lat2 = String(latNum2);
427         var lng2 = String(lngNum2);
428         var version = newData[i][4]
429
430         if (!lineData[lat1]) {
431             lineData[lat1] = {};
432         }
433         if (!lineData[lat1][lng1]) {
434             lineData[lat1][lng1] = {};
435         }
436         if (!lineData[lat1][lng1][lat2]) {
437             lineData[lat1][lng1][lat2] = {};
438         }
439         if (lineData[lat1][lng1][lat2][lng2]) {
440             if (lineData[lat1][lng1][lat2][lng2].version == version) {
441                 continue;
442             }
443             map.removeOverlay(lineData[lat1][lng1][lat2][lng2].polyline);
444         }
445         var polyline = new GPolyline([new GLatLng(latNum1, lngNum1), new GLatLng(latNum2, lngNum2)], colors[version], 8);
446         lineData[lat1][lng1][lat2][lng2] = {version:version, polyline:polyline};
447         map.addOverlay(polyline);
448     }
449 }
450