// Reusable great circle code for "graze" maps, with 2 independant lines
// Version 2 provides XTD (Cross Track Distance) from the center path, when map is double clicked. March 2006
// Version 3 allows offset lines to cross "dateline" without VML screwing it up! Dec 2006

// Copyright - Geoff Hitchcox, Christchurch, New Zealand, 

     var Offset_Line = [] // Array to hold all the new Offset Polylines, GRH Dec 2006
     var offset_count = 0 

     function init_arrays() // Instantiate OFFSET arrays
         {
         for (var i=0; i < pathcount; i++)  
	   {
	   offpts1.push(new GPoint(pathctr[i].x,pathctr[i].y));
	   offpts2.push(new GPoint(pathctr[i].x,pathctr[i].y));
           }
	 make_tracks();
         }

     function newoffsetA(form) // User has entered a new offset value for A line
         {
	 var stuff = form.offset.value;
         offset_A = stuff * 1.00;  // to ensure float value
         if (isNaN(offset_A)) 
           {
           msg =  "  Not a valid number!!!  \n\n"; 
           msg += "Click OK to continue.\n\n"; 
           msg += "Offset will then be set to 1,000 (Km)\n"; 
           alert(msg); 
           offset_A = 1000.0;
           } 
	 if (offset_A > 1000.0) offset_A = 1000.0;
         if (offset_A < -1000.0) offset_A = -1000.0;
         var infoStr = '(' + "Offset line is " + offset_A + " kilometres from path center" + ')';
         document.getElementById("message").innerHTML = infoStr;
         for (var i=0; i < offset_count; i++)
            {
            map.removeOverlay(Offset_Line[i]);
            }
	 make_tracks();
         }

     function newoffsetB(form) // User has entered a new offset value for B line
         {
	 var stuff = form.offset.value;
         offset_B = stuff * 1.00;  // to ensure float value
         if (isNaN(offset_B)) 
           {
           msg =  "  Not a valid number!!!  \n\n"; 
           msg += "Click OK to continue.\n\n"; 
           msg += "Offset will then be set to 1,000 (Km)\n"; 
           alert(msg); 
           offset_B = 1000.0;
           } 
	 if (offset_B > 1000.0) offset_B = 1000.0;
         if (offset_B < -1000.0) offset_B = -1000.0;
         var infoStr = '(' + "Offset line is " + offset_B + " kilometres from path center" + ')';
         document.getElementById("message").innerHTML = infoStr;
         for (var i=0; i < offset_count; i++)
            {
            map.removeOverlay(Offset_Line[i]);
            }
	 make_tracks();
         }
	
     function make_tracks()
         {
         var poly_array = [] // to hold temp plotting coords
         var prev_lon 
         poly_array.push(new GPoint(pathctr[0].x,pathctr[0].y)); // instantiate
         poly_array.length = 0; // Reset pointer

         var temp_bearing;

         for (var i=0; i < pathcount; i++)
           {
           lonrad1 = deg2rad(pathctr[i].x);
           latrad1 = deg2rad(pathctr[i].y);
           lonrad2 = deg2rad(pathctr[i+1].x);
           latrad2 = deg2rad(pathctr[i+1].y);
           calc_bearing();  // calculate great circle bearing
           temp_bearing = bearing;
           if (offset_A > 0)
              {
              bearing = mod360(bearing - 90.0);
              offset = offset_A;
              }
           else
              {
              bearing = mod360(bearing + 90.0);
              offset = offset_A * -1.0;
              }
           calc_point();  // calculate point on great circle
           offpts1[i].x = rad2deg(lonrad2);
           offpts1[i].y = rad2deg(latrad2);
           bearing = temp_bearing;
           if (offset_B > 0)
              {
              bearing = mod360(bearing - 90.0);
              offset = offset_B;
              }
           else
              {
              bearing = mod360(bearing + 90.0);
              offset = offset_B * -1.0;
              }
           calc_point();  // calculate point on great circle
           offpts2[i].x = rad2deg(lonrad2);
           offpts2[i].y = rad2deg(latrad2);
           }

        for (i=0; i < pathcount; i++)
           {
           if (offpts1[i].x >  179.9) offpts1[i].x =  179.9;
           if (offpts1[i].x < -179.9) offpts1[i].x = -179.9;
           if (offpts2[i].x >  179.9) offpts2[i].x =  179.9;
           if (offpts2[i].x < -179.9) offpts2[i].x = -179.9;
           }

         prev_lon = 0;
         offset_count = 0;

         for (i=0; i < pathcount; i++)
           {
           if (offpts1[i].x > 90.0 && prev_lon < -90.0 || offpts1[i].x < -90.0 && prev_lon > 90.0)
              {
	      Offset_Line[offset_count] = new GPolyline(poly_array,"#000000", 3, 0.5);
	      map.addOverlay(Offset_Line[offset_count]);
              poly_array.length = 0; // Reset pointer
              offset_count++; 
              }
           poly_array.push(new GPoint(offpts1[i].x,offpts1[i].y));
           prev_lon = offpts1[i].x;
           } 
         Offset_Line[offset_count] = new GPolyline(poly_array,"#000000", 3, 0.5);
         map.addOverlay(Offset_Line[offset_count]);
         poly_array.length = 0; // Reset pointer
         offset_count++;

         prev_lon = 0; 
         for (i=0; i < pathcount; i++)
           {
           if (offpts2[i].x > 90.0 && prev_lon < -90.0 || offpts2[i].x < -90.0 && prev_lon > 90.0)
              {
	      Offset_Line[offset_count] = new GPolyline(poly_array,"#000000", 3, 0.5);
	      map.addOverlay(Offset_Line[offset_count]);
              poly_array.length = 0; // Reset pointer
              offset_count++; 
              }
           poly_array.push(new GPoint(offpts2[i].x,offpts2[i].y));
           prev_lon = offpts2[i].x;
           } 
         Offset_Line[offset_count] = new GPolyline(poly_array,"#000000", 3, 0.5);
         map.addOverlay(Offset_Line[offset_count]);
         offset_count++;
         } 

     function find_xtd()   // Find Cross Track Distance from path center (new function) GRH March 2006
         {
         var dist_AD=10;
         var index_AD=0;        
         var dist_BD=10;
         var index_BD=0;        
         for (var i=0; i < pathcount; i++)
           {
           lonrad1 = deg2rad(pathctr[i].x);
           latrad1 = deg2rad(pathctr[i].y);
           calc_dist();
           if (Math.abs(bearing) < Math.abs(dist_AD))
              {
              dist_AD = bearing;
              index_AD = i;
              }
           }
         lonrad1 = deg2rad(pathctr[index_AD].x);
         latrad1 = deg2rad(pathctr[index_AD].y);
         calc_bearing();  // calculate great circle bearing
         var crs_AD = deg2rad(bearing);  
         for (i=0; i < pathcount; i++)
           {
           lonrad1 = deg2rad(pathctr[i].x);
           latrad1 = deg2rad(pathctr[i].y);
           calc_dist();
           if (Math.abs(bearing) > Math.abs(dist_AD))
              {
              if (Math.abs(bearing) < Math.abs(dist_BD))
                { 
                dist_BD = bearing;
                index_BD = i;
                }
              }
           }
         lonrad1 = deg2rad(pathctr[index_AD].x);
         latrad1 = deg2rad(pathctr[index_AD].y);
         lonrad2 = deg2rad(pathctr[index_BD].x);
         latrad2 = deg2rad(pathctr[index_BD].y);
         calc_bearing();  // calculate great circle bearing
         var crs_AB = deg2rad(bearing);  
         bearing = Math.asin(Math.sin(dist_AD) * Math.sin(crs_AD - crs_AB));
         if(bearing < 0.0) bearing = bearing * -1.0;
         bearing = (bearing * 180 * 60 * 1852) / pi;
         bearing = Math.floor(bearing + 0.5) / 1000.0; // to nearest metre  
         }  

     function calc_dist()   // Calculate great circle distance (new function), GRH March 2006
         {     
         var w = lonrad2 - lonrad1; 
         var v = latrad1 - latrad2;
         bearing = 2 * Math.asin(Math.sqrt((Math.sin(v / 2) * Math.sin(v / 2)) + (Math.cos(latrad1) * Math.cos(latrad2) * Math.sin(w / 2) * Math.sin(w / 2))));
         }

     function calc_bearing()   // calculate great circle bearing
         {     
         var w = lonrad2 - lonrad1; 
         var v = latrad1 - latrad2;
         var s = 2 * Math.asin(Math.sqrt((Math.sin(v / 2) * Math.sin(v / 2)) + (Math.cos(latrad1) * Math.cos(latrad2) * Math.sin(w / 2) * Math.sin(w / 2))));
         if (Math.cos(latrad1) < 0.000000000000001)
           {                               // initial point is pole
           if (latrad1 > 0) {var  x = pi;} // start from north pole
           else {var  x = 0;}              // start from south pole
           }
         else 
	   {                               // initial point isn't on pole
           if (Math.sin(lonrad1 - lonrad2) < 0)
             {var x = Math.acos((Math.sin(latrad2) - Math.sin(latrad1) * Math.cos(s)) / (Math.sin(s) * Math.cos(latrad1)));}
           else 
             {var x = 2 * pi - Math.acos((Math.sin(latrad2) - Math.sin(latrad1) * Math.cos(s)) / (Math.sin(s) * Math.cos(latrad1)));}
           }      
         bearing = rad2deg(x);
         }

     function calc_point()                                 //calculate point on great circle
         {   
         var x12 = deg2rad(bearing);                       // convert bearing to radians
         var s = offset * 1000.0;                          // offset in meters
         s = s * pi /(180 * 60 * 1852);                    // convert to radians 
         latrad2 = Math.asin(Math.sin(latrad1) * Math.cos(s) + Math.cos(latrad1) * Math.sin(s) * Math.cos(x12));
         var w = Math.atan2(Math.sin(x12) * Math.sin(s) * Math.cos(latrad1), Math.cos(s) - Math.sin(latrad1) * Math.sin(latrad2));
         lonrad2 = modlon(lonrad1 + w);
         latrad2 = modlat(latrad2);
         }
   
     function handleErrors(errorMessage, url, line) 
         { 
         msg = "There was an error on this page.\n\n"; 
         msg += "An internal programming error may keep\n"; 
         msg += "this page from displaying properly.\n"; 
         msg += "Click OK to continue.\n\n"; 
         msg += "Error message: " + errorMessage + "\n"; 
         msg += "URL: " + url + "\n"; 
         msg += "Line #: " + line; 
         alert(msg); 
         return true 
         } 

     function deg2rad(x)  //convert decimal degrees to radians
         {                  
         x = x * pi / 180;
         return x;
         }

     function rad2deg(x)  //convert radians to decimal degrees
         {                                                   
         x = x * 180 / pi;
         return x;
         }

     function mod(x,y)    // Modulus of x to y
         {           
         return x-y*Math.floor(x/y);
         }

     function modlon(x)   //ensure longitude is +/-180
         {                                                     
         return mod(x+pi,2*pi)-pi;
         }

     function modcrs(x)   //ensure 0-360 (radians)
         {                                                   
         return mod(x,2*pi);
         }

     function mod360(x)   //ensure 0-360 (degrees)
         {                                                   
         return mod(x,360.0);
         }
   
     function modlat(x)   //ensure latitude is +/-90 
         {                                                   
         return mod(x+pi/2,2*pi)-pi/2;
         }

     function wipe(obj)
         {
         obj.value="";    // clear the form field
         obj.focus();     // reset the focus
         }

