org.shredzone.commons.suncalc.MoonTimes Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-suncalc Show documentation
Show all versions of commons-suncalc Show documentation
Compute sun and moon phases
The newest version!
/*
* Shredzone Commons - suncalc
*
* Copyright (C) 2017 Richard "Shred" Körber
* http://commons.shredzone.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
package org.shredzone.commons.suncalc;
import static java.lang.Math.ceil;
import static java.lang.Math.floor;
import static org.shredzone.commons.suncalc.util.ExtendedMath.apparentRefraction;
import static org.shredzone.commons.suncalc.util.ExtendedMath.parallax;
import java.time.Duration;
import java.time.ZonedDateTime;
import edu.umd.cs.findbugs.annotations.Nullable;
import org.shredzone.commons.suncalc.param.Builder;
import org.shredzone.commons.suncalc.param.GenericParameter;
import org.shredzone.commons.suncalc.param.LocationParameter;
import org.shredzone.commons.suncalc.param.TimeParameter;
import org.shredzone.commons.suncalc.param.WindowParameter;
import org.shredzone.commons.suncalc.util.BaseBuilder;
import org.shredzone.commons.suncalc.util.JulianDate;
import org.shredzone.commons.suncalc.util.Moon;
import org.shredzone.commons.suncalc.util.QuadraticInterpolation;
import org.shredzone.commons.suncalc.util.Vector;
/**
* Calculates the times of the moon.
*/
public final class MoonTimes {
private final @Nullable ZonedDateTime rise;
private final @Nullable ZonedDateTime set;
private final boolean alwaysUp;
private final boolean alwaysDown;
private MoonTimes(@Nullable ZonedDateTime rise, @Nullable ZonedDateTime set,
boolean alwaysUp, boolean alwaysDown) {
this.rise = rise;
this.set = set;
this.alwaysUp = alwaysUp;
this.alwaysDown = alwaysDown;
}
/**
* Starts the computation of {@link MoonTimes}.
*
* @return {@link Parameters} to set.
*/
public static Parameters compute() {
return new MoonTimesBuilder();
}
/**
* Collects all parameters for {@link MoonTimes}.
*/
public static interface Parameters extends
GenericParameter,
LocationParameter,
TimeParameter,
WindowParameter,
Builder {
}
/**
* Builder for {@link MoonTimes}. Performs the computations based on the parameters,
* and creates a {@link MoonTimes} object that holds the result.
*/
private static class MoonTimesBuilder extends BaseBuilder implements Parameters {
private double refraction = apparentRefraction(0.0);
@Override
public MoonTimes execute() {
if (!hasLocation()) {
throw new IllegalArgumentException("Geolocation is missing.");
}
JulianDate jd = getJulianDate();
Double rise = null;
Double set = null;
boolean alwaysUp = false;
boolean alwaysDown = false;
double ye;
int hourStep;
double lowerLimitHours, upperLimitHours;
if (getDuration().isNegative()) {
hourStep = -1;
lowerLimitHours = getDuration().toMillis() / (60 * 60 * 1000.0);
upperLimitHours = 0.0;
} else {
hourStep = 1;
lowerLimitHours = 0.0;
upperLimitHours = getDuration().toMillis() / (60 * 60 * 1000.0);;
}
int hour = 0;
int minHours = (int) floor(lowerLimitHours);
int maxHours = (int) ceil(upperLimitHours);
double y_minus = correctedMoonHeight(jd.atHour(hour - 1.0));
double y_0 = correctedMoonHeight(jd.atHour(hour));
double y_plus = correctedMoonHeight(jd.atHour(hour + 1.0));
if (y_0 > 0.0) {
alwaysUp = true;
} else {
alwaysDown = true;
}
while (hour <= maxHours && hour >= minHours) {
QuadraticInterpolation qi = new QuadraticInterpolation(y_minus, y_0, y_plus);
ye = qi.getYe();
if (qi.getNumberOfRoots() == 1) {
double rt = qi.getRoot1() + hour;
if (y_minus < 0.0) {
if (rise == null && rt >= lowerLimitHours && rt < upperLimitHours) {
rise = rt;
alwaysDown = false;
}
} else {
if (set == null && rt >= lowerLimitHours && rt < upperLimitHours) {
set = rt;
alwaysUp = false;
}
}
} else if (qi.getNumberOfRoots() == 2) {
if (rise == null) {
double rt = hour + (ye < 0.0 ? qi.getRoot2() : qi.getRoot1());
if (rt >= lowerLimitHours && rt < upperLimitHours) {
rise = rt;
alwaysDown = false;
}
}
if (set == null) {
double rt = hour + (ye < 0.0 ? qi.getRoot1() : qi.getRoot2());
if (rt >= lowerLimitHours && rt < upperLimitHours) {
set = rt;
alwaysUp = false;
}
}
}
if (rise != null && set != null) {
break;
}
hour += hourStep;
if (hourStep > 0) {
y_minus = y_0;
y_0 = y_plus;
y_plus = correctedMoonHeight(jd.atHour(hour + 1.0));
} else {
y_plus = y_0;
y_0 = y_minus;
y_minus = correctedMoonHeight(jd.atHour(hour - 1.0));
}
}
return new MoonTimes(
rise != null ? jd.atHour(rise).getDateTime() : null,
set != null ? jd.atHour(set).getDateTime() : null,
alwaysUp,
alwaysDown);
}
/**
* Computes the moon height at the given date and position.
*
* @param jd {@link JulianDate} to use
* @return height, in radians
*/
private double correctedMoonHeight(JulianDate jd) {
Vector pos = Moon.positionHorizontal(jd, getLatitudeRad(), getLongitudeRad());
double hc = parallax(getElevation(), pos.getR())
- refraction
- Moon.angularRadius(pos.getR());
return pos.getTheta() - hc;
}
}
/**
* Moonrise time. {@code null} if the moon does not rise that day.
*/
@Nullable
public ZonedDateTime getRise() {
return rise;
}
/**
* Moonset time. {@code null} if the moon does not set that day.
*/
@Nullable
public ZonedDateTime getSet() {
return set;
}
/**
* {@code true} if the moon never rises/sets, but is always above the horizon.
*/
public boolean isAlwaysUp() {
return alwaysUp;
}
/**
* {@code true} if the moon never rises/sets, but is always below the horizon.
*/
public boolean isAlwaysDown() {
return alwaysDown;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("MoonTimes[rise=").append(rise);
sb.append(", set=").append(set);
sb.append(", alwaysUp=").append(alwaysUp);
sb.append(", alwaysDown=").append(alwaysDown);
sb.append(']');
return sb.toString();
}
}