<?php

/*
   LUNAR PERIGEE AND APOGEE CALCULATOR
   FOR THE YEARS FROM 1000 TO 9700.

   Based on the NASA/JPL Horizons API v1.1

   The NASA/JPL Horizons went on-line in July
   of 2021.

   This program is an experimental application
   of the API.

   AUTHOR   : Jay Tanner
   LANGUAGE : PHP v8.2.12
   LICENSE  : Public Domain
*/

// Initialize output buffer.
   
ob_start();

// Note currently used PHP version.
   
$PHPVersionStr =  'PHP v8.2.12';

// Set ephemeris year span.
   
$MinYear 1000;
   
$MaxYear 9998;
   
$EphSpan $MaxYear $MinYear 1;

// Define the program cookie name and set it to expire in 30 days.
// This cookie can be shared by other programs in the same common
// home folder together and using the same input interface.
   
$CookieName 'Perigees_and_Apogees_Calculator';
   
$SetToExpireIn30Days time() + 30*86400;

// Define script path and filename.
   
$_AUTHOR_          'Jay Tanner';
   
$_PROGRAM_VERSION_ ''$at "&#97;&#116;"$UTC "&#85;&#84;&#67;";
   
$_SCRIPT_PATH_     Filter_Input(INPUT_SERVER'SCRIPT_FILENAME');
   
$_RUN_             Filter_Input(INPUT_POST'SCRIPT_NAME');

// Define internal document page title, HTML
// page heading text and revision date.
   
$_INTERNAL_TITLE_  "Lunar Perigees and Apogees Calculator";
   
$_INTERFACE_TITLE_ "<b style='font-size:15pt; font-weight:normal;'>Lunar Perigees and Apogees Calculator</b><br><br><span style='font-size:9pt;'>For the $EphSpan-Year Span From&nbsp;&nbsp;$MinYear AD &nbsp;to&nbsp; $MaxYear AD<br>
   <b style='font-weight:normal;'>Built Around the NASA/JPL Horizons API<br>PHP Program by Jay Tanner</b></span>"
;
   
$_THIS_SCRIPT_NAME_ BaseName($_SERVER["SCRIPT_FILENAME"]);
   
$at  "&#97;&#116;";   $UTC "&#85;&#84;&#67;";
   
$_REVISION_DATE_ 'Revised: '.gmdate("l - F d, Y  $at H:i:s $UTC"FileMTime($_THIS_SCRIPT_NAME_));

// Define Java Script message to display while computing is in progress.
   
$_COMPUTING_ "TextArea1.innerHTML='     COMPUTING - THIS MAY TAKE SEVERAL SECONDS';";

// Define main TextArea text, background
// colors and HTML table row span.
   
$TxColor 'black';
   
$BgColor 'white';

// Initialize time scale strings.
   
$UT1C $TimeScale $TScaleStr '';

// Do this only if [COMPUTE] button was clicked.
   
$w Filter_Input(INPUT_POST'ComputeButton');
   if (!IsSet(
$w))

// ----------------------------------------------------
// If [COMPUTE] button was clicked and an active cookie
// exists, then restore the previous interface settings
// from it.
     
{
      
$w Filter_Input(INPUT_COOKIE$CookieName);
   if (IsSet(
$w))
      {
       
$CookieDataString Filter_Input(INPUT_COOKIE$CookieName);
        list
       (
        
$Year$TZhhmm$kmmi$LocLabel
       
) = Preg_Split("[\|]"$CookieDataString);
      }

   else

// Set the initial default interface startup values
// to current date UTC and Time Zone offset +00:00
// and distance units to kilometers (km).
 
{
  
$Year     GMDate('Y');
  
$TZhhmm   '+00:00';
  
$kmmi     'km';
  
$LocLabel '-';

// Store interface settings in cookie.
   
$CookieDataString "$Year|$TZhhmm|$kmmi|$LocLabel";
   
setcookie ($CookieName$CookieDataString$SetToExpireIn30Days);
  }
      } 
// End of  if (!isset(_POST['ComputeButton']))







// =======================================
// Read values of all interface arguments.
// Empty values are set to defaults.

   
$w Filter_Input(INPUT_POST'ComputeButton');

   if (IsSet(
$w))
{
// Read given year value.
   
$Year trim(Filter_Input(INPUT_POST'Year'));
   if (
$Year == '') {$Year GMDate('Y');}
   
$xYear $Year;
   
$Year = @bcAdd($Year0);

// Read local time zone offset.
   
$TZhhmm trim(Filter_Input(INPUT_POST'TZhhmm'));
   if (
$TZhhmm == '') {$TZhhmm '+00:00';}
   
$w Num_to_HMS($TZhhmm);
   
$w HMS_to_Hours($w);
   
$w Hours_to_HMS($w0'+'':');
   
$TZhhmm substr($w06);

// Determine distance units to apply.
   
$kmmi trim(StrToLower(Filter_Input(INPUT_POST'DistUnits')));
   
$kmmi = (substr($kmmi,0,1) <> 'm')? 'km':'mi';

// Read optional location label string. This location should
// match the given time zone. For example, TZ = -05:00
// would not apply to London, UK (TZ = 00:00) and would
// be wrong and look abnormal.  It is simply an optional
// reference description to apply to the returned table
// and can consist of any printable text.
   
$LocLabel trim(Filter_Input(INPUT_POST'LocLabel'));
   if (
$LocLabel == '') {$LocLabel '-';}

// -----------------------------------
// Store interface values in a cookie.
   
$CookieDataString "$Year|$TZhhmm|$kmmi|$LocLabel";
   
SetCookie ($CookieName$CookieDataString$SetToExpireIn30Days);
}




// =========================================
// Check for errors. FALSE = Error detected.
// Change this code to detect errors.
   
$ErrFlag TRUE;

// Check for valid year argument.
   
if (!Is_Numeric($Year) or $Year $MinYear or $Year $MaxYear)
     {
      
$Year $xYear;
      
$ErrFlag FALSE;
      
$ErrMssg =
"Invalid Year: '$xYear'

The year must be a positive integer in the range
from 
$MinYear to $MaxYear.
"
;
     }



// Check for invalid time zone argument.
   
if (abs(HMS_to_Hours($TZhhmm)) > 14)
     {
      
$ErrFlag FALSE;
      
$ErrMssg =
"Invalid Time Zone Offset: '$TZhhmm'

The time zone offset from UT must be in the range
from  00:00  to  &plusmn; 14:00
"
;
     }


// =====================================
// Check if any errors (FALSE) detected.
// If so, display error in RED/WHITE.

   
if ($ErrFlag === FALSE)
  {
   
$TxColor 'white';
   
$BgColor '#CC0000';
   
$TextArea2Text '';

  
$TextArea1Text =
"= ERROR =========

$ErrMssg";
   }

else

// BEGIN MAIN COMPUTATIONS HERE IF NO ERRORS
// DETECTED AT THIS POINT.

// -----------------------------------------
// DEFINE LUNAR EPHEMERIS PARAMETER SETTINGS.
//
// UT is used for internal computations and
// then the output converted for the given
// time zone offset from UT.
  
{
// Set solar system body ID (301 = Moon = Luna)
   
$body_id 301;

// Set start/stop times for a full year
// lunar perigee and apogee ephemeris.
   
$PrevYear   $Year 1;
   
$NextYear   $Year 1;
   
$TScale     'UT';
   
$start_time "$PrevYear-Dec-31"// Start of last day of previous year.
   
$stop_time  "$NextYear-Jan-02"// End of first day of following year.
   
$step_size  '3h';

// Set distance units symbol ('mi' or 'km').
   
$DistUnits  $kmmi;

// Construct calling URL for JPL Horizons API.   Internally, the
// distance units are kilometers. The (DistUnits) variable above
// can override kilometers and use miles instead.
   
$FROM_NASA_JPL_HORIZONS_API =
   
"https://ssd.jpl.nasa.gov/api/horizons.api" .
   
"?format=text" .
   
"&COMMAND='$body_id'" .
   
"&OBJ_DATA='YES'" .
   
"&MAKE_EPHEM='YES'" .
   
"&EPHEM_TYPE='OBSERVER'" .
   
"&CENTER='500@399'" .
   
"&START_TIME='$start_time'" .
   
"&STOP_TIME='$stop_time'" .
   
"&STEP_SIZE='$step_size'" .
   
"&QUANTITIES='20'" .
   
"&CAL_FORMAT='BOTH'" .
   
"&SUPPRESS_RANGE_RATE='NO'" .
   
"&RANGE_UNITS='KM'";

// Determine time scale (UT or LT)
// UT = Universal Time
// LT = Local Time for given TZ
   
$TZHours HMS_to_Hours($TZhhmm);
   
$TimeScale = ($TZHours <> 0)? 'LT':'UT';

// Adjust time scale label string.
// UT1 before 1962
// UTC from 1962
   
$UT1C = ($Year 1962)? '1':'C';
   
$TScaleStr "$TimeScale$UT1C";
   
$TScaleStr =($TScaleStr == 'LT1' or $TScaleStr == 'LTC')? 'LT ':"UT$UT1C";

// Get the raw lunar ephemeris table from the JPL Horizons API.
   
$RawMoonTable File_Get_Contents($FROM_NASA_JPL_HORIZONS_API);
// exit("<pre>$RawMoonTable</pre>");

// Extract ONLY the table between the ephemeris
// endpoint markers ($$SOE and $$EOE).
   
$MoonTable Extract_Lunar_Ephemeris($RawMoonTable$TZhhmm);

// Construct PA work tables separated by an asterisk (*).
   
$PAWorkTables Construct_Lunar_Extrema_Tables($MoonTable);

// Construct table of all PA events for the year.
   
$OutTable Compute_All_PA_Events ($PAWorkTables$kmmi);

// Run output table through final output filter.
   
$OutTable Final_Filter($OutTable);

// Define separator text lines.
   
$TxLine48 Str_Repeat('='48);
   
$TxLine67 Str_Repeat('='67);

// Determine if Julian or Gregorian calendar applies or both.
// Julian    = Dates before 1582-Oct-15-Fri
// Gregorian = Dates since  1582-Oct-15-Fri
// Both      = Only the month 1582-Oct

   
if ($Year 1583)
      {
$JGMessage "\n Dates refer to the old Julian calendar.";}

   if (
$Year == 1582)
      {
$JGMessage "\n Julian calendar used for dates < 1582-Oct-05-Thu\n          and Gregorian calendar used for all later dates.";}

   if (
$Year 1582)
      {
$JGMessage "\n Dates refer to the modern Gregorian calendar.";}


// Define output ephemeris header text.
   
$HeaderText " ALL LUNAR PERIGEES AND APOGEES FOR THE YEAR $Year
==================================================\n Time Zone : UT
$UT1C$TZhhmm          \n Location  : $LocLabel\n          $JGMessage";

// Construct tabulated computations for display by client web browser.
   
$TextArea1Text =
  
"$HeaderText

=================================================
 EVENT    Calendar Date  and Time 
$TScaleStr   Dist. $DistUnits
=======  ============================  ==========
$OutTable
=======  ===============  ===========  ==========
 EVENT    Calendar Date  and Time 
$TScaleStr   Dist. $DistUnits
=================================================
"
;

  }


// -------------------------------------------------------------
// Determine the number of text rows to use in output TextArea1.
   
$Text1Rows Substr_Count($TextArea1Text"\n");
   
$Text1Cols Max(Array_Map('StrLen'PReg_Split("[\n]"trim($TextArea1Text))));
                if (
$Text1Cols 80) {$Text1Cols 50;}




// Generate client webpage to display the computations.
   
print <<< _HTML

<!DOCTYPE HTML>

<head>

<title>Lunar Perigees and Apogees Calculator</title>
<meta name="viewport" content="width=device-width, initial-scale=0.8">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="description" content="Lunar Perigee and Apogee Calculator">
<meta name="keywords" content="apogee calculator,perigee calculator,lunar perigee,lunar apogee,apogee,perigee,PHPScienceLabs.com">
<meta name="author" content="Jay Tanner - https://www.PHPScienceLabs.com">
<meta http-equiv="pragma"  content="no-cache">
<meta http-equiv="expires" content="-1">
<meta name="robots"    content="index,follow">
<meta name="googlebot" content="index,follow">

<style type='text/css'>
 BODY
{color:silver; background-color:black; font-family:Verdana; font-size:100%;}

 TABLE
{font-size:12px; border: 1px solid black;}

 TD
{color:black; background-color:white; line-height:175%; font-size:14px;
padding:6px; text-align:center;}

 TEXTAREA
{
 color:black; background:white; font-family:monospace; font-size:11pt;
 font-weight:bold; border-radius: 8px; border:1px solid black; padding:4px;
 white-space:pre;
}

 INPUT[type='text']::-ms-clear {width:0; height:0;}

 INPUT[type='text']
{
 font-size:13pt; font-family:monospace; color:black; background:white;
 font-weight:bold; text-align:center; box-shadow:2px 2px 3px #666666;
 border:1px solid black; border-radius:4px;
}

 INPUT[type='text']:focus
{
 font-size:13pt; font-family:monospace; background:white; box-shadow:2px 2px 3px #666666;
 border:2px solid blue; text-align:center; font-weight:bold; border-radius:4px;
}




 INPUT[type='submit']
{
 background:black; color:cyan; font-family:Verdana; font-size:10pt;
 font-weight:bold; border-radius:4px; border:4px solid #777777;
 padding:3pt;
}
 INPUT[type='submit']:hover
{
 background:black; color:white; font-family:Verdana; font-size:10pt;
 font-weight:bold; border-radius:4px; border:4px solid red;
 padding:3pt;
}




 HR {background:black; height:2px; border:0px;}

 A:link
{
 font-size:100%; background:transparent; color:cyan; font-family:Verdana;
 font-weight:bold; text-decoration:none; line-height:175%; padding:3px;
 border:1px solid transparent;
}

 A:visited {font-size:100%; background:transparent; color:silver;}

 A:hover
{
 font-size:100%; background:yellow; color:black; border:1px solid black;
 box-shadow:1px 1px 3px #222222;
}

 A:active  {font-size:100%; background:yellow; color:black;}

::selection      {background-color:yellow; color:black;}
::-moz-selection {background-color:yellow; color:black;}
</style>

</head>

<body>

<form name='form1' method='post' action="
$_RUN_">

<table align='top' border='0' cellspacing='1' cellpadding='3'>

<tr><td colspan='3' style='color:white; background:#000044; border:2px solid white; border-radius:8px 8px 0px 0px;'><b>
$_INTERFACE_TITLE_</b></td></tr>

<tr>
<td style='background:#FFEAEA;'>Year<br><input name='Year'  type='text' value="
$Year"  size='5' maxlength='4' title=' Span: $MinYear  to  $MaxYear '></td>
<td style='background:#EAFFEA;'>Time Zone Offset<span style='font-size:140%; font-family:monospace;'><br>UT
$UT1C<sub></sub></span> <input name='TZhhmm'  type='text' value="$TZhhmm"  size='7' maxlength='6' title=' &minus; West   or   East &plus; ' style='border-width:1px 1px 1px 0px; text-align:left;'></td>
<td style='background:#E0FFFF;'>Distance Units (Km / Mi)<br><input name='DistUnits'  type='text' value="
$kmmi"  size='3' maxlength='2' title=' mi = Miles   or   km = Kilometers '></td>
</tr>

<tr><td colspan='3' style='background:LightYellow;'>
<br>
<input name='LocLabel' type='text' value="
$LocLabel" size='46' maxlength='45' title=' Optional Location Name should properly match the given Time Zone Offset '><br>Optional Location Name
</td></tr>

<tr>
<td colspan='3' style='background:black;'>
<input name='ComputeButton' type='submit' value=' C O M P U T E ' onClick="
$_COMPUTING_">
</td>
</tr>

</td></tr>





<tr>
<td colspan="3" style="font-size:10pt; color:black;
                       background:black; text-align:center;">
<br>
<b><a href='View-Source-Code.php' target='_blank'
style='font-family:Verdana; color:black; background:yellow;
       text-decoration:none; border:1px solid black; padding:4px;'>
       &nbsp;View Source Code&nbsp;</a></b>
</td></tr>




<tr><td colspan='3' style='background:black; text-align:center;'>

<!-- TABLE WITHIN A TABLE --->
<table width='100' align='bottom'>
<tr>
<td colspan='3' style='color:silver; font-size:10pt; background:black;'>
<span style='color:silver; font-size:9pt; background:black;'>Double-Click Within Text Area to Select ALL Text</span><br>
<textarea name='TextArea1' style='color:
$TxColor; background:$BgColor; padding:6px; border:2px solid white;' cols="$Text1Cols" rows="$Text1Rows" ReadOnly OnDblClick='this.select();' OnMouseUp='return true;'>
$TextArea1Text
</textarea></td>
</tr>



<tr><td style='color:gray; background:black;'>
<br>Program by 
$_AUTHOR_<br>$_REVISION_DATE_ - $PHPVersionStr
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
</td></tr>

</table>
</td></tr>
</form>

</body>




_HTML;













/*
   ###########################################################################
   BELOW ARE UTILITY FUNCTIONS FOR USE WITH THIS PROGRAM.  SOME FUNCTIONS ARE
   CUSTOMIZED JUST FOR THIS APPLICATION AND MAY NOT TRANSPORT TO OTHER WORKS
   OUT OF CONTEXT.
   ###########################################################################






   ============================================================================
   This function converts a time elements string ('h m s') to decimal hours.

   NO DEPENDENCIES
   ============================================================================
*/

   
function HMS_to_Hours ($hhmmss)
{
   
$hms StrToLower(trim($hhmmss));

   
$hms Str_Replace('h'' '$hms);
   
$hms Str_Replace('m'' '$hms);
   
$hms Str_Replace('s'' '$hms);
   
$hms Str_Replace(':'' '$hms);

   
$hms PReg_Replace("/\s+/"" "trim($hms));
   
$whms PReg_Split("[ ]"$hms);
   
$whmscount count($whms);
   
$hh = ($whmscount >= 1)? bcAdd($whms[0],"0"20) : "0";
   
$mm = ($whmscount >= 2)? bcAdd($whms[1],"0"20) : "0";
   
$ss = ($whmscount >= 3)? bcAdd($whms[2],"0"20) : "0";

   
$NumSign = (substr($hhmmss,0,1) == '-')? '-' '';
   
$hh Str_Replace('-'''$hh);
   
$hh Str_Replace('+'''$hh);

   if (
substr($hhmmss,0,1) == '+') {$NumSign '+';}

   
$w2 bcAdd(bcAdd(bcMul($hh,'3600',20),bcMul($mm,'60',20),20),$ss,20);

   if (
bcComp($w2'0'20) == 0) {$NumSign '';}

   
$w bcAdd(bcDiv($w2,360020), '0.00000000000000005'16);

   return 
$NumSign.Rtrim(Rtrim($w'0'), '.');

// end of  HMS_to_Hours (hhmmss)

/* ######################################################################## */


/*
   ===========================================================================
   This function returns the equivalent H:M:S time string with
   several formatting options.

   hours = � Decimal hours value
   ssDecimals = Number of decimals in seconds part

   posSign = Symbol to use for positive values ('' or '+')
   ''  = Empty = Return numbers only, no symbols.
   '+' = Attach '+' sign to positive values.
   Has no effect on negative values.

   SymbolsMode = If or not to attach time symbols (h m s) or (:)
   'h' = '01h 02m 03s' (Default)
   ':' = '01:02:03'
   ''  = '01 02 03'

   NO DEPENDENCIES
   ===========================================================================
*/

   
function Hours_to_HMS ($hours$ssDec=0$posSignSymb=''$SymbMode='h')
{
   
$_h_ $_m_ $_s_ '';

   if (
trim($posSignSymb) == '')  {$posSignSymb FALSE;}

   
$sign  = ($hours 0)? '-' '';
   
$hours abs($hours);

   if ((
$posSignSymb === TRUE or $posSignSymb == '+') and $sign == '')
      {
$sign '+';}

   
$hh floor($hours);
   
$minutes 60*($hours $hh);    $mm floor($minutes);
   
$seconds 60*($minutes $mm);  $ss SPrintF("%1.3f",  $seconds);

   
$hh SPrintF("%02d",  $hh);
   
$mm SPrintF("%02d",  $mm);
   
$ss SPrintF("%1.$ssDec"f"$ss);

   if (
$ss == 60) {$ss 0$mm++;}
   if (
$mm == 60) {$mm 0$hh++;}

   
$hh SPrintF("%02d",  $hh);
   
$mm SPrintF("%02d",  $mm);

   
$ss SPrintF("%1.$ssDec"f"$ss);
   if (
$ss 10) {$ss "0$ss";}

   if (
$SymbMode == or $SymbMode == '')
  {
$_h_  =  $_m_  =  $_s_  ' ';}

   if (
$SymbMode == or $SymbMode == ':')
  {
$_h_  =  ':';  $_m_  =  ':';  $_s_  '';}

   if (
$SymbMode == or StrToLower($SymbMode) == 'h')
  {
$_h_  =  'h ';  $_m_  =  'm ';  $_s_  =  's';}

   
$w "$sign$hh$_h_$mm$_m_$ss$_s_";

   return 
$w;

// end of  Hours_to_HMS(...)


/* ######################################################################## */






/*
   ---------------------------------------------------------------------------
   This function performs LaGrange interpolation within a 2-column
   data table.  Data intervals can be linear or non-linear forward.

   Given (x) value within the given range, this function interpolates
   the corresponding (y) value.

   Revised, stand-alone version.

   Table XY column format example:

   $XYTable =
   '
   481  10.012
   482  10.503
   483  11.083
   ';

   ---------------------------------------------------------------------------
*/

   
function Lagrange_Interp ($XYDataTable$xArg)
{

   
$XDataStr $YDataStr '';

   
$XY PReg_Split("[ ]"PReg_Replace("/\s+/"' 'trim($XYDataTable)));

   
$TotalDatacount count($XY);

   
$n $TotalDatacount 2;

   if (
$TotalDatacount )
  {return 
"ERROR: There must be at least two XY data pairs.";}

   if (
$n != floor($n 0.5))
  {return 
"ERROR: XY Data count Mismatch. Odd data element.";}

   
$n $TotalDatacount 2;

   for(
$i=0;   $i $TotalDatacount;   $i += 2)
{
   
$XDataStr .= $XY[$i]   . ' ';
   
$YDataStr .= $XY[$i+1] . ' ';
}
   
$X PReg_Split("[ ]"trim($XDataStr));
   
$Y PReg_Split("[ ]"trim($YDataStr));

   
$x trim($xArg);  if ($x == '') {$x 0.0;}

   
$y 0.0;

   for (
$i=0;   $i $n;   $i++)
{
   
$Li 1.0;

   for (
$j=0;   $j $n;   $j++)
{
   if (
$j <> $i// Skip this cycle when j == i
{
   
$Li = ($Li*($x $X[$j])) / ($X[$i] - $X[$j]);
}
// next j

   
$y += ($Y[$i] * $Li);

// next i

   
return $y;

// end of  Lagrange_Interp (XYDataTable, xArg)


/* ######################################################################## */


/*
   ===========================================================================
   This function translates a purely numeric time string into standard form.
   Applies ONLY to purely numerical time strings.

   time format used = 00 to 24 hours

   ----------------------------------
   EXAMPLES
   time String          Translates to
   -----------          -------------
   1                    01:00:00
   12                   12:00:00
   123                  01:23:00
   1234                 12:34:00
   12345                01:23:45
   123456               12:34:56

   This function does NOT check for errors.

   NO DEPENDENCIES
   ===========================================================================
*/

   
function Num_to_HMS ($NumHMSVal)
{
   
$w trim($NumHMSVal);

   if (
$w == '') {return '00:00:00';}

   if (!
Is_Numeric($w)) {return $NumHMSVal;}

   
$FirstChar substr($w,0,1);
   
$NumSign   = (IntVal($w) < 0)? '-' '+';

   
$w Str_Replace($NumSign''$w);

   if (
StrLen($w) == 1) {$w "0$w";}
   if (
StrLen($w) == 2) {$w $w."0000";}
   if (
StrLen($w) == 3) {$w "0$w";}
   if (
StrLen($w) == 4) {$w $w."00";}
   if (
StrLen($w) == 5) {$w "0$w";}

//   if ($FirstChar <> '+' and $NumSign == '+') {$NumSign = '';}
   
$hh SPrintF("%02d"IntVal(substr($w,0,2)));
   return 
$NumSign.$hh.':'.substr($w,2,2).':'.substr($w,4,2);

// end of  Num_to_HMS (...)

/* ######################################################################## */
































/*
  ============================================================================
  This function returns the time of an extremum (minimum or maximum), if
  any, within a given time vs event table.  For example, this function
  can be used to find the times of perihelion, aphelion, perigee, apogee
  or any general periapsis or apoapsis times.  It is based on a 5-point
  data table and the extremum is computed from a polynomial derived from
  the given data.

  In this program, this function is used to compute the JD (Julian Date)
  of a perigee or apogee event.

  ARGUMENT:
  DataTableStr = 5-point paired numerical data table.

  ERRORS:
  No error checking is done and the function assumes that an extremum
  exists within the given data table.

  possible Uses:
  XY-Data = JDTT vs LunarDistKm

  NO DEPENDENCIES
  ============================================================================
*/

   
function Extremum_5 ($DataTableStr)
{
// -----------------------------------------
// Read data table and parse numeric values.
   
$DataTable PReg_Replace("/\s+/"' 'trim($DataTableStr));
   @list (
$x1,$y1$x2,$y2$x3,$y3$x4,$y4$x5,$y5)
   = 
PReg_Split("[ ]"$DataTable);

   
$interval $x2 $x1;

   
$a $y2 $y1;
   
$b $y3 $y2;
   
$c $y4 $y3;
   
$d $y5 $y4;

   
$e $b $a;
   
$f $c $b;
   
$g $d $c;
   
$h $f $e;
   
$i $g $f;
   
$j $i $h;

   
$k $j/24;
   
$m = ($h $i)/12;
   
$n $f/$k;
   
$p = ($b $c)/$m;

   
$q $r 0;

   while (
$r 25)
  {
   
$s 6*($b $c) - $h $i 3*$q*$q*($h $i) + 2*$q*$q*$q*$j;
   
$t $j 12*$f;
   
$q $s/$t;

   
$r++;
  }
   return 
$x3 $q*$interval;

// end of  Extremum_5(...)


/* ######################################################################## */



/*
   ===========================================================================
   This is the inverse Julian date function.   Given any general Julian date
   on the old Julian or modern Gregorian calendar, it returns the correspond-
   ing full date and time string.  The fractional part of the Julian date
   indicates the time of day.

   RE-ENGINEERED FOR THIS PROGRAM TO NEAREST WHOLE SECOND.

   In this program, this function is used to compute the date and time of
   a perigee or apogee event given its computed JD (Julian Date).

   INPUT ARGUMENTS: (JDStr, AMPM24, ssDecimals)

   JDStr = Julian date as numeric string, like '2432959.1843657209'

   AMPM24 = time Mode
            '24h' = 24h military time mode = Default
            'A|P|AM|PM|AMPM|PMAM' All = 12h AM/PM civil time mode

   ssDecimals = Decimals at which to round off seconds part.
                Default = 0

   ----------------------
   INPUT/OUTPUT EXAMPLES:

   print Inv_JD ('2433057.13271234131', '', '');
// = G+1949-May-20-Fri 15:11:06

   print Inv_JD ('2433057.13271234131', 'AP');
// = G+1949-May-20-Fri 03:11:06 PM

   print Inv_JD ('1433057.13271234131', '');
// = J+1949-May-07-Fri 15:11:06


   NO DEPENDENCIES
   ===========================================================================
*/

   
function Inv_JD ($JDStr$AMPM24='24h'$ssDecimals=0)
{
   
$Q 32// Internal working decimals.

   
$q floor(trim($ssDecimals));
   
$ssFmt = ($q == 0)? "%02d" "%0".($q).".$q".'f';
   
$ssAdj = ($q == 0)? 0.5 0;

   
$JD trim($JDStr);

// Automatically determine calendar mode to use
// according to the JD value. If not the Julian
// calendar, then default to Gregorian calendar.
// Julian calendar if JD < 2299160.5
// Gregorian calendar if JD >= 2299160.5
   
$JG = ($JD 2299160.5)? 'J':'G';

   
$J  bcAdd($JD'0.5'$Q);

   
$timeMode substr(StrToUpper(trim($AMPM24)),0,1);

// Compute time of day in hours since beginning of date.
   
$timeHours bcMul('24'bcSub($JbcAdd($J'0'), $Q), $Q);

// Compute Julian Day Number from Julian date value.
   
$JDNum bcAdd($J'0');

// Compute date elements (m,d,y) according to (JDMode).
   
$MDYstr = ($JG == 'J')? JDtoJulian($JDNum) : JDtoGregorian($JDNum);
   list(
$m,$d,$y) = PReg_Split("[\/]"$MDYstr);

   
$y SPrintF("%+d"$y);
   
$dd SPrintF("%02d"$d);

// Get month name and day of week abbreviations.
   
$Mmm = ($JG == 'J')? JDMonthName($JDNum2) : JDMonthName($JDNum0);
   
$DoW JDDayOfWeek($JDNum2);

// Compute time of day elements (hh,mm,ss) = 00h to 24h
   
$hours $timeHours;
   
$hh bcAdd($hours'0');

   
$minutes bcMul('60'bcSub($hours$hh$Q), $Q);
   
$mm bcAdd($minutes'0');

   
$seconds bcMul('60'bcSub($minutes$mm$Q), $Q);
   
$ss SPrintF("%06.3f"$seconds); // 0x.xxx

// Construct time of day string.
   
$hh SPrintF("%02d"$hh);
   
$mm SPrintF("%02d"$mm);
   
$ss SPrintF($ssFmt$seconds $ssAdj); // 0x.xxx

// Account for that blasted 60s glitch.
   
if ($ss == 60) {$mm += 1;  $ss 0;}
   if (
$mm == 60) {$hh += 1;  $mm 0;}

// Reconstruct time of day string.
   
$hh SPrintF("%02d"$hh);
   
$mm SPrintF("%02d"$mm);
   
$ss SPrintF("%02d"$ss); // ss

// Adjust for AM/PM/24h time return mode.
// If not AM/PM mode, then default to 24h mode.
   
$AMPM '';  $hh floor($hh);

   if (
$timeMode == 'A' or $timeMode == 'P')
  {
   if (
$hh and $hh 12) {$AMPM ' AM';}
   if (
$hh == 12) {$AMPM ' PM';}
   if (
$hh  12) {$AMPM ' PM';  $hh -= 12;}
   if (
$hh ==  0) {$AMPM ' AM';  $hh =  12;}
  }
   
$hh SPrintF("%02d"$hh);

   
$BCAD = ($y 0)? 'BC':'AD';

//   $y = SPrintF("% 4d", $y);
// exit("'$y'");


// Done.
   
return "$JG$y-$Mmm-$dd-$DoW  $hh:$mm:$ss$AMPM";

// end of  Inv_JD (...)


/* ######################################################################## */




/*
   This function extracts only the lunar ephemeris
   columns needed to compute the PA events.
*/

   
function Extract_Lunar_Ephemeris ($EphemerisTableText$TZhhmm='+00:00')
{
   
$wArray explode("\n"trim($EphemerisTableText));
   
$wCount count($wArray);

   
$TZFracDay HMS_to_Hours(trim($TZhhmm)) / 24;

   
$wText '';
   
$j=0;

   for (
$i=0;   $i $wCount;   $i++)
  {
   
$CurrLine trim($wArray[$i]);
   
$CurrLine PReg_Replace("/\s+/"" "trim($CurrLine));

   if (
$CurrLine == '$$SOE') {$j=1;}
   if (
$CurrLine == '$$EOE') {break;}

   if (
$j == and $CurrLine <> '$$SOE')
      {
       list(
$DateStr$UTStr$JDUT$DistKm$RangeRateKm)
       = 
PReg_Split("[ ]"trim($CurrLine));

       
$JDNum floor($JDUT 0.5);
       
$DoW JDDayOfWeek($JDNum2);
       
$JDUT SPrintF("%1.9f"$JDUT);

//     Compute date and time for the given TZ offset.
       
$JDLT $JDUT $TZFracDay;
       
$JDNumLT floor($JDLT 0.5);
       
$DoWLT JDDayOfWeek($JDNumLT2);
       
$JDLT SPrintF("%1.9f"$JDLT);
       
$DateStrLT Inv_JD($JDLT'AP');

       
$DistKm SPrintf("%10.3f"$DistKm);
       
$RangeRateKm SPrintf("%+1.7f"$RangeRateKm);
       
$RangeRateKm = ($RangeRateKm 0)? '-':'+';

       
$wText .= "$DateStrLT $JDLT $DistKm $RangeRateKm\n";
      }
  }

// Filter out certain unneeded elements.
   
$w trim(Str_Replace('J+'''$wText));
   
$w trim(Str_Replace('G+'''$wText));
   
$w trim(Str_Replace(':00 AM'' AM'$w));
   
$w trim(Str_Replace(':00 PM'' PM'$w));
   
$w trim(Str_Replace('  '' '$w));

   return 
$w;

// End of  Extract_Lunar_Ephemeris (...)


/* ######################################################################## */


/*
   This function uses the +/= transitions markers to
   construct all tables used for the computation of
   the lunar perigees and apogees.

   Read forward looking for a change of +/- signs at
   the end of the lines.

   Change from - to + means perigee
   Change from + to - means apogee

   Collect up lines from 2 before to 2 lines after
   the trasition points to make 5-line tables.

   There will be at least two tables and sometimes
   a third table due to a second perigee or apogee
   in the same calendar month.

*/

   
function Construct_Lunar_Extrema_Tables ($MonthTable)
{
   
$mTable trim($MonthTable);
   
$wArray explode("\n"$mTable);
   
$wCount count($wArray);

   
$OutTable '';

   for(
$i=1;   $i $wCount;   $i++)
  {
   
$PrevLine trim($wArray[$i-1]);
   
$CurrLine trim($wArray[$i]);

// If signs differ, the create 5-line PA work-table.
   
if (substr($PrevLine, -1) <> substr($CurrLine, -1))
      {
       
$k 0;
       for(
$j=$i-2;   $j $i+3;   $j++)
          {
           
$k += 1;

           @
$ww substr($wArray[$j], -30);
           
$OutTable .= "$ww\n";
           if (
$k == and $k <> 0) {$OutTable .= "*\n";}
          }
      }
  }
   
$OutTable Str_Replace(' -'''$OutTable);
   
$OutTable Str_Replace(' +'''$OutTable);

   return 
RTrim(trim($OutTable), '*');

// End of  Construct_Lunar_Extrema_Tables(...)


/* ######################################################################## */


/*
   This function computes all the perigee and apogee events in the PA
   work-tables. The time zone has already been taken into account
   within the PA work-tables prior to calling this function.
*/

   
function Compute_All_PA_Events ($PAWorkTables$DistUnits='km')

{
   GLOBAL 
$Year;

   
$PAWTables trim($PAWorkTables);
   
$wArray    PReg_Split("[\*]"$PAWTables);
   
$wCount    count($wArray);

   
$DU substr(StrToLower(trim($DistUnits)),0,1);
   
$mi = ($DU == 'm')? 1.609344 1.0;


// Process each PA work-table within array
// and compute each PA event date and time
// and distance.

   
$OutTable '';

   for(
$i=0;   $i $wCount;   $i++)
  {
   
$wTable trim($wArray[$i]);

// Compute the PA event date and time string for the local TZ
   
$PAJDLT Extremum_5 ($wTable);
   
$PAJDLT SPrintF("%1.9f"$PAJDLT);
   
$PADateTimeLT Inv_JD($PAJDLT'AP');
   
$PADateTimeLT Str_Replace('G+'''$PADateTimeLT);
   
$PADateTimeLT Str_Replace('J+'''$PADateTimeLT);

// Compute the distance of the PA event in miles or kilometers.
   
$DistMiKm Lagrange_Interp($wTable$PAJDLT);
   
$PAMarker = (substr($DistMiKm,0,1) == '3')? 'Perigee ':'Apogee  ';
   
$DistMiKm SPrintF("%10.3f"$DistMiKm/$mi);

// Handle special overflow case(s) at ends.
   
$PAJDLT '                 ';
   
$w "$PAMarker $PAJDLT $PADateTimeLT  $DistMiKm\n";
   if (
IntVal($PADateTimeLT) == $Year) {$OutTable .= "$w";}
  }
   return 
trim($OutTable);
}

/* ######################################################################## */
//Perigee  2460322.936170310
//9 to 25 = JD





/*
   This function filters the table and separates
   the months of the year for easier readability.

*/

   
function Final_Filter ($FinalTableText)
{
   
$T trim($FinalTableText);

   
$wArray Preg_Split("[\n]"$T);
   
$wCount count($wArray);

   
$OutTable '';
   for (
$i=1;   $i $wCount;   $i++)
  {
   
$PrevLine trim($wArray[$i-1]);
   
$PrevMmm  substr($PrevLine323);

   
$CurrLine trim($wArray[$i]);
   
$CurrMmm  substr($CurrLine323);

   if (
$PrevMmm <> $CurrMmm)
      {
$LineBreak "\n";}
   else
      {
$LineBreak '';}

   
$OutTable .= "$PrevLine$LineBreak\n";
  }
   
$OutTable $OutTable.$wArray[$i-1];

   
$OutTable Str_Replace('e                    ''e  '$OutTable);


   return 
trim($OutTable);
}

/* ######################################################################## */



?>