Files
lazarus-ccr/components/systools/source/general/run/stastro.pas

1800 lines
50 KiB
ObjectPascal
Raw Normal View History

// Upgraded to Delphi 2009: Sebastian Zierer
(* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is TurboPower SysTools
*
* The Initial Developer of the Original Code is
* TurboPower Software
*
* Portions created by the Initial Developer are Copyright (C) 1996-2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK ***** *)
{*********************************************************}
{* SysTools: StAstro.pas 4.04 *}
{*********************************************************}
{* SysTools: Astronomical Routines *}
{*********************************************************}
{$IFDEF FPC}
{$mode DELPHI}
{$ENDIF}
//{$I StDefine.inc}
{ ************************************************************** }
{ Sources: }
{ 1. Astronomical Algorithms, Jean Meeus, Willmann-Bell, 1991. }
{ }
{ 2. Planetary and Lunar Coordinates (1984-2000), U.S. Govt, }
{ 1983. }
{ }
{ 3. Supplement to the American Ephemeris and Nautical Almanac,}
{ U.S. Govt, 1964. }
{ }
{ 4. MPO96 source files, Brian D. Warner, 1995. }
{ }
{ ************************************************************** }
unit StAstro;
interface
uses
{$IFNDEF FPC}
Windows,
{$ENDIF}
SysUtils,
StConst, StBase, StDate, StStrS, StDateSt, StMath;
type
TStTwilight = (ttCivil, ttNautical, ttAstronomical);
TStRiseSetRec = record
ORise : TStTime;
OSet : TStTime;
end;
TStPosRec = record
RA,
DC : Double;
end;
TStPosRecArray = array[1..3] of TStPosRec;
TStSunXYZRec = record
SunX,
SunY,
SunZ,
RV,
SLong,
SLat : Double;
end;
TStLunarRecord = record
T : array[0..1] of TStDateTimeRec;
end;
TStPhaseRecord = packed record
NMDate,
FQDate,
FMDate,
LQDate : Double;
end;
TStPhaseArray = array[0..13] of TStPhaseRecord;
TStDLSDateRec = record
Starts : TStDate;
Ends : TStDate;
end;
TStMoonPosRec = record
RA,
DC,
Phase,
Dia,
Plx,
Elong : Double;
end;
const
radcor = 57.29577951308232; {number of degrees in a radian}
StdDate = 2451545.0; {Ast. Julian Date for J2000 Epoch}
OB2000 = 0.409092804; {J2000 obliquity of the ecliptic (radians)}
{Sun procedures/functions}
function AmountOfSunlight(LD : TStDate; Longitude, Latitude : Double) : TStTime;
{-compute the hours, min, sec of sunlight on a given date}
function FixedRiseSet(LD : TStDate; RA, DC, Longitude, Latitude : Double) : TStRiseSetRec;
{-compute the rise and set time for a fixed object, e.g., a star}
function SunPos(UT : TStDateTimeRec) : TStPosRec;
{-compute the J2000 RA/Declination of the Sun}
function SunPosPrim(UT : TStDateTimeRec) : TStSunXYZRec;
{-compute the J2000 geocentric rectangular & ecliptic coordinates of the sun}
function SunRiseSet(LD : TStDate; Longitude, Latitude : Double) : TStRiseSetRec;
{-compute the Sun rise or set time}
function Twilight(LD : TStDate; Longitude, Latitude : Double; TwiType : TStTwilight) : TStRiseSetRec;
{-compute the beginning and end of twilight (civil, nautical, or astron.)}
{Lunar procedures/functions}
function LunarPhase(UT : TStDateTimeRec) : Double;
{-compute the phase of the moon}
function MoonPos(UT : TStDateTimeRec) : TStMoonPosRec;
{-compute the J2000 RA/Declination of the moon}
function MoonRiseSet(LD : TStDate; Longitude, Latitude : Double) : TStRiseSetRec;
{-compute the Moon rise and set time}
function FirstQuarter(D : TStDate) : TStLunarRecord;
{-compute date/time of FirstQuarter(s)}
function FullMoon(D : TStDate) : TStLunarRecord;
{-compute the date/time of FullMoon(s)}
function LastQuarter(D : TStDate) : TStLunarRecord;
{-compute the date/time of LastQuarter(s)}
function NewMoon(D : TStDate) : TStLunarRecord;
{-compute the date/time of NewMoon(s)}
function NextFirstQuarter(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the next closest FirstQuarter}
function NextFullMoon(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the next closest FullMoon}
function NextLastQuarter(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the next closest LastQuarter}
function NextNewMoon(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the next closest NewMoon}
function PrevFirstQuarter(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the prev closest FirstQuarter}
function PrevFullMoon(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the prev closest FullMoon}
function PrevLastQuarter(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the prev closest LastQuarter}
function PrevNewMoon(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the prev closest NewMoon}
{Calendar procedures/functions}
function SiderealTime(UT : TStDateTimeRec) : Double;
{-compute Sidereal Time at Greenwich}
function Solstice(Y, Epoch : Integer; Summer : Boolean) : TStDateTimeRec;
{-compute the date/time of the summer or winter solstice}
function Equinox(Y, Epoch : Integer; Vernal : Boolean) : TStDateTimeRec;
{-compute the date/time of the vernal/autumnal equinox}
function Easter(Y, Epoch : Integer) : TStDate;
{-compute the date of Easter (astronmical calendar)}
{Astronomical Julian Date Conversions}
function DateTimeToAJD(D : TDateTime) : Double;
{Conversion routines}
function HoursMin(RA : Double) : String;
{-convert RA to hh:mm:ss string}
function DegsMin(DC : Double) : String;
{-convert Declination to +/-dd:mm:ss string}
function AJDToDateTime(D : Double) : TDateTime;
implementation
var
AJDOffset : Double;
function CheckDate(UT : TStDateTimeRec) : Boolean;
begin
with UT do begin
if (D < MinDate) or (D > MaxDate) or
(T < 0) or (T > MaxTime) then
Result := False
else
Result := True;
end;
end;
function CheckYear(Y, Epoch : Integer) : Integer;
begin
if Y < 100 then begin
if Y >= (Epoch mod 100) then
Result := ((Epoch div 100) * 100) + Y
else
Result := ((Epoch div 100) * 100) + 100 + Y;
end else
Result := Y;
end;
function SiderealTime(UT : TStDateTimeRec) : Double;
{-compute Sidereal Time at Greenwich in degrees}
var
T,
JD : Double;
begin
if not CheckDate(UT) then begin
Result := -1;
Exit;
end;
JD := AstJulianDate(UT.D) + UT.T/86400;
T := (JD - 2451545.0) / 36525.0;
Result := 280.46061837
+ 360.98564736629 * (JD - 2451545.0)
+ 0.000387933 * sqr(T)
- (sqr(T) * T / 38710000);
Result := Frac(Result/360.0) * 360.0;
if Result < 0 then
Result := 360 + Result;
end;
function SunPosPrim(UT : TStDateTimeRec) : TStSunXYZRec;
{-compute J2000 XYZ coordinates of the Sun}
var
JD,
T0,
A,
L,
B,
X,Y,Z : Double;
begin
if not CheckDate(UT) then begin
with Result do begin
SunX := -99;
SunY := -99;
SunZ := -99;
RV := -99;
SLong := -99;
SLat := -99;
end;
Exit;
end;
JD := AstJulianDate(UT.D) + UT.T/86400;
T0 := (JD - StdDate) / 365250;
{solar longitude}
L := 175347046
+ 3341656 * cos(4.6692568 + 6283.07585*T0)
+ 34894 * cos(4.6261000 + 12566.1517*T0)
+ 3497 * cos(2.7441000 + 5753.3849*T0)
+ 3418 * cos(2.8289000 + 3.5231*T0)
+ 3136 * cos(3.6277000 + 77713.7715*T0)
+ 2676 * cos(4.4181000 + 7860.4194*T0)
+ 2343 * cos(6.1352000 + 3930.2097*T0)
+ 1324 * cos(0.7425000 + 11506.7698*T0)
+ 1273 * cos(2.0371000 + 529.6910*T0)
+ 1199 * cos(1.1096000 + 1577.3435*T0)
+ 990 * cos(5.2330000 + 5884.9270*T0)
+ 902 * cos(2.0450000 + 26.1490*T0)
+ 857 * cos(3.5080000 + 398.149*T0)
+ 780 * cos(1.1790000 + 5223.694*T0)
+ 753 * cos(2.5330000 + 5507.553*T0)
+ 505 * cos(4.5830000 + 18849.228*T0)
+ 492 * cos(4.2050000 + 775.523*T0)
+ 357 * cos(2.9200000 + 0.067*T0)
+ 317 * cos(5.8490000 + 11790.626*T0)
+ 284 * cos(1.8990000 + 796.298*T0)
+ 271 * cos(0.3150000 + 10977.079*T0)
+ 243 * cos(0.3450000 + 5486.778*T0)
+ 206 * cos(4.8060000 + 2544.314*T0)
+ 205 * cos(1.8690000 + 5573.143*T0)
+ 202 * cos(2.4580000 + 6069.777*T0)
+ 156 * cos(0.8330000 + 213.299*T0)
+ 132 * cos(3.4110000 + 2942.463*T0)
+ 126 * cos(1.0830000 + 20.775*T0)
+ 115 * cos(0.6450000 + 0.980*T0)
+ 103 * cos(0.6360000 + 4694.003*T0)
+ 102 * cos(0.9760000 + 15720.839*T0)
+ 102 * cos(4.2670000 + 7.114*T0)
+ 99 * cos(6.2100000 + 2146.170*T0)
+ 98 * cos(0.6800000 + 155.420*T0)
+ 86 * cos(5.9800000 +161000.690*T0)
+ 85 * cos(1.3000000 + 6275.960*T0)
+ 85 * cos(3.6700000 + 71430.700*T0)
+ 80 * cos(1.8100000 + 17260.150*T0);
A := 628307584999.0
+ 206059 * cos(2.678235 + 6283.07585*T0)
+ 4303 * cos(2.635100 + 12566.1517*T0)
+ 425 * cos(1.590000 + 3.523*T0)
+ 119 * cos(5.796000 + 26.298*T0)
+ 109 * cos(2.966000 + 1577.344*T0)
+ 93 * cos(2.590000 + 18849.23*T0)
+ 72 * cos(1.140000 + 529.69*T0)
+ 68 * cos(1.870000 + 398.15*T0)
+ 67 * cos(4.410000 + 5507.55*T0)
+ 59 * cos(2.890000 + 5223.69*T0)
+ 56 * cos(2.170000 + 155.42*T0)
+ 45 * cos(0.400000 + 796.30*T0)
+ 36 * cos(0.470000 + 775.52*T0)
+ 29 * cos(2.650000 + 7.11*T0)
+ 21 * cos(5.340000 + 0.98*T0)
+ 19 * cos(1.850000 + 5486.78*T0)
+ 19 * cos(4.970000 + 213.30*T0)
+ 17 * cos(2.990000 + 6275.96*T0)
+ 16 * cos(0.030000 + 2544.31*T0);
L := L + (A * T0);
A := 8722 * cos(1.0725 + 6283.0758*T0)
+ 991 * cos(3.1416)
+ 295 * cos(0.437 + 12566.1520*T0)
+ 27 * cos(0.050 + 3.52*T0)
+ 16 * cos(5.190 + 26.30*T0)
+ 16 * cos(3.69 + 155.42*T0)
+ 9 * cos(0.30 + 18849.23*T0)
+ 9 * cos(2.06 + 77713.77*T0);
L := L + (A * sqr(T0));
A := 289 * cos(5.842 + 6283.076*T0)
+ 21 * cos(6.05 + 12566.15*T0)
+ 3 * cos(5.20 + 155.42*T0)
+ 3 * cos(3.14);
L := L + (A * sqr(T0) * T0);
L := L / 1.0E+8;
{solar latitude}
B := 280 * cos(3.199 + 84334.662*T0)
+ 102 * cos(5.422 + 5507.553*T0)
+ 80 * cos(3.88 + 5223.69*T0)
+ 44 * cos(3.70 + 2352.87*T0)
+ 32 * cos(4.00 + 1577.34*T0);
B := B / 1.0E+8;
A := 227778 * cos(3.413766 + 6283.07585*T0)
+ 3806 * cos(3.3706 + 12566.1517*T0)
+ 3620
+ 72 * cos(3.33 + 18849.23*T0)
+ 8 * cos(3.89 + 5507.55*T0)
+ 8 * cos(1.79 + 5223.69*T0)
+ 6 * cos(5.20 + 2352.87*T0);
B := B + (A * T0 / 1.0E+8);
A := 9721 * cos(5.1519 + 6283.07585*T0)
+ 233 * cos(3.1416)
+ 134 * cos(0.644 + 12566.152*T0)
+ 7 * cos(1.07 + 18849.23*T0);
B := B + (A * sqr(T0) / 1.0E+8);
A := 276 * cos(0.595 + 6283.076*T0)
+ 17 * cos(3.14)
+ 4 * cos(0.12 + 12566.15*T0);
B := B + (A * sqr(T0) * T0 / 1.0E+8);
{solar radius vector (astronomical units)}
Result.RV := 100013989
+ 1670700 * cos(3.0984635 + 6283.07585*T0)
+ 13956 * cos(3.05525 + 12566.15170*T0)
+ 3084 * cos(5.1985 + 77713.7715*T0)
+ 1628 * cos(1.1739 + 5753.3849*T0)
+ 1576 * cos(2.8649 + 7860.4194*T0)
+ 925 * cos(5.453 + 11506.770*T0)
+ 542 * cos(4.564 + 3930.210*T0)
+ 472 * cos(3.661 + 5884.927*T0)
+ 346 * cos(0.964 + 5507.553*T0)
+ 329 * cos(5.900 + 5223.694*T0)
+ 307 * cos(0.299 + 5573.143*T0)
+ 243 * cos(4.273 + 11790.629*T0)
+ 212 * cos(5.847 + 1577.344*T0)
+ 186 * cos(5.022 + 10977.079*T0)
+ 175 * cos(3.012 + 18849.228*T0)
+ 110 * cos(5.055 + 5486.778*T0)
+ 98 * cos(0.89 + 6069.78*T0)
+ 86 * cos(5.69 + 15720.84*T0)
+ 86 * cos(1.27 +161000.69*T0)
+ 65 * cos(0.27 + 17260.15*T0)
+ 63 * cos(0.92 + 529.69*T0)
+ 57 * cos(2.01 + 83996.85*T0)
+ 56 * cos(5.24 + 71430.70*T0)
+ 49 * cos(3.25 + 2544.31*T0)
+ 47 * cos(2.58 + 775.52*T0)
+ 45 * cos(5.54 + 9437.76*T0)
+ 43 * cos(6.01 + 6275.96*T0)
+ 39 * cos(5.36 + 4694.00*T0)
+ 38 * cos(2.39 + 8827.39*T0)
+ 37 * cos(0.83 + 19651.05*T0)
+ 37 * cos(4.90 + 12139.55*T0)
+ 36 * cos(1.67 + 12036.46*T0)
+ 35 * cos(1.84 + 2942.46*T0)
+ 33 * cos(0.24 + 7084.90*T0)
+ 32 * cos(0.18 + 5088.63*T0)
+ 32 * cos(1.78 + 398.15*T0)
+ 28 * cos(1.21 + 6286.60*T0)
+ 28 * cos(1.90 + 6279.55*T0)
+ 26 * cos(4.59 + 10447.39*T0);
Result.RV := Result.RV / 1.0E+8;
A := 103019 * cos(1.107490 + 6283.075850*T0)
+ 1721 * cos(1.0644 + 12566.1517*T0)
+ 702 * cos(3.142)
+ 32 * cos(1.02 + 18849.23*T0)
+ 31 * cos(2.84 + 5507.55*T0)
+ 25 * cos(1.32 + 5223.69*T0)
+ 18 * cos(1.42 + 1577.34*T0)
+ 10 * cos(5.91 + 10977.08*T0)
+ 9 * cos(1.42 + 6275.96*T0)
+ 9 * cos(0.27 + 5486.78*T0);
Result.RV := Result.RV + (A * T0 / 1.0E+8);
A := 4359 * cos(5.7846 + 6283.0758*T0)
+ 124 * cos(5.579 + 12566.152*T0)
+ 12 * cos(3.14)
+ 9 * cos(3.63 + 77713.77*T0)
+ 6 * cos(1.87 + 5573.14*T0)
+ 3 * cos(5.47 + 18849.23*T0);
Result.RV := Result.RV + (A * sqr(T0) / 1.0E+8);
L := (L + PI);
L := Frac(L / 2.0 / PI) * 2.0 * Pi;
if L < 0 then
L := L + (2.0*PI);
B := -B;
Result.SLong := L * radcor;
Result.SLat := B * radcor;
X := Result.RV * cos(B) * cos(L);
Y := Result.RV * cos(B) * sin(L);
Z := Result.RV * sin(B);
Result.SunX := X + 4.40360E-7 * Y - 1.90919E-7 * Z;
Result.SunY := -4.79966E-7 * X + 0.917482137087 * Y - 0.397776982902 * Z;
Result.SunZ := 0.397776982902 * Y + 0.917482137087 * Z;
end;
function MoonPosPrim(UT : TStDateTimeRec) : TStMoonPosRec;
{-computes J2000 coordinates of the moon}
var
JD,
TD,
JCent,
JC2, JC3, JC4,
LP, D,
M, MP,
F, I,
A1,A2,A3,
MoonLong,
MoonLat,
MoonDst,
S1,C1,
SunRA,
SunDC,
EE,EES : Double;
SP : TStSunXYZRec;
begin
JD := AstJulianDate(UT.D) + UT.T/86400;
JCent := (JD - 2451545) / 36525;
JC2 := sqr(JCent);
JC3 := JC2 * JCent;
JC4 := sqr(JC2);
SP := SunPosPrim(UT);
SunRA := StInvTan2(SP.SunX, SP.SunY) * radcor;
SunDC := StInvSin(SP.SunZ / SP.RV) * radcor;
{Lunar mean longitude}
LP := 218.3164591 + (481267.88134236 * JCent)
- (1.3268E-3 * JC2) + (JC3 / 538841) - (JC4 / 65194000);
LP := Frac(LP/360) * 360;
if LP < 0 then
LP := LP + 360;
LP := LP/radcor;
{Lunar mean elongation}
D := 297.8502042 + (445267.1115168 * JCent)
- (1.63E-3 * JC2) + (JC3 / 545868) - (JC4 / 113065000);
D := Frac(D/360) * 360;
if D < 0 then
D := D + 360;
D := D/radcor;
{Solar mean anomaly}
M := 357.5291092 + (35999.0502909 * JCent)
- (1.536E-4 * JC2) + (JC3 / 24490000);
M := Frac(M/360) * 360;
if M < 0 then
M := M + 360;
M := M/radcor;
{Lunar mean anomaly}
MP := 134.9634114 + (477198.8676313 * JCent)
+ (8.997E-3 * JC2) + (JC3 / 69699) - (JC4 / 14712000);
MP := Frac(MP/360) * 360;
if MP < 0 then
MP := MP + 360;
MP := MP/radcor;
{Lunar argument of latitude}
F := 93.2720993 + (483202.0175273 * JCent)
- (3.4029E-3 * JC2) - (JC3 / 3526000) + (JC4 / 863310000);
F := Frac(F/360) * 360;
if F < 0 then
F := F + 360;
F := F/radcor;
{Other required arguments}
A1 := 119.75 + (131.849 * JCent);
A1 := Frac(A1/360) * 360;
if A1 < 0 then
A1 := A1 + 360;
A1 := A1/radcor;
A2 := 53.09 + (479264.290 * JCent);
A2 := Frac(A2/360) * 360;
if A2 < 0 then
A2 := A2 + 360;
A2 := A2/radcor;
A3 := 313.45 + (481266.484 * JCent);
A3 := Frac(A3/360) * 360;
if A3 < 0 then
A3 := A3 + 360;
A3 := A3/radcor;
{Earth's orbital eccentricity}
EE := 1.0 - (2.516E-3 * JCent) - (7.4E-6 * JC2);
EES := sqr(EE);
MoonLong := 6288774 * sin(MP)
+ 1274027 * sin(2*D - MP)
+ 658314 * sin(2*D)
+ 213618 * sin(2*MP)
- 185116 * sin(M) * EE
- 114332 * sin(2*F)
+ 58793 * sin(2*(D-MP))
+ 57066 * sin(2*D-M-MP) * EE
+ 53322 * sin(2*D-MP)
+ 45758 * sin(2*D-M) * EE
- 40923 * sin(M-MP) * EE
- 34720 * sin(D)
- 30383 * sin(M+MP) * EE
+ 15327 * sin(2*(D-F))
- 12528 * sin(MP+2*F)
+ 10980 * sin(MP-2*F)
+ 10675 * sin(4*D-MP)
+ 10034 * sin(3*MP)
+ 8548 * sin(4*D-2*MP)
- 7888 * sin(2*D+M-MP) * EE
- 6766 * sin(2*D+M) * EE
- 5163 * sin(D-MP)
+ 4987 * sin(D+M) * EE
+ 4036 * sin(2*D-M+MP) * EE
+ 3994 * sin(2*(D+MP))
+ 3861 * sin(4*D)
+ 3665 * sin(2*D-3*MP)
- 2689 * sin(M-2*MP) * EE
- 2602 * sin(2*D-MP+2*F)
+ 2390 * sin(2*D-M-2*MP) * EE
- 2348 * sin(D-MP)
+ 2236 * sin(2*(D-M)) * EES
- 2120 * sin(M-2*MP) * EE
- 2069 * sin(2*M) * EE
+ 2048 * sin(2*D-2*M-MP) * EES
- 1773 * sin(2*D+MP-2*F)
- 1595 * sin(2*(D+F))
+ 1215 * sin(4*D-M-MP) * EE
- 1110 * sin(2*(MP+F))
- 892 * sin(3*D-MP)
- 810 * sin(2*D-M-MP) * EE
+ 759 * sin(4*D-M-2*MP) * EE
- 713 * sin(2*M-MP) * EE
- 700 * sin(2*D+2*M-MP) * EES
+ 691 * sin(2*D+M-2*MP) * EE
+ 596 * sin(2*D-M-2*F) * EE
+ 549 * sin(4*D+MP)
+ 537 * sin(4*MP)
+ 520 * sin(4*D-M) * EE;
MoonDst := - 20905355 * cos(MP)
- 3699111 * cos(2*D - MP)
- 2955968 * cos(2*D)
- 569925 * cos(2*MP)
+ 48888 * cos(M) * EE
- 3149 * cos(2*F)
+ 246158 * cos(2*(D-MP))
- 152138 * cos(2*D-M-MP) * EE
- 170733 * cos(2*D-MP)
- 204586 * cos(2*D-M) * EE
- 129620 * cos(M-MP) * EE
+ 108743 * cos(D)
+ 104755 * cos(M-MP) * EE
+ 10321 * cos(2*D-2*F)
+ 79661 * cos(MP-2*F)
- 34782 * cos(4*D-MP)
- 23210 * cos(3*MP)
- 21636 * cos(4*D-2*MP)
+ 24208 * cos(2*D+M-MP) * EE
+ 30824 * cos(2*D-M) * EE
- 8379 * cos(D-MP)
- 16675 * cos(D+M) * EE
- 12831 * cos(2*D-M+MP) * EE
- 10445 * cos(2*D+2*MP)
- 11650 * cos(4*D) * EE
+ 14403 * cos(2*D+3*MP)
- 7003 * cos(M-2*MP) * EE
+ 10056 * cos(2*D-M-2*MP) * EE
+ 6322 * cos(D+MP)
- 9884 * cos(2*D-2*M) * EES
+ 5751 * cos(M+2*MP) * EE
- 4950 * cos(2*D-2*M-MP) * EES
+ 4130 * cos(2*D+MP+2*F)
- 3958 * cos(4*D-M-MP) * EE
+ 3258 * cos(3*D-MP)
+ 2616 * cos(2*D+M+MP) * EE
- 1897 * cos(4*D-M-2*MP) * EE
- 2117 * cos(2*M-MP) * EES
+ 2354 * cos(2*D+2*M-MP) * EES
- 1423 * cos(4*D+MP)
- 1117 * cos(4*MP)
- 1571 * cos(4*D-M) * EE
- 1739 * cos(D-2*MP)
- 4421 * cos(2*MP-2*F)
+ 1165 * cos(2*M+MP)
+ 8752 * cos(2*D-MP-2*F);
MoonLat := 5128122 * sin(F)
+ 280602 * sin(MP+F)
+ 277693 * sin(MP-F)
+ 173237 * sin(2*D-F)
+ 55413 * sin(2*D-MP+F)
+ 46271 * sin(2*D-MP-F)
+ 32573 * sin(2*D+F)
+ 17198 * sin(2*MP+F)
+ 9266 * sin(2*D+MP-F)
+ 8822 * sin(2*MP-F)
+ 8216 * sin(2*D-M-F) * EE
+ 4324 * sin(2*D-2*MP-F)
+ 4200 * sin(2*D+MP+F)
- 3359 * sin(2*D+M-F) * EE
+ 2463 * sin(2*D-M-MP+F) * EE
+ 2211 * sin(2*D-M+F) * EE
+ 2065 * sin(2*D-M-MP-F) * EE
- 1870 * sin(M-MP-F) * EE
+ 1828 * sin(4*D-MP-F)
- 1794 * sin(M+F) * EE
- 1749 * sin(3*F)
- 1565 * sin(M-MP+F) * EE
- 1491 * sin(D+F)
- 1475 * sin(M+MP+F) * EE
- 1410 * sin(M+MP-F) * EE
- 1344 * sin(M-F) * EE
- 1335 * sin(D-F)
+ 1107 * sin(3*MP+F)
+ 1021 * sin(4*D-F)
+ 833 * sin(4*D-MP+F)
+ 777 * sin(MP-3*F)
+ 671 * sin(4*D-2*MP+F)
+ 607 * sin(2*D-3*F)
+ 596 * sin(2*D+2*MP-F)
+ 491 * sin(2*D-M+MP-F) * EE
- 451 * sin(2*D-2*MP+F)
+ 439 * sin(3*MP-F)
+ 422 * sin(2*D+2*MP+F)
+ 421 * sin(2*D-3*MP-F)
- 366 * sin(2*D+M-MP+F) * EE
- 351 * sin(2*D+M+F) * EE
+ 331 * sin(4*D+F)
+ 315 * sin(2*D-M+MP+F) * EE
+ 302 * sin(2*D-2*M-F) * EES
- 283 * sin(MP + 3*F)
- 229 * sin(2*D+M+MP-F) * EE
+ 223 * sin(D+M-F) * EE
+ 223 * sin(D+M+F) * EE;
MoonLong := MoonLong
+ 3958 * sin(A1)
+ 1962 * sin(LP-F)
+ 318 * sin(A2);
MoonLat := MoonLat
- 2235 * sin(LP)
+ 382 * sin(A3)
+ 175 * sin(A1-F)
+ 175 * sin(A1+F)
+ 127 * sin(LP-MP)
- 115 * sin(LP+MP);
MoonLong := LP + MoonLong/1000000/radcor;
MoonLat := MoonLat/1000000/radcor;
MoonDst := 385000.56 + MoonDst/1000;
Result.Plx := StInvSin(6378.14/MoonDst) * radcor;
Result.Dia := 358473400 / MoonDst * 2 / 3600;
S1 := sin(MoonLong) * cos(OB2000) - StTan(MoonLat) * sin(OB2000);
C1 := cos(MoonLong);
Result.RA := StInvTan2(C1, S1) * radcor;
TD := sin(MoonLat) * cos(OB2000)
+ cos(MoonLat) * sin(OB2000) * sin(MoonLong);
TD := StInvSin(TD);
Result.DC := TD * radcor;
I := sin(SunDC/radcor) * sin(TD)
+ cos(SunDC/radcor) * cos(TD) * cos((SunRA-Result.RA)/radcor);
Result.Phase := (1 - I) / 2;
I := StInvCos(I) * radcor;
Result.Elong := (Result.RA - SunRA);
if Result.Elong < 0 then
Result.Elong := 360 + Result.Elong;
if Result.Elong >= 180 then begin
Result.Phase := -Result.Phase; {waning moon}
Result.Elong := -I
end else
Result.Elong := I;
end;
function AmountOfSunlight(LD : TStDate; Longitude, Latitude : Double): TStTime;
{-compute the hours, min, sec of sunlight on a given date}
var
RS : TStRiseSetRec;
begin
RS := SunRiseSet(LD, Longitude, Latitude);
with RS do begin
if ORise = -3 then begin
{sun is always above the horizon}
Result := SecondsInDay;
Exit;
end;
if ORise = -2 then begin
{sun is always below horizon}
Result := 0;
Exit;
end;
if (ORise > -1) then begin
if (OSet > -1) then
Result := OSet - ORise
else
Result := SecondsInDay - ORise;
end else begin
if (OSet > -1) then
Result := OSet
else
Result := 0;
end;
end;
if (Result < 0) then
Result := Result + SecondsInDay
else if (Result >= SecondsInDay) then
Result := Result - SecondsInDay;
end;
function SunPos(UT : TStDateTimeRec) : TStPosRec;
{-compute the RA/Declination of the Sun}
var
SP : TStSunXYZRec;
begin
if not CheckDate(UT) then begin
Result.RA := -1;
Result.DC := -99;
Exit;
end;
SP := SunPosPrim(UT);
Result.RA := StInvTan2(SP.SunX, SP.SunY) * radcor;
Result.DC := StInvSin(SP.SunZ / SP.RV) * radcor;
end;
function RiseSetPrim(LD : TStDate;
Longitude, Latitude, H0 : Double;
PR : TStPosRecArray;
ApproxOnly : Boolean) : TStRiseSetRec;
{-primitive routine for finding rise/set times}
var
ST,
NST,
HA,
LatR,
N1,
N2,
N3,
TTran,
TRise,
TSet,
TV1,
TV2,
A1,
A2,
DeltaR,
DeltaS,
RA,
DC,
Alt : Double;
ICount : SmallInt;
UT : TStDateTimeRec;
begin
H0 := H0/radcor;
UT.D := LD;
UT.T := 0;
ST := SiderealTime(UT);
LatR := Latitude/radcor;
{check if object never rises/sets}
N1 := sin(H0) - sin(LatR) * sin(PR[2].DC/radcor);
N2 := cos(LatR) * cos(PR[2].DC/radcor);
HA := N1 / N2;
if (abs(HA) >= 1) then begin
{ if ((Latitude - 90) >= 90) then begin}
if (HA > 0) then begin
{object never rises}
Result.ORise := -2;
Result.OSet := -2;
end else begin
{object never sets, i.e., it is circumpolar}
Result.ORise := -3;
Result.OSet := -3;
end;
Exit;
end;
HA := StInvCos(HA) * radcor;
if HA > 180 then
HA := HA - 180;
if HA < 0 then
HA := HA + 180;
{compute approximate hour angle at transit}
TTran := (PR[2].RA - Longitude - ST) / 360;
if abs(TTran) >= 1 then
TTran:= Frac(TTran);
if TTran < 0 then
TTran := TTran + 1;
TRise := TTran - HA/360;
TSet := TTran + HA/360;
if abs(TRise) >= 1 then
TRise:= Frac(TRise);
if TRise < 0 then
TRise := TRise + 1;
if abs(TSet) >= 1 then
TSet := Frac(TSet);
if TSet < 0 then
TSet := TSet + 1;
if not ApproxOnly then begin
{refine rise time by interpolation/iteration}
ICount := 0;
TV1 := 0;
A1 := 0;
repeat
NST := ST + 360.985647 * TRise;
NST := Frac(NST / 360.0) * 360;
N1 := PR[2].RA - PR[1].RA;
N2 := PR[3].RA - PR[2].RA;
N3 := N2 - N1;
RA := PR[2].RA + TRise/2 * (N1 + N2 + TRise*N3);
N1 := PR[2].DC - PR[1].DC;
N2 := PR[3].DC - PR[2].DC;
N3 := N2 - N1;
DC := PR[2].DC + TRise/2 * (N1 + N2 + TRise*N3);
DC := DC/radcor;
HA := (NST + Longitude - RA) / radcor;
Alt := StInvSin(sin(LatR) * sin(DC) + cos(LatR) * cos(DC) * cos(HA));
DeltaR := ((Alt - H0) * radcor) / (360 * cos(DC) * cos(LatR) * sin(HA));
TRise := TRise + DeltaR;
Inc(ICount);
if (ICount > 3) and (abs(DeltaR) >= 0.0005) then begin
if (ICount = 4) then begin
TV1 := TRise;
A1 := (Alt-H0) * radcor;
end else if (ICount = 5) then begin
TV2 := TRise;
A2 := (Alt-H0) * radcor;
TRise := TV1 + (A1 / A2) * (TV1 - TV2);
break;
end;
end;
until (abs(DeltaR) < 0.0005); {0.0005d = 0.72 min}
{refine set time by interpolation/iteration}
ICount := 0;
TV1 := 0;
A1 := 0;
repeat
NST := ST + 360.985647 * TSet;
NST := Frac(NST / 360.0) * 360;
N1 := PR[2].RA - PR[1].RA;
N2 := PR[3].RA - PR[2].RA;
N3 := N2 - N1;
RA := PR[2].RA + TSet/2 * (N1 + N2 + TSet*N3);
N1 := PR[2].DC - PR[1].DC;
N2 := PR[3].DC - PR[2].DC;
N3 := N2 - N1;
DC := PR[2].DC + TSet/2 * (N1 + N2 + TSet*N3);
DC := DC/radcor;
HA := (NST + Longitude - RA) / radcor;
Alt := StInvSin(sin(LatR) * sin(DC) + cos(LatR) * cos(DC) * cos(HA));
DeltaS := ((Alt - H0) * radcor) / (360 * cos(DC) * cos(LatR) * sin(HA));
TSet := TSet + DeltaS;
Inc(ICount);
if (ICount > 3) and (abs(DeltaS) >= 0.0005) then begin
if (ICount = 4) then begin
TV1 := TSet;
A1 := (Alt-H0) * radcor;
end else if (ICount = 5) then begin
TV2 := TSet;
A2 := (Alt-H0) * radcor;
TSet := TV1 + (A1 / A2) * (TV1 - TV2);
break;
end;
end;
until (abs(DeltaS) < 0.0005); {0.0005d = 0.72 min}
end;
if (TRise >= 0) and (TRise < 1) then
Result.ORise := Trunc(TRise * SecondsInDay)
else begin
if TRise < 0 then
Result.ORise := Trunc((TRise + 1) * SecondsInDay)
else
Result.ORise := Trunc(Frac(TRise) * SecondsInDay);
end;
if Result.ORise < 0 then
Inc(Result.ORise, SecondsInDay);
if Result.ORise >= SecondsInDay then
Dec(Result.ORise, SecondsInDay);
if (TSet >= 0) and (TSet < 1) then
Result.OSet := Trunc(TSet * SecondsInDay)
else begin
if TSet < 0 then
Result.OSet := Trunc((TSet + 1) * SecondsInDay)
else
Result.OSet := Trunc(Frac(TSet) * SecondsInDay);
end;
if Result.OSet < 0 then
Inc(Result.OSet, SecondsInDay);
if Result.OSet >= SecondsInDay then
Dec(Result.OSet, SecondsInDay);
end;
function SunRiseSet(LD : TStDate; Longitude, Latitude : Double) : TStRiseSetRec;
{-compute the Sun rise or set time}
{the value for H0 accounts for approximate refraction of 0.5667 deg. and}
{that rise or set is based on the upper limb instead of the center of the solar disc}
var
I : Integer;
H0 : Double;
UT : TStDateTimeRec;
RP : TStPosRecArray;
begin
Dec(LD);
H0 := -0.8333;
UT.T := 0;
UT.D := LD-1;
if CheckDate(UT) then begin
UT.D := UT.D + 2;
if (not CheckDate(UT)) then begin
Result.ORise := -4;
Result.OSet := -4;
Exit;
end else
UT.D := UT.D-2;
end else begin
Result.ORise := -4;
Result.OSet := -4;
Exit;
end;
for I := 1 to 3 do begin
RP[I] := SunPos(UT);
if I >= 2 then begin
if RP[I].RA < RP[I-1].RA then
RP[I].RA := RP[I].RA + 360;
end;
Inc(UT.D);
end;
Result := RiseSetPrim(LD, Longitude, Latitude, H0, RP, False);
end;
function Twilight(LD : TStDate; Longitude, Latitude : Double;
TwiType : TStTwilight) : TStRiseSetRec;
{-compute the beginning or end of twilight}
{twilight computations are based on the zenith distance of the center }
{of the solar disc.}
{Civil = 6 deg. below the horizon}
{Nautical = 12 deg. below the horizon}
{Astronomical = 18 deg. below the horizon}
var
I : Integer;
H0 : Double;
UT : TStDateTimeRec;
RP : TStPosRecArray;
begin
UT.D := LD-1;
UT.T := 0;
if CheckDate(UT) then begin
UT.D := UT.D + 2;
if (not CheckDate(UT)) then begin
Result.ORise := -4;
Result.OSet := -4;
Exit;
end else
UT.D := UT.D-2;
end else begin
Result.ORise := -4;
Result.OSet := -4;
Exit;
end;
case TwiType of
ttCivil : H0 := -6.0;
ttNautical : H0 := -12.0;
ttAstronomical : H0 := -18.0;
else
H0 := -18.0;
end;
for I := 1 to 3 do begin
UT.D := LD + I-1;
RP[I] := SunPos(UT);
if (I > 1) then begin
if RP[I].RA < RP[I-1].RA then
RP[I].RA := RP[I].RA + 360.0;
end;
end;
Result := RiseSetPrim(LD, Longitude, Latitude, H0, RP, False);
end;
function FixedRiseSet(LD : TStDate;
RA, DC, Longitude, Latitude : Double) : TStRiseSetRec;
{-compute the rise/set time for a fixed object, e.g., star}
{the value for H0 accounts for approximate refraction of 0.5667 deg.}
{this routine does not refine the intial estimate and so may be off by five}
{minutes or so}
var
H0 : Double;
UT : TStDateTimeRec;
RP : TStPosRecArray;
begin
H0 := -0.5667;
UT.T := 0;
UT.D := LD;
if not CheckDate(UT) then begin
Result.ORise := -4;
Result.OSet := -4;
Exit;
end;
RP[2].RA := RA;
RP[2].DC := DC;
Result := RiseSetPrim(LD, Longitude, Latitude, H0, RP, True);
end;
function MoonPos(UT : TStDateTimeRec) : TStMoonPosRec;
{-compute the J2000 RA/Declination of the moon}
begin
if not CheckDate(UT) then begin
Result.RA := -1;
Result.DC := -1;
Exit;
end;
Result := MoonPosPrim(UT);
end;
function MoonRiseSet(LD : TStDate; Longitude, Latitude : Double) : TStRiseSetRec;
{compute the Moon rise and set time}
{the value for H0 accounts for approximate refraction of 0.5667 deg., }
{that rise or set is based on the upper limb instead of the center of the}
{lunar disc, and the lunar parallax. In accordance with American Ephemeris }
{practice, the phase of the moon is not taken into account, i.e., the time}
{is based on the upper limb whether it is lit or not}
var
I : Integer;
H0 : Double;
UT : TStDateTimeRec;
RP : TStPosRecArray;
MPR : TStMoonPosRec;
begin
H0 := 0.125; { default value }
Dec(LD);
UT.T := 0;
UT.D := LD;
if CheckDate(UT) then begin
UT.D := UT.D + 2;
if (not CheckDate(UT)) then begin
Result.ORise := -4;
Result.OSet := -4;
Exit;
end else
UT.D := UT.D-2;
end else begin
Result.ORise := -4;
Result.OSet := -4;
Exit;
end;
for I := 1 to 3 do begin
MPR := MoonPos(UT);
RP[I].RA := MPR.RA;
RP[I].DC := MPR.DC;
if I >= 2 then begin
if I = 2 then
H0 := 0.7275*MPR.Plx - 0.5667;
if RP[I].RA < RP[I-1].RA then
RP[I].RA := RP[I].RA + 360;
end;
Inc(UT.D);
end;
Result := RiseSetPrim(LD, Longitude, Latitude, H0, RP, False);
end;
function LunarPhase(UT : TStDateTimeRec) : Double;
{-compute the phase of the moon}
{The value is positive if between New and Full Moon}
{ negative if between Full and New Moon}
var
MPR : TStMoonPosRec;
begin
MPR := MoonPosPrim(UT);
Result := MPR.Phase;
end;
procedure GetPhases(K : Double; var PR : TStPhaseRecord);
{primitive routine to find the date/time of phases in a lunar cycle}
var
JD,
NK,
TD,
J1,
J2,
J3 : Double;
step : Integer;
E,
FP,
S1,
M1,
M2,
M3 : Double;
function AddCor : Double;
begin
AddCor := 0.000325 * sin((299.77 + 0.107408*K - 0.009173*J2)/radcor)
+ 0.000165 * sin((251.88 + 0.016321*K)/radcor)
+ 0.000164 * sin((251.83 + 26.651886*K)/radcor)
+ 0.000126 * sin((349.42 + 36.412478*K)/radcor)
+ 0.000110 * sin((84.660 + 18.206239*K)/radcor)
+ 0.000062 * sin((141.74 + 53.303771*K)/radcor)
+ 0.000060 * sin((207.14 + 2.453732*K)/radcor)
+ 0.000056 * sin((154.84 + 7.306860*K)/radcor)
+ 0.000047 * sin((34.520 + 27.261239*K)/radcor)
+ 0.000042 * sin((207.19 + 0.121824*K)/radcor)
+ 0.000040 * sin((291.34 + 1.844379*K)/radcor)
+ 0.000037 * sin((161.72 + 24.198154*K)/radcor)
+ 0.000035 * sin((239.56 + 25.513099*K)/radcor)
+ 0.000023 * sin((331.55 + 3.592518*K)/radcor);
end;
begin
NK := K;
FillChar(PR, SizeOf(TStPhaseRecord), #0);
for step := 0 to 3 do begin
K := NK + (step*0.25);
FP := Frac(K);
if FP < 0 then
FP := FP + 1;
{compute Julian Centuries}
J1 := K / 1236.85;
J2 := Sqr(J1);
J3 := J2 * J1;
{solar mean anomaly}
S1 := 2.5534
+ 29.1053569 * K
- 0.0000218 * J2
- 0.00000011 * J3;
S1 := Frac(S1 / 360.0) * 360;
if S1 < 0 then
S1 := S1 + 360.0;
{lunar mean anomaly}
M1 := 201.5643
+ 385.81693528 * K
+ 0.0107438 * J2
+ 0.00001239 * J3
- 0.000000058 * J2 * J2;
M1 := Frac(M1 / 360.0) * 360;
if M1 < 0 then
M1 := M1 + 360.0;
{lunar argument of latitude}
M2 := 160.7108
+ 390.67050274 * K
- 0.0016341 * J2
- 0.00000227 * J3
+ 0.000000011 * J2 * J2;
M2 := Frac(M2 / 360.0) * 360;
if M2 < 0 then
M2 := M2 + 360.0;
{lunar ascending node}
M3 := 124.7746
- 1.56375580 * K
+ 0.0020691 * J2
+ 0.00000215 * J3;
M3 := Frac(M3 / 360.0) * 360;
if M3 < 0 then
M3 := M3 + 360.0;
{convert to radians}
S1 := S1/radcor;
M1 := M1/radcor;
M2 := M2/radcor;
M3 := M3/radcor;
{mean Julian Date for phase}
JD := 2451550.09765
+ 29.530588853 * K
+ 0.0001337 * J2
- 0.000000150 * J3
+ 0.00000000073 * J2 * J2;
{earth's orbital eccentricity}
E := 1.0 - 0.002516 * J1 - 0.0000074 * J2;
{New Moon date time}
if FP < 0.01 then begin
TD := - 0.40720 * sin(M1)
+ 0.17241 * E * sin(S1)
+ 0.01608 * sin(2*M1)
+ 0.01039 * sin(2*M2)
+ 0.00739 * E * sin(M1-S1)
- 0.00514 * E * sin(M1 + S1)
+ 0.00208 * E * E * sin(2 * S1)
- 0.00111 * sin(M1 - 2*M2)
- 0.00057 * sin(M1 + 2*M2)
+ 0.00056 * E * sin(2*M1 + S1)
- 0.00042 * sin(3 * M1)
+ 0.00042 * E * sin(S1 + 2*M2)
+ 0.00038 * E * sin(S1 - 2*M2)
- 0.00024 * E * sin(2*(M1-S1))
- 0.00017 * sin(M3)
- 0.00007 * sin(M1 + 2*S1);
JD := JD + TD + AddCor;
PR.NMDate := JD;
end;
{Full Moon date/time}
if Abs(FP - 0.5) < 0.01 then begin
TD := - 0.40614 * sin(M1)
+ 0.17302 * E * sin(S1)
+ 0.01614 * sin(2*M1)
+ 0.01043 * sin(2*M2)
+ 0.00734 * E * sin(M1-S1)
- 0.00515 * E * sin(M1 + S1)
+ 0.00209 * E * E * sin(2 * S1)
- 0.00111 * sin(M1 - 2*M2)
- 0.00057 * sin(M1 + 2*M2)
+ 0.00056 * E * sin(2*M1 + S1)
- 0.00042 * sin(3 * M1)
+ 0.00042 * E * sin(S1 + 2*M2)
+ 0.00038 * E * sin(S1 - 2*M2)
- 0.00024 * E * sin(2*(M1-S1))
- 0.00017 * sin(M3)
- 0.00007 * sin(M1 + 2*S1);
JD := JD + TD + AddCor;
PR.FMDate := JD;
end;
{Quarters date/time}
if (abs(FP - 0.25) < 0.01) or (abs(FP - 0.75) < 0.01) then begin
TD := - 0.62801 * sin(M1)
+ 0.17172 * sin(S1) * E
- 0.01183 * sin(M1+S1) * E
+ 0.00862 * sin(2*M1)
+ 0.00804 * sin(2*M2)
+ 0.00454 * sin(M1-S1) * E
+ 0.00204 * sin(2*S1) * E * E
- 0.00180 * sin(M1 - 2*M2)
- 0.00070 * sin(M1 + 2*M2)
- 0.00040 * sin(3*M1)
- 0.00034 * sin(2*M1-S1) * E
+ 0.00032 * sin(S1 + 2*M2) * E
+ 0.00032 * sin(S1 - 2*M2) * E
- 0.00028 * sin(M1 + 2*S1) * E * E
+ 0.00027 * sin(2*M1 + S1) * E
- 0.00017 * sin(M3)
- 0.00005 * sin(M1 - S1 - 2*M2);
JD := JD + TD + AddCor;
{adjustment to computed Julian Date}
TD := 0.00306
- 0.00038 * E * cos(S1)
+ 0.00026 * cos(M1)
- 0.00002 * cos(M1-S1)
+ 0.00002 * cos(M1+S1)
+ 0.00002 * cos(2*M2);
if Abs(FP - 0.25) < 0.01 then
PR.FQDate := JD + TD
else
PR.LQDate := JD - TD;
end;
end;
end;
procedure PhasePrim(LD : TStDate; var PhaseArray : TStPhaseArray);
{-primitive phase calculation}
var
I,
D,
M,
Y : Integer;
K, TD,
LYear : Double;
begin
StDateToDMY(LD, D, M, Y);
LYear := Y - 0.05;
K := (LYear - 2000) * 12.3685;
K := Int(K);
TD := K / 12.3685 + 2000;
if TD > Y then
K := K-1;
{compute phases for each lunar cycle throughout the year}
for I := 0 to 13 do begin
GetPhases(K, PhaseArray[I]);
K := K + 1;
end;
end;
function GenSearchPhase(SD : TStDate; PV : Byte) : TStLunarRecord;
{searches for the specified phase in the given month/year expressed by SD}
var
I,
C,
LD,
LM,
LY,
TD,
TM,
TY : Integer;
ADate : TStDate;
JD : Double;
PhaseArray : TStPhaseArray;
begin
C := 0;
FillChar(Result, SizeOf(Result), $FF);
StDateToDMY(SD, LD, LM, LY);
PhasePrim(SD, PhaseArray);
for I := LM-1 to LM+1 do begin
if (PV = 0) then
JD := PhaseArray[I].NMDate
else if (PV = 1) then
JD := PhaseArray[I].FQDate
else if (PV = 2) then
JD := PhaseArray[I].FMDate
else
JD := PhaseArray[I].LQDate;
ADate := AstJulianDateToStDate(JD, True);
StDateToDMY(ADate, TD, TM, TY);
if TM < LM then
continue
else if TM = LM then begin
Result.T[C].D := ADate;
Result.T[C].T := Trunc((Frac(JD) + 0.5) * 86400);
if Result.T[C].T >= SecondsInDay then
Dec(Result.T[C].T, SecondsInDay);
Inc(C);
end;
end;
end;
function FirstQuarter(D : TStDate) : TStLunarRecord;
{-compute date/time of FirstQuarter(s)}
begin
Result := GenSearchPhase(D, 1);
end;
function FullMoon(D : TStDate) : TStLunarRecord;
{-compute the date/time of FullMoon(s)}
begin
Result := GenSearchPhase(D, 2);
end;
function LastQuarter(D: TStDate) : TStLunarRecord;
{-compute the date/time of LastQuarter(s)}
begin
Result := GenSearchPhase(D, 3);
end;
function NewMoon(D : TStDate) : TStLunarRecord;
{-compute the date/time of NewMoon(s)}
begin
Result := GenSearchPhase(D, 0);
end;
function NextPrevPhase(D : TStDate; Ph : Byte;
FindNext : Boolean) : TStDateTimeRec;
var
LD,
LM,
LY : Integer;
K,
JD,
TJD : Double;
PR : TStPhaseRecord;
OK : Boolean;
begin
if (D < MinDate) or (D > MaxDate) then begin
Result.D := BadDate;
Result.T := BadTime;
Exit;
end;
StDateToDMY(D, LD, LM, LY);
K := ((LY + LM/12 + LD/365.25) - 2000) * 12.3685 - 0.5;
if FindNext then
K := Round(K)-1
else
K := Round(K)-2;
OK := False;
TJD := AstJulianDate(D);
repeat
GetPhases(K, PR);
if (Ph = 0) then
JD := PR.NMDate
else if (Ph = 1) then
JD := PR.FQDate
else if (Ph = 2) then
JD := PR.FMDate
else
JD := PR.LQDate;
if (FindNext) then begin
if (JD > TJD) then
OK := True
else
K := K + 1;
end else begin
if (JD < TJD) then
OK := True
else
K := K - 1;
end;
until OK;
Result.D := AstJulianDateToStDate(JD, True);
if (Result.D <> BadDate) then begin
Result.T := Trunc((Frac(JD) + 0.5) * 86400);
if Result.T >= SecondsInDay then
Dec(Result.T, SecondsInDay);
end else
Result.T := BadTime;
end;
function NextFirstQuarter(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the next closest FirstQuarter}
begin
Result := NextPrevPhase(D, 1, True);
end;
function NextFullMoon(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the next closest FullMoon}
begin
Result := NextPrevPhase(D, 2, True);
end;
function NextLastQuarter(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the next closest LastQuarter}
begin
Result := NextPrevPhase(D, 3, True);
end;
function NextNewMoon(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the next closest NewMoon}
begin
Result := NextPrevPhase(D, 0, True);
end;
function PrevFirstQuarter(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the prev closest FirstQuarter}
begin
Result := NextPrevPhase(D, 1, False);
end;
function PrevFullMoon(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the prev closest FullMoon}
begin
Result := NextPrevPhase(D, 2, False);
end;
function PrevLastQuarter(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the prev closest LastQuarter}
begin
Result := NextPrevPhase(D, 3, False);
end;
function PrevNewMoon(D : TStDate) : TStDateTimeRec;
{-compute the date/time of the prev closest NewMoon}
begin
Result := NextPrevPhase(D, 0, False);
end;
{Calendar procedures/functions}
function SolEqPrim(Y : Integer; K : Byte) : TStDateTimeRec;
{primitive routine for finding equinoxes and solstices}
var
JD, TD, LY,
JCent, MA,
SLong : Double;
begin
JD := 0;
Result.D := BadDate;
Result.T := BadTime;
{the following algorithm is valid only in the range of [1000..3000 AD]}
{but is limited to returning dates in [MinYear..MaxYear]}
if (Y < MinYear) or (Y > MaxYear) then
Exit;
{compute approximate date/time for specified event}
LY := (Y - 2000) / 1000;
case K of
0 : JD := 2451623.80984
+ 365242.37404 * LY
+ 0.05169 * sqr(LY)
- 0.00411 * LY * sqr(LY)
- 0.00057 * sqr(sqr(LY));
1 : JD := 2451716.56767
+ 365241.62603 * LY
+ 0.00325 * sqr(LY)
+ 0.00888 * LY * sqr(LY)
- 0.00030 * sqr(sqr(LY));
2 : JD := 2451810.21715
+ 365242.01767 * LY
- 0.11575 * sqr(LY)
+ 0.00337 * sqr(LY) * LY
+ 0.00078 * sqr(sqr(LY));
3 : JD := 2451900.05952
+ 365242.74049 * LY
- 0.06223 * sqr(LY)
- 0.00823 * LY * sqr(LY)
+ 0.00032 * sqr(sqr(LY));
end;
{refine date/time by computing corrections due to solar longitude,}
{nutation and abberation. Iterate using the corrected time until}
{correction is less than one minute}
repeat
Result.D := AstJulianDateToStDate(JD, True);
Result.T := Trunc((Frac(JD) + 0.5) * 86400);
if Result.T >= SecondsInDay then
Dec(Result.T, SecondsInDay);
JCent := (JD - 2451545.0)/36525.0;
{approximate solar longitude - no FK5 correction}
SLong := 280.46645
+ 36000.76983 * JCent
+ 0.0003032 * sqr(JCent);
SLong := Frac((SLong)/360.0) * 360.0;
if SLong < 0 then
SLong := SLong + 360;
{Equation of the center correction}
MA := 357.52910
+ 35999.05030 * JCent;
MA := MA/radcor;
SLong := SLong
+ (1.914600 - 0.004817 * JCent - 0.000014 * sqr(JCent)) * sin(MA)
+ (0.019993 - 0.000101 * JCent) * sin(2*MA);
{approximate nutation}
TD := 125.04452
- 1934.136261 * JCent
+ 0.0020708 * sqr(JCent);
TD := TD/radcor;
TD := (-17.20 * sin(TD) - 1.32 * sin(2*SLong/radcor))/3600;
{approximate abberation - solar distance is assumed to be 1 A.U.}
SLong := SLong - (20.4989/3600) + TD;
{correction to compute Julian Date for event}
TD := 58 * sin((K*90 - SLong)/radcor);
if abs(TD) >= 0.0005 then
JD := JD + TD;
until abs(TD) < 0.0005;
end;
function Solstice(Y, Epoch : Integer; Summer : Boolean) : TStDateTimeRec;
{-compute the date/time of the summer or winter solstice}
{if Summer = True, compute astronomical summer solstice (summer in N. Hem.)}
{ = False, compute astronomical winter solstice (winter in N. Hem.)}
begin
Y := CheckYear(Y, Epoch);
if Summer then
Result := SolEqPrim(Y, 1)
else
Result := SolEqPrim(Y, 3);
end;
function Equinox(Y, Epoch : Integer; Vernal : Boolean) : TStDateTimeRec;
{-compute the date/time of the vernal/autumnal equinox}
{if Vernal = True, compute astronomical vernal equinox (spring in N. Hem.)}
{ = False, compute astronomical autumnal equinox (fall in N. Hem.)}
begin
Y := CheckYear(Y, Epoch);
if Vernal then
Result := SolEqPrim(Y, 0)
else
Result := SolEqPrim(Y, 2);
end;
function Easter(Y : Integer; Epoch : Integer) : TStDate;
{-compute the date of Easter}
var
A, B,
C, D,
E, F,
G, H,
I, K,
L, M,
N, P : LongInt;
begin
Y := CheckYear(Y, Epoch);
if (Y < MinYear) or (Y > MaxYear) then begin
Result := BadDate;
Exit;
end;
A := Y mod 19;
B := Y div 100;
C := Y mod 100;
D := B div 4;
E := B mod 4;
F := (B+8) div 25;
G := (B-F+1) div 3;
H := (19*A + B - D - G + 15) mod 30;
I := C div 4;
K := C mod 4;
L := (32 + 2*E + 2*I - H - K) mod 7;
M := (A + 11*H + 22*L) div 451;
N := (H + L - 7*M + 114) div 31;
P := (H + L - 7*M + 114) mod 31 + 1;
Result := DMYToStDate(P, N, Y, Epoch);
end;
{Conversion routines}
function HoursMin(RA : Double) : String;
{-convert RA to formatted hh:mm:ss string}
var
HR, MR : Double;
HS, MS : string[12];
begin
if abs(RA) >= 360.0 then
RA := Frac(RA / 360.0) * 360;
if RA < 0 then
RA := RA + 360.0;
HR := Int(RA / 15.0);
MR := Frac(RA / 15.0) * 60;
Str(MR:5:2, MS);
if MS = '60.00' then begin
MS := '00.00';
HR := HR + 1;
if HR = 24 then
HS := '0'
else
Str(HR:2:0, HS);
end else begin
if MS[1] = ' ' then
MS[1] := '0';
Str(Hr:2:0, HS);
end;
Result := HS + ' ' + MS;
end;
function DegsMin(DC : Double) : String;
{-convert Declination to formatted +/-dd:mm:ss string}
var
DR, MR : Double;
DS, MS : string[12];
begin
if abs(DC) > 90 then
DC := Frac(DC / 90.0) * 90.0;
DR := Int(DC);
MR := Frac(abs(DC)) * 60;
Str(MR:4:1, MS);
if MS = '60.0' then begin
MS := '00.0';
if DC >= 0 then
DR := DR + 1
else
DR := DR - 1;
end;
if abs(DC) < 10 then begin
Str(DR:2:0, DS);
DS := TrimLeadS(DS);
if DC < 0 then begin
if DC > -1 then
DS := '- 0'
else
DS := '- ' + DS[2];
end else
DS := '+ ' + DS;
end else begin
Str(DR:3:0, DS);
DS := TrimLeadS(DS);
if DC < 0 then begin
Delete(DS,1,1);
DS := '-' + DS;
end else
DS := '+' + DS;
end;
if MS[1] = ' ' then
MS[1] := '0';
Result := DS + ' ' + MS;
end;
function DateTimeToAJD(D : TDateTime) : Double;
begin
Result := D + AJDOffset;
end;
function AJDToDateTime(D : Double) : TDateTime;
begin
Result := D - AJDOffset;
end;
initialization
AJDOffSet := AstJulianDatePrim(1600, 1, 1, 0) - EncodeDate(1600, 1, 1);
end.