This is a follow-up to work provided by:
Eric Wheeler, KJ7LNW <astronomy-git@z.ewheeler.org>
Before now, the C function Astronomy_GetCurrentTime returned
the current time from the system clock, but only with whole
second resolution. Now it supports microsecond resolution on
Linux/Unix, Mac OS, and Windows.
For unsupported platforms, a compiler error will occur
to indicate that microsecond resolution is not available.
However, it is possible to define one of the following two
preprocessor symbols to work around the compiler error:
1. ASTRONOMY_ENGINE_NO_CURRENT_TIME
Excludes the function Astronomy_CurrentTime from the build.
If your project does not need to obtain the current time,
or your hardware platform does not provide current date
and time in the first place, this is likely the better option.
2. ASTRONOMY_ENGINE_WHOLE_SECOND
If your project does need to use the current date and time
for astronomy calculations, and it can tolerate whole
second resolution, this option provides a version of
Astronomy_CurrentTime that uses a call to `time(NULL)`.
Notes:
- Added unit test to confirm at least millisecond resolution.
Because these tests have to run on GitHub Actions cloud platform,
and those systems can be heavily CPU-loaded, I want to be tolerant
of resolution and avoid false failures.
- Added detection of Mac platform.
- Added preprocessor options documented above.
- On Windows, use ULARGE_INTEGER and eliminated one integer division.
- Added comments and developer documentation.
- Converted tabs to spaces in astronomy.c, for consistent code format.
Slightly different cppcheck dev 2.11 behaviors have added
another warning that I don't care about. I don't want to
have to convert callback pointers to const, then cast them
to const.
However, it did find a couple of useful cases I fixed in
astronomy.c where GravSim parameters could be made const.
Because I plan on adding metersAboveGround as a parameter
that defaults to 0.0 in the other languages, and I want
the language implementations to be reasonably consistent,
I moved the metersAboveGround parameter to the end of
the parameter list for the C version of SearchRiseSetEx.
I realized I had to rework the RiseSetEx function so that
it accepts a height above ground level, rather than a generic
altitude angle correction, because atmospheric effects are
important both for the horizon dip angle and for the amount
of refraction at ground level.
The atmosphere calculations were interesting enough that
I made them public as a new function Astronomy_Atmosphere.
This returns the idealized temperature, pressure, and relative
density of air at the given elevation above/below sea level.
This is the first step toward calculating body rise/set times
for an observer that is significantly above the ground.
It figures out the angular correction of the horizon
using both parallax and refractive correction of a light
ray from the horizon to the observer's eye.
We already had a function to search for the next time a body
reaches a certain hour angle. But we didn't have a function
to ask what the current hour angle of a body is.
This will resolve that problem, which will also answer
questions about true solar time: use the Sun as the body,
and add 12 to the hour angle, modulo 24.
Now the C function Astronomy_Ecliptic returns ecliptic
coordinates in true equinox of date instead of the
J2000 mean equinox. I'm doing this because it is a
better fit for physical phenomena that ecliptic
coordinates are often used for. For example, lunar nodes,
eclipses, phase angles, and oppositions make more sense
with true latitude and longitude of date.
I will port these changes to the other languages also.
More work standardizing the nomenclature of the
orientation systems across all language documents.
Added C functions to calculate rotation matrices
for EQJ/ECT and ECT/EQJ.
C function Astronomy_SearchLocalSolarEclipse now reports
obscuration for the peak of a solar eclipse seen at a given location.
Some of the test data from aa.usno.navy.mil looks suspiciously inaccurate.
I am finding better agreement with Fred Espenak's eclipsewise.com and
Xavier M. Jubier's xjubier.free.fr online calculators.
The larger obscuration differences are from aa.usno.navy.mil:
don@spearmint:~/github/astronomy/generate $ ./ctest -v solar_fraction
C GlobalAnnularCase(2023-10-14) obscuration error = 0.00009036, expected = 0.90638000, calculated = 0.90647036
C GlobalAnnularCase(2024-10-02) obscuration error = 0.00005246, expected = 0.86975000, calculated = 0.86980246
C GlobalAnnularCase(2027-02-06) obscuration error = 0.00007237, expected = 0.86139000, calculated = 0.86146237
C GlobalAnnularCase(2028-01-26) obscuration error = 0.00003656, expected = 0.84787000, calculated = 0.84790656
C GlobalAnnularCase(2030-06-01) obscuration error = 0.00008605, expected = 0.89163000, calculated = 0.89171605
C LocalSolarCase(2023-10-14) obscuration diff = 0.00006323, expected = 0.90638000, calculated = 0.90644323
C LocalSolarCase(2023-10-14) obscuration diff = -0.00521043, expected = 0.57800000, calculated = 0.57278957
C LocalSolarCase(2024-04-08) obscuration diff = 0.00000000, expected = 1.00000000, calculated = 1.00000000
C LocalSolarCase(2024-04-08) obscuration diff = 0.00304558, expected = 0.34000000, calculated = 0.34304558
C LocalSolarCase(2024-10-02) obscuration diff = 0.00007858, expected = 0.86975000, calculated = 0.86982858
C LocalSolarCase(2024-10-02) obscuration diff = -0.00343797, expected = 0.43600000, calculated = 0.43256203
C LocalSolarCase(2030-06-01) obscuration diff = 0.00007259, expected = 0.89163000, calculated = 0.89170259
C LocalSolarCase(2030-06-01) obscuration diff = -0.00059871, expected = 0.67240000, calculated = 0.67180129
C SolarFractionTest: PASS
I am going to rework using xjubier.free.fr in a subsequent commit.
Fixed bug in internal C function Obscuration().
It was not calculating obscuration correctly when
the second disc is completely inside the first disc,
i.e. the annular solar eclipse case.
Added C/C++ calculation of obscuration for global solar
eclipses that are total or annular at their peak
location and time. Obscuration is not calculated
for partial solar eclipses by SearchGlobalSolarEclipse.
Soon SearchLocalSolarEclipse will calculate obscuration
for partial solar eclipses at the geographic location
provided by the caller.
Starting to add support for calculating the intensity
of lunar eclipses and solar eclipses in terms of "obscuration".
This commit adds calculation of obscuration for lunar eclipses
in C/C++. The structure returned by SearchLunarEclipse and
NextLunarEclipse now includes an `obscuration` field whose value
is in the range [0, 1], indicating what fraction of the Moon's
apparent disc is covered by the Earth's umbra at the eclipse's peak.
The following C functions now support searching
in forward or reverse chronological order:
Astronomy_SearchRiseSet
Astronomy_SearchAltitude
Astronomy_SearchHourAngleEx
The function Astronomy_SearchHourAngleEx replaces
Astronomy_SearchHourAngle, adding a new `direction` parameter.
A #define for Astronomy_SearchHourAngle preserves backward
compatibility for older code.
Implementation notes:
Astronomy_SearchRiseSet and Astronomy_SearchAltitude used
to call a private function InternalSearchAltitude.
That function has been split into two functions:
BackwardSearchAltitude and ForwardSearchAltitude,
for searching both directions in time.
Fixed a bug where it was possible to report a successful
altitude event that went outside the time limit specified
by `limitDays`.
Implemented two new C functions for generic correction
of light travel time:
1. Astronomy_CorrectLightTravel
This is a completely generic solver that is passed
a pointer to a function that returns a relative
position vector for a given time.
The generic solver keeps calling the function
whose address is passed, until the light travel
solution converges. This usually takes 3 or 4 iterations.
2. Astronomy_BackdatePosition
This is a more specific solver that uses Astronomy_CorrectLightTravel
to backdate the position of a target body as seen from an
observer body at a specified observation time.
It implements the aberration option needed by Astronomy_HelioVector.
Astronomy_HelioVector now uses Astronomy_BackdatePosition,
instead of having a redundant light travel solver,
so the code isn't much larger than it was before.
Added the following functions:
GravSimTime
GravSimNumBodies
GravSimOrigin
GravSimSwap
The GravSimSwap function allows an instantaneous "undo"
of a simulation step, which required refactoring the internal
data structures of the simulator. I did this because I realized
I needed a way to undo exploration of time steps near a fixed
current time. This will make it easier to implement a
light travel time solver.
I have decided the ability to select different
collections of gravitating bodies causes far
more complexity in the code than it is worth.
So now the gravity simulator always calculates
the Sun and all planets except Pluto.
This greatly simplifies the core code, gets
a good balance between efficiency and accuracy,
and makes the test matrix much simpler.
Starting implementation of a generalized gravity simulator.
Already calculating the movement of Ceres, but with less
accuracy than I had hoped. I don't know if the lack of modeling
pull of the other asteroids has a larger effect than I expected,
or there is just something wrong with the implementation.
It makes more sense to report Jupiter's moons with
individually named structure fields rather than an array.
It reduces the overall code and documentation size,
and outside of unit testing, there are few cases
where iterating over an array of moons is more
lucid than using the names of the moons.
This is a breaking change, but hopefully very few
developers are using this function yet.
Fixing the breakage is very simple.
The existing lunar libration functions in the
other languages (C, C#, Python, JavaScript) were
calculating the Moon's ecliptic latitude and longitude
in radians, not degrees as intended. They have been fixed.
Implemented the libration function for Kotlin.
Ported the following types to the Kotlin code:
GlobalSolarEclipseInfo
EclipseEvent
LocalSolarEclipseInfo
TransitInfo
ShadowInfo
IllumInfo
AxisInfo
NodeEventKind
NodeEventInfo
Made some wording fixes in the documentation for the
other languages.
There was already an internal function for calculating
Greenwich Apparent Sidereal Time (GAST). By request,
I have exposed this function for outside users.
Added a minimal unit test to verify the function is
callable and returns the correct result for one case.
This function is already exhaustively tested by unit
tests that verify other functions that already called
this function when it was internal, so minimal testing
is sufficient in this case.
Added the following new functions to all 4 languages:
MassProduct: find the GM product for all Solar System bodies.
LagrangePoint: calculate L1..L5 state vectors for a pair of bodies.
LagrangePointFast: calculate L1..L5 state vectors given
state vectors and GM products of a pair of bodies.
In most cases, people calculating Lagrange points will just
want to pass in the bodies and not have to worry about calculating
their state vectors and masses.
Renamed Astronomy_LagrangePoint to Astronomy_LagrangePointFast.
Added new function Astronomy_LagrangePoint that accepts body enum
values instead of state vectors and masses. It knows to optimize
the precision of the calculation by calling GeoMoonState for the
Earth/Moon case.
The Lagrange point calculation is still not finished,
but L1 and L2 are working. L3 is probably correct, but there
is no test data for it.
I replaced the test data with new JPL Horizons output that
is centered on the primary body instead of the Solar System Barycenter.
This allows Astronomy_LagrangePoint() to be agnostic about
the coordinate systems of the state vectors handed to it.
I still need to get L4 and L5 calculations to match JPL Horizons
data, but it is not yet clear how to do that.
Implemented a pair of C functions for finding a series of
Moon nodes:
Astronomy_SearchMoonNode
Astronomy_NextMoonNode
Finished the C unit test "moon_nodes" that verifies
my calculations against Fred Espenak's test data.
This is a thin wrapper function for the internal
function CalcMoon, which has already been extensively
validated. It will enable outside users to search
for ascending and descending nodes of the Moon,
or to calculate ecliptic spherical coordinates for the Moon
for any other useful purpose.
See this discussion:
https://github.com/cosinekitty/astronomy/issues/150
For the case of calculating a map, where each pixel
on the map represents a different location on the Earth,
it is more efficient to factor out expensive calculation
of sidereal times, assuming the entire map represents
some phenomenon at a single moment in time.
For example, to determine whether the Moon is visible
at different places on the Earth, the following
functions can be calculated across thousands of
different (lat, lon) geographic coordinates around
the world:
ObserverVector
Rotation_EQD_HOR
Before iterating over the map pixels, a program
can call GeoMoon, then convert EQJ coordinates to EQD.
Then by passing the same time value in a loop to
ObserverVector and Rotation_EQD_HOR, the program
can calculate a vector from the observer to the Moon
in EQD coordinates, then convert EQD to HOR.
The z-coordinate of the horizontal coordinates
determines whether the Moon is above or below the
observer's horizon at that point on the Earth.
This calculation pattern performed redundant
sidereal time calculations for each pixel on the map.
I changed the code for all 4 languages to cache
sidereal time so that it only needs to be calculated
once.
In the C version of Astronomy Engine, this resulted
in a speedup factor of about 2.3 in the above use case.
(See the function MapPerformanceTest in generate/ctest.c.)
Reduce the number of redundant Earth nutation calculations
by passing astro_time_t values as pointers in more functions.
Nutation values can then be cached in the time parameter
and passed to other functions that can then avoid calculating
the same nutation again.
Nutation is an expensive calculation, so reducing this overhead
can dramatically speed up certain use cases.
This was only needed in C, because this is the only language
in which times are passed by value. In Python, C#, and JavaScript,
times are objects that are already passed by reference, and
they already benefit from this nutation recyling approach.
The following functions have had their parameters changed.
This is a breaking change, but in every case, the caller
usually just needs to change `time` to `&time`.
Astronomy_Rotation_EQD_EQJ
Astronomy_Rotation_EQD_ECL
Astronomy_Rotation_EQD_HOR
Astronomy_Rotation_EQJ_EQD
Astronomy_Rotation_EQJ_HOR
Astronomy_Rotation_ECL_EQD
Astronomy_Rotation_ECL_HOR
Astronomy_Rotation_HOR_EQD
Astronomy_Rotation_HOR_EQJ
Astronomy_Rotation_HOR_ECL
Astronomy_RotationAxis
Astronomy_VectorObserver
Added radius data for the Sun, Moon, and remaining planets.
Test the raytracer for all other bodies except the Earth and Sun.
There is a problem with Pluto that I still need to figure out.
Fixed an issue in the doxygen-to-markdown translator I wrote
(hydrogen.js): it did not handle when one #define referred
to another #define. Created a more generic markdown expansion
that works in all cases, and creates embedded hyperlinks.
Refactored the Jupiter imager to be a generic planet imager.
Added support for drawing an image of Venus.
Verified that its extreme crescent phase looks correct
for the current date.
I will add radius constants to astronomy.h for each body I support.
I don't think it's a good idea to imply that the body constants
are always going to be consecutive, or that it makes sense to
iterate over them. The caller needs to understand the body enough
to know which operations are allowed and which aren't.
So I removed the constants MIN_BODY and MAX_BODY.
Calculate the vector that points in the direction
of the body's north pole.
The unit test now checks for excessive angle
between the expected north pole vector and the
calculated north pole vector.
I'm starting to implement formulas from the IAU 2015 report:
https://astropedia.astrogeology.usgs.gov/download/Docs/WGCCRE/WGCCRE2015reprint.pdf
This is a work in progress. The unit test is partially finished.
The C function Astronomy_RotationAxis() works only for the Sun and Mercury.
I want to also return a rotation matrix that reports the rotating
frame in a way more suited to graphics work. I will add this to
the type astro_axis_t later.
I am starting to work on a function to find the position
and velocity vectors for an observer on the surface of the Earth.
I created the C function Astronomy_ObserverState(), but I don't
yet have a unit test for it.
Now the C version of Astronomy Engine supports calculating
the Earth/Moon Barycenter (EMB) state vector (position and velocity)
relative to the Earth's center (geocentric) or relative
to the Solar System Barycenter (SSB).
Astronomy_BaryState() now supports body == BODY_EMB.
Added a new function Astronomy_GeoEmbState() to calculate
the geocentric state for the EMB.
Both have been verified using test data generated by JPL Horizons.
I wanted to test the geocentric Moon state vector I
calculate for the sake of the Moon state relative to
the Solar System Barycenter (SSB). Because the geocentric
portion has such a small magnitude, I decided to go ahead
and expose GeoMoonState as part of the API, and create
a test for it specifically. I used JPL Horizons to generate
the test file GeoMoon.txt.