<?php

/*
   ###########################################################################
   RISE/TRANSIT/SET TIMES CALCULATOR AND NASA/JPL DATA QUERY PORTAL
   WITH 30-DAY COOKIE.

   The ICRF (International Celestial Reference Frame) is used used.
   The B1950/FK4 system does NOT apply to topocentric coordinates,
   but ONLY to epochal astrometric coordinates.

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

   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.

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

   NOTE:
   All ephemerides are generated as plain-text CSV data tables.
  ###########################################################################
*/


/* -----------------------------
   Is OB a clinic down the hall?
*/
   
ob_start();

// -----------------------------------
// Get current year from system clock.

   
$cYear date('Y');

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

   
$CookieName 'Rise-Transit-Set-Times-Calculator';
   
$ExpiresIn30Days time() + 30*86400;

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

   
$_AUTHOR_  "by Jay Tanner - $cYear";
   
$_VERSION_ 'v1.00 - ';
          
$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-F-d-l $at h:i:s A   ($LTC"FileMTime($_SCRIPT_FILE_PATH_))
                       . 
"&minus;05:00)";
   
$_BROWSER_TAB_TEXT_ "Rise | Transit | Set Times Calculator";
   
$_INTERFACE_TITLE_  "<span style='font-size:15pt;'>Rise&nbsp;|&nbsp;Transit&nbsp;|&nbsp;Set Times Calculator</span>&nbsp;
   <span style='font-size:13pt;'><br>&plus;<br>General Data Query Portal</span>
   <br><br>
   <span style='font-size:11pt;'>Built Around the NASA/JPL Horizons API </span><br>
   <span style='font-size:9pt;'>
$_VERSION_ $_AUTHOR_</span>";

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

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

// --------------------------------------
// Special GLOBALs just for this program.

   
GLOBAL $TargetBodyNameText$xDSSTText$MoonPhaseTableText;

// ----------------------------------
// Define default home location data.

   
$HomeLoc 'Central New York State, USA';
   
$HomeLon '-76.862737';
   
$HomeLat '+42.904788';
   
$HomeAlt '+150'// Meters

// -----------------------------------------
// Define 3-letter month name abbreviations.

   
define('MONTHS''JanFebMarAprMayJunJulAugSepOctNovDec');

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

   
$_BAR_ Str_Repeat('#'80);


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

QUERY   RETURNS
news    Horizons On-Line System 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 * (Also used as a search wildcard)
-*      List of Spacecraft, Vehicles, Rovers, etc.
com;    List of Comets and Record #s (very long list)&nbsp;
1;      The Asteroid Ceres (Asteroid #1)
Vesta   The Asteroid Vesta (Asteroid #4)
10      The Sun  (Sol)
301     The Moon (Luna)
199     The Planet Mercury
499     The Planet Mars
etc."
;

// ----------------------------------------------------
// 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 space(s) or empty

   
if (IsSet($Cookie))
      {
       
$CookieDataString Filter_Input(INPUT_COOKIE$CookieName);
       list
      (
       
$BodyID,
       
$StartYear,
       
$StartMonth,
       
$StartDay,
       
$TZSign,
       
$TZHH,
       
$TZmm,
       
$StopYear,
       
$StopMonth,
       
$StopDay,
       
$DSSTYN,
       
$LocName,
       
$LonDeg,
       
$LatDeg,
       
$AltMet
      
) = 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.

 
{
   
$BodyID     '301';
   
$StartYear  date('Y');
   
$StartMonth date('M');
   
$StartDay   date('d');

   
$TZSign     '-';
   
$TZHH       '05';
   
$TZmm       '00';
   
$StopYear   date('Y');
   
$StopMonth  date('M');
   
$StopDay    date('d');

   
$DSSTYN     'No';   // 'Yes|No'

   
$LocName    $HomeLoc;
   
$LonDeg     $HomeLon;
   
$LatDeg     $HomeLat;
   
$AltMet     $HomeAlt;

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

   
$CookieDataString "$BodyID|$StartYear|$StartMonth|$StartDay|$TZSign|$TZHH|$TZmm|$StopYear|$StopMonth|$StopDay|$DSSTYN|$LocName|$LonDeg|$LatDeg|$AltMet";
   
SetCookie ($CookieName$CookieDataString$ExpiresIn30Days);
  } 
// End of  else {...}

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


// ------------------------------------------
// Read values of interface argument(s) and
// set any empty arguments to default values.

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

   
$BodyID     trim(Filter_Input(INPUT_POST'BodyID'));
                 if (
$BodyID == '') {$BodyID '*';}

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

   
$TZSign trim(Filter_Input(INPUT_POST'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 '+';}

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


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

   
$LocName trim(Filter_Input(INPUT_POST'LocName'));
              if (
$LocName == '') {$LocName $HomeLoc;}

// ----------------------------------------------------
// Get longitude input as decimal degrees or DMS string
// and convert to decimal degrees.
   
$LonDeg trim(Filter_Input(INPUT_POST'Longitude'));
             if (
$LonDeg == '') {$LonDeg $HomeLon;}
   
$LonDeg DMS_to_Degrees($LonDeg9);

// ---------------------------------------------------
// Get latitude input as decimal degrees or DMS string
// and convert to decimal degrees.

   
$LocName trim(Filter_Input(INPUT_POST'LocName'));
              if (
$LocName == '') {$LocName $HomeLoc;}

   
$LatDeg trim(Filter_Input(INPUT_POST'Latitude'));
             if (
$LatDeg == '') {$LatDeg $HomeLat;}
   
$LatDeg DMS_to_Degrees($LatDeg9);

   
$AltMet   trim(Filter_Input(INPUT_POST'AltMet'));
               if (
$AltMet == '') {$AltMet '+0';}
   
$AltMet   SPrintF("%+d"$AltMet);



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

   
$CookieDataString "$BodyID|$StartYear|$StartMonth|$StartDay|$TZSign|$TZHH|$TZmm|$StopYear|$StopMonth|$StopDay|$DSSTYN|$LocName|$LonDeg|$LatDeg|$AltMet";
   
SetCookie ($CookieName$CookieDataString$ExpiresIn30Days);
}

// -------------------------------------------------------------
// Construct Start/Stop Dates and Time Zone strings for the API.
// Must be perfectly formatted or an error message will result.

   
$StartDate "$StartYear-$StartMonth-$StartDay";
   
$StopDate  "$StopYear-$StopMonth-$StopDay";
   
$TimeZone  "$TZSign$TZHH:$TZmm";

// ----------------------------------------
// Define Daylight Saving/Summer Time text.

   
$xDSSTText = ($DSSTYN == 'Yes')? 'Daylight/Summer Time' 'Standard Time';

// -----------------------------
// Set initial uniform width for
// tables alignment in pixels.

   
$TableWidth '800';

// ------------------------------
// Initialize output Text Areas.

   
$OutputText1 $OutputText2 '';


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

// ------------------------------------
// Get the raw RTS ephemeris CSV table.

   
$RawEphemText R_T_S_Times ($BodyID,$StartDate,$StopDate,$TimeZone,
                                
$DSSTYN,$LonDeg,$LatDeg,$AltMet);

   
$RawEphemText HTMLEntities($RawEphemText);


/* -----------------------------------------------------------
   Check for non-ephemeris data.  If there is no $$SOE marker,
   then no ephemeris table body was returned and it was some
   other text.
*/
   
$OutputText1  '';

// --------------------------
// Check for ephemeris body.

   
$ii StrPos($RawEphemText'$$SOE');

/* ----------------------------------------------------
   If an ephemeris table was returned, then extract the
   required text blocks from the raw ephemeris data.

   NOTE: The @ symbol supresses notices when the moon is
   not the selected object.
*/

   
if ($ii !== FALSE)
  {
     
$TargetBodyNameText Get_Target_Body_Name ($RawEphemText);


// ------------------------------
// Make the RTS table text block.

   
$RTSTableText = @Make_RTS_Table($RawEphemText);


// ----------------------------------------
// Check for spacecraft or special symbols.

   
if ($BodyID == '?!') {$TargetBodyNameText $RTSTableText '';}
   if (
StrPos($RawEphemText'spacecraft') !== FALSE) {$TargetBodyNameText $BodyID;}
   if (
StrPos($RTSTableText'No  ephemeris  meets  criteria.') !== FALSE) {$RTSTableText 'No ephemeris data.';}
   if (
StrPos($RTSTableText'Horizons On-Line System News') !== FALSE) {$TargetBodyNameText $RTSTableText '';}

/* --------------------------------------------
   Define table of lunar phases to be displayed
   if the target body is the Moon (310 Luna).
   Othersise, it will be ignored.
*/

   
$MoonPhaseTableText '';

   if (
$BodyID == '301')
      {
       
$MoonPhaseTableText =
"
                     ========================================
                      Phase_Deg    General_Phase_Description
                     ===========  ===========================
                          0&deg;      New Moon
                         45       Waxing Crescent Moon
                         90       First Quarter Moon
                        135       Waxing Gibbous Moon
                        180       Full Moon
                        225       Waning Gibbous Moon
                        270       Last Quarter Moon
                        315       Waning Crescent Moon
                        360/0     New Moon"
;
}





/* ------------------------------------------------------
   Combine all required segment text blocks into a single
   text block.
*/
   
$OutputText2 $RawEphemText;

   
$OutputText1 =
"$RTSTableText
$MoonPhaseTableText
$_BAR_
"
;


  }


else

// -----------------------------------------------------------------
// Do this if something other than an ephemeris table is returned.

  
{
   
$TargetBodyNameText "$BodyID\n";
   
$OutputText2 .= "NASA/JPL BODY ID:\n" $TargetBodyNameText "\n";
   
$OutputText2 .= $RawEphemText;
  }


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

   
$TextArea1Text =
"$_BAR_
                       RISE | TRANSIT | SET TIMES  CALCULATOR
                                        and
                         General NASA/JPL Data Query Portal


NASA/JPL BODY ID: 
$BodyID

$TargetBodyNameText

---------------
FOR OBSERVER AT

Location Name or Label  =  
$LocName
GPS Longitude           =  
$LonDeg&deg;   +Positive = East
GPS Latitude            =  
$LatDeg&deg;
Sea Level Altitude      =  
$AltMet m

Time Zone               =  UT
$TimeZone      +Positive = East
Start Date              =  
$StartDate
Stop  Date              =  
$StopDate
Daylight/Summer Time    =  
$DSSTYN

$_BAR_
$OutputText1";



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


   
$TextArea2Text "$OutputText2";



// **************************************************************************
// 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 - Default = At least 80 columns.

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

// --------------------------------------------
// Text Area 2 - Default = At least 80 columns.

   
$Text2Cols Max(Array_Map('StrLen'PReg_Split("[\n]"trim($TextArea2Text))));
                if (
$Text2Cols 80) {$Text2Cols 80;}
   
$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%;
}



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

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

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



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




/* -------------------------------------------
   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;
}






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


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

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


::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>





<!-- 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 Body ID#,&nbsp; Record #,&nbsp;or Special Query or Command</b><br>
<input name="BodyID"  type="text" value="
$BodyID"  size="65" maxlength="81"><br>
<span title-text="
$BodyIDTitleText"> <b>INFO</b> </span>
</td></tr>
</table>





<!-- Optional Location Name or Label --->
<table width="
$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr>
<td style='background:#E0FFE0;' title=' This can be any location name or label. \n'>Optional Location Name or Label<br><input name="LocName"  type="text" value="
$LocName" size="65" maxlength="64"></td>
</tr>
</table>



<!-- Longitude/Latitude/Altitude --->
<table width="
$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr title=" IMPORTANT:\n Make sure that the given geographic coordinates are&nbsp; \n  within the given time zone or computed times may \n not be accurate.">

<td width='33%' style='background:LightCyan; line-height:180%;'>GPS Longitude &nbsp;&nbsp;&plus;Pos = E<br>
<input name="LonDeg"  type="text" value="
$LonDeg" size="15" maxlength="14" title=' GPS Longitude in Decimal Degrees\nor as\n Deg Min Sec   Separated by Spaces \n'>
</td>

<td width='33%' style='background:LightCyan; line-height:180%;'>GPS Latitude &nbsp;&nbsp;&plus;Pos = N<br>
<input name="LatDeg"   type="text" value="
$LatDeg"  size="15" maxlength="14" title=' GPS Latitude in Decimal Degrees\nor as\n Deg Min Sec   Separated by Spaces \n'>
</td>

<td style='background:LightCyan; line-height:180%;'>&plusmn; Sea Level Altitude<br>
<input  name="AltMet"  type="text" value="
$AltMet"  size="9" maxlength="8" title=' Altitude in meters relative to sea level. '> meters
</td>

</tr>
</table>



<!-- Start Date/Time Zone --->
<table width="
$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr>
<td width='50%' style="text-align:left; line-height:175%;" title=" This is the ephemeris Start Date, Time Zone \n and Daylight Saving | Summer Time Choice. \n">
<b>START:</b> Calendar Date<br>
<input name="StartYear"  type="text" value="
$StartYear"  size="5" maxlength="4">
<input name="StartMonth" type="text" value="
$StartMonth" size="4" maxlength="3">
<input name="StartDay"   type="text" value="
$StartDay"   size="3" maxlength="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Time Zone
<input name="TZSign"     type="text" value="
$TZSign"     size="1" maxlength="1">
<input name="TZHH"       type="text" value="
$TZHH"       size="2" maxlength="2" title=''><b>&nbsp;:</b>
<input name="TZmm"       type="text" value="
$TZmm"       size="2" maxlength="2" title=''>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Daylight/Summer Time ?&nbsp;<input name='DSSTYN' type='text' value="
$DSSTYN" size='4' maxlength='3' title=' No = Use Standard Time (Default) \n\n Yes = Use Daylight/Summer Time '>
</td>
</tr>
</table>





<!-- Stop Date/Time --->
<table width="
$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr title=" This is the ephemeris table Stop Date \n\n For an ephemeris table, the Stop Date\n must be LATER&nbsp; than the Start Date. \n">
<td width='50%' style="background:white; text-align:left; line-height:180%;">
<b>STOP:</b> Calendar Date<br>
<input name="StopYear"  type="text" value="
$StopYear"  size="5" maxlength="4">
<input name="StopMonth" type="text" value="
$StopMonth" size="4" maxlength="3">
<input name="StopDay"   type="text" value="
$StopDay"   size="3" maxlength="2">
</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:center; 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;



/*
   ###########################################################################
   This function returns a topocentric ephemeris of rise/transit/set times
   from the NASA/JPL Horizons API. A topocentric ephemeris takes the location
   and altitude of the observer into account. Standard Earth refraction model
   is applied.

   DSSTYN = Means Daylght Saving/Summer Time 'Yes|No'
            'No'  = Use Standard Clock Time (Default)
            'Yes' = Use Daylight Saving/Summer Clock Time

   Angles are expressed in degrees.


   The test/demo ephemeris quantities returned are listed below, but more can
   easily be added.  The only extra feature added here applies to the Moon,
   appending two more columns (8,9) to the table.


COLUMN  CONTENT
======  =======================================================================
  1     Date and Time String (UT or ZONE)
  2     Sp (Solar Presence Symbol)
  3     Lp (Lunar Presence Symbol)
  4     Azimuth Angle (Compass Direction Reckoned Clockwise From North = 0)
  5     Elevation Angle (Relative to Horizon = 0 degrees)
  6     Apparent Visual Magnitude or Brightness Level
  7     S-brt (Brightness of 1 Square Arcsecond of Visible Disc)

  8     S-O-T (Sun-Observer-Target angle) used to find the lunar phase.
  9     /r (/L = Moon Leading the Sun  or  /T = Moon Trailing the Sun)


   NO DEPENDENCIES
   ###########################################################################
*/
   
function R_T_S_Times ($BodyID,$StartDate,$StopDate,$TimeZone,$DSSTYN,
                         
$LonDeg,$LatDeg,$AltMet)
{
// ===========================================================================
// Construct query URL for the NASA/JPL Horizons API.

   
$BodyID     trim($BodyID);
   
$Command    URLEncode($BodyID);
   
$AltKm      trim($AltMet) / 1000;

/* -----------------------------------------------------------
   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.
*/
   
$DSSTYN  substr(StrToUpper(trim($DSSTYN)),0,1);
   
$DSSTAdj = ($DSSTYN == '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";

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

   
if ($BodyID == '301') {$AUorKM 'KM';  $SOT ',23';}

// -------------------------------------
// Construct URL for Horizons API query.

   
$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'"                        .
   
"&REF_SYSTEM='ICRF'"                       .
   
"&APPARENT='REFRACTED'"                    .
   
"&CENTER='COORD@399'"                      .
   
"&COORD_TYPE='GEODETIC'"                   .
   
"&SITE_COORD='$LonDeg,$LatDeg,$AltKm'"     .
   
"&TIME_DIGITS='MINUTES'"                   .
   
"&TIME_ZONE='$TimeZone'"                   .
   
"&START_TIME='$StartDate%2000:00:00%20UT'" .
   
"&STOP_TIME='$StopDate%2023:59:59'"        .
   
"&STEP_SIZE='1 MINUTE'"                    .
   
"&EXTRA_PREC='YES'"                        .
   
"&R_T_S_ONLY='TVH'"                        .
   
"&QUANTITIES='4,9$SOT'"                    .
   
"&CSV_FORMAT='YES'"                        ;
// ============================================

/* -----------------------------------------------------------------------
   Send query to Horizons API to obtain the apparent topocentric ephemeris
   data we need for the R-T-S times of the given body ID.
*/
   
$RTS Str_Replace(",\n"" \n"File_Get_Contents($From_Horizons_API));

/* ----------------------------------------------------------------------
   If no ephemeris data is found, then return an empty string as an error
   state indicator.
*/
   
if (StrPos($RTS'$$SOE') === FALSE) {return $RTS;}

/* -------------------------------------------------------------------------
   DO NOT TRIM HERE BECAUSE INITIAL CHARACTER MAY BE A SPACE INDICATING AN
   'AD' YEAR.  'BC' YEARS BEGIN WITH A SINGLE 'b' AS THE INITIAL CHARACTER.
*/
   
return $RTS;

// End of  R_T_S_Times(...)






/* #########
   UTILITIES
   #########
*/

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

   
function DMS_to_Degrees ($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_Degrees(...)





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

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

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

   
function Get_Target_Body_Name ($RawEphemText)
{
   
$T trim($RawEphemText);
   
$i StrPos($T'$$SOE');  if ($i === FALSE) {return $i;}
   
$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;
   return 
substr($w0$i);
}



/*
   ###########################################################################
   This function constructs a complete Rise/Transit/Set Table from a raw RTS
   ephemeris as returned by the API.
   ###########################################################################
*/

   
function Make_RTS_Table ($RawRTSCSVText)
{
   GLOBAL 
$TargetBodyNameText$xDSSTText$MoonPhaseTableText;

   
$T trim($RawRTSCSVText);

// -----------------------------------------
// Extract only the CSV ephemeris text block
// from the given raw returned RTS text.

   
$i StrPos($T'$$SOE');

   if (
$i === FALSE) {return '';}

   
$j StrPos($T'$$EOE');
   
$T trim(substr($T$i+5$j-$i-5));

   
$wArray PReg_Split("[\n]"$T);
   
$wCount count($wArray);
   
$wTable '';

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

   list
  (
   
$DateTimeStr,
   
$P,
   
$RorTorS,
   
$AzimDeg,
   
$ElevDeg,
   
$VisMag,
   
$Sbrt,
   
$SOT,
   
$LorT
  
) = PReg_Split("[,]"$CurrLine);

  
$DateTimeStr Str_Replace(' ''  '$DateTimeStr);

  
$ElevDeg trim($ElevDeg);
  
$LorT    trim($LorT);
  
$VisMag  trim($VisMag);
  
$SOT     trim($SOT);

  
$AzimDeg SPrintF("% 7.3f",  trim($AzimDeg));
  
$ElevDeg SPrintF("% +7.3f"trim($ElevDeg));

  if (
$RorTorS == 'r') {$RorTorS 'Rise   '$ElevDeg '   -   ';}
  if (
$RorTorS == 't') {$RorTorS 'Transit';}
  if (
$RorTorS == 's') {$RorTorS 'Set    '$ElevDeg '   -   ';}

  if (
$LorT == '/L' or $LorT == '/T')
     {
      
$PhaseAng = ($LorT == '/L')? 360-trim($SOT) : trim($SOT);
      
$xPh1 'Phase_Deg'$xPh2 '=========';
     } else {
$PhaseAng ''$xPh1 $xPh2 '';}

// -----------------------------------------
// Determine compass directions of rise/set.

   
$a FloatVal($AzimDeg);
   
$a -= 360*floor($a/360);
   
$k = ($a 11.25 or $a 348.75)? floor(0.5 $a/22.5);
   
$u trim(substr('N  NNENE ENEE  ESESE SSES  SSWSW WSWW  WNWNW NNWN'3*$k3));
   
$u substr("$u   ",0,3);
        if (
substr($u, -2) == '  ') {$u ' '.trim($u).' ';}
   
$dir $u;

// -----------------
// Visual magnitude.

   
$VisMag  = ($VisMag <> 'n.a.')? SPrintF("% +7.3f"$VisMag) : "  $VisMag";


// --------------------------------
// Construct ephemeris output line.

   
$wTable .= "$DateTimeStr   $RorTorS   $AzimDeg   $dir   $ElevDeg    $VisMag   $PhaseAng\n";
  }



// @@@@@@@@@@@@@@@@@@@@@@@

// -------------------------------
// Store CSV text block in working
// array and count the elements.

   
unset($wArray);
   
$wArray PReg_Split("[\n]"$wTable);
   
$wCount count($wArray);
   
$wTable '';

// --------------------------------------------------------------
// Construct table with different dates separated by blank lines.

   
$FirstLine trim($wArray[0]);

   if (
Is_Numeric(substr($FirstLine,0,1))) {$FirstLine $FirstLine";}

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

   if (
Is_Numeric(substr($PrevLine,0,1))) {$PrevLine $PrevLine";}
   if (
Is_Numeric(substr($CurrLine,0,1))) {$CurrLine $CurrLine";}

   
$PrevDay substr($PrevLine,10,2);
   
$CurrDay substr($CurrLine,10,2);

/* --------------------------------------------------
   Compare previous and current day numbers to see if
   they are equal.   If not, then insert a blank line
   before printing the subsequent ephemeris line.
*/
   
if ($PrevDay == $CurrDay)
      {
$wTable .= "$CurrLine\n";}
   else
      {
$wTable .= "\n$CurrLine\n";}
  }
   
$wTable RTrim("$FirstLine\n$wTable");

   return
"                          RISE / TRANSIT / SET TIMES TABLE

 NASA/JPL BODY ID:
 
$TargetBodyNameText

 
$xDSSTText
 =============================================================================
  Calen_Date  Time     Event    Azim_Deg  Dir   Elev_Deg   Vis_Mag   
$xPh1
 ============ =====   =======   ========  ===   ========   =======   
$xPh2
$wTable";

// End of  Make_RTS_Table (...)






// END OF PROGRAM




?>