001    /*
002     * This file is part of McIDAS-V
003     *
004     * Copyright 2007-2013
005     * Space Science and Engineering Center (SSEC)
006     * University of Wisconsin - Madison
007     * 1225 W. Dayton Street, Madison, WI 53706, USA
008     * https://www.ssec.wisc.edu/mcidas
009     * 
010     * All Rights Reserved
011     * 
012     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013     * some McIDAS-V source code is based on IDV and VisAD source code.  
014     * 
015     * McIDAS-V is free software; you can redistribute it and/or modify
016     * it under the terms of the GNU Lesser Public License as published by
017     * the Free Software Foundation; either version 3 of the License, or
018     * (at your option) any later version.
019     * 
020     * McIDAS-V is distributed in the hope that it will be useful,
021     * but WITHOUT ANY WARRANTY; without even the implied warranty of
022     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023     * GNU Lesser Public License for more details.
024     * 
025     * You should have received a copy of the GNU Lesser Public License
026     * along with this program.  If not, see http://www.gnu.org/licenses.
027     */
028    package edu.wisc.ssec.mcidasv.data.adde.sgp4;
029    
030    /**
031     * Earth computation functions
032     */
033    public class GeoFunctions
034    {
035    
036        // same as below except the function takes Julian Date
037        /**
038         * Compute Geodetic Latatude/Longitude/Altitude from Mean of Date position vector and Date 
039         *
040         * @param modPos Mean of date position vector
041         * @param mjd modified julian date  (is this UTC or TT?) guessing UTC
042         * @return vector of geodetic [latitude,longitude,altitude]
043         */
044        public static double[] GeodeticLLA(double[] modPos, double mjd)
045        {
046            // calculate days since Y2k
047            // MJD = JD - 2400000.5
048            // passed in MJD - 51544.5
049            //double daysSinceY2k = (julDate - 2400000.5)-51544.5;
050            double daysSinceY2k = mjd - 51544.5;
051    
052            return calculateGeodeticLLA(modPos, daysSinceY2k);
053        } // GeodeticLLA
054    
055        // returned as Lat, Long, Alt
056        // LLA = corrected for time (geographical coordinates)
057        // for handling geodetic coordinates
058        // r = TEME positions, d = days since Y2K
059        private static double[] calculateGeodeticLLA(double[] r, double d)
060        {
061            double R_equ= AstroConst.R_Earth; // Equator radius [m]
062            double f    = AstroConst.f_Earth; // Flattening
063    
064            double eps_mach = 2.22E-16; // machine precision (double?)
065    
066            final double  eps     = 1.0e3*eps_mach;   // Convergence criterion
067            final double  epsRequ = eps*R_equ;
068            final double  e2      = f*(2.0-f);        // Square of eccentricity
069    
070            final double  X = r[0];                   // Cartesian coordinates
071            final double  Y = r[1];
072            final double  Z = r[2];
073            final double  rho2 = X*X + Y*Y;           // Square of distance from z-axis
074    
075            // original class vars
076            // double lon;
077            // double lat;
078            //double h;
079            double[] LLA = new double[3];
080    
081    
082            // Check validity of input data
083            if (MathUtils.norm(r)==0.0)
084            {
085                System.out.println(" invalid input in Geodetic constructor");
086                LLA[1]=0.0;
087                LLA[0]=0.0;
088                LLA[2]=-AstroConst.R_Earth;
089                return LLA;
090            }
091    
092            // Iteration
093            double  dZ, dZ_new, SinPhi;
094            double  ZdZ, Nh, N;
095    
096            dZ = e2*Z;
097            for(;;)
098            {
099                ZdZ    =  Z + dZ;
100                Nh     =  Math.sqrt( rho2 + ZdZ*ZdZ );
101                SinPhi =  ZdZ / Nh;                    // Sine of geodetic latitude
102                N      =  R_equ / Math.sqrt(1.0-e2*SinPhi*SinPhi);
103                dZ_new =  N*e2*SinPhi;
104                if ( Math.abs(dZ-dZ_new) < epsRequ ) break;
105                dZ = dZ_new;
106            }
107    
108            // Longitude, latitude, altitude
109            //double[] LLA = new double[3];
110            LLA[1] = Math.atan2( Y, X );  // longitude,  lon
111            LLA[0] = Math.atan2( ZdZ, Math.sqrt(rho2) ); // latitude, lat
112            LLA[2] = Nh - N; // altitute, h
113    
114            //System.out.println("LLA[1]: "+ LLA[1]);
115            //LLA[1] = LLA[1] -(280.4606 +360.9856473*d)*Math.PI/180.0; // shift based on time
116            // add fidelity to the line above
117            LLA[1] = LLA[1] - earthRotationDeg(d)*Math.PI/180.0; // shift based on time
118            double div = Math.floor(LLA[1]/(2*Math.PI));
119            LLA[1] = LLA[1] - div*2*Math.PI;
120            if(LLA[1] > Math.PI)
121            {
122                LLA[1] = LLA[1]- 2.0*Math.PI;
123            }
124    
125            //System.out.println("LLA[1]a: "+ LLA[1]);
126    
127            return LLA; //h
128    
129        } // calculateGeodeticLLA
130    
131        // SEG 10 June 2009 - help standardize earth Rotations
132        private static double earthRotationDeg(double d) // days since y2K
133        {
134            // LLA[1] = LLA[1] -(280.4606 +360.9856473*d)*Math.PI/180.0; // shift based on time
135    
136            // calculate T
137            double T = (d)/36525.0;
138    
139            // do calculation
140            return ( (280.46061837 + 360.98564736629*(d)) + 0.000387933*T*T - T*T*T/38710000.0) % 360.0;
141    
142        } // earthRotationRad
143    
144         /**
145         * calculate the pointing information Azumuth, Elevation, and Range (AER) to 
146         * a satellite from a location on Earth (given Lat, Long, Alt)
147         * if elevation >=0 then sat is above local horizon
148          * @param currentJulianDate Julian Date for AER calculation (corresponds to ECI position)
149          * @param lla_deg_m lat long and alt of station in deg/deg/meters (Geodetic)
150          * @param eci_pos ECI position of object in meters (sat)
151         * @return Azumuth [deg], Elevation [deg], and Range vector [m]
152         */
153        public static double[] calculate_AER(double currentJulianDate,double[] lla_deg_m, double[] eci_pos)
154        {        double[] aer = new double[3];
155    
156            // 0th step get local mean Sidereal time
157            // first get mean sidereal time for this station - since we use it twice
158            double thetaDeg = Sidereal.Mean_Sidereal_Deg(currentJulianDate-AstroConst.JDminusMJD, lla_deg_m[1]);
159            // first calculate ECI position of Station
160            double[] eciGS = calculateECIposition(lla_deg_m,thetaDeg);
161    
162            // find the vector between pos and GS
163            double[] rECI = MathUtils.sub(eci_pos, eciGS);
164    
165            // calculate range
166            aer[2] = MathUtils.norm(rECI);
167    
168            // now transform ECI to topocentric-horizon system (SEZ)  (use Geodetic Lat, not geocentric)
169            double[] rSEZ = eci2sez(rECI,thetaDeg,lla_deg_m[0]); // ECI vec, sidereal in Deg, latitude in deg
170    
171            // compute azimuth [radians] -> Deg
172            //aer[0] = Math.atan(-rSEZ[1]/rSEZ[0]) * 180.0/Math.PI;
173            aer[0] = Math.atan2(-rSEZ[0], rSEZ[1]) * 180.0/Math.PI;
174    
175            //System.out.println("aer[0]_0=" + aer[0] + ", rSEZ[-0,1]=" + (-rSEZ[0]) + ", " +rSEZ[1]);
176    
177            // do conversions so N=0, S=180, NW=270
178            if(aer[0] <= 0)
179            {
180                aer[0] = Math.abs(aer[0]) + 90;
181            }
182            else
183            {
184                if(aer[0]<= 90)  //(between 0 and 90)
185                {
186                    aer[0] = -1.0*aer[0] + 90.0;
187                }
188                else // between 90 and 180
189                {
190                    aer[0] = -1.0*aer[0] + 450.0;
191                }
192            }
193    
194            // compute elevation [radians]
195            aer[1] = Math.asin(rSEZ[2] / aer[2]) * 180.0/Math.PI;
196    
197            //System.out.println("SEZ: " + rSEZ[0] + ", " + rSEZ[1] + ", " + rSEZ[2]);
198    
199            return aer;
200        } // calculate_AER
201    
202        /**
203         * Calculate ECI position from local mean sidereal time and geodetic lat long alt
204         * @param lla_deg_m lat long and alt of station in deg/deg/meters (Geodetic)
205         * @param theta local mean sidereal time (Degrees)
206         * @return ECI position (meters)
207         */
208        public static double[] calculateECIposition(double[] lla_deg_m, double theta)
209        {
210            // calculate the ECI j2k position vector of the ground station at the current time
211            double [] eciVec = new double[3];
212    
213    //        // calculate geocentric latitude - using non spherical earth (in radians)
214    //        // http://celestrak.com/columns/v02n03/
215    //        double  geocentricLat = Math.atan( Math.pow(1.0-AstroConst.f_Earth, 2.0) * Math.tan( lla_deg_m[0]*Math.PI/180.0 )  ); // (1-f)^2 tan(?).
216    //        
217    //        eciVec[2] = AstroConst.R_Earth * Math.sin( geocentricLat ); //lla_deg_m[0]*Math.PI/180.0 );
218    //        double r = AstroConst.R_Earth * Math.cos( geocentricLat ); //lla_deg_m[0]*Math.PI/180.0 );
219    //        eciVec[0] = r * Math.cos(theta*Math.PI/180.0);
220    //        eciVec[1] = r * Math.sin(theta*Math.PI/180.0);
221    
222            // alternate way to calcuate ECI position - using earth flattening
223            // http://celestrak.com/columns/v02n03/
224            double C = 1.0 / Math.sqrt( 1.0+AstroConst.f_Earth*(AstroConst.f_Earth-2.0)*Math.pow(Math.sin(lla_deg_m[0]*Math.PI/180.0 ),2.0) );
225            double S = Math.pow(1.0-AstroConst.f_Earth, 2.0) * C;
226    
227            eciVec[0] = AstroConst.R_Earth * C * Math.cos(lla_deg_m[0]*Math.PI/180.0)*Math.cos(theta*Math.PI/180.0);
228            eciVec[1] = AstroConst.R_Earth * C * Math.cos(lla_deg_m[0]*Math.PI/180.0)*Math.sin(theta*Math.PI/180.0);
229            eciVec[2] = AstroConst.R_Earth * S * Math.sin(lla_deg_m[0]*Math.PI/180.0);
230    
231            return eciVec;
232    
233        } //calculateECIposition
234    
235        /**
236         * transform ECI to topocentric-horizon system (SEZ) (south-East-Zenith)
237         * @param rECI position in ECI coordinates (meters)
238         * @param thetaDeg local sidereal time (degrees)
239         * @param latDeg observer's latitude (degrees)
240         * @return topocentric-horizon system (SEZ) (south-East-Zenith)
241         */
242        public static double[] eci2sez(double[] rECI,double thetaDeg,double latDeg)
243        {
244            double[] rSEZ = new double[3]; // new postion in SEZ coorinates
245    
246            //? (the local sidereal time) -> (thetaDeg*Math.PI)
247            //? (the observer's latitude) - > (latDeg*Math.PI)
248            rSEZ[0] = Math.sin(latDeg*Math.PI/180.0) * Math.cos(thetaDeg*Math.PI/180.0) * rECI[0] + Math.sin(latDeg*Math.PI/180.0) * Math.sin(thetaDeg*Math.PI/180.0) * rECI[1] - Math.cos(latDeg*Math.PI/180.0) * rECI[2];
249            rSEZ[1] = -Math.sin(thetaDeg*Math.PI/180.0) * rECI[0] + Math.cos(thetaDeg*Math.PI/180.0) * rECI[1];        rSEZ[2] = Math.cos(latDeg*Math.PI/180.0) * Math.cos(thetaDeg*Math.PI/180.0) * rECI[0] + 
250    Math.cos(latDeg*Math.PI/180.0) * Math.sin(thetaDeg*Math.PI/180.0) * rECI[1] + Math.sin(latDeg*Math.PI/180.0) * rECI[2];
251    
252            return rSEZ;
253        }
254    }