public static Vector equatorialToHorizontal(double tau, double dec, double dist, double lat) {
    return Matrix.rotateY(PI / 2.0 - lat).multiply(Vector.ofPolar(tau, dec, dist));
} public static double refraction(double h) {
    if (h < 0.0) {
        return 0.0;
    }

    // refraction formula, converted to radians
    return 0.000296706 / tan(h + 0.00312537 / (h + 0.0890118));
} public interface Builder<T> {

    /**
     * Executes the calculation and returns the desired result.
     * <p>
     * The resulting object is immutable. You can change parameters, and then invoke
     * {@link #execute()} again, to get a new object with new results.
     *
     * @return Result of the calculation.
     */
    T execute();

} public interface GenericParameter<T> {

    /**
     * Creates a copy of the current parameters. The copy can be changed independently.
     */
    T copy();

} @SuppressWarnings("unchecked")
public interface LocationParameter<T> {

    /**
     * Sets the latitude.
     *
     * @param lat
     *            Latitude, in degrees.
     * @return itself
     */
    T latitude(double lat);

    /**
     * Sets the longitude.
     *
     * @param lng
     *            Longitude, in degrees.
     * @return itself
     */
    T longitude(double lng);

    /**
     * Sets the height.
     *
     * @param h
     *            Height, in meters above sea level. Default: 0.0 m. Negative values are
     *            silently changed to the acceptable minimum of 0.0 m.
     * @return itself
     */
    T height(double h);

    /**
     * Sets the geolocation.
     *
     * @param lat
     *            Latitude, in degrees.
     * @param lng
     *            Longitude, in degrees.
     * @return itself
     */
    default T at(double lat, double lng) {
        latitude(lat);
        longitude(lng);
        return (T) this;
    }

    /**
     * Sets the geolocation. In the given array, index 0 must contain the latitude, and
     * index 1 must contain the longitude. An optional index 2 may contain the height, in
     * meters.
     * <p>
     * This call is meant to be used for coordinates stored in constants.
     *
     * @param coords
     *            Array containing the latitude and longitude, in degrees.
     * @return itself
     */
    default T at(double[] coords) {
        if (coords.length != 2 && coords.length != 3) {
            throw new IllegalArgumentException("Array must contain 2 or 3 doubles");
        }
        if (coords.length == 3) {
            height(coords[2]);
        }
        return at(coords[0], coords[1]);
    }

    /**
     * Sets the latitude.
     *
     * @param d
     *            Degrees
     * @param m
     *            Minutes
     * @param s
     *            Seconds (and fraction of seconds)
     * @return itself
     */
    default T latitude(int d, int m, double s) {
        return latitude(dms(d, m, s));
    }

    /**
     * Sets the longitude.
     *
     * @param d
     *            Degrees
     * @param m
     *            Minutes
     * @param s
     *            Seconds (and fraction of seconds)
     * @return itself
     */
    default T longitude(int d, int m, double s) {
        return longitude(dms(d, m, s));
    }

    /**
     * Uses the same location as given in the {@link LocationParameter} at this moment.
     * <p>
     * Changes to the source parameter will not affect this parameter, though.
     *
     * @param l  {@link LocationParameter} to be used.
     * @return itself
     */
    T sameLocationAs(LocationParameter<?> l);

} @SuppressWarnings("unchecked")
public interface TimeParameter<T> {

    /**
     * Sets date and time. Note that also seconds can be passed in for convenience, but
     * the results are not that accurate.
     *
     * @param year
     *            Year
     * @param month
     *            Month (1 = January, 2 = February, ...)
     * @param date
     *            Day of month
     * @param hour
     *            Hour of day
     * @param minute
     *            Minute
     * @param second
     *            Second
     * @return itself
     */
    T on(int year, int month, int date, int hour, int minute, int second);

    /**
     * Sets midnight of the year, month and date.
     *
     * @param year
     *            Year
     * @param month
     *            Month (1 = January, 2 = February, ...)
     * @param date
     *            Day of month
     * @return itself
     */
    default T on(int year, int month, int date) {
        return on(year, month, date, 0, 0, 0);
    }

    /**
     * Uses the given {@link ZonedDateTime} instance.
     *
     * @param dateTime
     *            {@link ZonedDateTime} to be used.
     * @return itself
     */
    T on(ZonedDateTime dateTime);

    /**
     * Uses the given {@link LocalDateTime} instance.
     *
     * @param dateTime
     *         {@link LocalDateTime} to be used.
     * @return itself
     */
    T on(LocalDateTime dateTime);

    /**
     * Uses the given {@link LocalDate} instance, and assumes midnight.
     *
     * @param date
     *         {@link LocalDate} to be used.
     * @return itself
     */
    T on(LocalDate date);

    /**
     * Uses the given {@link Instant} instance.
     *
     * @param instant
     *            {@link Instant} to be used.
     * @return itself
     */
    T on(Instant instant);

    /**
     * Uses the given {@link Date} instance.
     *
     * @param date
     *         {@link Date} to be used.
     * @return itself
     */
    default T on(Date date) {
        Objects.requireNonNull(date, "date");
        return on(date.toInstant());
    }

    /**
     * Uses the given {@link Calendar} instance.
     *
     * @param cal
     *         {@link Calendar} to be used
     * @return itself
     */
    default T on(Calendar cal) {
        Objects.requireNonNull(cal, "cal");
        return on(ZonedDateTime.ofInstant(cal.toInstant(), cal.getTimeZone().toZoneId()));
    }

    /**
     * Sets the current date and time. This is the default.
     *
     * @return itself
     */
    T now();

    /**
     * Sets the time to the start of the current date ("last midnight").
     *
     * @return itself
     */
    T midnight();

    /**
     * Adds a number of days to the current date.
     *
     * @param days
     *            Number of days to add
     * @return itself
     */
    T plusDays(int days);

    /**
     * Sets today, midnight.
     * <p>
     * It is the same as <code>now().midnight()</code>.
     *
     * @return itself
     */
    default T today() {
        now();
        midnight();
        return (T) this;
    }

    /**
     * Sets tomorrow, midnight.
     * <p>
     * It is the same as <code>now().midnight().plusDays(1)</code>.
     *
     * @return itself
     */
    default T tomorrow() {
        today();
        plusDays(1);
        return (T) this;
    }

    /**
     * Sets the given {@link ZoneId}. The local time is retained, so the parameter order
     * is not important.
     *
     * @param tz
     *            {@link ZoneId} to be used.
     * @return itself
     */
    T timezone(ZoneId tz);

    /**
     * Sets the given timezone. This is a convenience method that just invokes
     * {@link ZoneId#of(String)}.
     *
     * @param id
     *            ID of the time zone.
     * @return itself
     * @see ZoneId#of(String)
     */
    default T timezone(String id) {
        return timezone(ZoneId.of(id));
    }

    /**
     * Sets the system's timezone. This is the default.
     *
     * @return itself
     */
    default T localTime() {
        return timezone(ZoneId.systemDefault());
    }

    /**
     * Sets the time zone to UTC.
     *
     * @return itself
     */
    default T utc() {
        return timezone("UTC");
    }

    /**
     * Sets the {@link TimeZone}.
     *
     * @param tz {@link TimeZone} to be used
     * @return itself
     */
    default T timezone(TimeZone tz) {
        Objects.requireNonNull(tz, "tz");
        return timezone(tz.toZoneId());
    }

    /**
     * Uses the same time as given in the {@link TimeParameter}.
     * <p>
     * Changes to the source parameter will not affect this parameter, though.
     *
     * @param t  {@link TimeParameter} to be used.
     * @return itself
     */
    T sameTimeAs(TimeParameter<?> t);

} @SuppressWarnings("unchecked")
public class BaseBuilder<T> implements GenericParameter<T>, LocationParameter<T>,
        TimeParameter<T>, Cloneable {

    private double lat = 0.0;
    private double lng = 0.0;
    private double height = 0.0;
    private ZonedDateTime dateTime = ZonedDateTime.now();

    @Override
    public T on(ZonedDateTime dateTime) {
        this.dateTime = Objects.requireNonNull(dateTime, "dateTime");
        return (T) this;
    }

    @Override
    public T on(LocalDateTime dateTime) {
        Objects.requireNonNull(dateTime, "dateTime");
        return on(ZonedDateTime.of(dateTime, this.dateTime.getZone()));
    }

    @Override
    public T on(LocalDate date) {
        Objects.requireNonNull(date, "date");
        return on(ZonedDateTime.of(date, LocalTime.MIDNIGHT, dateTime.getZone()));
    }

    @Override
    public T on(Instant instant) {
        Objects.requireNonNull(instant, "instant");
        return on(ZonedDateTime.ofInstant(instant, dateTime.getZone()));
    }

    @Override
    public T on(int year, int month, int date, int hour, int minute, int second) {
        return on(ZonedDateTime.of(year, month, date, hour, minute, second, 0, dateTime.getZone()));
    }

    @Override
    public T now() {
        return on(ZonedDateTime.now(dateTime.getZone()));
    }

    @Override
    public T plusDays(int days) {
        return on(dateTime.plusDays(days));
    }

    @Override
    public T midnight() {
        return on(dateTime.truncatedTo(ChronoUnit.DAYS));
    }

    @Override
    public T timezone(ZoneId tz) {
        Objects.requireNonNull(tz, "tz");
        on(dateTime.withZoneSameLocal(tz));
        return (T) this;
    }

    @Override
    public T latitude(double lat) {
        if (lat < -90.0 || lat > 90.0) {
            throw new IllegalArgumentException("Latitude out of range, -90.0 <= " + lat + " <= 90.0");
        }
        this.lat = lat;
        return (T) this;
    }

    @Override
    public T longitude(double lng) {
        if (lng < -180.0 || lng > 180.0) {
            throw new IllegalArgumentException("Longitude out of range, -180.0 <= " + lng + " <= 180.0");
        }
        this.lng = lng;
        return (T) this;
    }

    @Override
    public T height(double h) {
        this.height = max(h, 0.0);
        return (T) this;
    }

    @Override
    public T sameTimeAs(TimeParameter<?> t) {
        if (! (t instanceof BaseBuilder)) {
            throw new IllegalArgumentException("Cannot read the TimeParameter");
        }
        this.dateTime = ((BaseBuilder<?>) t).dateTime;
        return (T) this;
    }

    @Override
    public T sameLocationAs(LocationParameter<?> l) {
        if (! (l instanceof BaseBuilder)) {
            throw new IllegalArgumentException("Cannot read the LocationParameter");
        }
        BaseBuilder<?> origin = (BaseBuilder<?>) l;
        this.lat = origin.lat;
        this.lng = origin.lng;
        this.height = origin.height;
        return (T) this;
    }

    @Override
    public T copy() {
        try {
            return (T) clone();
        } catch (CloneNotSupportedException ex) {
            throw new RuntimeException(ex); // Should never be thrown anyway
        }
    }

    /**
     * Returns the longitude.
     *
     * @return Longitude, in degrees.
     */
    public double getLongitude() {
        return lng;
    }

    /**
     * Returns the latitude.
     *
     * @return Latitude, in degrees.
     */
    public double getLatitude() {
        return lat;
    }

    /**
     * Returns the longitude.
     *
     * @return Longitude, in radians.
     */
    public double getLongitudeRad() {
        return toRadians(lng);
    }

    /**
     * Returns the latitude.
     *
     * @return Latitude, in radians.
     */
    public double getLatitudeRad() {
        return toRadians(lat);
    }

    /**
     * Returns the height, in meters above sea level.
     *
     * @return Height, meters above sea level
     */
    public double getHeight() {
        return height;
    }

    /**
     * Returns the {@link JulianDate} to be used.
     *
     * @return {@link JulianDate}
     */
    public JulianDate getJulianDate() {
        return new JulianDate(dateTime);
    }

} public class JulianDate {

    private final ZonedDateTime dateTime;
    private final double mjd;

    /**
     * Creates a new {@link JulianDate}.
     *
     * @param time
     *            {@link ZonedDateTime} to use for the date.
     */
    public JulianDate(ZonedDateTime time) {
        dateTime = Objects.requireNonNull(time, "time");
        mjd = dateTime.toInstant().toEpochMilli() / 86400000.0 + 40587.0;
    }

    /**
     * Returns a {@link JulianDate} of the current date and the given hour.
     *
     * @param hour
     *            Hour of this date. This is a floating point value. Fractions are used
     *            for minutes and seconds.
     * @return {@link JulianDate} instance.
     */
    public JulianDate atHour(double hour) {
        return new JulianDate(dateTime.plusSeconds(round(hour * 60.0 * 60.0)));
    }

    /**
     * Returns a {@link JulianDate} of the given modified Julian date.
     *
     * @param mjd
     *            Modified Julian Date
     * @return {@link JulianDate} instance.
     */
    public JulianDate atModifiedJulianDate(double mjd) {
        Instant mjdi = Instant.ofEpochMilli(Math.round((mjd - 40587.0) * 86400000.0));
        return new JulianDate(ZonedDateTime.ofInstant(mjdi, dateTime.getZone()));
    }

    /**
     * Returns a {@link JulianDate} of the given Julian century.
     *
     * @param jc
     *            Julian Century
     * @return {@link JulianDate} instance.
     */
    public JulianDate atJulianCentury(double jc) {
        return atModifiedJulianDate(jc * 36525.0 + 51544.5);
    }

    /**
     * Returns this {@link JulianDate} as {@link ZonedDateTime} object.
     *
     * @return {@link ZonedDateTime} of this {@link JulianDate}.
     */
    public ZonedDateTime getDateTime() {
        return dateTime;
    }

    /**
     * Returns the Modified Julian Date.
     *
     * @return Modified Julian Date, UTC.
     */
    public double getModifiedJulianDate() {
        return mjd;
    }

    /**
     * Returns the Julian Centuries.
     *
     * @return Julian Centuries, based on J2000 epoch, UTC.
     */
    public double getJulianCentury() {
        return (mjd - 51544.5) / 36525.0;
    }

    /**
     * Returns the Greenwich Mean Sidereal Time of this Julian Date.
     *
     * @return GMST
     */
    public double getGreenwichMeanSiderealTime() {
        final double secs = 86400.0;

        double mjd0 = floor(mjd);
        double ut = (mjd - mjd0) * secs;
        double t0 = (mjd0 - 51544.5) / 36525.0;
        double t = (mjd - 51544.5) / 36525.0;

        double gmst = 24110.54841
                + 8640184.812866 * t0
                + 1.0027379093 * ut
                + (0.093104 - 6.2e-6 * t) * t * t;

        return (PI2 / secs) * (gmst % secs);
    }

    /**
     * Returns the earth's true anomaly of the current date.
     * <p>
     * A simple approximation is used here.
     *
     * @return True anomaly, in radians
     */
    public double getTrueAnomaly() {
        return PI2 * frac((dateTime.getDayOfYear() - 5.0) / 365.256363);
    }

    @Override
    public String toString() {
        return String.format("%dd %02dh %02dm %02ds",
                (long) mjd,
                (long) (mjd * 24 % 24),
                (long) (mjd * 24 * 60 % 60),
                (long) (mjd * 24 * 60 * 60 % 60));
    }

} public final class Sun {

    private static final double SUN_DISTANCE = 149598000.0;
    private static final double SUN_MEAN_RADIUS = 695700.0;

    private Sun() {
        // Utility class without constructor
    }

    /**
     * Calculates the equatorial position of the sun.
     *
     * @param date
     *            {@link JulianDate} to be used
     * @return {@link Vector} containing the sun position
     */
    public static Vector positionEquatorial(JulianDate date) {
        double T = date.getJulianCentury();
        double M = PI2 * frac(0.993133 + 99.997361 * T);
        double L = PI2 * frac(0.7859453 + M / PI2
            + (6893.0 * sin(M) + 72.0 * sin(2.0 * M) + 6191.2 * T) / 1296.0e3);

        double d = SUN_DISTANCE
            * (1 - 0.016718 * cos(date.getTrueAnomaly()));

        return Vector.ofPolar(L, 0.0, d);
    }

    /**
     * Calculates the geocentric position of the sun.
     *
     * @param date
     *            {@link JulianDate} to be used
     * @return {@link Vector} containing the sun position
     */
    public static Vector position(JulianDate date) {
        Matrix rotateMatrix = equatorialToEcliptical(date).transpose();
        return rotateMatrix.multiply(positionEquatorial(date));
    }

    /**
     * Calculates the horizontal position of the sun.
     *
     * @param date
     *            {@link JulianDate} to be used
     * @param lat
     *            Latitude, in radians
     * @param lng
     *            Longitute, in radians
     * @return {@link Vector} of horizontal sun position
     */
    public static Vector positionHorizontal(JulianDate date, double lat, double lng) {
        Vector mc = position(date);
        double h = date.getGreenwichMeanSiderealTime() + lng - mc.getPhi();
        return equatorialToHorizontal(h, mc.getTheta(), mc.getR(), lat);
    }

    /**
     * Returns the angular radius of the sun.
     *
     * @param distance
     *            Distance of the sun, in kilometers.
     * @return Angular radius of the sun, in radians.
     * @see <a href="https://en.wikipedia.org/wiki/Angular_diameter">Wikipedia: Angular
     *      Diameter</a>
     */
    public static double angularRadius(double distance) {
        return asin(SUN_MEAN_RADIUS / distance);
    }

} public class Vector {

    private final double x;
    private final double y;
    private final double z;
    private final Polar polar = new Polar();

    /**
     * Creates a new {@link Vector} of the given cartesian coordinates.
     *
     * @param x
     *            X coordinate
     * @param y
     *            Y coordinate
     * @param z
     *            Z coordinate
     */
    public Vector(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    /**
     * Creates a new {@link Vector} of the given cartesian coordinates.
     *
     * @param d
     *            Array of coordinates, must have 3 elements
     */
    public Vector(double[] d) {
        if (d.length != 3) {
            throw new IllegalArgumentException("invalid vector length");
        }
        this.x = d[0];
        this.y = d[1];
        this.z = d[2];
    }

    /**
     * Creates a new {@link Vector} of the given polar coordinates, with a radial distance
     * of 1.
     *
     * @param φ
     *            Azimuthal Angle
     * @param θ
     *            Polar Angle
     * @return Created {@link Vector}
     */
    public static Vector ofPolar(double φ, double θ) {
        return ofPolar(φ, θ, 1.0);
    }

    /**
     * Creates a new {@link Vector} of the given polar coordinates.
     *
     * @param φ
     *            Azimuthal Angle
     * @param θ
     *            Polar Angle
     * @param r
     *            Radial Distance
     * @return Created {@link Vector}
     */
    public static Vector ofPolar(double φ, double θ, double r) {
        double cosθ = cos(θ);
        Vector result = new Vector(
            r * cos(φ) * cosθ,
            r * sin(φ) * cosθ,
            r *          sin(θ)
        );
        result.polar.setPolar(φ, θ, r);
        return result;
    }

    /**
     * Returns the cartesian X coordinate.
     */
    public double getX() {
        return x;
    }

    /**
     * Returns the cartesian Y coordinate.
     */
    public double getY() {
        return y;
    }

    /**
     * Returns the cartesian Z coordinate.
     */
    public double getZ() {
        return z;
    }

    /**
     * Returns the azimuthal angle (φ) in radians.
     */
    public double getPhi() {
        return polar.getPhi();
    }

    /**
     * Returns the polar angle (θ) in radians.
     */
    public double getTheta() {
        return polar.getTheta();
    }

    /**
     * Returns the polar radial distance (r).
     */
    public double getR() {
        return polar.getR();
    }

    /**
     * Returns a {@link Vector} that is the sum of this {@link Vector} and the given
     * {@link Vector}.
     *
     * @param vec
     *            {@link Vector} to add
     * @return Resulting {@link Vector}
     */
    public Vector add(Vector vec) {
        return new Vector(
            x + vec.x,
            y + vec.y,
            z + vec.z
        );
    }

    /**
     * Returns a {@link Vector} that is the difference of this {@link Vector} and the
     * given {@link Vector}.
     *
     * @param vec
     *            {@link Vector} to subtract
     * @return Resulting {@link Vector}
     */
    public Vector subtract(Vector vec) {
        return new Vector(
            x - vec.x,
            y - vec.y,
            z - vec.z
        );
    }

    /**
     * Returns a {@link Vector} that is the scalar product of this {@link Vector} and the
     * given scalar.
     *
     * @param scalar
     *            Scalar to multiply
     * @return Resulting {@link Vector}
     */
    public Vector multiply(double scalar) {
        return new Vector(
            x * scalar,
            y * scalar,
            z * scalar
        );
    }

    /**
     * Returns the negation of this {@link Vector}.
     *
     * @return Resulting {@link Vector}
     */
    public Vector negate() {
        return new Vector(
            -x,
            -y,
            -z
        );
    }

    /**
     * Returns a {@link Vector} that is the cross product of this {@link Vector} and the
     * given {@link Vector}.
     *
     * @param right
     *            {@link Vector} to multiply
     * @return Resulting {@link Vector}
     */
    public Vector cross(Vector right) {
        return new Vector(
            y * right.z - z * right.y,
            z * right.x - x * right.z,
            x * right.y - y * right.x
        );
    }

    /**
     * Returns the dot product of this {@link Vector} and the given {@link Vector}.
     *
     * @param right
     *            {@link Vector} to multiply
     * @return Resulting dot product
     */
    public double dot(Vector right) {
        return x * right.x + y * right.y + z * right.z;
    }

    /**
     * Returns the norm of this {@link Vector}.
     *
     * @return Norm of this vector
     */
    public double norm() {
        return sqrt(dot(this));
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof Vector)) {
            return false;
        }

        Vector vec = (Vector) obj;
        return Double.compare(x, vec.x) == 0
            && Double.compare(y, vec.y) == 0
            && Double.compare(z, vec.z) == 0;
    }

    @Override
    public int hashCode() {
        return Double.valueOf(x).hashCode()
            ^  Double.valueOf(y).hashCode()
            ^  Double.valueOf(z).hashCode();
    }

    @Override
    public String toString() {
        return "(x=" + x + ", y=" + y + ", z=" + z + ")";
    }

    /**
     * Helper class for lazily computing the polar coordinates in an immutable Vector
     * object.
     */
    private class Polar {
        private @Nullable Double φ = null;
        private @Nullable Double θ = null;
        private @Nullable Double r = null;

        /**
         * Sets polar coordinates.
         *
         * @param φ
         *            Phi
         * @param θ
         *            Theta
         * @param r
         *            R
         */
        public synchronized void setPolar(double φ, double θ, double r) {
            this.φ = φ;
            this.θ = θ;
            this.r = r;
        }

        public synchronized double getPhi() {
            if (φ == null) {
                if (isZero(x) && isZero(y)) {
                    φ = 0.0;
                } else {
                    φ = atan2(y, x);
                }

                if (φ < 0.0) {
                    φ += PI2;
                }
            }
            return φ;
        }

        public synchronized double getTheta() {
            if (θ == null) {
                double ρSqr = x * x + y * y;

                if (isZero(z) && isZero(ρSqr)) {
                    θ = 0.0;
                } else {
                    θ = atan2(z, sqrt(ρSqr));
                }
            }
            return θ;
        }

        public synchronized double getR() {
            if (r == null) {
                r = sqrt(x * x + y * y + z * z);
            }
            return r;
        }
    }

}
import static java.lang.Math.toDegrees;
import static java.lang.Math.toRadians;
import static org.shredzone.commons.suncalc.util.ExtendedMath.equatorialToHorizontal;
import static org.shredzone.commons.suncalc.util.ExtendedMath.refraction;
import org.shredzone.commons.suncalc.param.Builder;
import org.shredzone.commons.suncalc.param.GenericParameter;
import org.shredzone.commons.suncalc.param.LocationParameter;
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.Sun;
import org.shredzone.commons.suncalc.util.Vector;
/*
 * Shredzone Commons - suncalc
 *
 * Copyright (C) 2017 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;



/**
 * Calculates the position of the sun.
 */
public class SunPosition {

    private final double azimuth;
    private final double altitude;
    private final double trueAltitude;
    private final double distance;

    private SunPosition(double azimuth, double altitude, double trueAltitude, double distance) {
        this.azimuth = (toDegrees(azimuth) + 180.0) % 360.0;
        this.altitude = toDegrees(altitude);
        this.trueAltitude = toDegrees(trueAltitude);
        this.distance = distance;
    }

    /**
     * Starts the computation of {@link SunPosition}.
     *
     * @return {@link Parameters} to set.
     */
    public static Parameters compute() {
        return new SunPositionBuilder();
    }

    /**
     * Collects all parameters for {@link SunPosition}.
     */
    public interface Parameters extends
            GenericParameter<Parameters>,
            LocationParameter<Parameters>,
            TimeParameter<Parameters>,
            Builder<SunPosition> {
    }

    /**
     * Builder for {@link SunPosition}. Performs the computations based on the parameters,
     * and creates a {@link SunPosition} object that holds the result.
     */
    private static class SunPositionBuilder extends BaseBuilder<Parameters> implements Parameters {