001/* 002 * Shredzone Commons - suncalc 003 * 004 * Copyright (C) 2018 Richard "Shred" Körber 005 * http://commons.shredzone.org 006 * 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 013 */ 014package org.shredzone.commons.suncalc; 015 016import static java.lang.Math.PI; 017import static java.lang.Math.toRadians; 018import static org.shredzone.commons.suncalc.util.ExtendedMath.PI2; 019 020import java.util.Date; 021 022import org.shredzone.commons.suncalc.param.Builder; 023import org.shredzone.commons.suncalc.param.GenericParameter; 024import org.shredzone.commons.suncalc.param.TimeParameter; 025import org.shredzone.commons.suncalc.param.TimeResultParameter; 026import org.shredzone.commons.suncalc.util.BaseBuilder; 027import org.shredzone.commons.suncalc.util.JulianDate; 028import org.shredzone.commons.suncalc.util.Moon; 029import org.shredzone.commons.suncalc.util.Pegasus; 030import org.shredzone.commons.suncalc.util.Sun; 031import org.shredzone.commons.suncalc.util.Vector; 032 033/** 034 * Calculates the date and time when the moon reaches the desired phase. 035 * <p> 036 * Note: Due to the simplified formulas used in suncalc, the returned time can have an 037 * error of several minutes. 038 * 039 * @since 2.3 040 */ 041public class MoonPhase { 042 043 private final Date time; 044 private final double distance; 045 046 private MoonPhase(Date time, double distance) { 047 this.time = time; 048 this.distance = distance; 049 } 050 051 /** 052 * Starts the computation of {@link MoonPhase}. 053 * 054 * @return {@link Parameters} to set. 055 */ 056 public static Parameters compute() { 057 return new MoonPhaseBuilder(); 058 } 059 060 /** 061 * Collects all parameters for {@link MoonPhase}. 062 * 063 * @since 2.3 064 */ 065 public interface Parameters extends 066 GenericParameter<Parameters>, 067 TimeParameter<Parameters>, 068 TimeResultParameter<Parameters>, 069 Builder<MoonPhase> { 070 071 /** 072 * Sets the desired {@link Phase}. 073 * <p> 074 * Defaults to {@link Phase#NEW_MOON}. 075 * 076 * @param phase 077 * {@link Phase} to be used. 078 * @return itself 079 */ 080 Parameters phase(Phase phase); 081 082 /** 083 * Sets a free phase to be used. 084 * 085 * @param phase 086 * Desired phase, in degrees. 0 = new moon, 90 = first quarter, 180 = 087 * full moon, 270 = third quarter. 088 * @return itself 089 */ 090 Parameters phase(double phase); 091 } 092 093 /** 094 * Enumeration of moon phases. 095 * 096 * @since 2.3 097 */ 098 public enum Phase { 099 100 /** 101 * New moon. 102 */ 103 NEW_MOON(0.0), 104 105 /** 106 * Waxing crescent moon. 107 * 108 * @since 2.12 109 */ 110 WAXING_CRESCENT(45.0), 111 112 /** 113 * Waxing half moon. 114 */ 115 FIRST_QUARTER(90.0), 116 117 /** 118 * Waxing gibbous moon. 119 * 120 * @since 2.12 121 */ 122 WAXING_GIBBOUS(135.0), 123 124 /** 125 * Full moon. 126 */ 127 FULL_MOON(180.0), 128 129 /** 130 * Waning gibbous moon. 131 * 132 * @since 2.12 133 */ 134 WANING_GIBBOUS(225.0), 135 136 /** 137 * Waning half moon. 138 */ 139 LAST_QUARTER(270.0), 140 141 /** 142 * Waning crescent moon. 143 * 144 * @since 2.12 145 */ 146 WANING_CRESCENT(315.0); 147 148 /** 149 * Converts an angle to the closest matching moon phase. 150 * 151 * @param angle 152 * Moon phase angle, in degrees. 0 = New Moon, 180 = Full Moon. Angles 153 * outside the [0,360) range are normalized into that range. 154 * @return Closest Phase that is matching that angle. 155 * @since 2.12 156 */ 157 public static Phase toPhase(double angle) { 158 // bring into range 0.0 .. 360.0 159 double normalized = angle % 360.0; 160 if (normalized < 0.0) { 161 normalized += 360.0; 162 } 163 164 if (normalized < 22.5) { 165 return NEW_MOON; 166 } 167 if (normalized < 67.5) { 168 return WAXING_CRESCENT; 169 } 170 if (normalized < 112.5) { 171 return FIRST_QUARTER; 172 } 173 if (normalized < 157.5) { 174 return WAXING_GIBBOUS; 175 } 176 if (normalized < 202.5) { 177 return FULL_MOON; 178 } 179 if (normalized < 247.5) { 180 return WANING_GIBBOUS; 181 } 182 if (normalized < 292.5) { 183 return LAST_QUARTER; 184 } 185 if (normalized < 337.5) { 186 return WANING_CRESCENT; 187 } 188 return NEW_MOON; 189 } 190 191 private final double angle; 192 private final double angleRad; 193 194 Phase(double angle) { 195 this.angle = angle; 196 this.angleRad = toRadians(angle); 197 } 198 199 /** 200 * Returns the moons's angle in reference to the sun, in degrees. 201 */ 202 public double getAngle() { 203 return angle; 204 } 205 206 /** 207 * Returns the moons's angle in reference to the sun, in radians. 208 */ 209 public double getAngleRad() { 210 return angleRad; 211 } 212 } 213 214 /** 215 * Builder for {@link MoonPhase}. Performs the computations based on the parameters, 216 * and creates a {@link MoonPhase} object that holds the result. 217 */ 218 private static class MoonPhaseBuilder extends BaseBuilder<Parameters> implements Parameters { 219 private static final double SUN_LIGHT_TIME_TAU = 8.32 / (1440.0 * 36525.0); 220 221 private double phase = Phase.NEW_MOON.getAngleRad(); 222 223 @Override 224 public Parameters phase(Phase phase) { 225 this.phase = phase.getAngleRad(); 226 return this; 227 } 228 229 @Override 230 public Parameters phase(double phase) { 231 this.phase = toRadians(phase); 232 return this; 233 } 234 235 @Override 236 public MoonPhase execute() { 237 final JulianDate jd = getJulianDate(); 238 239 double dT = 7.0 / 36525.0; // step rate: 1 week 240 double accuracy = (0.5 / 1440.0) / 36525.0; // accuracy: 30 seconds 241 242 double t0 = jd.getJulianCentury(); 243 double t1 = t0 + dT; 244 245 double d0 = moonphase(jd, t0); 246 double d1 = moonphase(jd, t1); 247 248 while (d0 * d1 > 0.0 || d1 < d0) { 249 t0 = t1; 250 d0 = d1; 251 t1 += dT; 252 d1 = moonphase(jd, t1); 253 } 254 255 double tphase = Pegasus.calculate(t0, t1, accuracy, new Pegasus.Function() { 256 @Override 257 public double apply(double x) { 258 return moonphase(jd, x); 259 } 260 }); 261 262 JulianDate tjd = jd.atJulianCentury(tphase); 263 return new MoonPhase(tjd.getDateTruncated(getTruncatedTo()), Moon.positionEquatorial(tjd).getR()); 264 } 265 266 /** 267 * Calculates the position of the moon at the given phase. 268 * 269 * @param jd 270 * Base Julian date 271 * @param t 272 * Ephemeris time 273 * @return difference angle of the sun's and moon's position 274 */ 275 private double moonphase(JulianDate jd, double t) { 276 Vector sun = Sun.positionEquatorial(jd.atJulianCentury(t - SUN_LIGHT_TIME_TAU)); 277 Vector moon = Moon.positionEquatorial(jd.atJulianCentury(t)); 278 double diff = moon.getPhi() - sun.getPhi() - phase; //NOSONAR: false positive 279 while (diff < 0.0) { 280 diff += PI2; 281 } 282 return ((diff + PI) % PI2) - PI; 283 } 284 285 } 286 287 /** 288 * Date and time of the desired moon phase. The time is rounded to full minutes. 289 */ 290 public Date getTime() { 291 return new Date(time.getTime()); 292 } 293 294 /** 295 * Geocentric distance of the moon at the given phase, in kilometers. 296 * 297 * @since 2.11 298 */ 299 public double getDistance() { return distance; } 300 301 /** 302 * Checks if the moon is in a SuperMoon position. 303 * <p> 304 * Note that there is no official definition of supermoon. Suncalc will assume a 305 * supermoon if the center of the moon is closer than 360,000 km to the center of 306 * Earth. Usually only full moons or new moons are candidates for supermoons. 307 * 308 * @since 2.11 309 */ 310 public boolean isSuperMoon() { 311 return distance < 360000.0; 312 } 313 314 /** 315 * Checks if the moon is in a MicroMoon position. 316 * <p> 317 * Note that there is no official definition of micromoon. Suncalc will assume a 318 * micromoon if the center of the moon is farther than 405,000 km from the center of 319 * Earth. Usually only full moons or new moons are candidates for micromoons. 320 * 321 * @since 2.11 322 */ 323 public boolean isMicroMoon() { 324 return distance > 405000.0; 325 } 326 327 @Override 328 public String toString() { 329 StringBuilder sb = new StringBuilder(); 330 sb.append("MoonPhase[time=").append(time); 331 sb.append(", distance=").append(distance); 332 sb.append(" km]"); 333 return sb.toString(); 334 } 335 336}