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