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}