<?php

/*
   MARS PERIGEE AND APOGEE CALCULATOR
   FOR THE YEARS FROM 1000 TO 9998.

   Based on the NASA/JPL Horizons API v1.1

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

   This program is an implementation of the API.

   PHP program by Jay Tanner
   License: Public Domain
*/

// Initialize output buffer.
   
ob_start();

// Set note of PHP version.
   
$PHPVersionStr =  'PHP v7.4.9';

// Get 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');

// Construct download link so that it only shows up
// when the program is posted on the public WWW. On
// the local desktop, the download link is supressed.
   
$DLLink '';

// Set ephemeris year range.
   
$MinYear 1601;
   
$MaxYear 2499;
   
$EphSpan $MaxYear $MinYear 1;

// Define internal document page title, HTML
// page heading text and revision date.
   
$_INTERNAL_TITLE_  "Mars Perigees and Apogees Calculator";
   
$_INTERFACE_TITLE_ "<b style='font-size:160%; font-weight:normal;'>Mars Perigees and Apogees Calculator</b><br><span style='font-size:100%;'>For the $EphSpan-Year Span From&nbsp;&nbsp;$MinYear AD &nbsp;to&nbsp; $MaxYear AD<br>
   <b 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 the program cookie name and set it to expire in 7 days.
// This cookie can be shared by other programs in the same common
// home folder together.
   
$_COOKIE_NAME_ 'Perigees_and_Apogees_Calculator';
   
$SetToExpireIn7Days time() + 7*86400;

// 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.
// If [COMPUTE] button was clicked and an active cookie
// exists, then restore the previous interface settings
// from it.
   
$w Filter_Input(INPUT_POST'ComputeButton');
   if (!IsSet(
$w))
     {
      
$w Filter_Input(INPUT_COOKIE$_COOKIE_NAME_);
   if (IsSet(
$w))
      {
       
$CookieDataString Filter_Input(INPUT_COOKIE$_COOKIE_NAME_);
        list
       (
        
$Year$TZhhmm$kmmi$LocLabel
       
) = Preg_Split("[\|]"$CookieDataString);
      }

   else

// Set the initial default interface startup values.
 
{
  
$Year     GMDate('Y');
  
$TZhhmm   '+00:00';
  
$kmmi     'km';
  
$LocLabel '';

// Store interface settings in cookie.
   
$CookieDataString "$Year|$TZhhmm|$kmmi|$LocLabel";
   
setcookie ($_COOKIE_NAME_$CookieDataString$SetToExpireIn7Days);
  }
      } 
// 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');}
   
$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);

// Select distance units if moon (ID 301)
// is the body. Can be either 'km' or 'mi'.
// Otherwise, 'AU' will be used for other
// bodies.
   
$kmmi 'km';

// 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'));

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




// =========================================
// 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)
     {
      
$ErrFlag FALSE;
      
$ErrMssg =
"Invalid Year: '$Year'

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 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 EPHEMERIS PARAMETER SETTINGS
// AND QUANTITIES TO BE COMPUTED.
//
// UT is used for internal computations and
// then the output converted for the given
// time zone offset from UT.
  
{
// Set solar system body ID
   
$body_id 499;

// Set start/stop times for a full year
// Earth perihelion and aphelion ephemeris.
   
$PrevYear   $Year 1;
   
$NextYear   $Year 1;
   
$TScale     'UT';

// Set starting and stopping times for the ephemeris.
// This range may vary depending on what computations
// are being done. Table computed at 3-hour intervals.
   
$start_time "$PrevYear-Dec-30"// Start
   
$stop_time  "$NextYear-Jan-02"// Stop
   
$step_size  '3h';

// ==============================================================
// CONSTRUCT CALLING URL FOR JPL HORIZONS API. IN THE CASE OF THE
// MOON, USING KILOMETERS AS RANGE (DISTANCE) UNITS MAY BE MORE
// CONVENIENT THAN THE DEFAULT AUs.

   
$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='AU'";
// ==============================================================





// Determine if Julian or Gregorian calendar
// applies or both (1582-Oct only).
// 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.";}



// Determine time scale (UT or LT)
// UT = Universal Time
// LT = Local Time for given TZ
   
$TimeZoneHM HMS_to_Hours($TZhhmm);
   
$TimeScale  = ($TimeZoneHM <> 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";

// ===========================
// CREATE INTERNAL WORK-TABLES

// -------------------------------------------------------------
// Compute RAW ephemeris text table using NASA/JPL Horizons API.
// This raw text includes the header info, the ephemeris itself
// and the footer explaining the columns.

   
$RawEphemerisTable trim(File_Get_Contents($FROM_NASA_JPL_HORIZONS_API));


// --------------------------------------------------------------------
// Extract ONLY the ephemeris part between the $$SOE and $$EOE markers.
// $$SOE = Start Of Ephemeris
// $$EOE = End Of Ephemeris

   
$EphemerisWorkTable Extract_Ephemeris($RawEphemerisTable);

// Construct P/A work table(s).  It may be possible for
// multiple A/P events to occur in a year, depending on
// the selected body.

@   $PAWorkTable     Make_PA_Work_Table($EphemerisWorkTable);
@   
$PAEventsTableLT Compute_PA_Events ($PAWorkTable$TimeZoneHM);
if (
$PAEventsTableLT == '') {$PAEventsTableLT "   NO PERIGEE OR APOGEE EVENTS DURING YEAR $Year IN TIME ZONE UT$UT1C$TZhhmm";}




// ====================================
// Define output ephemeris header text.
   
$HeaderText =
"                   MARS PERIGEE AND APOGEE FOR THE YEAR $Year
               ================================================
               Time Zone UT
$UT1C$TZhhmm
               
$LocLabel
$JGMessage";

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

========================================================================
 EVENT   Julian Date 
$TScaleStr   Calendar Date     Time $TScaleStr    Dist. AU/mi/km
------- ----------------- ---------------  -----------  ----------------
$PAEventsTableLT
------- ----------------- ---------------  -----------  ----------------

"
;

  }


// -----------------------------
// Define content for TextArea2.

   
$TextArea2Text =
"
NOTES:

[1] This Mars perigee and apogee calculator spans the 
$EphSpan year
    period from 
$MinYear AD to $MaxYear AD.

    It was written in 
$PHPVersionStr and makes internal calls to the
    NASA/JPL Horizons API v1.1

    Julian Dates (JD) and Calendar Dates:
    JD <  2299160.5 = Refers to Old-Style Julian Calendar Dates

    JD >= 2299160.5 = Refers to Modern Gregorian Calendar Dates

    Given JD = General Julian Date, then the Julian Day Number
               on the Calendar is JD Number = floor(JD + 0.5)

    Dates up to 1582-Oct-04-Thu refer to the Julian calendar.
    Dates from  1582-Oct-15-Fri refer to the Gregorian calendar.

    Technically speaking, there are no calendar dates in the range
    from 1582-Oct-05-Fri to 1582-Oct-14-Sun because those 10 dates
    were dropped from the calendar during the Julian to Gregorian
    calendar transition to bring dates of the seasons back into
    alignment with the sun and the rule for leap year was changed
    to prevent the previous calendar error from recurring.

    The date following 1582-Oct-04-Thu was 1582-Oct-15-Fri, the
    official first date on our modern Gregorian calendar system.



[2] Occasionally a time-out may occur if the JPL Horizons server
    is temporarily unavailable or too busy and an error occurs.

    If such a crash or hang-up does occur, simply refresh and try
    again.



[3] UT1 = Old Universal Time Scale - Solar based.
          Old previous UT time scale used for years up to 1961.

    UTC = Coordinated Universal Time Scale - Atomic based.
          Newer UT time scale used from 1962 to date.
          World times are now based on this standard.

    LT  = Local Time for the given time zone based on the
          given &plus;/&minus; HH:MM offset.

          Time Zone Offset Convention:
          West of Greenwich, time zone offsets are negative.
          East of Greenwich, time zone offsets are positive.



[4] Standard times are assumed. To adjust for Daylight Saving or
    Summer Time, add 1 hour to the standard times taking care to
    watch for any change of date -OR- subtract 1 hour from the
    time zone offset, which will automatically handle any date
    change.

    For example, the time zone offset for Eastern Standard Time
   (EST) is normally -5 hours. However, when Eastern Daylight Time
   (EDT) is in effect, use a time zone offset of -4 hours instead.
    The computed times will be in EDT and automatically handle any
    date changes.



[5] There is an optional location label that can be applied to the
    computed table for reference.  However, any label should match
    the indicated time zone so as to be accurate and make sense.

    For example, if using the time zone offset for India, putting
   'New York City, USA' as the location, would be rather absurd
    and an inaccurate match between location name and time zone.

    The label can consist of any printable plain-text string and
    defaults to 'Greenwich, England' at Time Zone Offset +00:00.



[6] This program implements a cookie to store and recall the year
    and other interface settings between calls. It does not track,
    monitor or perform any other activity. If you navigate away
    and come back later, the interface settings stored in the
    cookie will be recalled from your last visit.

    Each call will refresh the cookie for up to 7 days retention
    unless deleted sooner. There is no harm in deleting the cookie.
    The program will simply revert to the original default values.


"
;

// -----------------------------------------------------------------
// Determine the number of text rows to use in the output TextAreas.
   
$TextArea1Rows Substr_Count($TextArea1Text"\n");
   
$TextArea2Rows Substr_Count($TextArea2Text"\n");

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

<!DOCTYPE HTML>

<head>

<title>Mars - Perigee and Apogee Calculator</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<meta http-equiv='content-type' content='text/html; charset=UTF-8'>
<meta name='description' content='Mars Perigee and Apogee Calculator - Based on JPL Horizons API'>
<meta name='keywords' content='Mars,perigee,apogee,JPL Horizons API, Horizons, NASA'>
<meta name='author' content='Jay Tanner - PHP Science Labs'>
<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'>
<link rel="canonical" href="https://www.phpsciencelabs.com/nasa-jpl-horizons-mars-perigee-apogee-calculator/">

<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:200%; font-size:12px;
padding:6px; text-align:center;}

 TEXTAREA
{
 color:black; background:white; font-family:monospace; font-size:150%;
 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-family:monospace; color:black; background:white; font-size:150%;
 font-weight:bold; text-align:center; box-shadow:2px 2px 3px #666666;
 border:1px solid black; border-radius:0px;
}

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

 INPUT[type='submit']{font-weight:bold;}

 INPUT[type='submit']:hover
      {font-weight:bold; background:cyan; border-radius:4px;}

 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 width='700' align='top' border='0' cellspacing='1' cellpadding='3'>

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




<tr><td>
Year <input name='Year'  type='text' value="
$Year"  size='5' maxlength='4' title=' Span: $MinYear  to  $MaxYear '>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Time Zone&nbsp;&nbsp;<span style='font-size:140%; font-family:monospace;'><b>UT
$UT1C</b><sub>&nbsp;</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;'>
<br><br>
<input name='LocLabel' type='text' value="
$LocLabel" size='49' maxlength='48' title=' Optional Label Should Match the Given Time Zone '><br>Optional Location Label
</td></tr>




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



<tr>
<td style='color:silver; background:black;'>
Double-Click Within Text Area to Select ALL Text<br>
<textarea name='TextArea1' style='color:
$TxColor; background:$BgColor; padding:6px; border:2px solid white;' cols='78' rows="$TextArea1Rows" ReadOnly OnDblClick='this.select();' OnMouseUp='return true;'>
$TextArea1Text
</textarea></td>
</tr>



<tr>
<td style='color:silver; background:black;'>
Double-Click Within Text Area to Select ALL Text<br>
<textarea name='TextArea2' style='color:black; background:white; padding:6px;' cols='68' rows="
$TextArea2Rows" ReadOnly OnDblClick='this.select();' OnMouseUp='return true;'>
$TextArea2Text
</textarea></td>
</tr>


<tr>
<td colspan="1" style="background:transparent; color:black; font-size:10pt;
                       text-align:center;">
<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;
            border-radius:4px;'>
            &nbsp;View/Copy PHP Source Code&nbsp;</a></b>
</td>
</tr>

<!-- download link - -->
<tr><td style='color:gray; background:black;'>
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>
</form>

</body>

_HTML;





/*
  ============================================================================-
  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.

  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 Distance

  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 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)
{

// -----------
// Initialize.
   
$XDataStr $YDataStr '';

// --------------------------------------------------------------
// Read and split XY data pairs into a work array.  In the array,
// even number indices = X-data, odd number indices = Y-data.

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

// ---------------------------------------------
// count the total number of data elements. This
// value should always be an even number.

   
$TotalDatacount count($XY);

// ------------------------------------------------------------
// Number of data pairs.  This value should be an integer value
// exactly equal to 1/2 the total number of data points. If not,
// then there is an odd mismatched data point.

   
$n $TotalDatacount 2;

// -----------------------------------------------------------
// Return error message if data vector element count mismatch.
// For every X value there must be a corresponding Y value or
// an XY data count mismatch error occurs.  An error will also
// occur if insufficient data.  There must be at least two XY
// data points given.

   
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.";}

// Compute number of XY data pairs.  This value is exactly half
// the value of the total number of data elements.

   
$n $TotalDatacount 2;

// Construct separate XY data strings from the array data.
// The XY data strings should each contain the same number
// of matching data column pairs.

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

// Split the created XY data vector strings into matching indexed
// arrays.  For every X value there must be a matching Y value
// and no two X values can be identical.

   
$X PReg_Split("[ ]"trim($XDataStr));
   
$Y PReg_Split("[ ]"trim($YDataStr));

// Read X argument for which to interpolate
// the Y value from the given XY data.
   
$x trim($xArg);  if ($x == '') {$x 0.0;}

// Initialize Y summation accumulator.
   
$y 0.0;

// Compute Lagrangian product (Li) for given X argument.

   
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

//      Accumulate sum of Yi polynomial terms.
        
$y += ($Y[$i] * $Li);

       } 
// next i

   
return $y;

// end of  Lagrange_Interp (XYDataTable, xArg)

// ---------------------------------------------------------------------------





/*
   ===========================================================================
   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"abs($y));

// Done. JG marker removed from output. +y sign removed.
   
return "$y-$Mmm-$dd-$DoW  $hh:$mm:$ss$AMPM";

// end of  Inv_JD (...)


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

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

   
function Compute_All_PA_Events ($PAWorkTables)
{
   GLOBAL 
$Year;

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

// 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]);

// Determine if perigee or apogee table
// and then remove PA marker before
// performing computations.
   
if (StrPos($wTable'P') !== FALSE )
      {
$PAMarker 'Perigee';}
   else
      {
$PAMarker 'Apogee';}

//   $wTable = Str_Replace ('P', '', $wTable);
//   $wTable = Str_Replace ('A', '', $wTable);

// 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');

// Compute the distance of the PA event in AU.
   
$DistAU Lagrange_Interp($wTable$PAJDLT);
   
$DistAU SPrintF("%16.13f"$DistAU); // 12.1234567890123

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

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




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

   PATCHED AND MODIFIED FOR PERIHELION/APHELION

*/

   
function Final_Filter ($FinalTableText)
{
   
$wArray Preg_Split("[\n]"trim($FinalTableText));
   
$wCount count($wArray);

// Determine if initial event is a perigee or apogee event.
   
$u PReg_Replace("/\s+/"" "trim($wArray[0]));
   list(
$q,$q,$q,$q,$d1) = PReg_Split("[ ]"$u);
   
$w PReg_Replace("/\s+/"" "trim($wArray[1]));
   list(
$q,$q,$q,$q,$d2) = PReg_Split("[ ]"$w);

   
$OutTable '';
   for (
$i=1;   $i $wCount;   $i++)
  {
   
$PrevLine trim($wArray[$i-1]);
   
$PrevMmm  substr($PrevLine233); // Get PrevMmm
   
$pp trim(substr($PrevLine46StrLen($PrevLine)));
   
$CurrLine trim($wArray[$i]);
   
$CurrMmm  substr($CurrLine233); // Get CurrMmm
   
$qq trim(substr($CurrLine46StrLen($CurrLine)));
   if (
$pp $qq) {$PAM 'Perigee';}else{$PAM 'Apogee ';}

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

   
$OutTable .= "$PAM $PrevLine$LineBreak\n";
  }

// Determine final AP symbol to prepend to final line.
   
$APFinalMarker substr(trim($wArray[$i-1]),0,1);
   
$APFinalMarker = ($APFinalMarker == 'A')? 'Perigee' 'Apogee  ';

   
$OutTable $OutTable.$APFinalMarker.$wArray[$i-1];

// Patch to adjust spacing.
   
$OutTable Str_Replace(' AM',' AM '$OutTable);
   
$OutTable Str_Replace(' PM',' PM '$OutTable);


   return 
trim($OutTable);
}


/*
   ===========================================================================
   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 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 converts a time elements string ('h m s') to decimal hours.

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

   
function HMS_to_Hours ($hhmmss)
{
// Read hour angle string argument and force to lower case in
// the event any (h m s :) symbols are used.
   
$hms StrToLower(trim($hhmmss));

// Convert any (h m s :) symbols into spaces.
   
$hms Str_Replace('h'' '$hms);
   
$hms Str_Replace('m'' '$hms);
   
$hms Str_Replace('s'' '$hms);
   
$hms Str_Replace(':'' '$hms);

// Normalize the spacing and then split and extract the
// individual time string elements (hh, mm,ss).
   
$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";

// Remember and then remove any numerical (±) sign.
   
$NumSign = (substr($hhmmss,0,1) == '-')? '-' '';
   
$hh Str_Replace('-'''$hh);
   
$hh Str_Replace('+'''$hh);

// If original angle argument began with a + sign, then preserve
// it so that all returned positive results will have a + sign.
// Otherwise, positive results will NOT have a + sign.
   
if (substr($hhmmss,0,1) == '+') {$NumSign '+';}

// Compute decimal hours value equivalent
// to the combined hh,mm,ss values.
   
$w2 bcAdd(bcAdd(bcMul($hh,'3600',20),bcMul($mm,'60',20),20),$ss,20);

// If result equates to zero, then suppress any numerical sign.
   
if (bcComp($w2'0'20) == 0) {$NumSign '';}

// round off result to 16 decimals.
   
$w =  bcAdd(bcDiv($w2,360020), '0.00000000000000005'16);

// Reattach numerical sign and trim off
// any redundant zeros/decimal point.
   
return $NumSign.Rtrim(Rtrim($w'0'), '.');

// end of  HMS_to_Hours (hhmmss)



/*
   This function extracts ONLY the ephemeris portion between
   the $$SOE and $$EOE markers from the raw ephemeris table.

*/

   
function Extract_Ephemeris ($RawEphemerisTableText)
{
// Split text file into a string line array.
  
$wArray explode("\n"trim($RawEphemerisTableText));
  
$wCount count($wArray);

  
$wText '';
  
$NumColumns $ii 0;

// Collect ephemeris table between
// the $$SOE and $$EOE markers.
   
for ($i=0;   $i $wCount;   $i++)
  {
   
$CurrLine trim($wArray[$i]);

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

   if (
$ii == and $CurrLine <> '$$SOE')
      {
   if (
$NumColumns == 0)
      {
       
$NumColumns Substr_Count(PReg_Replace("/\s+/"" "trim($CurrLine)), ' ');
      }
       
$wText .= "$CurrLine\n";
      }
  }
    return 
trim($wText);
}



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

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

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

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

   The first line in each table contains a marker
   to indicate a perigee (P) or apogee (A) event.
*/

   
function Make_PA_Work_Table ($WorkTable)
{
   
$wTable trim($WorkTable);
   
$wArray explode("\n"$wTable);
   
$wCount count($wArray);

   
$OutTable $PAMarker '';

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

// PReg_Replace("/\s+/", " ", trim($TextString));
// Get numerical sign from (radial velocity) column 5 at end.
// 2020-Dec-11 12:00 2459195.000000000     0.00242737191826  -0.0225830
   
$PrevRadVel Get_Nth_Element ($PrevLine5);
   
$PrevSign = ($PrevRadVel 0)? '-':'+';
   
$CurrRadVel Get_Nth_Element ($CurrLine5);
   
$CurrSign = ($CurrRadVel 0)? '-' '+';

// If radial velocity signs differ, then
// create 5-line PA work-table.
   
if ($PrevSign <> $CurrSign)
      {
       
$k 0;
       for(
$j=$i-2;   $j $i+3;   $j++)
          {
           
$PAMarker = ($PrevSign == '+' and $CurrSign == '-')? 'A' 'P';

           
$k += 1;

           
$wLine  trim($wArray[$j]);
           
$wLine  PReg_Replace("/\s+/"" "trim($wLine)) ;
           
$JDLT   Get_Nth_Element ($wLine3);
           
$DistAU Get_Nth_Element ($wLine4);

           if (
$k == 1){$PAM "$PAMarker\n";} else $PAM '';

           
$ww "$PAM$JDLT $DistAU";
           
$OutTable .= "$ww\n";
           if (
$k == and $k <> 0) {$OutTable .= "*\n";}
          }
      }
  }

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

// End of  Make_Perigee_Apogee_Tables (...)



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



/*
   Given a table text string, this function extracts the Nth
   element within the string where the elements are delimited
   by at least one space.

   EXAMPLE TABLE TEXT LINE
   2020-Nov-30 00:00 2459183.500000000     0.00269100735899  -0.0235562

   The table columns are indexed 1, 2, 3, ...

   ERRORS:
   If N < 1 or N > number of elements in the string.
   An empty string is returned on error.
*/

   
function Get_Nth_Element ($TableTextString$ColumnNum)
{
   
$T PReg_Replace("/\s+/"' 'trim($TableTextString));
   
$N trim($ColumnNum);

   
$wArray PReg_Split("[ ]"$T);
   
$wCount count($wArray);

   if (
$N or $N $wCount){return '';}

   
$NthElement trim($wArray[$N 1]);

   return 
$NthElement;
}






/*

P
2459004.000000000 0.28858297958026
2459004.125000000 0.28857709028851
2459004.250000000 0.28857612885142
2459004.375000000 0.28858009578048
2459004.500000000 0.28858899128852

*/


   
function Compute_PA_Events ($PAWorkTable$TZhhmm='+00:00')
{
   GLOBAL 
$Year;

   
$PAWT trim($PAWorkTable);
   
$TZHM trim($TZhhmm);

   
$TZFracDay HMS_to_Hours($TZHM) / 24;

   
$wArray PReg_Split("[\*]"$PAWT);
   
$wCount count($wArray);

   
$EventsTable '';

   for(
$i=0;   $i $wCount;   $i++)
  {
   
$CurrPATable trim($wArray[$i]);
   
$PAMarker = (StrPos($CurrPATable'P') !== FALSE)? 'Perigee':'Apogee ';
   
$CurrPATable trim(Str_Replace('P'''$CurrPATable));
   
$CurrPATable trim(Str_Replace('A'''$CurrPATable));

   
$PAJDUT Extremum_5($CurrPATable);
   
$PADist Lagrange_Interp($CurrPATable$PAJDUT);
   
$PAJDLT $PAJDUT $TZFracDay;
   
$PADateTimeLT Inv_JD($PAJDLT'AMPM');





// Ignore the event if it occurs in a different year
// other than the given GLOBAL year in the interface.
   
if (IntVal(trim($PADateTimeLT)) == $Year)
  {
     
$PAJDLT    SPrintF("%17.9f",  $PAJDLT);
     
$PADist    SPrintF("% 16.13f"$PADist);
     
$PADistMkm Number_Format($PADist*149597870.70);
     
$PADistMmi Number_Format($PADist*92955807.2730);

// If lunar distance range < 0.005 AU,
// then convert distance from AU to km,
// assuming the body is the moon (301).
   
if ($PADist 0.005)
      {
       
$PADist *= 149597870.7;
       
$PADist SPrintF("%10.3f"$PADist);
      }
       
$EventsTable .= "$PAMarker $PAJDLT $PADateTimeLT  $PADist
                                                          
$PADistMmi mi
                                                          
$PADistMkm km\n\n";
    }

  }
   return 
trim($EventsTable);

// End of  Compute_PA_Events (...)






?>