diff --git a/src/main/java/eu/m724/wtapi/object/Twilight.java b/src/main/java/eu/m724/wtapi/object/Twilight.java new file mode 100644 index 0000000..26b5846 --- /dev/null +++ b/src/main/java/eu/m724/wtapi/object/Twilight.java @@ -0,0 +1,27 @@ +package eu.m724.wtapi.object; + +import java.time.Duration; +import java.time.LocalDate; + +public class Twilight { + /** + * The date this object contains times for + */ + public final LocalDate date; + + /** + * Time of sunrise relative to UTC midnight of the date + */ + public final Duration sunrise; + + /** + * Time of sunset relative to UTC midnight of the date + */ + public final Duration sunset; + + public Twilight(LocalDate date, Duration sunrise, Duration sunset) { + this.date = date; + this.sunrise = sunrise; + this.sunset = sunset; + } +} diff --git a/src/main/java/eu/m724/wtapi/provider/twilight/SimpleTwilightTimeProvider.java b/src/main/java/eu/m724/wtapi/provider/twilight/SimpleTwilightTimeProvider.java new file mode 100644 index 0000000..9bc9a06 --- /dev/null +++ b/src/main/java/eu/m724/wtapi/provider/twilight/SimpleTwilightTimeProvider.java @@ -0,0 +1,51 @@ +package eu.m724.wtapi.provider.twilight; + +import eu.m724.wtapi.object.Coordinates; +import eu.m724.wtapi.object.Twilight; + +import java.time.Duration; +import java.time.LocalDate; + +import static java.lang.Math.*; + +public class SimpleTwilightTimeProvider extends TwilightTimeProvider { + @Override + public Twilight calculateTwilightTime(LocalDate date, Coordinates coordinates) { + int dayOfYear = date.getDayOfYear() - 1; // -1 because we have to start from zero + // 2 * PI / 365 = 0.01721420632103996 + double fractionalYear = 0.01721420632103996 * dayOfYear; + + double equationOfTime = 229.18 * (0.000075 + + 0.001868 * cos(fractionalYear) + - 0.032077 * sin(fractionalYear) + - 0.014615 * cos(2 * fractionalYear) + - 0.040849 * sin(2 * fractionalYear)); + double declination = 0.006918 + - 0.399912 * cos(fractionalYear) + + 0.070257 * sin(fractionalYear) + - 0.006758 * cos(2 * fractionalYear) + + 0.000907 * sin(2 * fractionalYear) + - 0.002697 * cos(3 * fractionalYear) + + 0.00148 * sin(3 * fractionalYear); + + double latRad = toRadians(coordinates.latitude); + // 90.833 deg = 1.5853349194640094 rad + double n1 = cos(1.5853349194640094) / (cos(latRad) * cos(declination)); + double n2 = tan(latRad) * tan(declination); + double hourAngle = acos(n1 - n2); + + double hourAngleDeg = 4 * toDegrees(hourAngle); + + // sunrise = 720 - 4 * (coordinates.longitude + hourAngleDeg) - equationOfTime + // sunset = 720 - 4 * (coordinates.longitude - hourAngleDeg) - equationOfTime + double solarNoon = 720 - 4 * coordinates.longitude - equationOfTime; + double sunrise = solarNoon - hourAngleDeg; + double sunset = solarNoon + hourAngleDeg; + + return new Twilight( + date, + Duration.ofMillis((long) (sunrise * 60000)), + Duration.ofMillis((long) (sunset * 60000)) + ); + } +} diff --git a/src/main/java/eu/m724/wtapi/provider/twilight/TwilightTimeProvider.java b/src/main/java/eu/m724/wtapi/provider/twilight/TwilightTimeProvider.java new file mode 100644 index 0000000..92fd24d --- /dev/null +++ b/src/main/java/eu/m724/wtapi/provider/twilight/TwilightTimeProvider.java @@ -0,0 +1,22 @@ +package eu.m724.wtapi.provider.twilight; + +import eu.m724.wtapi.object.Coordinates; +import eu.m724.wtapi.object.Twilight; + +import java.time.LocalDate; + +// TODO make this an interface? also consider new name +/** + * Twilight refers to sunset and sunrise time
+ * Is it the correct term? I don't know + */ +public abstract class TwilightTimeProvider { + /** + * Calculates sunrise and sunset for provided coordinates + * + * @param date UTC date + * @param coordinates Coordinates of the observer + * @return {@link Twilight} + */ + public abstract Twilight calculateTwilightTime(LocalDate date, Coordinates coordinates); +} diff --git a/src/test/java/eu/m724/wtapi/twilight/ApproximateTwilightTimeTest.java b/src/test/java/eu/m724/wtapi/twilight/ApproximateTwilightTimeTest.java new file mode 100644 index 0000000..341e25b --- /dev/null +++ b/src/test/java/eu/m724/wtapi/twilight/ApproximateTwilightTimeTest.java @@ -0,0 +1,46 @@ +package eu.m724.wtapi.twilight; + +import eu.m724.wtapi.object.Coordinates; +import eu.m724.wtapi.object.Twilight; +import eu.m724.wtapi.provider.twilight.SimpleTwilightTimeProvider; +import eu.m724.wtapi.provider.twilight.TwilightTimeProvider; +import org.junit.Test; + +import java.time.LocalDate; + +public class ApproximateTwilightTimeTest { + /** + * Acceptable discrepancy in minutes + */ + public static final int ACCEPTABLE_DIFFERENCE = 10; + + @Test + public void approximateTest() { + TwilightTimeProvider provider = new SimpleTwilightTimeProvider(); + + testLocation(provider, 26, 6, 2023, 53.123394, 23.0864867, 122, 1139); + testLocation(provider, 13, 11, 2040, 45.432427, -122.3899276, 907, 1481); + testLocation(provider, 23, 3, 2021, 55.5024161, 9.6801853, 315, 1061); + testLocation(provider, 6, 8,1990, -72.012117, 2.5240873, 600, 832); + // TODO this is broken so fix + //testLocation(provider, 6, 9,2021, 82.498665, -62.3458366, 74, 1351); // date not on purpose as this was the first sunset in that location. first sunset since 5th april + } + + private void testLocation(TwilightTimeProvider provider, int day, int month, int year, double latitude, double longitude, int actualSunrise, int actualSunset) { + LocalDate date = LocalDate.of(year, month, day); + Coordinates coordinates = new Coordinates(latitude, longitude); + + Twilight twilight = provider.calculateTwilightTime(date, coordinates); + System.out.println(); + System.out.println(date); + System.out.println(coordinates.latitude + " " + coordinates.longitude); + + System.out.println("Calculated sunrise: " + date.atStartOfDay().plus(twilight.sunrise)); + System.out.println("Actual sunrise: " + date.atStartOfDay().plusMinutes(actualSunrise)); + assert Math.abs(twilight.sunrise.toMinutes() - actualSunrise) < ACCEPTABLE_DIFFERENCE; + + System.out.println("Calculated sunset: " + date.atStartOfDay().plus(twilight.sunset)); + System.out.println("Actual sunset: " + date.atStartOfDay().plusMinutes(actualSunset)); + assert Math.abs(twilight.sunset.toMinutes() - actualSunset) < ACCEPTABLE_DIFFERENCE; + } +} diff --git a/src/test/java/eu/m724/wtapi/twilight/MockTwilightTimeProvider.java b/src/test/java/eu/m724/wtapi/twilight/MockTwilightTimeProvider.java new file mode 100644 index 0000000..138b21b --- /dev/null +++ b/src/test/java/eu/m724/wtapi/twilight/MockTwilightTimeProvider.java @@ -0,0 +1,20 @@ +package eu.m724.wtapi.twilight; + +import eu.m724.wtapi.object.Coordinates; +import eu.m724.wtapi.object.Twilight; +import eu.m724.wtapi.provider.twilight.TwilightTimeProvider; + +import java.time.Duration; +import java.time.LocalDate; + +public class MockTwilightTimeProvider extends TwilightTimeProvider { + @Override + public Twilight calculateTwilightTime(LocalDate date, Coordinates coordinates) { + int time = (int) (coordinates.latitude + coordinates.longitude); + return new Twilight( + date, + Duration.ofMinutes(-time), + Duration.ofMinutes(time) + ); + } +} diff --git a/src/test/java/eu/m724/wtapi/twilight/MockTwilightTimeTest.java b/src/test/java/eu/m724/wtapi/twilight/MockTwilightTimeTest.java new file mode 100644 index 0000000..0b55511 --- /dev/null +++ b/src/test/java/eu/m724/wtapi/twilight/MockTwilightTimeTest.java @@ -0,0 +1,23 @@ +package eu.m724.wtapi.twilight; + +import eu.m724.wtapi.object.Coordinates; +import eu.m724.wtapi.object.Twilight; +import eu.m724.wtapi.provider.twilight.TwilightTimeProvider; +import org.junit.Test; + +import java.time.LocalDate; + +public class MockTwilightTimeTest { + @Test + public void testTwilight() { + TwilightTimeProvider provider = new MockTwilightTimeProvider(); + + LocalDate date = LocalDate.of(2077, 4, 20); + Coordinates coordinates = new Coordinates(52.4796012, 62.1847245); + Twilight twilight = provider.calculateTwilightTime(date, coordinates); + + assert twilight.date.equals(date); + assert twilight.sunrise.getSeconds() == -6840; + assert twilight.sunset.getSeconds() == 6840; + } +}