001/* 002 * Shredzone Commons - suncalc 003 * 004 * Copyright (C) 2017 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 org.shredzone.commons.suncalc.util.ExtendedMath.apparentRefraction; 017import static org.shredzone.commons.suncalc.util.ExtendedMath.parallax; 018 019import java.util.Date; 020 021import edu.umd.cs.findbugs.annotations.Nullable; 022import org.shredzone.commons.suncalc.param.Builder; 023import org.shredzone.commons.suncalc.param.GenericParameter; 024import org.shredzone.commons.suncalc.param.LocationParameter; 025import org.shredzone.commons.suncalc.param.TimeParameter; 026import org.shredzone.commons.suncalc.param.TimeResultParameter; 027import org.shredzone.commons.suncalc.util.BaseBuilder; 028import org.shredzone.commons.suncalc.util.JulianDate; 029import org.shredzone.commons.suncalc.util.Moon; 030import org.shredzone.commons.suncalc.util.QuadraticInterpolation; 031import org.shredzone.commons.suncalc.util.Vector; 032 033/** 034 * Calculates the times of the moon. 035 */ 036public final class MoonTimes { 037 038 private final @Nullable Date rise; 039 private final @Nullable Date set; 040 private final boolean alwaysUp; 041 private final boolean alwaysDown; 042 043 private MoonTimes(@Nullable Date rise, @Nullable Date set, boolean alwaysUp, 044 boolean alwaysDown) { 045 this.rise = rise; 046 this.set = set; 047 this.alwaysUp = alwaysUp; 048 this.alwaysDown = alwaysDown; 049 } 050 051 /** 052 * Starts the computation of {@link MoonTimes}. 053 * 054 * @return {@link Parameters} to set. 055 */ 056 public static Parameters compute() { 057 return new MoonTimesBuilder(); 058 } 059 060 /** 061 * Collects all parameters for {@link MoonTimes}. 062 */ 063 public static interface Parameters extends 064 GenericParameter<Parameters>, 065 LocationParameter<Parameters>, 066 TimeParameter<Parameters>, 067 TimeResultParameter<Parameters>, 068 Builder<MoonTimes> { 069 070 /** 071 * Checks only the next 24 hours. Rise or set times can be {@code null} if the 072 * moon never reaches the point during one day. 073 * <p> 074 * This is the default. 075 * 076 * @return itself 077 */ 078 Parameters oneDay(); 079 080 /** 081 * Computes until rise and set times are found, even if the moon needs more than a 082 * day for it. This can increase computation time. 083 * 084 * @return itself 085 */ 086 Parameters fullCycle(); 087 } 088 089 /** 090 * Builder for {@link MoonTimes}. Performs the computations based on the parameters, 091 * and creates a {@link MoonTimes} object that holds the result. 092 */ 093 private static class MoonTimesBuilder extends BaseBuilder<Parameters> implements Parameters { 094 private boolean fullCycle = false; 095 private double refraction = apparentRefraction(0.0); 096 097 @Override 098 public Parameters oneDay() { 099 this.fullCycle = false; 100 return this; 101 } 102 103 @Override 104 public Parameters fullCycle() { 105 this.fullCycle = true; 106 return this; 107 } 108 109 @Override 110 public MoonTimes execute() { 111 JulianDate jd = getJulianDate(); 112 113 Double rise = null; 114 Double set = null; 115 boolean alwaysUp = false; 116 boolean alwaysDown = false; 117 118 int hour = 0; 119 int maxHours = fullCycle ? 365 * 24 : 24; 120 121 double y_minus = correctedMoonHeight(jd.atHour(hour - 1.0)); 122 double y_0 = correctedMoonHeight(jd.atHour(hour)); 123 double y_plus = correctedMoonHeight(jd.atHour(hour + 1.0)); 124 125 while (hour <= maxHours) { 126 QuadraticInterpolation qi = new QuadraticInterpolation(y_minus, y_0, y_plus); 127 double ye = qi.getYe(); 128 129 if (qi.getNumberOfRoots() == 1) { 130 double rt = qi.getRoot1() + hour; 131 if (y_minus < 0.0) { 132 if (rise == null && rt >= 0.0) { 133 rise = rt; 134 } 135 } else { 136 if (set == null && rt >= 0.0) { 137 set = rt; 138 } 139 } 140 } else if (qi.getNumberOfRoots() == 2) { 141 if (rise == null) { 142 double rt = hour + (ye < 0.0 ? qi.getRoot2() : qi.getRoot1()); 143 if (rt >= 0.0) { 144 rise = rt; 145 } 146 } 147 if (set == null) { 148 double rt = hour + (ye < 0.0 ? qi.getRoot1() : qi.getRoot2()); 149 if (rt >= 0.0) { 150 set = rt; 151 } 152 } 153 } 154 155 if (hour == 23 && rise == null && set == null) { 156 alwaysUp = ye >= 0.0; 157 alwaysDown = ye < 0.0; 158 } 159 160 if (rise != null && set != null) { 161 break; 162 } 163 164 hour++; 165 y_minus = y_0; 166 y_0 = y_plus; 167 y_plus = correctedMoonHeight(jd.atHour(hour + 1.0)); 168 } 169 170 if (!fullCycle) { 171 if (rise != null && rise >= 24.0) { 172 rise = null; 173 } 174 if (set != null && set >= 24.0) { 175 set = null; 176 } 177 } 178 179 return new MoonTimes( 180 rise != null ? jd.atHour(rise).getDateTruncated(getTruncatedTo()) : null, 181 set != null ? jd.atHour(set).getDateTruncated(getTruncatedTo()) : null, 182 alwaysUp, 183 alwaysDown); 184 } 185 186 /** 187 * Computes the moon height at the given date and position. 188 * 189 * @param jd {@link JulianDate} to use 190 * @return height, in radians 191 */ 192 private double correctedMoonHeight(JulianDate jd) { 193 Vector pos = Moon.positionHorizontal(jd, getLatitudeRad(), getLongitudeRad()); 194 double hc = parallax(getHeight(), pos.getR()) 195 - refraction 196 - Moon.angularRadius(pos.getR()); 197 return pos.getTheta() - hc; 198 } 199 } 200 201 /** 202 * Moonrise time. {@code null} if the moon does not rise that day. 203 */ 204 @Nullable 205 public Date getRise() { 206 return rise != null ? new Date(rise.getTime()) : null; 207 } 208 209 /** 210 * Moonset time. {@code null} if the moon does not set that day. 211 */ 212 @Nullable 213 public Date getSet() { 214 return set != null ? new Date(set.getTime()) : null; 215 } 216 217 /** 218 * {@code true} if the moon never rises/sets, but is always above the horizon within 219 * the next 24 hours. 220 * <p> 221 * Note that {@link Parameters#fullCycle()} does not affect this result. 222 */ 223 public boolean isAlwaysUp() { 224 return alwaysUp; 225 } 226 227 /** 228 * {@code true} if the moon never rises/sets, but is always below the horizon within 229 * the next 24 hours. 230 * <p> 231 * Note that {@link Parameters#fullCycle()} does not affect this result. 232 */ 233 public boolean isAlwaysDown() { 234 return alwaysDown; 235 } 236 237 @Override 238 public String toString() { 239 StringBuilder sb = new StringBuilder(); 240 sb.append("MoonTimes[rise=").append(rise); 241 sb.append(", set=").append(set); 242 sb.append(", alwaysUp=").append(alwaysUp); 243 sb.append(", alwaysDown=").append(alwaysDown); 244 sb.append(']'); 245 return sb.toString(); 246 } 247 248}