ext.plantuml.com.ctreber.acearth.util.SunPositionCalculator Maven / Gradle / Ivy
Show all versions of plantuml-gplv2 Show documentation
// THIS FILE HAS BEEN GENERATED BY A PREPROCESSOR.
/* +=======================================================================
* |
* | PlantUML : a free UML diagram generator
* |
* +=======================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://plantuml.com/liberapay (only 1€ per month!)
* https://plantuml.com/paypal
*
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License V2.
*
* THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
* LICENSE ("AGREEMENT"). [GNU General Public License V2]
*
* ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES
* RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
*
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PlantUML can occasionally display sponsored or advertising messages. Those
* messages are usually generated on welcome or error images and never on
* functional diagrams.
* See https://plantuml.com/professional if you want to remove them
*
* Images (whatever their format : PNG, SVG, EPS...) generated by running PlantUML
* are owned by the author of their corresponding sources code (that is, their
* textual description in PlantUML language). Those images are not covered by
* this GPL v2 license.
*
* The generated images can then be used without any reference to the GPL v2 license.
* It is not even necessary to stipulate that they have been generated with PlantUML,
* although this will be appreciated by the PlantUML team.
*
* There is an exception : if the textual description in PlantUML language is also covered
* by any license, then the generated images are logically covered
* by the very same license.
*
* This is the IGY distribution (Install GraphViz by Yourself).
* You have to install GraphViz and to setup the GRAPHVIZ_DOT environment variable
* (see https://plantuml.com/graphviz-dot )
*
* Icons provided by OpenIconic : https://useiconic.com/open
* Archimate sprites provided by Archi : http://www.archimatetool.com
* Stdlib AWS provided by https://github.com/milo-minderbinder/AWS-PlantUML
* Stdlib Icons provided https://github.com/tupadr3/plantuml-icon-font-sprites
* ASCIIMathML (c) Peter Jipsen http://www.chapman.edu/~jipsen
* ASCIIMathML (c) David Lippman http://www.pierce.ctc.edu/dlippman
* CafeUndZopfli ported by Eugene Klyuchnikov https://github.com/eustas/CafeUndZopfli
* Brotli (c) by the Brotli Authors https://github.com/google/brotli
* Themes (c) by Brett Schwarz https://github.com/bschwarz/puml-themes
* Twemoji (c) by Twitter at https://twemoji.twitter.com/
*
*/
package ext.plantuml.com.ctreber.acearth.util;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
/**
* Calculates the position of the point on Earth which is directly
* below the sun or the moon.
*
*
© 2002 Christian Treber, [email protected]
* @author Christian Treber, [email protected]
*
*/
public class SunPositionCalculator
{
/*
* the epoch upon which these astronomical calculations are based is
* 1990 january 0.0, 631065600 seconds since the beginning of the
* "unix epoch" (00:00:00 GMT, Jan. 1, 1970)
*
* given a number of seconds since the start of the unix epoch,
* daysSinceEpoch() computes the number of days since the start of the
* astronomical epoch (1990 january 0.0)
*/
private static final long EPOCH_START = 631065600000l;
/*
* assuming the apparent orbit of the sun about the earth is circular,
* the rate at which the orbit progresses is given by RadsPerDay --
* TWOPI radians per orbit divided by 365.242191 days per year:
*/
private static final double RADS_PER_DAY = Toolkit.TWOPI / 365.242191;
/*
* details of sun's apparent orbit at epoch 1990.0 (after
* duffett-smith, table 6, section 46)
*
* Epsilon_g (ecliptic longitude at epoch 1990.0) 279.403303 degrees
* OmegaBar_g (ecliptic longitude of perigee) 282.768422 degrees
* Eccentricity (eccentricity of orbit) 0.016713
*/
private static final double EPSILON_G = Toolkit.degsToRads(279.403303);
private static final double OMEGA_BAR_G = Toolkit.degsToRads(282.768422);
private static final double ECCENTRICITY = 0.016713;
/*
* Lunar parameters, epoch January 0, 1990.0
*/
private static final double MOON_MEAN_LONGITUDE = Toolkit.degsToRads(318.351648);
private static final double MOON_MEAN_LONGITUDE_PERIGEE = Toolkit.degsToRads(36.340410);
private static final double MOON_MEAN_LONGITUDE_NODE = Toolkit.degsToRads(318.510107);
private static final double MOON_INCLINATION = Toolkit.degsToRads(5.145396);
private static final double SIDERAL_MONTH = 27.3217;
/**
*
Calculate the position of the mean sun: where the sun would
* be if the earth's orbit were circular instead of ellipictal.
*
*
Verified.
*
* @param pDays days since ephemeris epoch
*/
private static double getMeanSunLongitude(double pDays)
{
double N, M;
N = RADS_PER_DAY * pDays;
N = Toolkit.fmod(N, 0, Toolkit.TWOPI);
if(N < 0)
{
N += Toolkit.TWOPI;
}
M = N + EPSILON_G - OMEGA_BAR_G;
if(M < 0)
{
M += Toolkit.TWOPI;
}
return M;
}
/**
*
Compute ecliptic longitude of sun (in radians)
* (after duffett-smith, section 47)
*
*
Verified.
*
* @param pMillis Milliseconds since unix epoch
*/
private static double getSunEclipticLongitude(long pMillis)
{
final double lDays = daysSinceEpoch(pMillis);
final double M_sun = getMeanSunLongitude(lDays);
final double E = doKepler(M_sun);
final double v = 2 * Math.atan(Math.sqrt((1 + ECCENTRICITY) / (1 - ECCENTRICITY)) * Math.tan(E / 2));
return (v + OMEGA_BAR_G);
}
static double daysSinceEpoch(long pMillis)
{
return (double)(pMillis - EPOCH_START) / 24 / 3600 / 1000;
}
/**
* solve Kepler's equation via Newton's method
* (after duffett-smith, section 47)
*
*
Verified.
*/
private static double doKepler(double M)
{
double E;
double lDelta;
E = M;
while(true)
{
lDelta = E - ECCENTRICITY * Math.sin(E) - M;
if(Math.abs(lDelta) <= 1e-10)
{
break;
}
E -= lDelta / (1 - ECCENTRICITY * Math.cos(E));
}
return E;
}
/**
*
computing julian dates (assuming gregorian calendar, thus this is
* only valid for dates of 1582 oct 15 or later)
* (after duffett-smith, section 4)
*
*
Verified.
*
* @param pYear year (e.g. 19xx)
* @param pMonth month (jan=1, feb=2, ...)
* @param pDay day of month
*/
private static double getJulianDate(int pYear, int pMonth, int pDay)
{
if((pMonth == 1) || (pMonth == 2))
{
pYear -= 1;
pMonth += 12;
}
final int A = pYear / 100;
final int B = 2 - A + (A / 4);
final int C = (int)(365.25 * pYear);
final int D = (int)(30.6001 * (pMonth + 1));
return B + C + D + pDay + 1720994.5;
}
/**
*
compute greenwich mean sidereal time (getGST) corresponding to a given
* number of milliseconds since the unix epoch
* (after duffett-smith, section 12)
*
*
Verified.
*/
private static double getGST(long pMillis)
{
final Calendar lCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
lCal.setTime(new Date(pMillis));
final double lJulianDate = getJulianDate(lCal.get(Calendar.YEAR), lCal.get(Calendar.MONTH) + 1,
lCal.get(Calendar.DAY_OF_MONTH));
final double T = (lJulianDate - 2451545) / 36525;
double T0 = ((T + 2.5862e-5) * T + 2400.051336) * T + 6.697374558;
T0 = Toolkit.fmod(T0, 0, 24.0);
if(T0 < 0)
{
T0 += 24;
}
final double UT = lCal.get(Calendar.HOUR_OF_DAY) +
(lCal.get(Calendar.MINUTE) + lCal.get(Calendar.SECOND) / 60.0) / 60.0;
T0 += UT * 1.002737909;
T0 = Toolkit.fmod(T0, 0, 24.0);
if(T0 < 0)
{
T0 += 24;
}
return T0;
}
/**
*
Given a particular time (expressed in milliseconds since the unix
* epoch), compute position on the earth (lat, lon) such that sun is
* directly overhead.
*
*
Verified.
*
* @param pMillis seconds since unix epoch
*
*/
public static Coordinate getSunPositionOnEarth(long pMillis)
{
final Coordinate lSunPosEc = new Coordinate(0.0, getSunEclipticLongitude(pMillis));
final Coordinate lSunPosEq = lSunPosEc.eclipticToEquatorial();
final double lRA = Toolkit.limitRads(lSunPosEq.getRA() - (Toolkit.TWOPI / 24) * getGST(pMillis));
return new Coordinate(Toolkit.radsToDegs(lSunPosEq.getDE()), Toolkit.radsToDegs(lRA));
}
/**
*
Given a particular time (expressed in milliseconds since the unix
* epoch), compute position on the earth (lat, lon) such that the
* moon is directly overhead.
*
* Based on duffett-smith **2nd ed** section 61; combines some steps
* into single expressions to reduce the number of extra variables.
*
*
Verified.
*/
public static Coordinate getMoonPositionOnEarth(long pMillis)
{
final double lDays = daysSinceEpoch(pMillis);
double lSunLongEc = getSunEclipticLongitude(pMillis);
final double Ms = getMeanSunLongitude(lDays);
double L = Toolkit.limitRads(Toolkit.fmod(lDays / SIDERAL_MONTH, 0, 1.0) * Toolkit.TWOPI + MOON_MEAN_LONGITUDE);
double Mm = Toolkit.limitRads(L - Toolkit.degsToRads(0.1114041 * lDays) - MOON_MEAN_LONGITUDE_PERIGEE);
double N = Toolkit.limitRads(MOON_MEAN_LONGITUDE_NODE - Toolkit.degsToRads(0.0529539 * lDays));
final double Ev = Toolkit.degsToRads(1.2739) * Math.sin(2.0 * (L - lSunLongEc) - Mm);
final double Ae = Toolkit.degsToRads(0.1858) * Math.sin(Ms);
Mm += Ev - Ae - Toolkit.degsToRads(0.37) * Math.sin(Ms);
final double Ec = Toolkit.degsToRads(6.2886) * Math.sin(Mm);
L += Ev + Ec - Ae + Toolkit.degsToRads(0.214) * Math.sin(2.0 * Mm);
L += Toolkit.degsToRads(0.6583) * Math.sin(2.0 * (L - lSunLongEc));
N -= Toolkit.degsToRads(0.16) * Math.sin(Ms);
L -= N;
lSunLongEc = Toolkit.limitRads((Math.abs(Math.cos(L)) < 1e-12) ?
(N + Math.sin(L) * Math.cos(MOON_INCLINATION) * Math.PI / 2) :
(N + Math.atan2(Math.sin(L) * Math.cos(MOON_INCLINATION), Math.cos(L))));
final double lSunLatEc = Math.asin(Math.sin(L) * Math.sin(MOON_INCLINATION));
final Coordinate lSunPosEq = new Coordinate(lSunLatEc, lSunLongEc).eclipticToEquatorial();
final double lRA = Toolkit.limitRads(lSunPosEq.getRA() - (Toolkit.TWOPI / 24) * getGST(pMillis));
return new Coordinate(Toolkit.radsToDegs(lSunPosEq.getDE()), Toolkit.radsToDegs(lRA));
}
}