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