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.util;
015
016import static java.lang.Math.*;
017
018/**
019 * Contains constants and mathematical operations that are not available in {@link Math}.
020 */
021public final class ExtendedMath {
022
023    /**
024     * PI * 2
025     */
026    public static final double PI2 = PI * 2.0;
027
028    /**
029     * Arc-Seconds per Radian.
030     */
031    public static final double ARCS = toDegrees(3600.0);
032
033    /**
034     * Mean radius of the earth, in kilometers.
035     */
036    public static final double EARTH_MEAN_RADIUS = 6371.0;
037
038    private ExtendedMath() {
039        // utility class without constructor
040    }
041
042    /**
043     * Returns the decimal part of a value.
044     *
045     * @param a
046     *            Value
047     * @return Fraction of that value. It has the same sign as the input value.
048     */
049    public static double frac(double a) {
050        return a % 1.0;
051    }
052
053    /**
054     * Performs a safe check if the given double is actually zero (0.0).
055     * <p>
056     * Note that "almost zero" returns {@code false}, so this method should not be used
057     * for comparing calculation results to zero.
058     *
059     * @param d
060     *            double to check for zero.
061     * @return {@code true} if the value was zero, or negative zero.
062     */
063    public static boolean isZero(double d) {
064        // This should keep squid:S1244 silent...
065        return !Double.isNaN(d) && round(signum(d)) == 0L;
066    }
067
068    /**
069     * Converts equatorial coordinates to horizontal coordinates.
070     *
071     * @param tau
072     *            Hour angle (radians)
073     * @param dec
074     *            Declination (radians)
075     * @param dist
076     *            Distance of the object
077     * @param lat
078     *            Latitude of the observer (radians)
079     * @return {@link Vector} containing the horizontal coordinates
080     */
081    public static Vector equatorialToHorizontal(double tau, double dec, double dist, double lat) {
082        return Matrix.rotateY(PI / 2.0 - lat).multiply(Vector.ofPolar(tau, dec, dist));
083    }
084
085    /**
086     * Creates a rotational {@link Matrix} for converting equatorial to ecliptical
087     * coordinates.
088     *
089     * @param t
090     *            {@link JulianDate} to use
091     * @return {@link Matrix} for converting equatorial to ecliptical coordinates
092     */
093    public static Matrix equatorialToEcliptical(JulianDate t) {
094        double jc = t.getJulianCentury();
095        double eps = toRadians(23.43929111 - (46.8150 + (0.00059 - 0.001813 * jc) * jc) * jc / 3600.0);
096        return Matrix.rotateX(eps);
097    }
098
099    /**
100     * Returns the parallax for objects at the horizon.
101     *
102     * @param height
103     *            Observer's height, in meters above sea level. Must not be negative.
104     * @param distance
105     *            Distance of the sun, in kilometers.
106     * @return parallax, in radians
107     */
108    public static double parallax(double height, double distance) {
109        return asin(EARTH_MEAN_RADIUS / distance)
110             - acos(EARTH_MEAN_RADIUS / (EARTH_MEAN_RADIUS + (height / 1000.0)));
111    }
112
113    /**
114     * Calculates the atmospheric refraction of an object at the given apparent altitude.
115     * <p>
116     * The result is only valid for positive altitude angles. If negative, 0.0 is
117     * returned.
118     * <p>
119     * Assumes an atmospheric pressure of 1010 hPa and a temperature of 10 °C.
120     *
121     * @param ha
122     *            Apparent altitude, in radians.
123     * @return Refraction at this altitude
124     * @see <a href="https://en.wikipedia.org/wiki/Atmospheric_refraction">Wikipedia:
125     *      Atmospheric Refraction</a>
126     */
127    public static double apparentRefraction(double ha) {
128        if (ha < 0.0) {
129            return 0.0;
130        }
131
132        return PI / (tan(toRadians(ha + (7.31 / (ha + 4.4)))) * 10800.0);
133    }
134
135    /**
136     * Calculates the atmospheric refraction of an object at the given altitude.
137     * <p>
138     * The result is only valid for positive altitude angles. If negative, 0.0 is
139     * returned.
140     * <p>
141     * Assumes an atmospheric pressure of 1010 hPa and a temperature of 10 °C.
142     *
143     * @param h
144     *            True altitude, in radians.
145     * @return Refraction at this altitude
146     * @see <a href="https://en.wikipedia.org/wiki/Atmospheric_refraction">Wikipedia:
147     *      Atmospheric Refraction</a>
148     */
149    public static double refraction(double h) {
150        if (h < 0.0) {
151            return 0.0;
152        }
153
154        // refraction formula, converted to radians
155        return 0.000296706 / tan(h + 0.00312537 / (h + 0.0890118));
156    }
157
158}