6464 */
6565public class MeeusCalculator extends AstronomicalCalculator {
6666
67+ /**
68+ * {@inheritDoc} This calculator's results depend only on the arguments and on settings whose setters clear the cache.
69+ */
70+ @ Override
71+ protected boolean isResultCacheable () {
72+ return true ;
73+ }
74+
6775 /**
6876 * The <a href="https://en.wikipedia.org/wiki/Julian_day">Julian day</a> of January 1, 2000, known as
6977 * <a href="https://en.wikipedia.org/wiki/Epoch_(astronomy)#J2000">J2000.0</a>.
@@ -112,6 +120,7 @@ public String getCalculatorName() {
112120 */
113121 public void setApplyDeltaT (boolean applyDeltaT ) {
114122 this .applyDeltaT = applyDeltaT ;
123+ clearResultCache ();
115124 }
116125
117126 /**
@@ -227,15 +236,66 @@ private double getSolarNoonMidnightUTC(double julianDay, double longitude, Solar
227236 * @return The UTC time of sunrise or sunset in minutes. {@link Double#NaN} if the event does not occur (e.g. polar day
228237 * / night).
229238 */
239+ /**
240+ * The part of a rise / set solution that does not depend on the zenith: the equation of time and the declination at solar noon.
241+ * Every zman of a day needs the same values, so the last ones calculated are kept in {@link #lastNoonSolution} and reused. The
242+ * key is every input they depend on (the Julian day, the longitude and {@link #isApplyDeltaT()}), so a cached value is only ever
243+ * returned for identical inputs, and the results are bit-for-bit identical to calculating them each time. Immutable, so it can be
244+ * shared between threads and clones.
245+ */
246+ private static final class NoonSolution {
247+ final double julianDay ;
248+ final double longitude ;
249+ final boolean applyDeltaT ;
250+ final double equationOfTime ;
251+ final double solarDeclination ;
252+
253+ NoonSolution (double julianDay , double longitude , boolean applyDeltaT , double equationOfTime , double solarDeclination ) {
254+ this .julianDay = julianDay ;
255+ this .longitude = longitude ;
256+ this .applyDeltaT = applyDeltaT ;
257+ this .equationOfTime = equationOfTime ;
258+ this .solarDeclination = solarDeclination ;
259+ }
260+ }
261+
262+ /**
263+ * The last {@link NoonSolution} calculated. A single entry (callers almost always move one day at a time), so memory use is fixed
264+ * and nothing accumulates. Swapped atomically as one immutable object, so concurrent callers at most recalculate, and can never see a
265+ * partial or mismatched entry. Not part of {@link #equals(Object)} or {@link #hashCode()}.
266+ */
267+ private volatile NoonSolution lastNoonSolution ;
268+
269+ /**
270+ * Returns the noon solution for the day and longitude, reusing the last one when every input matches.
271+ * @param julianDay the Julian day
272+ * @param longitude the longitude
273+ * @return the noon solution
274+ */
275+ private NoonSolution getNoonSolution (double julianDay , double longitude ) {
276+ boolean applyDeltaTNow = applyDeltaT ; // read the setting once, so the key and the calculation use the same value
277+ NoonSolution cached = lastNoonSolution ;
278+ if (cached != null && cached .julianDay == julianDay && cached .longitude == longitude && cached .applyDeltaT == applyDeltaTNow ) {
279+ return cached ;
280+ }
281+ double noonmin = getSolarNoonMidnightUTC (julianDay , longitude , SolarEvent .NOON );
282+ double tnoon = getJulianCenturiesFromJulianDay (julianDay + noonmin / 1440.0 );
283+ double [] sun = getSunApparentEclipticCoordinates (tnoon ); // shared by the equation of time and the declination
284+ NoonSolution solution = new NoonSolution (julianDay , longitude , applyDeltaTNow , getEquationOfTime (tnoon , sun ), getSunDeclination (tnoon , sun ));
285+ if (applyDeltaT == applyDeltaTNow ) { // only cache if the setting did not change mid calculation
286+ lastNoonSolution = solution ;
287+ }
288+ return solution ;
289+ }
290+
230291 private double getSunRiseSetUTC (LocalDate localDate , double latitude , double longitude , double zenith ,
231292 SolarEvent solarEvent ) {
232293 double julianDay = getJulianDay (localDate );
233294
234295 // Use the declination at local solar noon as the starting point, then iterate.
235- double noonmin = getSolarNoonMidnightUTC (julianDay , longitude , SolarEvent .NOON );
236- double tnoon = getJulianCenturiesFromJulianDay (julianDay + noonmin / 1440.0 );
237- double equationOfTime = getEquationOfTime (tnoon );
238- double solarDeclination = getSunDeclination (tnoon );
296+ NoonSolution noon = getNoonSolution (julianDay , longitude ); // independent of the zenith, so shared by every zman of the day
297+ double equationOfTime = noon .equationOfTime ;
298+ double solarDeclination = noon .solarDeclination ;
239299 double hourAngle = getSunHourAngle (latitude , solarDeclination , zenith , solarEvent );
240300 double delta = longitude - Math .toDegrees (hourAngle );
241301 double timeUTC = 720 + (4 * delta ) - equationOfTime ;
@@ -244,8 +304,9 @@ private double getSunRiseSetUTC(LocalDate localDate, double latitude, double lon
244304 // implementation's two helps convergence at high latitudes).
245305 for (int i = 0 ; i < 2 ; i ++) {
246306 double newt = getJulianCenturiesFromJulianDay (julianDay + timeUTC / 1440.0 );
247- equationOfTime = getEquationOfTime (newt );
248- solarDeclination = getSunDeclination (newt );
307+ double [] sun = getSunApparentEclipticCoordinates (newt ); // shared by the equation of time and the declination
308+ equationOfTime = getEquationOfTime (newt , sun );
309+ solarDeclination = getSunDeclination (newt , sun );
249310 hourAngle = getSunHourAngle (latitude , solarDeclination , zenith , solarEvent );
250311 if (Double .isNaN (hourAngle )) {
251312 return Double .NaN ;
@@ -461,7 +522,16 @@ private static double getDeltaT(double julianDay) {
461522 * @return the sun's apparent declination in degrees.
462523 */
463524 private static double getSunDeclination (double julianCenturies ) {
464- double [] sun = getSunApparentEclipticCoordinates (julianCenturies ); // {lambda, beta} in degrees
525+ return getSunDeclination (julianCenturies , getSunApparentEclipticCoordinates (julianCenturies ));
526+ }
527+
528+ /**
529+ * {@link #getSunDeclination(double)} from already calculated apparent ecliptic coordinates.
530+ * @param julianCenturies the Julian centuries (TT)
531+ * @param sun {@link #getSunApparentEclipticCoordinates(double)} for the same time
532+ * @return the declination in degrees
533+ */
534+ private static double getSunDeclination (double julianCenturies , double [] sun ) { // {lambda, beta} in degrees
465535 double lambda = sun [0 ];
466536 double beta = sun [1 ];
467537 double epsilon = getTrueObliquity (julianCenturies );
@@ -476,7 +546,16 @@ private static double getSunDeclination(double julianCenturies) {
476546 * @return the sun's apparent right ascension in degrees.
477547 */
478548 private static double getSunRightAscension (double julianCenturies ) {
479- double [] sun = getSunApparentEclipticCoordinates (julianCenturies );
549+ return getSunRightAscension (julianCenturies , getSunApparentEclipticCoordinates (julianCenturies ));
550+ }
551+
552+ /**
553+ * {@link #getSunRightAscension(double)} from already calculated apparent ecliptic coordinates.
554+ * @param julianCenturies the Julian centuries (TT)
555+ * @param sun {@link #getSunApparentEclipticCoordinates(double)} for the same time
556+ * @return the right ascension in degrees
557+ */
558+ private static double getSunRightAscension (double julianCenturies , double [] sun ) {
480559 double lambda = sun [0 ];
481560 double beta = sun [1 ];
482561 double epsilon = getTrueObliquity (julianCenturies );
@@ -561,12 +640,22 @@ private static double[] getNutation(double julianCenturies) {
561640 * @return the equation of time in minutes of time.
562641 */
563642 private static double getEquationOfTime (double julianCenturies ) {
643+ return getEquationOfTime (julianCenturies , getSunApparentEclipticCoordinates (julianCenturies ));
644+ }
645+
646+ /**
647+ * {@link #getEquationOfTime(double)} from already calculated apparent ecliptic coordinates.
648+ * @param julianCenturies the Julian centuries (TT)
649+ * @param sun {@link #getSunApparentEclipticCoordinates(double)} for the same time
650+ * @return the equation of time in minutes
651+ */
652+ private static double getEquationOfTime (double julianCenturies , double [] sun ) {
564653 double tau = julianCenturies / 10.0 ;
565654 // Mean longitude of the sun (Meeus 28.1), in degrees.
566655 double l0 = 280.4664567 + tau * (360007.6982779 + tau * (0.03032028
567656 + tau * (1.0 / 49931.0 - tau * (1.0 / 15300.0 + tau / 2000000.0 ))));
568657 l0 = (l0 % 360 + 360 ) % 360 ;
569- double alpha = getSunRightAscension (julianCenturies );
658+ double alpha = getSunRightAscension (julianCenturies , sun );
570659 double [] nut = getNutation (julianCenturies );
571660 double epsilon = getTrueObliquity (julianCenturies );
572661 double e = l0 - 0.0057183 - alpha + (nut [0 ] / 3600.0 ) * cosDegrees (epsilon ); // degrees
0 commit comments