Source: util/SunPosition.js

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

define([
        '../geom/Angle',
        '../error/ArgumentError',
        './Logger',
        './WWMath'
    ],
    function (Angle,
              ArgumentError,
              Logger,
              WWMath) {
        'use strict';

        /**
         * Provides utilities for determining the Sun geographic and celestial location.
         * @exports SunPosition
         */
        var SunPosition = {

            /**
             * Computes the geographic location of the sun for a given date
             * @param {Date} date
             * @throws {ArgumentError} if the date is missing
             * @return {{latitude: Number, longitude: Number}} the geographic location
             */
            getAsGeographicLocation: function (date) {
                if (date instanceof Date === false) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "SunPosition", "getAsGeographicLocation",
                            "missingDate"));
                }

                var celestialLocation = this.getAsCelestialLocation(date);
                return this.celestialToGeographic(celestialLocation, date);
            },

            /**
             * Computes the celestial location of the sun for a given julianDate
             * @param {Date} date
             * @throws {ArgumentError} if the date is missing
             * @return {{declination: Number, rightAscension: Number}} the celestial location
             */
            getAsCelestialLocation: function (date) {
                if (date instanceof Date === false) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "SunPosition", "getAsCelestialLocation",
                            "missingDate"));
                }

                var julianDate = this.computeJulianDate(date);

                //number of days (positive or negative) since Greenwich noon, Terrestrial Time, on 1 January 2000 (J2000.0)
                var numDays = julianDate - 2451545;

                var meanLongitude = WWMath.normalizeAngle360(280.460 + 0.9856474 * numDays);

                var meanAnomaly = WWMath.normalizeAngle360(357.528 + 0.9856003 * numDays) * Angle.DEGREES_TO_RADIANS;

                var eclipticLongitude = meanLongitude + 1.915 * Math.sin(meanAnomaly) + 0.02 * Math.sin(2 * meanAnomaly);
                var eclipticLongitudeRad = eclipticLongitude * Angle.DEGREES_TO_RADIANS;

                var obliquityOfTheEcliptic = (23.439 - 0.0000004 * numDays) * Angle.DEGREES_TO_RADIANS;

                var declination = Math.asin(Math.sin(obliquityOfTheEcliptic) * Math.sin(eclipticLongitudeRad)) *
                    Angle.RADIANS_TO_DEGREES;

                var rightAscension = Math.atan(Math.cos(obliquityOfTheEcliptic) * Math.tan(eclipticLongitudeRad)) *
                    Angle.RADIANS_TO_DEGREES;

                //compensate for atan result
                if (eclipticLongitude >= 90 && eclipticLongitude < 270) {
                    rightAscension += 180;
                }
                rightAscension = WWMath.normalizeAngle360(rightAscension);

                return {
                    declination: declination,
                    rightAscension: rightAscension
                };
            },

            /**
             * Converts from celestial coordinates (declination and right ascension) to geographic coordinates
             * (latitude, longitude) for a given julian date
             * @param {{declination: Number, rightAscension: Number}} celestialLocation
             * @param {Date} date
             * @throws {ArgumentError} if celestialLocation or julianDate are missing
             * @return {{latitude: Number, longitude: Number}} the geographic location
             */
            celestialToGeographic: function (celestialLocation, date) {
                if (!celestialLocation) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "SunPosition", "celestialToGeographic",
                            "missingCelestialLocation"));
                }
                if (date instanceof Date === false) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "SunPosition", "celestialToGeographic", "missingDate"));
                }

                var julianDate = this.computeJulianDate(date);

                //number of days (positive or negative) since Greenwich noon, Terrestrial Time, on 1 January 2000 (J2000.0)
                var numDays = julianDate - 2451545;

                //Greenwich Mean Sidereal Time
                var GMST = WWMath.normalizeAngle360(280.46061837 + 360.98564736629 * numDays);

                //Greenwich Hour Angle
                var GHA = WWMath.normalizeAngle360(GMST - celestialLocation.rightAscension);

                var longitude = Angle.normalizedDegreesLongitude(-GHA);

                return {
                    latitude: celestialLocation.declination,
                    longitude: longitude
                };
            },

            /**
             * Computes the julian date from a javascript date object
             * @param {Date} date
             * @throws {ArgumentError} if the date is missing
             * @return {Number} the julian date
             */
            computeJulianDate: function (date) {
                if (date instanceof Date === false) {
                    throw new ArgumentError(
                        Logger.logMessage(Logger.LEVEL_SEVERE, "SunPosition", "computeJulianDate", "missingDate"));
                }

                var year = date.getUTCFullYear();
                var month = date.getUTCMonth() + 1;
                var day = date.getUTCDate();
                var hour = date.getUTCHours();
                var minute = date.getUTCMinutes();
                var second = date.getUTCSeconds();

                var dayFraction = (hour + minute / 60 + second / 3600) / 24;

                if (month <= 2) {
                    year -= 1;
                    month += 12;
                }

                var A = Math.floor(year / 100);
                var B = 2 - A + Math.floor(A / 4);
                var JD0h = Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5;

                return JD0h + dayFraction;
            }

        };

        return SunPosition;

    });