<?php

/*
   ###########################################################################
   This program gives the current ICRF coordinates, distances and light times
   for both Voyager 1 and Voyager 2 via the NASA/JPL Horizons API.

   AUTHOR   : Jay Tanner - 2025
   LANGUAGE : PHP v8.2.12
   LICENSE  : Public Domain
   ###########################################################################

   When will the light time to the Voyagers equate to 24 hours?
   24 hours = 1440 minutes

   Both Voyagers are headed in different directions at different
   speeds, so the dates where they both reach a light or signal
   time of 24 hours are widely separated.  In this case, the
   difference between them is some 9 years.

   =================================================================
   VOYAGER 1:

   Date__(UT)__HR:MN:SS, Date_________JDUT, , , 1-way_down_LT
   2026-Nov-17 00:00:00, 2461361.500000000, , , 1439.74276855
   2026-Nov-18 00:00:00, 2461362.500000000, , , 1439.86657188
   2026-Nov-19 00:00:00, 2461363.500000000, , , 1439.98846530
   2026-Nov-20 00:00:00, 2461364.500000000, , , 1440.10843736
   2026-Nov-21 00:00:00, 2461365.500000000, , , 1440.22647741
   2026-Nov-22 00:00:00, 2461366.500000000, , , 1440.34257554
   2026-Nov-23 00:00:00, 2461367.500000000, , , 1440.45672244

   -----------------------------------------
   Derived Lagrangian Interpolation Table 1:

     Light_Time  vs   JDate
   1439.74276855    2461361.5
   1439.86657188    2461362.5
   1439.98846530    2461363.5
   1440.10843736    2461364.5
   1440.22647741    2461365.5
   1440.34257554    2461366.5
   1440.45672244    2461367.5

   Light Time = 1440 Minutes (24 Hours)
                at JDUT = 2461363.595452058
              = 2026-Nov-19-Thu at 02:17:27.058 UT


   =================================================================
   VOYAGER 2:

   Date__(UT)__HR:MN:SS, Date_________JDUT, , , 1-way_down_LT
   2035-Oct-30 00:00:00, 2464630.500000000, , , 1439.51994751
   2035-Oct-31 00:00:00, 2464631.500000000, , , 1439.69796512
   2035-Nov-01 00:00:00, 2464632.500000000, , , 1439.87545799
   2035-Nov-02 00:00:00, 2464633.500000000, , , 1440.05239208
   2035-Nov-03 00:00:00, 2464634.500000000, , , 1440.22873372
   2035-Nov-04 00:00:00, 2464635.500000000, , , 1440.40444970
   2035-Nov-05 00:00:00, 2464636.500000000, , , 1440.57950730

   -----------------------------------------
   Derived Lagrangian Interpolation Table 2:

     Light_Time  vs   JDate
   1439.51994751    2464630.5
   1439.69796512    2464631.5
   1439.87545799    2464632.5
   1440.05239208    2464633.5
   1440.22873372    2464634.5
   1440.40444970    2464635.5
   1440.57950730    2464636.5

   Light Time = 1440 Minutes (24 Hours)
                at JDUT = 2464633.203548626
              = 2035-Nov-01-Thu at 16:53:06.601 UT

   ###########################################################################
   These Date/Time values were obtained  by 7-point Lagrangian Interpolation
   based on the above Light Times values vs. Julian Dates tabulated at 1-day
   intervals.

   --------------
   FINAL RESULTS:

   Voyager 1 Light Time reaches 24 hours on  AD 2026-Nov-19-Thu at 02:17 UTC.
   Voyager 2 Light Time reaches 24 hours on  AD 2035-Nov-01-Thu at 16:53 UTC.

   COMPUTED ON AD 2025-OCT-12-SUN  via  THE NASA/JPL HORIZONS API
   ###########################################################################
*/

// ---------------------
// Get current year UTC.

   
$cYear GMDate('Y');

// ----------------------------
// Define ephemeris parameters.

   
$TimeScale "UT";
   
$TimeZone  "+00:00";
   
$DaySumYN  'No';

   
$StartDateTime GMDate('Y-m-d  H:i:s');
   
$StopDateTime  GMDate('Y-m-d   H:i:') . FloatVal(GMDate('s')) . '.001';
   
$StepSize      '1 day';

// -----------------------
// RA, Declination (ICRF),
// Distance, Light Time.

   
$Quantities '1,20,21';


// -----------------------------------------
// Compute JD Number for current date (UTC).

   
$Y GMDate('Y');
   
$m IntVal(GMDate('m'));
   
$d IntVal(GMDate('d'));

   
$CurrJDNumUTC GregorianToJD($m$d$Y);

/*
   ------------------------------------------------------
    Compute time interval to date since initial launches.

    Diff = 17 days between launches
*/

// ------------------------------------------------------------
// V2 launched first (1977-Aug-20 @ 14:29 UTC = JDNum 2443376).

   
$xDaysSinceV2L $CurrJDNumUTC 2443376;
   
$YearsV2L SPrintF("%6.5f"$xDaysSinceV2L 365.25);
   
$DaysSinceV2L Number_Format($xDaysSinceV2L);
   
$Weeks floor(($xDaysSinceV2L) / 7);
   
$Days  = (($xDaysSinceV2L) % 7);
   if (
$Days == 0)
      {
       
$YearsV2L SPrintF("%6.5f"$Weeks 365.25);
       
$Days '';
       
$Weeks Number_Format($Weeks);
       
$WeeksDaysV2 "$Weeks weeks $Days";
      }
   else
      {
       
$Ds = ($Days <> 1)? 's':'';
       
$Days "and $Days day$Ds";
       
$Weeks Number_Format($Weeks);
       
$WeeksDaysV2 "$Weeks weeks $Days";
      }

// ------------------------------------------------------------
// V1 launched later (1977-Sep-06 @ 12:56 UTC = JDNum 2443393).

   
$xDaysSinceV1L $CurrJDNumUTC 2443393;
   
$YearsV1L SPrintF("%6.5f"$xDaysSinceV1L 365.25);
   
$DaysSinceV1L Number_Format($xDaysSinceV1L);
   
$Weeks floor(($xDaysSinceV1L) / 7);
   
$Days  = (($xDaysSinceV1L) % 7);
   if (
$Days == 0)
      {
       
$Days '';
       
$Weeks Number_Format($Weeks);
       
$WeeksDaysV1 "$Weeks weeks $Days";
      }
   else
      {
       
$Ds = ($Days <> 1)? 's':'';
       
$Days "and $Days day$Ds";
       
$Weeks Number_Format($Weeks);
       
$WeeksDaysV1 "$Weeks weeks $Days";
      }



/*
   -----------------------------------------------------------------------
   WHAT IS THE CURRENT DISTANCE BETWEEN VOYAGER 1 AND VOYAGER 2 IN SPACE ?

   We are simply computing the distance between two points in 3D classical
   Euclidean space.  In this case, the points in space are the Voyager 1
   and the Voyager 2 spacecraft.

   ICRF coordinates are used.
   Distances are in AUs.

   NOTE:
   In most computer languages, you have to convert angles from
   degrees to radians for use with the trigonometric functions.


   DEFINITIONS:

   1, 2     = Subscripts for Voyager numbers
   RA, Decl = Right Ascension and Declination ICRF
   D1, D2   = Earth distances to Voyagers 1, 2
   D12      = Distance between the Voyagers

   Then

   Compute rectangular 3D XYZ-coordinates for Voyager 1.
   x1 = D1 * cos(RA1) * cos(Decl1)
   y1 = D1 * sin(RA1) * cos(Decl1)
   z1 = D1 * sin(Decl1)

   and

   Compute rectangular 3D XYZ-coordinates for Voyager 2.
   x2 = D2 * cos(RA2) * cos(Decl2)
   y2 = D2 * sin(RA2) * cos(Decl2)
   z2 = D2 * sin(Decl2)

   Compute differences in their rectangular 3D coordinates.
   dx = x2 - x1
   dy = y2 - y1
   dz = z2 - z1

   Finally, compute Euclidean 3D distance between Voyagers.
   D12 = SqRoot(dx*dx + dy*dy + dz*dz)

*/






// ---------------------------------
// Define output header text blocks.

$OutputTextHeader1 =
"GEOCENTRIC ASTROMETRIC EPHEMERIS

Target Object ID  = Voyager 1

Start Date/Time   = 
$StartDateTime UTC

------------------------------------------------------------------------------
"
;

$OutputTextHeader2 =
"GEOCENTRIC ASTROMETRIC COORDINATES

Target Object ID  = Voyager 2

Start Date/Time   = 
$StartDateTime UTC

------------------------------------------------------------------------------
"
;



// --------------------------
// Compute astrometric stats.

   
$RawEphem1 Voyager('Voyager 1',$StartDateTime$StopDateTime'');
   
$RawEphem2 Voyager('Voyager 2',$StartDateTime$StopDateTime'');

/*
   Convert RA and Decl from HMS/DMS into equivalent radians.

   x1 = D1 * cos(RA1) * cos(Decl1)
   y1 = D1 * sin(RA1) * cos(Decl1)
   z1 = D1 * sin(Decl1)

   x2 = D2 * cos(RA2) * cos(Decl2)
   y2 = D2 * sin(RA2) * cos(Decl2)
   z2 = D2 * sin(Decl2)

   dx = x2 - x1
   dy = y2 - y1
   dz = z2 - z1

   D12 = SqRoot((dx*dx) + (dy*dy) + (dz*dz))
*/


// ----------------------------------------------
// Extract ONLY the required ephemeris CSV stats.

   
$V1BodyCSV trim(Get_CSV_Body($RawEphem1));
   
$V2BodyCSV trim(Get_CSV_Body($RawEphem2));


// ###########################################################################
// ###########################################################################
// FOR VOYAGER 1

   
list (
         
$DateTimeStr,
         
$w,$w,
         
$RAHMS,
         
$DeclDMS,
         
$DistAU,
         
$LTMins
        
) = Preg_Split("[,]"$V1BodyCSV);

    
$DateTimeStr Str_Replace(' ''  '$DateTimeStr);
    
$RAHMS   trim($RAHMS);
    
$DeclDMS trim($DeclDMS);
    
$DistAU  trim($DistAU);


// -------------------------------
// Compute RA and Decl in radians.

   
$RAHrs HMS_to_Hours($RAHMS);
   
$RADeg 15 $RAHrs;
   
$RArad Deg2Rad($RADeg);

   
$DeclDeg DMS_to_Deg($DeclDMS);
   
$Declrad Deg2Rad($DeclDeg);

// ----------------------------------------------
// Compute rectangular coordinates for Voyager 1.

   
$x1 $DistAU cos($RArad) * cos($Declrad);
   
$y1 $DistAU sin($RArad) * cos($Declrad);
   
$z1 $DistAU sin($Declrad);

   
$DistAU  trim($DistAU);
   
$DistKm  $DistAU 149597870.7;
   
$DistMi  Number_Format($DistKm 1.609344);
   
$DistKm  Number_Format($DistKm);

   
$RAHrs   SPrintF("%14.11f"$RAHrs);
   
$RADeg   SPrintF("%12.10f"15*$RAHrs);
   
$DeclDeg SPrintF("%+14.10f"$DeclDeg);

// -----------------------------
// Light time to nearest second.

   
$LTHrs trim($LTMins) / 60;
   
$LTHMS Hours_to_HMS ($LTHrs);


// =============
// FOR VOYAGER 1

$Stats1 =
"Right Ascension    Declination                Distance
 h  m   s           &deg;   &#39;   &#34;               AU | km | mi
$RAHMS    $DeclDMS      $DistAU AU
$RAHrs&#104;    $DeclDeg&deg;        $DistKm km
$RADeg&deg;                           $DistMi mi
                                        Light Time 
$LTHMS";

   list (
         
$DateTimeStr,
         
$w,$w,
         
$RAHMS,
         
$DeclDMS,
         
$DistAU,
         
$LTMins
        
) = Preg_Split("[,]"$V2BodyCSV);

   
$DateTimeStr Str_Replace(' ''  '$DateTimeStr);
   
$RAHMS  trim($RAHMS);
   
$DistAU trim($DistAU);

// -------------------------------
// Compute RA and Decl in radians.

   
$RAHrs HMS_to_Hours($RAHMS);
   
$RADeg $RAHrs 15;
   
$RArad Deg2Rad($RADeg);

   
$DeclDeg DMS_to_Deg($DeclDMS);
   
$Declrad Deg2Rad($DeclDeg);

// ------------------------------------------------
// Compute rectangular coordinates for Voyager 2.

   
$x2 $DistAU cos($RArad) * cos($Declrad);
   
$y2 $DistAU sin($RArad) * cos($Declrad);
   
$z2 $DistAU sin($Declrad);

// -----------------------------------
// Compute rectangular coordinates and
// differences between the Voyagers.

   
$dx $x2 $x1;
   
$dy $y2 $y1;
   
$dz $z2 $z1;

// -----------------------------------------------------
// Compute linear spatial distance between the Voyagers.

   
$D12AU SqRt(($dx*$dx) + ($dy*$dy) + ($dz*$dz));
   
$D12Km $D12AU 149597870.7;
   
$D12Mi Number_Format($D12Km 1.609344);
   
$D12Km Number_Format($D12Km);
   
$D12AU SPrintF("%14.10f"$D12AU);

// ---------------------
// Format output values.

   
$DistKm $DistAU 149597870.7;
   
$DistMi Number_Format($DistKm 1.609344);
   
$DistKm Number_Format($DistKm);

   
$RAHrs   SPrintF("%14.11f"$RAHrs);
   
$RADeg   SPrintF("%12.10f"15*$RAHrs);
   
$DeclDeg SPrintF("%+14.10f"$DeclDeg);

// -----------------------------
// Light time to nearest second.

   
$LTHrs trim($LTMins) / 60;
   
$LTHMS Hours_to_HMS ($LTHrs);

// ===============
// VOYAGER 2 STATS

$Stats2  =
"Right Ascension    Declination                Distance
 h  m   s           &deg;   &#39;   &#34;               AU | km | mi
$RAHMS   $DeclDMS      $DistAU AU
$RAHrs&#104;    $DeclDeg&deg;        $DistKm km
$RADeg&deg;                           $DistMi mi
                                        Light Time 
$LTHMS";


// --------------------------------------
// Copy ephemeris stats into (TextArea1).

   
$TextArea1Text =
"                    VOYAGER SPACECRAFT 1 and 2
      CURRENT APPARENT COORDINATES AND DISTANCES FROM EARTH
                     AND EACH OTHER IN SPACE

             Computed via the NASA/JPL Horizons API
              With a Little Extra High School Math

                    For Current Date and Time
                    
$DateTimeStr UTC

**************************************************************
VOYAGER 1 SPACECRAFT - JPL DATABASE ID# -31

Launched from Kennedy Space Center
1977-Sep-06-Tue   @ 12:56 UTC
Time since launch = 
$DaysSinceV1L days = $WeeksDaysV1
                  = 
$YearsV1L years ago

CURRENT APPARENT ICRF SKY COORDINATES
$Stats1

**************************************************************
VOYAGER 2 SPACECRAFT - JPL DATABASE ID# -32

Launched from Kennedy Space Center
1977-Aug-20-Sat   @ 14:29 UTC
Time since launch = 
$DaysSinceV2L days = $WeeksDaysV2
                  = 
$YearsV2L years ago

CURRENT APPARENT ICRF SKY COORDINATES
$Stats2

Current linear distance between Voyagers 1 and 2 in space:

$D12AU AU  =  $D12Km km  =  $D12Mi mi

SOME APPLIED DEFINITIONS AND EXTRA INFO FROM NASA/JPL

ICRF = International Celestial Reference Frame

1 AU = 1 Astronomical Unit
     = 149,597,870.700 km
     =  92,955,807.273 mi

1 mi = 1.609344 km

1 Standard (Julian) Year = 365.25 days
Refs:
https://www.nasa.gov/image-article/voyager-2-launched-before-voyager-1
https://www.jpl.nasa.gov/missions/voyager-2
https://www.jpl.nasa.gov/missions/voyager-1

Voyager 2 launched on Aug. 20, 1977, about two weeks before
the Sept. 6 (UTC) launch of Voyager 1.

Why the reversal of order?

The two were sent on different trajectories, and Voyager 1
was put on a path to reach its planetary targets, Jupiter
and Saturn, ahead of Voyager 2.
"
;





   
$TextArea2Text =
"
=====================================
SPECIAL NOTE ABOUT VOYAGER DISTANCES:

When tracking the distances to the Voyager spacecraft, you may
notice that there are times when the distances seem to be de-
creasing with time, rather than increasing with time.

This is because when Earth is on one side of its orbit, it is
moving towards the  spacecraft faster than the spacecraft are
moving away from us.  When the Earth is on the  other side of
its orbit, then the situation is reversed and the distance is
increasing over time.

The linear speed  of the spacecraft receding from Earth does
not change.   It is the speed of the Earth from which we are
making the observations that is moving at cyclically varying
relative speed over the year orbiting the sun.

So, approximately 1/2 of a year, the spacecraft are moving
away from Earth and the other 1/2 year towards Earth,  but
not necessarily at the same time, since the spacecraft are
traveling in very different directions at different speeds.

https://earthsky.org/space/voyager-spacecraft-getting-closer-to-earth/
Voyager 1 moves at a speed of 38,210 miles per hour (17 km/s).
Voyager 2 moves at a speed of 35,000 miles per hour (15 km/s).

The speed of the Earth in orbit is about
18.5 mi/s =  66,600 mi/h
29.8 km/s = 107,200 km/h

---------------------------------------------------------------
Q:
When will the light time to the Voyagers equate to 24 hours?

A:
According to the NASA/Horizons API, the answers are, as computed
on 2025-Oct-12-Sun:

=========
Voyager 1

Light Time = 1440 Minutes (24 Hours) at JDUT = 2461363.595452058
           = 2026-Nov-19-Thu at 02:17:27.058 UTC
           = 2026-Nov-18-Wed at 21:17:27.058 New York Standard Time

=========
Voyager 2

Light Time = 1440 Minutes (24 Hours) at JDUT = 2464633.203548626
           = 2035-Nov-01-Thu at 16:53:06.601 UTC
           = 2035-Nov-01-Thu at 11:53:06.601 New York Standard Time


Difference = 8.9519 years
           = 3269 days 14h 36m  =  467 weeks 0 days 14h 36m

These values were obtained by 7-point Lagrangian Interpolation
at 1-day intervals.    See top of source code comments listing
for the applied computational data and methodology.
"
;







/* ---------------------------------------------------------------------------
   Determine number of text rows to use in the output text area.  This value
   may vary randomly according to the text block length.  The idea is to
   eliminate the need for vertical scroll-bars within the text area or
   worry about the variable height (length) of a text display area.
*/

// ---------------------------
// TextArea1 columns and rows.

   
$Text1Cols Max(Array_Map('StrLen'PReg_Split("[\n]"trim($TextArea1Text))));
 
//  if ($Text1Cols > 80) {$Text1Cols = 78;}

   
$Text1Rows Substr_Count($TextArea1Text"\n");

// ---------------------------
// TextArea2 columns and rows.

   
$Text2Cols Max(Array_Map('StrLen'PReg_Split("[\n]"trim($TextArea2Text))));
   if (
$Text2Cols 80) {$Text2Cols 75;}
   
$Text2Rows Substr_Count($TextArea2Text"\n");


// ---------------------------------
// Construct output table structure.

  
$HTMLTableOut =
"<br>
<!-- Define main page title/header. --->
<table width='550' align='center' border='0' cellspacing='1' cellpadding='3'>


<tr><td colspan='99' style='color:white; background:#000066; font-size:14pt; font-weight:normal; border:2px solid white; border-radius:8px 8px 0px 0px;'>Current Status of Voyagers 1 and 2 Spacecraft<br><span style='font-size:10.5pt;'>Computations via the NASA/JPL Horizons API</span><br><span style='font-size:8pt;'>PHP Program by Jay Tanner of Geneva, NY, USA - 
$cYear</span></td></tr>
<tr><td>
<span style='color:GreenYellow; font-size:9pt; font-weight:normal;'><b>Refresh Page to Update to Current Moment UTC</b></span>
<br><br>

<!-- Yellow source code view link. --->
<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;
          font-size:8.5pt; border-radius:4px;'>
          &nbsp;View/Copy PHP Source Code&nbsp;</a>
<br><br>

<span style='color:GreenYellow; font-size:9pt; font-weight:normal;'>Double-Click Within Text Area to Select ALL Text</span><br>
<textarea name='TextArea1' cols='
$Text1Cols' rows='$Text1Rows' ReadOnly OnDblClick='this.select();' OnMouseUp='return true;'>
$TextArea1Text
</textarea>

<br>

<span style='color:GreenYellow; font-size:9pt; font-weight:normal;'>Double-Click Within Text Area to Select ALL Text</span><br>
<textarea name='TextArea2' cols='72' rows='
$Text2Rows' ReadOnly OnDblClick='this.select();' OnMouseUp='return true;'>
$TextArea2Text
</textarea>
</td></tr>

</table>


<br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br>
"
;


print <<< END_OF_HTML

<!DOCTYPE HTML>

<HTML>

<head>
<title>Current Status of Voyagers 1 and 2</title>

<style>
 BODY
{
 background:black; color:white; font-family:Verdana;
 font-size:10pt;
}

 TD
{
 background:black; color:silver; font-family:Verdana;
 font-size:10.5pt; font-weight:normal; text-align:center;
 padding:8px;
}

 TEXTAREA
{
 background:white; color:black; font-family:monospace; font-size:11pt;
 font-weight:bold; padding:6pt; white-space:pre; border-radius:8px;
 line-height:125%;
}

::selection{background-color:yellow !important; color:black !important;}
::-moz-selection{background-color:yellow !important; color:black !important;}

</style>

</head>

<body>

$HTMLTableOut

</body>

</HTML>


END_OF_HTML;




/*
   ###########################################################################
   This function returns a simple ephemeris from the NASA/JPL Horizons API.

   If no ephemeris is returned, then the text from the API is returned as-is
   because it could be an error message or some other status or query text.

   NO DEPENDENCIES
   ###########################################################################
*/
   
function Voyager($TargObjID,$StartDateTime,$StopDateTime,$OutputTextHeader)
{

// ===========================================================================
// Construct query URL for the NASA/JPL Horizons API.

   
$TargObjID trim($TargObjID);
   
$Command   URLEncode($TargObjID);

/* -----------------------------------------------------------
   Adjust for Daylight/Summer Time, if indicated. This assumes
   that the Time Zone string is given in the standard +-HH:mm
   format or an error may occur.

   $DaySumYN  = substr(StrToUpper(trim($DaySumYN)),0,1);
   $DSSTAdj = ($DaySumYN == 'N')? 0:1;
   list($TZHH, $TZmm) = PReg_Split("[\:]", $TimeZone);
   $TZSign = substr($TZHH,0,1);
   $TZHours = (($TZSign == '-')? -1:1)*(abs($TZHH) + $TZmm/60) + $DSSTAdj;
   $i = StrPos($TZHours, '.');  if ($i == FALSE) {$TZHours .= '.00';}
   $i = StrPos($TZHours, '.');
   $TZHH = $TZSign.SPrintF("%02d", abs(substr($TZHours,0,$i)));
   $TimeZone = "$TZHH:$TZmm";
*/

   
$From_Horizons_API =
   
"https://ssd.jpl.nasa.gov/api/horizons.api?format=text" .
   
"&COMMAND='$Command'"          .
   
"&OBJ_DATA='NO'"               .
   
"&MAKE_EPHEM='YES'"            .
   
"&EPHEM_TYPE='OBSERVER'"       .
   
"&CAL_FORMAT='CAL'"            .
   
"&REF_SYSTEM='ICRF'"           .
   
"&RANGE_UNITS='AU'"            .
   
"&SUPPRESS_RANGE_RATE='YES'"   .
   
"&ANG_FORMAT='HMS'"            .
   
"&APPARENT='AIRLESS'"          .
   
"&CENTER='500@399'"            .
   
"&TIME_DIGITS='SECONDS'"       .
   
"&TIME_ZONE='+00:00'"          .
   
"&START_TIME='$StartDateTime'" .
   
"&STOP_TIME='$StopDateTime'"   .
   
"&STEP_SIZE='1 day'"           .
   
"&EXTRA_PREC='YES'"            .
   
"&CSV_FORMAT='YES'"            .
   
"&QUANTITIES='2,20,21'"        ;
// ===========================================



/* -----------------------------------------------------------------------
   Send query to Horizons API to obtain the apparent topocentric ephemeris
   data for the given body ID as a plain-text CSV ephemeris table.
*/
   
$TopoEphem Str_Replace(",\n"" \n"File_Get_Contents($From_Horizons_API));

/* --------------------------------------------------------
   If no ephemeris is found,  then return the text from the
   API as-is. It may be an error message or some other text.
*/
   
if (StrPos($TopoEphem'$$SOE') === FALSE) {return HTMLEntities($TopoEphem);}

   
$TopoEphem "$OutputTextHeader\n$TopoEphem";

   return 
HTMLEntities($TopoEphem);

// End of  Voyager(...)




// *******************************
// SPECIAL INNER UTILITY FUNCTIONS
// *******************************

/*
   ###########################################################################
   This function can be used to extract any ephemeris table body from all of
   the other extraneous text that surrounds it. An ephemeris table can consist
   of 1 line up to thousands of lines of CSV data.

   The ephemeris CSV data lines are located between two markers:

   $$SOE = Start Of Ephemeris marker
           and
   $$EOE = End Of Ephemeris marker

   Generic.  No special error checking is done.

   NO DEPENDENCIES
   ###########################################################################
*/
   
function Get_CSV_Body ($RawEphemText)
{
// ---------------------------------
// Read raw ephemeris text argument.

   
$w trim($RawEphemText);

/* -------------------------------------------------
   Set pointers to start and end of ephemeris table.
   If there is no ephemeris found, then return the
   raw text as-is, nothing is done. It may possibly
   be an error or some other text instead.
*/
   
$i StrPos($w"\$\$SOE\n"); if ($i === FALSE) {return $w;}
   
$j StrPos($w"\$\$EOE\n");

/* --------------------------------------------
   Extract ONLY the required ephemeris CSV data
   line(s) from between the Start/End pointers.
*/
   
$EphemTableText trim(substr($w$i+5$j-$i-5));

   return (
substr($EphemTableText,0,1) == 'b')? $EphemTableText $EphemTableText";

// End of  Get_CSV_Body (...)





/*
   ###########################################################################
   This function returns the decimal hours equivalent to the given HMS string.

   Generic. No special error checking is done.

   NO DEPENDENCIES
   ###########################################################################
*/

   
function HMS_to_Hours ($HMSString$Decimals=16)
{
   
$HHmmss   trim($HMSString);
   
$decimals trim($Decimals);

/* ------------------------------------------------
   Account for and preserve any numerical +/- sign.
   Internal work will use absolute values and any
   numerical sign will be reattached the output.
*/
   
$NumSign substr($HHmmss,0,1);
   if (
$NumSign == '-')
      {
$HHmmss substr($HHmmss,1,StrLen($HHmmss));}
   else
      {
       if (
$NumSign == '+')
          {
$HHmmss substr($HHmmss,1,StrLen($HHmmss));}

       
$NumSign '+';
      }

// ------------------------------------------------------------------
// Replace any colons : with blank spaces and remove any white space.

   
$HHmmss PReg_Replace("/\s+/"" "Str_Replace(":"" "$HHmmss));

// ----------------------------------------
// Count the HMS time elements from 1 to 3.

   
$n  Substr_Count($HHmmss' ');

   
$hh $mm $ss 0;

/* ----------------------------------------------------------------------
   Collect all given time element values.  They can be integer or decimal
   values. Only counts up to three HMS values and any values beyond those
   are simply ignored.
*/
   
for ($i=0;   $i 1;   $i++)
  {
   if (
$n == 1){list($hh)         = PReg_Split("[ ]"$HHmmss);}
   if (
$n == 2){list($hh,$mm)     = PReg_Split("[ ]"$HHmmss);}
   if (
$n == 3){list($hh,$mm,$ss) = PReg_Split("[ ]"$HHmmss);}
  }

// ------------------------------------------------------------------------
// Compute HMS equivalent in decimal hours to the given number of decimals.

   
return $NumSign.(round((3600*$hh 60*$mm $ss)/3600,$decimals));

// End of  HMS_to_Hours(...)




/*
   ###########################################################################
   This function returns decimal degrees equivalent to a DMS string argument
   to the specified number of decimals.

   The Degrees, Minutes and Seconds string values are separated by spaces.

   Generic.  No special error checking is done.

   NO DEPENDENCIES
   ###########################################################################
*/

   
function DMS_to_Deg ($DMSString$Decimals=14)
{
   
$DDmmss   trim($DMSString);
   
$decimals trim($Decimals);

// -----------------------------------
// Account for any numerical +/- sign.

   
$NumSign substr($DDmmss,0,1);
   if (
$NumSign == '-')
      {
$DDmmss substr($DDmmss,1,StrLen($DDmmss));}
   else
      {
       if (
$NumSign == '+')
          {
$DDmmss substr($DDmmss,1,StrLen($DDmmss));}
           
$NumSign '+';
      }

// -----------------------
// Remove all white space.

   
$DDmmss PReg_Replace("/\s+/"" "$DDmmss);

// ----------------------------------------
// Count the DMS time elements from 1 to 3.

   
$n  Substr_Count($DDmmss' ');

   
$dd $mm $ss 0;

// --------------------------------------
// Collect all given time element values.
// They can be integer or decimal values.

   
for ($i=0;   $i 1;   $i++)
   {
   if (
$n == 1){list($dd)         = PReg_Split("[ ]"$DDmmss);}
   if (
$n == 2){list($dd,$mm)     = PReg_Split("[ ]"$DDmmss);}
   if (
$n == 3){list($dd,$mm,$ss) = PReg_Split("[ ]"$DDmmss);}
   }

// ----------------------------------
// Compute DMS equivalent in degrees.

   
return $NumSign.(round((3600*$dd 60*$mm $ss)/3600,$decimals));

// End of  DMS_to_Deg(...)






/*
   ###########################################################################
   This function returns an HMS string equivalent to an hours argument rounded
   to the specified number of decimals.

   Generic. No special error checking is done.

   NO DEPENDENCIES
   ###########################################################################
*/

   
function Hours_to_HMS ($Hours$Decimals=0)
{
   
$hours trim($Hours);  $NumSign = ($hours 0)? '-':'';
   
$hours Str_Replace('+'''Str_Replace('-'''$hours));

// ---------------------
// Set working decimals.

   
$Q 32;
   
$decimals floor(abs(trim($Decimals)));
   
$decimals = ($decimals $Q)? $Q $decimals;
   
$decimals = ($decimals <  0)?  $decimals;

// ------------------------------------
// Compute hours,minutes and seconds to
// the specified number of decimals.

   
$hh  bcAdd($hours'0');
   
$min bcMul('60'bcSub($hours$hh$Q),$Q);
   
$mm  bcAdd($min'0');
   
$sec bcMul('60'bcSub($min$mm$Q),$Q);
   
$ss  SPrintF("%1.$decimals"."f"$sec);
          if (
$ss 10){$ss "0$ss";}

// -------------------------------------------
// Try to account for that blasted 60s glitch.

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

// ------------------------------------------
// Construct and return time elements string.

   
$hh SPrintF("%02d"$hh);
   
$mm SPrintF("%02d"$mm);
   
$ss SPrintf("%1.$decimals"."f"$ss);
         if (
$ss 10){$ss "0$ss";}

   return 
"$NumSign$hh:$mm:$ss";

// End of  Hours_to_HMS (...)




?>