<?php

/*
   ###########################################################################
   PLANETOCENTRIC EPHEMERIS CALCULATOR - WITH 30-DAY COOKIE.

   AUTHOR   : Jay Tanner - 2024
   LANGUAGE : PHP v7.4.9
   LICENSE  : Public Domain

   Built around the NASA/JPL HORIZONS API:
   https://ssd.jpl.nasa.gov/horizons/app.html

   API Documentation:
   https://ssd-api.jpl.nasa.gov/doc/horizons.html

   PLANETOCENTRIC MEANS WE CAN SELECT ANOTHER BODY, SUCH AS MARS AND THEN
   COMPUTE AN EPHEMERIS AS VIEWED FROM THE CENTER OF THAT BODY INSTEAD OF
   THE STANDARD GEOCENTRIC VIEW FROM EARTH.

   THE PHASES OF THE MOONS OF MARS AND PLUTO AS VIEWED FROM THOSE PLANETS
   CAN ALSO BE COMPUTED.

   For example, we can compute the position of Earth among the stars as
   viewed from Mercury, Mars or Pluto, etc., and vice versa.

   This ephemeris returns only some basic ephemeris statistics, but more
   quantities could be added.

   Basically, it just calculates the astronomical coordinates, distance,
   apparent brightness and how fast the object is moving towards or away
   from the observer in space as viewed from the center of the central
   body and the constellation of the target body as viewed by an observ-
   er at that location.

   ----------------------------------------------------------
   The returned ephemeris statistics for the target body are:

   Column 01:
   Calendar Date and Time (UT|ZONE|TT).

   Column 02:
   Solar Presence Symbol.

   Column 03:
   Lunar Presence Symbol.

   Column 04:
   Apparent Geocentric or Planetocentric Right Ascension (ICRF).

   Column 05:
   Apparent Geocentric or Planetocentric Declination     (ICRF).

   Column 06:
   Geocentric or Planetocentric Distance (delta) Between Bodies  (AU).
   In the case of the moon, distance units = 'KM'

   Column 07:
   Geocentric or Planetocentric Radial Velocity (deldot).
   This is how fast the target body center is approaching (-) or receding (+)
   from the origin body where the observer (you) is located.

   Column 08:
   Angular Diameter of Target Body in Arc Seconds.

   Column 09:
   Apparent Visual Magnitude or Brightness Level of Target Body.

   Column 10:
   Surface Brightness of 1 Square Arc Second of Target Body Visible Disk.

   Column 11:
   Constellation ID (3-letter abbreviation based on constellation name)


   --------------------------------------------------------------
   These last two columns (12, 13) apply ONLY to the moons of the
   planets Earth, Mars and Pluto.  For those moons, the distance
   units used will be 'KM' instead of the default 'AU'.

   Column 12:
   SOT (Sun-Observer-Target) Angle (0 to 180 deg).
   This angle can be used with the /r column (sky position relative to sun)
   to determine the simple natural satellite phase angles.

   Column 13:
   Position of Target Body Relative (/r) to Sun.
   /L = Leading  = Rising/Setting BEFORE the Sun.
   /T = Trailing = Rising/Setting AFTER the Sun.

   -----------------------------------------------
   For the simple moon phase angle (0 to 360 deg):

   If '/L', then PhaseAng = 360 - SOT
   if '/T', then PhaseAng = SOT

   Where:

   PhaseAng      Visual_Phase
   --------    ----------------
    0 deg      New Moon
    45         Waxing Crescent
    90         First Quarter
    135        Waxing Gibbous
    180        Full Moon
    225        Waning Gibbous
    270        Last Quarter
    315        Waning Crescent
    360/0      New Moon


   The ICRF (International Celestial Reference Frame) is used.

   Selecting the TT time scale will force the Time Zone setting to 'No',
   since it is ignored and does not apply to that scale anyway.

   The Time Zone setting is only applied to the UT Time Scale.  When the
   Time Scale is UT and the Time Zone is +00:00, then UT is assumed.
   Otherwise, local ZONE Time is assumed.

   When Daylight/Summer Time is selected, +1 hour is applied to the Time
   Zone internally.

   NOTE:
   All ephemerides are plain-text tables in CSV (spreadsheet) format.
  ###########################################################################
*/


/* -----------------------------------------------
   Ob was the first caveman and Long was his mate.
*/
   
ob_start();

// -------------
// Current year;

   
$cYear date('Y');

// ---------------------------------------------------------------
// Define the program cookie name and set it to expire in 30 days.

   
$CookieName 'PLanetocentric-Ephemeris-Interface';
   
$ExpiresIn30Days time() + 30*86400;


// ---------------------------------
// Define PHP program and HTML info.

   
$_AUTHOR_  "by Jay Tanner";
   
$_VERSION_ "v1.0 - ";
          
$at "&#97;&#116;&#32;&#76;&#111;&#99;&#97;&#108;&#32;&#84;&#105;&#109;&#101;&#32;";
         
$LTC "&#85;&#84;&#67;";
   
$_SCRIPT_FILE_PATH_ Filter_Input(INPUT_SERVER'SCRIPT_FILENAME');
   
$_REVISION_DATE_    $_VERSION_ 'Revised: '
                       
date("Y-M-d-D $at h:i:s A   ($LTC"FileMTime($_SCRIPT_FILE_PATH_))
                       . 
"&minus;05:00)";
   
$_BROWSER_TAB_TEXT_ "Planetocentric Ephemeris Calculator";
   
$_INTERFACE_TITLE_  "<span style='font-size:15pt;'>Basic&nbsp;Planetocentric&nbsp;Ephemeris&nbsp;Calculator&nbsp;<span style='font-size:12pt;'>and</span>&nbsp;Data&nbsp;Query&nbsp;Portal</span>
   <br>
   <span style='font-size:11pt;'>Built Around The NASA/JPL Horizons API</span><br>
   <span style='font-size:10pt;'>
$_VERSION_ $_AUTHOR_</span><br>";


// ---------------------------------------------------
// Define JavaScript message to display while working.

   
$_COMPUTING_ "TextArea1.innerHTML='W.O.R.K.I.N.G --- This may take several seconds.';";


// -----------------------------------
// DEFINE DEFAULT HOME TIME ZONE DATA.
   
$HomeTZs '-';
   
$HomeTZH '05';
   
$HomeTZM '00';


// --------------------------------------------
// Define 3-letter month name abbreviations for
// use with calendar computations.

   
define('MONTHS''JanFebMarAprMayJunJulAugSepOctNovDec');


/* --------------------------------------------
   Define text for HTML title-text INFO blocks.
   Just hover over the 'INFO' text and a yellow
   menu will be displayed.
*/
   
$TargBodyIDTitleText =
" This can be any NASA/JPL Body ID or Special Query.

 QUERY   RETURNS
-------  ----------------------------------------------
News     Horizons System News + News Backlog
?        Basic information on using the API
?!       Technical information on using the API
*        List of Major Bodies and IDs or Record #s
MB       Same as *
-*       List of Spacecraft, Vehicles, Rovers, etc.
;*       List of Bodies and Record #s (very long list)
com;     List of Comets and Record #s (very long list)
Vesta    Asteroid #4
15;      Asteroid #15    Eunomia (A851 OA)
10       The Sun
301      The Moon
199      The Planet Mercury
599      The Planet Jupiter"
;


// ---------------------------
// Read [SUBMIT] button state.

   
$w Filter_Input(INPUT_POST'SubmitButton');

// Do this ONLY if the [SUBMIT] button was NOT clicked.
   
if (!IsSet($w))
  {

// ----------------------------------------------------------------------
// If this program is being called externally, rather than being executed
// by clicking the [SUBMIT] button, and an active cookie also exists,
// then restore the previously saved interface settings from it. If
// the user leaves and comes back later, all the interface settings
// will be remembered and restored if the cookie was not deleted.

// Try to read cookie content, if any.
   
$Cookie Filter_Input(INPUT_COOKIE$CookieName);

// Check if cookie content exists.
// IsSet === Has content === Not blank or empty.
   
if (IsSet($Cookie))
      {
       
$CookieDataString Filter_Input(INPUT_COOKIE$CookieName);
       list
      (
       
$CentBodyID,
       
$TargBodyID,
       
$StartBCAD,
       
$StartYear,
       
$StartMonth,
       
$StartDay,
       
$StartTime,
       
$TZSign,
       
$TZHH,
       
$TZmm,
       
$StopBCAD,
       
$StopYear,
       
$StopMonth,
       
$StopDay,
       
$StopTime,
       
$StepSize,
       
$DSSTYN,
       
$TimeScale,
       
$DEGorHMS
      
) = Preg_Split("[\|]"$CookieDataString);
      }

   else

// -----------------------------------------------------------
// If there is no previous cookie with the interface settings,
// then set the initial default interface startup values and
// store them in a new cookie.

 
{
   
$CentBodyID '399';
   
$TargBodyID '301';
   
$StartBCAD  'AD';
   
$StartYear  date('Y');
   
$StartMonth date('M');
   
$StartDay   date('01');
   
$StartTime  date('00:00:00');
   
$TZSign     $HomeTZs;
   
$TZHH       $HomeTZH;
   
$TZmm       $HomeTZM;
   
$StopBCAD   'AD';
   
$StopYear   date('Y');
   
$StopMonth  date('M');
   
$StopDay    date('15');
   
$StopTime   date('00:00:01');
   
$StepSize   '1 day'// 'Minutes|Hours|Days|Months|Years'
   
$DSSTYN     'No';    // 'No|Yes'
   
$TimeScale  'UT';    // 'UT|TT'
   
$DEGorHMS   'HMS';   // 'DEG|HMS'

// -------------------------------------------
// Store current interface settings in cookie.

   
$CookieDataString "$CentBodyID|$TargBodyID|$StartBCAD|$StartYear|$StartMonth|$StartDay|$StartTime|$TZSign|$TZHH|$TZmm|$StopBCAD|$StopYear|$StopMonth|$StopDay|$StopTime|$StepSize|$DSSTYN|$TimeScale|$DEGorHMS";
   
SetCookie ($CookieName$CookieDataString$ExpiresIn30Days);
  } 
// End of  else {...}

  
// End of  if (!isset(_POST['SubmitButton']))

/* ------------------------------------------
   READ VALUES OF INTERFACE ARGUMENTS AND SET
   ANY EMPTY ARGUMENTS TO THE DEFAULT VALUES
   GIVEN HERE.
*/

// Read [SUBMIT] button state.
   
$SubmitButton Filter_Input(INPUT_POST'SubmitButton');

// Check [SUBMIT] button state.
// IsSet TRUE Means Clicked.
   
if (IsSet($SubmitButton))
{
// -----------------------------------------------
// Read arguments from interface input text boxes.


/* ------------------------------------------------------------
   Central body should NOT be a small body, such as an asteroid.
   Any (CentBodyID) contains a semicolon ; character, it will
   be automatically removed.
*/
   
$CentBodyID trim(Filter_Input(INPUT_POST'CentBodyID'));
                 if (!
Is_Numeric($CentBodyID)) {$CentBodyID '399';}

   
$CentBodyID Str_Replace(';'''$CentBodyID);

   
$TargBodyID trim(Filter_Input(INPUT_POST'TargBodyID'));
                 if (
$TargBodyID == '') {$TargBodyID '301';}

   
$StartBCAD  trim(Filter_Input(INPUT_POST'StartBCAD'));
   
$StartBCAD  = (substr(StrToUpper($StartBCAD),0,1) == 'B')? 'BC':'AD';

   
$StartYear  trim(Filter_Input(INPUT_POST'StartYear'));
                 if (
$StartYear == '') {$StartYear date('Y');}
   
$StartYear  SPrintF("%04d"$StartYear);

   
$StartMonth trim(Filter_Input(INPUT_POST'StartMonth'));
                 if (
$StartMonth == '') {$StartMonth date('M');}
   
$StartMonth trim(UCFirst(StrToLower(substr($StartMonth,0,3))));
                 if (
Is_Numeric($StartMonth))
                    {
                     
$m IntVal(trim($StartMonth));
                          if (
$m or $m 12) {$m date('m');}
                     
$StartMonth substr(MONTHS3*($m-1), 3);
                    }

   
$StartDay   trim(Filter_Input(INPUT_POST'StartDay'));
                 if (
$StartDay == '') {$StartDay date('d');}
   
$StartDay   SPrintF("%02d"$StartDay);

   
$StartTime  trim(Filter_Input(INPUT_POST'StartTime'));
                 if (
$StartTime == '') {$StartTime date('H:i:s');}
                 
$w HMS_to_Hours($StartTime);
   
$StartTime  Hours_to_HMS($w0);

   
$TZSign trim(Filter_Input(INPUT_POST'TZSign'));
   
$TZSign = ($TZSign == '-')? '-' :'+';

   
$TZHH   trim(Filter_Input(INPUT_POST'TZHH'));
   
$TZmm   trim(Filter_Input(INPUT_POST'TZmm'));

   if (
$TZSign == '' and $TZHH == '' and $TZmm == '')
      {
$TZSign '-'$TZHH '05'$TZmm '00';}

   if (
$TZHH == '') {$TZHH '00';}
   if (
$TZmm == '') {$TZmm '00';}
   if (
$TZSign == '') {$TZSign '+';}
   
$TZHH SPrintF("%02d"$TZHH);
   
$TZmm SPrintF("%02d"$TZmm);
   if (
$TZHH == '00' and $TZmm == '00') {$TZSign '+';}

   
$StopBCAD trim(Filter_Input(INPUT_POST'StopBCAD'));
   
$StopBCAD = (substr(StrToUpper($StopBCAD),0,1) == 'B')? 'BC':'AD';

   
$StopYear trim(Filter_Input(INPUT_POST'StopYear'));
               if (
$StopYear == '') {$StopYear date('Y');}
   
$StopYear  SPrintF("%04d"$StopYear);

   
$StopMonth trim(Filter_Input(INPUT_POST'StopMonth'));
                if (
$StopMonth == '') {$StopMonth date('M');}
   
$StopMonth trim(UCFirst(StrToLower(substr($StopMonth,0,3))));
   if (
Is_Numeric($StopMonth))
      {
       
$m IntVal(trim($StopMonth));
            if (
$m or $m 12) {$m date('m');}
       
$StopMonth substr(MONTHS3*($m-1), 3);
      }

   
$StopDay   trim(Filter_Input(INPUT_POST'StopDay'));
                if (
$StopDay == '') {$StopDay date('d');}
   
$StopDay   SPrintF("%02d"$StopDay);

   
$StopTime  trim(Filter_Input(INPUT_POST'StopTime'));
   
$StopTime Hours_to_HMS(HMS_to_Hours($StopTime), 0);

   
$StepSize  trim(Filter_Input(INPUT_POST'StepSize'));
                 if (
$StepSize == '') {$StepSize '1 hour';}
   
$StepSize .= (Is_Numeric($StepSize))? ' h' '';

   
$TimeScale trim(Filter_Input(INPUT_POST'TimeScale'));
                if (
$TimeScale == '') {$TimeScale 'UT';}
   
$TimeScale = (substr(StrToUpper($TimeScale),0,1) == 'T')? 'TT':'UT';

   
$DSSTYN    trim(Filter_Input(INPUT_POST'DSSTYN'));
   
$DSSTYN    = (substr(StrToUpper($DSSTYN),0,1) == 'Y')? 'Yes':'No';


   
$DEGorHMS trim(Filter_Input(INPUT_POST'DEGorHMS'));
               if (
$DEGorHMS == '') {$DEGorHMS 'HMS';}
   
$DEGorHMS = (substr(StrToUpper($DEGorHMS),0,1) == 'D')? 'DEG':'HMS';

// --------------------------------------
// Store interface arguments in a cookie.

   
$CookieDataString "$CentBodyID|$TargBodyID|$StartBCAD|$StartYear|$StartMonth|$StartDay|$StartTime|$TZSign|$TZHH|$TZmm|$StopBCAD|$StopYear|$StopMonth|$StopDay|$StopTime|$StepSize|$DSSTYN|$TimeScale|$DEGorHMS";
   
SetCookie ($CookieName$CookieDataString$ExpiresIn30Days);
}

// ---------------------------------------------------------
// Turn off Daylight/Summer Time mode if using the TT scale.

   
if ($TimeScale == 'TT') {$DSSTYN 'No';}

// -------------------------------------------------------
// Construct start/stop and date/time strings for the API.

   
$StartDateTime "$StartBCAD $StartYear-$StartMonth-$StartDay  $StartTime";
   
$StopDateTime  "$StopBCAD $StopYear-$StopMonth-$StopDay  $StopTime";

   
$TimeZone  "$TZSign$TZHH:$TZmm";
   
$TZoneText = ($TimeScale == 'UT')? "\nTime Zone Offset       = UT$TimeZone      +Positive = East":"";

// -----------------------------------------------------
// Define a text separator bar of 80 pound # characters.

   
$_BAR_ Str_Repeat('#'80);

// ---------------------------------------------------------
// Set initial uniform width for table alignments in pixels.

   
$TableWidth '800';





// *********************************************************
// BEGIN MAIN COMPUTATIONS HERE IF NO ERRORS DETECTED ABOVE.
// *********************************************************

// -----------------------------------
// Get physical data for (CentBodyID).

   
$CentBodyDataText Get_Object_Data($CentBodyID);

/* -----------------------------------------------------------------------
   Call the main ephemeris function.  In this template,  we are generating
   a planetocentric ephemeris where the eye is at the center of the origin
   body (CentBodyID) looking at the center of the target body (TargBodyID).
   When (CentBodyID == '399'), it returns a geocentric ephemeris.
*/
   
$RawEphemText App_Planetocent_Ephem ($CentBodyID,$TargBodyID,
                                          
$StartDateTime,$StopDateTime,
                                          
$TimeZone,$StepSize,$TimeScale,
                                          
$DSSTYN,$DEGorHMS);


/* ------------------------------------------------------------
   CHECK IF OR NOT AN EPHEMERIS WAS RETURNED.  AN ERROR MESSAGE
   OR SOME OTHER KIND OF TEXT BLOCK MAY POSSIBLY BE RETURNED IF
   A BAD PARAMETER IS DETECTED OR A SPECIAL DIRECTIVE WAS SENT.
*/


/* ----------------------------------------------------
   Check if or not some form of ephemeris was returned.
   All ephemerides begin with special key markers.

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

   Any returned ephemeris CSV table body is found
   between those markers.
*/
   
if (
       
StrPos($RawEphemText,"\$\$SOE\n") !== FALSE
   
and
       
StrPos($RawEphemText,"\$\$EOE\n") !== FALSE
      
)

/* ----------------------------------------------
   If an ephemeris appears to have been returned,
   then start processsing it here.
*/
  
{
   
$OutputText $RawEphemText;

// ---------------------------------------
// Get target body ID name or designation.

   
$TargetBodyNameText Get_Target_Body_Name ($RawEphemText);
  }

else


// --------------------------------------------------------
// If an ephemeris WAS NOT returned, then start processsing
// the returned text here.

{
// ------------------------------
// Strip out unwanted characters.

   
$OutputText Str_Replace('`'''$RawEphemText);

   
$TargetBodyNameText $TargBodyID;
}







// *******************************************
// DROP THROUGH HERE AFTER COMPUTATIONS ABOVE
// TO PRINT OUT THE RESULTS OF THE OPERATIONS.
// *******************************************

   
$TextArea1Text =
"$_BAR_
$_BAR_
                     BASIC PLANETOCENTRIC EPHEMERIS CALCULATOR
                      Built Around  The NASA/JPL Horizons API


------------------------------------------------------------------------------
NASA/JPL CENTRAL BODY ID# WHERE OBSERVER IS LOCATED or SPECIAL DIRECTIVE

$CentBodyID

------------------------------------------------------------------------------
NASA/JPL BODY ID# OF BODY BEING OBSERVED FROM THE BODY SPECIFIED ABOVE

$TargetBodyNameText

------------------------------------------------------------------------------
OBSERVER LOCATION / CALENDAR DATE RANGE / TIME / ZONE / STEP SIZE

Base Time Scale        = 
$TimeScale$TZoneText
Daylight / Summer Time = 
$DSSTYN
Start Date/Time        = 
$StartDateTime
Stop  Date/Time        = 
$StopDateTime
Ephemeris Step Size    = 
$StepSize
------------------------------------------------------------------------------
Angular Output Mode    = 
$DEGorHMS
Reference System       = ICRF

$_BAR_
$_BAR_
       THIS IS THE CENTRAL BODY FROM WHICH THE OBSERVATION IS BEING MADE

$CentBodyDataText
$_BAR_
$_BAR_
    THIS IS THE PLANETOCENTRIC EPHEMERIS FOR THE TARGET BODY BEING OBSERVED
    OR THE RESULT RETURNED FOR A SPECIAL QUERY OR DIRECTIVE:


$OutputText";



// ****************************
// Define TextArea2 text block.

   
$TextArea2Text =
"##############################################################################
    Planetocentric Ephemeris Calculator + Basic NASA/JPL Data Query Portal
                     Built Around the NASA/JPL Horizons API

                     AUTHOR   : Jay Tanner - 
$cYear
                     LANGUAGE : PHP v7.4.9

This program allows you to move around the solar system and get an idea of
what it looks like (numerically) from other perspectives.   The numbers it
returns can be used in extended computations beyond Horizons.

For example, the program can help to answer questions like:

If we were on Mars, where would Earth appear to be among the stars?

If we were on Pluto, how much fainter would the sun appear from there as opposed
to as viewed from Earth?

etc.

Generally, what the program does, is simply generate a basic planetocentric
ephemeris as viewed from the central body.   In the case of the Earth, it
would generate an apparent geocentric ephemeris.  If the central body was
Mars (499), then it would be called an areocentric ephemeris, or an ephem-
eris as computed for the center of Mars instead of Earth. To accomodate any
planet in general, the generic term 'planetocentric' may be used.

We can easily move around the solar system and view it numerically from many
different perspectives very quickly and easily with this program.


================================
USAGE AS A JPL DATA QUERY PORTAL

The program can also be used to look up physical data on the all the planets
and their moons and thousands of asteroids and comets.

It can also look up data on past, present, future proposed missions and also
data on the space telescopes, numerous spacecraft and vehicles such as Rovers.

The program interface emulates some of the features of the NASA/JPL TelNet
connection (telnet://horizons.jpl.nasa.gov:6775) but not all of them.

However, students of NASA/JPL can use this program as a quick solar system
traveling ephemeris calculator and general data lookup tool and learn how
to access the Horizons API via PHP by examining the source code.

And, since it is entirely public domain and the PHP source code is available
on demand and it is a single PHP script, it can also be modified in any way
you wish, depending on your PHP programming skills.

Most of the source code is well-documented to explain its inner workings to
anyone wishing to modify it.

##############################################################################
"
;



// **************************************************************************
// Determine number of text columns and rows to use in the output text areas.
// These values vary randomly according to the text block width and length.
// The idea is to eliminate the need for scroll-bars within the text areas
// or worry as much about the variable dimensions of a text display area.

// --------------------------------------------
// Text Area 1

   
$Text1Cols Max(Array_Map('StrLen'PReg_Split("[\n]"trim($TextArea1Text))));
   if (
$Text1Cols 81) {$Text1Cols 81;}
   
$Text1Rows Substr_Count($TextArea1Text"\n");

// --------------------------------------------
// Text Area 2

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



// ******************************************
// ******************************************
// GENERATE CLIENT WEB PAGE TO DISPLAY OUTPUT

   
print <<< _HTML

<!DOCTYPE HTML>
<HTML>

<head>
<title>
$_BROWSER_TAB_TEXT_</title>

<meta name='viewport' content='width=device-width, initial-scale=0.8'>

<meta http-equiv='content-type' content='text/html; charset=UTF-8'>
<meta http-equiv='pragma'  content='no-cache'>
<meta http-equiv='expires' content='-1'>
<meta name='description' content='xxxxxxxxxxxxxxx'>
<meta name='keywords' content='PHPScienceLabs.com'>
<meta name='author' content='Jay Tanner - https://www.PHPScienceLabs.com'>
<meta name='robots' content='index,follow'>
<meta name='googlebot' content='index,follow'>

<style>

 BODY
{
 background:black; color:white; font-family:Verdana; font-size:12pt;
 line-height:125%;
}



 TABLE
{font-family:Verdana;}



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



 OL
{font-family:Verdana; font-size:12pt; line-height:150%; text-align:justify;}

 UL
{font-family:Verdana; font-size:12pt; line-height:150%; text-align:justify;}

 LI
{font-family:Verdana; line-height:150%;}



 PRE
{
 background:white; color:black; font-family:monospace; font-size:12.5pt;
 font-weight:bold; text-align:left; line-height:125%; padding:6px;
 border:2px solid black; border-radius:8px;
 page-break-before:page;
}



 DIV
{
 background:white; color:black; font-family:Verdana; font-size:11pt;
 font-weight:normal; line-height:125%; padding:6px;
}



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


/* ------------------------------------------
   Styles applied to the input text boxes. */

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

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

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


/* ------------------------------------
   Styles applied to the SUBMIT button. */

 INPUT[type='submit']
{
 background:black; color:gray; font-family:Verdana; font-size:10pt;
 font-weight:bold; border-radius:4px; border:4px solid gray;
 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 GreenYellow;
 padding:3pt;
}




/* -------------------------------------------
   Styles applied to hyper-links.

   Link states below MUST be defined in CSS in
   the following sequence to work correctly:

   :link,  :visited,  :hover,  :active
*/

 A:link
{
 font-size:10pt; background:transparent; color:#8080FF; border-radius:4px;
 font-family:Verdana; font-weight:bold; text-decoration:none;
 line-height:175%; padding:3px; border:1px solid transparent;
}

 A:visited
{
 font-size:10pt; background:transparent; color:DodgerBlue; border-radius:4px;
}

 A:hover
{
 font-size:10pt; background:yellow; color:black; border:1px solid black;
 box-shadow:1px 1px 3px #222222; border-radius:4px;
}

 A:active
{
 font-size:10pt; background:yellow; color:black; border-radius:4px;
}


/* -----------------------------------------
   Style applied to HR (Horizontal Rule). */

 HR {background:red; height:4px; border:0px;}


/* Special styles to create a synthetic title
   attribute tag called 'title-text' which
   does not formally exist. */

[title-text]:hover:after
{
 opacity:1.0;
 transition:all 0.25s ease 0.25s;
 text-align:left;
 visibility:visible;
}

[title-text]:after
{
 opacity:1.0;
 content:attr(title-text);
 text-align:left;
 left:-444%;
 background-color:yellow;
 color:black;
 font-family:monospace;
 font-size:10pt;
 font-weight:bold;
 position:absolute;
 padding:1px 5px 2px 5px;
 white-space:pre;
 border:3px solid red;
 border-radius:8px;
 z-index:1;
 visibility:hidden;
 line-height:130%;
}
[title-text] {position: relative;}


/* ----------------------------------------------------------------------
   These styles change the highlighting text colors to 'black/yellow'. */

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

</style>

</head>

<body>

<!-- Define container form --->
<form name="form1" method="post" action="">

<!-- Define main page title/header. --->
<table width="
$TableWidth" align="top" border="0" cellspacing="1" cellpadding="3">
<tr><td colspan="99" style='color:white; background-color:#000066; border:2px solid white; border-radius:8px 8px 0px 0px;'>
$_INTERFACE_TITLE_</td></tr>
</table>



<!-- Target Body ID input --->
<table width="
$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr><td style="background:LightYellow; line-height:175%;" colspan='99'>
<b>NASA/JPL Target Body ID # or Record # of Body Being Observed</b><br><span style='font-weight:normal'>or Enter a Special Data Query or Command Directive</span><br><input name="TargBodyID"  type="text" value="
$TargBodyID"  size="50" maxlength="81" title=' Ideally, this should be the Body ID # or Record # of the Observed Body. '><br><span title-text="$TargBodyIDTitleText"> <b>INFO</b> </span>
</td></tr>
</table>


<!-- Central Body ID input --->
<table width="
$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr><td style="background:LightCyan; line-height:175%;" colspan='99'>
<b>NASA/JPL Central Body ID# Where Observer is Located</b><br><span style='font-weight:normal'>This Could Be Earth or Another Planet (Excluding Asteroids)</span><br><input name="CentBodyID"  type="text" value="
$CentBodyID"  size="16" maxlength="16" title=' Use the ID# or Record # to Specify the Central Body of Observation. \n Default = 399 (Earth) '><br>
</td></tr>
</table>




<!-- Special Options --->
<table width="
$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3"><tr>

<td width='25%' style='background:#EFEFEF;' title=' D = DEG = Angles in Decimal Degrees \n\n H = HMS = Angles in HMS/DMS format '>Output DEG or HMS<br>
<input name="DEGorHMS" type="text" value="
$DEGorHMS" size="3" maxlength="3">
</td>

<td style="background:#EFEFEF; text-align:center; line-height:175%;" colspan='3' title=' No = Use Standard Time (Default) \n\n Yes = Use Daylight/Summer Time '>
Daylight/Summer Time?<br><input name='DSSTYN' type='text' value="
$DSSTYN" size='4' maxlength='3'>
</td>

<td width='25%' style='background:#EFEFEF;' title=" ICRF (International Celestial Reference Frame) ">Reference System<br>
<input name="RefSystem" type="text" value="ICRF"  size="5" maxlength="5" ReadOnly style='color:white; background:black;'>
</td>

</tr>
</table>



<!-- Start Date/Time/Time Zone/Time Scale --->
<table width="
$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr>
<td width='50%' style="text-align:left; line-height:175%;">
<b>START:</b> Calendar Date and Time<br>
<input name="StartBCAD"  type="text" value="
$StartBCAD"  size="3" maxlength="2" title=' BC|AD Era (AD = Automatic Default Era'>
<input name="StartYear"  type="text" value="
$StartYear"  size="5" maxlength="4" title=' Year From BC 9999  to  AD 9999 '>
<input name="StartMonth" type="text" value="
$StartMonth" size="4" maxlength="3" title=' The Month Can Be a Number From 1 to 12 \n or \n A 3-Letter Abbreviation (Jan  to  Dec) \n'>
<input name="StartDay"   type="text" value="
$StartDay"   size="3" maxlength="2">&nbsp;&nbsp;
<input name="StartTime"  type="text" value="
$StartTime"  size="8" maxlength="8" title=' Enter Start Time as  HH : mm : ss  (00 to 24h)'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span title=" Enter Time Zone offset from UT as  &plusmn;HH : mm&nbsp; \n\n +Positive Time Zone = East \n\n Time Zone is ignored if using TT scale.">Time Zone </span>
<input name="TZSign"     type="text" value="
$TZSign"     size="2" maxlength="1" style='text-align:right;'><input name="TZHH"       type="text" value="$TZHH"       size="2" maxlength="2" title=''><b>:</b><input name="TZmm"       type="text" value="$TZmm"       size="2" maxlength="2" title=''>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span title=' Base Time Scale \n\n UT = Universal Time (Default)\n TT = Terrestrial Dynamical Time '>Time Scale </span>
<input name='TimeScale'  type='text' value="
$TimeScale"  size='2' maxlength='2' title=' The UT Scale Mode Includes the Time Zone Offset. \n\n The Time Zone Offset is Ignored When Using the TT Scale.'>
</td>
</tr>
</table>

<!-- Stop Date/Time --->
<table width="
$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr title=" This is the ephemeris Stop Date and Time \n\n For an ephemeris table, the Stop Date/Time must be \n LATER than the Start Date/Time. \n\n For a SINGLE Date/Time, set the Step Size to beyond \n the Stop Date/Time so that only the Start Date/Time \n will be used. ">
<td width='50%' style="background:white; text-align:left; line-height:180%;">
<b>STOP:</b> Calendar Date and Time<br>
<input name="StopBCAD"  type="text" value="
$StopBCAD"  size="3" maxlength="2" title=' BC|AD Era (AD = Automatic Default Era'>
<input name="StopYear"  type="text" value="
$StopYear"  size="5" maxlength="4" title=' Year From BC 9999  to  AD 9999 '>
<input name="StopMonth" type="text" value="
$StopMonth" size="4" maxlength="3" title=' The Month Can Be a Number From 1 to 12 \n or \n A 3-Letter Abbreviation (Jan  to  Dec) \n'>
<input name="StopDay"   type="text" value="
$StopDay"   size="3" maxlength="2">&nbsp;&nbsp;
<input name="StopTime"  type="text" value="
$StopTime"  size="8" maxlength="12" title=' Enter Stop Time as  HH : mm : ss '>
</td>

<!-- Ephemeris Step Size --->
<td style='background:white; text-align:center; line-height:180%;' colspan='3'  title=' This is the time interval between the ephemeris computations. \n\n Step Units :  m = Minutes | h = Hours | d = Days | mo = Months | y = Years '>Step Size<br>
<input name="StepSize"  type="text" value="
$StepSize"  size="16" maxlength="16" title=" For a SINGLE Date/Time,  set the Step Size to beyond the Stop Date/Time \n so that only the Start Date/Time will be used. \n\n Step Units :  m = Minutes | h = Hours | d = Days | mo = Months | y = Years ">
</td>

</tr>
</table>




<!-- Define [SUBMIT] button --->
<table width="
$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr><td colspan="99" style="text-align:center; background-color:black;"><input type="submit" name="SubmitButton" value=" S U B M I T " OnClick="
$_COMPUTING_"></td></tr>
</table>


<!-- Source Code View and/or Download Link. --->
<table width="
$TableWidth">
<tr>
<td colspan="99" style="font-size:10pt; color:GreenYellow; background:black; text-align:center;">
<b><a href='View-Source-Code.php' target='_blank'>View/Copy PHP Source Code</a></b>
</td>
</tr>
</table>


<!-- Define TextArea1 --->
<table width="
$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr>
<td colspan="99" style="text-align:left; color:yellow; background-color:black;"><br><b> Double-Click Within Text Area to Select ALL Text </b><br>
<textarea ID="TextArea1" name="TextArea1" style="color:black; background:white; padding:6px; border:2px solid white;" cols="
$Text1Cols" rows="$Text1Rows" ReadOnly OnDblClick="this.select();" OnMouseUp="return true;">
$TextArea1Text
</textarea>
</td>
</tr>
</table>


<!-- Define TextArea2 --->
<table width="
$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr>
<td colspan="99" style="text-align:center; color:yellow; background:black;"><br>
<b> Double-Click Within Text Area to Select ALL Text </b><br>
<textarea ID="TextArea2" name="TextArea2" style="color:black; background:white; padding:6px;" cols="
$Text2Cols" rows="$Text2Rows" ReadOnly OnDblClick="this.select();" OnMouseUp="return true;">
$TextArea2Text
</textarea>
</td>
</tr>
</table>

<!-- Page footer --->
<table width="666" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr>
<td colspan="99" style="color:GreenYellow; background:black; text-align:center;">PHP Program 
$_AUTHOR_<br>
<span style="color:silver; background:black;">
$_REVISION_DATE_</span>
</td>
</tr>
</table>

</form>
<!-- End of container form --->


<!-- Extra bottom scroll space --->
<br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br>

</body>
</HTML>



_HTML;



/* ---------------------------------------------
   SPECIAL BUILT IN SUB-UTILITIES SO NO EXTERNAL
   INCLUDES ARE NECESSARY UNLESS DESIGNED THAT
   WAY ON PURPOSE. THIS ALLOWS THE PROGRAM TO
   BE CONSTRUCTED AS A SINGLE FILE INSTEAD OF
   MULTIPLE FILES FOR CONVENIENCE.
*/


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

   Generic. No special error checking is done.
   ###########################################################################
*/

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


/* ----------------------------------------------------------------------
   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.
*/
   
$hh $mm $ss 0;

   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 simply returns the basic physical data for any given body ID
   or space mission cataloged by NASA/JPL and also execute special Horizons
   queries.

   The body ID can be a unique name, NASA Body ID # or an SPK ID# DES=xxxxxxx
   or a special info query or directive.

   Generic.  No special error checking is done.
   ###########################################################################
*/

   
function Get_Object_Data ($BodyID)
{
// ===========================================================================
// Construct query URL for the NASA/JPL Horizons API.

   
$Command URLEncode(trim($BodyID));

   
$From_Horizons_API =
   
"https://ssd.jpl.nasa.gov/api/horizons.api?format=text" .
   
"&COMMAND='$Command'" .
   
"&MAKE_EPHEM='NO'"    .
   
"&OBJ_DATA='YES'"     ;

// ===========================================================================
// Send query to Horizons API to obtain the basic physical data for the given
// body ID as a single long text string with formatting control codes.

   
$w File_Get_Contents($From_Horizons_API);

   return 
$w;

// End of  Get_Object_Data(...)






/*
   ###########################################################################
   This function extracts and returns ONLY target body text line from a raw
   ephemeris.

   The target body text always begins with the characters:
   'Target body name:'

   Generic.  No special error checking is done other than if the name cannot
   be resolved, an empty string ('') will be returned.
   ###########################################################################
*/

   
function Get_Target_Body_Name ($RawEphemText)
{
   
$T trim($RawEphemText);
   
$i StrPos($T"\$\$SOE\n");  if ($i === FALSE) {return '';}
   
$i StrPos($T'Target body name:');
   
$j StrPos($T'}');
   
$w PReg_Replace("/\s+/"" "trim(substr($T$i$j-$i+1)));
   
$w trim(Str_Replace('Target body name:',''$w));
   
$i StrPos($w'}') + 1;

   if (
StrPos($w'API SOURCE: NASA/JPL Horizons API Introduction') !== FALSE)
      {return 
'';}

   return 
substr($w0$i);
}






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

   
function Hours_to_HMS ($Hours$Decimals=3)
{
   
$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 (...)







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

   The ephemeris quantities returned are listed
   below, but they can easily be changed:

   Date and Time UT/TT
   Sp (Solar Presence Symbol)
   Lp (Lunar Presence Symbol)
   Right Ascension ICRF
   Declination ICRF
   Distance (delta)
   Radial Velocity (deldot)    -Neg = Towards | +Pos = Away
   Angular Diameter in arcseconds
   Apparent Visual Magnitude
   S-brt (Brightness of 1 Square Arcsecond of Disc)
   Constellation ID (3-letter abbreviation based on constellation name)

   S-O-T (Sun-Observer-Target angle) used to find the simple phase angle.
   /r (/L = Leading the Sun  or  /T = Trailing the Sun)
   ###########################################################################
*/

   
function App_Planetocent_Ephem ($CentBodyID,$TargBodyID,$StartDateTime,
                                   
$StopDateTime,$TimeZone,$StepSize,
                                   
$TimeScale,$DSSTYN,$DEGorHMS)
{
// ===========================================================================
// Read and parse function arguments.

   
$CentBodyID trim($CentBodyID);

   
$TargBodyID  trim($TargBodyID);
   
$Command URLEncode($TargBodyID);

   
$TimeScale substr(StrToUpper(trim($TimeScale)),0,1);
   
$TimeScale = ($TimeScale == 'T')? 'TT':'UT';

   
$DSSTYN substr(StrToUpper(trim($DSSTYN)),0,1);
   
$DSSTAdj = ($DSSTYN == 'N')? 0:1;

/* ------------------------------------------------------
   Adjust for Daylight/Summer Time. This assumes that the
   Time Zone is given in the standard '+-HH:mm' format.
*/
   
list($TZHH$TZmm) = PReg_Split("[\:]"$TimeZone);
   
$TZSign substr($TZHH,0,1);
   
$TZSignVal = ($TZSign == '-')? -1:1;
   
$TZHours $TZSignVal * (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";

/* ----------------------------------------------------------
   Account for special cases of the moons of certain planets.
   Set distance (range) units = 'KM' and add SOT angle
   (Quantity #23) for finding the simple moon phase angles.
*/
   
$AUorKM 'AU';  $SOT '';

// ----------------------------
// Account for our moon (Luna).

   
if ($CentBodyID == '399' and $TargBodyID == '301')
      {
$AUorKM 'KM';  $SOT ',23';}



// ----------------------------
// Account for 2 moons of Mars.

   
if (
       
$CentBodyID == '499'
   
and
      (
       
$TargBodyID == '401'
    
or $TargBodyID == '402'
      
)
      )
      {
$AUorKM 'KM';  $SOT ',23';}



// -----------------------------
// Account for 5 moons of Pluto.

   
if (
       
$CentBodyID == '999'
   
and
      (
       
$TargBodyID == '901'
    
or $TargBodyID == '902'
    
or $TargBodyID == '903'
    
or $TargBodyID == '904'
    
or $TargBodyID == '905'
      
)
      )
      {
$AUorKM 'KM';  $SOT ',23';}



// -------------------------------------------------------
// Construct full query URL for the NASA/JPL Horizons API.

   
$From_Horizons_API =
   
"https://ssd.jpl.nasa.gov/api/horizons.api?format=text" .
   
"&COMMAND='$Command'"                       .
   
"&OBJ_DATA='YES'"                           .
   
"&MAKE_EPHEM='YES'"                         .
   
"&EPHEM_TYPE='OBSERVER'"                    .
   
"&CAL_FORMAT='CAL'"                         .
   
"&CAL_TYPE='GREGORIAN'"                     .
   
"&REF_SYSTEM='ICRF'"                        .
   
"&RANGE_UNITS='$AUorKM'"                    .
   
"&ANG_FORMAT='$DEGorHMS'"                   .
   
"&CENTER='500@$CentBodyID'"                 .
   
"&TIME_DIGITS='SECONDS'"                    .
   
"&TIME_ZONE='$TimeZone'"                    .
   
"&START_TIME='$StartDateTime%20$TimeScale'" .
   
"&STOP_TIME='$StopDateTime'"                .
   
"&STEP_SIZE='$StepSize'"                    .
   
"&EXTRA_PREC='YES'"                         .
   
"&CSV_FORMAT='YES'"                         .
   
"&QUANTITIES='2,20,13,9,29$SOT'"            ;
// ===========================================================================


/* ---------------------------------------------------------------------------
   Send query to Horizons API to obtain the apparent planetocentric ephemeris
   data for the given target body ID in plain-text CSV (spreadsheet) format to
   be subsequently parsed.

   NOTE:
   It is possible that an error message or some other type of content may be
   returned instead of an ephemeris. This should be taken into account while
   parsing.

   --------------------------------------------------------------------
   All ephemeris CSV tables will be contained between two special tags:

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

   Absent these tags, the returned text is NOT an ephemeris table and must
   be processed as something else.
*/

   
return Str_Replace(",\n"" \n"trim(File_Get_Contents($From_Horizons_API)));

// End of  App_Planetocent_Ephem (...)






// END OF PROGRAM



?>