├── docs └── suncalc.net paris lookup from east coast.png ├── .gitignore ├── src ├── main │ └── java │ │ └── com │ │ └── florianmski │ │ └── suncalc │ │ ├── utils │ │ ├── package-info.java │ │ ├── MoonUtils.java │ │ ├── DateUtils.java │ │ ├── SunUtils.java │ │ ├── TimeUtils.java │ │ ├── PositionUtils.java │ │ └── Constants.java │ │ ├── models │ │ ├── SunPosition.java │ │ ├── MoonPosition.java │ │ ├── EquatorialCoordinates.java │ │ ├── GeocentricCoordinates.java │ │ └── SunPhase.java │ │ └── SunCalc.java └── test │ └── groovy │ └── com │ └── florianmski │ └── suncalc │ └── SunCalcSpec.groovy ├── README.md └── pom.xml /docs/suncalc.net paris lookup from east coast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmski/SunCalc-Java/HEAD/docs/suncalc.net paris lookup from east coast.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | *.jar 5 | *.war 6 | *.ear 7 | 8 | # IDE files # 9 | .idea/ 10 | *.iml 11 | 12 | # Maven Files # 13 | target -------------------------------------------------------------------------------- /src/main/java/com/florianmski/suncalc/utils/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | *
3 | * The names of the utility functions here are camel cased getters that directly 4 | * map back to the original Javascript API's function names. 5 | *
6 | *7 | * Equations and tables referenced in this package are from the 8 | * 9 | * Astronomy Answers: Position of the Sun page 10 | *
11 | */ 12 | package com.florianmski.suncalc.utils; -------------------------------------------------------------------------------- /src/main/java/com/florianmski/suncalc/models/SunPosition.java: -------------------------------------------------------------------------------- 1 | package com.florianmski.suncalc.models; 2 | 3 | public class SunPosition 4 | { 5 | private double azimuth, altitude; 6 | 7 | public SunPosition(double azimuth, double altitude) 8 | { 9 | this.azimuth = azimuth; 10 | this.altitude = altitude; 11 | } 12 | 13 | public double getAzimuth() 14 | { 15 | return azimuth; 16 | } 17 | 18 | public void setAzimuth(double azimuth) 19 | { 20 | this.azimuth = azimuth; 21 | } 22 | 23 | public double getAltitude() 24 | { 25 | return altitude; 26 | } 27 | 28 | public void setAltitude(double altitude) 29 | { 30 | this.altitude = altitude; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/florianmski/suncalc/models/MoonPosition.java: -------------------------------------------------------------------------------- 1 | package com.florianmski.suncalc.models; 2 | 3 | public class MoonPosition 4 | { 5 | private double azimuth, altitude, distance; 6 | 7 | public MoonPosition(double azimuth, double altitude, double distance) 8 | { 9 | this.azimuth = azimuth; 10 | this.altitude = altitude; 11 | this.distance = distance; 12 | } 13 | 14 | public double getAzimuth() 15 | { 16 | return azimuth; 17 | } 18 | 19 | public void setAzimuth(double azimuth) 20 | { 21 | this.azimuth = azimuth; 22 | } 23 | 24 | public double getAltitude() 25 | { 26 | return altitude; 27 | } 28 | 29 | public void setAltitude(double altitude) 30 | { 31 | this.altitude = altitude; 32 | } 33 | 34 | public double getDistance() 35 | { 36 | return distance; 37 | } 38 | 39 | public void setDistance(double distance) 40 | { 41 | this.distance = distance; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/florianmski/suncalc/utils/MoonUtils.java: -------------------------------------------------------------------------------- 1 | package com.florianmski.suncalc.utils; 2 | 3 | import com.florianmski.suncalc.models.GeocentricCoordinates; 4 | 5 | public class MoonUtils 6 | { 7 | public static GeocentricCoordinates getMoonCoords(double d) 8 | { 9 | // geocentric ecliptic coordinates of the moon 10 | 11 | double L = Constants.TO_RAD * (218.316 + 13.176396 * d); // ecliptic longitude 12 | double M = Constants.TO_RAD * (134.963 + 13.064993 * d); // mean anomaly 13 | double F = Constants.TO_RAD * (93.272 + 13.229350 * d); // mean distance 14 | 15 | double l = L + Constants.TO_RAD * 6.289 * Math.sin(M); // longitude 16 | double b = Constants.TO_RAD * 5.128 * Math.sin(F); // latitude 17 | double dt = 385001 - 20905 * Math.cos(M); // distance to the moon in km 18 | 19 | return new GeocentricCoordinates( 20 | PositionUtils.getRightAscension(l, b), 21 | PositionUtils.getDeclination(l, b), 22 | dt); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/florianmski/suncalc/models/EquatorialCoordinates.java: -------------------------------------------------------------------------------- 1 | package com.florianmski.suncalc.models; 2 | 3 | /** 4 | * Container class for the equatorial coordinate system. See 5 | * 61 | 62 | 63 | License 64 | ======= 65 | 66 | "THE BEER-WARE LICENSE" (Revision 42): 67 | You can do whatever you want with this stuff. 68 | If we meet some day, and you think this stuff is worth it, you can buy me a beer in return. -------------------------------------------------------------------------------- /src/main/java/com/florianmski/suncalc/utils/DateUtils.java: -------------------------------------------------------------------------------- 1 | package com.florianmski.suncalc.utils; 2 | 3 | import java.util.Calendar; 4 | 5 | /** 6 | * Julian date utilities. 7 | */ 8 | public class DateUtils 9 | { 10 | /** Number of millis in one day */ 11 | public final static int DAY_MS = 1000 * 60 * 60 * 24; 12 | /** The Julian day of the POSIX epoch, i.e. Jan 1, 1970 */ 13 | public final static int J1970 = 2440588; 14 | /** The Julian day of Jan 1, 2000 */ 15 | public final static int J2000 = 2451545; 16 | 17 | /** 18 | * Converts a datetime into its Julian date 19 | * 20 | * @param date datetime with timezone information 21 | * @return the Julian date 22 | */ 23 | public static double toJulian(Calendar date) 24 | { 25 | // offset to add depending on user timezone 26 | // I'm not sure about that, it's not used in the JS lib but: 27 | // - I'm in France and between 00:00 and 01:00 the sunphases were still calculated for the day before 28 | // - I've tested the app with and without offset, with seems to be more accurate regarding the azimuth 29 | long offset = date.getTimeZone().getOffset(date.getTimeInMillis()); 30 | return ((double)date.getTimeInMillis() + offset) / DAY_MS - 0.5 + J1970; 31 | } 32 | 33 | /** 34 | * Converts a Julian date to a datetime ASSUMING the timezone of the current JVM 35 | * 36 | * @param j the Julian date 37 | * @return datetime using the current timezone system's timezone 38 | */ 39 | public static Calendar fromJulian(double j) 40 | { 41 | Calendar date = Calendar.getInstance(); 42 | date.setTimeInMillis((long) (((j + 0.5 - J1970) * DAY_MS))); 43 | return date; 44 | } 45 | 46 | /** 47 | * Number of Julian days since Jan 1, 2000. Often used in astronomical calculations 48 | * 49 | * @param date the current datetime with timezone info 50 | * @return number of Julian days 51 | */ 52 | public static double toDays(Calendar date) 53 | { 54 | return toJulian(date) - J2000; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/florianmski/suncalc/models/GeocentricCoordinates.java: -------------------------------------------------------------------------------- 1 | package com.florianmski.suncalc.models; 2 | 3 | /** 4 | * Container class for the equatorial coordinate system, when using the Earth as the point of reference. See 5 | * 25 | * http://users.electromagnetic.net/bu/astro/sunrise-set.php 26 | * 27 | * 28 | * @param d the day to calculate for, number Julian days since Jan 1, 2000 29 | * @param lw West longitude, in radians 30 | * @return the rounded julian cycle 31 | */ 32 | public static double getJulianCycle(double d, double lw) 33 | { 34 | return Math.round(d - J0 - lw / (2 * Math.PI)); 35 | } 36 | 37 | /** 38 | * First order estimate for approximate solar transit (eq. 34) 39 | * 40 | * @param Ht 41 | * @param lw West longitude, in radians 42 | * @param n the julian cycle, see {@link #getJulianCycle(double, double)} 43 | * @return reasonable estimate for the date and time of the transit 44 | */ 45 | public static double getApproxTransit(double Ht, double lw, double n) 46 | { 47 | return J0 + (Ht + lw) / (2 * Math.PI) + n; 48 | } 49 | 50 | /** 51 | * Better estimate for solar transit, with eccentricity and obliquity corrections (eq. 35) 52 | * 53 | * @param ds approximate first order solar transit, in Julian days 54 | * @param M Earth's mean anomaly, in radians, recalculated for the Julian day in question using eq. 33 and eq. 34 55 | * @param L ecliptic longitude of the Sun, in radians (eq. 8) 56 | * @return solar transit, in Julian days 57 | */ 58 | public static double getSolarTransitJ(double ds, double M, double L) 59 | { 60 | return DateUtils.J2000 + ds + J1 * Math.sin(M) - 0.0069 * Math.sin(2 * L); 61 | } 62 | 63 | /** 64 | * The hour angle (eq. 24). Indicates how long ago the celestial body has passed through the celestial meridian 65 | * 66 | * @param h altitude above the horizon, measured in radians. The altitude is zero at the horizon, PI/2 at the zenith 67 | * (straight above your head) and -PI/2 at nadir (straight down) 68 | * @param phi latitude from the North (beginning of section 7) 69 | * @param d declination from Earth, measured in radians 70 | * @return hour angle, in radians 71 | */ 72 | public static double getHourAngle(double h, double phi, double d) 73 | { 74 | return Math.acos((Math.sin(h) - Math.sin(phi) * Math.sin(d)) / (Math.cos(phi) * Math.cos(d))); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/florianmski/suncalc/utils/PositionUtils.java: -------------------------------------------------------------------------------- 1 | package com.florianmski.suncalc.utils; 2 | 3 | /** 4 | * Calculations for the position of the celestial bodies. 5 | */ 6 | public class PositionUtils 7 | { 8 | 9 | /** Sidereal time from Earth, in degrees at longitude 0 degrees at the instant of J2000 (table 5) */ 10 | private static final double THETA_0 = 280.16; 11 | 12 | /** Rate of change of sidereal time from Earth, in degrees/day (table 5) */ 13 | private static final double THETA_1 = 360.9856235; 14 | 15 | public static double getRightAscension(double l, double b) 16 | { 17 | return Math.atan2(Math.sin(l) * Math.cos(Constants.EARTH_OBLIQUITY) - Math.tan(b) * Math.sin(Constants.EARTH_OBLIQUITY), Math.cos(l)); 18 | } 19 | 20 | /** 21 | * Declination of a celestial body, with respect to an observer on the Earth (eq. 12) 22 | * 23 | * @param l ecliptic longitude of the celestial body, in radians 24 | * @param b ecliptic latitude of the sun for the celestial body 25 | * @return declination for an Earth observer, in radians 26 | */ 27 | public static double getDeclination(double l, double b) 28 | { 29 | return Math.asin(Math.sin(b) * Math.cos(Constants.EARTH_OBLIQUITY) + Math.cos(b) * Math.sin(Constants.EARTH_OBLIQUITY) * Math.sin(l)); 30 | } 31 | 32 | /** 33 | * The azimuth angle of the celestial body (eq. 25) 34 | *35 | * Note this returns the angle taking NORTH as zero, unlike the original SunCalcJS. See https://github.com/mourner/suncalc/issues/6 for more info 37 | *
38 | * @param H the hour angle, measured in radians 39 | * @param phi latitude, in radians 40 | * @param dec declination, in radians 41 | * @return the azimuth in radians, with NORTH as zero 42 | */ 43 | public static double getAzimuth(double H, double phi, double dec) 44 | { 45 | return Math.PI + Math.atan2(Math.sin(H), Math.cos(H) * Math.sin(phi) - Math.tan(dec) * Math.cos(phi)); 46 | // return Math.atan2(Math.sin(H), Math.cos(H) * Math.sin(phi) - Math.tan(dec) * Math.cos(phi)); 47 | } 48 | 49 | /** 50 | * The altitude above the horizon of the celestial body (eq. 23) 51 | * 52 | * @param H the hour angle, measured in radians 53 | * @param phi latitude, in radians 54 | * @param dec declination, in radians 55 | * @return the altitude in radians 56 | */ 57 | public static double getAltitude(double H, double phi, double dec) 58 | { 59 | return Math.asin(Math.sin(phi) * Math.sin(dec) + Math.cos(phi) * Math.cos(dec) * Math.cos(H)); 60 | } 61 | 62 | /** 63 | * Sidereal time, from the perspective of the Earth (eq. 20) 64 | * 65 | * @param d number of Julian days since Jan 1, 2000 66 | * @param lw West longitude, in radians 67 | * @return the sidereal time, in radians 68 | */ 69 | public static double getSiderealTime(double d, double lw) 70 | { 71 | return Constants.TO_RAD * (THETA_0 + THETA_1 * d) - lw; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 |13 | * Solar elevation angles in degrees, i.e. the angle of the sun from the horizon. 14 | * Defined as 90 deg - solar zenith angle. 15 | *
16 | *17 | * See 18 | * https://en.wikipedia.org/wiki/Solar_zenith_angle and 19 | * https://en.wikipedia.org/wiki/Twilight 20 | * for more info. 21 | *
22 | */ 23 | public class SunAngles 24 | { 25 | /** sunrise (top edge of the sun appears on the horizon) */ 26 | public final static double SUNRISE_START = -0.833; 27 | /** soft, warm light as sun is rising (best time for photography) */ 28 | public final static double GOLDEN_HOUR_MORNING_START = -0.3; 29 | /** daylight starts */ 30 | public final static double DAYLIGHT_START = 6.0; 31 | /** soft, warm light as sun is setting (best time for photography) */ 32 | public final static double GOLDEN_HOUR_EVENING_START = DAYLIGHT_START; 33 | /** sunset starts (bottom edge of the sun touches the horizon) */ 34 | public final static double SUNSET_START = GOLDEN_HOUR_MORNING_START; 35 | /** evening civil twilight starts (sun disappears below the horizon) */ 36 | public final static double TWILIGHT_CIVIL_EVENING_START = SUNRISE_START; 37 | /** evening nautical twilight starts (many brighter stars start appearing, horizon faintly visible) */ 38 | public final static double TWILIGHT_NAUTICAL_EVENING_START = -6.0; 39 | /** evening astronomical twilight starts (fainter stars start appearing) */ 40 | public final static double TWILIGHT_ASTRONOMICAL_EVENING_START = -12.0; 41 | /** night starts (dark enough for astronomical observations) */ 42 | public final static double NIGHT_START = -18.0; 43 | /** astronomical dawn (fainter stars start disappearing) */ 44 | public final static double TWILIGHT_ASTRONOMICAL_MORNING_START = NIGHT_START; 45 | /** nautical dawn (brighter stars start disappearing) */ 46 | public final static double TWILIGHT_NAUTICAL_MORNING_START = TWILIGHT_ASTRONOMICAL_EVENING_START; 47 | /** dawn (atmosphere begins to scatter light) */ 48 | public final static double TWILIGHT_CIVIL_MORNING_START = TWILIGHT_NAUTICAL_EVENING_START; 49 | 50 | /** sunrise ends (bottom edge of the sun touches the horizon) */ 51 | public final static double SUNRISE_END = GOLDEN_HOUR_MORNING_START; 52 | /** morning golden hour (soft light, best time for photography) ends */ 53 | public final static double GOLDEN_HOUR_MORNING_END = DAYLIGHT_START; 54 | /** daylight ends, evening golden hour starts */ 55 | public final static double DAYLIGHT_END = GOLDEN_HOUR_EVENING_START; 56 | /** sunset starts (bottom edge of the sun touches the horizon) */ 57 | public final static double GOLDEN_HOUR_EVENING_END = SUNSET_START; 58 | /** sunset ends (sun disappears below the horizon) */ 59 | public final static double SUNSET_END = TWILIGHT_CIVIL_EVENING_START; 60 | /** dusk (many brighter stars start appearing, horizon faintly visible) */ 61 | public final static double TWILIGHT_CIVIL_EVENING_END = TWILIGHT_NAUTICAL_EVENING_START; 62 | /** nautical dusk (fainter stars start appearing) */ 63 | public final static double TWILIGHT_NAUTICAL_EVENING_END = TWILIGHT_ASTRONOMICAL_EVENING_START; 64 | /** night starts, astronomical dusk (dark enough for astronomical observations) */ 65 | public final static double TWILIGHT_ASTRONOMICAL_EVENING_END = NIGHT_START; 66 | /** night ends (fainter stars start disappearing) */ 67 | public final static double NIGHT_END = TWILIGHT_ASTRONOMICAL_MORNING_START; 68 | /** morning astronomical twilight ends (brighter stars start disappearing) */ 69 | public final static double TWILIGHT_ASTRONOMICAL_MORNING_END = TWILIGHT_NAUTICAL_MORNING_START; 70 | /** morning nautical twilight ends (atmosphere begins to scatter light) */ 71 | public final static double TWILIGHT_NAUTICAL_MORNING_END = TWILIGHT_CIVIL_MORNING_START; 72 | /** morning civil twilight ends (top edge of the sun appears on the horizon) */ 73 | public final static double TWILIGHT_CIVIL_MORNING_END = SUNRISE_START; 74 | 75 | /** sun is directly overhead */ 76 | public final static double SOLAR_NOON = 90; 77 | /** sun is directly below */ 78 | public final static double NADIR = -90; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/florianmski/suncalc/SunCalc.java: -------------------------------------------------------------------------------- 1 | package com.florianmski.suncalc; 2 | 3 | 4 | import com.florianmski.suncalc.models.*; 5 | import com.florianmski.suncalc.utils.*; 6 | 7 | import java.util.Calendar; 8 | import java.util.List; 9 | import java.util.TimeZone; 10 | 11 | /** 12 | * Calculations for the sun and moon relative to earth 13 | */ 14 | public class SunCalc 15 | { 16 | /** 17 | * 18 | * Calculates the sun's position at a particular location and moment 19 | * 20 | * @param date the day, time and timezone to calculate for 21 | * @param lat measured from North, in degrees 22 | * @param lng measured from East, in degrees 23 | * @return the sun's position in the sky relative to the location 24 | */ 25 | public static SunPosition getSunPosition(Calendar date, double lat, double lng) 26 | { 27 | double lw = Constants.TO_RAD * -lng; 28 | double phi = Constants.TO_RAD * lat; 29 | double d = DateUtils.toDays(date); 30 | 31 | EquatorialCoordinates c = SunUtils.getSunCoords(d); 32 | double H = PositionUtils.getSiderealTime(d, lw) - c.getRightAscension(); 33 | 34 | return new SunPosition( 35 | PositionUtils.getAzimuth(H, phi, c.getDeclination()), 36 | PositionUtils.getAltitude(H, phi, c.getDeclination())); 37 | } 38 | 39 | /** 40 | * 41 | * Calculates the moon's position at a particular location and moment 42 | * 43 | * @param date the day, time and timezone to calculate for 44 | * @param lat measured from North, in degrees 45 | * @param lng measured from East, in degrees 46 | * @return the moon's position in the sky relative to the location 47 | */ 48 | public static MoonPosition getMoonPosition(Calendar date, double lat, double lng) 49 | { 50 | double lw = Constants.TO_RAD * -lng; 51 | double phi = Constants.TO_RAD * lat; 52 | double d = DateUtils.toDays(date); 53 | 54 | GeocentricCoordinates c = MoonUtils.getMoonCoords(d); 55 | double H = PositionUtils.getSiderealTime(d, lw) - c.getRightAscension(); 56 | double h = PositionUtils.getAltitude(H, phi, c.getDeclination()); 57 | 58 | // altitude correction for refraction 59 | h = h + Constants.TO_RAD * 0.017 / Math.tan(h + Constants.TO_RAD * 10.26 / (h + Constants.TO_RAD * 5.10)); 60 | 61 | return new MoonPosition(PositionUtils.getAzimuth(H, phi, c.getDeclination()), h, c.getDistance()); 62 | } 63 | 64 | /** 65 | * Calculates moon illumination for a particular day and time. 66 | * Location is not needed because percentage will be the same for 67 | * both Northern and Southern hemisphere. 68 | * 69 | * @param date the day, time and timezone to calculate for 70 | * @return fraction of moon's illuminated limb and phase 71 | */ 72 | public static double getMoonFraction(Calendar date) 73 | { 74 | double d = DateUtils.toDays(date); 75 | EquatorialCoordinates s = SunUtils.getSunCoords(d); 76 | GeocentricCoordinates m = MoonUtils.getMoonCoords(d); 77 | 78 | int sdist = 149598000; // distance from Earth to Sun in km 79 | 80 | double phi = Math.acos(Math.sin(s.getDeclination()) * Math.sin(m.getDeclination()) + Math.cos(s.getDeclination()) * Math.cos(m.getDeclination()) * Math.cos(s.getRightAscension() - m.getRightAscension())); 81 | double inc = Math.atan2(sdist * Math.sin(phi), m.getDistance() - sdist * Math.cos(phi)); 82 | 83 | return (1 + Math.cos(inc)) / 2; 84 | } 85 | 86 | /** 87 | * Calculates phases of the sun for a single day 88 | * 89 | * @param date the day and timezone to calculate sun positions for, time is ignored 90 | * @param lat measured from North, in degrees 91 | * @param lng measured from East, in degrees 92 | * @return phases by name, with their start/end angles and start/end times 93 | */ 94 | public static List36 | * This test was written from the eastern timezone, using suncalc.net. The reason it looks 37 | * funny is because although the region is in Paris, the times returned by the web API are always in the person's 38 | * default timezone [perhaps by design]. A screenshot is attached in 39 | *
40 | *41 | * Depsite its quirks, this parameterized test serves as a solid regression of all calculated phases of the Java 42 | * API. 43 | *
44 | */ 45 | def "should calculate correct phase #phase for #location.description"() { 46 | 47 | when: 48 | List