Skip to content

Commit d002497

Browse files
authored
AstronomicalCalculator Canges
- getApparentSolarRadius() - null handling - Rename getSolarRadius(LocalDate) -> getApparentSolarRadius(LocalDate) to avoid confusion - Rename astronomicalApparentSolarRadius -> apparentSolarRadius - setSolarRadius(double) now throws an IllegalArgumentException if the radius is not in the valid range - equals(Object) now also compares useApparentSolarRadius - hashCode() now uses apparentSolarRadius - Documentation clarifications
1 parent 23cd3e8 commit d002497

1 file changed

Lines changed: 117 additions & 65 deletions

File tree

‎src/main/java/com/kosherjava/zmanim/util/AstronomicalCalculator.java‎

Lines changed: 117 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -41,35 +41,42 @@ public abstract class AstronomicalCalculator implements Cloneable {
4141
* The commonly used average solar radius that is about 16′ or 0.2666°.
4242
*
4343
* @see #getSolarRadius()
44-
* @see #getSolarRadius(LocalDate)
44+
* @see #getApparentSolarRadius(LocalDate)
4545
*/
4646
private double solarRadius = 16 / 60d;
4747

4848
/**
49-
* Should the calculator use the {@link #isUseAstronomicApparentSolarRadius()} (defaults to {@code true}).
50-
* @see #setUseAstronomicApparentSolarRadius(boolean)
51-
* @see #setUseAstronomicApparentSolarRadius(boolean)
52-
* */
53-
private boolean useAstronomicApparentSolarRadius = true;
49+
* Whether sunrise and sunset should use the date-based apparent solar radius ({@link #getApparentSolarRadius(LocalDate)}),
50+
* which varies slightly through the year as the Earth-Sun distance changes, rather than the fixed {@link #getSolarRadius()
51+
* solar radius}. Defaults to {@code true}. When {@code false}, the fixed {@link #getSolarRadius()} value (default 16′)
52+
* is used instead, which is appropriate for matching other implementations that assume a constant solar radius. Note that
53+
* calling {@link #setSolarRadius(double)} automatically sets this to {@code false}.
54+
*
55+
* @see #isUseApparentSolarRadius()
56+
* @see #setUseApparentSolarRadius(boolean)
57+
* @see #getApparentSolarRadius(LocalDate)
58+
* @see #getSolarRadius()
59+
*/
60+
private boolean useApparentSolarRadius = true;
5461

5562
/**
5663
* Returns if useAstronomicApparentSolarRadius is true (the default) or false.
5764
* @return if useAstronomicApparentSolarRadius is true or false.
58-
* @see #getSolarRadius(LocalDate)
65+
* @see #getApparentSolarRadius(LocalDate)
5966
* @see #getSolarRadius()
6067
*/
61-
public boolean isUseAstronomicApparentSolarRadius() {
62-
return useAstronomicApparentSolarRadius;
68+
public boolean isUseApparentSolarRadius() {
69+
return useApparentSolarRadius;
6370
}
6471

6572
/**
6673
* Sets if useAstronomicApparentSolarRadius should be true (the default) or false.
67-
* @param useAstronomicalApparentSolarRadius should astronomic apparent solar radius be used (default is true).
68-
* @see #getSolarRadius(LocalDate)
74+
* @param useApparentSolarRadius should apparent solar radius be used (default is true).
75+
* @see #getApparentSolarRadius(LocalDate)
6976
* @see #getSolarRadius()
7077
*/
71-
public void setUseAstronomicApparentSolarRadius(boolean useAstronomicalApparentSolarRadius) {
72-
this.useAstronomicApparentSolarRadius = useAstronomicalApparentSolarRadius;
78+
public void setUseApparentSolarRadius(boolean useApparentSolarRadius) {
79+
this.useApparentSolarRadius = useApparentSolarRadius;
7380
}
7481

7582
/**
@@ -297,7 +304,7 @@ public abstract double getUTCSunset(LocalDate localDate, GeoLocation geoLocation
297304
* com.kosherjava.zmanim.AstronomicalCalendar#getEndNauticalTwilight()} that passes {@link
298305
* com.kosherjava.zmanim.AstronomicalCalendar#NAUTICAL_ZENITH} to this method.
299306
* @param elevation elevation in Meters.
300-
* @param localDate the date to use for the solar radius. See {@link #getSolarRadius(LocalDate)}.
307+
* @param localDate the date to use for the solar radius. See {@link #getApparentSolarRadius(LocalDate)}.
301308
* @return The zenith adjusted to include the {@link #getSolarRadius sun's radius}, {@link #getRefraction
302309
* refraction} and {@link #getElevationAdjustment elevation} adjustment. This will only be adjusted for
303310
* sunrise and sunset (if the zenith == 90°)
@@ -306,8 +313,8 @@ public abstract double getUTCSunset(LocalDate localDate, GeoLocation geoLocation
306313
double adjustZenith(double zenith, double elevation, LocalDate localDate) {
307314
double adjustedZenith = zenith;
308315
if (zenith == GEOMETRIC_ZENITH) { // only adjust if it is exactly sunrise or sunset
309-
if(isUseAstronomicApparentSolarRadius() && localDate != null) {
310-
adjustedZenith = zenith + (getSolarRadius(localDate) + getRefraction() + getElevationAdjustment(elevation));
316+
if(isUseApparentSolarRadius() && localDate != null) {
317+
adjustedZenith = zenith + (getApparentSolarRadius(localDate) + getRefraction() + getElevationAdjustment(elevation));
311318
} else {
312319
adjustedZenith = zenith + (getSolarRadius() + getRefraction() + getElevationAdjustment(elevation));
313320
}
@@ -341,34 +348,55 @@ public void setRefraction(double refraction) {
341348
}
342349

343350
/**
344-
* Method to get the sun's radius. The default value is 16 arcminutes. The sun's radius as it appears from earth is almost
345-
* universally given as 16 arcminutes but in fact it differs by the time of the year. At the <a
346-
* href="https://en.wikipedia.org/wiki/Perihelion">perihelion</a> it has an apparent radius of 16.293′ (0.2710°), while at the
347-
* <a href="https://en.wikipedia.org/wiki/Aphelion">aphelion</a> it has an apparent radius of 15.755′ (0.2622°). There is little
348-
* affect for most location, but at high and low latitudes the difference becomes more apparent. My Calculations for
349-
* the difference at the location of the <a href="https://www.rmg.co.uk/royal-observatory">Royal Observatory, Greenwich</a>
350-
* shows only a 4.494-second difference between the perihelion and aphelion radii, but moving into the arctic circle the
351-
* difference becomes more noticeable. Tests for Tromso, Norway (latitude 69.672312, longitude 19.049787) show that
352-
* on May 17, the rise of the midnight sun, a 2 minute and 23 second difference is observed between the perihelion and
353-
* aphelion radii using the USNO algorithm, but only 1 minute and 6 seconds difference using the NOAA algorithm.
354-
* Areas farther north show an even greater difference. Note that these test are not real valid test cases because
355-
* they show the extreme difference on days that are not the perihelion or aphelion, but are shown for illustrative
356-
* purposes only.
357-
*
358-
* @return The sun's radius in degrees.
359-
*/
351+
* Method to get the fixed sun's radius. The default value is 16 arcminutes. The sun's radius as it appears from earth is almost
352+
* universally given as 16 arcminutes but in fact it differs by the time of the year. At the <a href=
353+
* "https://en.wikipedia.org/wiki/Apsis#Perihelion_and_aphelion">perihelion</a> it has an apparent radius of 16.293′ (0.2710°),
354+
* while at the <a href="https://en.wikipedia.org/wiki/Apsis#Perihelion_and_aphelion">aphelion</a> it has an apparent radius of
355+
* 15.755′ (0.2622°). There is little effect for most locations, but at high and low latitudes the difference becomes more
356+
* apparent. Calculations for the difference at the location of the <a href="https://www.rmg.co.uk/royal-observatory">Royal
357+
* Observatory, Greenwich</a> show only a 4.494-second difference between the perihelion and aphelion radii, but moving into the
358+
* arctic circle the difference becomes more noticeable. Tests for Tromso, Norway (latitude 69.67°, longitude 19.05°) show that on
359+
* May 17, the rise of the midnight sun, a 2 minute and 23 second difference is observed between the perihelion and aphelion radii
360+
* using the USNO algorithm, but only 1 minute and 6 seconds difference using the NOAA algorithm. Areas farther north show an even
361+
* greater difference. Note that these are not real-world tests. It simply compared the min and max solar radius at different
362+
* locations. Real world examples of comparing the actual apperant solar radius would yield less significant differences.
363+
* Regardless, this is exactly the error that the date-based apparent solar radius eliminates: by default ({@link
364+
* #isUseApparentSolarRadius()} is {@code true}) sunrise and sunset use {@link #getApparentSolarRadius(LocalDate)}
365+
* instead, and the fixed value returned here only applies when that setting is {@code false} (typically to match other
366+
* implementations that assume a constant 16′ radius; see {@link #setSolarRadius(double)}, which switches that setting off
367+
* automatically).
368+
*
369+
* @return The fixed sun's radius in degrees, used only when {@link #isUseApparentSolarRadius()} is {@code false}.
370+
* @see #getApparentSolarRadius(LocalDate)
371+
* @see #isUseApparentSolarRadius()
372+
*/
360373
public double getSolarRadius() {
361374
return this.solarRadius;
362375
}
363376

364377
/**
365-
* Method to set the sun's radius.
366-
*
367-
* @param solarRadius The sun's radius in degrees.
378+
* Sets the Sun's radius as a fixed, date-independent value in degrees, for example {@code 16.0 / 60.0} for the
379+
* conventional 16′. This is typically used to match another implementation or reference that assumes a constant
380+
* solar radius.
381+
* <p>
382+
* A fixed radius has no effect while {@link #isUseApparentSolarRadius()} is {@code true} (the default), since
383+
* in that mode the date-based {@link #getApparentSolarRadius(LocalDate) apparent solar radius} is used instead. So that
384+
* a value set here actually takes effect, this method also disables that mode (equivalent to calling
385+
* {@link #setUseApparentSolarRadius(boolean) setUseAstronomicApparentSolarRadius(false)}). To return to the
386+
* date-based radius afterward, pass {@code true} to {@link #setUseApparentSolarRadius(boolean)}.
387+
*
388+
* @param solarRadius the Sun's radius in degrees. Must be a non-negative number.
389+
* @throws IllegalArgumentException if {@code solarRadius} is &lt; 0 or is {@link Double#NaN}.
368390
* @see #getSolarRadius()
391+
* @see #isUseApparentSolarRadius()
392+
* @see #getApparentSolarRadius(LocalDate)
369393
*/
370394
public void setSolarRadius(double solarRadius) {
395+
if (solarRadius < 0 || Double.isNaN(solarRadius)) {
396+
throw new IllegalArgumentException("Solar radius must be a non-negative number");
397+
}
371398
this.solarRadius = solarRadius;
399+
this.useApparentSolarRadius = false;
372400
}
373401

374402
/**
@@ -392,7 +420,8 @@ public boolean equals(Object object) {
392420

393421
return Double.compare(this.getEarthRadius(), calculator.getEarthRadius()) == 0
394422
&& Double.compare(this.getRefraction(), calculator.getRefraction()) == 0
395-
&& Double.compare(this.getSolarRadius(), calculator.getSolarRadius()) == 0;
423+
&& Double.compare(this.getSolarRadius(), calculator.getSolarRadius()) == 0
424+
&& this.isUseApparentSolarRadius() == calculator.isUseApparentSolarRadius();
396425
}
397426

398427
/**
@@ -405,7 +434,7 @@ public boolean equals(Object object) {
405434
*/
406435
@Override
407436
public int hashCode() {
408-
return Objects.hash(getEarthRadius(), getRefraction(), getSolarRadius());
437+
return Objects.hash(getEarthRadius(), getRefraction(), getSolarRadius(), useApparentSolarRadius);
409438
}
410439

411440
/**
@@ -468,15 +497,26 @@ protected static double asinDegrees(double angle) {
468497
}
469498

470499
/**
471-
* The Sun's apparent angular semi-diameter ("solar radius") in degrees for each day of the year, precomputed from the
472-
* <a href="https://en.wikipedia.org/wiki/VSOP_model">VSOP87</a> Earth-Sun distance for reference year 2050 (chosen as the
473-
* midpoint of 2000-2100 to keep the century drift symmetric and small; 2050 is a common year, so there are 365 entries and
474-
* February 29 reuses the February 28 value - see {@link #getSolarRadius(LocalDate)}). The Sun's apparent size varies through
475-
* the year as the Earth-Sun distance changes between perihelion (~Jan 3, about 16.27′ or 0.2710°) and aphelion (~Jul 5, about
476-
* 15.73′ or 0.2622°), with a mean of the conventional 16′. Indexed by day-of-year (1-365).
477-
* <p>A fixed table is used instead of evaluating the series at run time because the value for a given calendar date
478-
* drifts by less than ~0.4′ across the whole of 2000-2100 (perihelion advances ~1 day per 57 years), which
479-
* at latitude 60° is at most ~50 ms of sunrise / sunset time, basically negligible.
500+
* The Sun's apparent angular semi-diameter (the "solar radius") in degrees for each day of the year, precomputed from
501+
* the <a href="https://en.wikipedia.org/wiki/VSOP_(planets)">VSOP87</a> Earth-Sun distance for reference year 2050.
502+
* The Sun's apparent size varies through the year as the Earth-Sun distance changes between the perihelion (~Jan 3,
503+
* about 16.27′ or 0.2710°) and the aphelion (~Jul 5, about 15.73′ or 0.2622°), with a mean of the conventional 16′.
504+
* The table has 365 entries indexed by day-of-year (1-365); 2050 is a common year, so February 29 reuses the
505+
* February 28 value (see {@link #getApparentSolarRadius(LocalDate)}).
506+
* <p>
507+
* <b>Valid epoch.</b> Reference year 2050 was chosen as the midpoint of the years 2000-2100 so that the small drift in
508+
* the value for a fixed calendar date stays symmetric and minimal across that span. The date of the perihelion advances
509+
* through the calendar by roughly one day every 57 years, so the apparent radius for a given calendar date is not
510+
* perfectly constant from year to year. Across 2000-2100 this table is within about 0.4″ of the exact value (at most
511+
* roughly 50 ms of sunrise / sunset time at latitude 60°, and less elsewhere) - negligible for any practical purpose.
512+
* Outside 2000-2100 the table is still returned but its accuracy degrades gradually as the perihelion drifts further;
513+
* for use centuries away from 2050 the table should be regenerated against a closer reference year.
514+
* <p>
515+
* A fixed table is used in preference to evaluating the VSOP87 series at run time because, given the negligible drift
516+
* above, the lookup is both simpler and faster while remaining far more accurate than any sunrise / sunset calculation
517+
* requires.
518+
*
519+
* @see #getApparentSolarRadius(LocalDate)
480520
*/
481521
private static final double[] SOLAR_RADIUS_BY_DAY_OF_YEAR = {
482522
0.27108024, 0.27108486, 0.27108790, 0.27108930, 0.27108899, 0.27108695, 0.27108316, 0.27107762, 0.27107033,
@@ -522,26 +562,38 @@ protected static double asinDegrees(double angle) {
522562
0.27105266, 0.27106312, 0.27107182, 0.27107876, 0.27108399};
523563

524564
/**
525-
* Return the Sun's apparent angular semi-diameter ("solar radius") for the given date, in degrees, suitable for passing to
526-
* {@link #setSolarRadius(double)} for slightly more accurate sunrise / sunset times. The value comes from a precomputed table
527-
* ({@link #SOLAR_RADIUS_BY_DAY_OF_YEAR}) keyed by calendar day. The date's month/day is mapped onto the (common-year) reference
528-
* 2050 via {@code withYear(2050)}; because 2050 is not a leap year, a February 29 input is automatically resolved to February 28
529-
* and assigned that day's value.
530-
*
531-
* <p>Example: //FIXME once integrated
532-
* <pre>
533-
* calculator.setSolarRadius(calculator.getSolarRadius(localDate));
534-
* </pre>
565+
* Returns the Sun's apparent angular semi-diameter (the "solar radius") for the given date, in degrees. The Sun's
566+
* apparent size changes over the year as the Earth-Sun distance varies between the <a href=
567+
* "https://en.wikipedia.org/wiki/Apsis#Perihelion_and_aphelion">perihelion</a> (~Jan 3, when the Sun is largest)
568+
* and the <a href="https://en.wikipedia.org/wiki/Apsis#Perihelion_and_aphelion">aphelion</a> (~Jul 5, when it is
569+
* smallest). The value is read from a precomputed table ({@link #SOLAR_RADIUS_BY_DAY_OF_YEAR}) keyed by calendar
570+
* day; the date's month and day are mapped onto the (common-year) reference year 2050 via {@code withYear(2050)},
571+
* so a February 29 input is automatically resolved to February 28 and assigned that day's value.
572+
* <p>
573+
* This is the value the calculator applies <b>automatically</b> for sunrise and sunset while
574+
* {@link #isUseApparentSolarRadius()} is {@code true} (the default). It is recomputed from the date on
575+
* every call and is never stored, so it is always correct for the date being calculated. The method is exposed only
576+
* for inspection and comparison.
577+
* <p>
578+
* <b>Do not pass the result to {@link #setSolarRadius(double)}.</b> {@link #setSolarRadius(double)} is meant for a
579+
* fixed, date-independent radius (for example to match another implementation that uses a constant 16′). Storing
580+
* a single day's apparent radius there would freeze that one day's value onto this instance and apply it incorrectly
581+
* to every other date subsequently calculated (and would also switch off {@link #isUseApparentSolarRadius()
582+
* apparent-radius mode}). To use the date-based radius, simply leave {@link #isUseApparentSolarRadius()}
583+
* enabled and let the calculator call this method itself.
535584
*
536-
* @param date the date for which to return the solar semi-diameter.
537-
* @return the Sun's apparent semi-diameter in degrees (about 0.2711° near the
538-
* <a href="https://en.wikipedia.org/wiki/Apsis#Perihelion_and_aphelion">perihelion</a>, 0.2622° near the <a href=
539-
* "https://en.wikipedia.org/wiki/Apsis#Perihelion_and_aphelion">aphelion</a>, and about 0.2666° - the conventional 16′
540-
* near the <a href="https://en.wikipedia.org/wiki/Equinox">equinoxes</a>).
541-
* @see #setSolarRadius(double)
542-
*/
543-
public double getSolarRadius(LocalDate date) {
544-
return SOLAR_RADIUS_BY_DAY_OF_YEAR[date.withYear(2050).getDayOfYear() - 1];
585+
* @param localDate the date for which to return the Sun's apparent semi-diameter. If a {@code null} is passed, the
586+
* default solar radius of 16/60 will be returned.
587+
*
588+
* @return the Sun's apparent semi-diameter in degrees: about 0.2711° near the <a href=
589+
* "https://en.wikipedia.org/wiki/Apsis#Perihelion_and_aphelion">perihelion</a>, about 0.2622° near the
590+
* <a href="https://en.wikipedia.org/wiki/Apsis#Perihelion_and_aphelion">aphelion</a>, and about 0.2666°
591+
* - the conventional 16′ - near the <a href="https://en.wikipedia.org/wiki/Equinox">equinoxes</a>.
592+
* @see #isUseApparentSolarRadius()
593+
* @see #getSolarRadius()
594+
*/
595+
public double getApparentSolarRadius(LocalDate localDate) {
596+
return localDate == null ? 16 / 60d : SOLAR_RADIUS_BY_DAY_OF_YEAR[localDate.withYear(2050).getDayOfYear() - 1];
545597
}
546598

547599
}

0 commit comments

Comments
 (0)