All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.shredzone.commons.suncalc.MoonPhase Maven / Gradle / Ivy

The newest version!
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2018 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.PI;
import static java.lang.Math.toRadians;
import static org.shredzone.commons.suncalc.util.ExtendedMath.PI2;

import java.time.ZonedDateTime;

import org.shredzone.commons.suncalc.param.Builder;
import org.shredzone.commons.suncalc.param.GenericParameter;
import org.shredzone.commons.suncalc.param.TimeParameter;
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.Pegasus;
import org.shredzone.commons.suncalc.util.Sun;
import org.shredzone.commons.suncalc.util.Vector;

/**
 * Calculates the date and time when the moon reaches the desired phase.
 * 

* Note: Due to the simplified formulas used in suncalc, the returned time can have an * error of several minutes. */ public class MoonPhase { private final ZonedDateTime time; private final double distance; private MoonPhase(ZonedDateTime time, double distance) { this.time = time; this.distance = distance; } /** * Starts the computation of {@link MoonPhase}. * * @return {@link Parameters} to set. */ public static Parameters compute() { return new MoonPhaseBuilder(); } /** * Collects all parameters for {@link MoonPhase}. */ public interface Parameters extends GenericParameter, TimeParameter, Builder { /** * Sets the desired {@link Phase}. *

* Defaults to {@link Phase#NEW_MOON}. * * @param phase * {@link Phase} to be used. * @return itself */ Parameters phase(Phase phase); /** * Sets a free phase to be used. * * @param phase * Desired phase, in degrees. 0 = new moon, 90 = first quarter, 180 = * full moon, 270 = third quarter. * @return itself */ Parameters phase(double phase); } /** * Enumeration of moon phases. */ public enum Phase { /** * New moon. */ NEW_MOON(0.0), /** * Waxing crescent moon. * * @since 3.5 */ WAXING_CRESCENT(45.0), /** * Waxing half moon. */ FIRST_QUARTER(90.0), /** * Waxing gibbous moon. * * @since 3.5 */ WAXING_GIBBOUS(135.0), /** * Full moon. */ FULL_MOON(180.0), /** * Waning gibbous moon. * * @since 3.5 */ WANING_GIBBOUS(225.0), /** * Waning half moon. */ LAST_QUARTER(270.0), /** * Waning crescent moon. * * @since 3.5 */ WANING_CRESCENT(315.0); /** * Converts an angle to the closest matching moon phase. * * @param angle * Moon phase angle, in degrees. 0 = New Moon, 180 = Full Moon. Angles * outside the [0,360) range are normalized into that range. * @return Closest Phase that is matching that angle. * @since 3.5 */ public static Phase toPhase(double angle) { // bring into range 0.0 .. 360.0 double normalized = angle % 360.0; if (normalized < 0.0) { normalized += 360.0; } if (normalized < 22.5) { return NEW_MOON; } if (normalized < 67.5) { return WAXING_CRESCENT; } if (normalized < 112.5) { return FIRST_QUARTER; } if (normalized < 157.5) { return WAXING_GIBBOUS; } if (normalized < 202.5) { return FULL_MOON; } if (normalized < 247.5) { return WANING_GIBBOUS; } if (normalized < 292.5) { return LAST_QUARTER; } if (normalized < 337.5) { return WANING_CRESCENT; } return NEW_MOON; } private final double angle; private final double angleRad; Phase(double angle) { this.angle = angle; this.angleRad = toRadians(angle); } /** * Returns the moons's angle in reference to the sun, in degrees. */ public double getAngle() { return angle; } /** * Returns the moons's angle in reference to the sun, in radians. */ public double getAngleRad() { return angleRad; } } /** * Builder for {@link MoonPhase}. Performs the computations based on the parameters, * and creates a {@link MoonPhase} object that holds the result. */ private static class MoonPhaseBuilder extends BaseBuilder implements Parameters { private static final double SUN_LIGHT_TIME_TAU = 8.32 / (1440.0 * 36525.0); private double phase = Phase.NEW_MOON.getAngleRad(); @Override public Parameters phase(Phase phase) { this.phase = phase.getAngleRad(); return this; } @Override public Parameters phase(double phase) { this.phase = toRadians(phase); return this; } @Override public MoonPhase execute() { final JulianDate jd = getJulianDate(); double dT = 7.0 / 36525.0; // step rate: 1 week double accuracy = (0.5 / 1440.0) / 36525.0; // accuracy: 30 seconds double t0 = jd.getJulianCentury(); double t1 = t0 + dT; double d0 = moonphase(jd, t0); double d1 = moonphase(jd, t1); while (d0 * d1 > 0.0 || d1 < d0) { t0 = t1; d0 = d1; t1 += dT; d1 = moonphase(jd, t1); } double tphase = Pegasus.calculate(t0, t1, accuracy, x -> moonphase(jd, x)); JulianDate tjd = jd.atJulianCentury(tphase); return new MoonPhase(tjd.getDateTime(), Moon.positionEquatorial(tjd).getR()); } /** * Calculates the position of the moon at the given phase. * * @param jd * Base Julian date * @param t * Ephemeris time * @return difference angle of the sun's and moon's position */ private double moonphase(JulianDate jd, double t) { Vector sun = Sun.positionEquatorial(jd.atJulianCentury(t - SUN_LIGHT_TIME_TAU)); Vector moon = Moon.positionEquatorial(jd.atJulianCentury(t)); double diff = moon.getPhi() - sun.getPhi() - phase; //NOSONAR: false positive while (diff < 0.0) { diff += PI2; } return ((diff + PI) % PI2) - PI; } } /** * Date and time of the desired moon phase. The time is rounded to full minutes. */ public ZonedDateTime getTime() { return time; } /** * Geocentric distance of the moon at the given phase, in kilometers. * * @since 3.4 */ public double getDistance() { return distance; } /** * Checks if the moon is in a SuperMoon position. *

* Note that there is no official definition of supermoon. Suncalc will assume a * supermoon if the center of the moon is closer than 360,000 km to the center of * Earth. Usually only full moons or new moons are candidates for supermoons. * * @since 3.4 */ public boolean isSuperMoon() { return distance < 360000.0; } /** * Checks if the moon is in a MicroMoon position. *

* Note that there is no official definition of micromoon. Suncalc will assume a * micromoon if the center of the moon is farther than 405,000 km from the center of * Earth. Usually only full moons or new moons are candidates for micromoons. * * @since 3.4 */ public boolean isMicroMoon() { return distance > 405000.0; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("MoonPhase[time=").append(time); sb.append(", distance=").append(distance); sb.append(" km]"); return sb.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy