<?php
/*
###########################################################################
DUAL-DATE NASA/JPL HORIZONS TOPOCENTRIC EPHEMERIS INPUT TEMPLATE
WITH 30-DAY COOKIE.
FOR TOPOCENTRIC PERSPECTIVE TAKING INTO ACCOUNT GEOGRAPHIC LOCATION.
AUTHOR : Jay Tanner - 2026
LANGUAGE : PHP v8.2.12
LICENSE : Public Domain
This interface template is for operations that require two date arguments
as well as geographic coordinates of the observer (you). More arguments
could be added for future interface expansions.
It can be used as a base template upon which to build an ephemeris program.
This version includes the geographic location coordinates, so it is ideal
for the making of topocentric computations, such as local rising/setting
times.
NOTES:
[1] Use Quantity #2 for apparent topocentric RA and Declination.
[2] The default location coordinates are for Geneva, NY, USA. My town.
These default values can be easily changed in the source code.
[3] The program can also be used as a general data portal and can emulate
several TelNet operations.
More items can be added to the interface. This is just a starter template.
###########################################################################
*/
OB_Start(); // Oy! Is the OB for a baby or a funeral?
// ---------------------------------------------------------------
// Define the program cookie name and set it to expire in 30 days.
$CookieName = 'DUAL-DATE-TOPOCENTRIC-INPUT-TEMPLATE';
$SetToExpireIn30Days = time() + 30*86400;
// ------------------------------------------------------------------
// Define JavaScript message to display in (TextArea1) while working.
$_COMPUTING_ = "TextArea1.innerHTML='W.O.R.K.I.N.G --- This may take a few seconds.';";
// ---------------------------------
// Define PHP program and HTML info.
$_AUTHOR_ = 'Kilroy of Null Island';
$_PROGRAM_VERSION_ = 'v1.00 - '; $at = "at Time "; $LTC = "UTC";
$_SCRIPT_FILE_PATH_ = Filter_Input(INPUT_SERVER, 'SCRIPT_FILENAME');
$_REVISION_DATE_ = $_PROGRAM_VERSION_ .'Revised: '. GMDate("Y-F-d-l $at H:i:s ($LTC", FileMTime($_SCRIPT_FILE_PATH_)).")";
$_BROWSER_TAB_TEXT_ = "DUAL DATE TOPOCENTRIC INPUT TEMPLATE";
$_INTERFACE_TITLE_ = "<span style='font-size:15pt;'>DUAL DATE TOPOCENTRIC INPUT TEMPLATE</span><br><span style='font-size:12pt;'>Built Around The NASA/JPL Horizons API</span><br><span style='font-size:10pt;'>PHP Program by $_AUTHOR_</span>";
/* -------------------------------------
Define main TextArea text and background
colors and HTML table row span. If an
error is reported, then these colors
will change internally to red/white.
*/
$TxColor = 'black';
$BgColor = 'white';
/* ---------------------------
Default GPS coordinates are
for Geneva, NY, USA.
*/
$DefaultLocLabel = 'Geneva, NY, USA';
$DefaultTimeZone = '-05:00'; // Negative = West
$DefaultLonDeg = '-76.996491'; // Negative = West
$DefaultLatDeg = '+42.868473';
$DefaultAltMet = '+0.0';
/* ----------------------------------------------------------
Define number of days in each month and the 3-letter month
name abbreviations and 3-letter weekday name abbreviations
for use with calendar computations.
*/
define('MONTH_DAYS', '312831303130313130313031');
define('MONTH_NAMES', 'JanFebMarAprMayJunJulAugSepOctNovDec');
define('DOW_NAMES', 'SunMonTueWedThuFriSat');
// --------------------------
// Get [SUBMIT] button state.
$SubmitButton = Filter_Input(INPUT_POST, 'SubmitButton');
// Do this only if the [SUBMIT] button was NOT clicked.
if (!IsSet($SubmitButton))
{
/* ----------------------------------------------------------------------
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.
*/
$CookieState = Filter_Input(INPUT_COOKIE, $CookieName);
if (IsSet($CookieState))
{
$CookieDataString = Filter_Input(INPUT_COOKIE, $CookieName);
list
(
$BodyID,
$TimeScale,
$StartBCAD,
$StartYear,
$StartMonth,
$StartDay,
$StartTime,
$TimeZone,
$StopBCAD,
$StopYear,
$StopMonth,
$StopDay,
$StopTime,
$DaySumYN,
$StepSize,
$LocLabel,
$LonDeg,
$LatDeg,
$AltMet,
$RefractYN,
$ObjDataYN,
$HMSorDEG,
$JDateYN,
$SuppRangeRate,
$AUorKM,
$Quantities
) = 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';
$TimeScale = 'UT';
$StartBCAD = 'AD';
$StartYear = date('Y');
$StartMonth = date('M');
$StartDay = date('d');
$StartTime = '00:00:00';
$TimeZone = $DefaultTimeZone;
$DaySumYN = 'No';
$StopBCAD = 'AD';
$StopYear = date('Y');
$StopMonth = date('M');
$StopDay = date('d');
$StopTime = '23:00:01';
$StepSize = '1 hour';
$LocLabel = $DefaultLocLabel;
$LonDeg = $DefaultLonDeg;
$LatDeg = $DefaultLatDeg;
$AltMet = $DefaultAltMet;
$RefractYN = 'No'; // Yes|No
$ObjDataYN = 'Yes'; // Yes|No
$HMSorDEG = 'HMS'; // HMS|DEG
$JDateYN = 'No'; // Yes|No
$SuppRangeRate = 'Yes'; // Yes|No
$AUorKM = 'AU'; // AU|KM
$Quantities = '2,20,4,9';
// -------------------------------------------
// Store current interface settings in cookie.
$CookieDataString = "$BodyID|$TimeScale|$StartBCAD|$StartYear|$StartMonth|$StartDay|$StartTime|$TimeZone|$StopBCAD|$StopYear|$StopMonth|$StopDay|$StopTime|$DaySumYN|$StepSize|$LocLabel|$LonDeg|$LatDeg|$AltMet|$RefractYN|$ObjDataYN|$HMSorDEG|$JDateYN|$SuppRangeRate|$AUorKM|$Quantities";
SetCookie ($CookieName, $CookieDataString, $SetToExpireIn30Days);
} // End of else {...}
} // End of if (!isset(_POST['SubmitButton']))
// ------------------------------------------
// Read values of all interface arguments and
// set any empty arguments to default values.
$SubmitButton = Filter_Input(INPUT_POST, 'SubmitButton');
if (IsSet($SubmitButton))
{
$BodyID = trim(Filter_Input(INPUT_POST, 'BodyID'));
if ($BodyID == '') {$BodyID = '301';}
$TimeScale = StrToUpper(trim(Filter_Input(INPUT_POST, 'TimeScale')));
if ($TimeScale == '') {$TimeScale = 'UT';}
$TimeScale = (substr($TimeScale, 0,1) == 'T')? 'TT' : 'UT';
// ----------------------------------------------------
// Get longitude input as decimal degrees or DMS string
// which will be converted into decimal degrees.
$LonDeg = trim(Filter_Input(INPUT_POST, 'LonDeg'));
if ($LonDeg == '') {$LonDeg = $DefaultLonDeg;}
$LonDeg = DMS_to_Deg($LonDeg, 9);
// ---------------------------------------------------
// Get latitude input as decimal degrees or DMS string
// which will be converted into decimal degrees.
$LatDeg = trim(Filter_Input(INPUT_POST, 'LatDeg'));
if ($LatDeg == '') {$LatDeg = $DefaultLatDeg;}
$LatDeg = DMS_to_Deg($LatDeg, 9);
$AltMet = trim(Filter_Input(INPUT_POST, 'AltMet'));
if ($AltMet == '') {$AltMet = '+0';}
$AltMet = SPrintF("%+d", $AltMet);
$StartBCAD = StrToUpper(trim(Filter_Input(INPUT_POST, 'StartBCAD')));
if ($StartBCAD == '') {$StartBCAD = 'AD';}
$StartBCAD = (substr($StartBCAD,0,1) == 'B')? 'BC' : 'AD';
$StartYear = trim(Filter_Input(INPUT_POST, 'StartYear'));
if ($StartYear == '') {$StartYear = date('Y');}
$StartYear = SPrintF("%04d", $StartYear);
/* -------------------------------------------
Get START month as a number (1 to 12) or as
a 3-letter abbreviation ('Jan' to 'Dec').
NOT case-sensitive.
*/
$StartMonth = UCFirst(substr(StrToLower(trim(Filter_Input(INPUT_POST, 'StartMonth'))),0,3));
if ($StartMonth == '') {$StartMonth = date('M');}
if (Is_Numeric($StartMonth) and $StartMonth == 0) {$StartMonth = 'Jan';}
for ($ii=0; $ii < 1; $ii++)
{
if (Is_Numeric($StartMonth))
{
$m = IntVal($StartMonth);
if ($m < 0 or $m > 12) {$StartMonth = 0; break;}
$StartMonth = substr(MONTH_NAMES, 3*($m-1), 3);
}
$jj = StrPos(MONTH_NAMES, $StartMonth);
if ($jj === FALSE) {$StartMonth = 0; break;}
$StartMonth = substr(MONTH_NAMES, $jj, 3);
}
$StartDay = trim(Filter_Input(INPUT_POST, 'StartDay'));
if ($StartDay == '') {$StartDay = IntVal(date('d'));}
$StartDay = SPrintF("%02d", $StartDay);
$StartTime = trim(Filter_Input(INPUT_POST, 'StartTime'));
if ($StartTime == '') {$StartTime = date('H:i:s');}
$StartTime = (!Is_Numeric(substr($StartTime,0,1)))? '00:00:00': $StartTime;
$w = HMS_to_Hours($StartTime);
$StartTime = Hours_to_HMS($w,0);
$TimeZone = trim(Filter_Input(INPUT_POST, 'TimeZone'));
if ($TimeZone == '') {$TimeZone = $DefaultTimeZone;}
$TZHours = @HMS_to_Hours($TimeZone); // suppress a harmless warning.
$TimeZone = Hours_to_HMS($TZHours);
$TZSign = ($TZHours >= 0)? '+':'';
$TimeZone = substr($TZSign.$TimeZone,0,6);
$DaySumYN = trim(Filter_Input(INPUT_POST, 'DaySumYN'));
$DaySumYN = (substr(StrToUpper($DaySumYN),0,1) == 'Y')? 'Yes':'No';
$StopBCAD = StrToUpper(trim(Filter_Input(INPUT_POST, 'StopBCAD')));
if ($StopBCAD == '') {$StopBCAD = 'AD';}
$StopBCAD = (substr($StopBCAD,0,1) == 'B')? 'BC' : 'AD';
$StopYear = trim(Filter_Input(INPUT_POST, 'StopYear'));
if ($StopYear == '') {$StopYear = date('Y');}
$StopYear = SPrintF("%04d", $StopYear);
/* ------------------------------------------
Get STOP month as a number (1 to 12) or as
a 3-letter abbreviation ('Jan' to 'Dec').
*/
$StopMonth = UCFirst(substr(StrToLower(trim(Filter_Input(INPUT_POST, 'StopMonth'))),0,3));
if ($StopMonth == '') {$StopMonth = date('M');}
if (Is_Numeric($StopMonth) and $StopMonth == 0) {$StopMonth = 'Dec';}
for ($ii=0; $ii < 1; $ii++)
{
if (Is_Numeric($StopMonth))
{
$m = IntVal($StopMonth);
if ($m < 0 or $m > 12) {$StopMonth = 0; break;}
$StopMonth = substr(MONTH_NAMES, 3*($m-1), 3);
}
$jj = StrPos(MONTH_NAMES, $StopMonth);
if ($jj === FALSE) {$StopMonth = 0; break;}
$StopMonth = substr(MONTH_NAMES, $jj, 3);
}
$StopDay = trim(Filter_Input(INPUT_POST, 'StopDay'));
if ($StopDay == '') {$StopDay = IntVal(date('d'));}
$StopDay = SPrintF("%02d", $StopDay);
$StopTime = trim(Filter_Input(INPUT_POST, 'StopTime'));
if ($StopTime == '') {$StopTime = date('H:i:s');}
$StopTime = (!Is_Numeric(substr($StopTime,0,1)))? '00:00:00': $StopTime;
$w = HMS_to_Hours($StopTime);
$StopTime = Hours_to_HMS(HMS_to_Hours($w), 0);
$StepSize = trim(Filter_Input(INPUT_POST, 'StepSize'));
if ($StepSize == '') {$StepSize = '1 hour';}
$StepSize .= (Is_Numeric($StepSize))? ' day' : '';
$LocLabel = trim(Filter_Input(INPUT_POST, 'LocLabel'));
if ($LocLabel == '') {$LocLabel = '---';}
// ---------------------------------------------------
// Get latitude input as decimal degrees or DMS string
// which will be converted into decimal degrees.
$LatDeg = trim(Filter_Input(INPUT_POST, 'LatDeg'));
if ($LatDeg == '') {$LatDeg = $DefaultLatDeg;}
$LatDeg = DMS_to_Deg($LatDeg, 9);
$AltMet = trim(Filter_Input(INPUT_POST, 'AltMet'));
if ($AltMet == '') {$AltMet = '+0';}
$AltMet = SPrintF("%+1.1f", $AltMet);
$RefractYN = StrToUpper(trim(Filter_Input(INPUT_POST, 'RefractYN')));
if ($RefractYN == '') {$RefractYN = 'No';}
$RefractYN = (substr($RefractYN,0,1) == 'Y')? 'Yes' : 'No';
$ObjDataYN = StrToUpper(trim(Filter_Input(INPUT_POST, 'ObjDataYN')));
if ($ObjDataYN == '') {$ObjDataYN = 'No';}
$ObjDataYN = (substr($ObjDataYN,0,1) == 'Y')? 'Yes' : 'No';
$HMSorDEG = StrToUpper(trim(Filter_Input(INPUT_POST, 'HMSorDEG')));
if ($HMSorDEG == '') {$HMSorDEG = 'HMS';}
$HMSorDEG = (substr($HMSorDEG,0,1) == 'D')? 'DEG' : 'HMS';
$JDateYN = StrToUpper(trim(Filter_Input(INPUT_POST, 'JDateYN')));
if ($JDateYN == '') {$JDateYN = 'No';}
$JDateYN = (substr($JDateYN,0,1) == 'Y')? 'Yes' : 'No';
$SuppRangeRate = StrToUpper(trim(Filter_Input(INPUT_POST, 'SuppRangeRate')));
if ($SuppRangeRate == '') {$SuppRangeRate = 'Yes';}
$SuppRangeRate = (substr($SuppRangeRate,0,1) == 'Y')? 'Yes' : 'No';
$AUorKM = StrToUpper(trim(Filter_Input(INPUT_POST, 'AUorKM')));
if ($AUorKM == '') {$AUorKM = 'AU';}
$AUorKM = (substr($AUorKM,0,1) == 'K')? 'Km' : 'AU';
$Quantities = StrToUpper(trim(Filter_Input(INPUT_POST, 'Quantities')));
if ($Quantities == '') {$Quantities = '2,20,4,9';}
// --------------------------------------
// Store interface arguments in a cookie.
$CookieDataString = "$BodyID|$TimeScale|$StartBCAD|$StartYear|$StartMonth|$StartDay|$StartTime|$TimeZone|$StopBCAD|$StopYear|$StopMonth|$StopDay|$StopTime|$DaySumYN|$StepSize|$LocLabel|$LonDeg|$LatDeg|$AltMet|$RefractYN|$ObjDataYN|$HMSorDEG|$JDateYN|$SuppRangeRate|$AUorKM|$Quantities";
SetCookie ($CookieName, $CookieDataString, $SetToExpireIn30Days);
}
/* -----------------------------------------------------------
Put code here to optionally check individual arguments for
validity in reverse input order. If errors found, then set
error flag and message values accordingly.
NOTE: Horizons will catch some errors, such as invalid date
errors.
*/
$ErrorReported = FALSE;
$ErrMssg = '';
// -----------------------------
// Set initial uniform width for
// tables alignment in pixels.
$TableWidth = '780';
// -------------------------------------------
// If error was reported (TRUE), then display
// the error message on a red background.
if ($ErrorReported)
{
$TxColor = 'white';
$BgColor = '#CC0000';
$TextArea2Text = '';
$TextArea1Text =
"=== ERROR ===
$ErrMssg";
}
else
{
// *********************************************************
// BEGIN MAIN COMPUTATIONS HERE IF NO ERRORS DETECTED ABOVE.
// *********************************************************
// ------------------------------------------------------
// Construct full date and time strings for Horizons API.
$StartDateTime = "$StartBCAD $StartYear-$StartMonth-$StartDay $StartTime $TimeScale";
$StopDateTime = "$StopBCAD $StopYear-$StopMonth-$StopDay $StopTime";
/* ----------------------------------
Computation or function call code.
EXAMPLE
Function_Name ($Arg1,$Arg2,$Arg3);
*/
$EphemStr = Topocentric_Ephem ($BodyID,$TimeScale,$TimeZone,$StartDateTime,
$StopDateTime,$StepSize,$LonDeg,$LatDeg,$AltMet,
$DaySumYN,$RefractYN,$HMSorDEG,$ObjDataYN,
$SuppRangeRate,$JDateYN,$AUorKM,$Quantities);
// *******************************************
// DROP THROUGH HERE AFTER COMPUTATIONS ABOVE
// TO PRINT OUT THE RESULTS OF THE OPERATIONS.
// *******************************************
$TextArea1Text =
"#############################################################################
MAIN OUTPUT TEXT AREA 1
The basic concept is to design a function to perform a NASA/JPL Horizons API
computation and modify the interface to make use of it. It can be customized
to serve a dedicated purpose.
The results of computations can be printed in this area and/or the optional
second text area below as well. The text areas should automatically change
size according to the size of the returned text block to minimize scrolling.
-----------------------------------
For JPL Database Body ID = $BodyID
Base Time Scale = $TimeScale
Local Time Zone Offset = UT$TimeZone (Ignored if using TT scale.)
Daylight / Summer Time = $DaySumYN
Optional Location Label = $LocLabel
GPS Longitude = $LonDeg °\t(+Pos = East)
GPS Latitude = $LatDeg °\t(+Pos = North)
START Era / Date / Time = $StartBCAD $StartYear-$StartMonth-$StartDay $StartTime
STOP Era / Date / Time = $StopBCAD $StopYear-$StopMonth-$StopDay $StopTime
Ephemeris Step Size = $StepSize
******************************************************************************
NASA/JPL HORIZONS EPHEMERIS / DATA OUTPUT FOLLOWS BELOW
******************************************************************************
$EphemStr
";
}
// ****************************
// Define TextArea2 text block.
$TextArea2Text =
"TEXT AREA 2 = OPTIONAL SECOND TEXT AREA
For optional program info or other use or it can be removed entirely or more
text areas could be added as needed. This text area is independent of the
text area above.
";
/* --------------------------------------------------------------------------
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 = 1 + Max(Array_Map('StrLen', PReg_Split("[\n]", trim($TextArea1Text))));
if ($Text1Cols < 80) {$Text1Cols = 80;} // Default
$Text1Rows = 2 + Substr_Count($TextArea1Text, "\n");
// --------------------------------------------
// Text Area 2 - Default = At least 80 columns.
$Text2Cols = 1 + Max(Array_Map('StrLen', PReg_Split("[\n]", trim($TextArea2Text))));
if ($Text2Cols < 80) {$Text2Cols = 80;} // Default
$Text2Rows = 2 + 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; chrset=UTF-8'>
<meta http-equiv='pragma' content='no-cache'>
<meta http-equiv='expires' content='-1'>
<meta name='description' content='xxxxxxxxxxxxxxx'>
<meta name='keywords' content='NeoProgrammics / PHP Science Labs'>
<meta name='author' content='Jay Tanner - https://www.NeoProgrammics.com'>
<meta name='robots' content='index,follow'>
<meta name='googlebot' content='index,follow'>
<style>
BODY {color:white; background:black; font-family:Verdana; font-size:12pt; line-height:125%;}
TABLE
{font-size:13pt; border: 1px solid black;}
TD
{
color:black; background:white; line-height:150%; font-size:10pt;
padding:6px; text-align:center;
}
UL
{font-family:Verdana; font-size:12pt; line-height:150%; text-align:justify;}
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:12pt;
font-weight:bold; padding:4pt; white-space:pre; border-radius:8px;
line-height:125%;
}
INPUT[type='text']::-ms-clear {width:0; height:0; text-align:center;}
INPUT[type='text']
{
font-family:monospace; color:black; background:white; font-size:12pt;
font-weight:bold; text-align:center; box-shadow:2px 2px 3px #666666;
border:2px solid black; border-radius:4px;
}
INPUT[type='text']:focus
{
font-family:monospace; background:white; box-shadow:2px 2px 3px #666666;
font-size:12pt; border:2px solid blue; text-align:center; font-weight:bold;
border-radius:4px;
}
INPUT[type='submit']
{
background:black; color:cyan; font-family:Verdana; font-size:10pt;
font-weight:bold; border-radius:4px; border:4px solid #777777;
padding:3pt;
}
INPUT[type='submit']:hover
{
background:black; color:white; font-family:Verdana; font-size:10pt;
font-weight:bold; border-radius:4px; border:4px solid red;
padding:3pt;
}
// Link states MUST be set in the following order:
// :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:DarkCyan; 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 1.0s ease 1.0s;
text-align:left;
visibility:visible;
}
[title-text]:after
{
opacity:1.0;
content:attr(title-text);
text-align:left;
left:50%;
background-color:yellow;
color:black;
font-size:10pt;
position:absolute;
padding:1px 5px 2px 5px;
white-space:pre;
border:1px solid red;
z-index:1;
visibility:hidden;
}
[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="center" 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>
<!-- Define input (BodyID) text box --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr>
<td colspan="999" style='line-height:175%; background:LightCyan;' width='25%'
title=' MAJOR BODY IDs
10 = Sun
199 = Mercury
299 = Venus
301 = Moon (Luna)
399 = Earth
499 = Mars
599 = Jupiter
699 = Saturn
799 = Uranus
899 = Neptune
999 = Pluto
ASTEROID IDs
1; = Ceres;
2; = Pallas;
3; = Juno;
4; = Vesta;
5; = Astraea;
6; = Hebe;
7; = Iris;
8; = Flora;
9; = Metis;
10; = Hygiea;
SPECIAL QUERIES
* = Listing of Major Bodies and ID #s
? = General Horizons API Info
?! = Technical Horizons API Info
News = General NASA/JPL News Archive '>
Enter NASA/JPL Database Body ID#, Designation or Special Query<br>
<input name="BodyID" type="text" value="$BodyID" size="65" maxlength="64"></td>
</tr>
<tr>
<td style='line-height:175%; background:LightYellow;' width='25%'
title=''>Requested Quantities List<br>
<input name="Quantities" type="text" value="$Quantities" size="65" maxlength="64"></td>
</tr>
<tr>
<td style='line-height:175%; background:#CCFFCC;' width='25%'
title=''>Optional Location Label<br>
<input name="LocLabel" type="text" value="$LocLabel" size="65" maxlength="64"></td>
</tr>
</table>
<!-- LONGITUDE/LATITUDE/ALTITUDE SETTINGS TABLE --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr title=" IMPORTANT:\n Make sure that the given Geographic Coordinates are within the \n given Time Zone or the computed times may not be accurate. \n\n">
<!-- GPS Longitude in Degrees or DMS --->
<td colspan="1" style='background:#CCFFCC; line-height:180%;'>± GPS Longitude °<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>
<!-- GPS Latitude in Degrees or DMS --->
<td colspan="1" style='background:#CCFFCC; line-height:180%;'>± GPS Latitude °<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>
<!-- Sea level altitude --->
<td colspan="2" style='background:#CCFFCC; line-height:180%;'>± Sea Level Altitude in Meters<br>
<input name="AltMet" type="text" value="$AltMet" size="13" maxlength="12" title=' Altitude in meters relative to sea level. '></td>
</tr>
<tr>
<td width='25%' style='line-height:175%; background:LightYellow;' title=' UT = Universal Time (Default)\n TT = Terrestrial (Dynamical) Time '>Base Time Scale<br><input name="TimeScale" type="text" value="$TimeScale" size="3" maxlength="2"></td>
<td colspan='2' title=' START Date/Time must be EARLIER than STOP Date/Time '><b>START</b> Date and Time<br><input name="StartBCAD" type="text" value="$StartBCAD" size="3" maxlength="2">
<input name="StartYear" type="text" value="$StartYear" size="6" maxlength="5"><input name="StartMonth" type="text" value="$StartMonth" size="4" maxlength="3"><input name="StartDay" type="text" value="$StartDay" size="3" maxlength="2">
<input name="StartTime" type="text" value="$StartTime" size="9" maxlength="8"></td>
<td width='25%' style='line-height:175%; background:LightYellow;' title=' Local Time Zone Offset From UT. \n −Negative = W +Positive = E \n\n IMPORTANT:\n Make sure that the given Geographic Coordinates are within the \n given Time Zone or the computed times may not be accurate. \n\n'>Time Zone<br><input name="TimeZone" type="text" value="$TimeZone" size="7" maxlength="6"></td>
</tr>
<tr>
<td width='33%' style='line-height:175%; background:LightYellow;' title=' No = Standard Time (Default) \n Yes = Daylight / Summer Time '>Daylight / Summer Time<br>
<input name="DaySumYN" type="text" value="$DaySumYN" size="4" maxlength="3"></td>
<td colspan="2" title=' STOP Date/Time must be LATER than START Date/Time '><b>STOP</b> Date and Time<br>
<input name="StopBCAD" type="text" value="$StopBCAD" size="3" maxlength="2">
<input name="StopYear" type="text" value="$StopYear" size="6" maxlength="5"><input name="StopMonth" type="text" value="$StopMonth" size="4" maxlength="3"><input name="StopDay" type="text" value="$StopDay" size="3" maxlength="2">
<input name="StopTime" type="text" value="$StopTime" size="9" maxlength="8">
</td>
<td width='33%' style='line-height:175%; background:LightYellow;' title=' Minutes | Days | Hours | Months | Years '>Step Size<br><input name="StepSize" type="text" value="$StepSize" size="17" maxlength="16"></td>
</tr>
<tr>
<td style='line-height:175%; background:LightYellow;' width='25%' title=' HMS = Hours, Minutes and Seconds = Default \n DEG = Decimal Hours / Degrees '>Coords Output Mode<br><input name="HMSorDEG" type="text" value="$HMSorDEG" size="4" maxlength="3"></td><td style='line-height:175%; background:LightYellow;' width='25%' title=''>Include Julian Date<br><input name="JDateYN" type="text" value="$JDateYN" size="4" maxlength="3"></td>
<td style='line-height:175%; background:LightYellow;' width='25%' title=' No = Airless Coordinates Without Refraction = Default \n Yes = Apply Standard Atmospheric Refraction \n'>Apply Refraction<br><input name="RefractYN" type="text" value="$RefractYN" size="4" maxlength="3"></td><td style='line-height:175%; background:LightYellow;' width='25%' title=' Yes = Include Optional Object Data \n No = Do Not Include Optional Data '>Include Object Data<br><input name="ObjDataYN" type="text" value="$ObjDataYN" size="4" maxlength="3"></td>
</tr>
<tr>
<td style='line-height:175%; background:LightYellow;' width='25%' title=' AU = Astronomical Units \n KM = Kilometers '>Range (Distance) Units<br><input name="AUorKM" type="text" value="$AUorKM" size="3" maxlength="2"></td><td style='line-height:175%; background:LightYellow;' width='25%' title=' Yes = Include Range Rate \n No = Do Not Include Range Rate.\n\n The Range Rate is the radial velocity \n or the speed at which the object is \n receding or approaching the Earth. '>Suppress Range Rate<br><input name="SuppRangeRate" type="text" value="$SuppRangeRate" size="4" maxlength="3"></td><td style='line-height:175%; background:LightYellow;' width='25%' title=' Not Yet Defined '>Not Yet Defined<br>[XXXXX]</td><td style='line-height:175%; background:LightYellow;' width='25%' title=' Not Yet Defined '>Not Yet Defined<br>[XXXXX]</td>
</tr>
<!-- Template extension for 4-cells.
<tr>
<td style='line-height:175%; background:LightYellow;' width='25%' title=''>Not Yet Defined<br>[XXXXX]</td><td style='line-height:175%; background:LightYellow;' width='25%' title=''>Not Yet Defined<br>[XXXXX]</td><td style='line-height:175%; background:LightYellow;' width='25%' title=''>Not Yet Defined<br>[XXXXX]</td><td style='line-height:175%; background:LightYellow;' width='25%' title=''>Not Yet Defined<br>[XXXXX]</td>
</tr>
--->
<!-- Template extension for 3-cells.
<tr>
<td colspan='1' style='line-height:175%; background:LightYellow;' title=''>Not Yet Defined<br>[XXXXX]</td><td colspan='2' style='line-height:175%; background:LightYellow;' title=''>Not Yet Defined<br>[XXXXX]</td><td colspan='1' style='line-height:175%; background:LightYellow;' title=''>Not Yet Defined<br>[XXXXX]</td>
</tr>
--->
</table>
<!-- Define [SUBMIT] button --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr><td colspan="99" style="background-color:black;"><input type="submit" name="SubmitButton" value=" S U B M I T " OnClick="$_COMPUTING_"
></td></tr>
</table>
<!-- Yellow source code view link. --->
<br>
<table width="$TableWidth" align="center" cellspacing="1" cellpadding="3">
<tr>
<td colspan="1" style='font-size:10pt; color:black; background:black;
text-align:center;' title=' Tries to Open in a New Tab. '>
<b><a href="View-Source-Code.php" target="_blank"
style='font-family:Verdana; color:black; background:yellow;
text-decoration:none; border:1px solid black; padding:4px;
border-radius:4px; font-weight:normal;'>
View/Copy Source Code </a></b>
</td>
</tr>
</table>
<!-- Define TextArea1 --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr>
<td colspan="99" style="text-align:center; color:GreenYellow; background-color:black;">Double-Click Within Text Area to Select ALL Text<br>
<textarea ID="TextArea1" name="TextArea1" style="color:$TxColor; background:$BgColor; padding:6px; border:2px solid white;" cols="$Text1Cols" rows="$Text1Rows" ReadOnly OnDblClick="this.select();" OnMouseUp="return true;">
$TextArea1Text
</textarea>
</td>
</tr>
</table>
<!-- Define TextArea2 --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr>
<td colspan="99" style="text-align:center; color:GreenYellow; background:black;">Double-Click Within Text Area to Select ALL Text<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>
</tr>
</table>
<!-- Define page footer --->
<table width="$TableWidth" align="center" border="0" cellspacing="1" cellpadding="3">
<tr>
<td colspan="99" style="color:GreenYellow; background:black;">PHP Program by $_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;
// ####################################################
// INPUT INTERFACE UTILITY FUNCTIONS
// ####################################################
/*
###########################################################################
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 = 1 + 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 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)? 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 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 = 1 + 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 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 Topocentric_Ephem ($BodyID,$TimeScale,$TimeZone,$StartDateTime,
$StopDateTime,$StepSize,$LonDeg,$LatDeg,$AltMet,
$DaySumYN,$RefractYN,$HMSorDEG,$ObjDataYN,
$SuppRangeRate,$JDateYN,$AUorKM,$Quantities)
{
// ===========================================================================
// Construct query URL for the NASA/JPL Horizons API.
$BodyID = trim($BodyID);
$Command = URLEncode($BodyID);
$AltKm = trim($AltMet) / 1000;
// --------------------------------------------------------------------------
// Adjust for TT scale. Time Zone and Daylight/Summer Time mode are ignored.
$TimeScale = (StrToUpper(substr(trim($TimeScale),0,1)) == 'T')? 'TT':'UT';
if ($TimeScale == 'TT')
{$TimeZone = '+00:00'; $DaySumYN = 'No';}
/* -----------------------------------------------------------
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";
$AUorKM = (StrToUpper(substr($AUorKM,0,1)) == 'K')? 'KM':'AU';
$ObjDataYN = ((substr(StrToUpper(trim($ObjDataYN)),0,1)) == 'Y')? 'YES':'NO';
$SuppRangeRate = ((substr(StrToUpper(trim($SuppRangeRate)),0,1)) == 'Y')? 'YES':'NO';
$RefractYN = substr(StrToUpper(trim($RefractYN)),0,1);
$REFRACTEDorAIRLESS = ($RefractYN == 'Y')? 'REFRACTED':'AIRLESS';
$HMSorDEG = (StrToUpper(substr(trim($HMSorDEG),0,1)) == 'D')? 'DEG':'HMS';
$CalFormat = ((substr(StrToUpper(trim($JDateYN)),0,1)) == 'Y')? 'BOTH':'CAL';
$Quantities = trim($Quantities);
$From_Horizons_API =
"https://ssd.jpl.nasa.gov/api/horizons.api?format=text" .
"&COMMAND='$Command'" .
"&MAKE_EPHEM='YES'" .
"&EPHEM_TYPE='OBSERVER'" .
"&OBJ_DATA='$ObjDataYN'" .
"&CAL_FORMAT='$CalFormat'" .
"&REF_SYSTEM='ICRF'" .
"&RANGE_UNITS='$AUorKM'" .
"&SUPPRESS_RANGE_RATE='$SuppRangeRate'" .
"&ANG_FORMAT='$HMSorDEG'" .
"&APPARENT='$REFRACTEDorAIRLESS'" .
"&CENTER='COORD@399'" .
"&COORD_TYPE='GEODETIC'" .
"&SITE_COORD='$LonDeg,$LatDeg,$AltKm'" .
"&TIME_DIGITS='MINUTES'" .
"&TIME_ZONE='$TimeZone'" .
"&START_TIME='$StartDateTime '" .
"&STOP_TIME='$StopDateTime'" .
"&STEP_SIZE='$StepSize'" .
"&EXTRA_PREC='YES'" .
"&CSV_FORMAT='YES'" .
"&QUANTITIES='$Quantities'" ;
// ===========================================
/* -----------------------------------------------------------------------
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);}
return HTMLEntities($TopoEphem);
} // End of Topocentric_Ephem(...)
// END OF PROGRAM
?>