1 /** 2 * Nautical Charts API 3 * NOAA, RNC, Nautical, Navigation, Charts 4 * 5 * @file NauticalChartsAPI.js 6 * @date 2010-10-20 16:28 HST 7 * @author Paul Reuter <preuter@ucsd.edu> 8 * @version 1.0.1 9 * 10 * @depends Google Maps API v2.x 11 * 12 * @modifications 13 * 1.0.0 - 2010-08-25 - Created 14 * 1.0.0 - 2010-08-26 - Continued development 15 * 1.0.0 - 2010-10-19 - Continued development 16 * 1.0.1 - 2010-10-20 - Add documentation; BugFix: missing methods. 17 */ 18 19 20 // =========================================================================== 21 // Class: NauticalChartsAPI 22 // =========================================================================== 23 24 25 /** 26 * The API object, used to access Chart and Panel metadata and tile layers. 27 * 28 * @constructor 29 * @extends Object 30 */ 31 function NauticalChartsAPI() { 32 // Programmatic access to the API version. 33 34 /** 35 * API version 36 * @type string 37 */ 38 this.version = "1.0.1"; 39 40 /** 41 * Default TileLayer opacity 42 * @private 43 * @type float 44 */ 45 this.opacity = 0.8; 46 47 /** 48 * Array of {@link Chart} objects 49 * @type array 50 */ 51 this.charts = []; 52 53 /** 54 * Flag indicating load status 55 * @private 56 * @type bool 57 */ 58 this._loaded = false; 59 60 // Do stuffs 61 this.m_initializeRNCs(); 62 }; 63 NauticalChartsAPI.prototype = new Object; 64 65 66 /** 67 * Test if the API object has loaded and is initialized. 68 * 69 * @return bool true if loaded, false otherwise 70 * @type bool 71 */ 72 NauticalChartsAPI.prototype.isLoaded = function() { 73 return this._loaded; 74 } // END: function isLoaded 75 76 77 /** 78 * Fetch the Chart objected identified by chart number (eg. 18740) 79 * 80 * @param string no A chart number, like 1116A or something. 81 * @return Chart The chart object, or null if none found. 82 * @type Chart 83 */ 84 NauticalChartsAPI.prototype.getChartByNumber = function(no) { 85 for(var i=0,n=this.charts.length; i<n; i++) { 86 if( this.charts[i].getNumber() == no ) { 87 return this.charts[i]; 88 } 89 } 90 return null; 91 } // END: function getChartByNumber(no) 92 93 94 /** 95 * Fetch the Panel objected identified by file name (eg. 18740_1, 18740_1.KAP) 96 * 97 * @param string no A KAP file name, like '18740_1.KAP', or '18740_1' 98 * @return Panel The panel object, or null if none found. 99 * @type Panel 100 */ 101 NauticalChartsAPI.prototype.getPanelByFileName = function(fn) { 102 var cn = fn.split('_')[0]; 103 var chart = this.getChartByNumber(cn); 104 return (!chart) ? null : chart.getPanelByFileName(fn); 105 } // END: function getPanelByFileName(fn) 106 107 108 /** 109 * Fetch the Panel objected identified by NGA catalog number (eg. 1893) 110 * 111 * @param int no A panel number, like those assigned by the NGA. 112 * @return Panel The panel object, or null if none found. 113 * @type Panel 114 */ 115 NauticalChartsAPI.prototype.getPanelByNumber = function(no) { 116 for(var i=0,n=this.charts.length; i<n; i++) { 117 var panels = this.charts[i].getPanels(); 118 for(var j=0,m=panels.length; j<m; j++) { 119 if( panels[j].getNumber() == no ) { 120 return panels[j]; 121 } 122 } 123 } 124 return null; 125 } // END: function getPanelByNumber(no) 126 127 128 /** 129 * Fetch an array of all Chart objects. 130 * 131 * @return array of {@link Chart} objects. 132 * @type array 133 */ 134 NauticalChartsAPI.prototype.getCharts = function() { 135 return this.charts; 136 } // END: function getCharts() 137 138 139 /** 140 * Find all charts that contain the point identified by latlng. 141 * 142 * @param GLatLng latlng A Google latlng object. 143 * @return array of {@link Chart} objects having a panel that contains latlng. 144 * @type array 145 * @see Panel 146 */ 147 NauticalChartsAPI.prototype.getChartsByLatLng = function(latlng) { 148 var charts = []; 149 for(var i=0,n=this.charts.length; i<n; i++) { 150 if( this.charts[i].hasLatLng(latlng) ) { 151 charts.push( this.charts[i] ); 152 } 153 } 154 return charts; 155 } // END: function getChartsByLatLng(latlng) 156 157 158 /** 159 * Find all panels that contain the point identified by latlng. 160 * 161 * @param GLatLng latlng A Google latlng object. 162 * @return array of {@link Panel} objects that contain latlng. 163 * @type array 164 */ 165 NauticalChartsAPI.prototype.getPanelsByLatLng = function(latlng) { 166 var panels = []; 167 for(var i=0,ni=this.charts.length; i<ni; i++) { 168 for(var j=0,nj=this.charts[i].panels.length; j<nj; j++) { 169 if( this.charts[i].panels[j].hasLatLng(latlng) ) { 170 panels.push( this.charts[i].panels[j] ); 171 } 172 } 173 } 174 return panels; 175 } // END: function getPanelsByLatLng(latlng) 176 177 178 /** 179 * Find all charts that overlap bounds. 180 * 181 * @param GLatLngBounds bounds A Google LatLngBounds object. 182 * @return array of {@link Chart} objects having a panel that overlaps bounds. 183 * @type array 184 * @see Panel 185 * @see GLatLngBounds 186 */ 187 NauticalChartsAPI.prototype.getChartsByBounds = function(bounds) { 188 var charts = []; 189 for(var i=0,n=this.charts.length; i<n; i++) { 190 if( this.charts[i].overlapsBounds(bounds) ) { 191 charts.push( this.charts[i] ); 192 } 193 } 194 return charts; 195 } // END: function getChartsByBounds(bounds) 196 197 198 /** 199 * Find all panels that overlap the specified bounds. 200 * 201 * @param GLatLngBounds A Google lat/lon bounds object. 202 * @return array of {@link Panel} objects that overlap bounds. 203 * @type array 204 */ 205 NauticalChartsAPI.prototype.getPanelsByBounds = function(bounds) { 206 var panels = []; 207 for(var i=0,ni=this.charts.length; i<ni; i++) { 208 for(var j=0,nj=this.charts[i].panels.length; j<nj; j++) { 209 if( this.charts[i].panels[j].overlapsBounds(bounds) ) { 210 panels.push( this.charts[i].panels[j] ); 211 } 212 } 213 } 214 return panels; 215 } // END: function getPanelsByBounds(bounds) 216 217 218 /** 219 * Find all charts that overlap bounds, as specified by n,e,s,w. 220 * 221 * @param float n North-most coordinate 222 * @param float e East-most coordinate 223 * @param float s South-most coordinate 224 * @param float w West-most coordinate 225 * @return array of {@link Chart} objects having a panel that overlaps bounds. 226 * @type array 227 * @see NauticalChartsAPI#getChartsByBounds 228 */ 229 NauticalChartsAPI.prototype.getChartsByBoundsNESW = function(n,e,s,w) { 230 var bounds = new GBounds(new GLatLng(s,w),new GLatLng(n,e)); 231 return this.getChartsByBounds(bounds); 232 } // END: function getChartsByBoundsNESW(n,e,s,w) 233 234 235 /** 236 * Find all panels that overlap the specified bounds. 237 * 238 * @param float n North-most coordinate 239 * @param float e East-most coordinate 240 * @param float s South-most coordinate 241 * @param float w West-most coordinate 242 * @return array of {@link Panel} objects that overlap bounds. 243 * @type array 244 * @see Chart#getPanelsByBounds 245 */ 246 NauticalChartsAPI.prototype.getPanelsByBoundsNESW = function(n,e,s,w) { 247 var bounds = new GBounds(new GLatLng(s,w),new GLatLng(n,e)); 248 return this.getPanelsByBounds(bounds); 249 } // END: function getPanelsByBoundsNESW(n,e,s,w) 250 251 252 /** 253 * Get the opacity of newly created TileLayerOverlays will be. 254 * 255 * @return float The opacity [0,1] of future created GTileLayerOverlay. 256 * @type float 257 * @see GTileLayerOverlay 258 */ 259 NauticalChartsAPI.prototype.getOpacity = function() { 260 return this.opacity; 261 }; // END: function getOpacity() 262 263 264 /** 265 * Set the opacity of newly created TileLayerOverlays will be. 266 * 267 * @param float o The opacity [0,1] of future created GTileLayerOverlay. 268 * @return bool True if opacity is in range, false otherwise. 269 * @type bool 270 * @see GTileLayerOverlay 271 */ 272 NauticalChartsAPI.prototype.setOpacity = function(o) { 273 this.opacity = (o>1) ? o/100 : o; 274 return (o>100||o<0) ? false : true; 275 }; // END: function setOpacity(o) 276 277 278 /** 279 * Load and parse remote JSON metadata. 280 * This is done async to speed-up page load, at expense of complexity. 281 * 282 * @private 283 * @return bool always true 284 */ 285 NauticalChartsAPI.prototype.m_initializeRNCs = function() { 286 this._loaded = false; 287 this.m_appendScript( 288 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js', 289 'jquery_143',function() { 290 var that = this; 291 jQuery.ajaxSetup({async:false}); 292 jQuery.getJSON( 293 'http://cordc.ucsd.edu/js/RNC/rnc.php?callback=?', 294 function(jcharts,status) { 295 that.charts = []; 296 for(var c=0,nc=jcharts.length; c<nc; c++) { 297 that.charts[c] = new Chart(that); 298 var chart = that.charts[c]; 299 chart.number = jcharts[c].chart_id; 300 chart.title = jcharts[c].title; 301 chart.sourceEdition = jcharts[c].source_edition; 302 chart.rasterEdition = jcharts[c].raster_edition; 303 chart.ntmEdition = jcharts[c].ntm_edition; 304 chart.sourceDate = jcharts[c].source_date; 305 chart.ntmDate = jcharts[c].ntm_date; 306 for(var p=0,np=jcharts[c].panels.length; p<np; p++) { 307 var jpanel = jcharts[c].panels[p]; 308 that.charts[c].panels[p] = new Panel(chart); 309 var panel = that.charts[c].panels[p]; 310 panel.encodedPolygon = jpanel.encoded_polygon; 311 panel.encodedLevels = jpanel.encoded_levels; 312 panel.scale = jpanel.scale; 313 panel.fileName = jpanel.file_name.toUpperCase(); 314 panel.number = jpanel.panel_no; 315 } // end: for panels[0..M-1] 316 } // end: for jcharts[0..N-1] 317 318 that._loaded = true; 319 if( typeof(GEvent) !== 'undefined' ) { 320 GEvent.trigger(that,'load'); 321 } 322 }); 323 }); 324 } // END: function m_initializeRNCs() 325 326 327 328 /** 329 * Append a script to the head section of our HTML website. 330 * Used to load jQuery's awesome JSON features. 331 * 332 * @private 333 * @param string url A url of a javascript to append to header section. 334 * @param name An optional name to give to this script (for replacement later) 335 * @param function cb A callback to execute on script load. 336 * @return bool true for success, always true. 337 * @type bool 338 */ 339 NauticalChartsAPI.prototype.m_appendScript = function(url,name,cb) { 340 if( typeof(name) == 'undefined' ) { 341 name = '__nci_' + (new Date()).getTime() + '_' + 342 Math.round(Math.random()*1000); 343 } else { 344 name = '__nci_' + name; 345 } 346 347 var head = document.getElementsByTagName("head")[0]; 348 var scriptTag = document.getElementById(name); 349 if(scriptTag) head.removeChild(scriptTag); 350 script = document.createElement('script'); 351 script.src = url; 352 script.type = 'text/javascript'; 353 script.id = name; 354 script.onload_done = false; 355 356 var that = this; 357 script.onload = function() { 358 if( !script.onload_done ) { 359 script.onload_done = true; 360 that.m_tell(name); 361 if(typeof(cb) !== 'undefined') { 362 cb.call(that); 363 } 364 } 365 }; 366 367 script.onreadystatechange = function() { 368 if( ( "loaded"===script.readyState || "complete"===script.readyState ) 369 && !script.onload_done ) { 370 script.onload_done = true; 371 that.m_tell(name); 372 if(typeof(cb) !== 'undefined') { 373 cb.call(that); 374 } 375 } 376 } 377 378 head.appendChild(script); 379 this.m_wait(name,true); 380 381 return true; 382 } // END: function m_appendScript(url,name=null) 383 384 385 /** 386 * Notify the object of an event. (Silly-man's event handling) 387 * -- Unused at present -- 388 * 389 * @private 390 * @param string name The name of an event to mark as occuring. 391 * @return bool always true. 392 * @type bool 393 */ 394 NauticalChartsAPI.prototype.m_tell = function(name) { 395 if( typeof(this.m_notices) == 'undefined' ) { 396 this.m_notices = []; 397 } 398 if( typeof(this.m_notices[name]) == 'undefined' ) { 399 this.m_notices[name] = [true,null]; 400 } else { 401 this.m_notices[name][0] = true; 402 } 403 return true; 404 } // END: function m_tell(name) 405 406 407 /** 408 * Wait for an event to occur, then return. 409 * This basically freezes execution until some event happens. 410 * -- Unused at present -- 411 * 412 * @private 413 * @param string name The name of an event to mark as occuring. 414 * @return bool always true. 415 * @type bool 416 */ 417 NauticalChartsAPI.prototype.m_wait = function(name,sem) { 418 if( typeof(this.m_notices) == 'undefined' ) { 419 this.m_notices = []; 420 } 421 // First time this is called, the state is assumed false. 422 if( typeof(sem) !== 'undefined' 423 || typeof(this.m_notices[name]) === 'undefined') { 424 this.m_notices[name] = [false,null]; 425 } 426 427 if( this.m_notices[name][1] ) { 428 window.clearTimeout(this.m_notices[name][1]); 429 this.m_notices[name][1] = null; 430 } 431 432 if( !this.m_notices[name][0] ) { 433 // sleep 434 // TODO 435 } else { 436 return true; 437 } 438 } // END: function m_wait(name,sem) 439 440 441 442 // =========================================================================== 443 // Class: Chart 444 // =========================================================================== 445 446 447 448 /** 449 * Create a new Chart object. A chart holds title, edition and panel info. 450 * 451 * @constructor 452 * @extends Object 453 * @param NauticalChartsAPI api The parent API object to associate with. 454 * 455 * @return Chart A new object. 456 * @type Chart 457 * @see NauticalChartsAPI 458 */ 459 function Chart(api) { 460 /** 461 * Reference to parent API object 462 * @private 463 * @type NauticalChartsAPI 464 */ 465 this.api = api; 466 467 /** 468 * A chart number (1116A, or something) 469 * @private 470 * @type string 471 */ 472 this.number = null; 473 474 /** 475 * The chart's title 476 * @private 477 * @type string 478 */ 479 this.title = null; 480 481 /** 482 * Data source edition 483 * @private 484 * @type number 485 */ 486 this.sourceEdition = null; 487 488 /** 489 * Printed raster edition 490 * @private 491 * @type number 492 */ 493 this.rasterEdition = null; 494 495 /** 496 * Notice to Mariners edition 497 * @private 498 * @type number 499 */ 500 this.ntmEdition = null; 501 502 /** 503 * Date of data source edition 504 * @private 505 * @type string 506 */ 507 this.sourceDate = null; 508 509 /** 510 * Date of NTM edition 511 * @private 512 * @type string 513 */ 514 this.ntmDate = null; 515 516 /** 517 * Array of {@link Panel} objects. 518 * @type array 519 */ 520 this.panels = []; 521 522 // Do stuffs 523 return this; 524 } 525 Chart.prototype = new Object; 526 527 528 /** 529 * Get the parent API object. Used internally for the most part. 530 * 531 * @return NauticalChartsAPI 532 * @type NauticalChartsAPI 533 */ 534 Chart.prototype.getAPI = function() { 535 return this.api; 536 } // END: function getAPI() 537 538 539 /** 540 * Fetch the chart number of this object (eg. 18740) 541 * 542 * @return string A chart number. 543 * @type string 544 */ 545 Chart.prototype.getNumber = function() { 546 return this.number; 547 } // END: function getNumber() 548 549 550 /** 551 * Fetch the chart title of this object (eg. "San Diego to Santa Rosa Island") 552 * 553 * @return string The title(s) of this chart. 554 * @type string 555 */ 556 Chart.prototype.getTitle = function() { 557 return this.title; 558 } // END: function getTitle() 559 560 561 /** 562 * Fetch the source edition, a number. 563 * 564 * @return number Source Edition 565 * @type number 566 */ 567 Chart.prototype.getSourceEdition = function() { 568 return this.sourceEdition; 569 } // END: function getSourceEdition() 570 571 572 /** 573 * Fetch the raster edition, a number. 574 * 575 * @return Raster Edition 576 * @type number 577 */ 578 Chart.prototype.getRasterEdition = function() { 579 return this.rasterEdition; 580 } // END: function getRasterEdition() 581 582 583 /** 584 * Fetch the NTM (notice to mariners) edition, a number. 585 * 586 * @return NTM Edition 587 * @type number 588 */ 589 Chart.prototype.getNTMEdition = function() { 590 return this.ntmEdition; 591 } // END: function getNTMEdition() 592 593 594 /** 595 * Fetch the date the source edition was published. 596 * 597 * @return Source Date (YYYY-MM-DD) 598 * @type string 599 */ 600 Chart.prototype.getSourceDate = function() { 601 return this.sourceDate; 602 } // END: function getSourceDate() 603 604 605 /** 606 * Fetch the date the NTM edition was published. 607 * 608 * @return string NTM Date (YYYY-MM-DD) 609 * @type string 610 */ 611 Chart.prototype.getNTMDate = function() { 612 return this.ntmDate; 613 } // END: function getNTMDate() 614 615 616 /** 617 * Return a bounding box of all panels contained by this chart. 618 * 619 * @return GLatLngBounds A bounding box. 620 * @type GLatLngBounds 621 * @see GLatLngBounds 622 */ 623 Chart.prototype.getBounds = function() { 624 if( this.panels.length < 1 ) { 625 return null; 626 } 627 var bounds = this.panels[0].getBounds(); 628 for(var i=1,n=this.panels.length; i<n; i++) { 629 var tmp = this.panels[i].getBounds(); 630 bounds.extend(tmp.getSouthWest()); 631 bounds.extend(tmp.getNorthEast()); 632 } 633 return bounds; 634 } // END: function getBounds() 635 636 637 /** 638 * Test to see if this chart has data for a specified GLatLng location. 639 * 640 * @param GLatLng latlng A Google lat/lon object. 641 * @return bool true if any {@link Panel} contains the latlng, false if none do. 642 * @type bool 643 * @see GLatLng 644 */ 645 Chart.prototype.hasLatLng = function(latlng) { 646 for(var i=0,n=this.panels.length; i<n; i++) { 647 if( this.panels[i].hasLatLng(latlng) ) { 648 return true; 649 } 650 } 651 return false; 652 } // END: function hasLatLng(latlng) 653 654 655 /** 656 * Test to see if any of this chart's panels overlap the specified bounds. 657 * 658 * @param GLatLngBounds A Google lat/lon bounds object. 659 * @return bool true if any {@link Panel} overlaps the bounds, false if none. 660 * @type bool 661 * @see GLatLngBounds 662 */ 663 Chart.prototype.overlapsBounds = function(bounds) { 664 return bounds.intersects(this.getBounds()); 665 } // END: function overlapsBounds(bounds) 666 667 668 /** 669 * Test to see if any of this chart's panels overlap the specified bounds. 670 * 671 * @param float n North-most coordinate 672 * @param float e East-most coordinate 673 * @param float s South-most coordinate 674 * @param float w West-most coordinate 675 * @return bool true if any panel overlaps the bounds, false if none do. 676 * @type bool 677 * @see Chart#overlapsBounds 678 */ 679 Chart.prototype.overlapsBoundsNESW = function(n,e,s,w) { 680 var bounds = new GBounds(new GLatLng(s,w),new GLatLng(n,e)); 681 return this.overlapsBounds(bounds); 682 } // END: function overlapsBoundsNESW(n,e,s,w) 683 684 685 /** 686 * Fetch an array of all panel objects contained by this chart. 687 * 688 * @return array of {@link Panel} objects. 689 * @type array 690 */ 691 Chart.prototype.getPanels = function() { 692 return this.panels; 693 } // END: function getPanels() 694 695 696 /** 697 * Fetch the panel identified by the file name (eg. 18740_1, or 18740_1.KAP) 698 * 699 * @param string fn A file name. This method safely compares panel file names. 700 * @return Panel The object identified by the file name fn, or null if none. 701 * @type Panel 702 */ 703 Chart.prototype.getPanelByFileName = function(fn) { 704 for(var i=0,n=this.panels.length; i<n; i++) { 705 if( this.panels[i].matchesFileName(fn) ) { 706 return this.panels[i]; 707 } 708 } 709 return null; 710 } // END: function getPanelByFileName(fn) 711 712 713 /** 714 * Fetch the Panel objected identified by NGA catalog number (eg. 1893) 715 * 716 * @param int no A panel number, like those assigned by the NGA. 717 * @return Panel The panel object, or null if none found. 718 * @type Panel 719 */ 720 Chart.prototype.getPanelByNumber = function(no) { 721 for(var i=0,n=this.panels.length; i<n; i++) { 722 if( panels[i].getNumber() == no ) { 723 return panels[i]; 724 } 725 } 726 return null; 727 } // END: function getPanelByNumber(no) 728 729 730 /** 731 * Find all panels that contain the point identified by latlng. 732 * 733 * @param GLatLng latlng A Google latlng object. 734 * @return array of {@link Panel} objects that contain latlng. 735 * @type array 736 */ 737 Chart.prototype.getPanelsByLatLng = function(latlng) { 738 var panels = []; 739 for(var i=0,n=this.panels.length; i<n; i++) { 740 if( this.panels[i].hasLatLng(latlng) ) { 741 panels.push( this.panels[i] ); 742 } 743 } 744 return panels; 745 } // END: function getPanelsByLatLng(latlng) 746 747 748 /** 749 * Find all panels that overlap the specified bounds. 750 * 751 * @param GLatLngBounds A Google lat/lon bounds object. 752 * @return array of {@link Panel} objects that overlap bounds. 753 * @type array 754 */ 755 Chart.prototype.getPanelsByBounds = function(bounds) { 756 var panels = []; 757 for(var i=0,n=this.panels.length; i<n; i++) { 758 if( this.panels[i].overlapsBounds(bounds) ) { 759 panels.push( this.panels[i] ); 760 } 761 } 762 return panels; 763 } // END: function getPanelsByBounds(bounds) 764 765 766 /** 767 * Find all panels that overlap the specified bounds. 768 * 769 * @param float n North-most coordinate 770 * @param float e East-most coordinate 771 * @param float s South-most coordinate 772 * @param float w West-most coordinate 773 * @return array of {@link Panel} objects that overlap bounds. 774 * @type array 775 * @see Chart#getPanelsByBounds 776 */ 777 Chart.prototype.getPanelsByBoundsNESW = function(n,e,s,w) { 778 var bounds = new GBounds(new GLatLng(s,w),new GLatLng(n,e)); 779 return this.getPanelsByBounds(bounds); 780 } // END: function getPanelsByBoundsNESW(n,e,s,w) 781 782 783 // =========================================================================== 784 // Class: Panel 785 // =========================================================================== 786 787 788 789 /** 790 * Create a new Panel object 791 * 792 * @constructor 793 * @extends Object 794 * @param Chart chart The parent {@link Chart} object. 795 * @return Panel A new Panel object. 796 * @type Panel 797 * @see Chart 798 */ 799 function Panel(chart) { 800 /** 801 * Reference to parent's {@link chart} object. 802 * @private 803 * @type Chart 804 */ 805 this.chart = chart; 806 807 /** 808 * ASCII representation of a GPolygon 809 * @private 810 * @type string 811 */ 812 this.encodedPolygon = null; 813 814 /** 815 * ASCII representation of level-of-detail for a GPolygon 816 * @private 817 * @type string 818 */ 819 this.encodedLevels = null; 820 821 /** 822 * A GPolygon that represents the bounds of the panel 823 * @private 824 * @type GPolygon 825 */ 826 this.polygon = null; 827 828 /** 829 * Map scale resolution (1:40000 would be represented as 40000) 830 * @private 831 * @type int 832 */ 833 this.scale = null; 834 835 /** 836 * The KAP file name (base name) used by NOAA for their RNC storage. 837 * @private 838 * @type string 839 */ 840 this.fileName = null; 841 842 /** 843 * The NGA panel number identifier. 844 * @private 845 * @type int 846 */ 847 this.number = null; 848 849 // Do stuffs 850 return this; 851 } 852 Panel.prototype = new Object; 853 854 855 /** 856 * Get the parent API object. Used internally for the most part. 857 * 858 * @return NauticalChartsAPI 859 * @type NauticalChartsAPI 860 */ 861 Panel.prototype.getAPI = function() { 862 return this.chart.getAPI(); 863 } // END: function getAPI() 864 865 866 /** 867 * Get the parent Chart object. Used internally for the most part. 868 * 869 * @return Chart 870 * @type Chart 871 */ 872 Panel.prototype.getChart = function() { 873 return this.chart; 874 } // END: function getChart() 875 876 877 /** 878 * Fetch the string representation of the polygon that identifies this panel. 879 * 880 * @return string Some ASCII encoding of a polyline. 881 * @type string 882 */ 883 Panel.prototype.getEncodedPolygon = function() { 884 return this.encodedPolygon; 885 } // END: function getEncodedPolygon() 886 887 888 /** 889 * Fetch the string representation of the polygon's level-of-detail 890 * 891 * @return string Some ASCII encoding of a polyline's level-of-detail. 892 * @type string 893 */ 894 Panel.prototype.getEncodedLevels = function() { 895 return this.encodedLevels; 896 } // END: function getEncodedLevels() 897 898 899 /** 900 * Create a new GPolygon object using the internal encoded representation 901 * of the bounds (an encodedPolygon, with encodedLevels) 902 * 903 * @return GPolygon A new Google GPolygon object. 904 * @type GPolygon 905 */ 906 Panel.prototype.getPolygon = function() { 907 if( !this.polygon ) { 908 var enc_poly = { 909 'numLevels':18, 910 'zoomFactor':2, 911 'points':this.encodedPolygon, 912 'levels':this.encodedLevels, 913 'opacity':0.5, 914 'weight':2, 915 'color':'#f33' 916 }; 917 this.polygon = GPolygon.fromEncoded({ 918 'polylines':[enc_poly], 919 'color':'#333', 920 'opacity':0.1, 921 'fill':true, 922 'outline':true 923 }); 924 } 925 return this.polygon; 926 } // END: function getPolygon() 927 928 929 /** 930 * Fetch the scale of the current chart panel (1:40000 would return 40000). 931 * 932 * @return int The map scale. 933 * @type int 934 */ 935 Panel.prototype.getScale = function() { 936 return this.scale; 937 } // END: function getScale() 938 939 940 /** 941 * Fetch the panel's KAP file name (base name) used by NOAA for RNC storage. 942 * 943 * @return string The file's base name (eg. 18740_1.KAP) 944 * @type string 945 */ 946 Panel.prototype.getFileName = function() { 947 return this.fileName; 948 } // END: function getFileName() 949 950 951 /** 952 * Test if the user's file name matches the panel's file name. 953 * A user may provide a file name like 18740_1, 18740_1.KAP, or 18740_1.kap . 954 * This method will compare the core components. Case insensitive. 955 * 956 * @param string fn A file name to compare Panel's fileName to. 957 * @return bool true if core components match, false otherwise. 958 * @type bool 959 */ 960 Panel.prototype.matchesFileName = function(fn) { 961 fn = fn.toUpperCase().replace(/^.*?(\/|\\)/,'').replace(/\..*$/,''); 962 return (this.fileName.replace(/\..*$/,'') == fn); 963 } // END: function getFileName() 964 965 966 /** 967 * Fetch the panel number, as assigned by the NGA. 968 * 969 * @return int The NGA panel number. 970 * @type int 971 */ 972 Panel.prototype.getNumber = function() { 973 return this.number; 974 } // END: function getNumber() 975 976 977 /** 978 * Determine if this panel contains the point specified by latlng. 979 * 980 * @param GLatLng latlng A Google lat/lon object. 981 * @return bool true if panel contains point, false otherwise. 982 * @type bool 983 */ 984 Panel.prototype.hasLatLng = function(latlng) { 985 return this.getPolygon().containsLatLng(latlng); 986 } // END: function hasLatLng(latlng) 987 988 989 /** 990 * Fetch the bounds of this panel. The bounds is simply the polygon's bounds. 991 * 992 * @return GLatLngBounds A Google lat/lon bounds object. 993 * @type GLatLngBounds 994 */ 995 Panel.prototype.getBounds = function() { 996 return this.getPolygon().getBounds(); 997 } // END: function getBounds() 998 999 1000 /** 1001 * Determine if this panel overlaps the bounds specified by bounds. 1002 * 1003 * @param GLatLngBounds bounds A Google lat/lon bounds object. 1004 * @return bool true if panel overlaps bounds, false otherwise. 1005 * @type bool 1006 */ 1007 Panel.prototype.overlapsBounds = function(bounds) { 1008 return bounds.intersects( this.getPolygon().getBounds() ); 1009 } // END: function overlapsBounds(bounds) 1010 1011 1012 /** 1013 * Determine if this panel overlaps the bounds specified by bounds. 1014 * 1015 * @param float n The North-most coordinate 1016 * @param float e The East-most coordinate 1017 * @param float s The South-most coordinate 1018 * @param float w The West-most coordinate 1019 * @return bool true if panel overlaps bounds, false otherwise. 1020 * @type bool 1021 * @see Panel#overlapsBounds 1022 */ 1023 Panel.prototype.overlapsBoundsNESW = function(n,e,s,w) { 1024 var bounds = new GBounds(new GLatLng(s,w),new GLatLng(n,e)); 1025 return this.overlapsBounds(bounds); 1026 } // END: function overlapsBoundsNESW(n,e,s,w) 1027 1028 1029 /** 1030 * Initialize and fetch the GTileLayer object that represents this panel. 1031 * 1032 * @return PanelTileLayer A PanelTileLayer that can be added to a Google map. 1033 * @type PanelTileLayer 1034 * @see GTileLayer 1035 */ 1036 Panel.prototype.getTileLayer = function() { 1037 if( typeof(this._tileLayer) == "undefined" ) { 1038 this._tileLayer = new PanelTileLayer(this); 1039 this._tileLayer.setOpacity(this.getAPI().getOpacity()); 1040 } 1041 return this._tileLayer; 1042 } // END: function getTileLayer() 1043 1044 1045 /** 1046 * Initialize and fetch the GTileLayerOverlay object for this panel. 1047 * 1048 * @return GTileLayerOverlay A Google tile layer overlay 1049 * @type GTileLayerOverlay 1050 * @see Panel#getTileLayer 1051 */ 1052 Panel.prototype.getTileLayerOverlay = function() { 1053 return new GTileLayerOverlay(this.getTileLayer()); 1054 } // END: function getTileLayerOverlay() 1055 1056 1057 1058 // =========================================================================== 1059 // Class: PanelTileLayer 1060 // =========================================================================== 1061 1062 1063 1064 /** 1065 * Create a new PanelTileLayer, used by Google Maps as a TileLayerOverlay 1066 * 1067 * @constructor 1068 * @extends GTileLayer 1069 * @param Panel a A parent {@link Panel}, or copyright. 1070 * @param int b Minimum zoom level 1071 * @param int c Maximum zoom level 1072 * @param mixed d Not sure. 1073 * @return PanelTileLayer A new PanelTileLayer object. 1074 * @type PanelTileLayer 1075 * @see GTileLayer 1076 */ 1077 function PanelTileLayer(a,b,c,d) { 1078 // Programmatic access to the API version. 1079 this.version = "1.0.1"; 1080 this.extension = 'gif'; 1081 this.opacity = 1; 1082 1083 this.panel = (a instanceof Panel) ? a : null; 1084 this.minZoom = b || 0; 1085 this.maxZoom = c || 18; 1086 1087 var copy = new GCopyrightCollection('(c) '); 1088 copy.addCopyright( 1089 new GCopyright( 1090 871, 1091 new GLatLngBounds( 1092 new GLatLng(0,-180), 1093 new GLatLng(90,0) 1094 ), 1095 0, 1096 'CORDC' 1097 ) 1098 ); 1099 1100 // Let the parent object handle this initialization 1101 GTileLayer.call(this,copy,b,c,d); 1102 } 1103 PanelTileLayer.prototype = new GTileLayer(null,0,18); 1104 1105 1106 /** 1107 * Get the parent API object. Used internally for the most part. 1108 * 1109 * @return NauticalChartsAPI 1110 * @type NauticalChartsAPI 1111 */ 1112 PanelTileLayer.prototype.getAPI = function() { 1113 return this.panel.getAPI(); 1114 } // END: function getAPI() 1115 1116 1117 /** 1118 * Get the parent Chart object. Used internally for the most part. 1119 * 1120 * @return Chart 1121 * @type Chart 1122 */ 1123 PanelTileLayer.prototype.getChart = function() { 1124 return this.panel.getChart(); 1125 } // END: function getChart() 1126 1127 1128 /** 1129 * Get the parent Panel object. Used internally for the most part. 1130 * 1131 * @return Panel 1132 * @type Panel 1133 */ 1134 PanelTileLayer.prototype.getPanel = function() { 1135 return this.panel; 1136 } // END: function getPanel() 1137 1138 1139 /** 1140 * Get the file name of the panel we intend to display. 1141 * 1142 * @return string A panel file name (eg. 18740_1, or 18740_1.KAP) 1143 * @type string 1144 */ 1145 PanelTileLayer.prototype.getFileName = function() { 1146 return this.getPanel().getFileName(); 1147 } // END: function getFileName() 1148 1149 1150 /** 1151 * Set the parent Panel object. Used internally for the most part. 1152 * 1153 * @param Panel panel The panel we intend to display 1154 * @return bool true if panel is an instance of Panel, false otherwise. 1155 * @type bool 1156 */ 1157 PanelTileLayer.prototype.setPanel = function(panel) { 1158 this.panel = panel; 1159 return (panel instanceof Panel); 1160 } // END: function setPanel(panel) 1161 1162 1163 /** 1164 * Assign the opacity of future created GTileLayer objects. 1165 * 1166 * @param float o Opacity in the range [0,1]. 1167 * @return bool true if o is in range, false otherwise. 1168 * @type bool 1169 */ 1170 PanelTileLayer.prototype.setOpacity = function(o) { 1171 this.opacity = (o>1) ? o/100 : o; 1172 return (o>100||o<0) ? false : true; 1173 } // END: funtion setOpacity(o) 1174 1175 1176 1177 // ----------------------------- 1178 // GTileLayer interface methods: 1179 // ----------------------------- 1180 1181 1182 /** 1183 * Detect if the tile image file type is png. 1184 * 1185 * @return bool 1186 * @type bool 1187 */ 1188 PanelTileLayer.prototype.isPng = function() { 1189 return (this.extension=="png"); 1190 } // END: function isPng() 1191 1192 1193 /** 1194 * Fetch the desired opacity for the tiles. 1195 * 1196 * @return float 1197 * @type float 1198 */ 1199 PanelTileLayer.prototype.getOpacity = function() { 1200 return this.opacity; 1201 } // END: function getOpacity() 1202 1203 1204 /** 1205 * Fetch the target tile url for a GPoint t, and zoom level z. 1206 * 1207 * @param GPoint t The tile coordinate object 1208 * @param int z The zoom level of the Google map 1209 * @return string A url of the tile requested. 1210 * @type string 1211 */ 1212 PanelTileLayer.prototype.getTileUrl = function(t,z) { 1213 var zz = (z<10) ? '0'+z : z; 1214 var fn = this.getFileName().replace(/\..*$/,''); 1215 var chart_id = fn.split('_')[0]; 1216 return 'http://mosaic.ucsd.edu/tiles/charts/'+this.extension+ 1217 '/'+chart_id+'/latest/'+fn+ 1218 '/'+zz+'/z'+z+'y'+t.y+'x'+t.x+'.'+this.extension; 1219 } // END: fucntion getTileUrl(t,z) 1220 1221 1222 /** 1223 * Fetch the min resolution at which we should display this tile layer. 1224 * 1225 * @return int 1226 * @type int 1227 */ 1228 PanelTileLayer.prototype.minResolution = function() { 1229 return this.minZoom; 1230 } // END: function minResolution() 1231 1232 1233 /** 1234 * Fetch the max resolution at which we should display this tile layer. 1235 * 1236 * @return int 1237 * @type int 1238 */ 1239 PanelTileLayer.prototype.maxResolution = function() { 1240 return this.maxZoom; 1241 } // END: function maxResolution() 1242 1243 1244 1245 // =========================================================================== 1246 // Extended Class: GPolygon 1247 // =========================================================================== 1248 1249 1250 /** 1251 * Use ray tracing to determine if latlng fall within the GPolygon object. 1252 * 1253 * @param GLatLng latlng A Google lat/lon object. 1254 * @return bool true if latlng is contained by the polygon, false otherwise. 1255 * @type bool 1256 */ 1257 GPolygon.prototype.containsLatLng = function(latlng) { 1258 var j=0; 1259 var oddNodes = false; 1260 var x = latlng.lng(); 1261 var y = latlng.lat(); 1262 for (var i=0; i < this.getVertexCount(); i++) { 1263 j++; 1264 if (j == this.getVertexCount()) {j = 0;} 1265 if (((this.getVertex(i).lat() < y) && (this.getVertex(j).lat() >= y)) 1266 || ((this.getVertex(j).lat() < y) && (this.getVertex(i).lat() >= y))) { 1267 if ( this.getVertex(i).lng() + (y - this.getVertex(i).lat()) 1268 / (this.getVertex(j).lat()-this.getVertex(i).lat()) 1269 * (this.getVertex(j).lng() - this.getVertex(i).lng())<x ) { 1270 oddNodes = !oddNodes; 1271 } 1272 } 1273 } 1274 return oddNodes; 1275 } // END: function containsLatLng(latlng) 1276 1277 1278 1279 // =========================================================================== 1280 // Ensure XMLHttpRequest exists 1281 // =========================================================================== 1282 1283 1284 1285 /** 1286 * Provide backwards compatibility of XMLHttpRequest in IE lt 7. 1287 * 1288 * {@link http://onwebdevelopment.blogspot.com/2008/02/native-w3c-xmlhttprequest-for-ie6-and.html} 1289 * @date 2009-04-08 1290 * @private 1291 */ 1292 if (!window.XMLHttpRequest) { 1293 var ms_xhr_ver = false; 1294 window.XMLHttpRequest = function() { 1295 if (ms_xhr_ver) return new ActiveXObject(ms_xhr_ver); 1296 var xhr = false; 1297 var versions = [ 1298 "Msxml2.XMLHTTP.12.0", 1299 "Msxml2.XMLHTTP.11.0", 1300 "Msxml2.XMLHTTP.10.0", 1301 "Msxml2.XMLHTTP.9.0", 1302 "Msxml2.XMLHTTP.8.0", 1303 "Msxml2.XMLHTTP.7.0", 1304 "Msxml2.XMLHTTP.6.0", 1305 "Msxml2.XMLHTTP.5.0", 1306 "Msxml2.XMLHTTP.4.0", 1307 "MSXML2.XMLHTTP.3.0", 1308 "MSXML2.XMLHTTP", 1309 "Microsoft.XMLHTTP" 1310 ]; 1311 var n = versions.length; 1312 for (var i=0; i<n; i++) { 1313 try { 1314 if( xhr = new ActiveXObject(versions[i]) ) { 1315 ms_xhr_ver = versions[i]; 1316 break; 1317 } 1318 } catch (e) { /* try next */ } 1319 } 1320 return xhr; 1321 }; 1322 } 1323 1324 1325 // =========================================================================== 1326 // EOF -- NauticalChartsAPI.js 1327 // =========================================================================== 1328