<?php
/*
THIS IS A CUSTOM JPL HORIZONS API EPHEMERIS INTERFACE - WITH 30-DAY COOKIE.
BUILT AROUND THE NASA/JPL HORIZONS API.
---------------------------------------------------------------------------
This program can produce more than one type of custom ephemeris, depending
on the given settings. The three most common types are:
Apparent Local Topocentric (Default)
Apparent Astrometric (Both ICRF and B1950/FK4 systems)
Apparent Geocentric (ICRF)
###########################################################################
The ICRF (International Celestial Reference Frame) is the default frame,
but the B1950/FK4 frame can be used for older astrometric coordinates.
The refraction setting is ignored for astrometric coordinates or
geocentric 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 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.
AUTHOR : Jay Tanner - 2025
LANGUAGE : PHP v8.2.12
LICENSE : Public Domain
You are free to change the program in any way you wish to
expand upon or customize it.
NOTE:
All ephemerides are plain-text tables in CSV (spreadsheet) format to
facilitate easy column parsing.
Ref:
https://ssd.jpl.nasa.gov/horizons/app.html
See: [Table Settings] for full quantities list.
###########################################################################
*/
/* ----------------------------------------------------------
OB was the first caveman and LONG was his life. (33 years)
*/
ob_start();
/* --------------------------------
Define the program cookie name
and set it to expire in 30 days.
*/
$CookieName = 'NASA-JPL-Horizons-Ephemeris-Tool';
$SetToExpireIn30Days = time() + 30*86400;
// ---------------------------------
// Define PHP program and HTML info.
$_AUTHOR_ = 'by Jay Tanner';
$_VERSION_ = '2025-Apr-23-Wed';
$at = "at Local Time ";
$LTC = "UTC";
$_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_))
. "−05:00)";
$_BROWSER_TAB_TEXT_ = "NASA/JPL General Horizons Ephemeris Tool";
$_INTERFACE_TITLE_ = "<span style='font-size:14.5pt;'>EXPERIMENTAL<br><br>NASA/JPL Horizons General Ephemeris Tool</span>
<span style='font-size:12pt;'>+</span>
<span style='font-size:12pt;'>Data Query 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>";
// ---------------------------------------------------
// 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 LOCATION AND TIME ZONE.
$DefaultLoc = 'Central New York State, USA';
$DefaultLon = '-76.862737';
$DefaultLat = '+42.904788';
$DefaultTZ = '-05:00';
$DefaultGeocentYN = 'Yes'; // 'Yes|No'
$DefaultQuantities = '1,20,9'; // (1=Astrometric) RA, Decl, Dist, RadVel
/* -----------------------------------
Define number of days in each month
for use with calendar computations.
*/
define('MONTHDAYS', '312831303130313130313031');
/* ----------------------------------------
Define 3-letter month name abbreviations
for use with calendar computations.
*/
define('MONTHS', 'JanFebMarAprMayJunJulAugSepOctNovDec');
/* ------------------------------------------
Define 3-letter weekday name abbreviations
for use with calendar computations.
*/
define('DOWs', 'SunMonTueWedThuFriSat');
/* -----------------------------------------------
Define text for HTML title-text INFO blocks.
Just hover over the 'INFO' text and a yellow
menu will be displayed.
*/
$TargObjIDTitleText =
" This can be any NASA/JPL Object ID or Special Query.
QUERY RETURNS
===== ==============================================
News Horizons System News + News Backlog
? Basic information on using the Horizons API
?! Technical information behind 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)
";
// ----------------------------------------------------
// 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 empty
if (IsSet($Cookie))
{
$CookieDataString = Filter_Input(INPUT_COOKIE, $CookieName);
list
(
$TargObjID,
$TimeScale,
$TimeZone,
$StartBCAD,
$StartYear,
$StartMonth,
$StartDay,
$StartTime,
$StopBCAD,
$StopYear,
$StopMonth,
$StopDay,
$StopTime,
$StepSize,
$LocName,
$LonDeg,
$LatDeg,
$AltMet,
$DaySumYN,
$RefractYN,
$DEGorHMS,
$ObjDataYN,
$SuppRangeRateYN,
$JDateYN,
$AUorKM,
$EphemHeaderYN,
$EphemFooterYN,
$RefSystem,
$GeocentYN,
$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.
*/
{
$TargObjID = '10'; // Sun = Default
$TimeScale = 'UT'; // or 'TT'
$TimeZone = '+00:00';
$StartBCAD = 'AD';
$StartYear = date('Y');
$StartMonth = date('M');
$StartDay = date('d'); // Current day of the month.
$StartTime = date('00:00:00');
$StopBCAD = 'AD';
$StopYear = date('Y');
$StopMonth = date('M');
$StopDay = Days_In_Month("$StartBCAD $StartYear-$StartMonth-01"); // End of month.
$StopTime = date('00:00:01');
$StepSize = '1 day';
$LocName = '---';
$LonDeg = '0';
$LatDeg = '0';
$AltMet = '+0';
$DaySumYN = 'No'; // 'Yes|No'
$RefractYN = 'No'; // 'Yes|No'
$DEGorHMS = 'HMS'; // 'DEG|HMS'
$ObjDataYN = 'No'; // 'Yes|No'
$SuppRangeRateYN = 'No'; // 'Yes|No'
$JDateYN = 'No'; // 'Yes|No'
$AUorKM = 'AU'; // 'AU|KM'
$EphemHeaderYN = 'No'; // 'Yes|No'
$EphemFooterYN = 'No'; // 'Yes|No'
$RefSystem = 'ICRF'; // 'ICRF|B1950/FK4'
$GeocentYN = 'Yes';
$Quantities = '1,20,9';
// -------------------------------------------
// Store current interface settings in cookie.
$CookieDataString = "$TargObjID|$TimeScale|$TimeZone|$StartBCAD|$StartYear|$StartMonth|$StartDay|$StartTime|$StopBCAD|$StopYear|$StopMonth|$StopDay|$StopTime|$StepSize|$LocName|$LonDeg|$LatDeg|$AltMet|$DaySumYN|$RefractYN|$DEGorHMS|$ObjDataYN|$SuppRangeRateYN|$JDateYN|$AUorKM|$EphemHeaderYN|$EphemFooterYN|$RefSystem|$GeocentYN|$Quantities";
SetCookie ($CookieName, $CookieDataString, $SetToExpireIn30Days);
} // 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.
$TargObjID = trim(Filter_Input(INPUT_POST, 'TargObjID'));
if ($TargObjID == '') {$TargObjID = '10';}
$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);
/* -------------------------------------------
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(MONTHS, 3*($m-1), 3);
}
$jj = StrPos(MONTHS, $StartMonth);
if ($jj === FALSE) {$StartMonth = 0; break;}
$StartMonth = substr(MONTHS, $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');}
$w = HMS_to_Hours($StartTime);
$StartTime = Hours_to_HMS($w,0);
$TimeZone = trim(Filter_Input(INPUT_POST, 'TimeZone'));
$TZHours = @HMS_to_Hours($TimeZone); // 2 suppresses a harmless warning.
$TimeZone = Hours_to_HMS($TZHours);
$TZSign = ($TZHours >= 0)? '+':'';
$TimeZone = substr($TZSign.$TimeZone,0,6);
$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);
/* ------------------------------------------
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(MONTHS, 3*($m-1), 3);
}
$jj = StrPos(MONTHS, $StopMonth);
if ($jj === FALSE) {$StopMonth = 0; break;}
$StopMonth = substr(MONTHS, $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');}
$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 day';}
$StepSize .= (Is_Numeric($StepSize))? ' day' : '';
$DaySumYN = trim(Filter_Input(INPUT_POST, 'DaySumYN'));
$DaySumYN = (substr(StrToUpper($DaySumYN),0,1) == 'Y')? 'Yes':'No';
$LocName = trim(Filter_Input(INPUT_POST, 'LocName'));
if ($LocName == '') {$LocName = $DefaultLoc;}
// ----------------------------------------------------
// 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 = $DefaultLon;}
$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 = $DefaultLat;}
$LatDeg = DMS_to_Deg($LatDeg, 9);
$AltMet = trim(Filter_Input(INPUT_POST, 'AltMet'));
if ($AltMet == '') {$AltMet = '+0';}
$AltMet = SPrintF("%+d", $AltMet);
$RefractYN = trim(Filter_Input(INPUT_POST, 'RefractYN'));
if ($RefractYN == '') {$RefractYN = 'No';}
$RefractYN = (substr(StrToUpper($RefractYN),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';
/* ---------------------------------------------------------------
TOPOCENTRIC CUSTOMIZATION NOTE:
In this TOPOCENTRIC ephemeris, only ICRF coordinates are used.
The B1950/FK4 frame only applies to astrometric ephemerides.
*/
$RefSystem = trim(Filter_Input(INPUT_POST, 'RefSystem'));
if ($RefSystem == '') {$RefSystem = 'ICRF';}
$RefSystem = (substr(StrToUpper($RefSystem),0,1) == 'B')? 'B1950':'ICRF';
$LocName = trim(Filter_Input(INPUT_POST, 'LocName'));
if ($LocName == '') {$LocName = $DefaultLoc;}
$ObjDataYN = trim(Filter_Input(INPUT_POST, 'ObjDataYN'));
if ($ObjDataYN == '') {$ObjDataYN = 'No';}
$ObjDataYN = (StrToUpper(substr($ObjDataYN,0,1)) == 'Y')? 'Yes':'No';
$SuppRangeRateYN = trim(Filter_Input(INPUT_POST, 'SuppRangeRateYN'));
if ($SuppRangeRateYN == '') {$SuppRangeRateYN = 'Yes';}
$SuppRangeRateYN = (StrToUpper(substr($SuppRangeRateYN,0,1)) == 'Y')? 'Yes':'No';
$JDateYN = trim(Filter_Input(INPUT_POST, 'JDateYN'));
if ($JDateYN == '') {$JDateYN = 'Yes';}
$JDateYN = (StrToUpper(substr($JDateYN,0,1)) == 'Y')? 'Yes':'No';
$AUorKM = trim(Filter_Input(INPUT_POST, 'AUorKM'));
$AUorKM = (StrToUpper(substr($AUorKM,0,1)) == 'K')? 'KM':'AU';
$EphemHeaderYN = trim(Filter_Input(INPUT_POST, 'EphemHeaderYN'));
if ($EphemHeaderYN == '') {$EphemHeaderYN = 'No';}
$EphemHeaderYN = (StrToUpper(substr($EphemHeaderYN,0,1)) == 'Y')? 'Yes':'No';
$EphemFooterYN = trim(Filter_Input(INPUT_POST, 'EphemFooterYN'));
if ($EphemFooterYN == '') {$EphemFooterYN = 'No';}
$EphemFooterYN = (StrToUpper(substr($EphemFooterYN,0,1)) == 'Y')? 'Yes':'No';
$RefSystem = trim(Filter_Input(INPUT_POST, 'RefSystem'));
if ($RefSystem == '') {$RefSystem = 'ICRF';}
$RefSystem = (StrToUpper(substr($RefSystem,0,1)) == 'B')? 'B1950':'ICRF';
$GeocentYN = trim(Filter_Input(INPUT_POST, 'GeocentYN'));
if ($GeocentYN == '') {$GeocentYN = 'Yes';}
$GeocentYN = (StrToUpper(substr($GeocentYN,0,1)) == 'Y')? 'Yes':'No';
$Quantities = trim(Filter_Input(INPUT_POST, 'Quantities'));
if ($Quantities == '') {$Quantities = $DefaultQuantities;}
$Quantities = Str_Replace(',', ' ', $Quantities);
$Quantities = PReg_Replace("/\s+/", " ", trim($Quantities));
$Quantities = Str_Replace(' ', ',', $Quantities);
$Quantities = LTrim(RTrim($Quantities, ','), ',');
$TimeScale = trim(Filter_Input(INPUT_POST, 'TimeScale'));
if ($TimeScale == '') {$TimeScale = 'UT';}
// ------------------------------------------------------------
// Patches to prevent Day=00 error crash by inserting defaults.
if ($StartDay == '00') {$StartDay = '01';}
$StartDate = "$StartBCAD $StartYear-$StartMonth-$StartDay";
if ($StopDay == '00') {$StopDay = Days_In_Month($StartDate);}
$StopDate = "$StopBCAD $StopYear-$StopMonth-$StopDay";
// ################################################################
// ################################################################
// ################################################################
// ################################################################
// ################################################################
/* -----------------------------------------------------
Entering 'DEMO' will set up these demo example values
as used for the JPL HTTP example ephemeris query at:
https://ssd-api.jpl.nasa.gov/doc/horizons.html
*/
$WXYZ = trim(Filter_Input(INPUT_POST, 'WXYZ'));
if (StrToUpper($WXYZ) == 'DEMO')
{
$TargObjID = '499';
$LocName = '---';
$TimeScale = 'UT';
$StartBCAD = 'AD';
$StartYear = '2006';
$StartMonth = 'Jan';
$StartDay = '01';
$StartTime = '00:00:00';
$TimeZone = '+00:00';
$DaySumYN = 'No';
$StopBCAD = 'AD';
$StopYear = '2006';
$StopMonth = 'Jan';
$StopDay = '20';
$StopTime = '00:00:00';
$StepSize = '1 day';
$LonDeg = '+0';
$LatDeg = '+0';
$AltMet = '+0';
$RefSystem = 'ICRF';
$GeocentYN = 'Yes';
$DEGorHMS = 'HMS';
$RefractYN = 'No';
$ObjDataYN = 'Yes';
$JDateYN = 'No';
$EphemHeaderYN = 'Yes';
$EphemFooterYN = 'Yes';
$AUorKM = 'AU';
$SuppRangeRateYN = 'No';
$Quantities = '1,9,20,23,24,29';
}
/* --------------------
SPECIAL RESET PATCH.
Entering 'RESET' will reset ALL interface
values to their initial default values.
*/
if (StrToUpper($WXYZ) == 'RESET')
{
$TargObjID = '10';
$LocName = '---';
$TimeScale = 'UT';
$StartBCAD = 'AD';
$StartYear = date('Y');
$StartMonth = date('M');
$StartDay = date('d');
$StartTime = '00:00:00';
$TimeZone = '+00:00';
$DaySumYN = 'No';
$StopBCAD = 'AD';
$StopYear = date('Y');
$StopMonth = date('M');
$StopDay = Days_in_Month ("$StartBCAD $StartYear-$StartMonth-$StartDay");
$StopTime = '00:00:01';
$StepSize = '1 day';
$LonDeg = '0';
$LatDeg = '0';
$AltMet = '+0';
$RefSystem = 'ICRF';
$GeocentYN = 'Yes';
$DEGorHMS = 'HMS';
$RefractYN = 'No';
$ObjDataYN = 'No';
$JDateYN = 'No';
$EphemHeaderYN = 'No';
$EphemFooterYN = 'No';
$AUorKM = 'AU';
$SuppRangeRateYN = 'No';
$Quantities = $DefaultQuantities;
}
/* -------------------------------------------
Entering 'NOW' will set to current date and
time only. Current single point ephemeris.
*/
if (StrToUpper($WXYZ) == 'NOW')
{
$StartBCAD = 'AD';
$StartYear = date('Y');
$StartMonth = date('M');
$StartDay = date('d');
$StartTime = date('H:i:s');
$StopBCAD = 'AD';
$StopYear = date('Y');
$StopMonth = date('M');
$StopDay = date('d');
$StopTime = date('H:i:59');
$StepSize = '1 day';
}
/* -------------------
SPECIAL HOME PATCH.
Entering 'HOME' will default ALL interface
values to their custom home values.
*/
if (StrToUpper($WXYZ) == 'HOME')
{
$TargObjID = '10';
$LocName = $DefaultLoc;
$TimeScale = 'UT';
$StartBCAD = 'AD';
$StartYear = date('Y');
$StartMonth = date('M');
$StartDay = date('d');
$StartTime = '00:00:00';
$TimeZone = '-05:00';
$DaySumYN = 'No';
$StopBCAD = 'AD';
$StopYear = date('Y');
$StopMonth = date('M');
$StopDay = Days_in_Month ("$StartBCAD $StartYear-$StartMonth-$StartDay");
$StopTime = '00:00:01';
$StepSize = '1 day';
$LonDeg = $DefaultLon;
$LatDeg = $DefaultLat;
$AltMet = '+0';
$RefSystem = 'ICRF';
$GeocentYN = 'No';
$DEGorHMS = 'HMS';
$RefractYN = 'No';
$ObjDataYN = 'No';
$JDateYN = 'No';
$EphemHeaderYN = 'No';
$EphemFooterYN = 'No';
$AUorKM = 'AU';
$SuppRangeRateYN = 'Yes';
$Quantities = '2,20,9';
}
$TimeScale = (substr(StrToUpper($TimeScale),0,1) == 'T')? 'TT':'UT';
// --------------------------------------
// Store interface arguments in a cookie.
$CookieDataString = "$TargObjID|$TimeScale|$TimeZone|$StartBCAD|$StartYear|$StartMonth|$StartDay|$StartTime|$StopBCAD|$StopYear|$StopMonth|$StopDay|$StopTime|$StepSize|$LocName|$LonDeg|$LatDeg|$AltMet|$DaySumYN|$RefractYN|$DEGorHMS|$ObjDataYN|$SuppRangeRateYN|$JDateYN|$AUorKM|$EphemHeaderYN|$EphemFooterYN|$RefSystem|$GeocentYN|$Quantities";
SetCookie ($CookieName, $CookieDataString, $SetToExpireIn30Days);
}
// ---------------------------------------------------------
// Turn off Daylight/Summer Time mode if using the TT scale.
if ($TimeScale == 'TT') {$DaySumYN = 'No';}
// ------------------------------------------------------------
// Construct full start/stop and date/time strings for the API.
$StartDateTime = "$StartBCAD $StartYear-$StartMonth-$StartDay $StartTime";
$StopDateTime = "$StopBCAD $StopYear-$StopMonth-$StopDay $StopTime";
// ---------------------------------------
// Time Zone text string for UT mode only.
$TZoneText = ($TimeScale == 'UT')? "Local Time Zone Offset = UT$TimeZone +Positive = East":"";
// ----------------------------------------------------------
// Define a text separator bar of 80 (# or pound) characters.
$_BAR_ = Str_Repeat('#', 80);
// ---------------------------------------------------------
// Set initial uniform width for table alignments in pixels.
$TableWidth = '836';
// --------------------------------------------------
// Determine the number of days in the default month.
$mDays = Days_in_Month ("$StartBCAD $StartYear-$StartMonth-$StartDay");
// ****************************************************************
// TRY TO BEGIN MAIN COMPUTATIONS HERE IF NO ERRORS DETECTED ABOVE.
// ****************************************************************
// --------------------------------------------------------
// Intercept special for special '?!' query to avoid crash.
if ($TargObjID == '?!')
{
$RawEphem = Get_Horizons_Tech_Data();
}
else
/* ---------------------------------------------------------------
Call the general ephemeris function using the given parameters.
*/
{
$RawEphem = General_Ephem ($TargObjID,$TimeScale,$TimeZone,
$StartDateTime,$StopDateTime,$StepSize,$LonDeg,
$LatDeg,$AltMet,$DaySumYN,$RefractYN,$DEGorHMS,
$ObjDataYN,$SuppRangeRateYN,$JDateYN,$AUorKM,
$RefSystem,$GeocentYN,$Quantities);
}
// ----------------------------------------------------------------
// Check for a few specific errors and rephrase the error messages.
// NOTE: Certain special characters are encoded as HTML entities.
// Invalid date error.
$RawEphem = Str_Replace('Cannot interpret date. Type "?!" or try YYYY-Mon-Dy {HH:MM} format.', 'ERROR: Either the Start Date or Stop Date is invalid.', $RawEphem);
// Invalid step size units error.
$RawEphem = Str_Replace('Unknown units specification -- re-enter', "ERROR: Unknown Step Size Units.\n'$StepSize'\nMust be a numerical value followed by a units symbol.\n\nValid units are:\nM = Minutes | H = Hours | D = Days | MO = Months | Y = Years\n\nNOT case-sensitive.", $RawEphem);
// Invalid quantities error 1.
$RawEphem = Str_Replace('Cannot read QUANTITIES. Type ? or ?! for explanation.', "ERROR: One or more of the quantity values requested is invalid or unknown.", $RawEphem);
// Invalid quantities error 2.
$RawEphem = Str_Replace('Unknown quantity requested. Type ? for info.', "ERROR: One or more of the quantity values requested is invalid or unknown.", $RawEphem);
$EphemFooterText = '';
if ($EphemFooterYN == 'Yes')
{
$EphemFooterText = Get_Column_Meanings ($RawEphem);
}
/* ----------------------------------------------------------
Get simple lunar phase angle and text description of phase
for the interface start Date/Time.
*/
$LunarPhaseAng = $LunarPhaseText = $MoonBlock = '';
if ($TargObjID == '301')
{
$LunarPhaseAng = Lunar_Phase_Angle ($StartDateTime,$TimeZone,$DaySumYN);
$LunarPhaseAng = SPrintF("%1.4f", $LunarPhaseAng);
$LunarPhaseText = Lunar_Phase_to_Text($LunarPhaseAng);
$MoonBlock =
"
-----------------------------------
MOON PHASE AT GIVEN START DATE/TIME
Lunar Phase Angle = $LunarPhaseAng °
General Appearance = $LunarPhaseText
";
}
/* ------------------------------------------------------------
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.
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($RawEphem,"\$\$SOE\n") !== FALSE
and
StrPos($RawEphem,"\$\$EOE\n") !== FALSE
)
/* ----------------------------------------------
If an ephemeris appears to have been returned,
then start processsing it here.
*/
{
$TargetNameText = Get_Target_Name($TargObjID);
$ObjDataText = '';
if ($ObjDataYN == 'Yes')
{$ObjDataText = Get_Obj_Data($TargObjID);}
$EphemHeaderText = '';
if ($EphemHeaderYN == 'Yes')
{$EphemHeaderText = Get_Ephem_Header ($RawEphem);}
$CSVLabelsText = Get_CSV_Labels ($RawEphem);
$CSVBodyText = Get_CSV_Body ($RawEphem);
// ---------------------------------
// Construct main output text block.
$OutputText =
"$ObjDataText
$EphemHeaderText$TargetNameText
$CSVLabelsText
$CSVBodyText
$EphemFooterText";
// ---------------------------------------
// Get target body ID name or designation.
$TargetBodyNameText = Get_Target_Name ($TargObjID);
/* ########################################################
AVAILABLE FUNCTIONS
Get_Horizons_Tech_Data ()
Special_Horizons_Query (Query)
Get_Obj_Data (TargObjID)
Get_Target_Name (TargObjID)
Get_Ephem_Header (RawEphem)
Get_CSV_Labels (RawEphem)
Get_CSV_Body (RawEphem)
Get_Column_Meanings (RawEphem)
Split_Dates (RawEphem)
HMS_to_Hours (HMSString, Decimals)
Hours_to_HMS (Hours, Decimals)
DMS_to_Deg (DMSString, Decimals)
Compass_Symbol (AzimDeg)
Lunar_Phase_to_Text (PhaseAngDeg)
JD_Num (BCADDateStr, JGAMode)
Inv_JD_Num (JDNumber, JGAMode)
Is_Valid_Date_Str (BCADDateStr)
Lunar_Phase_Angle (DateTimeStr,TimeZone,DaySumYN)
########################################################
*/
}
else
// --------------------------------------------------------
// If an ephemeris WAS NOT returned, then start processsing
// the returned text here.
{
// ------------------------------
// Strip out unwanted characters.
$OutputText = Str_Replace('`', '', $RawEphem);
$TargetBodyNameText = $TargObjID;
}
// *******************************************
// DROP THROUGH HERE AFTER COMPUTATIONS ABOVE
// TO PRINT OUT THE RESULTS OF THE OPERATIONS.
// *******************************************
$TextArea1Text =
"$_BAR_
BASIC HORIZONS EPHEMERIS TOOL
Based on the NASA/JPL Horizons API
NASA/JPL OBJECT ID:
$TargetBodyNameText
------------------------------------------------------------
OBSERVER LOCATION / CALENDAR DATE / TIME / ZONE
Optional Location Name = $LocName
Base Time Scale = $TimeScale
$TZoneText
Daylight / Summer Time = $DaySumYN
Start Date/Time = $StartDateTime
Stop Date/Time = $StopDateTime
Ephemeris Step Size = $StepSize
GPS Longitude = $LonDeg° +Positive = East
GPS Latitude = $LatDeg°
Sea Level Altitude = $AltMet m
Reference System = $RefSystem
Use Geocentric Coords = $GeocentYN
Angular Output Mode = $DEGorHMS
Apply Refraction = $RefractYN
Include Object Data = $ObjDataYN
Include Julian Date = $JDateYN
Include Ephem Header = $EphemHeaderYN
Include Ephem Footer = $EphemFooterYN
Distance (Range) Units = $AUorKM
Supppress Range Rate = $SuppRangeRateYN
Requested Quantities = $Quantities
$MoonBlock
_LONG_BAR_
$OutputText
_LONG_BAR_";
// ****************************
// Define TextArea2 text block.
$TextArea2Text =
"
...
";
/* --------------------------------------------------------------------------
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 = 4 + Substr_Count($TextArea1Text, "\n");
// ----------------------------------------------------------
// Define long separator bar to try to match TextArea1 width.
$_LONG_BAR_ = Str_Repeat('#', $Text1Cols);
$TextArea1Text = Str_Replace('_LONG_BAR_', $_LONG_BAR_, $TextArea1Text);
// --------------------------------------------
// 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 = 3 + Substr_Count($TextArea2Text, "\n");
// ******************************************
// ******************************************
// GENERATE CLIENT WEB PAGE TO DISPLAY OUTPUT
print <<< HTML_WEB_PAGE
<!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:10.5pt;
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:11pt;
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:11pt; border:2px solid blue; text-align:center; font-weight:bold;
border-radius:4px; padding:2px;
}
/* ------------------------------------
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 DarkGreen;
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:DodgerBlue; 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:-378%;
background-color:yellow;
color:black;
font-family:monospace;
font-size:10pt;
font-weight:bold;
line-height:150%;
position:absolute;
padding:1px 5px 2px 5px;
white-space:pre;
border:3px solid red;
border-radius:8px;
box-shadow:1px 1px 3px #222222;
z-index:1;
visibility:hidden;
}
[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>
<!-- Object 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 Object ID#, Record #, Query or Special Directive</b><br>
<input name="TargObjID" type="text" value="$TargObjID" size="50" maxlength="81"
title='MAJOR BODIES
10 = Sun
199 = Mercury
299 = Venus
301 = Moon (Luna)
399 = Earth
499 = Mars
599 = Jupiter
699 = Saturn
799 = Uranus
899 = Neptune
999 = Pluto
ASTEROIDS
1; = Ceres;
2; = Pallas;
3; = Juno;
4; = Vesta;
6; = Hebe;
7; = Iris;
8; = Flora;
9; = Metis;
10; = Hygiea;
15; = Eunomia;
16; = Psyche;
52; = Europa;
65; = Cybele;
511; = Davida;
704; = Interamnia; '><br>
<span title-text="$TargObjIDTitleText"> <b>INFO</b> </span>
</td></tr>
</table>
<!-- Optional Location Name Setting Table --->
<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<br>
<input name="LocName" type="text" value="$LocName" size="51" maxlength="50"></td>
</tr>
</table>
<!-- START DATE/TIME/ZONE TABLE --->
<table width="$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr>
<!-- Base Time Scale Setting --->
<td width='25%' style='background:#EFEFEF;'
title=' Base Time Scale
UT = Universal Time (Default)
Uses the Time Zone Setting.
TT = Terrestrial Dynamical Time
Ignores the Time Zone Setting and
the Daylight/Summer Time Setting. '>
Base Time Scale<br>
<input name='TimeScale' type='text' value="$TimeScale" size='3' maxlength='2'>
</td>
<!-- START Date Setting --->
<td style="text-align:center; line-height:175%; background:LightCyan;" title=" This is the ephemeris Start Date and Time. \n\n">
<b>START:</b> Calendar Date and Time<br>
<input name="StartBCAD" type="text" value="$StartBCAD" size="3" maxlength="2"><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"> <input name="StartTime" type="text" value="$StartTime" size="9" maxlength="8" title=' Enter START Time as HH : mm : ss '>
</td>
<td width='25%' style='background:#EFEFEF;'
title=
" Enter Time Zone offset from UT as ±HH : mm
+Positive Time Zone = East
This setting is ignored if using the TT scale. ">
Time Zone<br>
<input name="TimeZone" type="text" value="$TimeZone" size="7" maxlength="6" style='text-align:center;'>
</td>
</tr>
</table>
<!-- STOP DATE/TIME/STEP SIZE TABLE --->
<table width="$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr title=
" This is the ephemeris Stop Date and Time
For an ephemeris table, the Stop Date/Time must be
LATER than the Start Date/Time.
For a SINGLE Date/Time, set the Step Size to beyond
the Stop Date/Time so that only the Start Date/Time
will be used. ">
<!-- Daylight/Summer Time Setting --->
<td width='25%' style="background:#EFEFEF; text-align:center; line-height:175%;" colspan='3' title=' No = Use Standard Time (Default) \n This setting is ignored if using the TT scale. \n\n Yes = Use Daylight/Summer Time '>
Daylight/Summer Time<br>
<input name='DaySumYN' type='text' value="$DaySumYN" size='4' maxlength='3'>
</td>
</td>
<!-- STOP Date/Time Setting --->
<td width='50%' style="background:LightCyan; text-align:center; line-height:180%;">
<b>STOP:</b> Calendar Date and Time<br>
<input name="StopBCAD" type="text" value="$StopBCAD" size="3" maxlength="2"><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"> <input name="StopTime" type="text" value="$StopTime" size="9" maxlength="12" title=' Enter STOP Time as HH : mm : ss '>
</td>
<!-- Ephemeris Step Size Setting --->
<td width='25%'title=' This is the time interval between the tabulated ephemeris computations.\n
Step Units : M = Minutes | H = Hours | D = Days | Mo = Months | Y = Years \n\n For a SINGLE Date/Time, set Step Size to reach beyond the Stop Date/Time \n so that only the Start Date/Time will be used.'
style='background:#EFEFEF; text-align:center; line-height:180%;' colspan='3'>Step Size<br>
<input name="StepSize" type="text" value="$StepSize" size="16" maxlength="16">
</td>
</tr>
</table>
<!-- LONGITUDE/LATITUDE/ALTITUDE SETTINGS TABLE --->
<table width="$TableWidth" align="bottom" 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 NOTE: These coordinates are ignored for geocentric ephemerides. ">
<!-- GPS Longitude in Degrees or DMS --->
<td width='33%' style='background:#E0FFE0; 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'> +Pos = E</td>
<!-- GPS Latitude in Degrees or DMS --->
<td width='33%' style='background:#E0FFE0; 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'> +Pos = N</td>
<!-- Sea level altitude --->
<td style='background:#E0FFE0; line-height:180%;'>± 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>
<!-- SPECIAL OPTIONS TABLE 1 --->
<table width="$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3"><tr>
<!-- Reference System ICRF or B1950/FK4 --->
<td width='25%' style='background:#EFEFEF; line-height:175%;' title=" I = Use ICRF (International Celestial Reference Frame). \n B = Use B1950/FK4 (Astrometric Ephemerides Only). ">Reference System<br>
<input name="RefSystem" type="text" value="$RefSystem" size="6" maxlength="5" style='color:white; background:black;'>
</td>
<!-- Geocentric Coords Setting--->
<td width='25%' style="background:#EFEFEF; text-align:center; line-height:175%;" colspan='3' title=' No = Use observer GPS surface coordinates (Default). \n\n Yes = Use geocentric coordinates. \n Observer GPS Lon/Lat coordinates and refraction \n settings are ignored. '>Use Geocentric Coords<br>
<input name="GeocentYN" type="text" value="$GeocentYN" size="4" maxlength="3" style='background:black; color:white;'>
</td>
<!-- Angle Output DEG|HMS Setting --->
<td width='25%' style='background:#EFEFEF; line-height:175%;' title=' DEG = Output Hour Angles and Declinations in decimal degrees. \n\n HMS = Output Hour Angles and Declinations in HMS/DMS format. '>Angle Output DEG or HMS<br>
<input name="DEGorHMS" type="text" value="$DEGorHMS" size="4" maxlength="3">
</td>
<!-- Refraction Setting --->
<td width='25%' style="background:#EFEFEF; text-align:center; line-height:175%;" colspan='3' title=' No = Airless = Do NOT Apply Refraction \n Yes = Apply Standard Earth Refraction. \n\n NOTE: Refraction settting is ignored when \n using geocentric or astrometric coordinates.'>Apply Refraction<br>
<input name='RefractYN' type='text' value="$RefractYN" size='4' maxlength='3'>
</td>
</tr></table>
<!-- SPECIAL OPTIONS TABLE 2 --->
<table width="$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3"><tr>
<!-- Object Data Setting --->
<td width='25%' style="background:#EFEFEF; text-align:center; line-height:175%;" colspan='3' title=' This will include the target body physical \n data in the returned ephemeris. '>Include Object Data<br>
<input name="ObjDataYN" type="text" value="$ObjDataYN" size="4" maxlength="3">
</td>
<!-- Julian Date Setting --->
<td style="background:#EFEFEF; text-align:center; line-height:175%;" colspan='3' title=' No = Do NOT Include the JD in the Ephemeris \n Yes = Include the JD in the Ephemeris. '>Include Julian Date (JD)<br>
<input name='JDateYN' type='text' value="$JDateYN" size='4' maxlength='3'>
</td>
<!-- Ephemeris Header Setting --->
<td width='25%' style="background:#EFEFEF; text-align:center; line-height:175%;" colspan='3' title=' No = Do NOT include the ephemeris header text in output. \n Yes = Include the ephemeris header text in output. \n\n The header contains text showing the ephemeris parameters
used for the computations.'>Include Ephem Header<br>
<input name="EphemHeaderYN" type="text" value="$EphemHeaderYN" size="4" maxlength="3">
</td>
<!-- Ephemeris Footer Setting --->
<td width='25%' style="background:#EFEFEF; text-align:center; line-height:175%;" colspan='3' title=' No = Do NOT include footer text. \n Yes = Include the footer text. \n The ephemeris footer contains text explaining \n\n the meanings of the column values. '>Include Ephem Footer<br>
<input name="EphemFooterYN" type="text" value="$EphemFooterYN" size="4" maxlength="3">
</td>
</tr></table>
<!-- SPECIAL OPTIONS TABLE 3 --->
<table width="$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3"><tr>
<!-- Not yet defined --->
<td width='25%' style="background:#EFEFEF; text-align:center; line-height:175%;" title=' Not yet defined. '> <br>
<!-- <input name="ZZZ" type="text" value="ZZZ" size="4" maxlength="3"> --->
</td>
<!-- Distance (Range) Units Setting --->
<td width='25%' style='background:#EFEFEF; line-height:175%;' title=' Distance units used. (NASA calls distance "range". \n\n AU = Astronomical Units = Default \n KM = Kilometers \n\n 1.0 AU = 149,597,870.7 km'>Range (Distance) Units<br>
<input name="AUorKM" type="text" value="$AUorKM" size="3" maxlength="2">
</td>
<!-- Suppress Range Rate Setting --->
<td width='25%' style='background:#EFEFEF; line-height:175%;' title=' Yes = Display the range rate with the distance. \n No = Do NOT display the range rate with the distance. \n\n The Range Rate (deldot) refers to the radial velocity of the \n target center towards (−) or away (+) from the observer. '>Suppress Range Rate<br>
<input name="SuppRangeRateYN" type="text" value="$SuppRangeRateYN" size="4" maxlength="3">
</td>
<td width='25%' style="background:#EFEFEF; text-align:center; line-height:175%;" title=" Enter the word 'DEMO' to set JPL demo values. \n\n Enter the word 'RESET' to reset all program defaults. \n\n Enter the word 'NOW' to set to current Date/Time. \n\n Enter the word 'HOME' to set custom home defaults. \n\n NOT case-sensitive. ">DEMO | RESET | NOW.<br>
<input name="WXYZ" type="text" value="" size="6" maxlength="5" style='color:black; background:yellow;'>
</td>
</tr></table>
<!-- EXTRA SPECIAL OPTIONS TABLE --->
<table width="$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3"><tr>
<!-- Lunar Phase Image Table Link --->
<td width='25%' style="background:#EFEFEF; text-align:center; line-height:175%;">
<a href ='Lunar-Phase-Image-Table.php' target='_blank'>Moon Phase Image Table</a>
</td>
<!-- Moon Phase Angle --->
<td width='25%' style="background:#EFEFEF; text-align:center; line-height:175%;">
<A href ='Lunar-Phase-Angle-Table.php' target='_blank'>Moon Phase Angle Table</A>
</td>
<!-- Rise/Transit/Set Times --->
<td width='25%' style="background:#EFEFEF; text-align:center; line-height:175%;">
<a href ='Rise-Transit-Set-Times.php' target='_blank'>Rise/Transit/Set Times</a>
</td>
<!-- Not yet defined --->
<td width='25%' style="background:#EFEFEF; text-align:center; line-height:175%;" title=' Not Yet Defined. '>
<a href ='DDD.xxx'>DDD</a>
</td>
</tr></table>
<!-- REQUESTED QUANTITIES TABLE --->
<table width="$TableWidth" align="bottom" border="0" cellspacing="1" cellpadding="3">
<tr><td
title=' These are the numerical codes for the requested quantities. Following the
date, table columns will be in the same sequence as the given quantity
codes.
Some quantities return two ephemeris columns (Such as RA, Decl). '
style='line-height:200%; background:LightYellow; border-radius:0px 0px 8px 8px;'>
Requested Quantities ?<br>
<input name="Quantities" type="text" value="$Quantities" size="81" maxlength="80"><br>
<a href="Quantities-List.php" target='_blank'> View Quantity Codes Listing </a>
<a title=' This opens a page in another tab to display \n some general program usage tips. ' href="Ephemeris-Info.php" target='_blank'> View Ephemeris Info </a>
</td></tr>
</table>
<!-- THIS IS THE [SUBMIT] BUTTON TABLE.--->
<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. --->
<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><br>
<a href='General-Ephemeris-Tool.7z'>Download PHP Source Code + Images (Approx. 3.9 MB)</a>
</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:left; 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_WEB_PAGE;
/*
###########################################################################
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 General_Ephem ($TargObjID,$TimeScale,$TimeZone,$StartDateTime,
$StopDateTime,$StepSize,$LonDeg,$LatDeg,$AltMet,
$DaySumYN,$RefractYN,$DEGorHMS,$ObjDataYN,
$SuppRangeRateYN,$JDateYN,$AUorKM,$RefSystem,
$GeocentYN,$Quantities)
{
// ===========================================================================
// Construct query URL for the NASA/JPL Horizons API.
$TargObjID = trim($TargObjID);
$Command = URLEncode($TargObjID);
$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';
$RefSystem = ((substr(StrToUpper(trim($RefSystem)),0,1)) == 'B')? 'B1950':'ICRF';
$ObjDataYN = ((substr(StrToUpper(trim($ObjDataYN)),0,1)) == 'Y')? 'YES':'NO';
$Center = ((substr(StrToUpper(trim($GeocentYN)),0,1)) == 'Y')? '500':'COORD';
$SuppRangeRateYN = ((substr(StrToUpper(trim($SuppRangeRateYN)),0,1)) == 'Y')? 'YES':'NO';
$RefractYN = substr(StrToUpper(trim($RefractYN)),0,1);
$REFRACTEDorAIRLESS = ($RefractYN == 'Y')? 'REFRACTED':'AIRLESS';
$DEGorHMS = (StrToUpper(substr(trim($DEGorHMS),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'" .
"&OBJ_DATA='NO'" .
"&MAKE_EPHEM='YES'" .
"&EPHEM_TYPE='OBSERVER'" .
"&CAL_FORMAT='$CalFormat'" .
"&REF_SYSTEM='$RefSystem'" .
"&RANGE_UNITS='$AUorKM'" .
"&SUPPRESS_RANGE_RATE='$SuppRangeRateYN'" .
"&ANG_FORMAT='$DEGorHMS'" .
"&APPARENT='$REFRACTEDorAIRLESS'" .
"&CENTER='$Center@399'" .
"&COORD_TYPE='GEODETIC'" .
"&SITE_COORD='$LonDeg,$LatDeg,$AltKm'" .
"&TIME_DIGITS='SECONDS'" .
"&TIME_ZONE='$TimeZone'" .
"&START_TIME='$StartDateTime $TimeScale'" .
"&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 General_Ephem(...)
/* ###########################################################################
The KERNAL - 2025
THESE ARE CUSTOM TOOLS DESIGNED FOR USE WITH THE NASA/JPL HORIZONS API.
The code can be added to a program internally or externally via the PHP
'include()' directive. It can be changed or expanded upon as needed.
Author : Jay Tanner - 2025
Version : 2460692
Language : PHP v8.2.12
License : Public Domain
--------------------------------------------------------------------
KERNAL CUSTOM EPHEMERIS PARSING VARIABLES, FUNCTIONS AND ARGUMENTS.
UNNEEDED REFERENCES CAN BE REMOVED. IF ONLY A FEW ARE NEEDED, THEN
THEY COULD BE COPIED INTO YOUR SCRIPT AND THEN NO INCLUDED FILES FROM
OUTSIDE WOULD BE NEEDED.
Get_Horizons_Tech_Data ()
Special_Horizons_Query (Query)
Get_Obj_Data (TargObjID)
Get_Target_Name (TargObjID)
Get_Ephem_Header (RawEphem)
Get_CSV_Labels (RawEphem)
Get_CSV_Body (RawEphem)
Get_Column_Meanings (RawEphem)
Split_Days (RawEphem)
HMS_to_Hours (HMSString, Decimals)
Hours_to_HMS (Hours, Decimals)
DMS_to_Deg (DMSString, Decimals)
Compass_Symbol (AzimDeg)
Lunar_Phase_to_Text (PhaseAngDeg)
JD_Num (BCADDateStr, JGAMode)
Inv_JD_Num (JDNumber, JGAMode)
Is_Valid_Date_Str (BCADDateStr)
###########################################################################
*/
/*
###########################################################################
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 extracts and returns ONLY target object name text for any
given solar syetem object ID# or record #, if such data exists.
Since there are many, many, many thousands of asteroids, there are many of
them without actual names like Ceres or Vesta, but identified by a variety
of various catalog symbols that may seem cryptic at first.
Example: For Target Object ID = 9941;
Returns: 9941 Iguanodon (1989 CB3)
NOTE:
A semicolon (;) at the end of the ID means an asteroid or small body.
EXAMPLES:
Given the Target Object ID = 301
The returned target name string would be: 'Moon (301)'
Given the Target Object ID = 301;
The returned target name string would be: '301 Bavaria (A890 WA)'
----------------------------
Given the asteroid BodyID #;
2934;
The returned target name string would be:
'2934 Aristophanes (4006 P-L)'
If no target object name is found, then an error message is returned.
###########################################################################
*/
function Get_Target_Name ($TargObjID)
{
// ===========================================================================
// Read arguments.
$TargObjID = trim($TargObjID);
$Command = URLEncode($TargObjID);
// ---------------------------
// Set to current system date.
$StartDate = date("Y-M-d");
$From_Horizons_API =
"https://ssd.jpl.nasa.gov/api/horizons.api?format=text" .
"&COMMAND='$Command'" .
"&OBJ_DATA='NO'" .
"&MAKE_EPHEM='YES'" .
"&EPHEM_TYPE='OBSERVER'" ;
// ===========================================================================
/* ----------------------------------------------------------
Send query to Horizons API to obtain the target body name.
*/
$W = Str_Replace(",\n", " \n", File_Get_Contents($From_Horizons_API));
/* -----------------------------------
Check if an ephemeris was returned. If
not, then return an empty string ('').
*/
if (StrPos($W, '$$SOE') === FALSE) {return trim($W);}
/* ----------------------------------------------------------
Get the NASA/JPL Horizons target body name string, if any.
Not every target body may yet have a fixed name or ID, but
may possibly still have a record # and ephemeris.
*/
$T = trim($W);
$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, '{');
return trim(substr($W, 0, $i));
} // End of Get_Target_Name (...)
/*
###########################################################################
This function extracts and returns ONLY the ephemeris parameters text. If
the given data does NOT contain an ephemeris, then FALSE is returned.
The parameters text contains the body ID and ephemeris settings.
The ephemeris parameters section always begins with the characters:
'Ephemeris / API_USER'
Generic. No special error checking is done.
NO DEPENDENCIES
###########################################################################
*/
function Get_Ephem_Header($RawEphem)
{
$T = trim($RawEphem);
$i = StrPos($T, "\$\$SOE\n"); if ($i === FALSE) {return $i;}
$i = StrPos($T, 'Ephemeris / API_USER');
$T = substr($T, $i, StrLen($T));
$j = StrPos($T, 'Date_');
$T = substr($T, 0, $j);
$i = StrPos($T, '(spreadsheet)') + 13;
$T = substr($T, 0, $i);
return trim($T) . "\n" . Str_Repeat("=", 80) . "\n";
}
/*
###########################################################################
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.
NO DEPENDENCIES
###########################################################################
*/
function Get_Obj_Data ($TargObjID)
{
// ===========================================================================
// Construct query URL for the NASA/JPL Horizons API.
$Command = URLEncode(trim($TargObjID));
$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_Obj_Data(...)
/*
###########################################################################
This function extracts and returns ONLY the ephemeris CSV labels line.
If the given data does NOT contain an ephemeris, then FALSE is returned.
The column header line always begins with the characters: 'Date_'
Generic. No special error checking is done.
NO DEPENDENCIES
###########################################################################
*/
function Get_CSV_Labels ($RawEphemText)
{
$T = trim($RawEphemText);
$i = StrPos($T, "\$\$SOE\n"); if ($i === FALSE) {return $i;}
$T = substr($T,0,$i);
$T = substr($T, StrPos($T, 'Date_'), StrLen($T));
$i = StrPos($T, '*');
$T = RTrim(substr($T, 0, $i));
return " $T ";
}
/*
###########################################################################
This function can be used to extract any ephemeris table body from all of
the other extraneous text that surrounds it. An ephemeris table can consist
of 1 line up to thousands of lines of CSV data.
The ephemeris CSV data lines are located between two markers:
$$SOE = Start Of Ephemeris marker
and
$$EOE = End Of Ephemeris marker
Generic. No special error checking is done.
NO DEPENDENCIES
###########################################################################
*/
function Get_CSV_Body ($RawEphemText)
{
// ---------------------------------
// Read raw ephemeris text argument.
$w = trim($RawEphemText);
/* -------------------------------------------------
Set pointers to start and end of ephemeris table.
If there is no ephemeris found, then return the
raw text as-is, nothing is done. It may possibly
be an error or some other text instead.
*/
$i = StrPos($w, "\$\$SOE\n"); if ($i === FALSE) {return $w;}
$j = StrPos($w, "\$\$EOE\n");
/* --------------------------------------------
Extract ONLY the required ephemeris CSV data
line(s) from between the Start/End pointers.
*/
$EphemTableText = trim(substr($w, $i+5, $j-$i-5));
return (substr($EphemTableText,0,1) == 'b')? $EphemTableText : " $EphemTableText";
} // End of Get_CSV_Body (...)
/*
###########################################################################
This function extracts and returns ONLY the ephemeris footer text. If the
given data does NOT contain an ephemeris, then FALSE is returned.
The footer text explains the meanings of the ephemeris data columns.
The ephemeris footer text always begins with the characters:
'Column meaning:'
Generic. No special error checking is done.
NO DEPENDENCIES
###########################################################################
*/
function Get_Column_Meanings ($RawEphemText)
{
$T = trim($RawEphemText);
$i = StrPos($T, '$$SOE'); if ($i === FALSE) {return $i;}
$i = StrPos($T, 'Column meaning:');
$T = substr($T, $i, StrLen($T));
$w = trim($T);
return "\n" . Str_Repeat('*', 80)."\n".substr($w, 0, StrPos($T, '*******'))
. Str_Repeat('*', 80);
} // End of Get_Column_Meanings (...)
/*
############################################################################
This functions splits an ephemeris at at points where the day changes.
Generic. No special error checking is done.
NO DEPENDENCIES
############################################################################
*/
function Split_Days ($MarkedListText)
{
$T = trim($MarkedListText);
/* ----------------------------------------------------------------------
If no ephemeris data is found, then return an empty string as an error
state indicator.
*/
if (StrPos($T, '$$SOE') === FALSE) {return '';}
// -------------------------------------------------------------------
// Extract ONLY the CSV ephemeris table from a marked raw dated table.
// -------------------------------------------------
// Set pointers to start and end of ephemeris table.
$i = StrPos($T, '$$SOE');
$j = StrPos($T, '$$EOE');
/* ------------------------------------------------
Extract ONLY the required ephemeris data line(s)
from between the pointers.
*/
$T = trim(substr($T, $i+5, $j-$i-5));
// -------------------------------
// Store CSV text block in working
// array and count the elements.
$wArray = PReg_Split("[\n]", $T);
$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";}
}
return RTrim("$FirstLine\n$wTable");
} // End of Split_Days(...)
/*
###########################################################################
This function returns the closest compass symbol corresponding to azimuth
angle in degrees. Measured clockwise from North = 0.
The compass is divided into 16 named
cardinal zones.
---------------------------------------
AZIMUTH ANGLE CLOCKWISE FROM NORTH
Symbol direction Angle Deg.
------ --------------- ----------
N North 0.0
NNE North Northeast 22.5
NE Northeast 45.0
ENE East Northeast 67.5
E East 90.0
ESE East Southeast 112.5
SE Southeast 135.0
SSE South Southeast 157.5
S South 180.0
SSW South Southwest 202.5
SW Southwest 225.0
WSW West Southwest 247.5
W West 270.0
WNW West Northwest 292.5
NW Northwest 315.0
NNW North Northwest 337.5
N North 360.0
Generic. No special error checking is done.
NO DEPENDENCIES
###########################################################################
*/
function Compass_Symbol ($AzimDeg)
{
$a = FloatVal($AzimDeg);
$a -= 360*floor($a/360);
$i = ($a < 11.25 or $a > 348.75)? 0 : floor(0.5 + $a/22.5);
$w = trim(substr('N NNENE ENEE ESESE SSES SSWSW WSWW WNWNW NNWN', 3*$i, 3));
$w = substr("$w ",0,3);
if (substr($w, -2) == ' ') {$w = ' '.trim($w).' ';}
return $w;
} // End of Compass_Symbol (...)
/*
###########################################################################
This function returns a text description of the lunar phase corresponding
to the given phase angle argument in degrees.
------------------------------------------------------
The simple lunar phase angle = 0 to 360 degrees
reckoned clockwise where:
0 = New Moon
90 = First Quarter Moon
180 = Full Moon
270 = Last Quarter Moon
360 = New Moon
ERRORS
Returns FALSE on error if phase angle
is non-numeric or out of valid range.
NO DEPENDENCIES
###########################################################################
*/
function Lunar_Phase_to_Text ($PhaseAngDeg)
{
// Round absolute phase angle to nearest integer value.
$a = trim($PhaseAngDeg);
if (!Is_Numeric($a) or FloatVal(trim($a)) > 360) {return FALSE;}
$a = abs(round(FloatVal($a),0));
if ($a >= 0 and $a <= 10)
{return 'New Moon.';}
if ($a > 10 and $a <= 36)
{return 'Waxing evening crescent, just past new.';}
if ($a > 36 and $a <= 75)
{return 'Waxing evening crescent moon.';}
if ($a > 75 and $a <= 88)
{return 'Waxing crescent, approaching first quarter.';}
if ($a > 88 and $a <= 101)
{return 'First quarter moon.';}
if ($a > 101 and $a <= 115)
{return 'Waxing gibbous, just past first quarter.';}
if ($a > 115 and $a <= 159)
{return 'Waxing gibbous moon.';}
if ($a > 159 and $a <= 170)
{return 'Waxing gibbous, approaching full moon.';}
if ($a > 170 and $a <= 192)
{return 'Full Moon.';}
if ($a > 192 and $a <= 205)
{return 'Waning gibbous, just past full moon.';}
if ($a > 205 and $a <= 245)
{return 'Waning gibbous moon.';}
if ($a > 245 and $a <= 260)
{return 'Waning gibbous, approaching last quarter.';}
if ($a > 260 and $a <= 271)
{return 'Last quarter moon.';}
if ($a > 271 and $a <= 282)
{return 'Waning crescent, just past last quarter.';}
if ($a > 282 and $a <= 319)
{return 'Waning morning crescent moon.';}
if ($a > 319 and $a <= 349)
{return 'Waning morning crescent, approaching new moon.';}
if ($a > 349 and $a <= 360)
{return 'New Moon.';}
} // End of Lunar_Phase_to_Text (...)
/*
###########################################################################
This function simply returns the technical details on using the API and is
invoked when the ?! query is entered.
Generic. No special error checking is done.
NO DEPENDENCIES
###########################################################################
*/
function Get_Horizons_Tech_Data ()
{
// ===========================================================================
// Construct query URL for the NASA/JPL Horizons API.
$Command = URLEncode("?!");
$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 to obtain the technical details on using the API.
$w = File_Get_Contents($From_Horizons_API);
return HTMLEntities($w);
} // End of Get_Horizons_Tech_Data()
function Special_Horizons_Query ($Query)
{
$Command = URLEncode(trim($Query));
$From_Horizons_API =
"https://ssd.jpl.nasa.gov/api/horizons.api?format=text" .
"&COMMAND='$Command'" .
"&MAKE_EPHEM='YES'" .
"&OBJ_DATA='NO'" ;
// ===========================================================================
// 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 HTMLEntities($W);
}
/*
###########################################################################
This function returns the Julian Day Number for any given calendar date in
the range from BC 19999 to AD 19999 on the old Julian calendar or on the
modern Gregorian calendar system.
It returns negative JD numbers for dates prior to mathematical origins of
the respective calendar systems.
--------------------
CALENDAR YEAR RANGE:
BC 19999 to AD 19999
There is no calendar year 0 (zero).
--------------------------------------------------------
For the mathematical year number (y) used in calendrical
computations for a BC calendar year Y = ('BC Year'):
Y = 'BC 1949' ---> y = '-1948'
We take the numerical value following the 'BC' prefix
and subtract 1 and then change the result to negative
to obtain the mathematical year value required for use
in the (JDNum) computation. This adjustment will only
apply to 'BC' date strings.
Example: 'BC 1949-May-20'
y = -(Y - 1)
= -(1949 - 1)
= -1948
So, (y,m,d) = (-1948,5,20)
-------------------------------------------------------------
Positive calendar year numbers do NOT require any adjustment.
Example: 'AD 1949-May-20'
So, (y,m,d) = (1949,5,20)
--------------------------------------------
MATHEMATICAL ORIGINS OF THE CALENDAR SYSTEMS
BC 4713-Jan-01-Mon JDNum = 0 On the old Julian calendar
BC 4714-Nov-24-Mon JDNum = 0 on the modern Gregorian calendar
NOTE:
If a year is given as a negative number, it refers to a 'BC' year
and will be converted to 'BC|AD' format internally for computations
because 'BC' years first require a special numerical adjustment not
needed for 'AD' years.
Month = 1 to 12 or as 3-letter abbreviation string ('Jan' to 'Dec').
Date strings are NOT case-sensitive.
The returned signed JD Number is left-space-padded to 8 characters
to facilitate easy columnar alignment if used for tabulation.
ARGUMENTS:
$BCADDateStr = Date string in BC|AD format.
VALID EXAMPLES:
'BC 9949-May-20'
'AD 1949-5-20'
'16959-1-29'
'-1023-Nov-15'
JGAMode = Calendar mode to apply, where:
'A' = Auto-select mode = Default
'G' = Gregorian
'J' = Julian
ERRORS:
FALSE is returned if an invalid argument is detected.
NO DEPENDENCIES
###########################################################################
*/
function JD_Num ($BCADDateStr, $JGAMode='A')
{
// --------------------------------------------------
// Read and adjust input date string argument format.
$BCADDateStr = PReg_Replace("/\s+/", " ", trim($BCADDateStr));
/* ---------------------------------------------------
If first character is a minus sign (negative year),
then convert it into a 'BC' calendar year string.
'-1949' becomes ---> 'BC 1949'
*/
if (substr($BCADDateStr,0,1) == '-')
{$BCADDateStr = 'BC ' . substr($BCADDateStr, 1, StrLen($BCADDateStr));}
// ---------------------------------------------------------------
// If no 'BC|AD' prefix at all, then attach a default 'AD' prefix.
$ww = StrToUpper(substr($BCADDateStr,0,2));
if ($ww <> 'BC' and $ww <> 'AD') {$BCADDateStr = "AD $BCADDateStr";}
// ------------------------------
// Read and parse date arguments.
list($BCADYear,$Month,$Day) = PReg_Split("[-]", $BCADDateStr);
// ------------------
// A few adjustments.
$BCADYear = trim($BCADYear);
$Month = trim($Month);
$Day = trim($Day);
// -----------------------------------
// Get BC|AD prefix and calendar year.
$BCAD = StrToUpper(substr($BCADYear, 0,2));
$Y = trim(substr($BCADYear, 2, StrLen($BCADYear)));
// ---------------------------------
// Adjust for BC year, if necessary.
if ($BCAD == 'BC') {$Y = -$Y;}
// ------------------------------------------------------------
// Read calendar year argument value and return FALSE on error.
$w = abs($Y); if ($w < -19999 or $w == 0 or $w > 19999) {return FALSE;}
// ---------------------------------------------------
// Read month argument. Could be a string or a number.
$m = UCFirst(substr(StrToLower(trim($Month)),0,3));
// ------------------------
// Read day argument value.
$d = trim($Day);
/* ----------------------------
Read calendar mode argument.
'G' = Gregorian | 'J' = Julian
'A' = Auto-select = Default
*/
$JGAMode = substr(StrToUpper(trim($JGAMode)),0,1);
if ($JGAMode == '') {$JGAMode = 'A';}
// -------------------------------------------------
// Define abbreviations for month and weekday names.
$MONTHS = 'JanFebMarAprMayJunJulAugSepOctNovDec';
$WEEKDAYS = 'SunMonTueWedThuFriSat';
$JGDiff = 0;
/* -----------------------------------------------------
If month is a 3-letter abbreviation ('Jan' to 'Dec'),
then replace it with the month number 1 to 12, if
possible. Otherwise, return FALSE if it cannot
resolve the abbreviation text.
*/
if (!Is_Numeric($m))
{
$i = StrPos($MONTHS, $m);
if ($i === FALSE) {return $i;}
$m = 1 + $i/3;
}
// ---------------------------------------
// Error if invalid month number.
if ($m < 1 or $m > 12) {return FALSE;}
/* -------------------------------------------------
Proceed to compute the Julian calendar JD Number.
This is the base JD Number value. If the Gregorian
calendar is selected, then the difference between
the calendars is applied to obtain the Gregorian
calendar JD Number.
*/
$A = floor((14-$m) / 12);
$B = (($Y < 0)? $Y+1 : $Y) - $A;
$C = floor($B/100);
$JDNum = floor(30.6001*(12*$A + $m + 1))
+ floor(365.25*($B + 4716)) - 1524 + $d;
/* ----------------------------------------------
Handle automatic calendar mode selection.
If calendar mode = 'A' = Auto-select, then the
calendar mode is automatically determined by
the computed JD Number value.
*/
if ($JGAMode == 'A')
{
$JGAMode = ($JDNum < 2299161)? 'J':'G';
}
/* ---------------------------------------------
Handle Gregorian (= default) calendar mode by
by ADDING the difference in days between the
Julian and Gregorian JD Numbers, if indicated
by the JGAMode setting. This value could be
negative or positive, depending on the given
date. Default Logic: If not 'J', then 'G'.
GregorianJDNum = JulianJDNum + (+-JGDiff)
*/
if ($JGAMode <> 'J')
{
$A = ($Y < 0)? $Y+1 : $Y;
$B = trim($m);
$C = $A - floor((14-$B) / 12);
$D = floor($C/100);
$JGDiff = (floor($D/4) - $D + 2);
}
$JDNum += $JGDiff;
/* -------------------------------------------------
Error if (JDNum) is outside of the valid calendar
range from 'BC 19999-Jan-01' to 'AD 19999-Dec-31'.
*/
if ($JDNum < -5583211 or $JDNum > 9025909) {return FALSE;}
/* -----------------------------------------------
Left-pad the signed JD number digits field with
spaces to span exactly 8 characters width, just
in case the values are used in a table. This
helps to arrange any (JDNum) column uniformly.
The white space can be removed by performing
a simple trim(JDNum) command, if not wanted.
*/
$JDNum = SPrintF("% 8d", $JDNum);
// DONE:
return $JDNum;
} // End of JD_Num(...)
/*
###########################################################################
This function is the inverse of the JD number function. Given any signed
JD Number, it will return the corresponding calendar date string
in 'BC|AD Yyyy-Mmm-dd-DoW' format.
CALENDAR YEAR RANGE:
BC 9999 to AD 9999
There is no calendar year 0 (zero).
Mathematical origins of the calendar systems:
BC 4713-Jan-01-Mon JDNum = 0 On old Julian calendar
BC 4714-Nov-24-Mon JDNum = 0 On modern Gregorian calendar
ARGUMENT:
JDNumber = Julian Day number for the calendar date to be computed.
JGAMode = Calendar mode
'G' = Gregorian
'J' = Julian
'A' = Auto-select mode = Default
RETURNS:
Calendar date string in 'BC|AD Yyyy-Mmm-dd-DoW' format
according to the selected calendar mode.
ERRORS:
FALSE is returned if JD number argument is non-numeric.
NO DEPENDENCIES
###########################################################################
*/
function Inv_JD_Num ($JDNumber, $JGAMode='A')
{
$JDNum = trim($JDNumber);
// -------------------------------------------
// Define Month and Day-Of-Week abbreviations.
$MONTHS = 'JanFebMarAprMayJunJulAugSepOctNovDec';
$WEEKDAYS = 'SunMonTueWedThuFriSat';
// ----------------------------
// Read calendar mode argument.
// 'G' = Gregorian = Default
// 'J' = Julian
// 'A' = Auto-select
$JGAMode = substr(StrToUpper(trim($JGAMode)),0,1);
if ($JGAMode == '') {$JGAMode = 'A';}
// ----------------------------------------------
// If calendar mode = 'A' = Auto-select, then the
// calendar mode is automatically determined by
// the given JD Number argument.
if ($JGAMode == 'A')
{
$CMode = ($JDNum < 2299161)? 0:1;
$JGAMode = ($CMode == 0)? 'J':'G';
}
else
{
$CMode = ($JGAMode == 'J')? 0:1;
}
// -----------------------------------------
// Compute numerical date elements (y, m, d)
// according to the calendar mode selection.
$A = floor($JDNum + 0.5);
$B = $CMode*floor(($A - 1867216.25) / 36524.25);
$C = $A + $CMode*($B - floor($B/4) + 1);
$D = $C + 1524;
$E = floor(($D - 122.1) / 365.25);
$F = floor(365.25 * $E);
$G = floor(($D - $F) / 30.6001);
$d = $D - $F - floor(30.6001 * $G); // Day num (1 to 31)
$m = $G - 12*floor($G/14) - 1; // Month num (1 to 12)
$y = $E - 4716 + floor((14 - $m) / 12); // Mathematical year
$Y = ($y > 0)? $y : $y-1; // Calendar year (Negative = BC)
// ------------------------------------------------------------
// At this point we have the numerical date elements (Y, m, d).
// The next step is to construct the full calendar date text
// string for output. EXAMPLE OUTPUT: 'BC 9998-May-20-Tue'
$i = (7 + ($JDNum + 1) % 7) % 7;
$DoW = substr($WEEKDAYS, 3*$i, 3);
$Y = Str_Replace('-', 'BC ', SPrintF("%+05d", $Y));
$Y = Str_Replace('+', 'AD ', $Y);
$Mmm = substr($MONTHS, 3*($m-1), 3);
$dd = SPrintf("%02d", $d);
$JDNum = SPrintF("% +8d", $JDNum);
// DONE.
return "$Y-$Mmm-$dd-$DoW";
} // End of Inv_JD_Num(...)
/*
###########################################################################
This function checks for an invalid date string and returns boolean FALSE
for an invalid date. If the date is OK, then it is returned in the same
formalized format as 'BC 9949-Nov-01', 'AD 0149-May-20' or 'AD 0009-Jun-09'
In formalized format, the year will always be expressed as 4 digits, padded
by zeros as needed, like those in the above examples.
NO DEPENDENCIES
###########################################################################
*/
function Is_Valid_Date_Str ($BCADDateStr)
{
$BCADDate = StrToUpper(trim($BCADDateStr));
// --------------------------------
// Define month name abbreviations.
$MONTHS = 'JanFebMarAprMayJunJulAugSepOctNovDec';
// --------------------------------------------------------
// Account for a negative year input and change it to 'BC'.
$BCAD = '';
if (substr($BCADDate,0,1) == '-')
{
$BCADDate = 'BC '.substr($BCADDate,1,StrLen($BCADDate));
list($BCAD, $YearMd) = PReg_Split("[ ]", $BCADDate);
list($Year,$Month,$Day) = Preg_Split("[-]", $YearMd);
$BCADDate = 'BC ' . SPrintF("%04d", $Year)."-$Month-$Day";
$BCAD = 'BC';
}
// ----------------------------------------------
// Error if missing any (-) separators.
if (Substr_Count($BCADDate, '-') <> 2) {return FALSE;}
// -------------------------------------------------
// Break up date elements. Error if missing element.
$wArray = PReg_Split("[-]", $BCADDate);
$wCount = count($wArray); if ($wCount <> 3){return FALSE;}
// -------------------------------------------
// Make year into a formal BCAD year string in
// the same format as 'AD 1776-Jul-04'
$Year = StrToUpper(trim($wArray[0]));
$Y = '';
for($i=0; $i < StrLen($Year); $i++)
{
$CurrChar = substr($Year,$i,1);
if (Is_Numeric($CurrChar)) {$Y .= $CurrChar;}
}
$FirstChar = substr($Year,0,1);
if ($FirstChar == 'B') {$BCAD = 'BC';}
if ($FirstChar == 'A') {$BCAD = 'AD';}
if (Is_Numeric($Year))
{
$BCAD = ($Year < 0)? 'BC' : 'AD';
}
if ($BCAD == '') {return FALSE;} // Error if not BC or AD.
$Year = "$BCAD ".SPrintF("%04d", $Y);
// -------------------------------------------------------
// Make numeric month into a 3-letter abbreviation string.
$Month = UCFirst(StrToLower(substr(trim($wArray[1]),0,3)));
if (!Is_Numeric($Month))
{
$i = StrPos($MONTHS, $Month); if ($i === FALSE) {return $i;}
$Month = 1 + $i/3;
$m = $Month;
}
if (Is_Numeric($Month))
{
if ($Month < 1 or $Month > 12) {return FALSE;}
$m = $Month;
$Month = substr($MONTHS, 3*($Month-1), 3);
}
$Day = trim($wArray[2]); if (!Is_Numeric($Day)) {return FALSE;}
$d = $Day; if ($d < 1 or $d > 31) {return FALSE;}
$Day = SPrintF("%02d", $Day);
// --------------------------------------------------------------
// Read year argument. (BC converts to negative year internally).
if (StrPos($Year, 'BC') !== FALSE)
{$Y = -$Y;}
/* -------------------------------------------------------
If negative year, then convert it to mathematical year.
If positive, then no adjustment is needed.
*/
$Y += ($Y < 1)? 1:0;
/* ---------------------------------------------------------
Automatically determine leap year adjustment according to
the 'J|G' = 'Julian|Gregorian' calendar mode switch.
'J' = Julian calendar for all years up to AD 1582.
'G' = Gregorian calendar for all years from AD 1583 onward.
*/
$JG = ($Y < 1583)? 'J':'G';
$LYFlag = ($JG == 'J')? $Y % 4 == 0 :
($Y % 4 == 0 and $Y % 100 <> 0) or $Y % 400 == 0;
$LYAdj = ($LYFlag)? 1:0;
// --------------------------
// Check for leap year error.
if ($Month == 'Feb' and $Day == 29 and $LYFlag === FALSE) {return FALSE;}
$FebDays = ($Month == 'Feb' and $Day == 29 and $LYFlag)? '29':'28';
// -----------------------------
// Check for day of month error.
$mDays = substr("31$FebDays"."31303130313130313031", 2*($m-1), 2);
if ($d > $mDays) {return FALSE;}
// -------------------------------------------------------
// Return reconstructed formal date string if all went OK.
// Otherwise, FALSE will be returned to indicate an error.
return "$Year-$Month-$Day";
} // End of Is_Valid_Date_Str (...)
/*
###########################################################################
Given a BC|AD calendar date string, this function returns the corresponding
mathematical date elements ('y,m,d') in a CSV format string.
y = Mathematical year (0 = BC 0001)
ARGUMENT:
BCADDateStr = BC|AD Date String
RETURNS:
The numeric date elements ('Y,m,d') are returned in a CSV string.
ERRORS:
FALSE is returned if the given year is non-numeric.
However, this function does NOT check the validity
of the numbers it returns. It merely breaks the date
string down into numbers, if in valid form and range.
*/
function BCAD_Date_Str_to_Ymd ($BCADDateStr)
{
// Read and adjust input date string argument format.
$BCADDateStr = PReg_Replace("/\s+/", " ", trim($BCADDateStr));
// Cut off any 3-letter week day abbreviation from end.
$uuu = substr($BCADDateStr, -4);
if (substr($uuu,0,1) == '-')
{$BCADDateStr = substr($BCADDateStr,0, StrLen($BCADDateStr)-4);}
// If no BC|AD prefix, then attach default AD prefix.
$ww = StrToUpper(substr($BCADDateStr,0,2));
if ($ww <> 'BC' and $ww <> 'AD') {$BCADDateStr = "AD $BCADDateStr";}
// ------------------------------------
// Error if instant bad argument format.
if (Substr_Count($BCADDateStr, '-') <> 2) {return FALSE;}
// ------------------------------
// Read and parse date arguments.
list($BCADYear,$Month,$Day) = PReg_Split("[-]", $BCADDateStr);
// ---------------------------------------------------
// A few adjustments. Month can be a number or string.
$BCADYear = trim($BCADYear);
$Month = $m = UCFirst(StrToLower(substr(trim($Month),0,3)));
$Day = $d = trim($Day);
// -----------------------------------
// Get BC|AD prefix and calendar year.
$BCAD = StrToUpper(substr($BCADYear, 0,2));
$Y = trim(substr($BCADYear, 2, StrLen($BCADYear)));
// --------------------------
// Error if Y is non-numeric.
if (!Is_Numeric($Y)) {return FALSE;}
// ---------------------------------
// Adjust for BC year, if necessary.
// Y = Calendar year number (Neg = BC).
if ($BCAD == 'BC') {$Y = -$Y;}
// ---------------------------------------------
// Error if year is 0 or outside calendar range.
// There is no year 0 (zero) on either calendar.
if ($Y < -19999 or $Y > 19999 or $Y == 0) {return FALSE;}
// -----------------------------------------------------
// If month is a 3-letter abbreviation ('Jan' to 'Dec'),
// then replace it with the month number 1 to 12, if
// possible. Otherwise, return FALSE.
if (!Is_Numeric($m))
{
$i = StrPos('JanFebMarAprMayJunJulAugSepOctNovDec', $m);
if ($i === FALSE) {return $i;}
$m = 1 + $i/3;
}
$m += 0;
$d += 0;
// ---------------------------------------
// Error if invalid month number.
if ($m < 1 or $m > 12) {return FALSE;}
// DONE.
return "$Y, $m, $d";
} // End of BCAD_Date_Str_to_Ymd (...)
/* ###########################################################################
This function returns the number of days in any given month of any given
year on the old Julian calendar or the modern Gregorian calendar.
The month can be a number (1 to 12) or a 3-letter
abbreviation ('Jan' to 'Dec').
The 'AD' prefix is optional.
-------------
DEPENDENCIES:
Is_Valid_Date()
###########################################################################
*/
function Days_in_Month ($BCADDateStr)
{
$BCADDate = trim($BCADDateStr);
$W = Is_Valid_Date_Str($BCADDate); if ($W === FALSE) {return FALSE;}
$YmdStr = BCAD_Date_Str_to_Ymd ($W);
/* -----------------------------------------------------------
Get 'Y/m/d' values corresponding to given BCAD date string.
'BC' years will be expressed as negative values. There is
no year 0 (zero) on either calendar. Note the numerical
date string element sequence is 'Y/m/d' and NOT 'm/d/Y".
*/
list($Y,$m,$d) = PReg_Split("[,]", $YmdStr);
$JG = ($Y < 1583)? 'J':'G';
$LYFlag = ($JG == 'J')? $Y % 4 == 0 :
($Y % 4 == 0 and $Y % 100 <> 0) or $Y % 400 == 0;
$LYAdj = ($LYFlag)? 1:0;
// --------------------------
// Check for leap year error.
if ($m == 2 and $d == 29 and $LYFlag === FALSE) {return FALSE;}
$FebDays = ($m == 2 and $d == 29 and $LYFlag)? '29':'28';
// ------------------------------------------------
// Check if day of month is outside of valid range.
$mDays = substr("31$FebDays"."31303130313130313031", 2*($m-1), 2);
if ($d > $mDays) {return FALSE;}
return $mDays;
}
/*
###########################################################################
###########################################################################
This function returns the simple lunar phase angle from 0 to 360 degrees
based on the difference between the geocentric ecliptical longitudes of
the moon and sun. This gives a very good visual approximation to the
geocentric lunar phase at the given local date, time and time zone.
--------------------------------------------------------------
Let:
Lm = Geocentric ecliptical longitude of the moon (0 to 360 deg)
Ls = Geocentric ecliptical longitude of the sun (0 to 360 deg)
PhaseAng = Simple lunar phase angle (0 to 360 deg)
Then:
w = 360 - Lm + Ls
if (w > 360) then w = w - 360
PhaseAng = 360 - w
or equivalently:
w = 360 - Lm + Ls
PhaseAng = 360 - (w -= (w > 360)? 360:0)
NO DEPENDENCIES
###########################################################################
*/
function Lunar_Phase_Angle ($DateTimeStr, $TimeZone='+00:00', $DaySumYN='N')
{
GLOBAL $Cnst;
$DaySumYN = substr(StrToUpper(trim($DaySumYN)),0,1);
$DaySumAdj = ($DaySumYN == '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) + $DaySumAdj;
$i = StrPos($TZHours, '.');
if ($i == FALSE) {$TZHours .= '.00';}
$i = StrPos($TZHours, '.');
$TZHH = $TZSign.SPrintF("%02d", abs(substr($TZHours,0,$i)));
$TimeZone = "$TZHH:$TZmm";
$From_Horizons_API =
"https://ssd.jpl.nasa.gov/api/horizons.api?format=text" .
"&COMMAND='301'" .
"&OBJ_DATA='NO'" .
"&MAKE_EPHEM='YES'" .
"&EPHEM_TYPE='OBSERVER'" .
"&CAL_FORMAT='CAL'" .
"&CAL_TYPE='MIXED'" .
"&REF_SYSTEM='ICRF'" .
"&APPARENT='REFRACTED'" .
"&RANGE_UNITS='AU'" .
"&CENTER='500@399'" .
"&TIME_DIGITS='SECONDS'" .
"&TIME_ZONE='$TimeZone'" .
"&START_TIME='$DateTimeStr UT'" .
"&STOP_TIME='$DateTimeStr.1'" .
"&STEP_SIZE='1d'" .
"&EXTRA_PREC='YES'" .
"&CSV_FORMAT='YES'" .
"&QUANTITIES='31,29" ;
// ---------------------------------------------------------------------
// Extract the geocentric ecliptical longitude and latitude of the moon.
$Moon = Str_Replace(",\n", " \n", trim(File_Get_Contents($From_Horizons_API)));
// ----------------------------------------------------
// Set pointers to start and end of ephemeris table and
// extract ONLY the required ephemeris CSV data line
// from between the Start/End pointers.
$i = StrPos($Moon, '$$SOE');
$j = StrPos($Moon, '$$EOE');
$Moon = trim(substr($Moon, $i+5, $j-$i-5));
$From_Horizons_API =
"https://ssd.jpl.nasa.gov/api/horizons.api?format=text" .
"&COMMAND='10'" .
"&OBJ_DATA='NO'" .
"&MAKE_EPHEM='YES'" .
"&EPHEM_TYPE='OBSERVER'" .
"&CAL_FORMAT='CAL'" .
"&CAL_TYPE='MIXED'" .
"&REF_SYSTEM='ICRF'" .
"&APPARENT='REFRACTED'" .
"&RANGE_UNITS='AU'" .
"&CENTER='500@399'" .
"&TIME_DIGITS='SECONDS'" .
"&TIME_ZONE='$TimeZone'" .
"&START_TIME='$DateTimeStr UT'" .
"&STOP_TIME='$DateTimeStr.1'" .
"&STEP_SIZE='1d'" .
"&EXTRA_PREC='YES'" .
"&CSV_FORMAT='YES'" .
"&QUANTITIES='31'" ;
// --------------------------------------------------------------------
// Extract the geocentric ecliptical longitude and latitude of the sun.
$Sun = Str_Replace(",\n", " \n", trim(File_Get_Contents($From_Horizons_API)));
/* ----------------------------------------------------
Set pointers to start and end of ephemeris table and
extract ONLY the required ephemeris CSV data line(s)
from between the Start/End pointers.
*/
$i = StrPos($Sun, '$$SOE');
$j = StrPos($Sun, '$$EOE');
$Sun = trim(substr($Sun, $i+5, $j-$i-5));
/* --------------------------------------------------------------
Extract ONLY the ecliptical longitude values for moon and sun.
The latitudes are not needed for these computations.
*/
list($w,$w,$w, $Lm,$w,$Cnst) = PReg_Split("[,]", $Moon);
list($w,$w,$w, $Ls) = PReg_Split("[,]", $Sun);
$Cnst = trim($Cnst);
// --------------------------------------------------------
// Compute the simple lunar phase angle (0 to 360 degrees).
$w = 360 - $Lm + $Ls;
$PhaseAng = 360 - ($w -= ($w > 360)? 360:0);
return $PhaseAng;
} // End of Lunar_Phase_Angle (...)
// END OF PROGRAM
?>