1 var madrona = (function(){
  2 
  3     var options = {};
  4     var that = {};    
  5     var layers = [];
  6     
  7     var localAuthority = new URI(window.location.href).getAuthority();
  8     
  9     var constructor_defaults = {
 10         hideGoogleLayers: false,
 11         rememberMapExtent: true,
 12         displayEnhancedContent: false,
 13         srcWhitelist: [new RegExp('^http://' + localAuthority)],
 14         targetWhitelist: [new RegExp('^http://' + localAuthority)]
 15     };
 16     
 17     
 18     that.addLayer = function(url, opts){
 19         layers.push({url: url, opts: opts});
 20     };
 21 
 22     that.init = function(opts){
 23         options = jQuery.extend({}, constructor_defaults, opts);
 24         that.options = opts;
 25         
 26         $('#sidebar').tabs({
 27             select: function(event, ui){
 28                 if($(this).hasClass('masked')){
 29                     event.preventDefault();
 30                 }
 31             }
 32         });
 33         
 34         var selectedTab = getStore('selectedTab');
 35         if(selectedTab){
 36             $('#sidebar').tabs('select', selectedTab);
 37         };
 38                 
 39         resize();
 40         
 41         $('.madrona-panel').live('click', function(){
 42             if(!$(this).hasClass('madrona-menu-items')){
 43                 madrona.menu_items.closeAll();
 44             }
 45         });
 46         
 47         if(window.google){
 48             google.earth.createInstance(
 49                 "map", 
 50                 function(i){
 51                     geInit(i);
 52                 },
 53                 function(code){
 54                     geFailure(code);
 55                 });
 56             setupListeners();
 57             madrona.menu_items.init($('.menu_items'));
 58             // makes button clicking a lot more reliable!!
 59         }else{
 60             geFailure();
 61         }
 62         // just so that buttons act more like buttons
 63         $(document).find('#meta-navigation a, a.button, a.close, ' +
 64             '.menu_items span, .ui-tabs-nav a').live('dragstart', function(){
 65             return false;
 66         });
 67     };
 68 
 69     var geInit = function(pluginInstance){
 70         ge = pluginInstance;
 71         ge.getWindow().setVisibility(true); // required
 72         ge.getOptions().setStatusBarVisibility(true);
 73         gex = new GEarthExtensions(ge);
 74         $(that).trigger('geReady');
 75         
 76         that.geocoder = new madrona.map.geocoder(gex, $('#flyToLocation'));
 77         that.measureTool = new madrona.measureTool();
 78                 
 79         $('#measure_distance').click(function(){
 80             that.measureTool.clear();
 81             that.measureTool.measureDistance(gex, "measureAmount");
 82             $('#measure_clear').removeClass('disabled');
 83             $('#measureAmountHolder').show();
 84         });
 85         $('#measure_area').click(function(){
 86             that.measureTool.clear();
 87             that.measureTool.measureArea(gex, "measureAmount");
 88             $('#measure_clear').removeClass('disabled');
 89             $('#measureAmountHolder').show();
 90         });
 91         $('#measure_clear').click(function(){
 92             that.measureTool.clear();
 93             $(this).addClass('disabled');
 94             $('#measureAmountHolder').hide();
 95         });
 96         $('#measure_units').change(function(){
 97             that.measureTool.setUnits($(this).val());
 98         });
 99         
100         var setEarthOptions = function(){
101             $('#earthOptions li').each(function(){
102                 var li = $(this);
103                 switch(li.attr('id')){
104                     case 'nav':
105                         if(li.hasClass('visible')){
106                             ge.getNavigationControl().setVisibility(
107                                 ge.VISIBILITY_AUTO);
108                         }else{
109                             ge.getNavigationControl().setVisibility(
110                                 ge.VISIBILITY_HIDE);
111                         }
112                         break;
113                     case 'overview':
114                         ge.getOptions().setOverviewMapVisibility(
115                             li.hasClass('visible'));
116                         break;
117                     case 'scale':
118                         ge.getOptions().setScaleLegendVisibility(
119                             li.hasClass('visible'));
120                         break;
121                     case 'atm':
122                         ge.getOptions().setAtmosphereVisibility(
123                             li.hasClass('visible'));
124                         break;
125                     case 'terrain':
126                         ge.getLayerRoot().enableLayerById(
127                             ge.LAYER_TERRAIN, li.hasClass('visible'));
128                         break;
129                 }
130             });
131         }
132         
133         $('#earthOptions li').click(function(e){
134             $(this).toggleClass('visible');
135             setEarthOptions();
136         });
137         
138         setEarthOptionsFromLocalStore();
139         setEarthOptions();
140         
141         var cameraSet = false;
142         if(options.rememberMapExtent){
143             cameraSet = setCameraFromLocalStorage();
144         }
145                 
146         for(var i=0; i<layers.length; i++){
147             var div = $('<div id="datalayerstree'+i+'"></div>');
148             $('#datalayerstree').append(div);
149             layers[i].tree = kmltree({
150                 url: layers[i].url,
151                 gex: gex,
152                 mapElement: $('#map'),
153                 element: div,
154                 restoreState: !$.browser.msie,
155                 refreshWithState: false,
156                 displayEnhancedContent: options.displayEnhancedContent,
157                 setExtent: !cameraSet && layers[i].opts && 
158                     layers[i].opts.setExtent
159             });
160             layers[i].tree.load();
161             // if its the public layer tree, bind the kmlload to trigger the ready event 
162             if (layers[i].url.indexOf('public') != -1) {
163                 $(layers[i].tree).bind("kmlLoaded", function() { 
164                     $(that).trigger('publicReady', [ge, gex]);
165                 });
166             };
167             if(layers[i]['opts'] && layers[i]['opts'].showDownloadLink){
168                 $('#datalayerstree').append('<p class="download_layer"><a href="'+layers[i].url+'">Download this layer</a> for use in Google Earth or your own website.</p>');
169             }
170         }
171 
172         that.layers = layers;
173 
174         if(!that.options.hideGoogleLayers){
175             var div = $('<div id="googlelayers"></div>');
176             $('#datalayerstree').append(div);
177 
178             var googleLayers = kmltree({
179                 url: options.media_url + 'common/fixtures/earthLayers.kml',
180                 gex: gex, 
181                 mapElement: $('#map'), 
182                 element: div,
183                 restoreState: true,
184                 supportItemIcon: true
185             });
186 
187             var updateGoogleLayers = function(tree){
188                 $('#googlelayers li').each(function(){
189                     var item = $(this);
190                     var name = item.find('span.name').text();
191                     switch(name){
192                         case 'Grid':
193                             ge.getOptions().setGridVisibility(
194                                 item.hasClass('visible'));
195                             break;
196                         case '3d Buildings':
197                             ge.getLayerRoot().enableLayerById(
198                                 ge.LAYER_BUILDINGS, item.hasClass('visible'));
199                             break;
200                         case 'Low Resolution 3d Buildings':
201                             ge.getLayerRoot().enableLayerById(
202                                 ge.LAYER_BUILDINGS_LOW_RESOLUTION, 
203                                 item.hasClass('visible'));
204                             break;
205                         case 'Roads':
206                             ge.getLayerRoot().enableLayerById(
207                                 ge.LAYER_ROADS, item.hasClass('visible'));
208                             break;
209                         case 'Borders and Labels':
210                             ge.getLayerRoot().enableLayerById(
211                                 ge.LAYER_BORDERS, item.hasClass('visible'));
212                             break;
213                     }
214                 });
215             }
216 
217             $(googleLayers).bind('kmlLoaded', function(){
218                 updateGoogleLayers(googleLayers);
219                 $(that).trigger('earthReady', [ge, gex]);
220             });
221 
222             $(googleLayers).bind('toggleItem', function(){
223                 updateGoogleLayers(googleLayers);
224             });
225 
226             googleLayers.load();
227         } else {
228             // No google layers, just trigger the event
229             $(that).trigger('earthReady', [ge, gex]);
230         }
231                 
232         var panel = madrona.panel({appendTo: $('#panel-holder'), 
233             showCloseButton: false});
234             
235         setupSidebarLinkHandler(panel);
236         
237         var editors = [];
238         
239         if(options.myshapes){
240             for(var i=0;i<options.myshapes.length; i++){
241                 var url = options.myshapes[i].url;
242                 var callback = options.myshapes[i]['callback'];
243                 var editor = madrona.features.kmlEditor({
244                     gex: gex,
245                     appendTo: '#myshapestree',
246                     url: options.myshapes[i].url,
247                     enhancedContent: options.displayEnhancedContent,
248                     panel: panel,
249                     textLookup: options.textLookup,
250                     refreshButton: options.refreshButton
251                 });
252                 if(callback){
253                     $(editor).bind('kmlLoaded', function(event, e, kmlObject){
254                         callback(this, this.el, kmlObject)
255                     });
256                 }
257                 editors.push(editor);
258                 $(editor.tree).bind('copyDone', function(e,location) {
259                     var myshapesEditor = editors[0];
260                     myshapesEditor.tree.clearSelection();
261                     myshapesEditor.refresh( function() {
262                         var node = myshapesEditor.tree.getNodesById(location);
263                         if(node.length === 1) {
264                             myshapesEditor.tree.selectNode(node); 
265                         }
266                     });
267                 });
268             }
269         }
270         
271         $('a.myshapes-link').live('click', function(e){
272             e.preventDefault();
273             $('.madrona-panel:visible a.close').click();
274             var href = $(this).attr('href');
275             for(var i=0;i<editors.length;i++){
276                 var editor = editors[i];
277                 var nodes = editor.tree.getNodesById(href);
278                 if(nodes.length === 1){
279                     editor.tree.selectNode(nodes);
280                     nodes.trigger('dblclick');
281                     return false;
282                 }
283             }
284             return false;
285         });
286         
287         that.editors = editors;
288 
289         if(options.sharedshapes){
290             for(var i=0;i<options.sharedshapes.length; i++){
291                 var editor = madrona.features.kmlEditor({
292                     gex: gex,
293                     appendTo: '#sharedshapestree',
294                     url: options.sharedshapes[i],
295                     enhancedContent: options.displayEnhancedContent,
296                     panel: panel,
297                     textLookup: options.textLookup,
298                     refreshButton: options.refreshButton
299                 });
300                 $(editor.tree).bind('copyDone', function(e, location) {
301                     $('#sidebar').tabs('select', "#MyShapes");
302                     var myshapesEditor = that.editors[0];
303                     myshapesEditor.tree.clearSelection();
304                     myshapesEditor.refresh( function() {
305                         var node = myshapesEditor.tree.getNodesById(location);
306                         if(node.length === 1) {
307                             myshapesEditor.tree.selectNode(node); 
308                         }
309                     });
310                 });
311                 editors.push(editor);
312             }            
313         }
314         
315         var onEditorSelect = function(e, originalEvent, node, kmlObject){
316             if(node){
317                 if(node.parents('#MyShapes').length !== 0){
318                     $('#sidebar').tabs('select', '#MyShapes');
319                 }else{
320                     $('#sidebar').tabs('select', '#SharedShapes');
321                 }
322             }
323         };
324                 
325         var onEditorEdit = function(e, data, status, xhr, context){
326             // myshapes panel is the only one that needs refreshing
327             var editor = editors[0];
328             $('a[href=#MyShapes]').click();
329             var info = typeof data === 'object' ? data:jQuery.parseJSON(data);
330             var select = info['X-Madrona-Select'];
331             var show = info['X-Madrona-Show'];
332             var untoggle = info['X-Madrona-UnToggle'];
333             var hint = info['X-Madrona-Parent-Hint'];
334             // var select = xhr.getResponseHeader('X-Madrona-Select');
335             $(editor.tree).one('kmlLoaded', function(){
336                 if(select){
337                     editor.tree.clearSelection();
338                     select = select.split(' ');
339                     var nodes = [];
340                     for(var i = 0; i < select.length; i++){
341                         var id = select[i];
342                         var ns = editor.tree.getNodesById(id);
343                         if(ns.length){
344                             nodes.push(ns[0]);
345                         }
346                     }
347                     if(nodes.length){
348                         editor.tree.selectNodes($(nodes));                        
349                     }else{
350                         // only supports one hint for now. TODO? Not needed?
351                         var nl = editor.tree.getNodesById(hint.split(' ')[0]);
352                         if(nl.length){
353                             $(editor.tree).one('networklinkload', function(){
354                                 var nodes = [];
355                                 for(var i = 0; i < select.length; i++){
356                                     var id = select[i];
357                                     var ns = editor.tree.getNodesById(id);
358                                     if(ns.length){
359                                         nodes.push(ns[0]);
360                                     }
361                                 }
362                                 if(nodes.length){
363                                     editor.tree.selectNodes($(nodes));                        
364                                 };
365                             });
366                             editor.tree.openNetworkLink(nl);
367                         }
368                     }
369                 }
370                 // Not sure why a timeout helps here, but otherwise the 
371                 // dblclick event wont trigger
372                 setTimeout(function(){
373                     if(untoggle){
374                         untoggle = untoggle.split(' ');
375                         for(var i = 0; i < untoggle.length; i++){
376                             var id = untoggle[i];
377                             var ns = editor.tree.getNodesById(id);
378                             $(ns[0]).find('> .toggler').click();
379                         }
380                     }
381                     if(show){
382                         var node = editor.tree.getNodesById(show);
383                         if(node.length){
384                             $(node).trigger('dblclick');
385                         }
386                     }
387                 }, 20);
388             });
389             editor.refresh();
390         }
391         
392         for(var i=0;i<editors.length;i++){
393             $(editors[i]).bind('select', onEditorSelect);
394             $(editors[i]).bind('edit', onEditorEdit);
395         };
396         
397         $('#sidebar, #meta-navigation').click(function(e){
398             if(e.target === this || e.target === $('#MyShapes')[0]){
399                 for(var i=0;i<editors.length;i++){
400                     editors[i].clearSelection();
401                 }
402             }
403         });
404         
405         // If news or about links aren't included in the interface these will
406         // do nothing (good for extensible templates).
407         $('#news').click(function(e){
408             opts = {};
409             opts['load_msg'] = 'Loading News';
410             opts['showClose'] = true;
411             panel.showUrl(that.options.news_url, opts);
412             e.preventDefault();
413         });
414 
415         $('#about').click(function(e){
416             opts = {};
417             opts['load_msg'] = 'Loading Intro';
418             opts['showClose'] = true;
419             panel.showUrl(that.options.about_url, opts);
420             e.preventDefault();
421         });
422 
423         $('#signin').click(function(e){
424             opts = {};
425             opts['load_msg'] = 'Loading Sign In Form';
426             opts['showClose'] = true;
427             panel.showUrl(that.options.signin_url, opts);
428             e.preventDefault();
429         });
430 
431         $('#profile').click(function(e){
432             opts = {};
433             opts['load_msg'] = 'Loading User Profile';
434             opts['showClose'] = true;
435             panel.showUrl(that.options.profile_url, opts);
436             e.preventDefault();
437         });
438 
439         $('#register').click(function(e){
440             opts = {};
441             opts['load_msg'] = 'Loading Registration Form';
442             opts['showClose'] = true;
443             panel.showUrl(that.options.registration_url, opts);
444             e.preventDefault();
445         });
446 
447         $('#help').click(function(e){
448             opts = {};
449             opts['load_msg'] = 'Loading Help Page';
450             opts['showClose'] = true;
451             panel.showUrl(that.options.help_url, opts);
452             e.preventDefault();
453         });
454 
455         // for showing the news or about panels if they haven't been viewed 
456         // yet (that determination is done with cookies in the django view)
457         if(options.show_panel){
458             opts = {};
459             opts['showClose'] = true;
460             if(options.show_panel == 'about' && that.options.about_url){
461                 panel.showUrl(that.options.about_url, opts);
462             }else if(options.show_panel == 'news' && that.options.news_url){
463                 panel.showUrl(that.options.news_url, opts);
464             }else if(options.show_panel == 'signin' && that.options.signin_url){
465                 panel.showUrl(that.options.signin_url, opts);
466             }
467         }
468 
469         // A UI enhancement to add a drop-shadow from sidebar falling on map
470         var url = that.options.media_url + 'common/kml/shadow.kmz';
471         google.earth.fetchKml(ge, url, function(k){
472             ge.getFeatures().appendChild(k);
473         });
474                 
475         $('#sidebar-toggler').click(function(){
476             $('#map_container').css({
477                 'width': '100%', 
478                 'left': 0, 
479                 'z-index': 10
480             });
481             $('.menu_items').hide();
482             $('#sidebar > .ui-tabs-nav').hide();
483             $(this).hide();
484             $('#show-sidebar').show();
485         });
486         
487         $('#show-sidebar').click(function(){
488             $(this).hide();
489             $('#map_container').css({left: 500});
490             $('.menu_items').show();
491             $('#sidebar > .ui-tabs-nav').show();            
492             $('#sidebar-toggler').show();
493             resize();
494         });
495 
496         $('#create_bookmark').click( function() {
497             var camera = ge.getView().copyAsCamera(ge.ALTITUDE_RELATIVE_TO_GROUND);
498             var url = "/bookmark/tool/";
499             // Get public layer state
500             var tree = null;
501             for(var i=0; i<madrona.layers.length; i++){
502                 if(madrona.layers[i].url.indexOf('public') != -1) {
503                     tree = madrona.layers[i].tree;
504                     break;
505                 }
506             }
507             if (tree) { 
508                 var publicstate = JSON.stringify(tree.getState());
509             } else {
510                 var publicstate = '{}';
511             }
512             // Get camera params
513             var params = { 
514                 Latitude : camera.getLatitude(),
515                 Longitude : camera.getLongitude(),
516                 Altitude : camera.getAltitude(),
517                 Heading : camera.getHeading(),
518                 Tilt : camera.getTilt(),
519                 Roll : camera.getRoll(),
520                 AltitudeMode : camera.getAltitudeMode(),
521                 publicstate: publicstate
522             };
523             // Post the info and display the returned URL
524             xhr = $.post( url, params,
525                 function( data, status, xhr ) {
526                     $('#bookmark_url').text(data);
527                 }
528             )
529             .error(function(a,b,c) {
530                 $('#bookmark_url').html("We encountered an error while saving your bookmark:<br/>" + a.responseText + " <br/> " + c + " , " + a.status );
531             })
532             .complete(function() { 
533                 $('#bookmark_results').show();
534                 $('#bookmark_url').selText();
535             });
536         });
537         
538         window.onbeforeunload = function(){
539             setCameraToLocalStorage();
540             saveEarthOptionsToLocalStore();
541             setStore('selectedTab', 
542                 $('#sidebar > ul > .ui-tabs-selected a').attr('href'));
543         }
544     };
545     
546     var studyRegionLoaded = function(kmlObject, node){
547         // Reorder so studyRegion is on top of the list
548         $('#datalayerstree').prepend(node);
549         if (kmlObject.getAbstractView()){
550             ge.getView().setAbstractView(kmlObject.getAbstractView());
551         }   
552     };
553     
554     var setStore = function(key, value){
555         if(!!window.localStorage){
556             localStorage.setItem(key, value);
557         }
558     };
559     
560     var getStore = function(key){
561         if(!!window.localStorage){
562             return localStorage.getItem(key);
563         }else{
564             return false;
565         }
566     }
567     
568     var setEarthOptionsFromLocalStore = function(){
569         if(!!window.localStorage && localStorage.getItem('earthOptions')){
570             var json = JSON.parse(localStorage.getItem('earthOptions'));
571             for(key in json){
572                 $('#earthOptions').find('#'+key).toggleClass('visible', 
573                     json[key]);
574             }
575         }else{
576             return false;
577         }
578     };
579     
580     var saveEarthOptionsToLocalStore = function(){
581         if(!!window.localStorage){
582             var json = {};
583             $('#earthOptions li').each(function(){
584                 json[$(this).attr('id')] = $(this).hasClass('visible');
585             });
586             localStorage.setItem('earthOptions', JSON.stringify(json));
587         }
588     };
589     
590     var geFailure = function(errorCode){
591         // alert("Failure loading the Google Earth Plugin: " + errorCode);
592     }
593     
594     var setupListeners = function(){
595         $(window).smartresize(function(){
596             resize();
597         });
598         $('#meta-navigation').click(function(){
599             madrona.menu_items.closeAll();
600             $('#panel-holder').find('a.close:visible').click();
601         });
602         $('#sidebar').bind('mouseup', function(e){
603             madrona.menu_items.closeAll();
604             // return false;
605         });
606         $('#sidebar-mask').click(function(){
607             madrona.menu_items.closeAll();
608         });
609         $('#sidebar > .ui-tabs-nav li a').click(function(){
610             $('#panel-holder').find('a.close:visible').click();
611         });
612         
613         $('#panel-holder').click(function(e){
614             if(e.originalTarget === this){
615                 madrona.menu_items.closeAll();
616                 $('#panel-holder').find('a.close:visible').click();
617             }
618         });
619     };
620     
621     var resize = function(){
622         var mh = $('#meta-navigation').outerHeight();
623         var h = $(document.body).height() - mh;
624         $('#sidebar').css({top: mh, height: h});
625         $('#panel-holder').css({top: mh, height: h});
626         
627         var w = $(document.body).width() - $('#sidebar').width();
628         $('#map_container').height(h).width(w);
629     };
630     
631     that.resize = resize;
632     
633     that.maskSidebar = function(){
634         $('#panel-holder').show();
635         $('#sidebar').addClass('masked');
636     };
637     
638     that.unmaskSidebar = function(){
639         if($('#panel-holder').find('.madrona-panel:visible').length === 0){
640             $('#panel-holder').hide();
641             $('#sidebar').removeClass('masked');
642         }
643     };
644     
645     that.showLoadingMask = function(msg){
646         var msg = msg || 'Loading';
647         var lmsg = $('#sidebar-mask').find('span.loadingMsg');
648         lmsg.text(msg);
649         lmsg.show();
650         madrona.menu_items.disable();
651         $('#sidebar').addClass('masked');
652     };
653     
654     that.hideLoadingMask = function(){
655         $('#sidebar-mask').find('span.loadingMsg').hide();
656         madrona.menu_items.enable();
657         that.unmaskSidebar();
658     }
659     
660     var panels = [];
661     
662     that.addPanel = function(panel){
663         panels.push(panel);
664         $(panel).bind('panelshow', onPanelShown);
665         $(panel).bind('panelhide', onPanelHide);
666         $(panel).bind('panelclose', onPanelHide);
667     };
668     
669     var onPanelShown = function(e, panel){
670         that.maskSidebar();
671     };
672     
673     var onPanelHide = function(e, panel){
674         var count = 0;
675         for(var i=0; i<panels.length; i++){
676             var p = panels[i];
677             if(p.shown){
678                 count++;
679             }
680         }
681         if(count <= 0){
682             that.unmaskSidebar();
683         }
684     };
685     
686     var setCameraToLocalStorage = function(){
687         if(!!window.localStorage){
688             localStorage.setItem('madrona-camera', gex.view.serialize());
689         }
690     };
691     
692     var setCameraFromLocalStorage = function(){
693         if(!!window.localStorage && localStorage.getItem('madrona-camera')){
694             gex.view.deserialize(localStorage.getItem('madrona-camera'));
695             return true;
696         }
697         return false;
698     };
699         
700     that.persistentReports = {};
701 
702     var setupSidebarLinkHandler = function(panel){
703         $('#map a[rel=sidebar]').live('click', function(e){
704             var balloon = ge.getBalloon();
705             if(balloon){
706                 var feature = balloon.getFeature();
707                 if(feature && 'getUrl' in feature){
708                     var src = feature.getUrl();
709                     if(src){
710                         var target = $(this).attr('href');
711                         if(safeURI(src, target)){
712                             panel.showUrl(target, {
713                                 load_msg: $(this).attr('title')
714                             });
715                             e.preventDefault();
716                         }
717                     }
718                 }
719             }
720         });
721     };
722     
723     var safeURI = function(src, target){
724         var src = new URI(src);
725         var target = new URI(target);
726         var wuri = new URI(window.location.href);
727         if(target.getAuthority() === null){
728             target = target.resolve(wuri);
729         }
730         if(src.getAuthority() === null){
731             src = src.resolve(wuri);
732         }
733         src = src.toString();
734         target = target.toString();
735         var safeSrc = false;
736         for(var i = 0; i<options.srcWhitelist.length; i++){
737             if(safeSrc === false){
738                 safeSrc = options.srcWhitelist[i].test(src);
739             }
740         }
741         var safeTarget = false;
742         for(var i = 0; i<options.targetWhitelist.length; i++){
743             if(safeTarget === false){
744                 safeTarget = options.targetWhitelist[i].test(target);
745             }
746         }
747         return (safeTarget && safeSrc);
748     };
749 
750     return that;
751 })();
752