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 java.lang.Math.*; 017 018import org.shredzone.commons.suncalc.param.Builder; 019import org.shredzone.commons.suncalc.param.GenericParameter; 020import org.shredzone.commons.suncalc.param.LocationParameter; 021import org.shredzone.commons.suncalc.param.TimeParameter; 022import org.shredzone.commons.suncalc.util.BaseBuilder; 023import org.shredzone.commons.suncalc.util.JulianDate; 024import org.shredzone.commons.suncalc.util.Moon; 025import org.shredzone.commons.suncalc.util.Sun; 026import org.shredzone.commons.suncalc.util.Vector; 027 028/** 029 * Calculates the illumination of the moon. 030 * <p> 031 * Starting with v3.9, a geolocation can be set optionally. If set, the results will be 032 * topocentric, relative to the given location. If not set, the result is geocentric, 033 * which was the standard behavior before v3.9. 034 */ 035public class MoonIllumination { 036 037 private final double fraction; 038 private final double phase; 039 private final double angle; 040 private final double elongation; 041 private final double radius; 042 private final double crescentWidth; 043 044 private MoonIllumination(double fraction, double phase, double angle, 045 double elongation, double radius, double crescentWidth) { 046 this.fraction = fraction; 047 this.phase = phase; 048 this.angle = angle; 049 this.elongation = elongation; 050 this.radius = radius; 051 this.crescentWidth = crescentWidth; 052 } 053 054 /** 055 * Starts the computation of {@link MoonIllumination}. 056 * 057 * @return {@link Parameters} to set. 058 */ 059 public static Parameters compute() { 060 return new MoonIlluminationBuilder(); 061 } 062 063 /** 064 * Collects all parameters for {@link MoonIllumination}. 065 */ 066 public interface Parameters extends 067 GenericParameter<Parameters>, 068 TimeParameter<Parameters>, 069 LocationParameter<Parameters>, 070 Builder<MoonIllumination> { 071 072 /** 073 * Clears the geolocation, so the result will be geocentric. 074 * 075 * @return itself 076 * @since 3.9 077 */ 078 Parameters geocentric(); 079 080 } 081 082 /** 083 * Builder for {@link MoonIllumination}. Performs the computations based on the 084 * parameters, and creates a {@link MoonIllumination} object that holds the result. 085 */ 086 private static class MoonIlluminationBuilder extends BaseBuilder<Parameters> implements Parameters { 087 @Override 088 public Parameters geocentric() { 089 clearLocation(); 090 return this; 091 } 092 093 @Override 094 public MoonIllumination execute() { 095 JulianDate t = getJulianDate(); 096 Vector s = Sun.position(t); 097 Vector m = Moon.position(t); 098 099 double phi = PI - acos(m.dot(s) / (m.getR() * s.getR())); 100 Vector sunMoon = m.cross(s); 101 double angle = atan2( 102 cos(s.getTheta()) * sin(s.getPhi() - m.getPhi()), 103 sin(s.getTheta()) * cos(m.getTheta()) - cos(s.getTheta()) * sin(m.getTheta()) * cos(s.getPhi() - m.getPhi()) 104 ); 105 106 Vector sTopo, mTopo; 107 if (hasLocation()) { 108 sTopo = Sun.positionTopocentric(t, getLatitudeRad(), getLongitudeRad(), getElevation()); 109 mTopo = Moon.positionTopocentric(t, getLatitudeRad(), getLongitudeRad(), getElevation()); 110 } else { 111 sTopo = s; 112 mTopo = m; 113 } 114 115 double r = mTopo.subtract(sTopo).norm(); 116 double re = sTopo.norm(); 117 double d = mTopo.norm(); 118 double elongation = acos((d*d + re*re - r*r) / (2.0*d*re)); 119 double moonRadius = Moon.angularRadius(mTopo.getR()); 120 double crescentWidth = moonRadius * (1 - cos(elongation)); 121 122 return new MoonIllumination( 123 (1 + cos(phi)) / 2, 124 toDegrees(phi * signum(sunMoon.getTheta())), 125 toDegrees(angle), 126 toDegrees(elongation), 127 toDegrees(moonRadius), 128 toDegrees(crescentWidth)); 129 } 130 } 131 132 /** 133 * Illuminated fraction. {@code 0.0} indicates new moon, {@code 1.0} indicates full 134 * moon. 135 */ 136 public double getFraction() { 137 return fraction; 138 } 139 140 /** 141 * Moon phase. Starts at {@code -180.0} (new moon, waxing), passes {@code 0.0} (full 142 * moon) and moves toward {@code 180.0} (waning, new moon). 143 * <p> 144 * Note that for historical reasons, the range of this phase is different to the 145 * moon phase angle used in {@link MoonPhase}. 146 */ 147 public double getPhase() { 148 return phase; 149 } 150 151 /** 152 * The angle of the moon illumination relative to earth. The moon is waxing if the 153 * angle is negative, and waning if positive. 154 * <p> 155 * By subtracting {@link MoonPosition#getParallacticAngle()} from {@link #getAngle()}, 156 * one can get the zenith angle of the moons bright limb (anticlockwise). The zenith 157 * angle can be used do draw the moon shape from the observer's perspective (e.g. the 158 * moon lying on its back). 159 */ 160 public double getAngle() { 161 return angle; 162 } 163 164 /** 165 * The closest {@link MoonPhase.Phase} that is matching the moon's angle. 166 * 167 * @return Closest {@link MoonPhase.Phase} 168 * @since 3.5 169 */ 170 public MoonPhase.Phase getClosestPhase() { 171 return MoonPhase.Phase.toPhase(phase + 180.0); 172 } 173 174 /** 175 * The elongation, which is the angular distance between the moon and the sun as 176 * observed from a specific location on earth. 177 * 178 * @return Elongation between moon and sun, in degrees. 179 * @since 3.9 180 */ 181 public double getElongation() { 182 return elongation; 183 } 184 185 /** 186 * The radius of the moon disk, as observed from a specific location on earth. 187 * 188 * @return Moon radius, in degrees. 189 * @since 3.9 190 */ 191 public double getRadius() { 192 return radius; 193 } 194 195 /** 196 * The width of the moon crescent, as observed from a specific location on earth. 197 * 198 * @return Crescent width, in degrees. 199 * @since 3.9 200 */ 201 public double getCrescentWidth() { 202 return crescentWidth; 203 } 204 205 @Override 206 public String toString() { 207 StringBuilder sb = new StringBuilder(); 208 sb.append("MoonIllumination[fraction=").append(fraction); 209 sb.append(", phase=").append(phase); 210 sb.append("°, angle=").append(angle); 211 sb.append("°, elongation=").append(elongation); 212 sb.append("°, radius=").append(radius); 213 sb.append("°, crescentWidth=").append(crescentWidth); 214 sb.append("°]"); 215 return sb.toString(); 216 } 217 218}