Repository: Bill-Gray/sat_code Branch: master Commit: ff7b98957dfa Files: 58 Total size: 448.3 KB Directory structure: gitextract_zy817hdm/ ├── .github/ │ └── workflows/ │ └── github_actions_build.yml ├── .gitignore ├── LICENSE ├── README.md ├── basics.cpp ├── common.cpp ├── deep.cpp ├── dropouts.c ├── dynamic.cpp ├── elem2tle.cpp ├── fake_ast.cpp ├── fix_tles.cpp ├── get_el.cpp ├── get_high.cpp ├── get_vect.cpp ├── line2.cpp ├── makefile ├── mergetle.cpp ├── msvc.mak ├── msvc_dll.mak ├── norad.h ├── norad_in.h ├── nu2vect.c ├── nu_readme.txt ├── obs_tes2.cpp ├── obs_test.cpp ├── obs_test.txt ├── observe.cpp ├── observe.h ├── out_comp.cpp ├── sat_code.def ├── sat_eph.c ├── sat_id.cpp ├── sat_id.txt ├── sat_id2.cpp ├── sat_id3.cpp ├── sat_util.c ├── sat_util.h ├── sdp4.cpp ├── sdp8.cpp ├── sgp.cpp ├── sgp4.cpp ├── sgp8.cpp ├── sm_sat.def ├── ssc_eph.c ├── summarize.c ├── test.tle ├── test2.cpp ├── test2.txt ├── test3.cpp ├── test_des.cpp ├── test_out.cpp ├── test_sat.cpp ├── tle2mpc.cpp ├── tle_date.c ├── tle_out.cpp ├── watcom.mak └── zlibstub.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/github_actions_build.yml ================================================ # Shameless copy of @AlastairUK's fine work for the jpl_eph repo. # Aside from the comment, this is almost a byte-for-byte copy. name: Build on: [push, pull_request] jobs: buildUbuntu: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: make run: | git clone https://github.com/Bill-Gray/lunar cd lunar make ERRORS=Y liblunar.a make install cd .. make ERRORS=Y buildOSX: runs-on: macOS-latest steps: - uses: actions/checkout@master - name: make run: | git clone https://github.com/Bill-Gray/lunar cd lunar make CC=clang ERRORS=Y liblunar.a make install cd .. make CC=clang ERRORS=Y buildWindows: runs-on: windows-latest steps: - uses: actions/checkout@master - name: make run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 git clone https://github.com/Bill-Gray/lunar mkdir myincl cd lunar nmake -f lunar.mak lunar64.lib nmake -f lunar.mak install cd .. set CL=/I"./myincl" copy lunar\*.lib . nmake -f msvc.mak shell: cmd ================================================ FILE: .gitignore ================================================ # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app *.cgi get_high mergetle obs_test obs_tes2 out_comp sat_id sat_id2 test2 test_out test_sat tle_date ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020, Project Pluto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # sat_code C/C++ code for the SGP4/SDP4 satellite motion model, and for manipulating TLEs (Two-Line Elements). Full details at [http://www.projectpluto.com/sat_code.htm](http://www.projectpluto.com/sat_code.htm). The only dependency is on the [`lunar`](https://github.com/Bill-Gray/lunar) (basic astronomical ephemeris/time functions) library. Make and `make install` that library before attempting to build this code. On Linux, run `make` to build the library and various test executables. (You can also do this with MinGW under Windows.) In Linux, you can then run `make install` to put libraries in `/usr/local/lib` and some include files in `/usr/local/include`. (You will probably have to make that `sudo make install`.) For BSD, and probably OS/X, run `gmake CLANG=Y` (GNU make, with the clang compiler), then `sudo gmake install`. On Windows, run `nmake -f msvc.mak` with MSVC++. Optionally, add `-BITS_32=Y` for 32-bit code. ================================================ FILE: basics.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include "norad.h" #include "norad_in.h" /*------------------------------------------------------------------*/ /* FMOD2P */ double FMod2p( const double x) { double rval = fmod( x, twopi); if( rval < 0.) rval += twopi; return( rval); } /* fmod2p */ #define EPHEM_TYPE_DEFAULT '0' #define EPHEM_TYPE_SGP '1' #define EPHEM_TYPE_SGP4 '2' #define EPHEM_TYPE_SDP4 '3' #define EPHEM_TYPE_SGP8 '4' #define EPHEM_TYPE_SDP8 '5' #define EPHEM_TYPE_HIGH 'H' /*------------------------------------------------------------------*/ void sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg); /* common.c */ /* Selects the type of ephemeris to be used (SGP*-SDP*) */ int DLL_FUNC select_ephemeris( const tle_t *tle) { int rval; if( tle->ephemeris_type == EPHEM_TYPE_HIGH) rval = 1; /* force high-orbit state vector model */ else if( tle->xno <= 0. || tle->eo > 1. || tle->eo < 0.) rval = -1; /* error in input data */ else if( tle->ephemeris_type == EPHEM_TYPE_SGP4 || tle->ephemeris_type == EPHEM_TYPE_SGP8) rval = 0; /* specifically marked non-deep */ else if( tle->ephemeris_type == EPHEM_TYPE_SDP4 || tle->ephemeris_type == EPHEM_TYPE_SDP8) rval = 1; /* specifically marked deep */ else { deep_arg_t deep_arg; sxpall_common_init( tle, &deep_arg); /* Select a deep-space/near-earth ephemeris */ /* If the orbital period is greater than 225 minutes... */ if (twopi / deep_arg.xnodp >= 225.) rval = 1; /* yes, it should be a deep-space (SDPx) ephemeris */ else rval = 0; /* no, you can go with an SGPx ephemeris */ } return( rval); } /* End of select_ephemeris() */ /*------------------------------------------------------------------*/ long DLL_FUNC sxpx_library_version( void) { return( 0x100); } ================================================ FILE: common.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include #include "norad.h" #include "norad_in.h" /* params[1] and [6]-[9] were used in earlier implementations, but are now unused */ #define c2 params[0] #define c1 params[2] #define c4 params[3] #define xnodcf params[4] #define t2cof params[5] void sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg) { const double a1 = pow(xke / tle->xno, two_thirds); /* in Earth radii */ double del1, ao, delo, tval; /* Recover original mean motion (xnodp) and */ /* semimajor axis (aodp) from input elements. */ deep_arg->cosio = cos( tle->xincl); deep_arg->cosio2 = deep_arg->cosio * deep_arg->cosio; deep_arg->eosq = tle->eo*tle->eo; deep_arg->betao2 = 1-deep_arg->eosq; deep_arg->betao = sqrt(deep_arg->betao2); tval = 1.5 * ck2 * (3. * deep_arg->cosio2 - 1.) / (deep_arg->betao * deep_arg->betao2); del1 = tval / (a1 * a1); ao = a1 * (1. - del1 * (1. / 3. + del1 * ( 1. + 134./81. * del1))); delo = tval / (ao * ao); deep_arg->xnodp = tle->xno / (1+delo); /* in radians/minute */ deep_arg->aodp = ao / (1-delo); } void sxpx_common_init( double *params, const tle_t *tle, init_t *init, deep_arg_t *deep_arg) { double eeta, etasq, perige, pinv, pinvsq, psisq, qoms24, temp1, temp2, temp3, cosio4, tsi_squared, x3thm1, xhdot1; sxpall_common_init( tle, deep_arg); x3thm1 = 3. * deep_arg->cosio2 - 1.; /* For perigee below 156 km, the values */ /* of s and qoms2t are altered. */ init->s4 = s_const; qoms24 = qoms2t; perige = (deep_arg->aodp * (1-tle->eo) - ae) * earth_radius_in_km; if( perige < 156.) { double temp_val, temp_val_squared; if(perige <= 98.) init->s4 = 20; else init->s4 = perige-78.; temp_val = (120. - init->s4) * ae / earth_radius_in_km; temp_val_squared = temp_val * temp_val; qoms24 = temp_val_squared * temp_val_squared; init->s4 = init->s4 / earth_radius_in_km + ae; } /* End of if(perige <= 156) */ pinv = 1. / (deep_arg->aodp * deep_arg->betao2); pinvsq = pinv * pinv; init->tsi = 1. / (deep_arg->aodp - init->s4); init->eta = deep_arg->aodp*tle->eo*init->tsi; etasq = init->eta*init->eta; eeta = tle->eo*init->eta; psisq = fabs(1-etasq); tsi_squared = init->tsi * init->tsi; init->coef = qoms24 * tsi_squared * tsi_squared; init->coef1 = init->coef / pow(psisq,3.5); c2 = init->coef1 * deep_arg->xnodp * (deep_arg->aodp*(1+1.5*etasq+eeta* (4+etasq))+0.75*ck2*init->tsi/psisq*x3thm1*(8+3*etasq*(8+etasq))); c1 = tle->bstar*c2; deep_arg->sinio = sin(tle->xincl); c4 = 2*deep_arg->xnodp*init->coef1*deep_arg->aodp*deep_arg->betao2* (init->eta*(2+0.5*etasq)+tle->eo*(0.5+2*etasq)-2*ck2*init->tsi/ (deep_arg->aodp*psisq)*(-3*x3thm1*(1-2*eeta+etasq* (1.5-0.5*eeta))+0.75*(1. - deep_arg->cosio2) *(2*etasq-eeta*(1+etasq))* cos(2*tle->omegao))); cosio4 = deep_arg->cosio2 * deep_arg->cosio2; temp1 = 3*ck2*pinvsq*deep_arg->xnodp; temp2 = temp1 * ck2 * pinvsq; temp3 = 1.25 * ck4 * pinvsq * pinvsq * deep_arg->xnodp; deep_arg->xmdot = deep_arg->xnodp + temp1 * deep_arg->betao* x3thm1 / 2. + temp2 * deep_arg->betao* (13-78*deep_arg->cosio2+137*cosio4) / 16.; deep_arg->omgdot = -temp1 * (1. - 5 * deep_arg->cosio2) / 2. + temp2 * (7-114*deep_arg->cosio2+395*cosio4) / 16. + temp3 * (3-36*deep_arg->cosio2+49*cosio4); xhdot1 = -temp1*deep_arg->cosio; deep_arg->xnodot = xhdot1+(temp2*(4-19*deep_arg->cosio2) / 2. + 2*temp3*(3-7*deep_arg->cosio2))*deep_arg->cosio; xnodcf = 3.5*deep_arg->betao2*xhdot1*c1; t2cof = 1.5*c1; } inline double centralize_angle( const double ival) { double rval = fmod( ival, twopi); if( rval > pi) rval -= twopi; else if( rval < - pi) rval += twopi; return( rval); } #define MAX_KEPLER_ITER 10 int sxpx_posn_vel( const double xnode, const double a, const double ecc, const double cosio, const double sinio, const double xincl, const double omega, const double xl, double *pos, double *vel) { /* Long period periodics */ const double axn = ecc*cos(omega); double temp = 1/(a*(1.-ecc*ecc)); const double xlcof = .125 * a3ovk2 * sinio * (3+5*cosio)/ (1. + cosio); const double aycof = 0.25 * a3ovk2 * sinio; const double xll = temp*xlcof*axn; const double aynl = temp*aycof; const double xlt = xl+xll; const double ayn = ecc*sin(omega)+aynl; const double elsq = axn*axn+ayn*ayn; const double capu = centralize_angle( xlt - xnode); const double chicken_factor_on_eccentricity = 1.e-6; double epw = capu; double temp1, temp2; double ecosE, esinE, pl, r; double betal; double u, sinu, cosu, sin2u, cos2u; double rk, uk, xnodek, xinck; double sinuk, cosuk, sinik, cosik, sinnok, cosnok, xmx, xmy; double sinEPW, cosEPW; double ux, uy, uz; int i, rval = 0; /* Dundee changes: items dependent on cosio get recomputed: */ const double cosio_squared = cosio * cosio; const double x3thm1 = 3.0 * cosio_squared - 1.0; const double sinio2 = 1.0 - cosio_squared; const double x7thm1 = 7.0 * cosio_squared - 1.0; /* Added 29 Mar 2003, modified 26 Sep 2006: extremely */ /* decayed satellites can end up "orbiting" within the */ /* earth. Eventually, the semimajor axis becomes zero, */ /* then negative. In that case, or if the orbit is near */ /* to parabolic, we zero the posn/vel and quit. If the */ /* object has a perigee or apogee indicating a crash, we */ /* just flag it. Revised 28 Oct 2006. */ if( a < 0.) rval = SXPX_ERR_NEGATIVE_MAJOR_AXIS; if( elsq > 1. - chicken_factor_on_eccentricity) rval = SXPX_ERR_NEARLY_PARABOLIC; for( i = 0; i < 3; i++) { pos[i] = 0.; if( vel) vel[i] = 0.; } if( rval) return( rval); if( a * (1. - ecc) < 1. && a * (1. + ecc) < 1.) /* entirely within earth */ rval = SXPX_WARN_ORBIT_WITHIN_EARTH; /* remember, e can be negative */ if( a * (1. - ecc) < 1. || a * (1. + ecc) < 1.) /* perigee within earth */ rval = SXPX_WARN_PERIGEE_WITHIN_EARTH; /* Solve Kepler's' Equation */ for( i = 0; i < MAX_KEPLER_ITER; i++) { const double newton_raphson_epsilon = 1e-12; double f, fdot, delta_epw; int do_second_order_newton_raphson = 1; sinEPW = sin( epw); cosEPW = cos( epw); ecosE = axn * cosEPW + ayn * sinEPW; esinE = axn * sinEPW - ayn * cosEPW; f = capu - epw + esinE; if (fabs(f) < newton_raphson_epsilon) break; fdot = 1. - ecosE; delta_epw = f / fdot; if( !i) { const double max_newton_raphson = 1.25 * fabs( ecc); do_second_order_newton_raphson = 0; if( delta_epw > max_newton_raphson) delta_epw = max_newton_raphson; else if( delta_epw < -max_newton_raphson) delta_epw = -max_newton_raphson; else do_second_order_newton_raphson = 1; } if( do_second_order_newton_raphson) delta_epw = f / (fdot + 0.5*esinE*delta_epw); /* f/(fdot - 0.5*fdotdot * f / fdot) */ epw += delta_epw; } if( i == MAX_KEPLER_ITER) return( SXPX_ERR_CONVERGENCE_FAIL); /* Short period preliminary quantities */ temp = 1-elsq; pl = a*temp; r = a*(1-ecosE); temp2 = a / r; betal = sqrt(temp); temp = esinE/(1+betal); cosu = temp2 * (cosEPW - axn + ayn * temp); sinu = temp2 * (sinEPW - ayn - axn * temp); u = atan2( sinu, cosu); sin2u = 2*sinu*cosu; cos2u = 2*cosu*cosu-1; temp1 = ck2 / pl; temp2 = temp1 / pl; /* Update for short periodics */ rk = r*(1-1.5*temp2*betal*x3thm1)+0.5*temp1*sinio2*cos2u; uk = u-0.25*temp2*x7thm1*sin2u; xnodek = xnode+1.5*temp2*cosio*sin2u; xinck = xincl+1.5*temp2*cosio*sinio*cos2u; /* Orientation vectors */ sinuk = sin(uk); cosuk = cos(uk); sinik = sin(xinck); cosik = cos(xinck); sinnok = sin(xnodek); cosnok = cos(xnodek); xmx = -sinnok*cosik; xmy = cosnok*cosik; ux = xmx*sinuk+cosnok*cosuk; uy = xmy*sinuk+sinnok*cosuk; uz = sinik*sinuk; /* Position and velocity */ pos[0] = rk * ux * earth_radius_in_km; pos[1] = rk * uy * earth_radius_in_km; pos[2] = rk * uz * earth_radius_in_km; if( vel) { const double rdot = xke * sqrt(a) * esinE / r; const double rfdot = xke * sqrt(pl) / r; const double xn = xke / (a * sqrt(a)); const double rdotk = rdot - xn * temp1 * sinio2 * sin2u; const double rfdotk = rfdot + xn * temp1 * (sinio2 * cos2u + 1.5 * x3thm1); const double vx = xmx * cosuk - cosnok * sinuk; const double vy = xmy * cosuk - sinnok * sinuk; const double vz = sinik*cosuk; vel[0] = (rdotk * ux + rfdotk * vx) * earth_radius_in_km; vel[1] = (rdotk * uy + rfdotk * vy) * earth_radius_in_km; vel[2] = (rdotk * uz + rfdotk * vz) * earth_radius_in_km; } return( rval); } /*SGP4*/ ================================================ FILE: deep.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include "norad.h" #include "norad_in.h" /* omega_E = number of (sidereal) rotations of the earth per UT day: */ const double omega_E = 1.00273790934; #ifdef USE_ACCURATE_ANOMALISTICS /* The anomalistic month is the mean time it takes the moon to go from perigee to perigee. The anomalistic year is the mean time it takes the earth to go from perihelion to perihelion. The following lines compute the "correct" mean motions of the earth and sun: zns_per_day is the rate of change of the earth's mean anomaly, in radians per day, and the 'znl' quantities give similar rates for the moon. Problem is, the original SxPx sources give values that are close to, but not exactly equal to, these values. The "new" values are probably improvements from further observations, but if you actually used them, you'd break compatibility with older implementations, and wouldn't match up with the way NORAD and others actually compute TLEs. So the following few lines should be regarded as explanatory; we're stuck with using the older, less accurate SxPx values. */ const double days_per_anomalistic_month = 27.554551; const double days_per_anomalistic_year = 365.259635864; const double zns_per_day = twopi / days_per_anomalistic_year; const double zns_per_min = zns_per_day / minutes_per_day; const double znl_per_day = twopi / days_per_anomalistic_month; const double znl_per_min = znl_per_day / minutes_per_day; /* thdt = angular velocity of the earth, in radians/minute. */ /* Again, we have to use a less accurate value from the original */ /* SxPx, to replicate everybody else's results. */ const double thdt = twopi * omega_E / minutes_per_day; #else const double zns_per_min = 1.19459E-5; const double zns_per_day = 0.017201977; const double znl_per_day = 0.228027132; const double znl_per_min = 1.5835218E-4; const double thdt = 4.37526908801129966e-3; #endif /* zes = mean eccentricity of earth's orbit */ /* zel = mean eccentricity of the moon's orbit */ #define zes 0.01675 #define zel 0.05490 /* thetag: computes Greenwich sidereal time, as an angle in radians from 0 to 2*pi, for a given UT0 JD. */ static inline double ThetaG( const double jd) { /* Reference: The 1992 Astronomical Almanac, page B6. */ /* Earth rotations per sidereal day (non-constant) */ const double UT = fmod( jd + .5, 1.); const double seconds_per_day = 86400.; const double jd_2000 = 2451545.0; /* 1.5 Jan 2000 = JD 2451545. */ double t_cen, GMST, rval; t_cen = (jd - UT - jd_2000) / 36525.; GMST = 24110.54841 + t_cen * (8640184.812866 + t_cen * (0.093104 - t_cen * 6.2E-6)); GMST = fmod( GMST / seconds_per_day + omega_E * UT, 1.); if( GMST < 0.) GMST += 1.; rval = twopi * GMST; return( rval); } /*Function thetag*/ /* Previously, the integration step was given as two variables: */ /* 'stepp' (positive step = +720) and 'stepn' (negative step = -720). */ /* Exactly why this should be made a variable, much less _different_ */ /* variables for positive and negative, is entirely unclear... */ /* (8 Apr 2003) INTEGRATION_STEP is now a maximum integration step. */ /* The code in 'dpsec' splits the integration range into equally-sized */ /* pieces of 720 minutes (half a day) or smaller. */ /* (25 Aug 2006) INTEGRATION_STEP is now the variable */ /* 'dpsec_integration_step' so I can experiment with different */ /* integration techniques & evaluate their errors. */ static double dpsec_integration_step = 720.; static int dpsec_integration_order = 2; static int is_dundee_compliant = 0; void DLL_FUNC sxpx_set_implementation_param( const int param_index, const int new_param) { switch( param_index) { case SXPX_DPSEC_INTEGRATION_ORDER: dpsec_integration_order = new_param; break; case SXPX_DUNDEE_COMPLIANCE: is_dundee_compliant = new_param; break; } } void DLL_FUNC sxpx_set_dpsec_integration_step( const double new_step_size) { dpsec_integration_step = new_step_size; } static inline double eval_cubic_poly( const double x, const double constant, const double linear, const double quadratic_term, const double cubic_term) { return( constant + x * (linear + x * (quadratic_term + x * cubic_term))); } /* DEEP */ void Deep_dpinit( const tle_t *tle, deep_arg_t *deep_arg) { const double sinq = sin(tle->xnodeo); const double cosq = cos(tle->xnodeo); const double aqnv = 1/deep_arg->aodp; const double c1ss = 2.9864797E-6; /* 1900 Jan 0.5 = JD 2415020. */ const double days_since_1900 = tle->epoch - 2415020.; /* zcosi, zsini start as cos & sin of obliquity of earth's */ /* orbit = 23.444100 degrees... matches obliquity in 1963; */ /* probably just a slightly inaccurate value: */ const double zcosi0 = 0.91744867; const double zsini0 = 0.39785416; double zcosi = zcosi0; double zsini = zsini0; /* zcosg, zsing start as cos & sin of -78.779197 degrees */ double zsing = -0.98088458; double zcosg = 0.1945905; double bfact, cc = c1ss, se; double ze = zes, zn = zns_per_min; double sgh, sh, si; double zsinh = sinq, zcosh = cosq; double zcosil, zsinil, zcoshl, zsinhl; double zcosgl, zsingl; double sl; int iteration; deep_arg->thgr = ThetaG( tle->epoch); deep_arg->xnq = deep_arg->xnodp; deep_arg->omegaq = tle->omegao; /* if( days_since_1900 != deep_arg->preep) */ { const double lunar_asc_node = 4.5236020 - 9.2422029E-4 * days_since_1900; const double sin_asc_node = sin(lunar_asc_node); const double cos_asc_node = cos(lunar_asc_node); const double c_minus_gam = znl_per_day * days_since_1900 - 1.1151842; /* gam = longitude of perigee for the moon, in radians: */ const double gam = 5.8351514 + 0.0019443680 * days_since_1900; double zx, zy; deep_arg->preep = days_since_1900; zcosil = 0.91375164 - 0.03568096 * cos_asc_node; zsinil = sqrt(1. - zcosil * zcosil); zsinhl = 0.089683511 * sin_asc_node / zsinil; zcoshl = sqrt(1. - zsinhl*zsinhl); deep_arg->zmol = FMod2p( c_minus_gam); zx = zsini0 * sin_asc_node / zsinil; zy = zcoshl * cos_asc_node + zcosi0 * zsinhl * sin_asc_node; zx = atan2( zx, zy) + gam - lunar_asc_node; zcosgl = cos( zx); zsingl = sin( zx); deep_arg->zmos = FMod2p( 6.2565837 + zns_per_day * days_since_1900); } /* End if( days_since_1900 != deep_arg->preep) */ /* Do solar terms */ deep_arg->savtsn = 1E20; /* There was previously some convoluted logic here, but it boils */ /* down to this: we compute the solar terms, then the lunar terms. */ /* On a second pass, we recompute the solar terms, taking advantage */ /* of the improved data that resulted from computing lunar terms. */ for( iteration = 0; iteration < 2; iteration++) { const double c1l = 4.7968065E-7; const double a1 = zcosg * zcosh + zsing * zcosi * zsinh; const double a3 = -zsing * zcosh + zcosg * zcosi * zsinh; const double a7 = -zcosg * zsinh + zsing * zcosi * zcosh; const double a8 = zsing * zsini; const double a9 = zsing * zsinh + zcosg * zcosi * zcosh; const double a10 = zcosg * zsini; const double a2 = deep_arg->cosio * a7 + deep_arg->sinio * a8; const double a4 = deep_arg->cosio * a9 + deep_arg->sinio * a10; const double a5 = -deep_arg->sinio * a7 + deep_arg->cosio * a8; const double a6 = -deep_arg->sinio * a9 + deep_arg->cosio * a10; const double x1 = a1 * deep_arg->cosg + a2 * deep_arg->sing; const double x2 = a3 * deep_arg->cosg + a4 * deep_arg->sing; const double x3 = -a1 * deep_arg->sing + a2 * deep_arg->cosg; const double x4 = -a3 * deep_arg->sing + a4 * deep_arg->cosg; const double x5 = a5 * deep_arg->sing; const double x6 = a6 * deep_arg->sing; const double x7 = a5 * deep_arg->cosg; const double x8 = a6 * deep_arg->cosg; const double z31 = 12 * x1 * x1 - 3 * x3 * x3; const double z32 = 24 * x1 * x2 - 6 * x3 * x4; const double z33 = 12 * x2 * x2 - 3 * x4 * x4; const double z11 = -6 * a1 * a5 + deep_arg->eosq * (-24 * x1 * x7 - 6 * x3 * x5); const double z12 = -6 * (a1 * a6 + a3 * a5) + deep_arg->eosq * (-24 * (x2 * x7 + x1 * x8) - 6 * (x3 * x6 + x4 * x5)); const double z13 = -6 * a3 * a6 + deep_arg->eosq * (-24 * x2 * x8 - 6 * x4 * x6); const double z21 = 6 * a2 * a5 + deep_arg->eosq * (24 * x1 * x5 - 6 * x3 * x7); const double z22 = 6 * (a4 * a5 + a2 * a6) + deep_arg->eosq * (24 * (x2 * x5 + x1 * x6) - 6 * (x4 * x7 + x3 * x8)); const double z23 = 6 * a4 * a6 + deep_arg->eosq * (24 * x2 * x6 - 6 * x4 * x8); const double s3 = cc / deep_arg->xnq; const double s2 = -0.5 * s3 / deep_arg->betao; const double s4 = s3 * deep_arg->betao; const double s1 = -15 * tle->eo * s4; const double s5 = x1 * x3 + x2 * x4; const double s6 = x2 * x3 + x1 * x4; const double s7 = x2 * x4 - x1 * x3; double z1 = 3 * (a1 * a1 + a2 * a2) + z31 * deep_arg->eosq; double z2 = 6 * (a1 * a3 + a2 * a4) + z32 * deep_arg->eosq; double z3 = 3 * (a3 * a3 + a4 * a4) + z33 * deep_arg->eosq; z1 = z1 + z1 + deep_arg->betao2 * z31; z2 = z2 + z2 + deep_arg->betao2 * z32; z3 = z3 + z3 + deep_arg->betao2 * z33; se = s1*zn*s5; si = s2*zn*(z11+z13); sl = -zn*s3*(z1+z3-14-6*deep_arg->eosq); sgh = s4*zn*(z31+z33-6); if( tle->xincl < pi / 60.) /* pi / 60 radians = 3 degrees */ sh = 0; else sh = -zn*s2*(z21+z23); deep_arg->ee2 = 2*s1*s6; deep_arg->e3 = 2*s1*s7; deep_arg->xi2 = 2*s2*z12; deep_arg->xi3 = 2*s2*(z13-z11); deep_arg->xl2 = -2*s3*z2; deep_arg->xl3 = -2*s3*(z3-z1); deep_arg->xl4 = -2*s3*(-21-9*deep_arg->eosq)*ze; deep_arg->xgh2 = 2*s4*z32; deep_arg->xgh3 = 2*s4*(z33-z31); deep_arg->xgh4 = -18*s4*ze; deep_arg->xh2 = -2*s2*z22; deep_arg->xh3 = -2*s2*(z23-z21); if( !iteration) /* we compute lunar terms only on the first pass: */ { deep_arg->sse = se; deep_arg->ssi = si; deep_arg->ssl = sl; deep_arg->ssh = (deep_arg->sinio ? sh / deep_arg->sinio : 0.); deep_arg->ssg = sgh-deep_arg->cosio*deep_arg->ssh; deep_arg->se2 = deep_arg->ee2; deep_arg->si2 = deep_arg->xi2; deep_arg->sl2 = deep_arg->xl2; deep_arg->sgh2 = deep_arg->xgh2; deep_arg->sh2 = deep_arg->xh2; deep_arg->se3 = deep_arg->e3; deep_arg->si3 = deep_arg->xi3; deep_arg->sl3 = deep_arg->xl3; deep_arg->sgh3 = deep_arg->xgh3; deep_arg->sh3 = deep_arg->xh3; deep_arg->sl4 = deep_arg->xl4; deep_arg->sgh4 = deep_arg->xgh4; zcosg = zcosgl; zsing = zsingl; zcosi = zcosil; zsini = zsinil; zcosh = zcoshl * cosq + zsinhl * sinq; zsinh = sinq * zcoshl - cosq * zsinhl; zn = znl_per_min; cc = c1l; ze = zel; } } deep_arg->sse += se; deep_arg->ssi += si; deep_arg->ssl += sl; deep_arg->ssg += sgh; if( deep_arg->sinio) { deep_arg->ssg -= sh * deep_arg->cosio / deep_arg->sinio; deep_arg->ssh += sh / deep_arg->sinio; } /* "if mean motion is 1.893053 to 2.117652 revs/day, and ecc >= .5" */ if( deep_arg->xnq >= 0.00826 && deep_arg->xnq <= 0.00924 && tle->eo >= .5) { /* start of 12-hour orbit, e >.5 section */ /* 'root##' variables are somewhat inaccurate values for */ /* a few fully normalized sectorial/tesseral spherical */ /* harmonics of the Earth's gravitational potential: */ const double root22 = 1.7891679E-6; const double root32 = 3.7393792E-7; const double root44 = 7.3636953E-9; const double root52 = 1.1428639E-7; const double root54 = 2.1765803E-9; const double g201 = -0.306 - (tle->eo - 0.64) * 0.440; const double sini2 = deep_arg->sinio*deep_arg->sinio; const double f220 = 0.75*(1+2*deep_arg->cosio+deep_arg->cosio2); const double f221 = 1.5 * sini2; const double f321 = 1.875 * deep_arg->sinio * (1 - 2 *\ deep_arg->cosio - 3 * deep_arg->cosio2); const double f322 = -1.875*deep_arg->sinio*(1+2* deep_arg->cosio-3*deep_arg->cosio2); const double f441 = 35 * sini2 * f220; const double f442 = 39.3750 * sini2 * sini2; const double f522 = 9.84375*deep_arg->sinio*(sini2*(1-2*deep_arg->cosio-5* deep_arg->cosio2)+0.33333333*(-2+4*deep_arg->cosio+ 6*deep_arg->cosio2)); const double f523 = deep_arg->sinio*(4.92187512*sini2*(-2-4* deep_arg->cosio+10*deep_arg->cosio2)+6.56250012 *(1+2*deep_arg->cosio-3*deep_arg->cosio2)); const double f542 = 29.53125*deep_arg->sinio*(2-8* deep_arg->cosio+deep_arg->cosio2* (-12+8*deep_arg->cosio+10*deep_arg->cosio2)); const double f543 = 29.53125*deep_arg->sinio*(-2-8*deep_arg->cosio+ deep_arg->cosio2*(12+8*deep_arg->cosio-10* deep_arg->cosio2)); double g410, g422, g520, g521, g532, g533; double g211, g310, g322; double temp, temp1; deep_arg->resonance_flag = 1; /* it _is_ resonant... */ deep_arg->synchronous_flag = 0; /* but it's not synchronous */ /* Geopotential resonance initialization for 12 hour orbits: */ if (tle->eo <= 0.65) { g211 = 3.616-13.247*tle->eo+16.290*deep_arg->eosq; g310 = eval_cubic_poly( tle->eo, -19.302, 117.390, -228.419, 156.591); g322 = eval_cubic_poly( tle->eo, -18.9068, 109.7927, -214.6334, 146.5816); g410 = eval_cubic_poly( tle->eo, -41.122, 242.694, -471.094, 313.953); g422 = eval_cubic_poly( tle->eo, -146.407, 841.880, -1629.014, 1083.435); g520 = eval_cubic_poly( tle->eo, -532.114, 3017.977, -5740.032, 3708.276); /* NOTE: quadratic coeff was 5740 */ } else { g211 = eval_cubic_poly( tle->eo, -72.099, 331.819, -508.738, 266.724); g310 = eval_cubic_poly( tle->eo, -346.844, 1582.851, -2415.925, 1246.113); g322 = eval_cubic_poly( tle->eo, -342.585, 1554.908, -2366.899, 1215.972); g410 = eval_cubic_poly( tle->eo, -1052.797, 4758.686, -7193.992, 3651.957); g422 = eval_cubic_poly( tle->eo, -3581.69, 16178.11, -24462.77, 12422.52); if (tle->eo <= 0.715) g520 = eval_cubic_poly( tle->eo, 1464.74, -4664.75, 3763.64, 0.); else g520 = eval_cubic_poly( tle->eo, -5149.66, 29936.92, -54087.36, 31324.56); } /* End if (tle->eo <= 0.65) */ if (tle->eo < 0.7) { g533 = eval_cubic_poly( tle->eo, -919.2277, 4988.61, -9064.77, 5542.21); g521 = eval_cubic_poly( tle->eo, -822.71072, 4568.6173, -8491.4146, 5337.524); g532 = eval_cubic_poly( tle->eo, -853.666, 4690.25, -8624.77, 5341.4); } else { g533 = eval_cubic_poly( tle->eo, -37995.78, 161616.52, -229838.2, 109377.94); g521 = eval_cubic_poly( tle->eo, -51752.104, 218913.95, -309468.16, 146349.42); g532 = eval_cubic_poly( tle->eo, -40023.88, 170470.89, -242699.48, 115605.82); } /* End if (tle->eo <= 0.7) */ temp1 = 3 * deep_arg->xnq * deep_arg->xnq * aqnv * aqnv; temp = temp1*root22; deep_arg->d2201 = temp * f220 * g201; deep_arg->d2211 = temp * f221 * g211; temp1 *= aqnv; temp = temp1*root32; deep_arg->d3210 = temp * f321 * g310; deep_arg->d3222 = temp * f322 * g322; temp1 *= aqnv; temp = 2*temp1*root44; deep_arg->d4410 = temp * f441 * g410; deep_arg->d4422 = temp * f442 * g422; temp1 *= aqnv; temp = temp1*root52; deep_arg->d5220 = temp * f522 * g520; deep_arg->d5232 = temp * f523 * g532; temp = 2*temp1*root54; deep_arg->d5421 = temp * f542 * g521; deep_arg->d5433 = temp * f543 * g533; deep_arg->xlamo = tle->xmo+tle->xnodeo+tle->xnodeo-deep_arg->thgr-deep_arg->thgr; bfact = deep_arg->xmdot + deep_arg->xnodot+ deep_arg->xnodot - thdt - thdt; bfact += deep_arg->ssl + deep_arg->ssh + deep_arg->ssh; } /* end of 12-hour orbit, e >.5 section */ else if( deep_arg->xnq < 1.2 * twopi / minutes_per_day && deep_arg->xnq > 0.8 * twopi / minutes_per_day) { /* "if mean motion is .8 to 1.2 revs/day" */ const double q22 = 1.7891679E-6; const double q31 = 2.1460748E-6; const double q33 = 2.2123015E-7; const double cosio_plus_1 = 1. + deep_arg->cosio; const double g200 = 1+deep_arg->eosq*(-2.5+0.8125*deep_arg->eosq); const double g300 = 1+deep_arg->eosq*(-6+6.60937*deep_arg->eosq); const double f311 = 0.9375*deep_arg->sinio*deep_arg->sinio* (1+3*deep_arg->cosio)-0.75*cosio_plus_1; const double g310 = 1+2*deep_arg->eosq; const double f220 = 0.75 * cosio_plus_1 * cosio_plus_1; const double f330 = 2.5 * f220 * cosio_plus_1; deep_arg->resonance_flag = deep_arg->synchronous_flag = 1; /* Synchronous resonance terms initialization */ deep_arg->del1 = 3*deep_arg->xnq*deep_arg->xnq*aqnv*aqnv; deep_arg->del2 = 2*deep_arg->del1*f220*g200*q22; deep_arg->del3 = 3*deep_arg->del1*f330*g300*q33*aqnv; deep_arg->del1 *= f311*g310*q31*aqnv; deep_arg->xlamo = tle->xmo+tle->xnodeo+tle->omegao-deep_arg->thgr; bfact = deep_arg->xmdot + deep_arg->omgdot + deep_arg->xnodot - thdt; bfact = bfact+deep_arg->ssl+deep_arg->ssg+deep_arg->ssh; } /* End of geosych case */ else /* it's neither a high-e 12-hr orbit nor a geosynch: */ deep_arg->resonance_flag = deep_arg->synchronous_flag = 0; if( deep_arg->resonance_flag) { deep_arg->xfact = bfact-deep_arg->xnq; /* Initialize integrator */ deep_arg->xli = deep_arg->xlamo; deep_arg->xni = deep_arg->xnq; deep_arg->atime = 0; } /* End case dpinit: */ } /* 'dpsec' is unavoidably confusing. See https://projectpluto.com/dpsec.htm for some commentary on what's going on here. */ static inline void compute_dpsec_derivs( const deep_arg_t *deep_arg, double *derivs) { const double sin_li = sin( deep_arg->xli); const double cos_li = cos( deep_arg->xli); const double sin_2li = 2. * sin_li * cos_li; const double cos_2li = 2. * cos_li * cos_li - 1.; int i; derivs[0] = 0.; /* Dot terms calculated, using a lot of trig add/subtract */ /* identities to reduce the computational load... at the */ /* cost of making the code somewhat hard to follow: */ if( deep_arg->synchronous_flag ) { /* const double fasx2 = 0.1313091 radians = 7.523456 degrees */ /* const double fasx4 = 2.8843198 radians = 165.259351 degrees */ /* const double fasx6 = 0.3744809 radians = 21.456173 degrees */ const double c_fasx2 = 0.99139134268488593; const double s_fasx2 = 0.13093206501640101; const double c_2fasx4 = 0.87051638752972937; const double s_2fasx4 = -0.49213943048915526; const double c_3fasx6 = 0.43258117585763334; const double s_3fasx6 = 0.90159499016666422; const double sin_3li = sin_2li * cos_li + cos_2li * sin_li; const double cos_3li = cos_2li * cos_li - sin_2li * sin_li; double term1a = deep_arg->del1 * (sin_li * c_fasx2 - cos_li * s_fasx2); double term2a = deep_arg->del2 * (sin_2li * c_2fasx4 - cos_2li * s_2fasx4); double term3a = deep_arg->del3 * (sin_3li * c_3fasx6 - cos_3li * s_3fasx6); double term1b = deep_arg->del1 * (cos_li * c_fasx2 + sin_li * s_fasx2); double term2b = 2. * deep_arg->del2 * (cos_2li * c_2fasx4 + sin_2li * s_2fasx4); double term3b = 3. * deep_arg->del3 * (cos_3li * c_3fasx6 + sin_3li * s_3fasx6); for( i = 0; i < dpsec_integration_order; i += 2) { *derivs++ = term1a + term2a + term3a; *derivs++ = term1b + term2b + term3b; if( i + 2 < dpsec_integration_order) { term1a = -term1a; term2a *= -4.; term3a *= -9.; term1b = -term1b; term2b *= -4.; term3b *= -9.; } } } /* end of geosynch case */ else { /* orbit is a 12-hour resonant one: */ /* const double g22 = 5.7686396; */ /* const double g32 = 0.95240898; */ /* const double g44 = 1.8014998; */ /* const double g52 = 1.0508330; */ /* const double g54 = 4.4108898; */ const double c_g22 = 0.87051638752972937; const double s_g22 = -0.49213943048915526; const double c_g32 = 0.57972190187001149; const double s_g32 = 0.81481440616389245; const double c_g44 = -0.22866241528815548; const double s_g44 = 0.97350577801807991; const double c_g52 = 0.49684831179884198; const double s_g52 = 0.86783740128127729; const double c_g54 = -0.29695209575316894; const double s_g54 = -0.95489237761529999; const double xomi = deep_arg->omegaq + deep_arg->omgdot * deep_arg->atime; const double sin_omi = sin( xomi), cos_omi = cos( xomi); const double sin_li_m_omi = sin_li * cos_omi - sin_omi * cos_li; const double sin_li_p_omi = sin_li * cos_omi + sin_omi * cos_li; const double cos_li_m_omi = cos_li * cos_omi + sin_omi * sin_li; const double cos_li_p_omi = cos_li * cos_omi - sin_omi * sin_li; const double sin_2omi = 2. * sin_omi * cos_omi; const double cos_2omi = 2. * cos_omi * cos_omi - 1.; const double sin_2li_m_omi = sin_2li * cos_omi - sin_omi * cos_2li; const double sin_2li_p_omi = sin_2li * cos_omi + sin_omi * cos_2li; const double cos_2li_m_omi = cos_2li * cos_omi + sin_omi * sin_2li; const double cos_2li_p_omi = cos_2li * cos_omi - sin_omi * sin_2li; const double sin_2li_p_2omi = sin_2li * cos_2omi + sin_2omi * cos_2li; const double cos_2li_p_2omi = cos_2li * cos_2omi - sin_2omi * sin_2li; const double sin_2omi_p_li = sin_li * cos_2omi + sin_2omi * cos_li; const double cos_2omi_p_li = cos_li * cos_2omi - sin_2omi * sin_li; double term1a = deep_arg->d2201 * (sin_2omi_p_li*c_g22 - cos_2omi_p_li*s_g22) + deep_arg->d2211 * (sin_li * c_g22 - cos_li * s_g22) + deep_arg->d3210 * (sin_li_p_omi*c_g32 - cos_li_p_omi*s_g32) + deep_arg->d3222 * (sin_li_m_omi*c_g32 - cos_li_m_omi*s_g32) + deep_arg->d5220 * (sin_li_p_omi*c_g52 - cos_li_p_omi*s_g52) + deep_arg->d5232 * (sin_li_m_omi*c_g52 - cos_li_m_omi*s_g52); double term2a = deep_arg->d4410 * (sin_2li_p_2omi*c_g44 - cos_2li_p_2omi*s_g44) + deep_arg->d4422 * (sin_2li * c_g44 - cos_2li * s_g44) + deep_arg->d5421 * (sin_2li_p_omi*c_g54 - cos_2li_p_omi*s_g54) + deep_arg->d5433 * (sin_2li_m_omi*c_g54 - cos_2li_m_omi*s_g54); double term1b = (deep_arg->d2201 * (cos_2omi_p_li*c_g22 + sin_2omi_p_li*s_g22) + deep_arg->d2211 * (cos_li * c_g22 + sin_li * s_g22) + deep_arg->d3210 * (cos_li_p_omi*c_g32 + sin_li_p_omi*s_g32) + deep_arg->d3222 * (cos_li_m_omi*c_g32 + sin_li_m_omi*s_g32) + deep_arg->d5220 * (cos_li_p_omi*c_g52 + sin_li_p_omi*s_g52) + deep_arg->d5232 * (cos_li_m_omi*c_g52 + sin_li_m_omi*s_g52)); double term2b = 2. * (deep_arg->d4410 * (cos_2li_p_2omi*c_g44 + sin_2li_p_2omi*s_g44) + deep_arg->d4422 * (cos_2li * c_g44 + sin_2li * s_g44) + deep_arg->d5421 * (cos_2li_p_omi*c_g54 + sin_2li_p_omi*s_g54) + deep_arg->d5433 * (cos_2li_m_omi*c_g54 + sin_2li_m_omi*s_g54)); for( i = 0; i < dpsec_integration_order; i += 2) { *derivs++ = term1a + term2a; *derivs++ = term1b + term2b; if( i + 2 < dpsec_integration_order) { term1a = -term1a; term2a *= -4.; term1b = -term1b; term2b *= -4.; } } } /* End of 12-hr resonant case */ } void Deep_dpsec( const tle_t *tle, deep_arg_t *deep_arg) { double temp, xni, xli; int final_integration_step = 0; deep_arg->xll += deep_arg->ssl*deep_arg->t; deep_arg->omgadf += deep_arg->ssg*deep_arg->t; deep_arg->xnode += deep_arg->ssh*deep_arg->t; deep_arg->em = tle->eo+deep_arg->sse*deep_arg->t; deep_arg->xinc = tle->xincl+deep_arg->ssi*deep_arg->t; if( !deep_arg->resonance_flag ) return; /* If we're closer to t=0 than to the currently-stored data from the previous call to this function, then we're better off "restarting", going back to the initial data. The Dundee code rigs things up to _always_ take 720-minute steps from epoch to end time, except for the final step. So if we'd have to integrate "backwards" (toward the epoch), we gotta do a restart if we're to be Dundee-compliant. */ if( fabs( deep_arg->t) < fabs( deep_arg->t - deep_arg->atime) || (is_dundee_compliant && fabs( deep_arg->t) < fabs( deep_arg->atime))) { /* Epoch restart */ deep_arg->atime = 0.; xni = deep_arg->xnq; xli = deep_arg->xlamo; } else /* use xni, xli from previous runs: */ { xni = deep_arg->xni; xli = deep_arg->xli; } while( !final_integration_step) { double xldot, derivs[20], xlpow = 1., delt_factor; double delt = deep_arg->t - deep_arg->atime; int i; deep_arg->xni = xni; deep_arg->xli = xli; compute_dpsec_derivs( deep_arg, derivs); if( delt > dpsec_integration_step) delt = dpsec_integration_step; else if( delt < -dpsec_integration_step) delt = -dpsec_integration_step; else final_integration_step = 1; xldot = xni+deep_arg->xfact; xli += delt * xldot; xni += delt * derivs[0]; delt_factor = delt; for( i = 2; i <= dpsec_integration_order; i++) { xlpow *= xldot; derivs[i - 1] *= xlpow; delt_factor *= delt / (double)i; xli += delt_factor * derivs[i - 2]; xni += delt_factor * derivs[i - 1]; } if( !is_dundee_compliant || !final_integration_step) { deep_arg->xni = xni; deep_arg->xli = xli; deep_arg->atime += delt; } } deep_arg->xn = xni; temp = -deep_arg->xnode + deep_arg->thgr + deep_arg->t * thdt; deep_arg->xll = xli + temp + (deep_arg->synchronous_flag ? -deep_arg->omgadf : temp); /*End case dpsec: */ } void Deep_dpper( const tle_t *tle, deep_arg_t *deep_arg) { double sinis, cosis; /* If the time didn't change by more than 30 minutes, */ /* there's no good reason to recompute the perturbations; */ /* they don't change enough over so short a time span. */ /* However, the Dundee code _always_ recomputes, so if */ /* we're attempting to replicate its results, we've gotta */ /* recompute everything, too. */ if( fabs(deep_arg->savtsn-deep_arg->t) >= 30. || is_dundee_compliant) { double zf, zm, sinzf, ses, sis, sil, sel, sll, sls; double f2, f3, sghl, sghs, shs, sh1; deep_arg->savtsn = deep_arg->t; /* Update solar perturbations for time T: */ zm = deep_arg->zmos+zns_per_min*deep_arg->t; zf = zm+2*zes*sin(zm); sinzf = sin(zf); f2 = 0.5*sinzf*sinzf-0.25; f3 = -0.5*sinzf*cos(zf); ses = deep_arg->se2*f2+deep_arg->se3*f3; sis = deep_arg->si2*f2+deep_arg->si3*f3; sls = deep_arg->sl2*f2+deep_arg->sl3*f3+deep_arg->sl4*sinzf; sghs = deep_arg->sgh2*f2+deep_arg->sgh3*f3+deep_arg->sgh4*sinzf; shs = deep_arg->sh2*f2+deep_arg->sh3*f3; /* Update lunar perturbations for time T: */ zm = deep_arg->zmol+znl_per_min*deep_arg->t; zf = zm+2*zel*sin(zm); sinzf = sin(zf); f2 = 0.5*sinzf*sinzf-0.25; f3 = -0.5*sinzf*cos(zf); sel = deep_arg->ee2*f2+deep_arg->e3*f3; sil = deep_arg->xi2*f2+deep_arg->xi3*f3; sll = deep_arg->xl2*f2+deep_arg->xl3*f3+deep_arg->xl4*sinzf; sghl = deep_arg->xgh2*f2+deep_arg->xgh3*f3+deep_arg->xgh4*sinzf; sh1 = deep_arg->xh2*f2+deep_arg->xh3*f3; /* Sum the solar and lunar contributions: */ deep_arg->pe = ses+sel; deep_arg->pinc = sis+sil; deep_arg->pl = sls+sll; deep_arg->pgh = sghs+sghl; deep_arg->ph = shs+sh1; #ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH if( deep_arg->solar_lunar_init_flag) { deep_arg->pe0 = deep_arg->pe; deep_arg->pinc0 = deep_arg->pinc; deep_arg->pl0 = deep_arg->pl; deep_arg->pgh0 = deep_arg->pgh; deep_arg->ph0 = deep_arg->ph; } deep_arg->pe -= deep_arg->pe0; deep_arg->pinc -= deep_arg->pinc0; deep_arg->pl -= deep_arg->pl0; deep_arg->pgh -= deep_arg->pgh0; deep_arg->ph -= deep_arg->ph0; if( deep_arg->solar_lunar_init_flag) return; /* done all we really need to do here... */ #endif } /* In Spacetrack 3, sinis & cosis were initialized */ /* _before_ perturbations were added to xinc. In */ /* Spacetrack 6, it's the other way around (see below). */ #ifndef SPACETRACK_3 deep_arg->xinc += deep_arg->pinc; #endif sinis = sin( deep_arg->xinc); cosis = cos( deep_arg->xinc); #ifdef SPACETRACK_3 deep_arg->xinc += deep_arg->pinc; #endif /* Add solar/lunar perturbation correction to eccentricity: */ deep_arg->em += deep_arg->pe; deep_arg->xll += deep_arg->pl; deep_arg->omgadf += deep_arg->pgh; if( tle->xincl >= 0.2) { /* Apply periodics directly */ double temp_val; #ifdef SPACETRACK_3 sinis = sin(deep_arg->xinc); cosis = cos(deep_arg->xinc); #endif temp_val = deep_arg->ph / sinis; deep_arg->omgadf -= cosis * temp_val; deep_arg->xnode += temp_val; } else { /* Apply periodics with Lyddane modification */ const double sinok = sin(deep_arg->xnode); const double cosok = cos(deep_arg->xnode); const double alfdp = deep_arg->ph * cosok + (deep_arg->pinc * cosis + sinis) * sinok; const double betdp = - deep_arg->ph * sinok + (deep_arg->pinc * cosis + sinis) * cosok; double dls, delta_xnode; // deep_arg->xnode = FMod2p(deep_arg->xnode); delta_xnode = atan2(alfdp,betdp) - deep_arg->xnode; /* This is a patch to Lyddane modification suggested */ /* by Rob Matson, streamlined very slightly by BJG, to */ /* keep 'delta_xnode' between +/- 180 degrees: */ if( delta_xnode < - pi) delta_xnode += twopi; else if( delta_xnode > pi) delta_xnode -= twopi; dls = -deep_arg->xnode * sinis * deep_arg->pinc; #ifdef SPACETRACK_3 deep_arg->omgadf += dls + cosis * deep_arg->xnode - - cos( deep_arg->xinc) * (deep_arg->xnode + delta_xnode); #else deep_arg->omgadf += dls - cosis * delta_xnode; #endif deep_arg->xnode += delta_xnode; } /* End case dpper: */ } ================================================ FILE: dropouts.c ================================================ /* Code to check for the existence of certain artsats in Space-Track's master TLE list. Occasionally, they've dropped objects and I didn't realize it. The objects ended up on NEOCP and I didn't ID them as quickly as might be desired, because I assumed they must be "new". This should warn me if certain artsats get dropped from 'all_tle.txt'. The absence of certain artsats is essentially routine. But for some objects (marked with an !), Space-Track is our only source of TLEs. (Or at least, I've been relying on them. I _could_ generate TLES for CXO, for example, based on _Horizons_ ephems. Since I don't, I want this program to squawk loudly if Space-Track stops supplying CXO TLEs.) As of 2024 Aug 31, the program can also be used for updating the Space-Track TLEs in a slightly more cautious manner. If you have downloaded new TLEs as the (default) ALL_TLE.TXT, and your "usual" TLEs are at all_tle.txt, then ./dropouts ALL_TLE.TXT 25000 all_tle.txt will check to see if ALL_TLE.TXT actually has 25000 or more TLEs in it. If it does, the download is presumed to have succeeded; all_tle.txt is unlinked and replaced with ALL_TLE.TXT. If it fails, we leave both files undisturbed. This should help in the increasingly frequent situations where new TLE files are downloaded and then have only an error message, or a drastically reduced number of TLEs. */ #include #include #include #include #ifdef _WIN32 #include #else #include #endif #define VT_NORMAL "\033[0m" #define VT_REVERSE "\033[7m" int main( const int argc, const char **argv) { static const char *sats[] = { "00041A ! Cluster II-FM7", "00045A Cluster II-FM5", "00045B ! Cluster II-FM8", "02048A ! INTEGRAL", "07004A ! THEMIS-A", "07004D ! THEMIS-D", "07004E ! THEMIS-E", "09017B ! Atlas 5 Centaur R/B", "09068B ! Delta 4 R/B", "12003B ! Delta 4 R/B", "12011B ! Breeze-M R/B", "13024B ! WGS-5 R/B", "13026B ! Breeze-M R/B", "15005B ! Inmarsat 5F2 booster", "15011A ! MMS 1", "15011B ! MMS 2", "15011C ! MMS 3", "15011D ! MMS 4", "15019C ! Yuanzheng-1 Y1", "15042B ! Breeze-M R/B", "16041A ! MUOS 5", "18038A TESS", "22110B ! Ariane 5 R/B", "22146B ! Falcon 9 R/B", "22134B ! Falcon 9 R/B", "24048E DRO R/B", "24059B ! Falcon 9 R/B", "24127B ! Falcon 9 R/B", "24233A ! Proba-3", "24233B ! Proba-3 booster", "63039A Vela 1A", "64040B Vela 2B", "65058A Vela 3A", "65058B Vela 6", "67040A Vela 4A", "67040F ! Titan 3C transtage booster", "69046F ! Titan 3C transtage booster", "69046G Vela 9/10 interstage", "70027C ! Vela 6 booster", "72073A IMP-7", "76023C ! SOLRAD-11A", "76023H ! SOLRAD-11 debris", "77093E SL-6 R/B(2)", "83020A ! ASTRON", "83020D ! ASTRON booster", "92044A GEOTAIL", "95062A ! ISO", "95062C ! ISO debris", "97075B ! Equator S", "99040B ! Chandra X-Ray Observatory", "99040D ! IUS (for CXO)", "99066A ! XMM/Newton", "99066B ! XMM/Newton booster", NULL }; FILE *ifile = fopen( argc == 1 ? "all_tle.txt" : argv[1], "rb"); char buff[100]; size_t i; int trouble_found = 0, n_found = 0; assert( ifile); while( fgets( buff, sizeof( buff), ifile)) if( *buff == '1' && buff[1] == ' ' && buff[7] == 'U') { size_t len = strlen( buff); while( len && buff[len - 1] < ' ') len--; if( 69 == len) { n_found++; for( i = 0; sats[i]; i++) if( sats[i][0] == buff[9] && !memcmp( sats[i], buff + 9, 7)) sats[i] = ""; } } fclose( ifile); printf( "This will list high-flying artsats for which TLEs are not provided :\n"); for( i = 0; sats[i]; i++) if( sats[i][0]) { printf( "%s\n", sats[i]); if( sats[i][7] == '!') { trouble_found = 1; printf( VT_REVERSE); printf( "DANGER!!! We do NOT have an independent source of TLEs\n"); printf( "for this object. Please report to " "pluto\x40\x70roject\x70luto\x2e\x63om.\n"); /* Above is (slightly) obfuscated address to foil spambots */ printf( "This needs to be fixed.\n"); printf( VT_NORMAL); } } if( !trouble_found) printf( "Any missing objects are covered by other sources. Nothing\n" "to worry about here.\n"); printf( "%d found\n", n_found); if( 4 == argc && n_found > atoi( argv[2])) { printf( "Replacing '%s' with '%s'\n", argv[3], argv[1]); #ifdef _WIN32 _unlink( argv[3]); #else unlink( argv[3]); #endif rename( argv[1], argv[3]); } return( 0); } ================================================ FILE: dynamic.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ /* Hooks to allow the satellite code to be accessed from a DLL, with the DLL not loaded at startup; instead, LoadLibrary is used at the time you decide you actually want satellite functions. Not something likely to be useful to many people. I used it some years back for my desktop planetarium software; I could check for the existence of the DLL, use it if available, or fall back to some built-in code if it wasn't. (The DLL was, by standards of the day, a little bit large. Not everybody had enough interest in artsats to download it.) */ #include #include #include "windows.h" #include "norad.h" typedef void (__stdcall *sxpx_init_fn)( double *params, const tle_t *tle); typedef int (__stdcall *sxpx_fn)( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel); typedef long (__stdcall *sxpx_version_fn)( void); static HINSTANCE load_sat_code_lib( const int unload) { static HINSTANCE h_sat_code_lib = (HINSTANCE)0; static int first_time = 1; if( unload) { if( h_sat_code_lib) FreeLibrary( h_sat_code_lib); h_sat_code_lib = NULL; first_time = 1; } else if( first_time) { h_sat_code_lib = LoadLibrary( "sat_code.dll"); first_time = 0; } return( h_sat_code_lib); } /* 26 Nov 2002: revised following two functions slightly so that the return values distinguish between "didn't get the function" and "didn't get the library" */ int SXPX_init( double *params, const tle_t *tle, const int sxpx_num) { static sxpx_init_fn func[5]; static char already_done[5]; int rval = 0; HINSTANCE h_sat_code_lib; if( !params) /* flag to unload library */ { int i; load_sat_code_lib( -1); for( i = 0; i < 5; i++) already_done[i] = 0; return( 0); } h_sat_code_lib = load_sat_code_lib( 0); if( !already_done[sxpx_num]) { if( h_sat_code_lib) func[sxpx_num] = (sxpx_init_fn)GetProcAddress( h_sat_code_lib, (LPCSTR)( sxpx_num + 1)); already_done[sxpx_num] = 1; } if( func[sxpx_num]) (*func[sxpx_num])( params, tle); else rval = -1; if( !h_sat_code_lib) rval = -2; return( rval); } int SXPX( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel, const int sxpx_num) { static sxpx_fn func[5]; static char already_done[5]; int rval = 0; HINSTANCE h_sat_code_lib = load_sat_code_lib( 0); if( !already_done[sxpx_num]) { if( h_sat_code_lib) func[sxpx_num] = (sxpx_fn)GetProcAddress( h_sat_code_lib, (LPCSTR)( sxpx_num + 6)); already_done[sxpx_num] = 1; } if( func[sxpx_num]) rval = (*func[sxpx_num])( tsince, tle, params, pos, vel); else rval = -1; if( !h_sat_code_lib) rval = -2; return( rval); } long get_sat_code_lib_version( void) { HINSTANCE h_sat_code_lib = load_sat_code_lib( 0); long rval; if( !h_sat_code_lib) rval = -2; else { sxpx_version_fn func = (sxpx_version_fn)GetProcAddress( h_sat_code_lib, (LPCSTR)20); if( !func) rval = -1; else rval = (*func)( ); } return( rval); } ================================================ FILE: elem2tle.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ /* MOSTLY OBSOLETE. See 'eph2tle.cpp' in the Find_Orb project (https://github.com/Bill-Gray/find_orb) for a considerably better approach to computing TLEs from orbital data. #include #include #include #include #include #include "watdefs.h" #include "afuncs.h" #include "comets.h" #include "norad.h" #include "norad_in.h" /* for xke definition */ #include "date.h" const double earth_mass_over_sun_mass = 2.98994e-6; #define GAUSS_K .01720209895 #define SOLAR_GM (GAUSS_K * GAUSS_K) #define PI 3.141592653589793238462643383279502884197169399375105 int write_tle_from_vector( char *buff, const double *state_vect, const double epoch, const char *norad_desig, const char *intl_desig); int verbose = 0; static void set_tle_defaults( tle_t *tle) { memset( tle, 0, sizeof( tle_t)); strcpy( tle->intl_desig, "56999ZZ "); tle->classification = 'U'; tle->ephemeris_type = '0'; } #define centralize_angle(x) (fmod( (x) + PI * 10., PI + PI)) int vector_to_tle( tle_t *tle, const double *state_vect) { ELEMENTS elem; int rval = 0, i; double tvect[6]; const double max_ecc = .9999; for( i = 0; i < 6; i++) /* cvt from km, km/min to AU, AU/day */ tvect[i] = state_vect[i]; elem.gm = xke * xke * earth_radius_in_km * earth_radius_in_km * earth_radius_in_km; calc_classical_elements( &elem, tvect, tle->epoch, 1); tle->xincl = centralize_angle( elem.incl); tle->xnodeo = centralize_angle( elem.asc_node); tle->omegao = centralize_angle( elem.arg_per); tle->xmo = centralize_angle( elem.mean_anomaly); if( elem.ecc > max_ecc || elem.major_axis <= 0.) rval = -1; else { tle->eo = elem.ecc; tle->xno = 1. / elem.t0; /* xno is now in radians per minute */ rval = 0; } if( tle->xincl < 0.) { tle->xincl = -tle->xincl; tle->xnodeo = centralize_angle( tle->xnodeo + PI);; tle->omegao = centralize_angle( tle->omegao + PI);; } return( rval); } static void show_results( const char *title, const tle_t *tle, const double *state_vect) { if( title) printf( "%s\n", title); if( tle) { char buff[200]; write_elements_in_tle_format( buff, tle); printf( "%s", buff); } printf(" %16.8f %16.8f %16.8f \n", state_vect[0], state_vect[1], state_vect[2]); printf(" %16.8f %16.8f %16.8f \n", state_vect[3] / 60., state_vect[4] / 60., state_vect[5] / 60.); } static int compute_new_state_vect( const tle_t *tle, double *state_vect, const int ephem) { double sat_params[N_SAT_PARAMS]; int rval = 0; switch( ephem) { case 0: SGP_init( sat_params, tle); rval = SGP( 0., tle, sat_params, state_vect, state_vect + 3); break; case 1: SGP4_init( sat_params, tle); rval = SGP4( 0., tle, sat_params, state_vect, state_vect + 3); break; case 2: SGP8_init( sat_params, tle); rval = SGP8( 0., tle, sat_params, state_vect, state_vect + 3); break; case 3: SDP4_init( sat_params, tle); rval = SDP4( 0., tle, sat_params, state_vect, state_vect + 3); break; case 4: SDP8_init( sat_params, tle); rval = SDP8( 0., tle, sat_params, state_vect, state_vect + 3); break; default: printf( "??? ephem = %d\n", ephem); rval = -99; break; } // if( rval) // printf( "??? rval = %d; ecc = %.6lf\n", rval, tle->eo); return( rval); } #define SIMPLEX_POINT struct simplex_point SIMPLEX_POINT { double state_vect[6]; double error; }; static double total_vector_diff( const double *vect1, const double *vect2) { int i; double rval = 0.; for( i = 0; i < 6; i++) { double delta = vect1[i] - vect2[i]; if( i >= 3) delta *= 1000.; rval += delta * delta; } return( rval); } static double compute_simplex_point_error( const double *state_vect, tle_t *tle, const double *start, const int ephem) { double rval = 0., state_out[6]; int compute_rval, vect_to_tle_rval; vect_to_tle_rval = vector_to_tle( tle, state_vect); if( vect_to_tle_rval == -1) return( 1.e+37); compute_rval = compute_new_state_vect( tle, state_out, ephem); if( compute_rval == SXPX_ERR_NEARLY_PARABOLIC || compute_rval == SXPX_ERR_NEGATIVE_MAJOR_AXIS || compute_rval == SXPX_ERR_NEGATIVE_XN || vect_to_tle_rval == -1) rval = 1.e+37; /* invalid vector */ else rval = total_vector_diff( state_out, start); return( rval); } static double try_simplex( SIMPLEX_POINT *simp, const double factor, tle_t *tle, const double *start, const int ephem) { SIMPLEX_POINT new_point; int i, j; for( i = 0; i < 6; i++) { new_point.state_vect[i] = factor * simp->state_vect[i]; for( j = 1; j < 7; j++) new_point.state_vect[i] += (1. - factor) * simp[j].state_vect[i] / 6.; } new_point.error = compute_simplex_point_error( new_point.state_vect, tle, start, ephem); if( new_point.error <= simp->error) *simp = new_point; return( new_point.error); } static void sort_simplexes( SIMPLEX_POINT *simp) { int i; for( i = 0; i < 7; i++) /* sort simplex points by error */ if( simp[i].error < simp[i + 1].error) /* highest to lowest */ { SIMPLEX_POINT temp_elem = simp[i]; simp[i] = simp[i + 1]; simp[i + 1] = temp_elem; if( i) i -= 2; } } double dist_offset = 10000., vel_offset = 10.; static void create_randomized_simplex( SIMPLEX_POINT *simp, const double *start_vect) { int i; for( i = 0; i < 6; i++) { const double zval = (double)rand( ) / (double)RAND_MAX - .5; simp->state_vect[i] = start_vect[i] + zval * (i < 3 ? dist_offset : vel_offset); } } static int initialize_simplexes( SIMPLEX_POINT *simp, const double *state_vect, const double *start_vect, const int ephem) { int i, rval = 0; memcpy( simp[6].state_vect, start_vect, 6 * sizeof( double)); assert( start_vect[0] && start_vect[1] && start_vect[2]); for( i = 0; i < 7 && !rval; i++) { tle_t tle; int iter = 0; set_tle_defaults( &tle); if( i != 6) create_randomized_simplex( simp + i, start_vect); while ( (simp[i].error = compute_simplex_point_error( simp[i].state_vect, &tle, state_vect, ephem)) > 1e+36 && iter++ < 1000) create_randomized_simplex( simp, start_vect); if( iter >= 1000) rval = -1; } return( rval); } static int find_tle_via_simplex_method( tle_t *tle, const double *state_vect, const double *start_vect, const int ephem) { SIMPLEX_POINT simp[7]; double best_rval_found = 1e+39, best_vect[6]; int i, j, soln_found = 0, n_iterations = 0; int n_consecutive_contractions = 0; const int max_iterations = 43000; if( verbose) show_results( "Setting up:", NULL, start_vect); srand( 1); if( initialize_simplexes( simp, state_vect, start_vect, ephem)) return( 0); /* no solution found */ while( !soln_found && n_iterations++ < max_iterations) { double ytry; sort_simplexes( simp); ytry = try_simplex( simp, -1., tle, state_vect, ephem); if( ytry <= simp[6].error) { if( verbose) { char buff[200]; printf( "New record low: %f\n", ytry); write_elements_in_tle_format( buff, tle); printf( "%s", buff); } try_simplex( simp, 2., tle, state_vect, ephem); if( ytry < 1e-13) soln_found = true; if( ytry < best_rval_found) { best_rval_found = ytry; memcpy( best_vect, simp[0].state_vect, 6 * sizeof( double)); } n_consecutive_contractions = 0; } else if( ytry > simp[1].error) { double ysave = simp[0].error; ytry = try_simplex( simp, .5, tle, state_vect, ephem); if( ytry > ysave) /* still no success; try contracting */ { /* around lowest point: */ // printf( "Contracting around best point\n"); for( i = 0; i < 6; i++) { for( j = 0; j < 6; j++) simp[i].state_vect[j] = (simp[i].state_vect[j] + simp[6].state_vect[j]) / 2.; simp[i].error = compute_simplex_point_error( simp[i].state_vect, tle, state_vect, ephem); } n_consecutive_contractions++; if( n_consecutive_contractions == 30) initialize_simplexes( simp, state_vect, best_vect, ephem); } else n_consecutive_contractions = 0; } if( n_iterations % 200 == 199) initialize_simplexes( simp, state_vect, best_vect, ephem); } sort_simplexes( simp); if( verbose) printf( "End err: %f\n", simp[6].error); vector_to_tle( tle, best_vect); // vector_to_tle( tle, simp[6].state_vect); return( soln_found); } int compute_tle_from_state_vector( tle_t *tle, const double *state_vect, const int ephem, double *trial_state) { int n_failed_steps = 0, i; double state_out[6], best_vect[6], curr_err; const double thresh = 1e-12; memcpy( trial_state, state_vect, 6 * sizeof( double)); if( vector_to_tle( tle, state_vect)) { printf( "Immediate failure\n"); return( -1); } memcpy( best_vect, state_vect, 6 * sizeof( double)); compute_new_state_vect( tle, state_out, ephem); for( i = 0; i < 6; i++) trial_state[i] += state_vect[i] - state_out[i]; curr_err = total_vector_diff( state_out, state_vect); if( verbose) show_results( "Initial guess", tle, state_out); if( curr_err < thresh) printf( "Got it right away\n"); while( curr_err > thresh && n_failed_steps < 20) { double new_err = 0.; tle_t new_tle = *tle; if( vector_to_tle( &new_tle, trial_state)) { memcpy( trial_state, best_vect, 6 * sizeof( double)); show_results( "Simple failure:", tle, trial_state); return( -1); } compute_new_state_vect( &new_tle, state_out, ephem); new_err = total_vector_diff( state_out, state_vect); if( new_err > curr_err * .9) n_failed_steps++; /* slow or no convergence */ if( new_err < curr_err) { curr_err = new_err; *tle = new_tle; memcpy( best_vect, trial_state, 6 * sizeof( double)); if( verbose) { printf( "New record %f\n", curr_err); show_results( NULL, tle, state_out); } } for( i = 0; i < 6; i++) trial_state[i] += state_vect[i] - state_out[i]; } memcpy( trial_state, best_vect, 6 * sizeof( double)); return( curr_err > thresh); } /* Main program */ int main( const int argc, const char **argv) { const char *tle_filename = ((argc == 1) ? "test.tle" : argv[1]); FILE *ifile = fopen( tle_filename, "rb"); tle_t tle; /* Pointer to two-line elements set for satellite */ char line1[100], line2[100]; int ephem = 1; /* default to SGP4 */ int i; /* Index for loops etc */ int n_failures = 0, n_simple = 0, n_simplex = 0; bool failures_only = false; for( i = 2; i < argc; i++) if( argv[i][0] == '-') switch( argv[i][1]) { case 'f': failures_only = true; break; case 'v': verbose = 1; break; case 'd': dist_offset = atof( argv[i] + 2); break; case 's': vel_offset = atof( argv[i] + 2); break; default: printf( "Option '%s' unrecognized\n", argv[i]); break; } if( !ifile) { printf( "Couldn't open input TLE file %s\n", tle_filename); exit( -1); } *line1 = '\0'; while( fgets( line2, sizeof( line2), ifile)) { int got_data = 0; double state_vect[6]; set_tle_defaults( &tle); if( strlen( line2) > 110 && line2[7] == '.' && line2[18] == '.' && line2[0] == '2' && line2[1] == '4') { got_data = 3; /* Find_Orb state vector ephemeris */ tle.epoch = atof( line2); sscanf( line2 + 13, "%lf %lf %lf %lf %lf %lf", state_vect + 0, state_vect + 1, state_vect + 2, state_vect + 3, state_vect + 4, state_vect + 5); } else if( strlen( line1) > 55 && !memcmp( line1 + 50, " (TDB)", 6)) { /* JPL Horizons vector */ const double obliq_2000 = 23.4392911 * PI / 180.; tle.epoch = atof( line1); /* get JD epoch from header... */ strcpy( line1, line2); if( fgets( line2, sizeof( line2), ifile)) got_data = 1; sscanf( line1, "%lf %lf %lf", state_vect + 0, state_vect + 1, state_vect + 2); sscanf( line2, "%lf %lf %lf", state_vect + 3, state_vect + 4, state_vect + 5); /* Cvt ecliptic to equatorial 2000: */ rotate_vector( state_vect , obliq_2000, 0); rotate_vector( state_vect + 3, obliq_2000, 0); } else if( parse_elements( line1, line2, &tle) >= 0) got_data = 2; if( got_data == 1 || got_data == 3) tle.epoch -= 68.00 / 86400.; /* rough convert TDT to UTC */ if( got_data) /* hey! we got a TLE! */ { double sat_params[N_SAT_PARAMS], trial_state[6]; int simple_rval; bool failed = false; tle_t new_tle; if( got_data == 1 || got_data == 3) { ephem = 3; /* Use SDP4 for JPL Horizons vectors */ for( i = 0; i < 6 && fabs( state_vect[i]) < 1.; i++) ; if( i == 6) /* all small quantities, must be in AU & AU/day : */ { for( i = 0; i < 6; i++) state_vect[i] *= AU_IN_KM; for( i = 3; i < 6; i++) state_vect[i] /= seconds_per_day; } for( i = 3; i < 6; i++) /* cvt km/sec to km/min */ state_vect[i] *= seconds_per_minute; if( !failures_only) show_results( "Before:", NULL, state_vect); } else { int is_deep = select_ephemeris( &tle); if( is_deep && (ephem == 1 || ephem == 2)) ephem += 2; /* switch to an SDx */ if( !is_deep && (ephem == 3 || ephem == 4)) ephem -= 2; /* switch to an SGx */ /* Calling of NORAD routines */ /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8) */ /* will be called in turn with the appropriate TLE set */ switch( ephem) { case 0: SGP_init( sat_params, &tle); SGP( 0., &tle, sat_params, state_vect, state_vect + 3); break; case 1: SGP4_init( sat_params, &tle); SGP4( 0., &tle, sat_params, state_vect, state_vect + 3); break; case 2: SGP8_init( sat_params, &tle); SGP8( 0., &tle, sat_params, state_vect, state_vect + 3); break; case 3: SDP4_init( sat_params, &tle); SDP4( 0., &tle, sat_params, state_vect, state_vect + 3); break; case 4: SDP8_init( sat_params, &tle); SDP8( 0., &tle, sat_params, state_vect, state_vect + 3); break; } if( !failures_only) show_results( "Before:", &tle, state_vect); } new_tle = tle; simple_rval = compute_tle_from_state_vector( &new_tle, state_vect, ephem, trial_state); if( simple_rval) { n_simplex++; find_tle_via_simplex_method( &new_tle, state_vect, trial_state, ephem); } else n_simple++; compute_new_state_vect( &new_tle, trial_state, ephem); for( i = 0; i < 6; i++) { trial_state[i] -= state_vect[i]; if( fabs( trial_state[i]) > 1e-6) failed = true; } if( failed && failures_only) show_results( "Before:", &tle, state_vect); if( failed || !failures_only) show_results( (simple_rval ? "Simplex result:" : "Simplest method:"), &new_tle, trial_state); if( failed) n_failures++; } strcpy( line1, line2); } fclose( ifile); printf( "%d solved with simple method; %d with simplex\n", n_simple, n_simplex); if( n_failures) printf( "%d failures\n", n_failures); return(0); } /* End of main() */ ================================================ FILE: fake_ast.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. This program will generate simulated geocentric observations for a given object from a TLE. In theory, one can then fit these pseudo-observations to a higher-quality physical model to get a considerably more accurate ephemeris for the object. */ #include #include #include #include "norad.h" #include "observe.h" #define PI 3.1415926535897932384626433832795028841971693993751058209749445923 int main( const int argc, const char **argv) { FILE *ifile = fopen( argv[1], "rb"); char line1[100], line2[100]; const char *intl_id = NULL; double step_size = .1; int i, n_steps = 100; bool show_vectors = false; if( !ifile) { printf( "Couldn't open input file\n"); exit( -1); } for( i = 1; i < argc; i++) if( argv[i][0] == '-') switch( argv[i][1]) { case 'i': intl_id = argv[i] + 2; break; case 'n': n_steps = atoi( argv[i] + 2); break; case 's': step_size = atof( argv[i] + 2); break; case 'v': show_vectors = true; break; default: printf( "Unrecognized option '%s'\n", argv[i]); break; } *line1 = '\0'; sxpx_set_implementation_param( SXPX_DUNDEE_COMPLIANCE, 1); while( fgets( line2, sizeof( line2), ifile)) { tle_t tle; /* Pointer to two-line elements set for satellite */ int err_val; if( (!intl_id || !memcmp( intl_id, line1 + 9, 6)) && (err_val = parse_elements( line1, line2, &tle)) >= 0) { /* hey! we got a TLE! */ int is_deep = select_ephemeris( &tle); double sat_params[N_SAT_PARAMS], observer_loc[3]; double prev_pos[3]; if( err_val) printf( "WARNING: TLE parsing error %d\n", err_val); for( i = 0; i < 3; i++) observer_loc[i] = 0.; if( is_deep) SDP4_init( sat_params, &tle); else SGP4_init( sat_params, &tle); for( i = 0; i < n_steps; i++) { double pos[3]; /* Satellite position vector */ double t_since = (double)( i - n_steps / 2) * step_size; double jd = tle.epoch + t_since; t_since *= 1440.; if( is_deep) err_val = SDP4( t_since, &tle, sat_params, pos, NULL); else err_val = SGP4( t_since, &tle, sat_params, pos, NULL); if( err_val) printf( "Ephemeris error %d\n", err_val); if( show_vectors) { if( i) printf( "%14.6f %14.6f %14.6f - ", pos[0] - prev_pos[0], pos[1] - prev_pos[1], pos[2] - prev_pos[2]); printf( "%14.6f %14.6f %14.6f\n", pos[0], pos[1], pos[2]); memcpy( prev_pos, pos, 3 * sizeof( double)); } else { double ra, dec, dist_to_satellite; get_satellite_ra_dec_delta( observer_loc, pos, &ra, &dec, &dist_to_satellite); epoch_of_date_to_j2000( jd, &ra, &dec); printf( "%-14sC%13.5f %08.4f %+08.4f", intl_id, jd, ra * 180. / PI, dec * 180. / PI); printf( " TLEs 500\n"); } } } strcpy( line1, line2); } fclose( ifile); return( 0); } /* End of main() */ ================================================ FILE: fix_tles.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. fix_tles : change TLE data and replace with correct checksums If one alters a TLE, one will normally cause a change in the checksum data at the end of the line. This program reads in successive lines from an input file; if a line and the preceding line make a TLE, the checksum is computed for both lines and is suitably reset. I've had instances where I realized that either the COSPAR or NORAD designation was incorrectly set, and added options that let you specify which designation should be used for all TLEs in the output. (This is specific to my use case : I usually compute many TLEs for a particular object, which each TLE being fitted to give good state vectors over one day.) At some point, I may need to similarly batch-correct bulletin numbers or something of that ilk, but at present, only the designations (and checksums) can be batch-corrected with this program. */ #include #include #include #include #include "norad.h" /* After being modified, the checksum byte in TLEs must be recomputed : */ static void set_checksum( char *line) { const int csum = tle_checksum( line); assert( csum >= 0); line[68] += csum; if( line[68] > '9') line[68] -= 10; } static void usage( void) { fprintf( stderr, "'fix_tles' reads in TLEs and outputs TLEs with corrected checksums.\n" "Options are :\n" " -i YYNNNA Replace COSPAR designation\n" " -n NNNNN Replace five-digit NORAD designation\n" " -f filename Specify input file (default = stdin)\n" " -o filename Specify output file (default = stdout)\n" ); exit( -1); } int main( const int argc, const char **argv) { int i, line_no = 0; const char *norad_desig = NULL; char intl_desig[10]; FILE *ifile = stdin, *ofile = stdout; char line1[200], line2[200]; *intl_desig = '\0'; for( i = 1; i < argc; i++) if( argv[i][0] == '-') { const char *arg = argv[i] + 2; if( !*arg && i < argc - 1) arg = argv[i + 1]; switch( argv[i][1]) { case 'i': assert( strlen( arg) < 9); snprintf( intl_desig, sizeof( intl_desig), "%-9s", arg); break; case 'n': norad_desig = arg; break; case 'o': ofile = fopen( arg, "wb"); if( !ofile) { fprintf( stderr, "'%s' not opened\n", arg); usage( ); } break; case 'f': ifile = fopen( arg, "rb"); if( !ifile) { fprintf( stderr, "'%s' not opened\n", arg); usage( ); } break; default: fprintf( stderr, "Unrecognized option '%s'\n", argv[i]); usage( ); break; } } *line1 = '\0'; while( fgets( line2, sizeof( line2), ifile)) { tle_t unused_tle; if( parse_elements( line1, line2, &unused_tle) >= 0) { if( norad_desig) { memcpy( line1 + 2, norad_desig, 5); memcpy( line2 + 2, norad_desig, 5); } if( *intl_desig) memcpy( line1 + 9, intl_desig, 8); set_checksum( line1); set_checksum( line2); } if( line_no) fputs( line1, ofile); line_no++; strcpy( line1, line2); } if( line_no) fputs( line1, ofile); return( 0); } ================================================ FILE: get_el.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include #include #include #include #include "norad.h" #define PI 3.141592653589793238462643383279502884197 #define TWOPI (2. * PI) #define MINUTES_PER_DAY 1440. #define MINUTES_PER_DAY_SQUARED (MINUTES_PER_DAY * MINUTES_PER_DAY) #define MINUTES_PER_DAY_CUBED (MINUTES_PER_DAY * MINUTES_PER_DAY_SQUARED) #define AE 1.0 /* distance units, earth radii */ /* TLEs have four angles on line 2, given in the form DDD.DDDD. This can be parsed more quickly as an integer, then cast to double and converted to radians, all in one step. */ static int get_angle( const char *buff) { int rval = 0; while( *buff == ' ') buff++; while( *buff != ' ') { if( *buff != '.') rval = rval * 10 + (int)( *buff - '0'); buff++; } return( rval); } /* Converts the quasi scientific notation of the "Motion Dot Dot/6" or "BSTAR" field to double. The input will always be of the form sdddddSe ....where s is blank or + or -; ddddd is a five-digit mantissa; S is + or - or blank; and e is a single-digit exponent. A decimal point is assumed before the five-digit mantissa. */ static double sci( const char *string) { double rval = 0.; if( string[1] != ' ') { const int ival = atoi( string); if( ival) { rval = (double)ival * 1.e-5; if( string[7] != '0') { int exponent = string[7] - '0'; if( string[6] == '-') while( exponent--) rval *= .1; else while( exponent--) rval *= 10.; } } } return( rval); } /* Does a checksum modulo 10 on the given line. Digits = their value, '-' = 1, all other chars = 0. Returns 0 if ok, a negative value if it's definitely not a TLE line, positive if it's all OK except the checksum. This last was added because people sometimes want to use TLEs without worrying about the checksum. */ int DLL_FUNC tle_checksum( const char *buff) { int rval = 0; int count = 69; if( (*buff != '1' && *buff != '2') || buff[1] != ' ') return( -1); while( --count) { if( *buff > '0' && *buff <= '9') rval += *buff - '0'; else if( *buff == '-') rval++; if( *buff < ' ' || *buff > 'z') /* invalid character */ return( -2); buff++; } rval -= *buff++ - '0'; if( *buff > ' ') /* line unterminated */ rval = -3; else { rval %= 10; if( rval < 0) rval += 10; } return( rval); } static inline int mutant_dehex( const char ichar) { int rval; if( ichar <= '9' && ichar >= '0') rval = ichar - '0'; else if( ichar >= 'A' && ichar <= 'Z') rval = ichar + 10 - 'A'; else rval = -1; return( rval); } /* The "standard" SDP4 model fails badly for very high-flying satellites (mostly, but not always, those with orbital periods of greater than about a week). Highly eccentric orbits are more likely to fail than near-circular ones. And of course, hyperbolic orbits never work with SGP4/SDP4. As a non-standard extension, I'm simply storing state vectors for such orbits, using the following somewhat odd scheme : 1 40391U 15007B 15091.99922241 sxxxxxxxx syyyyyyyy szzzzzzzzH 9997 2 49391 [valid range, accuracy] saaaaaaaa sbbbbbbbb scccccccc 0 8 Epoch, int'l & NORAD IDs are stored in the standard manner. The 'ephemeris type' is H (rather than the otherwise universal 0). The xyz position and vx, vy, vz velocity are stored as 8-digit signed base-36 integers, hence a range of +/- 36^8 = about +/- 2.82x10^12. x, y, z are in meters, and hence cover a range +/- 18.9 AU. vx, vy, vz are in 10^-4 m/s, range +/- 94% c. The state vectors are in the geocentric ecliptic plane of date. See 'sdp4.cpp' for a discussion of how they're actually used. */ static double get_high_value( const char *iptr) { int64_t rval = 0; assert( *iptr == '+' || *iptr == '-'); if( *iptr == '+' || *iptr == '-') { int i, digit; for( i = 1; i < 9; i++) { digit = mutant_dehex( iptr[i]); assert( digit >= 0); rval = rval * (int64_t)36 + (int64_t)digit; } if( *iptr == '-') rval = -rval; } return( (double)rval); } /* Traditionally, NORAD numbers were stored as five digits. In 2020, new detectors threatened to go past 100K objects; the 'Alpha-5' scheme allows the first byte to be replaced by an uppercase letter, with I and O skipped. That gets us to 339999 : https://www.space-track.org/documentation#tle-alpha5 Note that Alpha-5 is referred to as a "stopgap". Near the bottom of the above link, "space-track.org encourages users to switch to... XML, KVN, or JSON", (partly) because these will handle nine-digit catalog numbers. To go beyond the Alpha-5 limit of 340000 possible numbers and store all nine-digit numbers in five bytes, I have added options 3 and 4 below. To do so, we need a 'base64'-like scheme, using all ten digits, 26 uppercase and 26 lowercase letters, and + and /. d = digit, L = uppercase letter, x = any base64 character X = non-digit base-64 character (1) ddddd = 'traditional' scheme provides 100000 combinations; Numbers 0 to 99999 (2) Ldddd = Alpha-5 scheme adds 240000 Numbers 100000 to 339999; A0000 to Z9999 (3) xxxxX = 64^4*54 = 905969664 more (start of 'Super-5' range) Numbers 340000 to 906309663; 0000A to ----- (4) xxxXd = 64^3*54*10 = 141557760 more Numbers 906309664 to 1047867423; 000A0 and up (going slightly past the billion we actually need) */ static int base64_to_int( const char c) { int offset; if( c >= 'A') { if( c <= 'Z') offset = 'A' - 10; else if( c >= 'a' && c <= 'z') offset = 'a' - 10 - 26; else return( -1); } else { if( c >= '0' && c <= '9') offset = '0'; else if( c == ' ') return( 0); else if( c == '+') return( 62); else if( c == '-') return( 63); else return( -1); } return( c - offset); } static int get_norad_number( const char *buff) { size_t i; int digits[5], rval = 0; for( i = 0; i < 5; i++) { digits[i] = base64_to_int( buff[i]); if( digits[i] == -1) /* not a valid number */ return( 0); } if( digits[4] > 9) /* case (3): last char is uppercase */ rval = 340000 + (digits[4] - 10) + 54 * (digits[3] + (digits[2] << 6) + (digits[1] << 12) + (digits[0] << 18)); else if( digits[3] > 9) /* case (4) above */ rval = 340000 + 905969664 + digits[4] + (digits[3] - 10) * 10 + 540 * (digits[2] + (digits[1] << 6) + (digits[0] << 12)); else /* last four digits are 0-9; 'standard' NORAD desig */ { for( i = 1; i <= 4; i++) assert( (buff[i] >= '0' && buff[i] <= '9') || buff[i] == ' '); if( *buff > 'I') { digits[0]--; if( *buff > 'O') digits[0]--; } rval = digits[0] * 10000 + atoi( buff + 1); } return( rval); } static inline double get_eight_places( const char *ptr) { return( (double)atoi( ptr) + (double)atoi(ptr + 4) * 1e-8); } /* Meteor 2-08 */ /* 1 13113U 88245.60005115 0.00000076 63463-4 0 5998 */ /* 2 13113 82.5386 288.0994 0015973 147.1294 213.0868 13.83869004325321 */ #define J2000 2451545.5 #define J1900 (J2000 - 36525. - 1.) /* parse_elements returns: 0 if the elements are parsed without error; 1 if they're OK except the first line has a checksum error; 2 if they're OK except the second line has a checksum error; 3 if they're OK except both lines have checksum errors; a negative value if the lines aren't at all parseable */ int DLL_FUNC parse_elements( const char *line1, const char *line2, tle_t *sat) { int rval, checksum_problem = 0; if( *line1 != '1' || *line2 != '2') rval = -4; else { rval = tle_checksum( line1); if( rval > 0) { checksum_problem = 1; /* there's a checksum problem, but it's */ rval = 0; /* not fatal; continue processing the TLE */ } } if( rval) rval -= 100; else { rval = tle_checksum( line2); if( rval > 0) { checksum_problem |= 2; /* there's a checksum problem, but it's */ rval = 0; /* not fatal; continue processing the TLE */ } } if( !rval) { char tbuff[13]; int year = line1[19] - '0'; if( line1[18] >= '0') year += (line1[18] - '0') * 10; if( year < 57) /* cycle around Y2K */ year += 100; sat->epoch = get_eight_places( line1 + 20) + J1900 + (double)( year * 365 + (year - 1) / 4); sat->norad_number = get_norad_number( line1 + 2); memcpy( tbuff, line1 + 64, 4); tbuff[4] = '\0'; sat->bulletin_number = atoi( tbuff); sat->classification = line1[7]; /* almost always 'U' */ memcpy( sat->intl_desig, line1 + 9, 8); if( !memcmp( sat->intl_desig, " ", 5)) { /* usually 'analyst' object w/o international (COSPAR) desig; */ int i, n = sat->norad_number; /* set launch 000, year/part */ /* data mapped from NORAD # */ for( i = 7; i > 4; i--, n /= 26) sat->intl_desig[i] = 'A' + n % 26; sat->intl_desig[2] = sat->intl_desig[3] = sat->intl_desig[4] = '0'; sat->intl_desig[1] = '0' + n % 10; sat->intl_desig[0] = '0' + n / 10; } sat->intl_desig[8] = '\0'; memcpy( tbuff, line2 + 63, 5); tbuff[5] = '\0'; sat->revolution_number = atoi( tbuff); sat->ephemeris_type = line1[62]; if( sat->ephemeris_type == 'H') { size_t i; double *state_vect = &sat->xincl; for( i = 0; i < 3; i++) { state_vect[i] = get_high_value( line1 + 33 + i * 10); state_vect[i + 3] = get_high_value( line2 + 33 + i * 10) * 1e-4; } return( 0); } sat->xmo = (double)get_angle( line2 + 43) * (PI / 180e+4); sat->xnodeo = (double)get_angle( line2 + 17) * (PI / 180e+4); sat->omegao = (double)get_angle( line2 + 34) * (PI / 180e+4); sat->xincl = (double)get_angle( line2 + 8) * (PI / 180e+4); sat->eo = atoi( line2 + 26) * 1.e-7; /* Make sure mean motion is null-terminated, since rev. no. may immediately follow. */ memcpy( tbuff, line2 + 51, 12); tbuff[12] = '\0'; /* Input mean motion, derivative of mean motion and second */ /* deriv of mean motion, are all in revolutions and days. */ /* Convert them here to radians and minutes: */ sat->xno = get_eight_places( tbuff) * TWOPI / MINUTES_PER_DAY; sat->xndt2o = (double)atoi( line1 + 35) * 1.e-8 * TWOPI / MINUTES_PER_DAY_SQUARED; if( line1[33] == '-') sat->xndt2o *= -1.; sat->xndd6o = sci( line1 + 44) * TWOPI / MINUTES_PER_DAY_CUBED; sat->bstar = sci( line1 + 53) * AE; } return( rval ? rval : checksum_problem); } ================================================ FILE: get_high.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ /* Code to extract elements for high-flying artsats. Give */ /* it the name of the input file of TLEs and a cutoff of */ /* the mean motion, and only TLEs with a lower motion */ /* will be output. */ #include #include #include #include #include "norad.h" int main( const int argc, const char **argv) { FILE *ifile = fopen((argc > 1 ? argv[1] : "all_tle.txt"), "rb"); FILE *ofile; char line0[200], line1[200], line2[200]; const double cutoff = (argc > 2 ? atof( argv[2]) : .6); const time_t t0 = time( NULL); if( !ifile) perror( "Input file not opened"); ofile = (argc > 3 ? fopen( argv[3], "a") : stdout); if( !ofile) perror( "Output file not opened"); if( !ifile || !ofile) return( -1); *line0 = *line1 = '\0'; fprintf( ofile, "# Added %.24s UTC\n", asctime( gmtime( &t0))); while( fgets( line2, sizeof( line2), ifile)) { if( *line2 == '2' && *line1 == '1' && !tle_checksum( line1) && !tle_checksum( line2) && atof( line2 + 52) < cutoff) fprintf( ofile, "%s%s%s", line0, line1, line2); strcpy( line0, line1); strcpy( line1, line2); } fclose( ifile); return( 0); } ================================================ FILE: get_vect.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. This code can read a TLE and compute a geocentric state vector, formatted such that Find_Orb can then read it in. I did this partly to test the hypothesis that if you compute a state vector from Space-Track TLEs at their epoch, you get the "actual" motion. That is to say, you could numerically integrate it to get a better result. This turns out not to be the case. Space-Track TLEs may be a best-fit to a set of observations or (as with my own TLEs) a best fit to a numerically integrated ephemeris, but there doesn't seem to be a way to improve them by doing a numerical integration. My second purpose was to be able to feed the state vector created by this program into Find_Orb as an initial orbit guess. For that purpose, it seems to work. You see large residuals as a result of the difference between numerical integration and SGP4/SDP4. But it gets you close enough that you can then do differential corrections (least-squares fitting). */ #include #include #include #include "norad.h" #include "watdefs.h" #include "afuncs.h" #include "date.h" #define PI \ 3.1415926535897932384626433832795028841971693993751058209749445923 int main( const int argc, const char **argv) { FILE *ifile; const char *filename = "all_tle.txt"; char line0[100], line1[100], line2[100]; int i; const char *norad = NULL, *intl = NULL; double jd = 0.; int is_j2000 = 1, is_equatorial = 1; for( i = 1; i < argc; i++) if( argv[i][0] == '-') { const char *arg = (i < argc - 1 && !argv[i][2] ? argv[i + 1] : argv[i] + 2); switch( argv[i][1]) { case 'n': norad = arg; printf( "Looking for NORAD %s\n", norad); break; case 'i': intl = arg; printf( "Looking for international ID %s\n", intl); break; case 't': jd = get_time_from_string( 0., arg, FULL_CTIME_YMD, NULL); break; case 'd': is_j2000 = 0; break; default: printf( "'%s': unrecognized option\n", argv[i]); return( -1); break; } } if( argc > 1 && argv[1][0] != '-') filename = argv[1]; ifile = fopen( filename, "rb"); if( !ifile) { fprintf( stderr, "Couldn't open '%s': ", filename); perror( ""); return( -1); } *line0 = *line1 = '\0'; while( fgets( line2, sizeof( line2), ifile)) { if( *line1 == '1' && (!norad || !memcmp( line1 + 2, norad, 5)) && (!intl || !memcmp( line1 + 9, intl, strlen( intl))) && *line2 == '2') { tle_t tle; const int err_code = parse_elements( line1, line2, &tle); if( err_code >= 0) { const int is_deep = select_ephemeris( &tle); double state[6], state_j2000[6], precess_matrix[9]; double params[N_SAT_PARAMS], t_since; const double epoch_tdt = tle.epoch + td_minus_utc( tle.epoch) / seconds_per_day; const double J2000 = 2451545.; double *state_to_show; if( !jd) jd = epoch_tdt; t_since = (jd - epoch_tdt) * minutes_per_day; if( is_deep) { SDP4_init( params, &tle); SDP4( t_since, &tle, params, state, state + 3); } else { SGP4_init( params, &tle); SGP4( t_since, &tle, params, state, state + 3); } if( strlen( line0) < 60) printf( "%s", line0); setup_precession( precess_matrix, 2000. + (jd - J2000) / 365.25, 2000.); precess_vector( precess_matrix, state, state_j2000); precess_vector( precess_matrix, state + 3, state_j2000 + 3); state_to_show = (is_j2000 ? state_j2000 : state); printf( " %.6f %.6s\n", jd, line1 + 9); printf( " %.5f %.5f %.5f 0408 # Ctr 3 km sec %s %s\n", state_to_show[0], state_to_show[1], state_to_show[2], is_equatorial ? "eq" : "ecl", is_j2000 ? "" : "of_date"); printf( " %.5f %.5f %.5f 0 0 0\n", state_to_show[3] / seconds_per_minute, state_to_show[4] / seconds_per_minute, state_to_show[5] / seconds_per_minute); } } strcpy( line0, line1); strcpy( line1, line2); } fclose( ifile); return( 0); } ================================================ FILE: line2.cpp ================================================ /* See LICENSE. NOTE that this has been obsoleted by the 'add_off.c' program in the 'lunar' repository (q.v.). The only use this code would have would be for a spacecraft getting astrometric data for which we don't have Horizons data. I don't expect that to happen. With that disclaimer : The Minor Planet Center accepts astrometry from spacecraft using a modification of their usual 80-column "punched-card" format. A second line is used to tell you where the spacecraft was, relative to the geocenter. https://minorplanetcenter.net/iau/info/SatelliteObs.html This code can reset those positions for Earth-orbiting spacecraft using TLEs ('two-line elements'; https://www.projectpluto.com/tle_info.htm.) This helps when accurate positions are not provided or completely trusted or appear to be bad. This code will read the 80-column astrometry and, when it finds a spacecraft position report (the "second line"), look through the TLE file for a matching TLE. If it finds one, it'll compute the spacecraft location for that time and modify the position accordingly. This was written specifically to handle a problem with a satellite in LEO. TLEs don't exist for heliocentric objects, but I may revise this code eventually to add positions for them (this could be done by, for example, requesting them from JPL Horizons). TLEs exist for TESS, computed by me, but some work would be required to make those actually function properly (the numbers are larger and the format is somewhat different as a result). So this really works, at present, only for (C51) WISE, (C52) Swift, and (C53) NEOSSat. */ #include #include #include #include #include #include #include "norad.h" #include "watdefs.h" #include "afuncs.h" #include "mpc_func.h" #include "stringex.h" #define PI \ 3.1415926535897932384626433832795028841971693993751058209749445923 int find_tle( tle_t *tle, const char *filename, const int norad_no) { FILE *ifile = fopen( filename, "rb"); char line0[100], line1[100], line2[100]; int rval = -1; if( !ifile) { fprintf( stderr, "Couldn't open TLE file '%s'\n", filename); exit( -1); } *line0 = *line1 = '\0'; while( rval && fgets( line2, sizeof( line2), ifile)) { if( *line1 == '1' && *line2 == '2' && atoi( line1 + 2) == norad_no) if( parse_elements( line1, line2, tle) >= 0) rval = 0; strlcpy_error( line0, line1); strlcpy_error( line1, line2); } fclose( ifile); if( rval) { fprintf( stderr, "Couldn't find TLEs for %5d in TLE file '%s'\n", norad_no, filename); exit( -1); } return( rval); } /* C49 = 29510 = 2006-047A = STEREO-A C50 = 29511 = 2006-047B = STEREO-B C51 = 36119 = 2009-071A = WISE * C52 = 28485 = 2004-047A = Swift * C53 = 39089 = 2013-009D = NEOSSat * C54 = 28928 = 2006-001A = New Horizons C55 = 34380 = 2009-011A = Kepler C56 = 41043 = 2015-070A = LISA-Pathfinder C57 = 43435 = 2018-038A = TESS * Note that only the asterisked objects are actually in earth orbit and therefore have TLEs. But I may try to figure out some way to add in positions for the heliocentric spacecraft later. */ static int mpc_code_to_norad_number( const char *mpc_code) { const char *codes = "C49 C50 C51 C52 C53 C54 C55 C56 C57 "; const int norad_numbers[] = { 29510, 29511, 36119, 28485, 39089, 28928, 34380, 41043, 43435 }; int i; for( i = 0; codes[i * 4]; i++) if( !memcmp( codes + i * 4, mpc_code, 3)) return( norad_numbers[i]); assert( 1); /* not supposed to ever happen, unless a new */ return( 0); /* satellite has been added */ } int main( const int argc, const char **argv) { const char *tle_filename = (argc > 2 ? argv[2] : "all_tle.txt"); const char *astrometry_filename = argv[1]; FILE *ifile = (argc > 1 ? fopen( astrometry_filename, "rb") : NULL); int is_deep = 0, curr_norad = 0; char buff[200]; double params[N_SAT_PARAMS]; const double J2000 = 2451545.; tle_t tle; if( argc > 1 && !ifile) fprintf( stderr, "'%s' not found : %s\n", astrometry_filename, strerror( errno)); if( !ifile) { fprintf( stderr, "'line2' takes as a command-line argument the name of the input\n" "astrometry file. It sends astrometry with corrected/added\n" "spacecraft locations to stdout.\n"); exit( -1); } while( fgets( buff, sizeof( buff), ifile)) { double jd; if( strlen( buff) > 80 && buff[14] == 's' && (jd = extract_date_from_mpc_report( buff, NULL)) > 2400000.) { double state[6], state_j2000[6], precess_matrix[9]; double t_since; const int norad_number = mpc_code_to_norad_number( buff + 77); int i; if( curr_norad != norad_number) { curr_norad = norad_number; find_tle( &tle, tle_filename, norad_number); is_deep = select_ephemeris( &tle); if( is_deep) SDP4_init( params, &tle); else SGP4_init( params, &tle); } t_since = (jd - tle.epoch) * minutes_per_day; if( is_deep) SDP4( t_since, &tle, params, state, state + 3); else SGP4( t_since, &tle, params, state, state + 3); setup_precession( precess_matrix, 2000. + (jd - J2000) / 365.25, 2000.); precess_vector( precess_matrix, state, state_j2000); precess_vector( precess_matrix, state + 3, state_j2000 + 3); for( i = 0; i < 3; i++) { char *tptr = buff + 34 + i * 12; snprintf_err( tptr, 12, "%11.4f", fabs( state_j2000[i])); *tptr = (state_j2000[i] > 0. ? '+' : '-'); tptr[11] = ' '; } } printf( "%s", buff); } fclose( ifile); return( 0); } ================================================ FILE: makefile ================================================ # GNU MAKE Makefile for artsat code and utilities # # Usage: make [W64=Y] [W32=Y] [MSWIN=Y] [tgt] # # where tgt can be any of: # [all|get_high|mergetle|obs_tes2|...] # # ...see below for complete list. Note that you have to 'make tle_date' and/or # 'make tle_date.cgi' separately; they have a dependency on the 'lunar' # library (also on my GitHub site). # # 'W64'/'W32' = cross-compile for 64- or 32-bit Windows, using MinGW, # on a Linux box # 'MSWIN' = compile for Windows, using MinGW and PDCurses, on a Windows machine # 'CC=clang' or 'CC=g++-4.8' = use clang or older GCC # (I've used gmake CLANG=Y on PC-BSD; probably works on OS/X too) # None of these: compile using default g++ on Linux, for Linux # # As CC is an implicit variable, a simple CC?=g++ doesn't work. # We have to use this trick from https://stackoverflow.com/a/42958970 ifeq ($(origin CC),default) CC=gcc CXX=g++ endif ifeq ($(shell uname -s),FreeBSD) CC=cc CXX=c++ endif EXE= RM=rm -f LIB_DIR=$(INSTALL_DIR)/lib ifdef W64 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ EXE=.exe LIB_DIR=$(INSTALL_DIR)/win_lib endif ifdef W32 CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ EXE=.exe LIB_DIR=$(INSTALL_DIR)/win_lib32 endif # I'm using 'mkdir -p' to avoid error messages if the directory exists. # It may fail on very old systems, and will probably fail on non-POSIX # systems. If so, change to '-mkdir' and ignore errors. ifeq ($(EXE),.exe) MKDIR=-mkdir else ZLIB=-lz MKDIR=mkdir -p endif # You can have your include files in ~/include and libraries in # ~/lib, in which case only the current user can use them; or # (with root privileges) you can install them to /usr/local/include # and /usr/local/lib for all to enjoy. PREFIX?=~ ifdef GLOBAL INSTALL_DIR=/usr/local else INSTALL_DIR=$(PREFIX) endif INCL=$(INSTALL_DIR)/include all: dropouts$(EXE) fake_ast$(EXE) fix_tles$(EXE) get_high$(EXE) \ line2$(EXE) mergetle$(EXE) obs_tes2$(EXE) obs_test$(EXE) \ out_comp$(EXE) sat_cgi$(EXE) sat_eph$(EXE) sat_id$(EXE) \ sat_id2$(EXE) sat_id3$(EXE) summarize$(EXE) \ test_des$(EXE) test_out$(EXE) test_sat$(EXE) test2$(EXE) tle2mpc$(EXE) CFLAGS+=-Wextra -Wall -O3 -pedantic -Wshadow ifdef UCHAR CFLAGS += -funsigned-char endif ifdef DEBUG CFLAGS += -g endif ifndef NO_ERRORS CFLAGS += -Werror endif clean: $(RM) *.o $(RM) dropouts$(EXE) $(RM) fake_ast$(EXE) $(RM) fix_tles$(EXE) $(RM) get_high$(EXE) $(RM) get_vect$(EXE) $(RM) libsatell.a $(RM) line2$(EXE) $(RM) mergetle$(EXE) $(RM) obs_tes2$(EXE) $(RM) obs_test$(EXE) $(RM) out_comp$(EXE) $(RM) sat_cgi$(EXE) $(RM) sat_eph$(EXE) $(RM) sat_id$(EXE) $(RM) sat_id2$(EXE) $(RM) sat_id3$(EXE) $(RM) summarize$(EXE) $(RM) test2$(EXE) $(RM) test_des$(EXE) $(RM) test_out$(EXE) $(RM) test_sat$(EXE) $(RM) tle2mpc$(EXE) $(RM) tle_date$(EXE) $(RM) tle_date.cgi install: $(MKDIR) $(LIB_DIR) cp libsatell.a $(LIB_DIR) cp norad.h $(INSTALL_DIR)/include $(MKDIR) $(INSTALL_DIR)/bin cp sat_id$(EXE) $(INSTALL_DIR)/bin install_lib: $(MKDIR) $(LIB_DIR) cp libsatell.a $(LIB_DIR) cp norad.h $(INSTALL_DIR)/include uninstall: rm $(INSTALL_DIR)/lib/libsatell.a rm $(INSTALL_DIR)/include/norad.h rm $(INSTALL_DIR)/bin/sat_id uninstall_lib: rm $(INSTALL_DIR)/lib/libsatell.a rm $(INSTALL_DIR)/include/norad.h OBJS= sgp.o sgp4.o sgp8.o sdp4.o sdp8.o deep.o basics.o get_el.o common.o tle_out.o get_high$(EXE): get_high.o get_el.o $(CC) $(CFLAGS) -o get_high$(EXE) get_high.o get_el.o mergetle$(EXE): mergetle.o $(CC) $(CFLAGS) -o mergetle$(EXE) mergetle.o -lm dropouts$(EXE): dropouts.o $(CC) $(CFLAGS) -o dropouts$(EXE) dropouts.o obs_tes2$(EXE): obs_tes2.o observe.o libsatell.a $(CC) $(CFLAGS) -o obs_tes2$(EXE) obs_tes2.o observe.o libsatell.a -lm obs_test$(EXE): obs_test.o observe.o libsatell.a $(CC) $(CFLAGS) -o obs_test$(EXE) obs_test.o observe.o libsatell.a -lm fake_ast$(EXE): fake_ast.o observe.o libsatell.a $(CC) $(CFLAGS) -o fake_ast$(EXE) fake_ast.o observe.o libsatell.a -lm fix_tles$(EXE): fix_tles.o libsatell.a $(CC) $(CFLAGS) -o fix_tles$(EXE) fix_tles.o libsatell.a -lm get_vect$(EXE): get_vect.cpp observe.o libsatell.a $(CXX) $(CFLAGS) -o get_vect$(EXE) -I $(INCL) get_vect.cpp observe.o libsatell.a -lm -L $(LIB_DIR) -llunar line2$(EXE): line2.cpp libsatell.a $(CXX) $(CFLAGS) -o line2$(EXE) -I $(INCL) line2.cpp libsatell.a -lm -L $(LIB_DIR) -llunar out_comp$(EXE): out_comp.o $(CC) $(CFLAGS) -o out_comp$(EXE) out_comp.o -lm libsatell.a: $(OBJS) rm -f libsatell.a ar rv libsatell.a $(OBJS) sat_eph$(EXE): sat_eph.c observe.o libsatell.a $(CC) $(CFLAGS) -o sat_eph$(EXE) -I $(INCL) sat_eph.c observe.o libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB) sat_cgi$(EXE): sat_eph.c observe.o libsatell.a $(CC) $(CFLAGS) -o sat_cgi$(EXE) -I $(INCL) sat_eph.c observe.o -DON_LINE_VERSION libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB) sat_id$(EXE): sat_id.cpp sat_util.o observe.o libsatell.a $(CXX) $(CFLAGS) -o sat_id$(EXE) -I $(INCL) sat_id.cpp sat_util.o observe.o libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB) sat_id2$(EXE): sat_id2.cpp sat_id.cpp sat_util.o observe.o libsatell.a $(CXX) $(CFLAGS) -o sat_id2$(EXE) -I $(INCL) -DON_LINE_VERSION sat_id2.cpp sat_id.cpp sat_util.o observe.o libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB) sat_id3$(EXE): sat_id3.cpp sat_id.cpp sat_util.o observe.o libsatell.a $(CXX) $(CFLAGS) -o sat_id3$(EXE) -I $(INCL) -DON_LINE_VERSION sat_id3.cpp sat_id.cpp sat_util.o observe.o libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB) summarize$(EXE): summarize.c observe.o libsatell.a $(CC) $(CFLAGS) -o summarize$(EXE) -I $(INCL) summarize.c observe.o libsatell.a -lm -L $(LIB_DIR) -llunar test2$(EXE): test2.o sgp.o libsatell.a $(CC) $(CFLAGS) -o test2$(EXE) test2.o sgp.o libsatell.a -lm tle_date$(EXE): tle_date.o $(CC) $(CFLAGS) -o tle_date$(EXE) tle_date.o -L $(LIB_DIR) -llunar -lm tle_date.o: tle_date.c $(CC) $(CFLAGS) -o tle_date.o -c -I../include tle_date.c tle_date.cgi: tle_date.c $(CC) $(CFLAGS) -o tle_date.cgi -I../include -DON_LINE_VERSION tle_date.c -L $(LIB_DIR) -llunar tle2mpc$(EXE): tle2mpc.cpp libsatell.a $(CXX) $(CFLAGS) -o tle2mpc$(EXE) -I $(INCL) tle2mpc.cpp libsatell.a -lm -L $(LIB_DIR) -llunar test_des$(EXE): test_des.o libsatell.a $(CC) $(CFLAGS) -o test_des$(EXE) test_des.o libsatell.a -lm test_out$(EXE): test_out.o tle_out.o get_el.o sgp4.o common.o $(CC) $(CFLAGS) -o test_out$(EXE) test_out.o tle_out.o get_el.o sgp4.o common.o -lm test_sat$(EXE): test_sat.o libsatell.a $(CC) $(CFLAGS) -o test_sat$(EXE) test_sat.o libsatell.a -lm .cpp.o: $(CXX) $(CFLAGS) -c $< .c.o: $(CC) $(CFLAGS) -c $< ================================================ FILE: mergetle.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. Code to read one or more files of TLEs, remove duplicates, and sort them according to various possible criteria (eccentricity, orbital period, etc.) */ #include #include #include #include #define TLE struct tle #define MAX_TLES 200000 /* As of late 2020, NORAD numbers are stored in five digits. There's some possibility of that being bumped up to nine digits, in which case I'll have to revisit this code. */ #define MAX_NORAD_NUMBER 100000 TLE { char name_line[80], line1[80], line2[80]; }; int n_duplicates = 0, heavens_above_html_tles = 0; int load_tles_from_file( FILE *ifile, TLE *tles, char *already_found) { int rval = 0; char buff[80], prev_line[80]; *prev_line = '\0'; while( fgets( buff, sizeof( buff), ifile)) { if( *buff == '1' && heavens_above_html_tles && !memcmp( buff + 69, "", 7)) strcpy( buff + 69, "\n"); if( *buff == '1' && strlen( buff) > 69 && buff[69] < ' ') { char buff2[80]; const int norad_number = atoi( buff + 2); if( fgets( buff2, sizeof( buff2), ifile)) if( *buff2 == '2' && strlen( buff2) > 69 && buff2[69] < ' ') { if( !already_found[norad_number]) { if( heavens_above_html_tles) /* can't use HA names */ *prev_line = '\0'; strcpy( tles[rval].name_line, prev_line); strcpy( tles[rval].line1, buff); strcpy( tles[rval].line2, buff2); already_found[norad_number] = 1; rval++; *buff = '\0'; } else n_duplicates++; } } strcpy( prev_line, buff); } return( rval); } FILE *test_fopen( const char *filename, const char *permits) { FILE *rval = fopen( filename, permits); if( !rval) { printf( "%s not opened\n", filename); exit( -1); } return( rval); } void show_tle( FILE *ofile, const TLE *tle) { fprintf( ofile, "%s", tle->name_line); fprintf( ofile, "%s", tle->line1); fprintf( ofile, "%s", tle->line2); } static double get_perigee( const TLE *tle) { const double ecc = atof( tle->line2 + 26) * 1e-7; const double revs_per_day = atof( tle->line2 + 52); const double semimajor = pow( revs_per_day, -2. / 3.); return( semimajor * (1. - ecc)); } static int compare_doubles( const char *buff1, const char *buff2) { const double d1 = atof( buff1); const double d2 = atof( buff2); int rval; if( d1 < d2) rval = -1; else if( d1 > d2) rval = 1; else rval = 0; return( rval); } int tle_compare( const TLE *tle1, const TLE *tle2, const char sort_method) { int i, rval = 0; switch( sort_method) { case 'n': case 'N': /* sort by NORAD number */ rval = atoi( tle1->line1 + 2) - atoi( tle2->line1 + 2); break; case 'c': case 'C': /* sort by COSPAR (international) desig */ if( tle1->line1[9] >= '5' && tle2->line1[9] < '5') rval = -1; else if( tle2->line1[9] >= '5' && tle1->line1[9] < '5') rval = 1; else /* COSPAR IDs are from the same century */ rval = memcmp( tle1->line1 + 9, tle2->line1 + 9, 8); break; case 'm': case 'M': /* sort by mean motion */ rval = compare_doubles( tle1->line2 + 52, tle2->line2 + 52); break; case 'e': case 'E': /* sort by eccentricity */ for( i = 26; !rval && i < 33; i++) rval = tle1->line2[i] - tle2->line2[i]; break; case 'p': case 'P': /* sort by epoch */ for( i = 18; !rval && i < 32; i++) rval = tle1->line1[i] - tle2->line1[i]; break; case 'i': case 'I': /* sort by incl */ rval = compare_doubles( tle1->line2 + 8, tle2->line2 + 8); break; case 'o': case 'O': /* sort by ascending node */ rval = compare_doubles( tle1->line2 + 17, tle2->line2 + 17); break; case 'q': { const double q1 = get_perigee( tle1); const double q2 = get_perigee( tle2); rval = ( q1 > q2 ? 1 : -1); } break; } if( sort_method >= 'A' && sort_method <= 'Z') rval = -rval; return( rval); } /* MS Windows lacks the re-entrant qsort_r; we have to use */ /* plain old non-re-entrant qsort and a global variable. */ /* BSD has it, but with the wrong order of arguments. */ #ifdef __linux #define HAVE_REENTRANT_QSORT #endif #ifdef HAVE_REENTRANT_QSORT int tle_compare_for_qsort_r( const void *a, const void *b, void *c) { return( tle_compare( (const TLE *)a, (const TLE *)b, *(char *)c)); } #else static char comparison_method; int tle_compare_for_qsort( const void *a, const void *b) { return( tle_compare( (const TLE *)a, (const TLE *)b, comparison_method)); } #endif static void error_exit( void) { printf( "'mergetle' will merge TLEs from one or more files. Optionally,\n"); printf( "the output can be sorted. For example:\n\n"); printf( "mergetle geosynch.tle molniya.tle visual.tle -se -oall.tle\n\n"); printf( "would create a file 'all.tle', containing elements from the three\n"); printf( "input .tle files, sorted by NORAD number. If a satellite appears\n"); printf( "in more than one .tle, the .tle from the first file on the command\n"); printf( "line is used. Options are:\n\n"); printf( "-sn, -sN Sort output by ascending/descending NORAD number\n"); printf( "-sc, -sC Sort output by ascending/descending COSPAR desig\n"); printf( "-sm, -sM Sort output by ascending/descending mean motion\n"); printf( "-se, -sE Sort output by ascending/descending eccentricity\n"); printf( "-sp, -sP Sort output by ascending/descending epoch\n"); printf( "-si, -sI Sort output by ascending/descending inclination\n"); printf( "-so, -sO Sort output by ascending/descending ascending node\n"); printf( "-sq Sort output by ascending perigee\n"); printf( "-o(filename) Set name of output .tle file (default is out.tle)\n"); printf( "-n Remove names from input TLEs\n"); printf( "-h Remove HTML tags from input. This allows you to extract\n"); printf( " TLEs from certain Web pages.\n"); exit( -1); } int main( const int argc, const char **argv) { TLE *tles = (TLE *)calloc( MAX_TLES, sizeof( TLE)); char *already_found = (char *)calloc( MAX_NORAD_NUMBER, sizeof( char)); int n_found = 0, i, strip_names = 0; char sort_method = 0; const char *output_filename = "out.tle"; FILE *ofile; if( argc < 2) error_exit( ); for( i = 1; i < argc; i++) if( argv[i][0] == '-') switch( argv[i][1]) { case 'o': output_filename = argv[i] + 2; break; case 's': sort_method = argv[i][2]; break; case 'n': strip_names = 1; printf( "Names will be removed in output\n"); break; case 'h': heavens_above_html_tles = 1; printf( "HTML tags will be removed from input\n"); break; default: printf( "Ignoring unknown option '%s'\n", argv[i]); break; } else { FILE *ifile = test_fopen( argv[i], "rb"); int n; n_duplicates = 0; n = load_tles_from_file( ifile, tles + n_found, already_found); printf( "%d TLEs added from %s, with %d duplicates found\n", n, argv[i], n_duplicates); n_found += n; fclose( ifile); } free( already_found); if( strip_names) for( i = 0; i < n_found; i++) tles[i].name_line[0] = '\0'; #ifndef HAVE_REENTRANT_QSORT comparison_method = sort_method; qsort( tles, n_found, sizeof( TLE), tle_compare_for_qsort); #else qsort_r( tles, n_found, sizeof( TLE), tle_compare_for_qsort_r, &sort_method); #endif ofile = test_fopen( output_filename, "wb"); for( i = 0; i < n_found; i++) show_tle( ofile, tles + i); } ================================================ FILE: msvc.mak ================================================ # Makefile for MSVC all: dropouts.exe fix_tles.exe line2.exe mergetle.exe obs_test.exe \ obs_tes2.exe out_comp.exe sat_eph.exe sat_id.exe \ test2.exe test_out.exe test_sat.exe tle2mpc.exe COMMON_FLAGS=-nologo -W3 -EHsc -c -FD -D_CRT_SECURE_NO_WARNINGS RM=del !ifdef BITS_32 BITS=32 !else BITS=64 !endif CFLAGS=-MT -O1 -D "NDEBUG" $(COMMON_FLAGS) LINK=link /nologo /stack:0x8800 OBJS= sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj \ basics.obj get_el.obj common.obj tle_out.obj dropouts.exe: dropouts.obj $(LINK) dropouts.obj fix_tles.exe: fix_tles.obj sat_code$(BITS).lib $(LINK) fix_tles.obj sat_code$(BITS).lib line2.exe: line2.obj observe.obj sat_code$(BITS).lib $(LINK) line2.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib mergetle.exe: mergetle.obj $(LINK) mergetle.obj obs_test.exe: obs_test.obj observe.obj sat_code$(BITS).lib $(LINK) obs_test.obj observe.obj sat_code$(BITS).lib obs_tes2.exe: obs_tes2.obj observe.obj sat_code$(BITS).lib $(LINK) obs_tes2.obj observe.obj sat_code$(BITS).lib out_comp.exe: out_comp.obj $(LINK) out_comp.obj sat_code$(BITS).lib: $(OBJS) del sat_code$(BITS).lib lib /OUT:sat_code$(BITS).lib $(OBJS) sat_id.exe: sat_id.obj sat_util.obj observe.obj sat_code$(BITS).lib $(LINK) sat_id.obj sat_util.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib sat_eph.exe: sat_eph.obj sat_util.obj observe.obj sat_code$(BITS).lib $(LINK) sat_eph.obj sat_util.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib test2.exe: test2.obj sat_code$(BITS).lib $(LINK) test2.obj sat_code$(BITS).lib test_out.exe: test_out.obj sat_code$(BITS).lib $(LINK) test_out.obj sat_code$(BITS).lib test_sat.exe: test_sat.obj sat_code$(BITS).lib $(LINK) test_sat.obj sat_code$(BITS).lib tle2mpc.exe: tle2mpc.obj observe.obj sat_code$(BITS).lib $(LINK) tle2mpc.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib .cpp.obj: cl $(CFLAGS) $< clean: del *.obj del *.exe del *.idb del sat_code$(BITS).lib install: copy norad.h ..\myincl copy sat_code$(BITS).lib ..\lib ================================================ FILE: msvc_dll.mak ================================================ # MSVC makefile for a DLL version # NOTE: hasn't been used or updated in years; some work required # before it would be fit for purpose. all: test2.exe test_sat.exe obs_test.exe obs_tes2.exe sat_id.exe test_out.exe sm_sat.dll out_comp.exe out_comp.exe: out_comp.cpp cl -W3 -Ox -nologo out_comp.cpp test2.exe: test2.obj sat_code.lib link test2.obj sat_code.lib test_sat.exe: test_sat.obj sat_code.lib cl -nologo test_sat.obj sat_code.lib test_out.exe: test_out.obj tle_out.obj sat_code.lib cl -nologo test_out.obj tle_out.obj sat_code.lib obs_test.exe: obs_test.obj observe.obj sat_code.lib cl -nologo obs_test.obj observe.obj sat_code.lib obs_tes2.exe: obs_tes2.obj observe.obj sat_code.lib cl -nologo obs_tes2.obj observe.obj sat_code.lib sat_id.exe: sat_id.obj sat_util.obj sat_code.lib cl -nologo sat_id.obj sat_util.obj sat_code.lib sat_code.lib: sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj \ basics.obj get_el.obj observe.obj common.obj del sat_code.lib del sat_code.dll link /DLL /IMPLIB:sat_code.lib /DEF:sat_code.def /MAP:sat_code.map \ sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj \ basics.obj get_el.obj observe.obj common.obj sm_sat.dll: sgp4.obj basics.obj get_el.obj common.obj del sm_sat.lib del sm_sat.dll link /DLL /IMPLIB:sm_sat.lib /DEF:sm_sat.def /MAP:sm_sat.map \ sgp4.obj basics.obj get_el.obj common.obj #CFLAGS=-W3 -c -LD -Ox -DRETAIN_PERTURBATION_VALUES_AT_EPOCH CFLAGS=-W3 -c -LD -Ox -nologo -D_CRT_SECURE_NO_WARNINGS .cpp.obj: cl $(CFLAGS) $< common.obj: sgp.obj: sgp4.obj: sgp8.obj: sdp4.obj: sdp8.obj: deep.obj: basics.obj: get_el.obj: observe.obj: test2.obj: test_out.obj: test_sat.obj: tle_out.obj: obs_test.obj: obs_tes2.obj: sat_id.obj: clean: del sat_code.dll del *.obj del *.exe del *.idb del sat_code.exp del sat_code.map del sat_code$(BITS).lib ================================================ FILE: norad.h ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ /* * norad.h v. 01.beta 03/17/2001 * * Header file for norad.c */ #ifndef NORAD_H #define NORAD_H 1 /* #define RETAIN_PERTURBATION_VALUES_AT_EPOCH 1 */ /* Two-line-element satellite orbital data */ typedef struct { double epoch, xndt2o, xndd6o, bstar; double xincl, xnodeo, eo, omegao, xmo, xno; int norad_number, bulletin_number, revolution_number; char classification; /* "U" = unclassified; only type I've seen */ char ephemeris_type; char intl_desig[9]; } tle_t; /* NOTE: xndt2o and xndt6o are used only in the "classic" SGP, */ /* not in SxP4 or SxP8. */ /* epoch is a Julian Day, UTC */ /* xmo = mean anomaly at epoch, radians */ /* xno = mean motion at epoch, radians/minute*/ #ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH #define DEEP_ARG_T_PARAMS 87 #else #define DEEP_ARG_T_PARAMS 81 #endif #define N_SGP_PARAMS 11 #define N_SGP4_PARAMS 30 #define N_SGP8_PARAMS 25 #define N_SDP4_PARAMS (10 + DEEP_ARG_T_PARAMS) #define N_SDP8_PARAMS (11 + DEEP_ARG_T_PARAMS) /* 94 = maximum possible size of the 'deep_arg_t' structure, in 8-byte units */ /* You can use the above constants to minimize the amount of memory used, but if you use the following constant, you can be assured of having enough memory for any of the five models: */ #define N_SAT_PARAMS (11 + DEEP_ARG_T_PARAMS) /* Byte 63 of the first line of a TLE contains the ephemeris type. The */ /* following five values are recommended, but it seems the non-zero */ /* values are only used internally; "published" TLEs all have type 0. */ /* However, I've had occasion to produce SGP4 TLEs for high-fliers, in */ /* cases where I couldn't get an SDP4 fit. */ #define TLE_EPHEMERIS_TYPE_DEFAULT 0 #define TLE_EPHEMERIS_TYPE_SGP 1 #define TLE_EPHEMERIS_TYPE_SGP4 2 #define TLE_EPHEMERIS_TYPE_SDP4 3 #define TLE_EPHEMERIS_TYPE_SGP8 4 #define TLE_EPHEMERIS_TYPE_SDP8 5 #define SXPX_DPSEC_INTEGRATION_ORDER 0 #define SXPX_DUNDEE_COMPLIANCE 1 #define SXPX_ZERO_PERTURBATIONS_AT_EPOCH 2 /* SDP4 and SGP4 can return zero, or any of the following error/warning codes. The 'warnings' result in a mathematically reasonable value being returned, and perigee within the earth is completely reasonable for an object that's just left the earth or is about to hit it. The 'errors' mean that no reasonable position/velocity was determined. */ #define SXPX_ERR_NEARLY_PARABOLIC -1 #define SXPX_ERR_NEGATIVE_MAJOR_AXIS -2 #define SXPX_WARN_ORBIT_WITHIN_EARTH -3 #define SXPX_WARN_PERIGEE_WITHIN_EARTH -4 #define SXPX_ERR_NEGATIVE_XN -5 #define SXPX_ERR_CONVERGENCE_FAIL -6 /* Function prototypes */ /* norad.c */ /* The Win32 version can be compiled to make a .DLL, if the */ /* functions are declared to be of type __stdcall... _and_ the */ /* functions must be declared to be extern "C", something I */ /* overlooked and added 24 Sep 2002. The DLL_FUNC macro lets */ /* this coexist peacefully with other OSes. */ #ifdef _WIN32 #define DLL_FUNC __stdcall #else #define DLL_FUNC #endif #ifdef __cplusplus extern "C" { #endif void DLL_FUNC SGP_init( double *params, const tle_t *tle); int DLL_FUNC SGP( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel); void DLL_FUNC SGP4_init( double *params, const tle_t *tle); int DLL_FUNC SGP4( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel); void DLL_FUNC SGP8_init( double *params, const tle_t *tle); int DLL_FUNC SGP8( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel); void DLL_FUNC SDP4_init( double *params, const tle_t *tle); int DLL_FUNC SDP4( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel); void DLL_FUNC SDP8_init( double *params, const tle_t *tle); int DLL_FUNC SDP8( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel); int DLL_FUNC select_ephemeris( const tle_t *tle); int DLL_FUNC parse_elements( const char *line1, const char *line2, tle_t *sat); int DLL_FUNC tle_checksum( const char *buff); void DLL_FUNC write_elements_in_tle_format( char *buff, const tle_t *tle); void DLL_FUNC sxpx_set_implementation_param( const int param_index, const int new_param); void DLL_FUNC sxpx_set_dpsec_integration_step( const double new_step_size); void DLL_FUNC lunar_solar_position( const double jd, double *lunar_xyzr, double *solar_xyzr); #ifdef __cplusplus } /* end of 'extern "C"' section */ #endif /* Following are in 'dynamic.cpp', for C/C++ programs that want */ /* to load 'sat_code.dll' and use its functions at runtime. They */ /* only make sense in the Win32 world: */ #ifdef _WIN32 int SXPX_init( double *params, const tle_t *tle, const int sxpx_num); int SXPX( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel, const int sxpx_num); #endif #endif ================================================ FILE: norad_in.h ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #ifndef NORAD_IN_H #define NORAD_IN_H /* Common "internal" arguments between deep-space functions; users of */ /* the satellite routines shouldn't need to bother with any of this */ typedef struct { double /* Common between SGP4 and SDP4: */ aodp, cosio, sinio, omgdot, xmdot, xnodot, xnodp, /* Used by dpinit part of Deep() */ eosq, betao, cosio2, sing, cosg, betao2, /* Used by dpsec and dpper parts of Deep() */ xll, omgadf, xnode, em, xinc, xn, t, /* 'd####' secular coeffs for 12-hour, e>.5 orbits: */ d2201, d2211, d3210, d3222, d4410, d4422, d5220, d5232, d5421, d5433, /* formerly static to Deep( ), but more logically part of this struct: */ atime, del1, del2, del3, e3, ee2, omegaq, pe, pgh, ph, pinc, pl, preep, savtsn, se2, se3, sgh2, sgh3, sgh4, sh2, sh3, si2, si3, sl2, sl3, sl4, sse, ssg, ssh, ssi, ssl, thgr, xfact, xgh2, xgh3, xgh4, xh2, xh3, xi2, xi3, xl2, xl3, xl4, xlamo, xli, xni, xnq, zmol, zmos; /* Epoch offsets, described by Rob Matson, added by BJG, */ /* then commented out; I don't think they really ought to */ /* be used... */ #ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH double pe0, pinc0, pl0, pgh0, ph0; int solar_lunar_init_flag; #endif int resonance_flag, synchronous_flag; } deep_arg_t; double FMod2p( const double x); void Deep_dpinit( const tle_t *tle, deep_arg_t *deep_arg); void Deep_dpsec( const tle_t *tle, deep_arg_t *deep_arg); void Deep_dpper( const tle_t *tle, deep_arg_t *deep_arg); int sxpx_posn_vel( const double xnode, const double a, const double e, const double cosio, const double sinio, const double xincl, const double omega, const double xl, double *pos, double *vel); typedef struct { double coef, coef1, tsi, s4, unused_a3ovk2, eta; } init_t; void sxpx_common_init( double *params, const tle_t *tle, init_t *init, deep_arg_t *deep_arg); /* Table of constant values */ #define pi 3.141592653589793238462643383279502884197 #define twopi (pi*2.) #define e6a 1.0E-6 #define two_thirds (2. / 3.) #define xj3 -2.53881E-6 #define minus_xj3 2.53881E-6 #define earth_radius_in_km 6378.135 #ifndef minutes_per_day #define minutes_per_day 1440. #endif #define ae 1.0 #define xj2 1.082616e-3 #define ck2 (.5 * xj2 * ae * ae) /* xke^2 = earth GM, in (earth radii)^3/minutes^2. */ #ifdef OLD_CONSTANTS #define ck4 6.209887E-7 #define s 1.012229 #define qoms2t 1.880279E-09 #define xke 7.43669161E-2 #else #define xj4 (-1.65597e-6) #define ck4 (-.375 * xj4 * ae * ae * ae * ae) #define s_const (ae * (1. + 78. / earth_radius_in_km)) #define qoms2t 1.880279159015270643865e-9 #define xke 0.0743669161331734132 #endif #define a3ovk2 (minus_xj3/ck2*ae*ae*ae) #endif /* #ifndef NORAD_IN_H */ ================================================ FILE: nu2vect.c ================================================ /* Code to read in the Spektr-RG state vectors from .nu files at ftp://ftp.kiam1.rssi.ru/pub/gps/spectr-rg/nu/ and output into a form ingestible by Find_Orb. (Note that these haven't been updated for a while; we've been tracking Spektr-RG solely through observed astrometry.) Input lines give 0.000 (always zero, means J2000) 1 (loop number; ignore) 2.021030100000E+7 ( = 20210301 = 2021 Mar 01) 1.649335000000E+05 ( = 16:49:33.5 Moscow time (three hours ahead of UTC)) -1.190093387974E+03 (x-coord, geocentric, equatorial J2000, in thousands of km) 9.733125144680E+02 (y-coord, same system) 4.956578219661E+02 (z-coord, same system) -7.639675611173E-02 (x-velocity, in km/s) -6.678070845750E-02 (y-velocity) -2.372125806637E-01 (z-velocity) 0.000000000000E+00 (ballistic coefficient) 1.246012245177E-05 (solar radiation coefficient, unitless) Note that the file name gives the UTC, and the time given within the file is three hours ahead of that. The positions match those determined by optical astrometry to within a few kilometers. The above solar radiation coefficient means that the sun's gravity is counteracted by a force 1.24601e-5 times as great. It corresponds approximately to an area/mass ratio of 0.015 m^2/kg, which is quite close to what we've been getting from the optical astrometry orbit solution. Compile with gcc -Wextra -Wall -O3 -pedantic nu2vect.c -I../include -L../lib -o nu2vect -llunar Python code to convert .nu files to STK ephemeris files is at https://github.com/Satsir/STK/blob/main/nuToEph.py */ #include #include #include #include #include "stringex.h" int main( const int argc, const char **argv) { FILE *ifile = (argc == 2 ? fopen( argv[1], "rb") : NULL); char buff[90]; char command[200]; int i; if( !ifile) fprintf( stderr, "See comments at start of 'nu2vect.c' for usage\n"); assert( ifile); for( i = 0; i < 10; i++) if( fgets( buff, sizeof( buff), ifile)) { const double ival = atof( buff); switch( i) { case 2: { const int day = (int)ival; snprintf_err( buff, sizeof( buff), "%04d-%02d-%02d", day / 10000, (day / 100) % 100, day % 100); printf( "Date : %s\n", buff); snprintf_err( command, sizeof( command), "find_orb \"-oSpektr-RG = 2019-040A = NORAD 44432\" -v%sT", buff); } break; case 3: { const int millisec = (int)( ival * 1000.); snprintf_err( buff, sizeof( buff), "%02d:%02d:%02d.%03d", millisec / 10000000, (millisec / 100000) % 100, (millisec / 1000) % 100, millisec % 100); printf( "Time : %s\n", buff); strlcat_error( command, buff); strlcat_error( command, "-3h"); /* correction for Moscow time */ } break; case 4: case 5: case 6: printf( " Posn %f km\n", ival * 1000.); snprintf_append( command, sizeof( command), ",%.2f", ival * 1000.); break; case 7: case 8: case 9: printf( " Vel %f km/s\n", ival); snprintf_append( command, sizeof( command), ",%.7f", ival); break; break; } } fclose( ifile); printf( "%s,eq,geo,km,s\n", command); return( 0); } ================================================ FILE: nu_readme.txt ================================================ (Translation/additions to ftp://ftp.kiam1.rssi.ru/pub/gps/spectr-rg/nu/00_read.me . This also applies to the files for Spektr-R in the ftp://ftp.kiam1.rssi.ru/pub/gps/spectr-r/nu/ directory.) Format of .nu files : Line 1: 0.000000000000E+00 // 0 = coordinates are in J2000, geocentric, equatorial Line 2: 1.000000000000E+00 // orbit number (always zero for Spektr-RG) Line 3: 2.011111500000E+07 // Epoch date, YYYYMMDD (example is 2011 11 15) Line 4: 1.492674329226E+04 // Epoch time, HH:MM:SS (example is 01:49:26.74329226) Line 5: 5.174588077311E+00 // x-coordinate, in thousands of kilometers Line 6: -2.277098498781E+00 // y-coordinate Line 7: -3.461034587339E+00 // z-coordinate Line 8: 4.762109598043E+00 // x-velocity, in kilometers/second Line 9: 4.109503042673E+00 // y-velocity Line 10: 4.560902129453E+00 // z-velocity Line 11: 3.000000000000E-02 // Ballistic coefficient, m^3/seconds^2/kilograms (?) Line 12: 1.248000000000E-05 // Solar radiation coefficient, unitless For Spektr-R, the orbit number is non-zero and gives the number of completed orbits. It is not defined for Spektr-RG. Note that the file name gives the epoch in UTC. The epoch given within the file is three hours ahead of that (Moscow time). The positions match those determined by optical astrometry to within about 20 kilometers. It is possible that the epoch is in TT (plus three hours); the difference is below what I can detect. The above solar radiation coefficient means that the sun's gravity is counteracted by a force 1.248e-5 times as great. It corresponds approximately to an area/mass ratio of 0.015 m^2/kg, which is quite close to what we've been getting from the optical astrometry orbit solution. ================================================ FILE: obs_tes2.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ /* obs_tes2.cpp 12 December 2002 (Revised slightly December 2012 to fix compiler warning errors.) An example 'main' function illustrating how to find which satellite(s) are within a given radius of a given RA/dec, as seen from a given point. The code reads in a TLE file (name provided as the first command-line argument). Details of the observer position, search radius, date/time, and RA/dec are provided on the command line. For example: obs_tes2 alldat.tle -l44.01,-69.9,10 -p90,30 -j2452623.5 -r10 would hunt through the TLE element file 'alldat.tle' for satellites visible from latitude +44.01, longitude -69.9, altitude 10 metres; at RA=90 degrees (6h), dec=+30; on JD 2452623.5 (UTC); within a ten-degree search radius. (All of these are the default values.) The output looks like this: NORAD Int'l RA (J2000) dec Delta Radius PA Speed 08593U 74089DG 88.7235 22.9622 2293.0 7.15 225 10.75 15830U 85049D 82.5051 34.9711 32143.6 8.99 34 0.24 17642U 81053LQ 88.1471 32.6585 1428.5 3.24 213 17.60 21833U 91088A 80.6400 27.9649 36216.6 9.58 87 0.17 ...with 'delta'=distance to satellite in km, 'radius'=angular distance in degrees from the search point, 'PA' = position angle of motion, 'Speed' = apparent angular rate of motion in arcminutes/second (or degrees/minute). */ #include #include #include #include #include "norad.h" #include "observe.h" #define PI 3.141592653589793238462643383279 #define TIME_EPSILON (1./86400.) int main( const int argc, const char **argv) { const char *tle_file_name = ((argc == 1) ? "alldat.tle" : argv[1]); FILE *ifile = fopen( tle_file_name, "rb"); char line1[100], line2[100]; double lat = 44.01, lon = -69.9, ht_in_meters = 10.; double jd = 2452623.5; /* 15 Dec 2002 0h UT */ double search_radius = 10.; /* default to ten-degree search */ double target_ra = 90., target_dec = 30.; /* default search is at RA=6h, dec=+30 */ double rho_sin_phi, rho_cos_phi, observer_loc[3], observer_loc2[3]; int i, header_line_shown = 0; if( !ifile) { printf( "Couldn't open input file %s\n", tle_file_name); exit( -1); } for( i = 1; i < argc; i++) if( argv[i][0] == '-') switch( argv[i][1]) { case 'l': sscanf( argv[i] + 2, "%lf,%lf,%lf", &lat, &lon, &ht_in_meters); break; case 'p': sscanf( argv[i] + 2, "%lf,%lf", &target_ra, &target_dec); break; case 'j': jd = atof( argv[i] + 2); break; case 'r': search_radius = atof( argv[i] + 2); break; default: printf( "Unrecognized command-line option '%s'\n", argv[i]); exit( -2); break; } /* Figure out where the observer _really_ is, in Cartesian */ /* coordinates of date: */ earth_lat_alt_to_parallax( lat * PI / 180., ht_in_meters, &rho_cos_phi, &rho_sin_phi); observer_cartesian_coords( jd, lon * PI / 180., rho_cos_phi, rho_sin_phi, observer_loc); observer_cartesian_coords( jd + TIME_EPSILON, lon * PI / 180., rho_cos_phi, rho_sin_phi, observer_loc2); target_ra *= PI / 180.; target_dec *= PI / 180.; if( fgets( line1, sizeof( line1), ifile)) while( fgets( line2, sizeof( line2), ifile)) { tle_t tle; /* Structure for two-line elements set for satellite */ if( !parse_elements( line1, line2, &tle)) /* hey! we got a TLE! */ { int is_deep = select_ephemeris( &tle); double sat_params[N_SAT_PARAMS], radius, d_ra, d_dec; double ra, dec, dist_to_satellite, t_since; double pos[3]; /* Satellite position vector */ double unused_delta2; t_since = (jd - tle.epoch) * 1440.; if( is_deep) { SDP4_init( sat_params, &tle); SDP4( t_since, &tle, sat_params, pos, NULL); } else { SGP4_init( sat_params, &tle); SGP4( t_since, &tle, sat_params, pos, NULL); } get_satellite_ra_dec_delta( observer_loc, pos, &ra, &dec, &dist_to_satellite); epoch_of_date_to_j2000( jd, &ra, &dec); d_ra = (ra - target_ra + PI * 4.); while( d_ra > PI) d_ra -= PI + PI; d_dec = dec - target_dec; radius = sqrt( d_ra * d_ra + d_dec * d_dec) * 180. / PI; if( radius < search_radius) /* good enough for us! */ { double speed, posn_ang_of_motion; line1[16] = '\0'; if( !header_line_shown) { printf( "NORAD Int'l RA (J2000) dec Delta Radius PA Speed\n"); header_line_shown = 1; } /* Compute position one second later, so we */ /* can show speed/PA of motion: */ t_since += TIME_EPSILON * 1440.; if( is_deep) SDP4( t_since, &tle, sat_params, pos, NULL); else SGP4( t_since, &tle, sat_params, pos, NULL); get_satellite_ra_dec_delta( observer_loc2, pos, &d_ra, &d_dec, &unused_delta2); epoch_of_date_to_j2000( jd, &d_ra, &d_dec); d_ra -= ra; d_dec -= dec; while( d_ra > PI) d_ra -= PI + PI; while( d_ra < -PI) d_ra += PI + PI; d_ra *= cos( dec); posn_ang_of_motion = atan2( d_ra, d_dec); if( posn_ang_of_motion < 0.) posn_ang_of_motion += PI + PI; speed = sqrt( d_ra * d_ra + d_dec * d_dec) * 180. / PI; /* Put RA into 0 to 2pi range: */ ra = fmod( ra + PI * 10., PI + PI); printf( "%s %8.4f %8.4f %8.1f %5.2f %3d %5.2f\n", line1 + 2, ra * 180. / PI, dec * 180. / PI, dist_to_satellite, radius, (int)(posn_ang_of_motion * 180 / PI), speed * 60.); /* "Speed" is displayed in arcminutes/second, or in degrees/minute */ } } strcpy( line1, line2); } fclose( ifile); return( 0); } /* End of main() */ ================================================ FILE: obs_test.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ /* obs_test.cpp 23 September 2002 (Revised slightly December 2012 to fix compiler warning errors.) An example 'main' function illustrating how to get topocentric ephemerides for artificial satellites, using the basic satellite code plus the add-on topocentric functions. The code reads the file 'obs_test.txt', getting commands setting the observer lat/lon and altitude and time of observation. When it gets a command setting a particular JD, it computes the topocentric RA/dec/dist and prints them out. At present, 'obs_test.txt' sets up a lat/lon/height in Bowdoinham, Maine, corporate headquarters of Project Pluto, and computes the position of one low-orbit satellite (ISS) and one high-orbit satellite (Cosmos 1966 rocket booster). You should get : Near-Earth type Ephemeris (SGP4) selected: Object 25544U 98067A, as seen from lat 44.01000 lon -69.90000, JD 2452541.50000 RA 350.1615 (J2000) dec -24.0241 dist 1867.97481 km Deep-Space type Ephemeris (SDP4) selected: Object 19448U 88076D, as seen from lat 44.01000 lon -69.90000, JD 2452541.50000 RA 3.5743 (J2000) dec 30.4293 dist 32114.83370 km */ #include #include #include #include "norad.h" #include "observe.h" #define PI 3.141592653589793238462643383279 int main( const int argc, const char **argv) { FILE *ifile = fopen( (argc == 1) ? "obs_test.txt" : argv[1], "rb"); tle_t tle; /* Pointer to two-line elements set for satellite */ char line1[100], line2[100]; double lat = 0., lon = 0., ht_in_meters = 0., jd = 0.; int ephem = 1; /* default to SGP4 */ if( !ifile) { printf( "Couldn't open input OBS_TEST.TXT file\n"); exit( -1); } if( fgets( line1, sizeof( line1), ifile)) while( fgets( line2, sizeof( line2), ifile)) { int err_val; if( !memcmp( line2, "Ephem ", 6)) ephem = (line2[6] - '0'); else if( !memcmp( line2, "JD ", 3)) jd = atof( line2 + 3); else if( !memcmp( line2, "ht ", 3)) ht_in_meters = atof( line2 + 3); else if( !memcmp( line2, "lat ", 4)) lat = atof( line2 + 4) * PI / 180.; /* cvt degrees to radians */ else if( !memcmp( line2, "lon ", 4)) lon = atof( line2 + 4) * PI / 180.; else if( (err_val = parse_elements( line1, line2, &tle)) >= 0) { /* hey! we got a TLE! */ int is_deep = select_ephemeris( &tle); const char *ephem_names[5] = { "SGP ", "SGP4", "SGP8", "SDP4", "SDP8" }; double sat_params[N_SAT_PARAMS], observer_loc[3]; double rho_sin_phi, rho_cos_phi; double ra, dec, dist_to_satellite, t_since; double pos[3]; /* Satellite position vector */ if( err_val) printf( "WARNING: TLE parsing error %d\n", err_val); earth_lat_alt_to_parallax( lat, ht_in_meters, &rho_cos_phi, &rho_sin_phi); observer_cartesian_coords( jd, lon, rho_cos_phi, rho_sin_phi, observer_loc); if( is_deep && (ephem == 1 || ephem == 2)) ephem += 2; /* switch to an SDx */ if( !is_deep && (ephem == 3 || ephem == 4)) ephem -= 2; /* switch to an SGx */ if( is_deep) printf("Deep-Space type Ephemeris (%s) selected:\n", ephem_names[ephem]); else printf("Near-Earth type Ephemeris (%s) selected:\n", ephem_names[ephem]); /* Calling of NORAD routines */ /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8) */ /* will be called in turn with the appropriate TLE set */ t_since = (jd - tle.epoch) * 1440.; switch( ephem) { case 0: SGP_init( sat_params, &tle); err_val = SGP( t_since, &tle, sat_params, pos, NULL); break; case 1: SGP4_init( sat_params, &tle); err_val = SGP4( t_since, &tle, sat_params, pos, NULL); break; case 2: SGP8_init( sat_params, &tle); err_val = SGP8( t_since, &tle, sat_params, pos, NULL); break; case 3: SDP4_init( sat_params, &tle); err_val = SDP4( t_since, &tle, sat_params, pos, NULL); break; case 4: SDP8_init( sat_params, &tle); err_val = SDP8( t_since, &tle, sat_params, pos, NULL); break; default: printf( "? How did we get here? ephem = %d\n", ephem); err_val = 0; break; } if( err_val) printf( "Ephemeris error %d\n", err_val); line1[15] = '\0'; printf( "Object %s, as seen from lat %.5f lon %.5f, JD %.5f\n", line1 + 2, lat * 180. / PI, lon * 180. / PI, jd); get_satellite_ra_dec_delta( observer_loc, pos, &ra, &dec, &dist_to_satellite); epoch_of_date_to_j2000( jd, &ra, &dec); printf( "RA %.4f (J2000) dec %.4f dist %.5f km\n", ra * 180. / PI, dec * 180. / PI, dist_to_satellite); } strcpy( line1, line2); } fclose( ifile); return( 0); } /* End of main() */ ================================================ FILE: obs_test.txt ================================================ # Input file for 'obs_test' # Set up desired lat/lon/ht in meters/JD, specify a TLE, and # the topocentric RA/dec/distance will be shown. lat 44.01 lon -69.9 # Western longitudes are negative, eastern positive. The above # lat/lon corresponds to my location in Bowdoinham, Maine, in the # northeastern United States. ht 100 JD 2452541.5 /* 24 Sep 2002 0h UT */ ISS 1 25544U 98067A 02256.70033192 .00045618 00000-0 57184-3 0 1499 2 25544 51.6396 328.6851 0018421 253.2171 244.7656 15.59086742217834 # Above should give RA = 350.1615 deg, dec = -24.0241, dist = 1867.97542 km # Now compute a second, higher satellite for the same place/time: Cosmos 1966 Rk 1 19448U 88076D 02255.52918163 -.00000002 00000-0 10000-3 0 4873 2 19448 65.7943 338.1906 7142558 193.4853 125.7046 2.04085818104610 # Above should give RA = 3.5743, dec = 30.4293, dist = 32114.83063 km ================================================ FILE: observe.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include "observe.h" /* Assorted functions useful in conjunction with the satellite code library for determining where the _observer_, as well as the _target_, happens to be. Combine the two positions, and you can get the distance/RA/dec of the target as seen by the observer. */ #define PI 3.141592653589793238462643383279 #define EARTH_MAJOR_AXIS 6378140. #define EARTH_MINOR_AXIS 6356755. #define EARTH_AXIS_RATIO (EARTH_MINOR_AXIS / EARTH_MAJOR_AXIS) /* function for Greenwich sidereal time, ripped from 'deep.cpp' */ static inline double ThetaG( double jd) { /* Reference: The 1992 Astronomical Almanac, page B6. */ const double omega_E = 1.00273790934; /* Earth rotations per sidereal day (non-constant) */ const double UT = fmod( jd + .5, 1.); const double seconds_per_day = 86400.; const double jd_2000 = 2451545.0; /* 1.5 Jan 2000 = JD 2451545. */ double t_cen, GMST, rval; t_cen = (jd - UT - jd_2000) / 36525.; GMST = 24110.54841 + t_cen * (8640184.812866 + t_cen * (0.093104 - t_cen * 6.2E-6)); GMST = fmod( GMST + seconds_per_day * omega_E * UT, seconds_per_day); if( GMST < 0.) GMST += seconds_per_day; rval = 2. * PI * GMST / seconds_per_day; return( rval); } /*Function thetag*/ void DLL_FUNC observer_cartesian_coords( const double jd, const double lon, const double rho_cos_phi, const double rho_sin_phi, double *vect) { const double angle = lon + ThetaG( jd); *vect++ = cos( angle) * rho_cos_phi * EARTH_MAJOR_AXIS / 1000.; *vect++ = sin( angle) * rho_cos_phi * EARTH_MAJOR_AXIS / 1000.; *vect++ = rho_sin_phi * EARTH_MAJOR_AXIS / 1000.; } void DLL_FUNC earth_lat_alt_to_parallax( const double lat, const double ht_in_meters, double *rho_cos_phi, double *rho_sin_phi) { const double u = atan( sin( lat) * EARTH_AXIS_RATIO / cos( lat)); *rho_sin_phi = EARTH_AXIS_RATIO * sin( u) + (ht_in_meters / EARTH_MAJOR_AXIS) * sin( lat); *rho_cos_phi = cos( u) + (ht_in_meters / EARTH_MAJOR_AXIS) * cos( lat); } void DLL_FUNC get_satellite_ra_dec_delta( const double *observer_loc, const double *satellite_loc, double *ra, double *dec, double *delta) { double vect[3], dist2 = 0.; int i; for( i = 0; i < 3; i++) { vect[i] = satellite_loc[i] - observer_loc[i]; dist2 += vect[i] * vect[i]; } *delta = sqrt( dist2); *ra = atan2( vect[1], vect[0]); if( *ra < 0.) *ra += PI + PI; *dec = asin( vect[2] / *delta); } /* Formulae from Meeus' _Astronomical Algorithms_ for approximate precession. More than accurate enough for our purposes. */ static void precess( const double t_centuries, double *ra, double *dec) { const double m = (3.07496 + .00186 * t_centuries / 2.) * (PI / 180.) / 240.; const double n = (1.33621 - .00057 * t_centuries / 2.) * (PI / 180.) / 240.; const double ra_rate = m + n * sin( *ra) * tan( *dec); const double dec_rate = n * cos( *ra); *ra -= t_centuries * ra_rate * 100.; *dec -= t_centuries * dec_rate * 100.; } void DLL_FUNC epoch_of_date_to_j2000( const double jd, double *ra, double *dec) { const double t_centuries = (jd - 2451545.) / 36525.; precess( t_centuries, ra, dec); } void DLL_FUNC j2000_to_epoch_of_date( const double jd, double *ra, double *dec) { const double t_centuries = (jd - 2451545.) / 36525.; precess( -t_centuries, ra, dec); } ================================================ FILE: observe.h ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #ifdef _WIN32 #define DLL_FUNC __stdcall #else #define DLL_FUNC #endif #ifdef __cplusplus extern "C" { #endif void DLL_FUNC earth_lat_alt_to_parallax( const double lat, const double ht_in_meters, double *rho_cos_phi, double *rho_sin_phi); void DLL_FUNC observer_cartesian_coords( const double jd, const double lon, const double rho_cos_phi, const double rho_sin_phi, double *vect); void DLL_FUNC get_satellite_ra_dec_delta( const double *observer_loc, const double *satellite_loc, double *ra, double *dec, double *delta); void DLL_FUNC epoch_of_date_to_j2000( const double jd, double *ra, double *dec); void DLL_FUNC j2000_to_epoch_of_date( const double jd, double *ra, double *dec); #ifdef __cplusplus } /* end of 'extern "C"' section */ #endif ================================================ FILE: out_comp.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include #include #include static int is_position_line( const char *buff) { int rval = 0; if( strlen( buff) > 73 && buff[73] < ' ' && buff[29] == '.' && buff[46] == '.' && buff[63] == '.') rval = 1; return( rval); } int main( const int argc, const char **argv) { FILE *ifile1 = fopen( argv[1], "rb"); FILE *ifile2 = fopen( argv[2], "rb"); char buff1[80], buff2[80]; int line = 0, n_valid = 0, worst_line = -1; double max_diff2 = 0.; if( !ifile1) printf( "%s not opened\n", argv[1]); if( !ifile2) printf( "%s not opened\n", argv[2]); if( argc < 3 || !ifile1 || !ifile2) { printf( "out_comp needs two files of output from test_sat to compare.\n"); exit( -1); } while( fgets( buff1, sizeof( buff1), ifile1) && fgets( buff2, sizeof( buff2), ifile2)) { line++; if( is_position_line( buff1) && is_position_line( buff2)) { double diff2 = 0., delta; int i; n_valid++; for( i = 22; i < 60; i += 17) { delta = atof( buff1 + i) - atof( buff2 + i); diff2 += delta * delta; } if( diff2 > max_diff2) { max_diff2 = diff2; worst_line = line; } } } fclose( ifile1); fclose( ifile2); printf( "%d lines read in; %d had positions\n", line, n_valid); printf( "Max difference: %.8f km at line %d\n", sqrt( max_diff2), worst_line); return( 0); } ================================================ FILE: sat_code.def ================================================ LIBRARY sat_code EXPORTS SGP_init @1 SGP4_init @2 SGP8_init @3 SDP4_init @4 SDP8_init @5 SGP @6 SGP4 @7 SGP8 @8 SDP4 @9 SDP8 @10 select_ephemeris @11 parse_elements @12 observer_cartesian_coords @14 get_satellite_ra_dec_delta @15 epoch_of_date_to_j2000 @16 tle_checksum @17 sxpx_set_implementation_param @18 sxpx_set_dpsec_integration_step @19 sxpx_library_version @20 j2000_to_epoch_of_date @21 ================================================ FILE: sat_eph.c ================================================ #include #include #include #include #include #include #include #include #if defined( _WIN32) || defined( __WATCOMC__) #include "zlibstub.h" #else #include #endif #include "watdefs.h" #include "afuncs.h" #include "comets.h" #include "date.h" #include "norad.h" #include "mpc_func.h" #include "stringex.h" #include "observe.h" /* Code to generate topocentric ephemerides from TLE data, mostly focussed on the TLEs provided in https://www.github.com/Bill-Gray/tles. The program can be compiled for standalone use or for use with the on-line artsat ephemeris service at https://www.projectpluto.com/sat_eph.htm (q.v.). */ #define PI 3.1415926535897932384626433832795028841971693993751058209749445923 typedef struct { double lat, lon, alt, rho_sin_phi, rho_cos_phi; double jd_start, jd_end, step_size; int n_steps; const char *desig; } ephem_t; static int verbose = 0; static double ra_offset = 0., dec_offset = 0.; static char *gzgets_trimmed( char *buff, const int buffsize, gzFile ifile) { char *rval = gzgets( ifile, buff, buffsize); if( rval) { size_t i = 0; while( rval[i] != 10 && rval[i] != 13 && rval[i]) i++; while( i && rval[i - 1] == ' ') i--; /* drop trailing spaces */ rval[i] = '\0'; } return( rval); } static void show_base_60( char *buff, const unsigned n_millisec) { snprintf( buff, 15, "%03u %02u %02u.%03u", n_millisec / 3600000u, (n_millisec / 60000u) % 60u, (n_millisec / 1000u) % 60u, n_millisec % 1000u); } static void put_ra_in_buff( char *buff, double ra) { ra = fmod( ra, 2. * PI); if( ra < 0.) ra += PI + PI; show_base_60( buff, (unsigned)( 3600. * 1000. * ra * 12. / PI)); memmove( buff, buff + 1, strlen( buff)); /* remove leading zero */ } static void put_dec_in_buff( char *buff, const double dec) { show_base_60( buff, (unsigned)( 3600. * 1000. * fabs( dec) * 180. / PI)); *buff = (dec > 0. ? '+' : '-'); } static double angle_between( const double *a, const double *b) { const double cos_ang = dot_product( a, b) / sqrt( dot_product( a, a) * dot_product( b, b)); double rval = acose( cos_ang); return( rval * 180. / PI); } static inline bool desig_match( const tle_t *tle, const char *desig) { size_t i = 0; bool rval = false; while( isdigit( desig[i])) i++; if( i == 5) { if( !desig[i]) /* desig is all digits -> it's the NORAD # */ rval = (atoi( desig) == tle->norad_number); else { i = strlen( desig); if( i > 5 && i < 9) rval = !memcmp( tle->intl_desig, desig, i) && tle->intl_desig[i] <= ' '; } } return( rval); } /* Generates unit vector in the direction of ivect and stores it in z_vect; a unit vector perpendicular to that in the xy plane, stored in x_vect; and a unit vector perpendicular to both, stored in y_vect. */ static double make_orthogonal_basis( const double *ivect, double *x_vect, double *y_vect, double *z_vect) { double rval, len; memcpy( z_vect, ivect, 3 * sizeof( double)); rval = normalize_vect3( z_vect); len = hypot( z_vect[0], z_vect[1]); x_vect[0] = z_vect[1] / len; x_vect[1] = -z_vect[0] / len; x_vect[2] = 0.; vector_cross_product( y_vect, z_vect, x_vect); return( rval); } /* 'obs_pos' = observer position relative to the geocenter, km 'topo_posn' = artsat position relative to the observer, km 'sat_vel' = artsat vel, km/minute (usual, though somewhat oddball, SxPx units) '*motion_pa' = returned posn angle of motion, degrees apparent total angular motion is returned */ static double compute_angular_rates( const double *obs_pos, const double *topo_posn, const double *sat_vel, double *motion_pa, double *ra_motion, double *dec_motion) { double vel[3]; /* velocity of sat relative to observer */ double x_vect[3]; /* unit vector in equatorial plane perpendicular to topo_posn */ double y_vect[3]; /* unit vector perpendicular to topo_posn & x_vect */ double z_vect[3]; /* unit vector version of topo_posn */ double xmotion, ymotion, total_motion, dist; const double omega_E = 1.00273790934; /* Earth rotations per sidereal day (non-constant) */ const double omega = omega_E * 2. * PI / minutes_per_day; /* Earth rotational rate in radians/minute */ vel[0] = sat_vel[0] + omega * obs_pos[1]; vel[1] = sat_vel[1] - omega * obs_pos[0]; vel[2] = sat_vel[2]; dist = make_orthogonal_basis( topo_posn, x_vect, y_vect, z_vect); xmotion = dot_product( vel, x_vect) / dist; /* all in radians/minute */ ymotion = dot_product( vel, y_vect) / dist; total_motion = hypot( xmotion, ymotion); *motion_pa = PI + atan2( xmotion, ymotion); *motion_pa *= 180. / PI; *ra_motion = -xmotion * (180. / PI) * 60.; *dec_motion = -ymotion * (180. / PI) * 60.; return( total_motion * (180. / PI) * 60.); /* cvt to arcmin/min = arcsec/sec */ } static char _header[200]; static int motion_units = 1; /* default to '/minute = degrees/hr = "/sec */ static bool show_separate_motions = false; static bool output_state_vectors = false; static bool output_mjd = false; static int show_ephems_from( const char *path_to_tles, const ephem_t *e, const char *filename, int start_line) { gzFile ifile; char line0[100], line1[100], line2[100]; int show_it = 1, header_shown = 0; double jd_tle = 0., tle_range = 1e+10, abs_mag = 0.; const bool is_geocentric = (e->rho_sin_phi == 0. && e->rho_cos_phi == 0.); static const char *header_text = "Date (UTC) Time R.A. (J2000) decl Azim Alt Elong" " LuElo Dist(km) \"/sec PA"; static const char *geo_header_text = "Date (UTC) Time R.A. (J2000) decl Elong LuElo Dist(km) \"/sec PA"; if( verbose) printf( "Should examine '%s'; start line %d\n", filename, start_line); snprintf( line0, sizeof( line0), "%s/%s", path_to_tles, filename); ifile = gzopen( line0, "rb"); if( !ifile) /* maybe it's compressed */ { strlcat_error( line0, ".gz"); ifile = gzopen( line0, "rb"); } if( !ifile) { fprintf( stderr, "'%s' not opened\n", line0); exit( 0); } *line0 = *line1 = '\0'; while( gzgets_trimmed( line2, sizeof( line2), ifile)) { tle_t tle; if( *line2 == '#') { char *tptr = strstr( line2, " H "); if( !memcmp( line2, "# MJD ", 6)) { jd_tle = atof( line2 + 6) + 2400000.5; tle_range = 1.; show_it = (jd_tle < e->jd_end && jd_tle + tle_range > e->jd_start); } else if( tptr) { abs_mag = atof( tptr + 2); if( verbose) printf( "H = %.3f\n", abs_mag); } } else if( show_it && parse_elements( line1, line2, &tle) >= 0 && desig_match( &tle, e->desig)) { double sat_params[N_SAT_PARAMS], jd = e->jd_start; size_t i, j; const int is_deep_type = select_ephemeris( &tle); if( is_deep_type) SDP4_init( sat_params, &tle); else SGP4_init( sat_params, &tle); if( verbose > 1) { printf( "Got TLEs for %f :\n", jd); printf( "%s\n%s\n%s\n", line0, line1, line2); } for( i = 0; i < (size_t)e->n_steps; i++, jd = e->jd_start + (double)i * e->step_size) if( (int)i >= start_line && jd >= jd_tle && jd < jd_tle + tle_range) { char buff[90], dec_buff[20], ra_buff[20], alt_buff[17]; double pos[3], vel[3], obs_pos[3], ra, dec, dist; const double t_since = (jd - tle.epoch) * minutes_per_day; double solar_xyzr[4], lunar_xyzr[4], topo_posn[3], elong; double motion_rate, motion_pa; double ra_motion, dec_motion; const char *format_string; if( !header_shown) { char *tptr; header_shown = 1; printf( "\nEphemerides for %05d = %s%.2s-%s\n", tle.norad_number, (atoi( tle.intl_desig) > 57000) ? "19" : "20", tle.intl_desig, tle.intl_desig + 2); tptr = line0; if( *tptr == '0' && tptr[1] == ' ') tptr += 2; /* actually a '3LE' with name prefaced by '0 ' */ snprintf( _header, sizeof( _header), "%s\n%s", tptr, (is_geocentric ? geo_header_text : header_text)); if( show_separate_motions) strcat( _header, " RA \"/sec dec"); if( motion_units == 60) while( NULL != (tptr = strstr( _header, "/sec "))) memcpy( tptr, "/min", 4); strcat( _header, abs_mag ? " Mag\n" : "\n"); if( output_state_vectors) strcpy( _header, "Date (UTC) Time" " x y z" " vx vy vz\n"); printf( "%s", _header); } if( output_mjd) snprintf( buff, sizeof( buff), "%.5f", jd - 2400000.5); else full_ctime( buff, jd, FULL_CTIME_YMD | FULL_CTIME_MONTHS_AS_DIGITS | FULL_CTIME_LEADING_ZEROES); if( is_deep_type) SDP4( t_since, &tle, sat_params, pos, vel); else SGP4( t_since, &tle, sat_params, pos, vel); observer_cartesian_coords( jd, e->lon, e->rho_cos_phi, e->rho_sin_phi, obs_pos); get_satellite_ra_dec_delta( obs_pos, pos, &ra, &dec, &dist); epoch_of_date_to_j2000( jd, &ra, &dec); if( output_state_vectors) { const double year = 2000. + (jd - 2451545.) / 365.25; double matrix[9]; setup_precession( matrix, year, 2000.); precess_vector( matrix, pos, pos); precess_vector( matrix, vel, vel); printf( "%s %14.5f%14.5f%14.5f%11.5f%11.5f%11.5f\n", buff, pos[0], pos[1], pos[2], vel[0] / 60., vel[1] / 60., vel[2] / 60.); } ra += ra_offset; dec += dec_offset; put_ra_in_buff( ra_buff, ra); put_dec_in_buff( dec_buff, dec); ra_buff[10] = dec_buff[9] = '\0'; for( j = 0; j < 3; j++) topo_posn[j] = pos[j] - obs_pos[j]; motion_rate = compute_angular_rates( obs_pos, topo_posn, vel, &motion_pa, &ra_motion, &dec_motion); lunar_solar_position( jd, lunar_xyzr, solar_xyzr); ecliptic_to_equatorial( solar_xyzr); ecliptic_to_equatorial( lunar_xyzr); if( !is_geocentric) { double x_vect[3], y_vect[3], z_vect[3], alt, az; make_orthogonal_basis( obs_pos, x_vect, y_vect, z_vect); az = PI + atan2( dot_product( x_vect, topo_posn), dot_product( y_vect, topo_posn)); az *= 180. / PI; alt = 90. - angle_between( topo_posn, obs_pos); snprintf( alt_buff, sizeof( alt_buff), " %5.1f %+05.1f", az, alt); } else *alt_buff = '\0'; elong = angle_between( topo_posn, solar_xyzr); if( !output_state_vectors) { printf( "%s %s %s%s %6.1f %6.1f %8.0f", buff, ra_buff, dec_buff, alt_buff, elong, angle_between( topo_posn, lunar_xyzr), dist); motion_rate *= (double)motion_units; if( motion_rate < 9.999) format_string = " %6.4f %6.1f"; else if( motion_rate < 99.99) format_string = " %6.3f %6.1f"; else if( motion_rate < 999.9) format_string = " %6.2f %6.1f"; else if( motion_rate < 9999.) format_string = " %6.1f %6.1f"; else format_string = " %6.0f %6.1f"; printf( format_string, motion_rate, motion_pa); if( show_separate_motions) { const char precision = format_string[5]; snprintf( buff, sizeof( buff), " %%+7.%cf %%+7.%cf", precision, precision); printf( buff, ra_motion * (double)motion_units, dec_motion * (double)motion_units); } if( !abs_mag) printf( "\n"); else { const double phase_ang = (180. - elong) * (PI / 180.); double mag = abs_mag + 5. * log10( dist / AU_IN_KM) + phase_angle_correction_to_magnitude( phase_ang, 0.15); printf( "%8.1f\n", mag); } } start_line = (int)i + 1; } } strcpy( line0, line1); strcpy( line1, line2); } gzclose( ifile); return( start_line); } static const char *tle_list_filename = "tle_list.txt"; int generate_artsat_ephems( const char *path_to_tles, const ephem_t *e) { gzFile ifile; char buff[100]; int is_in_range = 0, id_matches = 1, start_line = 0; snprintf( buff, sizeof( buff), "%s/%s", path_to_tles, tle_list_filename); if( verbose > 1) printf( "Opening '%s', looking for '%s'\n", buff, e->desig); ifile = gzopen( buff, "rb"); if( !ifile) { strlcat_error( buff, ".gz"); ifile = gzopen( buff, "rb"); } if( !ifile) { fprintf( stderr, "'%s' not opened\n", buff); exit( 0); } while( start_line != e->n_steps && gzgets_trimmed( buff, sizeof( buff), ifile)) { if( !memcmp( buff, "# Range:", 8)) { char t_start[40], t_end[40]; int n_scanned = sscanf( buff + 8, "%39s %39s", t_start, t_end); assert( 2 == n_scanned); if( get_time_from_string( 0., t_start, FULL_CTIME_YMD, NULL) < e->jd_end && get_time_from_string( 0., t_end, FULL_CTIME_YMD, NULL) > e->jd_start) is_in_range = 1; } if( !memcmp( buff, "# ID:", 5)) { int i; if( buff[5] != ' ' || buff[11] != ' ' || buff[12] != ' ') fprintf( stderr, "BAD LINE %s\n", buff); for( i = 6; i < 10; i++) if( !isdigit( buff[i]) || !isdigit( buff[i + 7])) { printf( "BAD LINE (2) %s\n", buff); i = 99; } if( strcmp( e->desig, buff + 13) && atoi( buff + 5) != atoi( e->desig)) id_matches = 0; } if( !memcmp( buff, "# Include ", 10)) { if( is_in_range && id_matches) start_line = show_ephems_from( path_to_tles, e, buff + 10, start_line); is_in_range = 0; id_matches = 1; } } gzclose( ifile); if( start_line) printf( "%s", _header); return( start_line); } static int set_location( ephem_t *e, const char *mpc_code, const char *obscode_file_name) { mpc_code_t c; int rval = get_lat_lon_info( &c, mpc_code); if( rval) { gzFile ifile = gzopen( obscode_file_name, "rb"); char buff[200]; if( !ifile) { fprintf( stderr, "'%s' not found\n", obscode_file_name); exit( 0); } while( rval && gzgets_trimmed( buff, sizeof( buff), ifile)) if( !memcmp( mpc_code, buff, 3)) { const int planet = get_mpc_code_info( &c, buff); if( planet != 3) { fprintf( stderr, "MPC code '%s' is for planet %d\n", mpc_code, planet); exit( 0); } rval = 0; printf( "%s\n", c.name); } gzclose( ifile); } if( !rval) { e->lat = c.lat; e->lon = c.lon; e->alt = c.alt; e->rho_cos_phi = c.rho_cos_phi; e->rho_sin_phi = c.rho_sin_phi; if( c.lon > PI) c.lon -= PI + PI; if( c.rho_sin_phi || c.rho_cos_phi) printf( "Latitude %c %f, Longitude %c %f\nAltitude %.1f meters (above WGS84 ellipsoid)\n", (c.lat > 0. ? 'N' : 'S'), fabs( c.lat) * 180. / PI, (c.lon > 0. ? 'E' : 'W'), fabs( c.lon) * 180. / PI, c.alt); } return( rval); } #ifdef ON_LINE_VERSION #define OBSCODES_DOT_HTML_FILENAME "/home/projectp/public_html/cgi-bin/fo/ObsCodes.htm" #define ROVERS_DOT_TXT_FILENAME "/home/projectp/public_html/cgi-bin/fo/rovers.txt" #define PATH_TO_TLES "/home/projectp/public_html/tles" #else #define OBSCODES_DOT_HTML_FILENAME "/home/phred/.find_orb/ObsCodes.htm" #define ROVERS_DOT_TXT_FILENAME "/home/phred/.find_orb/rovers.txt" #define PATH_TO_TLES "/home/phred/tles" #endif static const char *get_arg( const char **argv) { const char *rval; if( argv[0][0] == '-' && argv[0][1]) { if( !argv[0][2] && argv[1]) rval = argv[1]; else rval = argv[0] + 2; } else rval = NULL; if( !rval) { fprintf( stderr, "Can't get an argument : '%s'\n", argv[0]); exit( 0); } return( rval); } static void fix_desig( char *desig) { size_t i; int bitmask = 0; for( i = 0; i < 10 && desig[i]; i++) if( isdigit( desig[i])) bitmask |= (1 << (int)i); if( i >= 9 && bitmask == 0xef && desig[4] == '-') { desig[0] = desig[2]; /* it's in YYYY-NNNA form; */ desig[1] = desig[3]; /* cvt to YYNNNA form */ for( i = 5; desig[i - 1]; i++) desig[i - 3] = desig[i]; } } static void error_help( void) { printf( "'sat_eph' arguments:\n" " -c(MPC code) : specify location (default = geocentric)\n" " -t(date/time) : starting time of ephemeris (default = now)\n" " -n(#) : number of ephemeris steps (default = 20)\n" " -s(#) : ephemeris step size in days (default = 1h)\n" " -S : show motions in RA/dec components, as well as total/PA\n"); printf( " -o(#) : five digit NORAD number or YYNNNA international designation\n" " -r : do _not_ round times to nearest step size\n" " -u : show motions in \"/min = degrees/hr (default is \"/sec)\n" " -m : show times as MJD\n" " -V : output state vectors instead of observables\n" " -v(#) : level of verbosity\n"); } int dummy_main( const int argc, const char **argv) { int i; ephem_t e; bool round_to_nearest_step = true; const char *mpc_code = "500"; const char *override_tle_filename = NULL; if( argc < 2) { error_help( ); return( 0); } memset( &e, 0, sizeof( ephem_t)); e.jd_start = current_jd( ); e.n_steps = 20; e.step_size = 1. / 24.; for( i = 1; i < argc; i++) if( argv[i][0] == '-' && argv[i][1]) { const char *arg = get_arg( argv + i); switch( argv[i][1]) { case 'c': mpc_code = arg; break; case 'f': tle_list_filename = arg; break; case 'F': override_tle_filename = arg; break; case 't': e.jd_start = get_time_from_string( e.jd_start, arg, FULL_CTIME_YMD, NULL); break; case 'm': output_mjd = true; break; case 'n': e.n_steps = atoi( arg); break; case 'O': if( sscanf( arg, "%lf,%lf", &ra_offset, &dec_offset) == 2) { printf( "Offsetting by %f degrees in RA, %f degrees in dec\n", ra_offset, dec_offset); ra_offset *= PI / 180.; dec_offset *= PI / 180.; } break; case 'r': round_to_nearest_step = false; break; case 's': if( arg && *arg) { const char end_char = arg[strlen( arg) - 1]; e.step_size = atof( arg); switch( end_char) { case 'h': e.step_size /= hours_per_day; break; case 'm': e.step_size /= minutes_per_day; break; case 's': e.step_size /= seconds_per_day; break; } } break; case 'S': show_separate_motions = true; break; case 'u': motion_units = 60; break; case 'o': /* Will handle below */ break; case 'v': verbose = 1 + atoi( arg); break; case 'V': output_state_vectors = 1; break; default: fprintf( stderr, "Unrecognized option '%s'\n", argv[i]); error_help( ); return( 0); } } if( set_location( &e, mpc_code, OBSCODES_DOT_HTML_FILENAME)) if( set_location( &e, mpc_code, ROVERS_DOT_TXT_FILENAME)) fprintf( stderr, "WARNING: Could not parse location '%s'\n", mpc_code); if( round_to_nearest_step && e.step_size) e.jd_start = floor( (e.jd_start - 0.5) / e.step_size) * e.step_size + 0.5; e.jd_end = e.jd_start + (double)e.n_steps * e.step_size; if( verbose) printf( "arguments parsed; JDs %f to %f\n", e.jd_start, e.jd_end); for( i = 1; i < argc; i++) if( argv[i][0] == '-' && argv[i][1] == 'o') { char desig[30]; strncpy( desig, get_arg( argv + i), 29); fix_desig( desig); e.desig = desig; if( override_tle_filename) show_ephems_from( PATH_TO_TLES, &e, override_tle_filename, 0); else generate_artsat_ephems( PATH_TO_TLES, &e); } return( 0); } #ifndef ON_LINE_VERSION int main( const int argc, const char **argv) { return( dummy_main( argc, argv)); } #else #include #ifdef __has_include #if __has_include() #include "cgi_func.h" #else #error \ 'cgi_func.h' not found. This project depends on the 'lunar'\ library. See www.github.com/Bill-Gray/lunar .\ Clone that repository, 'make' and 'make install' it. #endif #else #include "cgi_func.h" #endif int main( const int unused_argc, const char **unused_argv) { const char *argv[2000]; const size_t max_buff_size = 40000; /* room for 500 obs */ char *buff = (char *)malloc( max_buff_size); char field[30], time_text[80]; char num_steps[30], step_size[30], obs_code[40]; FILE *lock_file = fopen( "lock.txt", "w"); int cgi_status, i, argc = 9; bool round_step = false; #ifndef _WIN32 extern char **environ; avoid_runaway_process( 15); #endif /* _WIN32 */ setbuf( lock_file, NULL); INTENTIONALLY_UNUSED_PARAMETER( unused_argc); INTENTIONALLY_UNUSED_PARAMETER( unused_argv); printf( "Content-type: text/html\n\n"); printf( "
\n");
   if( !lock_file)
      {
      printf( "

Server is busy. Try again in a minute or two.

"); printf( "

Your artsat ephemerides are very important to us!

"); return( 0); } fprintf( lock_file, "We're in\n"); *time_text = *num_steps = *step_size = *obs_code = '\0'; #ifndef _WIN32 for( i = 0; environ[i]; i++) fprintf( lock_file, "%s\n", environ[i]); #endif cgi_status = initialize_cgi_reading( ); fprintf( lock_file, "CGI status %d\n", cgi_status); if( cgi_status <= 0) { printf( "

CGI data reading failed : error %d ", cgi_status); printf( "This isn't supposed to happen.

\n"); return( 0); } while( !get_cgi_data( field, buff, NULL, max_buff_size)) { fprintf( lock_file, "Field '%s'\n", field); if( !strcmp( field, "time") && strlen( buff) < sizeof( time_text)) strcpy( time_text, buff); if( !strcmp( field, "obj_name")) { char *obj_name = (char *)malloc( strlen( buff) + 1); strcpy( obj_name, buff); argv[argc++] = "-o"; argv[argc++] = obj_name; } else if( !memcmp( field, "obj_", 4)) /* selected an object check-box */ { char *obj_name = (char *)malloc( strlen( field) - 3); strcpy( obj_name, field + 4); argv[argc++] = "-o"; argv[argc++] = obj_name; } else if( !strcmp( field, "motion60")) motion_units = 60; if( !strcmp( field, "num_steps") && strlen( buff) < sizeof( num_steps)) strcpy( num_steps, buff); if( !strcmp( field, "step_size") && strlen( buff) < sizeof( step_size)) { char *tptr = strchr( buff, 'v'); if( tptr) { verbose = atoi( tptr + 1); *tptr = '\0'; } strcpy( step_size, buff); } if( !strcmp( field, "obs_code") && strlen( buff) < sizeof( obs_code)) strcpy( obs_code, buff); if( !strcmp( field, "round_step")) round_step = true; if( !strcmp( field, "vectors")) output_state_vectors = true; if( !strcmp( field, "mjd")) output_mjd = true; if( !strcmp( field, "show_separate_motions")) argv[argc++] = "-S"; } fprintf( lock_file, "Fields read\n"); if( !round_step) argv[argc++] = "-r"; argv[0] = "sat_eph"; argv[1] = "-t"; argv[2] = time_text; argv[3] = "-c"; argv[4] = obs_code; argv[5] = "-n"; argv[6] = num_steps; argv[7] = "-s"; argv[8] = step_size; argv[argc] = NULL; for( i = 0; argv[i]; i++) fprintf( lock_file, "arg %d: '%s'\n", (int)i, argv[i]); dummy_main( argc, argv); fprintf( lock_file, "dummy_main called\n"); free( buff); printf( "On-line Sat_eph compiled " __DATE__ " " __TIME__ " UTC-5h\n"); printf( "See " "https://www.github.com/Bill-Gray/sat_code for source code\n"); printf( "
"); return( 0); } #endif ================================================ FILE: sat_id.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ /* sat_id.cpp 8 March 2003, with updates as listed below An example 'main' function illustrating how to find which satellite(s) are within a given radius of a given RA/dec, as seen from a given point. The code reads in a file of observations in MPC format (name provided as the first command-line argument). For example: sat_id mpc_obs.txt would hunt through the file 'mpc_obs.txt' for MPC-formatted observations. It would then read the file 'alldat.tle', looking for corresponding satellites within .2 degrees of said observations. It then spits out the original file, with satellite IDs added (when found) after each observation line. For each IDed satellite, the international and NORAD designations are given, along with its angular distance from the search point, position angle of motion, and apparent angular rate of motion in arcminutes/second (or, equivalently, degrees/minute). */ /* 2 July 2003: fixed the day/month/year to JD part of 'get_mpc_data()' so it will work for all years >= 0 (previously, it worked for years 2000 to 2099... plenty for the practical purpose of ID'ing recently-found satellites, but this is also an 'example' program.) */ /* 3 July 2005: revised the check on the return value for parse_elements(). Now elements with bad checksums won't be rejected. */ /* 23 June 2006: after comment from Eric Christensen, revised to use names 'ObsCodes.html' or 'ObsCodes.htm', with 'stations.txt' being a third choice. Also added the '-a' command line switch to cause the program to show all lines from input (default is now that only MPC astrometric input gets echoed.) */ /* 30 June 2006: further comment from Eric Christensen: when computing object motion from two consecutive observations, if the second one has a date/time preceding the first, you get a negative rate of motion that's off by 180 degrees. Fixed this. */ /* 17 Nov 2006: artificial satellite data is now being provided in a file named 'ALL_TLE.TXT'. I've modified the default TLE to match. (Note : since modified to be read from 'tle_list.txt', as found in the https://www.github.com/Bill-Gray/tles repository. This allows for multiple TLEs to be read.) */ /* 22 Oct 2012: minor cosmetic changes, such as making constant variables of type 'const', updating URL for the MPC station code file, adding a comment or two. */ /* 7 Jan 2013: revised output to show satellite name if available, plus the eccentricity, orbital period, and inclination. */ /* 2013 Dec 8: revised to pay attention to "# MJD" and "#Ephem start" lines, for files that contain many TLEs covering different time spans for the same object. I sometimes create such files; when that happens, for each observation, only the TLE(s) covering that observation's time should be used, and the others are suppressed. */ #include #include #include #include #include #include #include #if defined( _WIN32) || defined( __WATCOMC__) #include /* for alloca() prototype */ #include "zlibstub.h" #else #include #include #endif #if defined(_MSC_VER) && _MSC_VER < 1900 /* For older MSVCs, we have to supply our own */ /* snprintf(). See snprintf.cpp for details. */ int snprintf( char *string, const size_t max_len, const char *format, ...); #endif #if defined( __has_include) #if !__has_include() #error \ "You need the 'lunar' library (https://www.github.com/Bill-Gray/lunar).\ See https://github.com/Bill-Gray/sat_code/issues/2 for a fix to this." /* This would normally be followed by dozens of errors. GCC, */ /* at least, stops completely when it can't find a system */ /* include file. */ #include #endif #endif #include "norad.h" #include "observe.h" #include "watdefs.h" #include "mpc_func.h" #include "afuncs.h" #include "date.h" #include "sat_util.h" #include "stringex.h" #define OBSERVATION struct observation OBSERVATION { char text[81]; double jd, ra, dec; double observer_loc[3]; }; typedef struct { double dist, ra, dec, motion_rate, motion_pa; int norad_number; char intl_desig[9]; char text[80]; bool in_shadow; } match_t; typedef struct { OBSERVATION *obs; size_t idx1, idx2, n_obs, n_matches; double speed; match_t *matches; } object_t; /* When we encounter a line for a spacecraft-based observation's offset from the geocenter (a "second line" in the MPC's punched-card format system), we store that offset in an offset_t structure. Once the file has been read in, we go back and match the offsets to their corresponding observations. If the lines aren't in proper order (which happens), we'll fix it, and can detect errors where we get one line but not the other. */ typedef struct { double jd, posn[3]; char mpc_code[4]; } offset_t; #define PI 3.1415926535897932384626433832795028841971693993751058209749445923 #define TIME_EPSILON (1./86400.) #define EARTH_MAJOR_AXIS 6378137. #define EARTH_MINOR_AXIS 6356752. #define is_power_of_two( X) (!((X) & ((X) - 1))) /* By default, Sat_ID checks for possible matches with both artificial objects (its main use) and some natural irregular satellites of the gas giants. Use the '-n0' command line option to identify only artificial objects, or '-n1' to identify only the natural irregular objects. */ #define IDENTIFY_ALL 2 #define IDENTIFY_ARTSATS 0 #define IDENTIFY_NATSATS 1 static int identify_filter = IDENTIFY_ALL; /* For testing purposes, I sometimes want _only_ "my" TLEs (not the 'classified' or Space-Watch TLEs) to be used. This is invoked with -s. */ static bool my_tles_only = false; #if !defined( ON_LINE_VERSION) && !defined( _WIN32) #define CSI "\x1b[" #define OSC "\x1b]" #define REVERSE_VIDEO CSI "0m" CSI "7m" #define NORMAL_VIDEO CSI "0m" #else #define REVERSE_VIDEO #define NORMAL_VIDEO #endif /* Sputnik 1 was launched on 1957 Oct 4. There is no point in checking for satellites before that date. TLEs, and therefore this program, won't work after 2057 Jan 1. */ const double oct_4_1957 = 2436115.5; const double jan_1_2057 = 2472364.5; static int get_mpc_data( OBSERVATION *obs, const char *buff) { obs->jd = extract_date_from_mpc_report( buff, NULL); if( obs->jd < oct_4_1957 || obs->jd > jan_1_2057) return( -1); /* not an 80-column MPC record */ if( get_ra_dec_from_mpc_report( buff, NULL, &obs->ra, NULL, NULL, &obs->dec, NULL)) if( 's' != buff[14] && 'v' != buff[14]) /* satellite offsets and */ return( -2); /* roving obs lat/lons won't have RA/decs */ assert( strlen( buff) < sizeof( obs->text)); strncpy( obs->text, buff, sizeof( obs->text)); obs->text[80] = '\0'; return( 0); } static int extract_csv_value( char *obuff, const char *ibuff, int idx, size_t obuff_len) { while( idx && *ibuff) if( *ibuff++ == ',') idx--; if( !*ibuff) return( -1); if( *ibuff == '"') ibuff++; obuff_len--; while( obuff_len-- && *ibuff && *ibuff != ',' && *ibuff != '"') *obuff++ = *ibuff++; *obuff = '\0'; return( 0); } /* If we encounter 'field' data -- we're determining which artsats are in a field, not which ones match moving objects -- the output is very different : */ static bool field_mode = false; /* An imaging field is specified as an image name, date/time, RA and dec in decimal degrees, and MPC obscode, all comma-separated, such as MyImage,2023 nov 17 03:14:15.9,271.818,-14.142,T05 "Another image",2023-11-17.314159,21 16 58.21,"+31 41 59.26","V00" In the first, the RA and dec are in decimal degrees; in the second, sexagesimal notation is used and the RA is assumed to be in hours, minutes, and seconds. You need the commas, but can skip the quotation marks. */ static int get_field_data( OBSERVATION *obs, const char *buff) { int rval = -1; size_t n_commas, i; for( i = n_commas = 0; buff[i] && n_commas < 5; i++) if( buff[i] == ',') n_commas++; if( n_commas == 4 && strlen( buff) < 80) { char tbuff[80]; int bytes_read; extract_csv_value( tbuff, buff, 1, sizeof( tbuff)); obs->jd = get_time_from_string( 0., tbuff, 0, NULL); if( obs->jd > oct_4_1957 && obs->jd < jan_1_2057) { extract_csv_value( tbuff, buff, 4, sizeof( tbuff)); if( strlen( tbuff) != 3) /* MPC code must be three characters */ return( -1); strcpy( obs->text + 77, tbuff); extract_csv_value( tbuff, buff, 2, sizeof( tbuff)); obs->ra = get_ra_from_string( tbuff, &bytes_read); if( bytes_read <= 0) return( -1); extract_csv_value( tbuff, buff, 3, sizeof( tbuff)); obs->dec = get_dec_from_string( tbuff, &bytes_read); if( bytes_read <= 0) return( -1); extract_csv_value( obs->text, buff, 0, sizeof( obs->text)); field_mode = true; rval = 0; } } return( rval); } /* This loads up the file 'ObsCodes.html' into memory on its first call. Then, given an MPC code, it finds the corresponding line and copies it into 'station_code_data'. It looks in several places for the file; if you've installed Find_Orb, it should be able to get it from the ~/.find_orb directory. It also checks for the truncated 'ObsCodes.htm' version of the file. */ int verbose = 0; static bool check_all_tles = false; static int get_station_code_data( char *station_code_data, const char *mpc_code) { static char *cached_data, *cached_ptr; if( !mpc_code) /* freeing memory */ { if( cached_data) free( cached_data); cached_data = cached_ptr = NULL; return( 0); } *station_code_data = '\0'; if( !cached_data) { const char *filenames[2] = { "ObsCodes.html", "ObsCodes.htm" }; FILE *ifile = NULL; size_t size; int i; for( i = 0; !ifile && i < 2; i++) ifile = local_then_config_fopen( filenames[i], "rb"); if( !ifile) { printf( "Failed to find MPC station list 'ObsCodes.html'\n"); printf( "This can be downloaded at:\n\n"); printf( "http://www.minorplanetcenter.org/iau/lists/ObsCodes.html\n"); exit( -3); } fseek( ifile, 0L, SEEK_END); size = (size_t)ftell( ifile); fseek( ifile, 0L, SEEK_SET); cached_data = (char *)malloc( size + 1); if( fread( cached_data, 1, size, ifile) != size) { printf( "Failed to read station file\n"); exit( -4); } fclose( ifile); if( verbose) printf( "Station codes: %u bytes read\n", (unsigned)size); ifile = local_then_config_fopen( "rovers.txt", "rb"); if( ifile) { /* if 'rovers.txt' is available, */ size_t size2; /* append its data to ObsCodes.htm */ fseek( ifile, 0L, SEEK_END); size2 = (size_t)ftell( ifile); fseek( ifile, 0L, SEEK_SET); cached_data = (char *)realloc( cached_data, size + size2 + 1); if( fread( cached_data + size, 1, size2, ifile) != size2) { printf( "Failed to read 'rovers.txt'\n"); exit( -4); } fclose( ifile); size += size2; } cached_data[size] = '\0'; } if( !cached_ptr || memcmp( cached_ptr, mpc_code, 3)) { char search_buff[5]; snprintf_err( search_buff, sizeof( search_buff), "\n%.3s", mpc_code); cached_ptr = strstr( cached_data, search_buff); if( cached_ptr) cached_ptr++; } if( cached_ptr) { size_t i; for( i = 0; cached_ptr[i] >= ' '; i++) station_code_data[i] = cached_ptr[i]; station_code_data[i] = '\0'; } else { static char *codes_already_reported = NULL; static size_t n_reported = 0; if( codes_already_reported && strstr( codes_already_reported, mpc_code)) return( -1); /* we've already reported this as 'unknown' */ n_reported++; codes_already_reported = (char *)realloc( codes_already_reported, n_reported * 4 + 1); if( n_reported == 1) /* realloc( ) doesn't initialize to zero */ *codes_already_reported = '\0'; strcat( codes_already_reported, mpc_code); strcat( codes_already_reported, " "); printf( "Station code '%s' not found.\n", mpc_code); if( n_reported == 1) /* only really need the following text once */ { #ifdef ON_LINE_VERSION printf( "If this is a new MPC code, it could be that this service needs to be\n"); printf( "updated to know about it. Please contact pluto at projectpluto.com so\n"); printf( "I can fix this.\n"); #else printf( "If this is a new MPC code, you may need to get this file:\n"); printf( "http://www.minorplanetcenter.org/iau/lists/ObsCodes.html\n"); printf( "and replace the existing ObsCodes.html.\n"); #endif } } return( cached_ptr ? 0 : -1); } /* Code to check if a 'second line' (v for roving observer or s for spacecraft observation) matches a 'first line' (the one that has the actual astrometry). If the date and obscode match, and the observation doesn't already have a non-geocentric position set, then we have a match. */ static bool offset_matches_obs( const offset_t *offset, const OBSERVATION *obs) { if( offset->jd == obs->jd && !obs->observer_loc[0] && !strcmp( offset->mpc_code, obs->text + 77) && !obs->observer_loc[1] && !obs->observer_loc[2]) return( true); else return( false); } /* (XXX) locations are specified with text such as COM Long. 239 18 45 E, Lat. 33 54 11 N, Alt. 100m, Google Earth */ static char xxx_location[80]; static int set_observer_location( OBSERVATION *obs) { mpc_code_t code_data; int rval; if( memcmp( obs->text + 77, "XXX", 3)) { char station_data[100]; rval = get_station_code_data( station_data, obs->text + 77); if( !rval) get_mpc_code_info( &code_data, station_data); } else { static bool first_time = true; rval = get_xxx_location_info( &code_data, xxx_location); if( rval && first_time) printf( "WARNING: (XXX) observations won't be handled, because the\n" "observatory location was either missing or incorrectly formatted.\n" "See https://www.projectpluto.com/xxx.htm for information on how\n" "this line should be handled.\n"); first_time = false; } if( !rval) { observer_cartesian_coords( obs->jd, code_data.lon, code_data.rho_cos_phi, code_data.rho_sin_phi, obs->observer_loc); j2000_to_epoch_of_date( obs->jd, &obs->ra, &obs->dec); } return( rval); } /* Loads up MPC-formatted 80-column observations from a file. Makes a pass to find out how many observations there are, allocates space for them, then reads them again to actually load the observations. */ static const char *_target_desig; static OBSERVATION *get_observations_from_file( FILE *ifile, size_t *n_found, const double t_low, const double t_high) { OBSERVATION *rval = NULL, obs; void *ades_context = init_ades2mpc( ); char buff[400]; size_t count = 0, n_allocated = 0, n_offsets = 0, i; size_t n_obs_without_loc = 0; offset_t *offsets = NULL; int n_errors_found = 0; const clock_t t0 = clock( ); assert( ades_context); memset( &obs, 0, sizeof( OBSERVATION)); while( fgets_with_ades_xlation( buff, sizeof( buff), ades_context, ifile)) if( (!get_mpc_data( &obs, buff) || !get_field_data( &obs, buff)) && obs.jd > t_low && obs.jd < t_high && (!_target_desig || strstr( buff, _target_desig))) { if( buff[14] == 's' || buff[14] == 'v') { /* satellite obs or roving observer */ offset_t toff; toff.jd = obs.jd; strlcpy_error( toff.mpc_code, buff + 77); if( buff[14] == 's') { int err_code = get_satellite_offset( buff, toff.posn); for( i = 0; i < 3; i++) toff.posn[i] *= AU_IN_KM; ecliptic_to_equatorial( toff.posn); if( err_code < 0 && n_errors_found++ < 10) fprintf( stderr, REVERSE_VIDEO "Malformed satellite offset; err %d\n%s\n" NORMAL_VIDEO, err_code, buff); } else { double lat, lon, alt_in_meters, rho_sin_phi, rho_cos_phi; if( sscanf( buff + 34, "%lf %lf %lf", &lon, &lat, &alt_in_meters) != 3) if( n_errors_found++ < 10) fprintf( stderr, "Couldn't parse roving observer:\n%s\n", buff); lon *= PI / 180.; lat *= PI / 180.; lat_alt_to_parallax( lat, alt_in_meters, &rho_cos_phi, &rho_sin_phi, EARTH_MAJOR_AXIS, EARTH_MINOR_AXIS); observer_cartesian_coords( obs.jd, lon, rho_cos_phi, rho_sin_phi, toff.posn); } n_offsets++; offsets = (offset_t *)realloc( offsets, n_offsets * sizeof( offset_t)); offsets[n_offsets - 1] = toff; } else if( !set_observer_location( &obs)) { if( count == n_allocated) { n_allocated += 10 + n_allocated / 2; rval = (OBSERVATION *)realloc( rval, (n_allocated + 1) * sizeof( OBSERVATION)); } rval[count] = obs; if( !rval[count].observer_loc[0] && !rval[count].observer_loc[1] && !rval[count].observer_loc[2]) { const OBSERVATION temp_swap = rval[count]; /* put obs w/out positions */ /* at the start of array */ rval[count] = rval[n_obs_without_loc]; rval[n_obs_without_loc] = temp_swap; n_obs_without_loc++; } count++; if( count % 500000 == 0) { const clock_t dt = clock( ) - t0; if( dt) fprintf( stderr, "%ld obs (%.0lf obs/second) \r", count, (double)count * (double)CLOCKS_PER_SEC / (double)dt); } } } else if( !strncmp( buff, "COM verbose ", 12)) verbose = atoi( buff + 12) + 1; else if( !strcmp( buff, "COM check tles")) check_all_tles = true; else if( !memcmp( buff, "COM Long.", 9)) strlcpy_error( xxx_location, buff); else if( !strcmp( buff, "COM ignore obs")) while( fgets_trimmed( buff, sizeof( buff), ifile)) if( !strcmp( buff, "COM end ignore obs")) break; *n_found = count; free_ades2mpc_context( ades_context); /* for each spacecraft offset, look for the corresponding observation. If we don't find one, emit a warning. */ for( i = 0; i < n_offsets; i++) { size_t j = 0; while( j < n_obs_without_loc && !offset_matches_obs( offsets + i, rval + j)) j++; if( j == count) { if( n_errors_found++ < 10) fprintf( stderr, REVERSE_VIDEO "Unmatched artsat obs for JD %f\n" NORMAL_VIDEO, offsets[i].jd); } else memcpy( rval[j].observer_loc, offsets[i].posn, 3 * sizeof( double)); } /* Warn about obs with no offset data, too. */ for( i = 0; i < count; i++) if( !rval[i].observer_loc[0] && !rval[i].observer_loc[1] && !rval[i].observer_loc[2]) if( n_errors_found++ < 10) fprintf( stderr, REVERSE_VIDEO "No position for this observation :\n%s\n" NORMAL_VIDEO, rval[i].text); free( offsets); if( n_errors_found >= 10) fprintf( stderr, "Showing first ten of %d errors\n", n_errors_found); return( rval); } static int id_compare( const OBSERVATION *a, const OBSERVATION *b) { return( memcmp( a->text, b->text, 12)); } static int compare_obs( const void *a, const void *b, void *context) { const OBSERVATION *aptr = (const OBSERVATION *)a; const OBSERVATION *bptr = (const OBSERVATION *)b; int rval = id_compare( aptr, bptr); INTENTIONALLY_UNUSED_PARAMETER( context); if( !rval) /* same IDs? Then sort by JD of observation */ rval = (aptr->jd > bptr->jd ? 1 : -1); return( rval); } /* Copied straight from 'shellsor.cpp' in Find_Orb. See comments there. */ void shellsort_r( void *base, const size_t n_elements, const size_t elem_size, int (*compare)(const void *, const void *, void *), void *context) { #if (defined _GNU_SOURCE || defined __GNU__ || defined __linux) qsort_r( base, n_elements, elem_size, compare, context); #else size_t gap = 250104703; char *data = (char *)base; char *pivot = (char *)alloca( elem_size); while( gap < n_elements) gap = gap * 8 / 3 + 1; while( (gap = gap * 3 / 8) != 0) { size_t j; const size_t spacing = elem_size * gap; for( j = gap; j < n_elements; j++) { char *tptr = data + j * elem_size; char *tptr2 = tptr - spacing; if( (compare)( tptr2, tptr, context) > 0) { memcpy( pivot, tptr, elem_size); memcpy( tptr, tptr2, elem_size); tptr = tptr2; tptr2 -= spacing; while( tptr2 >= base && (compare)( tptr2, pivot, context) > 0) { memcpy( tptr, tptr2, elem_size); tptr = tptr2; tptr2 -= spacing; } memcpy( tptr, pivot, elem_size); } } } #endif } /* Given a unit vector, this creates a perpendicular xi_vect in the xy plane and an eta_vect perpendicular to them both. */ static void create_orthogonal_vects( const double *v, double *xi_vect, double *eta_vect) { const double tval = sqrt( v[0] * v[0] + v[1] * v[1]); xi_vect[2] = 0.; if( !tval) /* 'mid' is directly at a celestial pole */ { xi_vect[0] = 1.; xi_vect[1] = 0.; } else { xi_vect[0] = v[1] / tval; xi_vect[1] = -v[0] / tval; } vector_cross_product( eta_vect, v, xi_vect); } /* relative_motion() is intended for situations where you've computed that an object moved from p1 to p2, and want to compare that motion vector to an observed motion from p3 to p4. Initial use cases are in Sat_ID (we have a computed motion from TLEs and two observed RA/decs) and astcheck (similar, but the computed positions are from orbital elements). In each case, you're trying to determine if the observed and computed motions match to within some threshhold. I used to do this by computing (delta_RA * cos_dec, delta_dec) for both observed and computed motions. That works well as long as the two points are very close together. As they're separated, you get distortions. This should be more accurate. Given four points (ra_dec[0], ra_dec[1]) = starting point object 1 (ra_dec[2], ra_dec[3]) = ending point object 1 (ra_dec[4], ra_dec[5]) = starting point object 2 (ra_dec[6], ra_dec[7]) = ending point object 2 we compute their (xi, eta) sky plane coordinates, using a plane tangent to the midpoint of the starting locations. That way, any distortion will affect both ends equally. Then we compute how far each object moved in the sky plane coordinates. Then we compute the differences in speed. */ double relative_motion( const double *ra_dec) { double mid[3], xi_vect[3], eta_vect[3]; double xi[4], eta[4], v[4][3]; double delta_xi, delta_eta; int i; for( i = 0; i < 4; i++) polar3_to_cartesian( v[i], ra_dec[i + i], ra_dec[i + i + 1]); for( i = 0; i < 3; i++) mid[i] = v[0][i] + v[1][i] + v[2][i] + v[3][i]; normalize_vect3( mid); create_orthogonal_vects( mid, xi_vect, eta_vect); for( i = 0; i < 4; i++) { const double dist = dot_product( mid, v[i]); xi[i] = dot_product( xi_vect, v[i]) / dist; eta[i] = dot_product( eta_vect, v[i]) / dist; } delta_xi = (xi[0] - xi[1]) - (xi[2] - xi[3]); delta_eta = (eta[0] - eta[1]) - (eta[2] - eta[3]); return( sqrt( delta_xi * delta_xi + delta_eta * delta_eta)); } static double angular_sep( const double delta_ra, const double dec1, const double dec2, double *posn_ang) { double p1[2], p2[2], dist; p1[0] = 0.; p1[1] = dec1; p2[0] = delta_ra; p2[1] = dec2; calc_dist_and_posn_ang( p1, p2, &dist, posn_ang); if( posn_ang) *posn_ang *= 180. / PI; return( dist); } /* Out of all observations for a given object, this function will pick two that "best" describe the object's motion. For that purpose, we look for a pair closest to 'optimal_dist' apart. We also limit the separation in time to 'max_time_sep'; that's to avoid a situation where the observations are really close to the optimal distance apart, but are actually from different orbits. This code thinks in terms of pairs of observations. If somebody insists on providing a single observation, we duplicate it. (Unless the '-1' switch is specified, in which case single observations are just dropped.) */ static bool include_singletons = true; static double find_good_pair( OBSERVATION *obs, const size_t n_obs, size_t *idx1, size_t *idx2) { size_t a, b; double speed = 0., dt; const double max_time_sep = 0.1; /* .1 day = 2.4 hr */ double best_score = 1e+30; *idx1 = *idx2 = 0; for( b = 0; b < n_obs; b++) for( a = b + 1; a < n_obs && (dt = obs[a].jd - obs[b].jd) < max_time_sep; a++) if( !memcmp( &obs[a].text[77], &obs[b].text[77], 3)) { const double optimal_dist = PI / 180.; /* one degree */ const double dist = angular_sep( obs[b].ra - obs[a].ra, obs[b].dec, obs[a].dec, NULL); const double score = fabs( dist - optimal_dist); assert( dt >= .0); if( best_score > score) { best_score = score; *idx2 = a; *idx1 = b; speed = dist / dt; } } speed *= 180. / PI; /* cvt speed from radians/day to deg/day */ speed /= hours_per_day; /* ...then to deg/hour = arcmin/minute */ return( speed); } /* Aberration from the Ron-Vondrak method, from Meeus' _Astronomical Algorithms_, p 153, just the leading terms */ static void compute_aberration( const double t_cen, double *ra, double *dec) { const double l3 = 1.7534703 + 628.3075849 * t_cen; const double sin_l3 = sin( l3), cos_l3 = cos( l3); const double sin_2l3 = 2. * sin_l3 * cos_l3; const double cos_2l3 = 2. * cos_l3 * cos_l3 - 1.; const double x = -1719914. * sin_l3 - 25. * cos_l3 +6434. * sin_2l3 + 28007 * cos_2l3; const double y = 25. * sin_l3 + 1578089 * cos_l3 +25697. * sin_2l3 - 5904. * cos_2l3; const double z = 10. * sin_l3 + 684185. * cos_l3 +11141. * sin_2l3 - 2559. * cos_2l3; const double c = 17314463350.; /* speed of light is 173.1446335 AU/day */ const double sin_ra = sin( *ra), cos_ra = cos( *ra); *ra -= (y * cos_ra - x * sin_ra) / (c * cos( *dec)); *dec += ((x * cos_ra + y * sin_ra) * sin( *dec) - z * cos( *dec)) / c; } static void error_exit( const int exit_code) { printf( "sat_id takes the name of an input file of MPC-formatted (80-column)\n\ astrometry as a command-line argument. It searches for matches between\n\ the observation data and satellites in TLEs specified in 'tle_list.txt'.\n\ By default, matches within .2 degrees are shown.\n\n\ Additional command-line arguments are:\n\ -a YYYYMMDD Only use observations after this time\n\ -b YYYYMMDD Only use observations before this time\n\ -c Check all TLEs for existence\n\ -m (nrevs) Only consider objects with fewer # revs/day (default=6)\n\ -n (NORAD) Only consider objects with this NORAD identifier\n\ -r (radius) Only show matches within this radius in degrees (default=4)\n\ -t (fname) Get TLEs from this filename\n\ -v Verbose output. '-v2' gets still more verboseness.\n\ -y Set tolerance for apparent motion mismatch\n\ -z (rate) Only consider observations above 'rate' deg/hr (default=.001)\n\ \n\ See 'sat_id.txt' for additional details.\n"); exit( exit_code); } static int compute_artsat_ra_dec( double *ra, double *dec, double *dist, const OBSERVATION *optr, tle_t *tle, const double *sat_params, bool *in_shadow) { double pos[3]; /* Satellite position vector */ double sun_xyzr[4], tval; double t_since = (optr->jd - tle->epoch) * minutes_per_day; const double j2000 = 2451545.; /* JD 2451545 = 2000 Jan 1.5 */ int sxpx_rval; if( select_ephemeris( tle)) sxpx_rval = SDP4( t_since, tle, sat_params, pos, NULL); else sxpx_rval = SGP4( t_since, tle, sat_params, pos, NULL); if( sxpx_rval == SXPX_WARN_PERIGEE_WITHIN_EARTH) sxpx_rval = 0; if( verbose > 2 && sxpx_rval) printf( "TLE failed for JD %f: %d\n", optr->jd, sxpx_rval); get_satellite_ra_dec_delta( optr->observer_loc, pos, ra, dec, dist); compute_aberration( (optr->jd - j2000) / 36525., ra, dec); lunar_solar_position( optr->jd, NULL, sun_xyzr); ecliptic_to_equatorial( sun_xyzr); tval = dot_product( sun_xyzr, pos); if( tval < 0. && in_shadow) /* elongation greater than 90 degrees; */ { /* may be in earth's shadow */ double tvect[3], r2 = 0.; const double earth_r = EARTH_MAJOR_AXIS / 1000.; /* in km */ size_t i; tval /= sun_xyzr[3] * sun_xyzr[3]; for( i = 0; i < 3; i++) { tvect[i] = pos[i] - sun_xyzr[i] * tval; r2 += tvect[i] * tvect[i]; } *in_shadow = (r2 < earth_r * earth_r); } else if( in_shadow) *in_shadow = false; return( sxpx_rval); } static bool is_in_range( const double jd, const double tle_start, const double tle_range) { return( !tle_range || !tle_start || (jd >= tle_start && jd <= tle_start + tle_range)); } /* Determines if we have _any_ observations between the given JDs. If we don't, we can skip an individual TLE or an entire file. */ static bool got_obs_in_range( const object_t *objs, size_t n_objects, const double jd_start, const double jd_end) { while( n_objects--) { OBSERVATION *obs = objs->obs; size_t i; if( obs[0].jd < jd_end && obs[objs->n_obs - 1].jd > jd_start) for( i = 0; i < objs->n_obs; i++) if( obs[i].jd > jd_start && obs[i].jd < jd_end) return( true); objs++; } return( false); } static int _pack_intl_desig( char *desig_out, const char *desig) { size_t i = 0; int rval = 0; while( desig[i] && isdigit( desig[i])) i++; memset( desig_out, ' ', 8); desig_out[8] = '\0'; if( i == 5 && isupper( desig[5])) /* already in YYYNNAaa form */ { memcpy( desig_out, desig, 5); desig += 5; } else if( i == 4 && desig[4] == '-' && isdigit( desig[5]) && isdigit( desig[6]) && isdigit( desig[7]) && isupper( desig[8])) { desig_out[0] = desig[2]; /* desig is in YYYY-NNNAaa form */ desig_out[1] = desig[3]; desig_out[2] = desig[5]; desig_out[3] = desig[6]; desig_out[4] = desig[7]; desig += 8; } else /* not a recognized intl desig form */ rval = -1; if( !rval) { desig_out[5] = *desig++; if( isupper( *desig)) desig_out[6] = *desig++; if( isupper( *desig)) desig_out[7] = *desig++; } return( rval); } static int _compare_intl_desigs( const char *desig1, const char *desig2) { char odesig1[9], odesig2[9]; _pack_intl_desig( odesig1, desig1); _pack_intl_desig( odesig2, desig2); return( strcmp( odesig1, odesig2)); } /* Code to look through 'sat_xref.txt', if available, and assign NORAD and international designations to TLEs with only default designations. See 'sat_xref.txt' and 'eph2tle.cpp' in the Find_Orb repository. */ static int look_up_extended_identifiers( const char *line0, tle_t *tle) { static char buff[100]; int match_found = !strcmp( line0, buff + 21); static bool got_sat_xref_txt = true; /* until proven otherwise */ if( !match_found && got_sat_xref_txt) { FILE *ifile = local_then_config_fopen( "sat_xref.txt", "rb"); if( !ifile) /* don't look for it again */ got_sat_xref_txt = false; else { while( !match_found && fgets_trimmed( buff, sizeof( buff), ifile)) match_found = !strcmp( line0, buff + 21); fclose( ifile); } } if( match_found) { tle->norad_number = atoi( buff); memcpy( tle->intl_desig, buff + 12, 8); } return( match_found); } /* The international (YYYY-NNNletter(s)) designation and the NORAD five-digit designation are always shown for a match. If they're in the name as well, we should remove them so that more of the actual name gets displayed. */ static void remove_redundant_desig( char *name, const char *desig) { size_t len = strlen( desig), i; while( len && desig[len - 1] == ' ') len--; for( i = 0; name[i]; i++) if( !memcmp( name + i, desig, len)) { size_t n = len; if( i >= 3 && name[i - 2] == '=' && name[i - 1] == ' ' && name[i - 3] == ' ') { i -= 3; /* remove preceding '=' */ n += 3; } else if( name[i + n] == ' ' && name[i + n + 1] == '=' && name[i + n + 2] == ' ') n += 3; memmove( name + i, name + i + n, strlen( name + i + n) + 1); i--; } } static char *gzgets_trimmed( gzFile ifile, char *buff, const size_t buff_len) { char *rval = gzgets( ifile, buff, (int)buff_len); if( rval) { size_t len = strlen( buff); while( len && buff[len - 1] <= ' ') len--; buff[len] = '\0'; } return( rval); } /* We check astrometry first against TLEs from github.com/Bill-Gray/tles, then some other sources such as the amateur community's TLEs, and only then against Space-Track TLEs. If we've already checked an object against the previous sources, using TLEs whose date range would cover that object, then we really ought not to check the Space-Track TLEs. For one thing, the mere fact that we've gone to the effort of computing "our own" TLEs means the Space-Track TLEs are unreliable (some have poor accuracy; others are not consistently available). Therefore, we maintain a list of '# ID:' numbers from tle_list.txt, and when we get to '# ID off', we figure we're in Space-Track TLE territory. If we encounter a NORAD number that's in our list, we essentially say : we already found this object and have handled it. */ typedef struct { int norad_number; double min_jd, max_jd; } already_found_t; static bool already_found_desig( const int curr_norad, size_t n_found, const already_found_t *found, double jd) { bool rval = false; for( size_t i = 0; !rval && i < n_found; i++) rval = (curr_norad == found[i].norad_number && jd > found[i].min_jd && jd < found[i].max_jd); return( rval); } /* By default, we warn you if TLEs are going to run out next week. */ static double lookahead_warning_days = 7.; /* The computed and observed motions should match, but (obviously) only to some tolerance. A tolerance of 60 arcseconds seems to work. (May seem large, but these objects often 'streak' and have large residuals.) */ double motion_mismatch_limit = 60.; /* Given a set of MPC observations and a TLE file, this function looks at each TLE in the file and checks to see if that satellite came close to any of the observations. The function is called for each TLE file. */ int norad_id = 0; const char *intl_desig = NULL; static int search_norad = 0; static char search_intl[12]; double tle_start = 0., tle_range = 1e+9; /* The following may be reset for a particular object where we have TLEs that we expect have a certain maximum expected error, using a line starting with # Max error. This lets us use a large search radius for some objects with poor TLEs, but restrict the radius tightly for objects that we know we have a good handle on. */ static double max_expected_error = 180.; static int n_tles_expected_in_file = 0; static int add_tle_to_obs( object_t *objects, const size_t n_objects, const char *tle_file_name, const double search_radius, const double max_revs_per_day) { char line0[100], line1[100], line2[100]; gzFile tle_file; int rval = 0, n_tles_found = 0; bool check_updates = true; bool look_for_tles = true; static bool error_check_date_ranges = true; const clock_t time_started = clock( ); static already_found_t *norad_ids = NULL; static size_t n_norad_ids = 0; const double mjd_1970 = 40587.; /* MJD for 1970 Jan 1 */ const double curr_mjd = mjd_1970 + (double)time( NULL) / 86400.; if( !tle_file_name) /* flag to free internal memory at shutdown */ { if( norad_ids) free( norad_ids); norad_ids = NULL; n_norad_ids = 0; return( 0); } tle_file = gzopen( tle_file_name, "rb"); if( !tle_file) { char buff[200]; /* try again with .gz added to the filename */ strlcpy_error( buff, tle_file_name); strlcat_error( buff, ".gz"); tle_file = gzopen( buff, "rb"); } if( !tle_file) { #ifdef ON_LINE_VERSION printf( "

WARNING : '%s' not opened
\n", tle_file_name); printf( "Please e-mail pluto\x40projectpluto\x2e\x63om about this.

\n"); #else fprintf( stderr, "WARNING : '%s' not opened\n", tle_file_name); fprintf( stderr, "Please e-mail pluto\x40projectpluto\x2e\x63om about this.\n"); #endif return( -1); } if( verbose) printf( "Looking through TLE file '%s', %u objs, radius %f, max %f revs/day\n", tle_file_name, (unsigned)n_objects, search_radius, max_revs_per_day); *line0 = *line1 = '\0'; while( gzgets_trimmed( tle_file, line2, sizeof( line2))) { tle_t tle; /* Structure for two-line elements set for satellite */ const double mins_per_day = 24. * 60.; bool is_a_tle = false; if( verbose > 3) printf( "%s\n", line2); if( look_for_tles && parse_elements( line1, line2, &tle) >= 0) { is_a_tle = true; n_tles_found++; if( tle.norad_number == 99999) look_up_extended_identifiers( line0, &tle); if( line0[0] == '0' && line0[1] == ' ') memmove( line0, line0 + 2, strlen( line0 + 1)); if( (search_norad && search_norad != tle.norad_number) || (*search_intl && strcmp( search_intl, tle.intl_desig))) { fprintf( stderr, REVERSE_VIDEO "WARNING: desigs mismatch the 'ID' field : %s" NORMAL_VIDEO "\n", tle_file_name); fprintf( stderr, "NORAD IDs are %d, %d\n", search_norad, tle.norad_number); fprintf( stderr, "Int'l IDs are '%s', '%s'\n", search_intl, tle.intl_desig); search_norad = 0; /* suppress cascading errors */ *search_intl = '\0'; } } if( is_a_tle && (tle.ephemeris_type == 'H' || tle.xno < 2. * PI * max_revs_per_day / mins_per_day) && (!norad_id || norad_id == tle.norad_number) && (!intl_desig || !_compare_intl_desigs( tle.intl_desig, intl_desig))) { /* hey! we got a TLE! */ double sat_params[N_SAT_PARAMS]; size_t idx; if( verbose > 1) printf( "TLE found:\n%s\n%s\n", line1, line2); if( select_ephemeris( &tle)) SDP4_init( sat_params, &tle); else SGP4_init( sat_params, &tle); for( idx = 0; idx < n_objects; idx++) { object_t *obj_ptr = objects + idx; const OBSERVATION *optr1 = obj_ptr->obs + obj_ptr->idx1; const OBSERVATION *optr2 = obj_ptr->obs + obj_ptr->idx2; assert( obj_ptr->idx1 <= obj_ptr->idx2); assert( optr2->jd >= optr1->jd); if( is_in_range( optr1->jd, tle_start, tle_range) && (search_norad || !already_found_desig( tle.norad_number, n_norad_ids, norad_ids, optr1->jd))) { double radius; double ra, dec, dist_to_satellite; int sxpx_rval; size_t i = 0; bool in_shadow; sxpx_rval = compute_artsat_ra_dec( &ra, &dec, &dist_to_satellite, optr1, &tle, sat_params, &in_shadow); radius = angular_sep( ra - optr1->ra, dec, optr1->dec, NULL) * 180. / PI; while( i < obj_ptr->n_matches && obj_ptr->matches[i].norad_number != tle.norad_number && obj_ptr->matches[i].norad_number) i++; if( !sxpx_rval && radius < search_radius /* good enough for us! */ && radius < max_expected_error && i == obj_ptr->n_matches) { double dt = optr2->jd - optr1->jd; const double min_dt = 1e-6; /* 0.0864 seconds */ double motion_diff, ra2, dec2; double temp_array[8]; bool show_computed_motion = true; assert( dt >= 0.); if( !dt) { OBSERVATION temp_obs = *optr2; double dist2; temp_obs.jd += min_dt; if( memcmp( temp_obs.text + 77, "247", 3)) set_observer_location( &temp_obs); if( vector3_length( optr2->observer_loc) > 6400.) show_computed_motion = false; /* spacecraft-based obs */ compute_artsat_ra_dec( &ra2, &dec2, &dist2, &temp_obs, &tle, sat_params, NULL); } else compute_artsat_ra_dec( &ra2, &dec2, &dist_to_satellite, optr2, &tle, sat_params, NULL); temp_array[0] = ra; /* starting point (computed) */ temp_array[1] = dec; temp_array[2] = ra2; /* ending point (computed) */ temp_array[3] = dec2; temp_array[4] = optr1->ra; /* starting point (observed) */ temp_array[5] = optr1->dec; temp_array[6] = optr2->ra; /* ending point (observed) */ temp_array[7] = optr2->dec; if( !dt) motion_diff = 0.; else motion_diff = relative_motion( temp_array); motion_diff *= 3600. * 180. / PI; /* cvt to arcseconds */ if( motion_diff < motion_mismatch_limit) { char obuff[200]; char full_intl_desig[20]; double motion_rate = 0., motion_pa = 0.; const double arcminutes_per_radian = 60. * 180. / PI; motion_rate = angular_sep( optr1->ra - optr2->ra, optr1->dec, optr2->dec, &motion_pa); motion_rate *= arcminutes_per_radian; if( dt) motion_rate /= dt * minutes_per_day; line1[8] = line1[16] = '\0'; memcpy( line1 + 30, line1 + 11, 6); line1[11] = '\0'; i = 0; while( i < obj_ptr->n_matches && radius > obj_ptr->matches[i].dist) i++; obj_ptr->matches = (match_t *)realloc( obj_ptr->matches, (obj_ptr->n_matches + 1) * sizeof( match_t)); memmove( obj_ptr->matches + i + 1, obj_ptr->matches + i, (obj_ptr->n_matches - i) * sizeof( match_t)); memset( obj_ptr->matches + i, 0, sizeof( match_t)); obj_ptr->matches[i].dist = radius; obj_ptr->matches[i].norad_number = tle.norad_number; obj_ptr->n_matches++; strncpy( obj_ptr->matches[i].intl_desig, tle.intl_desig, 9); snprintf_err( full_intl_desig, sizeof( full_intl_desig), "%s%.2s-%s", (tle.intl_desig[0] < '5' ? "20" : "19"), tle.intl_desig, tle.intl_desig + 2); snprintf_err( obuff, sizeof( obuff), " %05dU = %-11s ", tle.norad_number, full_intl_desig); if( tle.ephemeris_type != 'H') snprintf_append( obuff, sizeof( obuff), "e=%.2f; P=%.1f min; i=%.1f", tle.eo, 2. * PI / tle.xno, tle.xincl * 180. / PI); if( tle_checksum( line0)) /* object name given... */ { char norad_desig[20]; remove_redundant_desig( line0, full_intl_desig); snprintf( norad_desig, sizeof( norad_desig), "NORAD %05d", tle.norad_number); remove_redundant_desig( line0, norad_desig); snprintf_append( obuff, sizeof( obuff), ": %s", line0); } strlcpy( obj_ptr->matches[i].text, obuff + 26, sizeof( obj_ptr->matches[i].text)); obuff[79] = '\0'; /* avoid buffer overrun */ // snprintf_append( obuff, sizeof( obuff), " motion %f", motion_diff); strlcat_error( obuff, "\n"); if( !dt) strlcat_error( obuff, " no observed motion (single obs) "); else snprintf_append( obuff, sizeof( obuff), " motion %7.4f\"/sec at PA %5.1f;", motion_rate, motion_pa); snprintf_append( obuff, sizeof( obuff), " dist=%8.1f km; offset=%7.4f deg\n", dist_to_satellite, radius); /* "Speed" is displayed in arcminutes/second, or in degrees/minute */ if( verbose || !field_mode) { printf( "%s\n", optr1->text); printf( "%s", obuff); #ifdef SHOW_RA_DEC_OFFSETS printf( "dRA = %.3f dDec = %.3f\n", (ra - optr1->ra) * 180. / PI, (dec - optr1->dec) * 180. / PI); #endif } motion_rate = angular_sep( ra - ra2, dec, dec2, &motion_pa); motion_rate *= arcminutes_per_radian; if( dt) motion_rate /= dt * minutes_per_day; else motion_rate /= min_dt * minutes_per_day; if( show_computed_motion && (verbose || !field_mode)) printf( " motion %7.4f\"/sec at PA %5.1f (computed)\n\n", motion_rate, motion_pa); obj_ptr->matches[i].ra = ra; obj_ptr->matches[i].dec = dec; obj_ptr->matches[i].motion_rate = motion_rate; obj_ptr->matches[i].motion_pa = motion_pa; obj_ptr->matches[i].in_shadow = in_shadow; } } } } } else if( !strncmp( line2, "# No updates", 12)) check_updates = false; else if( !strncmp( line2, "# Max error", 11)) max_expected_error = atof( line2 + 12); else if( !strncmp( line2, "# TLEs expected:", 16)) n_tles_expected_in_file = atoi( line2 + 17); else if( !strncmp( line2, "# Range_check ", 14)) error_check_date_ranges = (line2[14] != '0'); else if( !strncmp( line2, "# Ephem range:", 14)) { double mjd_start, mjd_end, tle_step; int n_read; n_read = sscanf( line2 + 14, "%lf %lf %lf\n", &mjd_start, &mjd_end, &tle_step); assert( n_read == 3); if( error_check_date_ranges && tle_start) /* do date ranges specified */ { /* in tle_list.txt and in the TLE file itself match? */ const double tolerance = 0.001; /* an allowance for roundoff */ if( fabs( mjd_start + 2400000.5 - tle_start) > tolerance) { char time_buff[40]; fprintf( stderr, REVERSE_VIDEO "WARNING: starting date for TLEs in '%s' " "mismatches that in tle_list.txt\n" NORMAL_VIDEO, tle_file_name); full_ctime( time_buff, tle_start, FULL_CTIME_YMD); fprintf( stderr, "TLE list start MJD %f = %s\n", tle_start - 2400000.5, time_buff); full_ctime( time_buff, mjd_start + 2400000.5, FULL_CTIME_YMD); fprintf( stderr, "'Range:' line start MJD %f = %s\n", mjd_start, time_buff); fprintf( stderr, "diff = %f\n", mjd_start + 2400000.5 - tle_start); } if( fabs( mjd_end + 2400000.5 - tle_start - tle_range) > tolerance) { char time_buff[40]; fprintf( stderr, REVERSE_VIDEO "WARNING: ending date for TLES in '%s' " "mismatches that in tle_list.txt\n" NORMAL_VIDEO, tle_file_name); full_ctime( time_buff, tle_start + tle_range, FULL_CTIME_YMD); fprintf( stderr, "TLE list ends MJD %f = %s\n", tle_start + tle_range - 2400000.5, time_buff); full_ctime( time_buff, mjd_end + 2400000.5, FULL_CTIME_YMD); fprintf( stderr, "'Range:' line ends MJD %f = %s\n", mjd_end, time_buff); fprintf( stderr, "diff = %f\n", mjd_end + 2400000.5 - tle_start - tle_range); } } tle_range = tle_step; if( check_updates && mjd_end < curr_mjd + lookahead_warning_days) { char time_buff[40]; full_ctime( time_buff, mjd_end + 2400000.5, FULL_CTIME_YMD); fprintf( stderr, REVERSE_VIDEO "WARNING: TLEs in '%s' run out on %s (%.2f days)\n" NORMAL_VIDEO, tle_file_name, time_buff, mjd_end - curr_mjd); } if( !got_obs_in_range( objects, n_objects, mjd_start + 2400000.5, mjd_end + 2400000.5) && !check_all_tles) { if( verbose) fprintf( stderr, REVERSE_VIDEO "'%s' contains no TLEs for our time range\n" NORMAL_VIDEO, tle_file_name); gzclose( tle_file); return( 0); } } else if( !memcmp( line2, "# ID: ", 6)) { int n_read = sscanf( line2 + 6, "%d %s", &search_norad, search_intl); size_t i; bool is_natural; assert( 2 == n_read); for( i = strlen( search_intl); i < 8; i++) search_intl[i] = ' '; search_intl[8] = '\0'; is_natural = isdigit( search_intl[7]); if( identify_filter == IDENTIFY_ARTSATS && is_natural) look_for_tles = false; if( identify_filter == IDENTIFY_NATSATS && !is_natural) look_for_tles = false; } else if( !memcmp( line2, "# ID off", 8)) { search_norad = 0; *search_intl = '\0'; if( my_tles_only) break; } else if( !memcmp( line2, "# Range: ", 9)) { int n_read; char start[20], end[20]; n_read = sscanf( line2 + 9, "%19s %19s", start, end); assert( n_read == 2); tle_start = get_time_from_string( 0, start, FULL_CTIME_YMD, NULL); tle_range = get_time_from_string( 0, end, FULL_CTIME_YMD, NULL) - tle_start; if( !check_all_tles) look_for_tles = got_obs_in_range( objects, n_objects, tle_start, tle_start + tle_range); } else if( !memcmp( line2, "# MJD ", 6)) { tle_start = atof( line2 + 6) + 2400000.5; // look_for_tles = got_obs_in_range( objects, n_objects, tle_start, // tle_start + tle_range); } else if( !memcmp( line2, "# Include ", 10)) { if( look_for_tles) { char iname[255]; size_t i = strlen( tle_file_name); const double saved_max_expected_error = max_expected_error; while( i && tle_file_name[i - 1] != '/' && tle_file_name[i - 1] != '\\') i--; memcpy( iname, tle_file_name, i); strlcpy_err( iname + i, line2 + 10, sizeof( iname) - i); if( verbose > 1) printf( "Including '%s'\n", iname); if( search_norad) { /* add newly-found ID */ if( !n_norad_ids) norad_ids = (already_found_t *)malloc( sizeof( already_found_t)); else if( is_power_of_two( n_norad_ids)) norad_ids = (already_found_t *)realloc( norad_ids, 2 * n_norad_ids * sizeof( already_found_t)); norad_ids[n_norad_ids].norad_number = search_norad; norad_ids[n_norad_ids].min_jd = tle_start; norad_ids[n_norad_ids].max_jd = tle_start + tle_range; n_norad_ids++; } rval = add_tle_to_obs( objects, n_objects, iname, search_radius, max_revs_per_day); max_expected_error = saved_max_expected_error; } tle_start = 0.; tle_range = 1e+9; look_for_tles = true; } else if( !memcmp( line2, "# Warn ", 7)) { char *tptr = strchr( line2, '|'); double jd_warn; assert( tptr); *tptr = '\0'; jd_warn = get_time_from_string( 0, line2 + 7, FULL_CTIME_YMD, NULL); if( jd_warn < curr_mjd + 2400000.5 + lookahead_warning_days) fprintf( stderr, REVERSE_VIDEO "WARNING: %s" NORMAL_VIDEO "\n", tptr + 1); } strlcpy_error( line0, line1); strlcpy_error( line1, line2); } if( verbose) printf( "%d TLEs read from '%s', %.3f seconds\n", n_tles_found, tle_file_name, (double)( clock( ) - time_started) / (double)CLOCKS_PER_SEC); if( n_tles_found < n_tles_expected_in_file) { const char *err_message = REVERSE_VIDEO "**** WARNING : %d TLEs were read from '%s'. At least %d were expected.\n" NORMAL_VIDEO; #ifdef ON_LINE_VERSION printf( err_message, n_tles_found, tle_file_name, n_tles_expected_in_file); #else fprintf( stderr, err_message, n_tles_found, tle_file_name, n_tles_expected_in_file); #endif printf( "Please e-mail the author (pluto at projectpluto dot com) about this.\n"); } gzclose( tle_file); return( rval); } /* Output punch-card formatted astrometry is time-stamped when possible. The scheme is the same as used for time-stamping NEOCP observations in 'neocp.cpp' and 'neocp2.cpp' in the 'miscell' repository (q.v) and makes use of the fact that columns 57 to 65 default to being spaces. */ static void time_tag( char *tag) { if( !memcmp( tag, " ", 5)) { const time_t t0 = time( NULL); struct tm tm; #if defined( _WIN32) || defined( __WATCOMC__) memcpy( &tm, gmtime( &t0), sizeof( tm)); #else gmtime_r( &t0, &tm); #endif tag[0] = '~'; tag[1] = int_to_mutant_hex_char( tm.tm_mon + 1); tag[2] = int_to_mutant_hex_char( tm.tm_mday); tag[3] = int_to_mutant_hex_char( tm.tm_hour); tag[4] = int_to_mutant_hex_char( tm.tm_min); } } /* Given a 'packed' international designation such as 92044A or 10050BZ, this outputs the 'unpacked/ desig 1992-044A or 2010-050BZ. */ static char *unpack_intl( const char *packed, char *unpacked) { snprintf( unpacked, 12, "%s%.2s-%s", (atoi( packed) > 57000 ? "19" : "20"), packed, packed + 2); return( unpacked); } /* The "on-line version", sat_id2, gathers data from a CGI multipart form, puts it into a file, possibly adds in some options, puts together the command-line arguments, and then calls sat_id_main. See 'sat_id2.cpp'. By default, TLE information is drawn from 'tle_list.txt'; this can be overridden with the -t (filename) option. The specified file is first searched for in the current directory, then in ~/.find_orb, then in ~/.find_orb/tles, then in ~/tles. */ #ifdef ON_LINE_VERSION int sat_id_main( const int argc, const char **argv) #else int main( const int argc, const char **argv) #endif { char tle_file_name[256]; const char *tname = "tle_list.txt"; const char *output_astrometry_filename = NULL; bool output_only_matches = false; const char *ifilename = NULL; FILE *ifile; OBSERVATION *obs; object_t *objects; size_t n_obs, n_objects; double search_radius = 4; /* default to 4-degree search */ /* Asteroid searchers _sometimes_ report Molniyas to me, which make two revolutions a day. This limit could safely be set to three, but few artsats are between 6 and 3 revs/day (i.e., in four to eight-hour orbits). So this doesn't result in much extra checking. */ double max_revs_per_day = 6.; /* Anything slower than 0.001'/sec is almost certainly not an artsat. We don't even bother to check those (unless the -z option is used to reset this limit). */ double speed_cutoff = 0.001; double t_low = oct_4_1957; double t_high = jan_1_2057; int rval, i, j, prev_i; bool show_summary = false, add_new_line = false, all_single = false; if( argc == 1) { fprintf( stderr, "No input file of astrometry specified on command line\n\n"); error_exit( -2); } for( i = 1; i < argc; i++) if( argv[i][0] == '-') { const char *param = argv[i] + 2; if( !*param && i < argc - 1) param = argv[i + 1]; switch( argv[i][1]) { case '1': include_singletons = false; break; case 'a': t_low = get_time_from_string( 0, param, FULL_CTIME_YMD, NULL); break; case 'b': t_high = get_time_from_string( 0, param, FULL_CTIME_YMD, NULL); break; case 'c': check_all_tles = true; break; case 'd': _target_desig = param; break; case 'f': if( *param == 'n') identify_filter = IDENTIFY_NATSATS; else if( *param == 'a') identify_filter = IDENTIFY_ARTSATS; else { fprintf( stderr, "Bad command line switch '%s'\n", argv[i]); fprintf( stderr, "Use -fa to show only artsats, -fn to show only natsats\n"); return( -1); } break; case 'i': intl_desig = param; break; case 'l': lookahead_warning_days = atof( param); break; case 'r': search_radius = atof( param); break; case 'y': motion_mismatch_limit = atof( param); break; case 'm': max_revs_per_day = atof( param); break; case 'n': norad_id = atoi( param); break; case 'N': add_new_line = true; break; case 'o': case 'O': output_astrometry_filename = param; output_only_matches = (argv[i][1] == 'o'); break; case 's': my_tles_only = true; break; case 'S': /* treat each observation individually */ all_single = true; break; case 't': tname = param; break; case 'u': show_summary = true; break; case 'v': verbose = atoi( param) + 1; break; case 'z': speed_cutoff = atof( param); break; default: fprintf( stderr, "Unrecognized command-line option '%s'\n", argv[i]); error_exit( -3); break; } } else if( !ifilename) ifilename = argv[i]; if( verbose) for( i = 0; i < argc; i++) printf( "Arg %d: '%s'\n", i, argv[i]); strlcpy_error( tle_file_name, tname); #if !defined( _WIN32) && !defined( __WATCOMC__) if( access( tle_file_name, F_OK)) { if( verbose) fprintf( stderr, "'%s' failed\n", tle_file_name); make_config_dir_name( tle_file_name, tname); if( access( tle_file_name, F_OK)) { char buff[256]; if( verbose) fprintf( stderr, "'%s' failed\n", tle_file_name); strlcpy_error( buff, "tles/"); strlcat_error( buff, tname); make_config_dir_name( tle_file_name, buff); if( access( tle_file_name, F_OK)) { if( verbose) fprintf( stderr, "'%s' failed\n", tle_file_name); strlcpy_error( tle_file_name, getenv( "HOME")); strlcat_error( tle_file_name, "/tles/"); strlcat_error( tle_file_name, tname); } } } #endif assert( ifilename); ifile = fopen( ifilename, "rb"); if( !ifile) { fprintf( stderr, "Couldn't open input file %s\n", ifilename); perror( NULL); return( -1); } obs = get_observations_from_file( ifile, &n_obs, t_low, t_high); printf( "%d observations found\n", (int)n_obs); if( !obs || !n_obs) return( -2); shellsort_r( obs, n_obs, sizeof( obs[0]), compare_obs, NULL); for( n_objects = i = 0; (size_t)i < n_obs; i++) if( !i || id_compare( obs + i - 1, obs + i) || field_mode || all_single) n_objects++; objects = (object_t *)calloc( n_objects, sizeof( object_t)); assert( objects); if( !field_mode) printf( "%d objects\n", (int)n_objects); for( n_objects = i = prev_i = 0; (size_t)i < n_obs; i++) if( !i || id_compare( obs + i - 1, obs + i) || field_mode || all_single) { objects[n_objects].obs = obs + i; if( n_objects) objects[n_objects - 1].n_obs = i - prev_i; n_objects++; prev_i = i; } objects[n_objects - 1].n_obs = i - prev_i; for( i = j = 0; (size_t)i < n_objects; i++) { objects[i].speed = find_good_pair( objects[i].obs, objects[i].n_obs, &objects[i].idx1, &objects[i].idx2); if( (objects[i].speed >= speed_cutoff && objects[i].n_obs > 1) || (include_singletons && objects[i].n_obs == 1)) { /* fast enough to be considered */ objects[j] = objects[i]; j++; } } n_objects = j; if( !field_mode) printf( "%u objects after removing slow ones\n", (unsigned)n_objects); else max_revs_per_day = 20.; /* for field-finding, list everything */ rval = add_tle_to_obs( objects, n_objects, tle_file_name, search_radius, max_revs_per_day); if( rval) fprintf( stderr, "Couldn't open TLE file %s\n", tname); else if( show_summary) { int n_matched = 0; size_t n_unmatched = 0; for( i = 0; (size_t)i < n_objects; i++) if( objects[i].n_matches) /* first show those that were IDed */ { char buff[30]; printf( "\n%.12s ", objects[i].obs->text); for( j = 0; j < (int)objects[i].n_matches; j++) printf( " %05d %s", objects[i].matches[j].norad_number, unpack_intl( objects[i].matches[j].intl_desig, buff)); if( objects[i].n_matches == 1) { char *tptr = strchr( objects[i].matches[0].text, ':'); if( tptr) /* if only one match, output */ printf( " %s", tptr + 1); /* the object name */ } if( objects[i].n_matches) n_matched++; } for( i = 0; (size_t)i < n_objects; i++) if( !objects[i].n_matches) /* now show those _not_ IDed */ { if( !n_unmatched) printf( "\nUnmatched objects :"); printf( "%s%.12s", (n_unmatched % 5 == 0 ? "\n" : ""), objects[i].obs->text); n_unmatched++; } if( n_objects) printf( "\n%d matched of %d tracklets (%.1f%%)\n", n_matched, (int)n_objects, (double)n_matched * 100. / (double)n_objects); } else if( field_mode) { const char *header = "Field RA (J2000) dec '/min PA NORAD Int'l desig Name"; printf( "%s", header); for( i = 0; (size_t)i < n_objects; i++) for( j = 0; j < (int)objects[i].n_matches; j++) { double ra = objects[i].matches[j].ra; double dec = objects[i].matches[j].dec; char buff[30]; epoch_of_date_to_j2000( objects[i].obs->jd, &ra, &dec); printf( "\n%-12.12s %7.3f %7.3f %7.2f %5.1f", objects[i].obs->text, ra * 180. / PI, dec * 180. / PI, objects[i].matches[j].motion_rate, objects[i].matches[j].motion_pa); printf( " %c %05d %-11s", objects[i].matches[j].in_shadow ? '*' : ' ', objects[i].matches[j].norad_number, unpack_intl( objects[i].matches[j].intl_desig, buff)); printf( "%s", strchr( objects[i].matches[j].text, ':') + 1); } printf( "\n%s", header); } if( output_astrometry_filename) { FILE *ofile = fopen( output_astrometry_filename, "wb"); if( !ofile) { fprintf( stderr, "Couldn't open '%s' :", output_astrometry_filename); perror( NULL); } else { char buff[256]; fseek( ifile, 0L, SEEK_SET); while( fgets( buff, sizeof( buff), ifile)) { if( strlen( buff) > 80 && buff[80] < ' ') { char tbuff[30]; bool was_matched = false; time_tag( buff + 59); for( i = 0; (size_t)i < n_objects; i++) if( !memcmp( objects[i].obs->text, buff, 12)) { if( objects[i].n_matches > 0 && objects[i].matches[0].norad_number > 0) { const char *common_name = strchr( objects[i].matches[0].text, ':') + 1; was_matched = true; unpack_intl( objects[i].matches[0].intl_desig, tbuff); if( add_new_line) if( !memcmp( tbuff + 5, "999", 3) || !memcmp( tbuff + 5, "000", 3)) { fprintf( ofile, "\nCOM =%s %05dU = %s\n", common_name, objects[i].matches[0].norad_number, tbuff); *tbuff = '\0'; } if( *tbuff) fprintf( ofile, "%sCOM %05dU = %s %s\n", (add_new_line ? "\n" : ""), objects[i].matches[0].norad_number, tbuff, common_name); objects[i].matches[0].norad_number = -1; } } if( was_matched && output_only_matches) fputs( buff, ofile); } if( !output_only_matches) fputs( buff, ofile); } fclose( ofile); } } fclose( ifile); free( obs); for( i = 0; (size_t)i < n_objects; i++) if( objects[i].matches) free( objects[i].matches); free( objects); get_station_code_data( NULL, NULL); add_tle_to_obs( NULL, 0, NULL, 0., 0.); printf( "\n%.1f seconds elapsed\n", (double)clock( ) / (double)CLOCKS_PER_SEC); return( rval); } /* End of main() */ ================================================ FILE: sat_id.txt ================================================ -a(date) : only consider observations after (date) -b(date) : only consider observations before (date) -c : check all TLEs -l(num) : set "lookahead" warning on expiring TLEs (default=7 days) -m(num) : ignore TLEs in orbits lower than (num) revs/day (default=6) -n(num) : only look for NORAD ID (num) -o(filename) : output astrometry with IDs added -r(num) : set tolerance for computed-observed dist -t(filename) : set input TLE file name -u : show a summary of results -y(num) : set tolerance for apparent motion mismatch -z(num) : ignore objects slower than (num) arcmin/sec -a(date) tells Sat_ID to ignore observations from after a certain date; -b(date) tells it to ignore observations from _before_ a certain time. The date format is flexible, but something like "-a2021Jan13" is a good choice (four-digit year and month names mean there's no confusion about what's the date, what's the month, and what's the year... dates such as "11/09/08" can lead to madness.) For example, I might check my large, multi-year NEOCP archives for just the objects found in late January 2021 by running sat_id neocp.old -a2021jan15 -b2021feb1 -c is basically for debugging purposes. Normally, Sat_ID gets its idea of which TLEs to check from the file 'tle_list.txt', which gives it a long list of files. And normally, it'll ignore most of them; give it some observations from 2021, and it's bright enough to realize that the TLEs for 2020 can be skipped. -c causes Sat_ID to check those files anyway, which sometimes lets me see my blunders (files that don't exist or are corrupted or don't actually contain TLEs.) -l sets a "lookahead" time for expiring TLEs. I can usually only compute TLEs for just so far into the future. If they're about to run out for a particular object in (by default) a week, you get a warning message to that effect. Except that on my own machine, I run it with -l10, so I'll know three days ahead of everybody else and can post updated TLEs before they even get a warning. On my own machine, though, I run it with -l10. That way, I'll know about expiring TLEs three days ahead of everybody else and can post updated TLEs before anybody else even sees a warning. At least, that's the theory... once in a while, I've been on vacation or something and people have seen those errors. -m(num) tells Sat_ID to neglect low-earth orbiters : anything making more than (num) revolutions around the earth per day, with (num) defaulting to 6 (i.e., Sat_ID won't identify objects in orbits lower than four hours). -n(num) restricts output to the specified NORAD five-digit number. Handy when you're just wondering when/where object X got caught. -o(filename) causes the input astrometry to be written to the output file with COM lines specifying to what the objects were matched. The COM lines, just ahead of the first observation for each object, look like COM 44432U = 2019-040A COM 37175U = 2010-050B The above lines are a convenience for Find_Orb. The first would tell it than the next object it finds is actually NORAD 44432 = 2019-040A = Spektr-RG; it would "remap" the designation of the next observation it found to that. The second would do the same thing, except for NORAD 37175 = 2010-050B = Chang'e 2 booster. This lets me avoid having to alter the original IDs in the astrometry, but means that I want to compute an orbit for 2010-050B, I'll get the data for it despite different designations being used. -r(num) says that the observed position of an object and the computed position from a TLE are considered to be 'matching' if the positions are within (num) degrees. Defaults to four degrees, which is probably too large... but see above comments : even with the very loose cutoffs, we rarely falsely ID a rock as an artsat. -t(filename) resets the input TLE filename. This defaults to ~/tles/tle_list.txt. But let's say I'm wondering what Space-Track can identify without my help. I might then set -t all_tle.txt, where 'all_tle.txt' is the master set of Space-Track TLEs. Or I may have created some batch of TLEs for a new object, and I'll run the MPC's ITF file or my accumulated NEOCP observations against it to see if I can spot any other finds of the new object. -u causes Sat_ID to emit a "summary" at the end, listing the objects it found in the input file and any matches. -y(num) says that the observed _motion_ of an object and the computed _motion_ from a TLE are considered to be a match if the motion is within 20 arcseconds. That is to say, if the observations say the object moved 1300 arcseconds in RA between first and last observation, and the TLEs compute a motion of 1319 arcsec over that time, it is (just barely) a match. 20 arcseconds may seem like a lot, and I used to have it set much lower than that. The problem is that if timing is off slightly, or the object fades in and out a bit, you can easily have more of a motion mismatch than you'd expect. And in general, setting this 'loose' tolerance only rarely results in objects being falsely identified as artsats. -z(speed) sets a lower limit on speed. By default, anything moving slower than 0.001'/sec is simply tossed out as too slow to possibly be an artsat; that would be -z.001. As with the -y option described above, this default is probably excessively low (could result in mistakenly identifying some rocks as artsats). But -- as with that option -- it appears to be unusual for such false detections to occur. I originally set both limts them to be tighter, but I found that Sat_ID would occasionally fail to identify real artsats. Generally speaking, it's hard for a rock to move so fast, and in the same direction and part of the sky, as an artsat that it would fool you. Note that this comes from the standpoint of a guy fed a steady diet of asteroid data, trying to filter out the occasional artsat. If you are deliberately targeting artsats and only rarely seeing rocks, you could set a still lower speed limit with -z, and a higher limit on observed motion (-y), _and_ a higher position mismatch tolerance (-r), and not get a flood of rocks misidentified as artsats. In fact, I do set looser limits when examining the NEOCP; I get some rocks misidentified as artsats as a result, which I then am usually able to rubbish. But those looser limits also allow me to occasionally say "this really is an artsat; it's just wandered further than expected from where the TLEs expected it to be." ================================================ FILE: sat_id2.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include #include #include #ifdef __has_include #if __has_include() #include "cgi_func.h" #else #error \ 'cgi_func.h' not found. This project depends on the 'lunar'\ library. See www.github.com/Bill-Gray/lunar .\ Clone that repository, 'make' and 'make install' it. #endif #else #include "cgi_func.h" #endif #include "watdefs.h" #include "stringex.h" int sat_id_main( const int argc, const char **argv); int main( const int unused_argc, const char **unused_argv) { const char *argv[20]; const size_t max_buff_size = 400000; /* room for 5000 obs */ char *buff = (char *)malloc( max_buff_size), *tptr; char field[30]; const char *temp_obs_filename = "sat_obs.txt"; double search_radius = 4.; /* look 4 degrees for matches */ double motion_cutoff = 60.; /* up to 60" discrepancy OK */ double low_speed_cutoff = 0.001; /* anything slower than this is almost */ int argc = 0; /* certainly not an artsat */ FILE *lock_file = fopen( "lock.txt", "w"); size_t bytes_written = 0, i; int cgi_status, show_summary = 0; extern int verbose; #ifndef _WIN32 extern char **environ; avoid_runaway_process( 15); #endif /* _WIN32 */ setbuf( lock_file, NULL); INTENTIONALLY_UNUSED_PARAMETER( unused_argc); INTENTIONALLY_UNUSED_PARAMETER( unused_argv); printf( "Content-type: text/html\n\n"); printf( "
\n");
   if( !lock_file)
      {
      printf( "

Server is busy. Try again in a minute or two.

"); printf( "

Your orbit is very important to us!

"); return( 0); } fprintf( lock_file, "We're in\n"); #ifndef _WIN32 for( i = 0; environ[i]; i++) fprintf( lock_file, "%s\n", environ[i]); #endif cgi_status = initialize_cgi_reading( ); fprintf( lock_file, "CGI status %d\n", cgi_status); if( cgi_status <= 0) { printf( "

CGI data reading failed : error %d ", cgi_status); printf( "This isn't supposed to happen.

\n"); return( 0); } while( !get_cgi_data( field, buff, NULL, max_buff_size)) { fprintf( lock_file, "Field '%s'\n", field); if( !strcmp( field, "TextArea") || !strcmp( field, "upfile")) { if( strlen( buff) > 70) { FILE *ofile = fopen( temp_obs_filename, (bytes_written ? "ab" : "wb")); fprintf( lock_file, "File opened : %p\n", (void *)ofile); if( !ofile) { printf( "

Couldn't open %s : %s

\n", temp_obs_filename, strerror( errno)); fprintf( lock_file, "Couldn't open %s: %s\n", temp_obs_filename, strerror( errno)); return( -1); } bytes_written += fwrite( buff, 1, strlen( buff), ofile); fclose( ofile); } } if( !strcmp( field, "radius")) { const char *verbosity = strchr( buff, 'v'); search_radius = atof( buff); if( verbosity) verbose = atoi( verbosity + 1) + 1; } if( !strcmp( field, "motion")) motion_cutoff = atof( buff); if( !strcmp( field, "low_speed")) low_speed_cutoff = atof( buff); if( !strcmp( field, "summary")) show_summary = 1; } fprintf( lock_file, "Fields read\n"); // printf( "

Fields read

\n"); if( verbose) printf( "Searching to %f degrees; %u bytes read from input\n", search_radius, (unsigned)bytes_written); argv[argc++] = "sat_id"; argv[argc++] = temp_obs_filename; argv[argc++] = "-t../../tles/tle_list.txt"; snprintf_err( field, sizeof( field), "-r%.2f", search_radius); argv[argc++] = field; snprintf_err( buff, max_buff_size, "-y%f", motion_cutoff); argv[argc++] = buff; tptr = buff + strlen( buff) + 1; snprintf( tptr, 15, "-z%f", low_speed_cutoff); argv[argc++] = tptr; if( show_summary) argv[argc++] = "-u"; argv[argc] = NULL; for( i = 0; argv[i]; i++) fprintf( lock_file, "arg %d: '%s'\n", (int)i, argv[i]); sat_id_main( argc, argv); fprintf( lock_file, "sat_id_main called\n"); free( buff); printf( "On-line Sat_ID compiled " __DATE__ " " __TIME__ " UTC-5h\n"); printf( "See " "https://www.github.com/Bill-Gray/sat_code for source code\n"); printf( "
"); return( 0); } ================================================ FILE: sat_id3.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include #include #include #ifdef __has_include #if __has_include() #include "cgi_func.h" #else #error \ 'cgi_func.h' not found. This project depends on the 'lunar'\ library. See www.github.com/Bill-Gray/lunar .\ Clone that repository, 'make' and 'make install' it. #endif #else #include "cgi_func.h" #endif #include "watdefs.h" #include "stringex.h" /* This is the code behind https://www.projectpluto.com/sat_img.htm It takes the input data, creates a temporary file containing a single 'image field data' line, then uses the Sat_ID code (see sat_id.cpp) to figure out which artsats are in that field. */ int sat_id_main( const int argc, const char **argv); int main( const int unused_argc, const char **unused_argv) { const char *argv[20]; char buff[80]; char field[30]; char search_radius[10], date[50], latitude[30], longitude[30]; char altitude[10], ra[20], dec[20]; const int argc = 3; const char *output_file_name = "field.txt"; FILE *ofile; FILE *lock_file = fopen( "lock.txt", "w"); size_t i; int cgi_status; #ifndef _WIN32 extern char **environ; avoid_runaway_process( 15); #endif /* _WIN32 */ setbuf( lock_file, NULL); INTENTIONALLY_UNUSED_PARAMETER( unused_argc); INTENTIONALLY_UNUSED_PARAMETER( unused_argv); printf( "Content-type: text/html\n\n"); printf( "
\n");
   if( !lock_file)
      {
      printf( "

Server is busy. Try again in a minute or two.

"); printf( "

Your orbit is very important to us!

"); return( 0); } fprintf( lock_file, "We're in\n"); #ifndef _WIN32 for( i = 0; environ[i]; i++) fprintf( lock_file, "%s\n", environ[i]); #endif cgi_status = initialize_cgi_reading( ); fprintf( lock_file, "CGI status %d\n", cgi_status); if( cgi_status <= 0) { printf( "

CGI data reading failed : error %d ", cgi_status); printf( "This isn't supposed to happen.

\n"); return( 0); } *search_radius = *date = *latitude = *longitude = *altitude = '\0'; *ra = *dec = '\0'; while( !get_cgi_data( field, buff, NULL, sizeof( buff))) { fprintf( lock_file, "Field '%s'\n", field); if( !strcmp( field, "radius")) { strlcpy( search_radius, buff, sizeof( search_radius)); if( !atof( search_radius)) printf( "Search radius must be non-zero\n"); } else if( !strcmp( field, "time")) strlcpy( date, buff, sizeof( date)); else if( !strcmp( field, "lat")) strlcpy( latitude, buff, sizeof( latitude)); else if( !strcmp( field, "lon")) strlcpy( longitude, buff, sizeof( longitude)); else if( !strcmp( field, "alt")) strlcpy( altitude, buff, sizeof( altitude)); else if( !strcmp( field, "ra")) strlcpy( ra, buff, sizeof( ra)); else if( !strcmp( field, "dec")) strlcpy( dec, buff, sizeof( dec)); } ofile = fopen( output_file_name, "w"); fprintf( ofile, "COD XXX\n"); fprintf( ofile, "COM Long. %s, Lat. %s, Alt. %s, unspecified\n", longitude, latitude, altitude); fprintf( ofile, "Field,%s,%s,%s,XXX\n", date, ra, dec); fclose( ofile); argv[0] = "sat_id"; argv[1] = output_file_name; argv[2] = "-t../../tles/tle_list.txt"; argv[3] = NULL; for( i = 0; argv[i]; i++) fprintf( lock_file, "arg %d: '%s'\n", (int)i, argv[i]); sat_id_main( argc, argv); fprintf( lock_file, "sat_id_main called\n"); printf( "On-line artsats-in-field-finder compiled" __DATE__ " " __TIME__ " UTC-5h\n"); printf( "See " "https://www.github.com/Bill-Gray/sat_code for source code\n"); printf( "
"); return( 0); } ================================================ FILE: sat_util.c ================================================ #include #include #include #include "sat_util.h" char *fgets_trimmed( char *buff, const int buffsize, FILE *ifile) { char *rval = fgets( buff, buffsize, ifile); if( rval) { size_t i = 0; while( rval[i] != 10 && rval[i] != 13 && rval[i]) i++; while( i && rval[i - 1] == ' ') i--; /* drop trailing spaces */ rval[i] = '\0'; } return( rval); } #if !defined( _WIN32) void make_config_dir_name( char *oname, const char *iname) { #ifdef ON_LINE_VERSION strcpy( oname, getenv( "DOCUMENT_ROOT")); strcat( oname, "/"); #else const char *home_dir = getenv( "HOME"); if( home_dir) { strcpy( oname, home_dir); strcat( oname, "/.find_orb/"); } else *oname = '\0'; #endif strcat( oname, iname); } FILE *local_then_config_fopen( const char *filename, const char *permits) { FILE *rval = fopen( filename, permits); if( !rval) { char ext_filename[255]; make_config_dir_name( ext_filename, filename); rval = fopen( ext_filename, "rb"); } return( rval); } #else FILE *local_then_config_fopen( const char *filename, const char *permits) { return( fopen( filename, permits)); } #endif ================================================ FILE: sat_util.h ================================================ #ifndef SAT_UTIL_H_INCLUDED #define SAT_UTIL_H_INCLUDED /* A few functions that are used in common by sat_id, sat_id2, and sat_eph. */ #ifdef __cplusplus extern "C" { #endif /* #ifdef __cplusplus */ FILE *local_then_config_fopen( const char *filename, const char *permits); char *fgets_trimmed( char *buff, const int buffsize, FILE *ifile); #if !defined( _WIN32) void make_config_dir_name( char *oname, const char *iname); #endif #ifdef __cplusplus } #endif /* #ifdef __cplusplus */ #endif /* #ifndef SAT_UTIL_H_INCLUDED */ ================================================ FILE: sdp4.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include #include "norad.h" #include "norad_in.h" #include /* For high satellites, we do a numerical integration that uses a rather drastic set of simplifications. We include the earth, moon, and sun, but with low-precision approximations for the positions of those last two. References are to Meeus' _Astronomical Algorithms_, 2nd edition. Results are in meters from the center of the earth, in ecliptic coordinates of date. The numerical integration is done using the 'classic' RK4 algorithm, as described at (for example) https://en.wikipedia.org/wiki/Runge–Kutta_methods The solar and lunar positions are computed using Meeus' formulae, which are a little computationally intensive. RK4 has the slight advantage of requiring us to compute lunar/solar positions only for the steps themselves and their midpoints. You probably know that the 'elements' for traditional TLEs are fitted to the SGP4 and SDP4 models : if you tried to numerically integrate TLEs using a more sophisticated model, you'd actually get _worse_ results. Similarly, the state vectors for my modified TLEs are integrated using the following model, which tries to balance accuracy and speed. Just as you shouldn't try to "improve" SGP4/SDP4, you shouldn't try to "improve" the following; it'll only break backward compatibility. */ static void raw_lunar_solar_position( const double jd, double *lunar_xyzr, double *solar_xyzr) { const double j2000 = 2451545; /* 1.5 Jan 2000 = JD 2451545 */ const double t_cen = (jd - j2000) / 36525.; /* Mean lunar longitude, (47.1) */ const double l_prime = 218.3164477 * pi / 180. + (481267.88123421 * pi / 180.) * t_cen; /* Lunar mean anomaly, (47.4) */ const double m_prime = 134.9633964 * pi / 180. + (477198.8675055 * pi / 180.) * t_cen; /* Solar mean longitude, (25.2) */ const double l_solar = 280.46646 * pi / 180. + (36000.76983 * pi / 180.) * t_cen; /* Solar mean anomaly, (47.3) */ const double m_solar = 357.5291092 * pi / 180. + (35999.0502909 * pi / 180.) * t_cen; /* Lunar mean argument of latitude (47.5) */ const double f = 93.2720950 * pi / 180. + (483202.0175233 * pi / 180.) * t_cen; const double lunar_mean_elong = (297.8501921 * pi / 180.) + (445267.1114034 * pi / 180.) * t_cen; const double term2 = 2. * lunar_mean_elong - m_prime; const double lunar_lon = l_prime /* See table 47.A */ + (6.288774 * pi / 180.) * sin( m_prime) + (1.274027 * pi / 180.) * sin( term2) + (0.658314 * pi / 180.) * sin( 2. * lunar_mean_elong) + (0.213618 * pi / 180.) * sin( 2. * m_prime) - (0.185166 * pi / 180.) * sin( m_solar) - (0.114332 * pi / 180.) * sin( 2. * f); const double lunar_lat = (5.128122 * pi / 180.) * sin( f) + (0.280602 * pi / 180.) * sin( m_prime + f) + (0.277693 * pi / 180.) * sin( m_prime - f) + (0.173237 * pi / 180.) * sin( 2. * lunar_mean_elong - f); const double lunar_r = 385000560. /* in meters */ - 20905355. * cos( m_prime) - 3699111. * cos( term2) - 2955968 * cos( 2. * lunar_mean_elong) - 569925 * cos( 2. * m_solar); const double solar_ecc = 0.016708634; /* (25.4) */ const double solar_lon = l_solar /* (above (25.5)) */ + (1.914602 * pi / 180.) * sin( m_solar); const double au_in_meters = 1.495978707e+11; const double solar_r = au_in_meters * (1. - solar_ecc * cos( m_solar)); double tval; tval = lunar_r * cos( lunar_lat); *lunar_xyzr++ = tval * cos( lunar_lon); *lunar_xyzr++ = tval * sin( lunar_lon); *lunar_xyzr++ = lunar_r * sin( lunar_lat); *lunar_xyzr++ = lunar_r; *solar_xyzr++ = solar_r * cos( solar_lon); *solar_xyzr++ = solar_r * sin( solar_lon); *solar_xyzr++ = 0.; *solar_xyzr++ = solar_r; } /* For the RK4 integration, we're frequently asking for the sun and moon positions at the exact same time we needed for the preceding step. Caching those positions saves recomputing them. */ void DLL_FUNC lunar_solar_position( const double jd, double *lunar_xyzr, double *solar_xyzr) { static double curr_jd = 0., lunar[4], solar[4]; size_t i; if( curr_jd != jd) { curr_jd = jd; raw_lunar_solar_position( jd, lunar, solar); } for( i = 0; i < 4; i++) { if( lunar_xyzr) lunar_xyzr[i] = lunar[i]; if( solar_xyzr) solar_xyzr[i] = solar[i]; } } static const double sin_obliq_2000 = 0.397777155931913701597179975942380896684; static const double cos_obliq_2000 = 0.917482062069181825744000384639406458043; static void equatorial_to_ecliptic( double *vect) { double temp; temp = vect[2] * cos_obliq_2000 - vect[1] * sin_obliq_2000; vect[1] = vect[1] * cos_obliq_2000 + vect[2] * sin_obliq_2000; vect[2] = temp; } static void ecliptic_to_equatorial( double *vect) { double temp; temp = vect[2] * cos_obliq_2000 + vect[1] * sin_obliq_2000; vect[1] = vect[1] * cos_obliq_2000 - vect[2] * sin_obliq_2000; vect[2] = temp; } static void init_high_ephemeris( double *params, const tle_t *tle) { const double *state_vect = &tle->xincl; /* position at epoch, in meters */ size_t i; for( i = 0; i < 6; i++) params[i] = state_vect[i]; equatorial_to_ecliptic( params); equatorial_to_ecliptic( params + 3); } #define c1 params[2] #define c4 params[3] #define xnodcf params[4] #define t2cof params[5] #define deep_arg ((deep_arg_t *)( params + 10)) void DLL_FUNC SDP4_init( double *params, const tle_t *tle) { init_t init; if( tle->ephemeris_type == 'H') { init_high_ephemeris( params, tle); return; } sxpx_common_init( params, tle, &init, deep_arg); deep_arg->sing = sin(tle->omegao); deep_arg->cosg = cos(tle->omegao); /* initialize Deep() */ Deep_dpinit( tle, deep_arg); #ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH /* initialize lunisolar perturbations: */ deep_arg->t = 0.; /* added 30 Dec 2003 */ deep_arg->solar_lunar_init_flag = 1; Deep_dpper( tle, deep_arg); deep_arg->solar_lunar_init_flag = 0; #endif } /*End of SDP4() initialization */ static inline double vector_len( const double *vect) { double len2 = vect[0] * vect[0] + vect[1] * vect[1] + vect[2] * vect[2]; return( sqrt( len2)); } /* Input position is in meters, accel is in m/sec^2 */ static int calc_accel( const double jd, const double *pos, double *accel) { size_t i; const double earth_gm = 3.9860044e+14; /* in m^3/s^2 */ const double solar_gm = 1.3271243994e+20; /* m^3/s^2 */ const double lunar_gm = 4.902798e+12; /* m^3/s^2 */ double r = vector_len( pos); double accel_factor = -earth_gm / (r * r * r); double lunar_xyzr[4], solar_xyzr[4]; unsigned obj_idx; for( i = 0; i < 3; i++) accel[i] = accel_factor * pos[i]; lunar_solar_position( jd, lunar_xyzr, solar_xyzr); for( obj_idx = 0; obj_idx < 2; obj_idx++) { double *opos = (obj_idx ? lunar_xyzr : solar_xyzr); double delta[3], d; const double gm = (obj_idx ? lunar_gm : solar_gm); double accel_factor2; accel_factor = gm / (opos[3] * opos[3] * opos[3]); for( i = 0; i < 3; i++) delta[i] = opos[i] - pos[i]; d = vector_len( delta); accel_factor2 = gm / (d * d * d); for( i = 0; i < 3; i++) accel[i] -= accel_factor * opos[i] - accel_factor2 * delta[i]; } return( 0); } static int calc_state_vector_deriv( const double jd, const double state_vect[6], double deriv[6]) { deriv[0] = state_vect[3]; deriv[1] = state_vect[4]; deriv[2] = state_vect[5]; return( calc_accel( jd, state_vect, deriv + 3)); } /* NOTE: t_since is in minutes, posn is in km, vel is in km/minutes. State vector is in meters and m/s. Hence some conversions... 'high_ephemeris()' does the actual RK4 numerical integration, using a simplified model of the earth and moon and a quite basic integration step size adjustment so that it can take small steps when the object is close to the earth or moon and larger steps when far away. As described above, any temptation to "improve" the integration should be resisted. */ static int high_ephemeris( double tsince, const tle_t *tle, const double *params, double *pos, double *vel) { const double meters_per_km = 1000.; const double seconds_per_minute = 60.; const double seconds_per_day = seconds_per_minute * minutes_per_day; /* a.k.a. 86400 */ size_t i, j; double jd = tle->epoch, state_vect[6]; for( i = 0; i < 6; i++) state_vect[i] = params[i]; tsince /= minutes_per_day; /* input was in minutes; days are */ while( tsince) /* more convenient hereforth */ { double dt = tsince, dt_in_seconds; double max_step = 1.; double kvects[4][6]; calc_state_vector_deriv( jd, state_vect, kvects[0]); for( j = 3; j < 6; j++) if( max_step > 1e-3 / fabs( kvects[0][j])) max_step = 1e-3 / fabs( kvects[0][j]); if( max_step < 1e-5) max_step = 1e-5; if( tsince > max_step) dt = max_step; else if( tsince < -max_step) dt = -max_step; dt_in_seconds = dt * seconds_per_day; for( j = 1; j < 4; j++) { const double step = (j == 3 ? dt_in_seconds : dt_in_seconds * .5); double tstate[6]; for( i = 0; i < 6; i++) tstate[i] = state_vect[i] + step * kvects[j - 1][i]; calc_state_vector_deriv( jd + (j == 3 ? dt : dt / 2.), tstate, kvects[j]); } for( i = 0; i < 6; i++) state_vect[i] += (dt_in_seconds / 6.) * (kvects[0][i] + 2. * (kvects[1][i] + kvects[2][i]) + kvects[3][i]); jd += dt; tsince -= dt; } for( i = 0; i < 3; i++) { pos[i] = state_vect[i]; vel[i] = state_vect[i + 3]; } ecliptic_to_equatorial( vel); ecliptic_to_equatorial( pos); /* Now, cvt meters to km, meters/second to km/minute: */ for( i = 0; i < 3; i++) { pos[i] /= meters_per_km; vel[i] *= seconds_per_minute / meters_per_km; } return( 0); } int DLL_FUNC SDP4( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel) { double a, tempa, tsince_squared, xl, xnoddf; if( tle->ephemeris_type == 'H') { double unused_vel[3]; return( high_ephemeris( tsince, tle, params, pos, (vel ? vel : unused_vel))); } /* Update for secular gravity and atmospheric drag */ deep_arg->omgadf = tle->omegao + deep_arg->omgdot * tsince; xnoddf = tle->xnodeo + deep_arg->xnodot * tsince; tsince_squared = tsince*tsince; deep_arg->xnode = xnoddf + xnodcf * tsince_squared; deep_arg->xn = deep_arg->xnodp; /* Update for deep-space secular effects */ deep_arg->xll = tle->xmo + deep_arg->xmdot * tsince; deep_arg->t = tsince; Deep_dpsec( tle, deep_arg); tempa = 1-c1*tsince; if( deep_arg->xn < 0.) return( SXPX_ERR_NEGATIVE_XN); a = pow(xke/deep_arg->xn,two_thirds)*tempa*tempa; deep_arg->em -= tle->bstar*c4*tsince; /* Update for deep-space periodic effects */ deep_arg->xll += deep_arg->xnodp * t2cof * tsince_squared; Deep_dpper( tle, deep_arg); /* Keeping xinc positive is not really necessary, unless */ /* you're displaying elements and dislike negative inclinations. */ #ifdef KEEP_INCLINATION_POSITIVE if (deep_arg->xinc < 0.) /* Begin April 1983 errata correction: */ { deep_arg->xinc = -deep_arg->xinc; deep_arg->sinio = -deep_arg->sinio; deep_arg->xnode += pi; deep_arg->omgadf -= pi; } /* End April 1983 errata correction. */ #endif xl = deep_arg->xll + deep_arg->omgadf + deep_arg->xnode; /* Dundee change: Reset cosio, sinio for new xinc: */ deep_arg->cosio = cos( deep_arg->xinc); deep_arg->sinio = sin( deep_arg->xinc); return( sxpx_posn_vel( deep_arg->xnode, a, deep_arg->em, deep_arg->cosio, deep_arg->sinio, deep_arg->xinc, deep_arg->omgadf, xl, pos, vel)); } /* SDP4 */ ================================================ FILE: sdp8.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include "norad.h" #include "norad_in.h" #define tthmun params[0] #define sini2 params[1] #define cosi2 params[2] #define unm5th params[3] #define unmth2 params[4] #define xmdt1 params[5] #define xgdt1 params[6] #define xhdt1 params[7] #define xndt params[8] #define edot params[9] void sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg); void sxp8_common_init( double *params, const tle_t *tle, deep_arg_t *deep_arg); void DLL_FUNC SDP8_init( double *params, const tle_t *tle) { const double rho = .15696615; const double b = tle->bstar*2./rho; double alpha2, b1, b2, b3, c0, c1, c4, c5, cos2g, d1, d2, d3, d4, d5, eeta, eta, eta2, po, psim2, r1, tsi, xndtn; deep_arg_t *deep_arg = ((deep_arg_t *)( params + 10)); sxpall_common_init( tle, deep_arg); sxp8_common_init( params, tle, deep_arg); deep_arg->sinio = sin( tle->xincl); /* Initialization */ po = deep_arg->aodp*deep_arg->betao2; tsi = 1./(po-s_const); eta = tle->eo*s_const*tsi; eta2 = eta * eta; psim2 = (r1 = 1./(1.-eta2), fabs(r1)); alpha2 = deep_arg->eosq+1.; eeta = tle->eo*eta; cos2g = deep_arg->cosg * deep_arg->cosg * 2. - 1.; d5 = tsi*psim2; d1 = d5/po; d2 = eta2*(eta2*4.5+36.)+12.; d3 = eta2*(eta2*2.5+15.); d4 = eta*(eta2*3.75+5.); b1 = ck2*tthmun; b2 = -ck2*unmth2; b3 = a3ovk2*deep_arg->sinio; r1 = tsi, r1 *= r1; c0 = b*.5*rho*qoms2t*deep_arg->xnodp*deep_arg->aodp* (r1*r1)*pow( psim2, 3.5)/sqrt(alpha2); r1 = alpha2; c1 = deep_arg->xnodp*1.5*(r1*r1)*c0; c4 = d1*d3*b2; c5 = d5*d4*b3; xndt = c1*(eta2*(deep_arg->eosq*34.+3.)+2.+eeta*5.*(eta2+4.)+ deep_arg->eosq*8.5+d1*d2*b1+c4*cos2g+c5*deep_arg->sing); xndtn = xndt/deep_arg->xnodp; edot = -two_thirds*xndtn*(1.-tle->eo); /* initialize Deep() */ Deep_dpinit( tle, deep_arg); #ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH /* initialize lunisolar perturbations: */ deep_arg->t = 0.; /* added 30 Dec 2003 */ deep_arg->solar_lunar_init_flag = 1; Deep_dpper( tle, deep_arg); deep_arg->solar_lunar_init_flag = 0; #endif } /* End of SDP8() initialization */ int DLL_FUNC SDP8( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel) { double am, aovr, axnm, aynm, beta, beta2m, cose, cosos, cs2f2g, csf, csfg, cslamb, di, diwc, dr, ecosf, fm, g1, g10, g13, g14, g2, g3, g4, g5, pm, r1, rdot, rm, rr, rvdot, sine, sinos, sn2f2g, snf, snfg, sni2du, sinio2, snlamb, temp, ux, uy, uz, vx, vy, vz, xlamb, xmam, xmamdf, y4, y5, z1, z7, zc2, zc5; int i; deep_arg_t *deep_arg = ((deep_arg_t *)( params + 10)); /* Update for secular gravity and atmospheric drag */ z1 = xndt*.5*tsince*tsince; z7 = two_thirds*3.5*z1/deep_arg->xnodp; xmamdf = tle->xmo+deep_arg->xmdot*tsince; deep_arg->omgadf = tle->omegao+deep_arg->omgdot*tsince+z7*xgdt1; deep_arg->xnode = tle->xnodeo+deep_arg->xnodot*tsince+z7*xhdt1; deep_arg->xn = deep_arg->xnodp; /* Update for deep-space secular effects */ deep_arg->xll = xmamdf; deep_arg->t = tsince; Deep_dpsec( tle, deep_arg); xmamdf = deep_arg->xll; deep_arg->xn += xndt*tsince; deep_arg->em += edot*tsince; xmam = xmamdf+z1+z7*xmdt1; /* Update for deep-space periodic effects */ deep_arg->xll = xmam; Deep_dpper( tle, deep_arg); xmam = deep_arg->xll; xmam = FMod2p(xmam); /* Solve Kepler's equation */ zc2 = xmam+deep_arg->em*sin(xmam)*(deep_arg->em*cos(xmam)+1.); i = 0; do { double cape; sine = sin(zc2); cose = cos(zc2); zc5 = 1./(1.-deep_arg->em*cose); cape = (xmam+deep_arg->em*sine-zc2)*zc5+zc2; r1 = cape-zc2; if (fabs(r1) <= e6a) break; zc2 = cape; } while(i++ < 10); /* Short period preliminary quantities */ am = pow( xke / deep_arg->xn, two_thirds); beta2m = 1.f-deep_arg->em*deep_arg->em; sinos = sin(deep_arg->omgadf); cosos = cos(deep_arg->omgadf); axnm = deep_arg->em*cosos; aynm = deep_arg->em*sinos; pm = am*beta2m; g1 = 1./pm; g2 = ck2*.5*g1; g3 = g2*g1; beta = sqrt(beta2m); g4 = a3ovk2*.25*deep_arg->sinio; g5 = a3ovk2*.25*g1; snf = beta*sine*zc5; csf = (cose-deep_arg->em)*zc5; fm = atan2(snf, csf); if( fm < 0.) fm += pi + pi; snfg = snf*cosos+csf*sinos; csfg = csf*cosos-snf*sinos; sn2f2g = snfg*2.*csfg; r1 = csfg; cs2f2g = r1*r1*2.-1.; ecosf = deep_arg->em*csf; g10 = fm-xmam+deep_arg->em*snf; rm = pm/(ecosf+1.); aovr = am/rm; g13 = deep_arg->xn*aovr; g14 = -g13*aovr; dr = g2*(unmth2*cs2f2g-tthmun*3.)-g4*snfg; diwc = g3*3.*deep_arg->sinio*cs2f2g-g5*aynm; di = diwc*deep_arg->cosio; sinio2 = sin(deep_arg->xinc*.5); /* Update for short period periodics */ sni2du = sini2*(g3*((1.-deep_arg->cosio2*7.)*.5*sn2f2g-unm5th* 3.*g10)-g5*deep_arg->sinio*csfg*(ecosf+2.))-g5*.5* deep_arg->cosio2*axnm/cosi2; xlamb = fm+deep_arg->omgadf+deep_arg->xnode+g3*((deep_arg->cosio*6.+ 1.-deep_arg->cosio2*7.)*.5*sn2f2g-(unm5th+deep_arg->cosio*2.)* 3.*g10)+g5*deep_arg->sinio*(deep_arg->cosio*axnm/ (deep_arg->cosio+1.)-(ecosf+2.)*csfg); y4 = sinio2*snfg+csfg*sni2du+snfg*.5*cosi2*di; y5 = sinio2*csfg-snfg*sni2du+csfg*.5*cosi2*di; rr = rm+dr; rdot = deep_arg->xn*am*deep_arg->em*snf/beta+g14*(g2*2.*unmth2*sn2f2g+g4*csfg); r1 = am; rvdot = deep_arg->xn*(r1*r1)*beta/rm+g14*dr+am*g13*deep_arg->sinio*diwc; /* Orientation vectors */ snlamb = sin(xlamb); cslamb = cos(xlamb); temp = (y5*snlamb-y4*cslamb)*2.; ux = y4*temp+cslamb; vx = y5*temp-snlamb; temp = (y5*cslamb+y4*snlamb)*2.; uy = -y4*temp+snlamb; vy = -y5*temp+cslamb; temp = sqrt(1.-y4*y4-y5*y5)*2.; uz = y4*temp; vz = y5*temp; /* Position and velocity */ pos[0] = rr*ux*earth_radius_in_km; pos[1] = rr*uy*earth_radius_in_km; pos[2] = rr*uz*earth_radius_in_km; if( vel) { vel[0] = (rdot*ux+rvdot*vx)*earth_radius_in_km; vel[1] = (rdot*uy+rvdot*vy)*earth_radius_in_km; vel[2] = (rdot*uz+rvdot*vz)*earth_radius_in_km; } return( 0); } /* SDP8 */ ================================================ FILE: sgp.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include "norad.h" #include "norad_in.h" #define ao params[0] #define qo params[1] #define xlo params[2] #define d1o params[3] #define d2o params[4] #define d3o params[5] #define d4o params[6] #define omgdt params[7] #define xnodot params[8] #define c5 params[9] #define c6 params[10] void DLL_FUNC SGP_init( double *params, const tle_t *tle) { double c1, c2, c3, c4, r1, cosio, sinio, a1, d1, po, po2no; c1 = ck2*1.5; c2 = ck2/4.; c3 = ck2/2.; r1 = ae; c4 = xj3*(r1*(r1*r1))/(ck2*4.); cosio = cos(tle->xincl); sinio = sin(tle->xincl); a1 = pow( xke / tle->xno, two_thirds); d1 = c1/a1/a1*(cosio*3.*cosio-1.)/pow( 1.-tle->eo*tle->eo, 1.5); ao = a1*(1.-d1*.33333333333333331-d1*d1-d1* 1.654320987654321*d1*d1); po = ao*(1.-tle->eo*tle->eo); qo = ao*(1.-tle->eo); xlo = tle->xmo+tle->omegao+tle->xnodeo; d1o = c3*sinio*sinio; d2o = c2*(cosio*7.*cosio-1.); d3o = c1*cosio; d4o = d3o*sinio; po2no = tle->xno/(po*po); omgdt = c1*po2no*(cosio*5.*cosio-1.); xnodot = d3o*-2.*po2no; c5 = c4*.5*sinio*(cosio*5.+3.)/(cosio+1.); c6 = c4*sinio; } int DLL_FUNC SGP( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel) { double temp, rdot, cosu, sinu, cos2u, sin2u, a, e, p, rr, u, ecose, esine, omgas, cosik, xinck, sinik, axnsl, aynsl, sinuk, rvdot, cosuk, coseo1, sineo1, pl, rk, uk, xl, su, ux, uy, uz, vx, vy, vz, pl2, xnodek, cosnok, xnodes, el2, eo1, r1, sinnok, xls, xmx, xmy, tem2, tem5; const double chicken_factor_on_eccentricity = 1.e-6; int i, rval = 0; /* Update for secular gravity and atmospheric drag */ a = tle->xno+(tle->xndt2o*2.+tle->xndd6o*3.*tsince)*tsince; if( a < 0.) rval = SXPX_ERR_NEGATIVE_MAJOR_AXIS; e = e6a; if( e > 1. - chicken_factor_on_eccentricity) rval = SXPX_ERR_NEARLY_PARABOLIC; if( rval) { for( i = 0; i < 3; i++) { pos[i] = 0.; if( vel) vel[i] = 0.; } return( rval); } a = ao * pow( tle->xno / a, two_thirds); if( a * (1. - e) < 1. && a * (1. + e) < 1.) /* entirely within earth */ rval = SXPX_WARN_ORBIT_WITHIN_EARTH; /* remember, e can be negative */ if( a * (1. - e) < 1. || a * (1. + e) < 1.) /* perigee within earth */ rval = SXPX_WARN_PERIGEE_WITHIN_EARTH; if (a > qo) e = 1.-qo/a; p = a*(1.-e*e); xnodes = tle->xnodeo+xnodot*tsince; omgas = tle->omegao+omgdt*tsince; r1 = xlo+(tle->xno+omgdt+xnodot+ (tle->xndt2o+tle->xndd6o*tsince)*tsince)*tsince; xls = FMod2p(r1); /* Long period periodics */ axnsl = e*cos(omgas); aynsl = e*sin(omgas)-c6/p; r1 = xls-c5/p*axnsl; xl = FMod2p(r1); /* Solve Kepler's equation */ r1 = xl-xnodes; u = FMod2p(r1); eo1 = u; tem5 = 1.; i = 0; do { sineo1 = sin(eo1); coseo1 = cos(eo1); if (fabs(tem5) < e6a) break; tem5 = 1.-coseo1*axnsl-sineo1*aynsl; tem5 = (u-aynsl*coseo1+axnsl*sineo1-eo1)/tem5; tem2 = fabs(tem5); if (tem2 > 1.) tem5 = tem2/tem5; eo1 += tem5; } while(i++ < 10); /* Short period preliminary quantities */ ecose = axnsl*coseo1+aynsl*sineo1; esine = axnsl*sineo1-aynsl*coseo1; el2 = axnsl*axnsl+aynsl*aynsl; pl = a*(1.-el2); pl2 = pl*pl; rr = a*(1.-ecose); rdot = xke*sqrt(a)/rr*esine; rvdot = xke*sqrt(pl)/rr; temp = esine/(sqrt(1.-el2)+1.); sinu = a/rr*(sineo1-aynsl-axnsl*temp); cosu = a/rr*(coseo1-axnsl+aynsl*temp); su = atan2(sinu, cosu); /* Update for short periodics */ sin2u = (cosu+cosu)*sinu; cos2u = 1.-2.*sinu*sinu; rk = rr+d1o/pl*cos2u; uk = su-d2o/pl2*sin2u; xnodek = xnodes+d3o*sin2u/pl2; xinck = tle->xincl+d4o/pl2*cos2u; /* Orientation vectors */ sinuk = sin(uk); cosuk = cos(uk); sinnok = sin(xnodek); cosnok = cos(xnodek); sinik = sin(xinck); cosik = cos(xinck); xmx = -sinnok*cosik; xmy = cosnok*cosik; ux = xmx*sinuk+cosnok*cosuk; uy = xmy*sinuk+sinnok*cosuk; uz = sinik*sinuk; vx = xmx*cosuk-cosnok*sinuk; vy = xmy*cosuk-sinnok*sinuk; vz = sinik*cosuk; /* Position and velocity */ pos[0] = rk*ux*earth_radius_in_km; pos[1] = rk*uy*earth_radius_in_km; pos[2] = rk*uz*earth_radius_in_km; if( vel) { vel[0] = (rdot*ux + rvdot * vx)*earth_radius_in_km; vel[1] = (rdot*uy + rvdot * vy)*earth_radius_in_km; vel[2] = (rdot*uz + rvdot * vz)*earth_radius_in_km; } return( rval); } /* SGP */ ================================================ FILE: sgp4.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include #include "norad.h" #include "norad_in.h" #define c1 params[2] #define c4 params[3] #define xnodcf params[4] #define t2cof params[5] #define p_aodp params[10] #define p_cosio params[11] #define p_sinio params[12] #define p_omgdot params[13] #define p_xmdot params[14] #define p_xnodot params[15] #define p_xnodp params[16] #define c5 params[17] #define d2 params[18] #define d3 params[19] #define d4 params[20] #define delmo params[21] #define p_eta params[22] #define omgcof params[23] #define sinmo params[24] #define t3cof params[25] #define t4cof params[26] #define t5cof params[27] #define xmcof params[28] #define simple_flag *((int *)( params + 29)) #define MINIMAL_E 1.e-4 #define ECC_EPS 1.e-6 /* Too low for computing further drops. */ void DLL_FUNC SGP4_init( double *params, const tle_t *tle) { deep_arg_t deep_arg; init_t init; double eeta, etasq; sxpx_common_init( params, tle, &init, &deep_arg); p_aodp = deep_arg.aodp; p_cosio = deep_arg.cosio; p_sinio = deep_arg.sinio; p_omgdot = deep_arg.omgdot; p_xmdot = deep_arg.xmdot; p_xnodot = deep_arg.xnodot; p_xnodp = deep_arg.xnodp; p_eta = deep_arg.aodp*tle->eo*init.tsi; // p_eta = init.eta; eeta = tle->eo*p_eta; /* For perigee less than 220 kilometers, the "simple" flag is set */ /* and the equations are truncated to linear variation in sqrt a */ /* and quadratic variation in mean anomaly. Also, the c3 term, */ /* the delta omega term, and the delta m term are dropped. */ simple_flag = ((p_aodp*(1-tle->eo)/ae) < (220./earth_radius_in_km+ae)); if( !simple_flag) { const double c1sq = c1*c1; double temp; simple_flag = 0; delmo = 1. + p_eta * cos(tle->xmo); delmo *= delmo * delmo; d2 = 4*p_aodp*init.tsi*c1sq; temp = d2*init.tsi*c1/3; d3 = (17*p_aodp+init.s4)*temp; d4 = 0.5*temp*p_aodp*init.tsi*(221*p_aodp+31*init.s4)*c1; t3cof = d2+2*c1sq; t4cof = 0.25*(3*d3+c1*(12*d2+10*c1sq)); t5cof = 0.2*(3*d4+12*c1*d3+6*d2*d2+15*c1sq*(2*d2+c1sq)); sinmo = sin(tle->xmo); if( tle->eo < MINIMAL_E) omgcof = xmcof = 0.; else { const double c3 = init.coef * init.tsi * a3ovk2 * p_xnodp * ae * p_sinio / tle->eo; xmcof = -two_thirds * init.coef * tle->bstar * ae / eeta; omgcof = tle->bstar*c3*cos(tle->omegao); } } /* End of if (isFlagClear(SIMPLE_FLAG)) */ etasq = p_eta * p_eta; c5 = 2*init.coef1*p_aodp * deep_arg.betao2*(1+2.75*(etasq+eeta)+eeta*etasq); } /* End of SGP4() initialization */ int DLL_FUNC SGP4( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel) { double a, e, omega, omgadf, temp, tempa, tempe, templ, tsq, xl, xmdf, xmp, xnoddf, xnode; /* Update for secular gravity and atmospheric drag. */ xmdf = tle->xmo+p_xmdot*tsince; omgadf = tle->omegao+p_omgdot*tsince; xnoddf = tle->xnodeo+p_xnodot*tsince; omega = omgadf; xmp = xmdf; tsq = tsince*tsince; xnode = xnoddf+xnodcf*tsq; tempa = 1-c1*tsince; tempe = tle->bstar*c4*tsince; templ = t2cof*tsq; if( !simple_flag) { const double delomg = omgcof*tsince; double delm = 1. + p_eta * cos(xmdf); double tcube, tfour; delm = xmcof * (delm * delm * delm - delmo); temp = delomg+delm; xmp = xmdf+temp; omega = omgadf-temp; tcube = tsq*tsince; tfour = tsince*tcube; tempa = tempa-d2*tsq-d3*tcube-d4*tfour; tempe = tempe+tle->bstar*c5*(sin(xmp)-sinmo); templ = templ+t3cof*tcube+tfour*(t4cof+tsince*t5cof); }; /* End of if (isFlagClear(SIMPLE_FLAG)) */ a = p_aodp*tempa*tempa; e = tle->eo-tempe; /* A highly arbitrary lower limit on e, of 1e-6: */ if( e < ECC_EPS) e = ECC_EPS; xl = xmp+omega+xnode+p_xnodp*templ; if( tempa < 0.) /* force negative a, to indicate error condition */ a = -a; return( sxpx_posn_vel( xnode, a, e, p_cosio, p_sinio, tle->xincl, omega, xl, pos, vel)); } /*SGP4*/ ================================================ FILE: sgp8.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include #include "norad.h" #include "norad_in.h" #define tthmun params[0] #define sini2 params[1] #define cosi2 params[2] #define unm5th params[3] #define unmth2 params[4] #define xmdt1 params[5] #define xgdt1 params[6] #define xhdt1 params[7] #define xndt params[8] #define edot params[9] #define ed params[10] #define gamma params[11] #define omgdt params[12] #define ovgpp params[13] #define pp params[14] #define qq params[15] #define sini params[16] #define cosi params[17] #define cosio_2 params[18] #define xlldot params[19] #define xnd params[20] #define xnodot_ params[21] #define xnodp_ params[22] #define simple_flag *((int *)( params + 23)) void sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg); void sxp8_common_init( double *params, const tle_t *tle, deep_arg_t *deep_arg) { const double half_inclination = tle->xincl*.5; const double cosio4 = deep_arg->cosio2 * deep_arg->cosio2; double po, pom2, pardt1, pardt2, pardt4; deep_arg->sing = sin( tle->omegao); deep_arg->cosg = cos( tle->omegao); sini2 = sin( half_inclination); cosi2 = cos( half_inclination); tthmun = deep_arg->cosio2 * 3. - 1.; unm5th = 1.-deep_arg->cosio2 * 5.; unmth2 = 1.-deep_arg->cosio2; po = deep_arg->aodp * deep_arg->betao2; pom2 = 1./(po*po); pardt1 = 3. * ck2 * pom2 * deep_arg->xnodp; pardt2 = pardt1 * ck2 * pom2; pardt4 = ck4 * 1.25 * pom2 * pom2 * deep_arg->xnodp; xmdt1 = .5 * pardt1 * deep_arg->betao * tthmun; xgdt1 = -.5 * pardt1 * unm5th; xhdt1 = -pardt1 * deep_arg->cosio; deep_arg->xmdot = deep_arg->xnodp+xmdt1+pardt2*.0625*deep_arg->betao* (13.-deep_arg->cosio2*78.+cosio4*137.); deep_arg->omgdot = xgdt1+pardt2*.0625*(7.-deep_arg->cosio2* 114.+cosio4*395.)+pardt4*(3.-deep_arg->cosio2* 36.+cosio4*49.); deep_arg->xnodot = xhdt1+(pardt2*.5*(4.-deep_arg->cosio2*19.)+pardt4* 2.*(3.-deep_arg->cosio2*7.))*deep_arg->cosio; } void DLL_FUNC SGP8_init( double *params, const tle_t *tle) { const double rho = .15696615; const double b = tle->bstar*2./rho; double alpha2, b1, b2, b3, c0, c1, c4, c5, cos2g, d1, d2, d3, d4, d5, eeta, eta, eta2, eddot, etdt, po, psim2, r1, tsi, xndtn; deep_arg_t deep_arg; sxpall_common_init( tle, &deep_arg); sxp8_common_init( params, tle, &deep_arg); sini = sin( tle->xincl); cosi = deep_arg.cosio; cosio_2 = deep_arg.cosio2; /* Initialization */ xnodp_ = deep_arg.xnodp; xlldot = deep_arg.xmdot; omgdt = deep_arg.omgdot; xnodot_ = deep_arg.xnodot; po = deep_arg.aodp * deep_arg.betao2; tsi = 1./(po-s_const); eta = tle->eo*s_const*tsi; eta2 = eta * eta; psim2 = (r1 = 1./(1.-eta2), fabs(r1)); alpha2 = deep_arg.eosq+1.; eeta = tle->eo*eta; cos2g = deep_arg.cosg * deep_arg.cosg * 2. - 1.; d5 = tsi*psim2; d1 = d5/po; d2 = eta2*(eta2*4.5+36.)+12.; d3 = eta2*(eta2*2.5+15.); d4 = eta*(eta2*3.75+5.); b1 = ck2*tthmun; b2 = -ck2*unmth2; b3 = a3ovk2*sini; r1 = tsi, r1 *= r1; c0 = b*.5*rho*qoms2t*xnodp_*deep_arg.aodp*(r1*r1)* pow(psim2, 3.5)/sqrt(alpha2); r1 = alpha2; c1 = xnodp_*1.5*(r1*r1)*c0; c4 = d1*d3*b2; c5 = d5*d4*b3; xndt = c1*(eta2*(deep_arg.eosq*34.+3.)+2.+eeta*5.*(eta2+4.) +deep_arg.eosq*8.5+d1*d2*b1+c4*cos2g+c5*deep_arg.sing); xndtn = xndt/xnodp_; /* If drag is very small, the isimp flag is set and the */ /* equations are truncated to linear variation in mean */ /* motion and quadratic variation in mean anomaly */ r1 = xndtn * minutes_per_day; if( fabs(r1) > .00216) { const double d6 = eta*(eta2*22.5+30.); const double d7 = eta*(eta2*12.5+5.); const double d8 = eta2*(eta2+6.75)+1.; const double d9 = eta*(deep_arg.eosq*68.+6.)+tle->eo*(eta2*15.+20.); const double d10 = eta*5.*(eta2+4.)+tle->eo*(eta2*68.+17.); const double d11 = eta*(eta2*18.+72.); const double d12 = eta*(eta2*10.+30.); const double d13 = eta2*11.25+5.; const double d20 = two_thirds*.5*xndtn; const double c8 = d1*d7*b2; const double c9 = d5*d8*b3; const double sin2g = deep_arg.sing*2.*deep_arg.cosg; double d1dt, d2dt, d3dt, d4dt, d5dt, temp; double d14, d15, d16, d17, d18, d19, d23, d25, aldtal, psdtps; double c4dt, c5dt, c0dtc0, c1dtc1, rr2; double d1ddt, etddt, tmnddt, tsdtts, tsddts, xnddt, xntrdt; simple_flag = 0; edot = -c0*(eta*(eta2+4.+deep_arg.eosq*(eta2*7.+15.5))+tle->eo* (eta2*15.+5.)+d1*d6*b1+c8*cos2g+c9*deep_arg.sing); tsdtts = deep_arg.aodp*2.*tsi*(d20*deep_arg.betao2+tle->eo*edot); aldtal = tle->eo*edot/alpha2; etdt = (edot+tle->eo*tsdtts)*tsi*s_const; psdtps = -eta*etdt*psim2; c0dtc0 = d20+tsdtts*4.-aldtal-psdtps*7.; c1dtc1 = xndtn+aldtal*4.+c0dtc0; d14 = tsdtts-psdtps*2.; d15 = (d20+tle->eo*edot/deep_arg.betao2)*2.; d1dt = d1*(d14+d15); d2dt = etdt*d11; d3dt = etdt*d12; d4dt = etdt*d13; d5dt = d5*d14; c4dt = b2*(d1dt*d3+d1*d3dt); c5dt = b3*(d5dt*d4+d5*d4dt); d16 = d9*etdt+d10*edot+b1*(d1dt*d2+d1*d2dt)+c4dt* cos2g+c5dt*deep_arg.sing+xgdt1*(c5*deep_arg.cosg-c4*2.*sin2g); xnddt = c1dtc1*xndt+c1*d16; eddot = c0dtc0*edot-c0*((eta2*3.+4.+eeta*30.+deep_arg.eosq* (eta2*21.+15.5))*etdt+(eta2*15.+5.+eeta* (eta2*14.+31.))*edot+b1*(d1dt*d6+d1*etdt* (eta2*67.5+30.))+b2*(d1dt*d7+d1*etdt* (eta2*37.5+5.))*cos2g+b3*(d5dt*d8+d5*etdt*eta* (eta2*4.+13.5))*deep_arg.sing+xgdt1*(c9*deep_arg.cosg-c8*2.*sin2g)); r1 = edot; d25 = r1*r1; r1 = xndtn; d17 = xnddt/xnodp_-r1*r1; tsddts = tsdtts*2.*(tsdtts-d20)+deep_arg.aodp*tsi* (two_thirds*deep_arg.betao2*d17-d20*4.*tle->eo*edot+ (d25+tle->eo*eddot)*2.); etddt = (eddot+edot*2.*tsdtts)*tsi*s_const+tsddts*eta; r1 = tsdtts; d18 = tsddts-r1*r1; r1 = psdtps; rr2 = psdtps; d19 = -(r1*r1)/eta2-eta*etddt*psim2-rr2*rr2; d23 = etdt*etdt; d1ddt = d1dt*(d14+d15)+d1*(d18-d19*2.+two_thirds*d17+ (alpha2*d25/deep_arg.betao2+tle->eo*eddot)*2./deep_arg.betao2); r1 = aldtal; xntrdt = xndt*(two_thirds*2.*d17+(d25+tle->eo*eddot)*3./ alpha2-r1*r1*6.+d18*4.-d19*7.)+ c1dtc1*xnddt+c1*(c1dtc1*d16+d9*etddt+d10* eddot+d23*(eeta*30.+6.+deep_arg.eosq*68.)+etdt*edot* (eta2*30.+40.+eeta*272.)+d25*(eta2*68.+17.)+ b1*(d1ddt*d2+d1dt*2.*d2dt+d1*(etddt*d11+d23* (eta2*54.+72.)))+b2*(d1ddt*d3+d1dt*2.*d3dt+d1 *(etddt*d12+d23*(eta2*30.+30.)))*cos2g+b3* ((d5dt*d14+d5*(d18-d19*2.))*d4+d4dt*2.*d5dt+ d5*(etddt*d13+eta*22.5*d23))*deep_arg.sing+xgdt1*((d20* 7.+tle->eo*4.*edot/deep_arg.betao2)*(c5*deep_arg.cosg-c4*2.* sin2g)+(c5dt*2.*deep_arg.cosg-c4dt*4.*sin2g-xgdt1* (c5*deep_arg.sing+c4*4.*cos2g)))); tmnddt = xnddt*1e9; r1 = tmnddt; temp = r1*r1-xndt*1e18*xntrdt; r1 = tmnddt; pp = (temp+r1*r1)/temp; gamma = -xntrdt/(xnddt*(pp-2.)); xnd = xndt/(pp*gamma); qq = 1.-eddot/(edot*gamma); ed = edot/(qq*gamma); ovgpp = 1./(gamma*(pp+1.)); } else { simple_flag = 1; edot = -two_thirds*xndtn*(1.-tle->eo); } /* End of if (fabs(r1) > .00216) */ } /* End of SGP8() initialization */ int DLL_FUNC SGP8( const double tsince, const tle_t *tle, const double *params, double *pos, double *vel) { int i; double am, aovr, axnm, aynm, beta, beta2m, cose, cosos, cs2f2g, csf, csfg, cslamb, di, diwc, dr, ecosf, em, fm, g1, g10, g13, g14, g2, g3, g4, g5, omgasm, pm, r1, rdot, rm, rr, rvdot, sine, sinos, sn2f2g, snf, snfg, sni2du, snlamb, temp, ux, uy, uz, vx, vy, vz, xlamb, xmam, xn, xnodes, y4, y5, z1, z7, zc2, zc5; /* Update for secular gravity and atmospheric drag */ r1 = tle->xmo+xlldot*tsince; xmam = FMod2p(r1); omgasm = tle->omegao+omgdt*tsince; xnodes = tle->xnodeo+xnodot_*tsince; if( !simple_flag) { double temp1; temp = 1.-gamma*tsince; temp1 = pow(temp, pp); xn = xnodp_+xnd*(1.-temp1); em = tle->eo+ed*(1.-pow(temp, qq)); z1 = xnd*(tsince+ovgpp*(temp*temp1-1.)); } else { xn = xnodp_+xndt*tsince; em = tle->eo+edot*tsince; z1 = xndt*.5*tsince*tsince; } /* if(isFlagClear(SIMPLE_FLAG)) */ z7 = two_thirds*3.5*z1/xnodp_; r1 = xmam+z1+z7*xmdt1; xmam = FMod2p(r1); omgasm += z7*xgdt1; xnodes += z7*xhdt1; /* Solve Kepler's equation */ zc2 = xmam+em*sin(xmam)*(em*cos(xmam)+1.); i = 0; do { double cape; sine = sin(zc2); cose = cos(zc2); zc5 = 1./(1.-em*cose); cape = (xmam+em*sine-zc2)*zc5+zc2; r1 = cape-zc2; if(fabs(r1) <= e6a) break; zc2 = cape; } while(i++ < 10 ); /* Short period preliminary quantities */ am = pow( xke / xn, two_thirds); beta2m = 1.-em*em; sinos = sin(omgasm); cosos = cos(omgasm); axnm = em*cosos; aynm = em*sinos; pm = am*beta2m; g1 = 1./pm; g2 = ck2*.5*g1; g3 = g2*g1; beta = sqrt(beta2m); g4 = a3ovk2 * .25 * sini; g5 = a3ovk2 * .25 * g1; snf = beta*sine*zc5; csf = (cose-em)*zc5; fm = atan2(snf, csf); if( fm < 0.) fm += pi + pi; snfg = snf*cosos+csf*sinos; csfg = csf*cosos-snf*sinos; sn2f2g = snfg*2.*csfg; r1 = csfg; cs2f2g = r1*r1*2.-1.; ecosf = em*csf; g10 = fm-xmam+em*snf; rm = pm/(ecosf+1.); aovr = am/rm; g13 = xn*aovr; g14 = -g13*aovr; dr = g2*(unmth2*cs2f2g-tthmun*3.)-g4*snfg; diwc = g3*3.*sini*cs2f2g-g5*aynm; di = diwc*cosi; /* Update for short period periodics */ sni2du = sini2*(g3*((1.-cosio_2*7.)*.5*sn2f2g-unm5th*3.*g10)- g5*sini*csfg*(ecosf+2.))-g5*.5f*cosio_2*axnm/cosi2; xlamb = fm+omgasm+xnodes+g3*((cosi*6.+1.-cosio_2*7.)* .5*sn2f2g-(unm5th+cosi*2.)*3.*g10)+g5*sini* (cosi*axnm/(cosi+1.)-(ecosf+2.)*csfg); y4 = sini2*snfg+csfg*sni2du+snfg*.5*cosi2*di; y5 = sini2*csfg-snfg*sni2du+csfg*.5*cosi2*di; rr = rm+dr; rdot = xn*am*em*snf/beta+g14*(g2*2.*unmth2*sn2f2g+g4*csfg); r1 = am; rvdot = xn*(r1*r1)*beta/rm+g14 * dr+am*g13*sini*diwc; /* Orientation vectors */ snlamb = sin(xlamb); cslamb = cos(xlamb); temp = (y5*snlamb-y4*cslamb)*2.; ux = y4*temp+cslamb; vx = y5*temp-snlamb; temp = (y5*cslamb+y4*snlamb)*2.; uy = -y4*temp+snlamb; vy = -y5*temp+cslamb; temp = sqrt(1.-y4*y4-y5*y5)*2.; uz = y4*temp; vz = y5*temp; /* Position and velocity */ pos[0] = rr*ux*earth_radius_in_km; pos[1] = rr*uy*earth_radius_in_km; pos[2] = rr*uz*earth_radius_in_km; if( vel) { vel[0] = (rdot*ux+rvdot*vx)*earth_radius_in_km; vel[1] = (rdot*uy+rvdot*vy)*earth_radius_in_km; vel[2] = (rdot*uz+rvdot*vz)*earth_radius_in_km; } return( 0); } /* SGP8 */ ================================================ FILE: sm_sat.def ================================================ LIBRARY sm_sat EXPORTS SGP4_init @2 SGP4 @7 select_ephemeris @11 parse_elements @12 tle_checksum @17 ================================================ FILE: ssc_eph.c ================================================ /* Code to convert ephems from SSCWeb into the format 'eph2tle' uses to generate TLEs. The usefulness here is that you can get TLEs for the Magnetospheric Multiscale (MMS) satellites that are good enough to ID the individual satellites, even though the four of them are in a tight cluster. This does fail if the satellites maneuver during the course of a day; the TLEs are then unable to fit the (non-gravitational) motion. Compile with gcc -I../include -Wextra -Wall -O3 -pedantic ssc_eph.c -o ssc_eph ../lib/liblunar.a -lm Go to the SSCWeb Locator page : https://sscweb.gsfc.nasa.gov/cgi-bin/Locator.cgi Select the 'Standard' interface. Turn on MMS-1, 2, 3, and 4. Set the output frequency to 144 minutes (= 0.1 day). Under 'Optional Settings', select kilometers and yy/mm/dd output. Select the desired time span. Under 'Output Options', select GEI/J2000 XYZ. Generate the output and save it as /tmp/mms.txt. The result can be fed through 'eph2tle' (see the 'find_orb' repository) to generate TLEs. */ #include #include #include #include #include "watdefs.h" #include "date.h" #define is_power_of_two( X) (!((X) & ((X) - 1))) static void dump_to_file( const int mms_no, const double t0, const double dt, const int n_found, const double *xyz) { FILE *ofile; char buff[200]; int i, j; snprintf( buff, sizeof( buff), "mms%d.txt", mms_no); ofile = fopen( buff, "wb"); assert( ofile); fprintf( ofile, "%f %f %d 0,149597870.700000,86400.000000,2000" " (500) Geocentric: MMS-%d = 2015-011%c = NORAD %d\n", t0, dt, n_found, mms_no, mms_no + '@', mms_no + 40481); for( i = 0; i < n_found; i++) { double vel[3]; const double *tptr = xyz + i * 3; for( j = 0; j < 3; j++) vel[j] = (i == n_found - 1 ? tptr : tptr + 3)[j]; for( j = 0; j < 3; j++) { vel[j] -= (i ? tptr - 3 : tptr)[j]; vel[j] /= 86400. * dt; if( i && i != n_found - 1) vel[j] /= 2.; } fprintf( ofile, "%f %11.2f %11.2f %11.2f %11.6f %11.6f %11.6f\n", t0 + (double)i * dt, tptr[0], tptr[1], tptr[2], vel[0], vel[1], vel[2]); } fclose( ofile); } int main( const int argc, const char **argv) { FILE *ifile = fopen( "/tmp/mms.txt", "rb"); char buff[100]; long header_offset = 0; int mms_no; assert( ifile); while( !header_offset && fgets( buff, sizeof( buff), ifile)) if( !memcmp( buff, "yy/mm/dd", 8)) header_offset = ftell( ifile); if( !header_offset) { fprintf( stderr, "Couldn't find yy/mm/dd header\n"); return( -1); } for( mms_no = 1; mms_no <= 4; mms_no++) { int n_found = 0; double t0 = 0., step = 0., *xyz = NULL; const double td_minus_utc = 69.184 / 86400.; char *tptr; fseek( ifile, header_offset, SEEK_SET); buff[0] = '2'; /* assume 2nd millennium */ buff[1] = '0'; /* assume 21st century */ while( fgets( buff + 2, sizeof( buff) - 2, ifile)) if( (tptr = strstr( buff, "mms")) != NULL && tptr[3] == '0' + mms_no) { int n_fields_read; tptr[-1] = '\0'; if( !n_found) t0 = get_time_from_string( 0., buff, FULL_CTIME_YMD, NULL); if( n_found == 1) step = get_time_from_string( 0., buff, FULL_CTIME_YMD, NULL) - t0; n_found++; if( is_power_of_two( n_found)) xyz = (double *)realloc( xyz, 2 * n_found * 3 * sizeof( double)); n_fields_read = sscanf( tptr + 4, "%lf %lf %lf", xyz + n_found * 3 - 3, xyz + n_found * 3 - 2, xyz + n_found * 3 - 1); assert( 3 == n_fields_read); } dump_to_file( mms_no, t0 + td_minus_utc, step, n_found, xyz); free( xyz); } fclose( ifile); return( 0); } ================================================ FILE: summarize.c ================================================ /* Code to read 'tle_list.txt' and insert # Range: lines for files that lack them, and (for files containing only one object) NORAD/COSPAR identifiers. The former can be used to speed up some programs (by skipping 'included' files that won't cover a desired time span). The latter can be used in an ephemeris program to quickly find a desired object. Important notes : -- tle_list.txt will require occasional updating, of course, as new objects are added and archival Space-Track and other TLE sets are added. My hope is to run this once and then add # Range: and # ID: lines as needed. -- THEMIS-A, D, and E are oddball cases. They maneuver; I get state vectors from UC Berkeley and compute TLEs from those (see 'up_them' in the 'tles' repository). Those are updated roughly weekly. I don't want to have to update 'tle_list' each time for that, so I've pushed the day of reckoning for those off by a year. */ #include #include #include #include #include "watdefs.h" #include "afuncs.h" #include "date.h" #include "norad.h" static void get_range_info( const char *filename, const int range_already_set) { char buff[200], *tptr, id[100]; int n_ids_found = 0; const int is_themis = !memcmp( filename, "07004", 5) && filename[5] < 'g'; FILE *ifile; strcpy( buff, filename); tptr = strchr( buff, '\n'); assert( tptr); *tptr = '\0'; ifile = fopen( buff, "rb"); assert( ifile); *id = '\0'; while( 2 > n_ids_found && fgets( buff, sizeof( buff), ifile)) { if( !memcmp( buff, "# Ephem range:", 14) && !range_already_set) { double mjd[2]; size_t i; const int n_read = sscanf( buff + 14, "%lf %lf", mjd, mjd + 1); assert( n_read == 2); if( is_themis) /* THEMIS sats get updated/extended */ mjd[1] += 365.; /* regularly; 'pad' the end date accordingly */ printf( "# Range:"); for( i = 0; i < 2; i++) { full_ctime( buff, 2400000.5 + mjd[i], FULL_CTIME_YMD | FULL_CTIME_DATE_ONLY | FULL_CTIME_LEADING_ZEROES | FULL_CTIME_MONTHS_AS_DIGITS); buff[4] = buff[7] = '-'; printf( " %s", buff); } printf( "\n"); } else if( *buff == '1' && !tle_checksum( buff)) { buff[7] = ' '; buff[17] = '\0'; if( strcmp( id, buff + 2)) n_ids_found++; strcpy( id, buff + 2); } } if( n_ids_found == 1) printf( "# ID: %s\n", id); fclose( ifile); } int main( const int unused_argc, const char **unused_argv) { char prev_line[100], line[100]; FILE *ifile = fopen( "tle_list.txt", "rb"); INTENTIONALLY_UNUSED_PARAMETER( unused_argc); INTENTIONALLY_UNUSED_PARAMETER( unused_argv); assert( ifile); *prev_line = '\0'; while( fgets( line, sizeof( line), ifile)) { if( !memcmp( line, "# Include ", 10) && memcmp( line + 10, "old_tles", 8)) get_range_info( line + 10, !memcmp( prev_line, "# Range:", 8)); printf( "%s", line); strcpy( prev_line, line); } fclose( ifile); return( 0); } ================================================ FILE: test.tle ================================================ Times: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 25 27 28 29 30 31 1 11801U 80230.29629788 .01431103 00000-0 14311-1 2 2 11801U 46.7916 230.4354 7318036 47.4722 10.4117 2.28537848 2 Times: 0 360 720 1080 1440 # I played around with some zero-e satellites to check that bug: #1 11801U 80230.29629788 .01431103 00000-0 14311-1 2 #2 11801U 46.7916 230.4354 0000000 47.4722 10.4117 2.28537848 4 1 23581U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214 2 23581 1.1236 93.7945 0005741 214.4722 151.5103 1.00270260 23672 /* above is GOES 9 */ 1 11871U 80057A 01309.36911127 -.00000499 +00000-0 +10000-3 0 08380 2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886 /* above is Cosmos 1191 */ 1 09931U 77029A 01309.17453186 -.00000329 +00000-0 +10000-3 0 05967 2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451 /* Switch around to different times to test: */ Times: 1440 0 360 1080 720 719 -2880 /* Cosmos 1217 */ 1 12032U 80085A 01309.42683181 .00000182 00000-0 10000-3 0 3499 2 12032 65.2329 86.7607 7086222 172.0967 212.4632 2.00879501101699 /* Molniya 3-19Rk */ 1 13446U 82083E 01283.10818257 .00098407 45745-7 54864-3 0 6240 2 13446 62.1717 83.8458 7498877 273.9677 320.2568 2.06357523137203 /* Ariane Deb */ 1 23246U 91015G 01311.70347086 .00004957 00000-0 43218-2 0 8190 2 23246 7.1648 263.6949 5661268 241.8299 50.5793 4.44333001129208 1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 87 2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 1058 1 00553U 63004A 77069.11186343 -.00000050 +00000-0 +00000-0 0 00056 2 00553 033.5060 020.0500 0337500 270.6300 085.5000 01.00948389000106 /* above is Syncom 1 */ Unknown 98003 1 98003U 01079.82102009 0.00000000 00000-0 +00000+0 0 01 2 98003 15.5935 354.8013 0015600 263.1079 96.8986 0.99816000 03 95586 A 1 99331U 95586 A 01083.00000000 .00000000 00000-0 00000+0 0 02 2 99331 2.0413 66.6424 0042740 166.0448 281.2000 1.00273142 04 96750A 1 99337U 96750A 01088.00000000 .00000000 00000-0 00000+0 0 07 2 99337 6.1829 349.1711 0918991 187.9440 347.1192 1.00269427 03 Iridium 921tum 5.5 6.6 1 24873U 97034E 02082.49700151 .00007707 00000-0 80741-3 0 6717 2 24873 86.3901 131.0630 0010160 338.7719 21.3095 14.89420428254310 96051J 1 24875U 96051J 02082.46969260 .00045815 00000-0 12983-1 0 540 2 24875 71.1295 245.1704 0037175 68.5321 292.0237 14.44819470284117 97035A 1 24876U 97035A 02078.28561105 +.00000010 +00000-0 +00000-0 0 09256 2 24876 055.7160 084.3160 0019362 356.8391 003.1921 02.00573292034149 94029GM 1 24138U 94029GM 02078.77267326 +.00026592 +00000-0 +63509-2 0 09707 2 24138 081.5941 007.5003 0284757 314.7693 043.0606 14.31683973298361 94029HA 1 24151U 94029HA 02078.68402149 +.00005369 +00000-0 +11280-2 0 09568 2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215 94029HF 1 24156U 94029HF 02079.22959898 .00078684 00000-0 11005-1 0 3773 2 24156 82.1981 162.4355 0207818 237.7291 120.3519 14.63437792297579 Molniya 2-9 4.5 15 1 07276U 74026A 02076.77143097 -.00000040 +00000-0 +10000-3 0 09926 2 07276 064.4058 003.2732 6887090 270.2374 017.8845 02.24788602077456 73086EZ 1 07191U 73086EZ 02076.63595131 -.00000031 00000-0 10000-3 0 9045 2 07191 102.1490 334.0121 0253607 54.7437 307.7251 12.10431881825991 73086FA 1 07192U 73086FA 02077.02656547 -.00000031 00000-0 10000-3 0 6091 2 07192 102.1562 252.5740 0443592 220.8713 135.8303 11.59390485200610 GPS-0003 5.0 .53 1 11054U 78093A 02069.41465182 -.00000030 00000-0 00000+0 0 7661 2 11054 62.4872 198.8383 0057450 192.9653 166.8284 1.93504662169140 Molniya 3-10 5.0 0.0 0.0 4.5 15 1 11057U 78095A 02077.63138055 .00000149 00000-0 10000-3 0 5309 2 11057 63.7422 203.7241 6717022 282.5815 14.8175 2.34176921180342 99066A 1 25989U 99066A 02080.37500000 .00000000 00000-0 00000+0 0 4082 2 25989 34.6613 172.6861 7995138 107.4591 3.7150 0.50103982 1159 XMM Ari Rk 3.0 20 1 25990U 99066B 02082.00000000 .00000000 00000-0 00000+0 0 3260 2 25990 44.2277 151.3168 8216838 138.1402 356.4324 0.55308221 1445 Now do some tests of "plain, ordinary" SGP: Ephem 1 1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 87 2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 1058 73086EZ 1 07191U 73086EZ 02076.63595131 -.00000031 00000-0 10000-3 0 9045 2 07191 102.1490 334.0121 0253607 54.7437 307.7251 12.10431881825991 94029HA 1 24151U 94029HA 02078.68402149 +.00005369 +00000-0 +11280-2 0 09568 2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215 Now test some SxP8 cases: Ephem 4 1 11801U 80230.29629788 .01431103 00000-0 14311-1 2 2 11801U 46.7916 230.4354 7318036 47.4722 10.4117 2.28537848 2 Times: 1440 0 360 1080 720 719 -2880 1 23581U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214 2 23581 1.1236 93.7945 0005741 214.4722 151.5103 1.00270260 23672 /* above is GOES 9 */ 1 11871U 80057A 01309.36911127 -.00000499 +00000-0 +10000-3 0 08380 2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886 /* above is Cosmos 1191 */ 1 09931U 77029A 01309.17453186 -.00000329 +00000-0 +10000-3 0 05967 2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451 /* Cosmos 1217 */ 1 12032U 80085A 01309.42683181 .00000182 00000-0 10000-3 0 3499 2 12032 65.2329 86.7607 7086222 172.0967 212.4632 2.00879501101699 /* Molniya 3-19Rk */ 1 13446U 82083E 01283.10818257 .00098407 45745-7 54864-3 0 6240 2 13446 62.1717 83.8458 7498877 273.9677 320.2568 2.06357523137203 /* Ariane Deb */ 1 23246U 91015G 01311.70347086 .00004957 00000-0 43218-2 0 8190 2 23246 7.1648 263.6949 5661268 241.8299 50.5793 4.44333001129208 1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 87 2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 1058 1 00553U 63004A 77069.11186343 -.00000050 +00000-0 +00000-0 0 00056 2 00553 033.5060 020.0500 0337500 270.6300 085.5000 01.00948389000106 /* above is Syncom 1 */ Now switch back to SxP4: Ephem 2 Unknown 98003 1 98003U 01079.82102009 0.00000000 00000-0 +00000+0 0 01 2 98003 15.5935 354.8013 0015600 263.1079 96.8986 0.99816000 03 95586 A 1 99331U 95586 A 01083.00000000 .00000000 00000-0 00000+0 0 02 2 99331 2.0413 66.6424 0042740 166.0448 281.2000 1.00273142 04 96750A 1 99337U 96750A 01088.00000000 .00000000 00000-0 00000+0 0 07 2 99337 6.1829 349.1711 0918991 187.9440 347.1192 1.00269427 03 Iridium 921tum 5.5 6.6 1 24873U 97034E 02082.49700151 .00007707 00000-0 80741-3 0 6717 2 24873 86.3901 131.0630 0010160 338.7719 21.3095 14.89420428254310 96051J 1 24875U 96051J 02082.46969260 .00045815 00000-0 12983-1 0 540 2 24875 71.1295 245.1704 0037175 68.5321 292.0237 14.44819470284117 97035A 1 24876U 97035A 02078.28561105 +.00000010 +00000-0 +00000-0 0 09256 2 24876 055.7160 084.3160 0019362 356.8391 003.1921 02.00573292034149 94029GM 1 24138U 94029GM 02078.77267326 +.00026592 +00000-0 +63509-2 0 09707 2 24138 081.5941 007.5003 0284757 314.7693 043.0606 14.31683973298361 94029HA 1 24151U 94029HA 02078.68402149 +.00005369 +00000-0 +11280-2 0 09568 2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215 94029HF 1 24156U 94029HF 02079.22959898 .00078684 00000-0 11005-1 0 3773 2 24156 82.1981 162.4355 0207818 237.7291 120.3519 14.63437792297579 Molniya 2-9 4.5 15 1 07276U 74026A 02076.77143097 -.00000040 +00000-0 +10000-3 0 09926 2 07276 064.4058 003.2732 6887090 270.2374 017.8845 02.24788602077456 73086EZ 1 07191U 73086EZ 02076.63595131 -.00000031 00000-0 10000-3 0 9045 2 07191 102.1490 334.0121 0253607 54.7437 307.7251 12.10431881825991 73086FA 1 07192U 73086FA 02077.02656547 -.00000031 00000-0 10000-3 0 6091 2 07192 102.1562 252.5740 0443592 220.8713 135.8303 11.59390485200610 GPS-0003 5.0 .53 1 11054U 78093A 02069.41465182 -.00000030 00000-0 00000+0 0 7661 2 11054 62.4872 198.8383 0057450 192.9653 166.8284 1.93504662169140 Molniya 3-10 5.0 0.0 0.0 4.5 15 1 11057U 78095A 02077.63138055 .00000149 00000-0 10000-3 0 5309 2 11057 63.7422 203.7241 6717022 282.5815 14.8175 2.34176921180342 99066A 1 25989U 99066A 02080.37500000 .00000000 00000-0 00000+0 0 4082 2 25989 34.6613 172.6861 7995138 107.4591 3.7150 0.50103982 1159 XMM Ari Rk 3.0 20 1 25990U 99066B 02082.00000000 .00000000 00000-0 00000+0 0 3260 2 25990 44.2277 151.3168 8216838 138.1402 356.4324 0.55308221 1445 28 Jun 2007: test some imaginary 0-incl and 90-incl cases (Just taking some above cases and resetting incl) XMM Ari Rk 3.0 20 1 95990U 99066B 02082.00000000 .00000000 00000-0 00000+0 0 3260 2 95990 00.0000 151.3168 8216838 138.1402 356.4324 0.55308221 1445 XMM Ari Rk 3.0 20 1 95991U 99066B 02082.00000000 .00000000 00000-0 00000+0 0 3260 2 95991 90.0000 151.3168 8216838 138.1402 356.4324 0.55308221 1445 GPS-0003 5.0 .53 1 91054U 78093A 02069.41465182 -.00000030 00000-0 00000+0 0 7661 2 91054 00.0000 198.8383 0057450 192.9653 166.8284 1.93504662169140 GPS-0003 5.0 .53 1 91055U 78093A 02069.41465182 -.00000030 00000-0 00000+0 0 7661 2 91055 90.0000 198.8383 0057450 192.9653 166.8284 1.93504662169140 GOES-9 1 93581U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214 2 93581 0.0000 93.7945 0005741 214.4722 151.5103 1.00270260 23672 GOES-9 1 93582U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214 2 93582 90.0000 93.7945 0005741 214.4722 151.5103 1.00270260 23672 94029HF 1 94156U 94029HF 02079.22959898 .00078684 00000-0 11005-1 0 3773 2 94156 00.0000 162.4355 0207818 237.7291 120.3519 14.63437792297579 94029HF 1 94156U 94029HF 02079.22959898 .00078684 00000-0 11005-1 0 3773 2 94156 90.0000 162.4355 0207818 237.7291 120.3519 14.63437792297579 ================================================ FILE: test2.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ /* * test2.cpp 5 July 2002 * * A skeleton main() function to demonstrate the use of * the various NORAD ephemerides. It reads in the file * 'test.tle' and computes ephemerides at assorted times for * all elements in the file. The times used, and the choice * of SxP4 vs. SxP8, can be switched with various keywords in * the 'test.tle' file. The result should match 'test2.txt'. */ #include #include #include #include "norad.h" /* Main program */ int main( int argc, char **argv) { double vel[3], pos[3]; /* Satellite position and velocity vectors */ FILE *ifile = fopen( (argc == 1) ? "test.tle" : argv[1], "rb"); tle_t tle; /* Pointer to two-line elements set for satellite */ char line1[100], line2[100]; int n_times = 5, dundee_output = 0; double times[400]; int ephem = TLE_EPHEMERIS_TYPE_SGP4; /* default to SGP4 */ int i; /* Index for loops etc */ for( i = 2; i < argc; i++) switch( argv[i][0]) { case 's': case 'S': sxpx_set_dpsec_integration_step( atof( argv[i] + 1)); break; case 'd': case 'D': sxpx_set_implementation_param( SXPX_DUNDEE_COMPLIANCE, 1); break; case 'o': case 'O': { const int new_order = atoi( argv[i] + 1); sxpx_set_implementation_param( SXPX_DPSEC_INTEGRATION_ORDER, new_order); printf( "Set to order %d\n", new_order); } break; default: break; } if( !ifile) { printf( "Couldn't open input TLE file\n"); exit( -1); } for( i = 0; i < n_times; i++) times[i] = (double)(i * 30); if( fgets( line1, sizeof( line1), ifile)) while( fgets( line2, sizeof( line2), ifile)) { if( !strncmp( line2, "Ephem ", 6)) ephem = (line2[6] - '0'); else if( !strncmp( line2, "Dundee ", 7)) { double t0, step; sscanf( line2 + 7, "%lf %lf %d", &t0, &step, &n_times); for( i = 0; i < n_times; i++) times[i] = t0 + (double)i * step; sxpx_set_implementation_param( SXPX_DUNDEE_COMPLIANCE, 1); dundee_output = 1; } else if( !strncmp( line2, "Times: ", 7)) { int loc = 7, bytes_read; n_times = 0; while( sscanf( line2 + loc, "%lf%n", times + n_times, &bytes_read) == 1) { loc += bytes_read; n_times++; } } else if( parse_elements( line1, line2, &tle) >= 0) { /* hey! we got a TLE! */ int is_deep = select_ephemeris( &tle); const char *ephem_names[6] = { "???", "SGP ", "SGP4", "SDP4", "SGP8", "SDP8" }; double sat_params[N_SAT_PARAMS]; if( is_deep) if( ephem == TLE_EPHEMERIS_TYPE_SGP4 || ephem == TLE_EPHEMERIS_TYPE_SGP8) ephem++; /* switch to an SDPx model */ if( !is_deep) if( ephem == TLE_EPHEMERIS_TYPE_SDP4 || ephem == TLE_EPHEMERIS_TYPE_SDP8) ephem--; /* switch to an SGPx model */ line1[69] = line2[69] = '\0'; if( dundee_output) printf( "#%s\n#%s\n", line1, line2); else printf( "%s\n%s\n", line1, line2); if( is_deep) printf("Deep-Space type Ephemeris (%s) selected:", ephem_names[ephem]); else printf("Near-Earth type Ephemeris (%s) selected:", ephem_names[ephem]); /* Print some titles for the results */ printf("\nEphem:%s Tsince " "X/Xdot Y/Ydot Z/Zdot\n", ephem_names[ephem]); /* Calling of NORAD routines */ /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8) */ /* will be called in turn with the appropriate TLE set */ switch( ephem) { case TLE_EPHEMERIS_TYPE_SGP: SGP_init( sat_params, &tle); break; case TLE_EPHEMERIS_TYPE_SGP4: SGP4_init( sat_params, &tle); break; case TLE_EPHEMERIS_TYPE_SGP8: SGP8_init( sat_params, &tle); break; case TLE_EPHEMERIS_TYPE_SDP4: SDP4_init( sat_params, &tle); break; case TLE_EPHEMERIS_TYPE_SDP8: SDP8_init( sat_params, &tle); break; } for( i = 0; i < n_times; i++) { switch( ephem) { case TLE_EPHEMERIS_TYPE_SGP: SGP(times[i], &tle, sat_params, pos, vel); break; case TLE_EPHEMERIS_TYPE_SGP4: SGP4(times[i], &tle, sat_params, pos, vel); break; case TLE_EPHEMERIS_TYPE_SGP8: SGP8(times[i], &tle, sat_params, pos, vel); break; case TLE_EPHEMERIS_TYPE_SDP4: SDP4(times[i], &tle, sat_params, pos, vel); break; case TLE_EPHEMERIS_TYPE_SDP8: SDP8(times[i], &tle, sat_params, pos, vel); break; } /* Calculate and print results */ vel[0] /= 60.; /* cvt km/minute to km/second */ vel[1] /= 60.; vel[2] /= 60.; if( dundee_output) { printf("%12.4f %16.8f %16.8f %16.8f", times[i],pos[0],pos[1],pos[2]); printf(" %16.8f %16.8f %16.8f\n", vel[0],vel[1],vel[2]); } else { printf("%5d %12.4f %16.8f %16.8f %16.8f\n", tle.norad_number, times[i],pos[0],pos[1],pos[2]); printf(" %16.8f %16.8f %16.8f\n", vel[0],vel[1],vel[2]); } } /* End of for( i = 0; i < n_times; i++) */ printf( "\n"); } strcpy( line1, line2); } fclose( ifile); return(0); } /* End of main() */ /*------------------------------------------------------------------*/ ================================================ FILE: test2.txt ================================================ 1 11801U 80230.29629788 .01431103 00000-0 14311-1 2 2 11801U 46.7916 230.4354 7318036 47.4722 10.4117 2.28537848 2 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 11801 0.0000 7473.37102491 428.94748312 5828.74846783 5.10715539 6.44468030 -0.18613330 11801 30.0000 12817.23227575 10727.64071596 3224.73035812 1.55348611 4.88681176 -2.04097954 11801 60.0000 14347.23583626 18291.73191417 -650.88975796 0.31695966 3.60827212 -2.18699629 11801 90.0000 14313.54593833 23940.73287015 -4510.03744310 -0.29382550 2.72035225 -2.08527406 11801 120.0000 13433.32206997 28200.97792854 -8121.65089654 -0.65569830 2.04581505 -1.92429145 1 23581U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214 2 23581 1.1236 93.7945 0005741 214.4722 151.5103 1.00270260 23672 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 23581 0.0000 -7160.41365734 41573.61482091 85.47880804 -3.02812768 -0.52082979 0.05893406 23581 360.0000 -41536.21736236 -7273.04931852 809.12349307 0.53157545 -3.02755929 -0.00641841 23581 720.0000 7464.73494206 -41476.91139123 -91.39056841 3.02688546 0.54571783 -0.05902754 23581 1080.0000 41434.84155007 7708.64310940 -808.83820227 -0.56086085 3.02392840 0.00697885 23581 1440.0000 -7871.57126245 41445.00919691 99.12160293 -3.01873852 -0.57266839 0.05898119 1 11871U 80057A 01309.36911127 -.00000499 +00000-0 +10000-3 0 08380 2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 11871 0.0000 43282.52935746 1440.14268930 -0.01880874 0.08916078 0.70325544 1.69640498 11871 360.0000 -8886.88881104 1787.65122655 5038.32829715 -2.42927268 -2.92232292 -6.88492344 11871 720.0000 43297.72522868 1558.05868418 352.88012824 0.04464192 0.70168665 1.69622177 11871 1080.0000 -9328.72863362 1155.93861586 3528.92688833 -1.69877545 -3.03902445 -7.22864706 11871 1440.0000 43303.79129284 1675.62317479 705.61253596 0.00018291 0.70000195 1.69566320 1 09931U 77029A 01309.17453186 -.00000329 +00000-0 +10000-3 0 05967 2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 9931 0.0000 -1450.46578294 -13892.29635713 0.21811478 5.30608015 2.82110366 2.48435091 9931 360.0000 -37380.50682907 14075.66229597 -19257.82108262 -0.81609862 -1.55557599 -0.32333790 9931 720.0000 -5779.43293287 -15647.33186164 -2041.47031557 4.98318767 1.44460184 2.39381161 9931 1080.0000 -36647.73763359 15310.44505813 -18967.56466829 -0.95882958 -1.49958741 -0.39571389 9931 1440.0000 -9777.78151614 -16467.82307878 -3965.44782190 4.55173861 0.54513994 2.22628227 1 12032U 80085A 01309.42683181 .00000182 00000-0 10000-3 0 3499 2 12032 65.2329 86.7607 7086222 172.0967 212.4632 2.00879501101699 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 12032 1440.0000 2331.62892366 44022.20816050 554.21122658 -0.72034020 -0.57755776 1.48580161 12032 0.0000 2510.47723752 44233.19407940 0.93706893 -0.71498431 -0.50248390 1.48587218 12032 360.0000 8450.60804136 7596.98538745 -17343.84383269 0.97944154 4.47406400 -1.56440368 12032 1080.0000 8636.82456996 8420.67864547 -17626.79631967 0.91532271 4.40846479 -1.42419046 12032 720.0000 2421.36024093 44131.25141321 277.59524786 -0.71766025 -0.53991700 1.48595676 12032 719.0000 2464.34935316 44163.28337493 188.44421966 -0.71698368 -0.52770165 1.48602103 12032 -2880.0000 2860.46995216 44570.57466099 -1104.55343567 -0.70434023 -0.35454669 1.48326012 1 13446U 82083E 01283.10818257 .00098407 45745-7 54864-3 0 6240 2 13446 62.1717 83.8458 7498877 273.9677 320.2568 2.06357523137203 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 13446 1440.0000 -2156.94601599 -12986.70815558 1319.55403922 2.99651462 3.43770618 -4.91521599 13446 0.0000 -8858.01184499 -17331.75404577 13120.92920652 2.05423032 0.46265679 -3.77255550 13446 360.0000 -20305.89938689 4925.35315665 39231.84704259 -0.47149899 -1.43712333 0.59080129 13446 1080.0000 -20848.14292436 2991.39610461 39855.37807885 -0.35039272 -1.46076889 0.35347694 13446 720.0000 -5855.20063554 -16115.69300375 7668.38932692 2.47282838 1.42587377 -4.35844448 13446 719.0000 -6002.95015689 -16199.52165415 7929.07184135 2.45181263 1.36859026 -4.33078580 13446 -2880.0000 -16562.18261910 -15455.95800455 28206.33120550 1.07001582 -0.90409223 -2.19013296 1 23246U 91015G 01311.70347086 .00004957 00000-0 43218-2 0 8190 2 23246 7.1648 263.6949 5661268 241.8299 50.5793 4.44333001129208 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 23246 1440.0000 21837.19258152 -9442.43552275 2873.31676381 0.50387639 2.74057691 0.01948022 23246 0.0000 -1584.31681305 -14414.24207936 0.71409546 4.09367813 -3.52120490 0.56056050 23246 360.0000 7115.97219959 -18536.64843313 1154.11312419 3.73304414 -0.64667684 0.47582954 23246 1080.0000 19317.46548831 -14693.47102098 2636.02297085 1.75419415 2.01009519 0.18820066 23246 720.0000 14292.78023665 -18025.45817671 2051.73388801 2.82632243 0.95550567 0.33889498 23246 719.0000 14122.26322965 -18081.82054152 2031.25426527 2.85402200 0.92028827 0.34287876 23246 -2880.0000 6652.58546066 -18825.60389076 1016.60260765 3.69013844 -0.73211531 0.47012980 1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 87 2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 1058 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 88888 1440.0000 2742.55398832 -6079.67009123 -326.39012649 1.94849765 1.21107268 -7.35619313 88888 0.0000 2328.96975262 -5995.22051338 1719.97297192 2.91207328 -0.98341796 -7.09081621 88888 360.0000 2456.10706534 -6071.93855503 1222.89768554 2.67939004 -0.44829081 -7.22879215 88888 1080.0000 2663.08964352 -6115.48290885 196.40072867 2.19612156 0.65241509 -7.36282415 88888 720.0000 2567.56229695 -6112.50383923 713.96374435 2.44024575 0.09810900 -7.31995926 88888 719.0000 2415.04546550 -6103.57181162 1151.06777227 2.64172961 -0.39588102 -7.24431138 88888 -2880.0000 811.47797491 -4309.22921192 4950.18397460 4.37308232 -4.52925216 -4.56259925 1 00553U 63004A 77069.11186343 -.00000050 +00000-0 +00000-0 0 00056 2 00553 033.5060 020.0500 0337500 270.6300 085.5000 01.00948389000106 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 553 1440.0000 38666.00852401 16320.06355336 1390.26214785 -0.95534267 2.38460447 1.69996307 553 0.0000 39371.88960849 14357.67432729 3.56059383 -0.78417317 2.45190105 1.70300152 553 360.0000 -10430.26409143 34654.56621873 23921.02602917 -2.83879733 -0.90289886 0.08194908 553 1080.0000 10636.62965551 -32115.18508783 -22388.43831871 3.01460343 1.03226685 -0.04110622 553 720.0000 -40584.37401875 -10916.51306065 2408.92105255 0.67194594 -2.47531856 -1.69215116 553 719.0000 -40624.29934803 -10767.89279867 2510.42520728 0.65893063 -2.47879395 -1.69136266 553 -2880.0000 40358.16947683 10286.74194270 -2763.22558192 -0.43103606 2.56117493 1.69059979 1 98003U 01079.82102009 0.00000000 00000-0 +00000+0 0 01 2 98003 15.5935 354.8013 0015600 263.1079 96.8986 0.99816000 03 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 98003 1440.0000 42094.43874734 -4162.63317801 -92.11251765 0.29731062 2.94167365 0.82466681 98003 0.0000 42138.92218986 -3695.09300700 36.83858364 0.26211534 2.94493828 0.82474875 98003 360.0000 3816.94976897 40619.28630698 11380.14550089 -3.05212619 0.28557745 0.00228001 98003 1080.0000 -4017.34782962 -40477.81421130 -11345.46738327 3.05972108 -0.30236485 -0.00667726 98003 720.0000 -42078.79869336 4169.84032031 95.55774114 -0.28895565 -2.94353633 -0.82500563 98003 719.0000 -42061.06251637 4346.40846620 145.05553205 -0.30226340 -2.94218937 -0.82496759 98003 -2880.0000 42211.20422977 -2757.46203195 295.45498207 0.19153724 2.95030553 0.82456479 1 99331U 95586 A 01083.00000000 .00000000 00000-0 00000+0 0 02 2 99331 2.0413 66.6424 0042740 166.0448 281.2000 1.00273142 04 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 99331 1440.0000 -37967.20007787 18188.54928284 1490.04691741 -1.31786034 -2.78108633 0.00497201 99331 0.0000 -37650.29239730 18842.51288431 1488.61754716 -1.36547168 -2.75777441 0.00690546 99331 360.0000 -18244.13481977 -37816.78394398 78.83403095 2.77788965 -1.34339543 -0.10919525 99331 1080.0000 18205.05681771 38226.11783331 -71.55581790 -2.76381418 1.31337921 0.10832465 99331 720.0000 38182.58653158 -17913.55930782 -1493.30077039 1.31619112 2.77567162 -0.00502478 99331 719.0000 38103.25227752 -18079.92503503 -1492.98404546 1.32832823 2.76994505 -0.00549993 99331 -2880.0000 -36982.00226763 20135.38091665 1484.30297813 -1.45957012 -2.70863518 0.01067441 1 99337U 96750A 01088.00000000 .00000000 00000-0 00000+0 0 07 2 99337 6.1829 349.1711 0918991 187.9440 347.1192 1.00269427 03 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 99337 1440.0000 -36655.10977909 11362.03173985 455.96900256 -0.92608924 -3.21432746 -0.35997337 99337 0.0000 -36430.83156122 12108.20282917 539.55631417 -0.98617001 -3.19517122 -0.35913115 99337 360.0000 -3574.53338013 -41263.36760245 -4450.55948116 3.06244248 -0.55314863 0.00394174 99337 1080.0000 18299.51337777 39008.25661897 4512.66010157 -2.81152115 1.02034041 0.05076290 99337 720.0000 44763.86907633 -10494.10728220 -197.95581429 0.68873260 2.70568360 0.30113860 99337 719.0000 44722.21536405 -10656.36841511 -216.02165984 0.69974345 2.70308111 0.30108769 99337 -2880.0000 -35941.07714058 13582.00600072 706.72604147 -1.10477292 -3.15343148 -0.35704600 1 24873U 97034E 02082.49700151 .00007707 00000-0 80741-3 0 6717 2 24873 86.3901 131.0630 0010160 338.7719 21.3095 14.89420428254310 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 24873 1440.0000 -3170.36218705 4147.28608625 -4630.10112111 -3.53759777 3.58053394 5.63682354 24873 0.0000 -4581.41284523 5258.61084472 0.13120073 -0.35532106 -0.31679238 7.55020536 24873 360.0000 1154.60502384 -670.29871734 -6855.07085843 -4.79972247 5.66175625 -1.35462785 24873 1080.0000 -2633.05049869 2482.40755471 5958.60901423 4.03798855 -5.06780188 3.89613866 24873 720.0000 4157.11952736 -5047.38360362 2456.26467926 2.08212434 -1.72942731 -7.05069815 24873 719.0000 4023.52709308 -4933.06375726 2873.82317083 2.36940346 -2.07990992 -6.86301350 24873 -2880.0000 -844.68212043 286.22703339 6914.24678007 4.98354396 -5.61903649 0.84742770 1 24875U 96051J 02082.46969260 .00045815 00000-0 12983-1 0 540 2 24875 71.1295 245.1704 0037175 68.5321 292.0237 14.44819470284117 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 24875 1440.0000 3727.97388868 5657.05158335 2196.20530149 -0.91008388 3.24025401 -6.68298737 24875 0.0000 -2985.35799132 -6456.57434435 5.43144043 2.21461091 -0.98130257 7.09050046 24875 360.0000 954.42359398 5539.03869291 -4419.41213767 -3.73131396 -3.60392820 -5.34789246 24875 1080.0000 -3385.36425424 -2309.38161890 -5850.47516091 -1.78554526 -6.30701412 3.55127838 24875 720.0000 1525.50746008 -2036.85257183 6614.96286888 3.59899132 6.48806129 1.17208193 24875 719.0000 1306.65238603 -2421.78994445 6531.38238635 3.69374045 6.33889898 1.61303838 24875 -2880.0000 -433.70797548 -5513.40720019 4444.41457039 3.44271869 4.03963766 5.31126150 1 24876U 97035A 02078.28561105 +.00000010 +00000-0 +00000-0 0 09256 2 24876 055.7160 084.3160 0019362 356.8391 003.1921 02.00573292034149 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 24876 1440.0000 2099.41349937 26412.90516367 804.49807544 -2.18932651 0.07620057 3.20431913 24876 0.0000 2625.97440180 26378.11936794 4.93598359 -2.17691797 0.21563137 3.20640808 24876 360.0000 -2497.91488385 -26492.75727038 -215.05161132 2.17194116 -0.17926961 -3.19391301 24876 1080.0000 -2235.67544642 -26509.99535031 -613.34346165 2.17808366 -0.11008692 -3.19285598 24876 720.0000 2363.06016716 26399.81509304 404.83568315 -2.18346040 0.14593024 3.20589849 24876 719.0000 2493.97473666 26390.04248290 212.46848448 -2.18034211 0.17982242 3.20629487 24876 -2880.0000 3668.52647508 26205.35817905 -1594.36971909 -2.14399113 0.49367668 3.19776010 1 24138U 94029GM 02078.77267326 +.00026592 +00000-0 +63509-2 0 09707 2 24138 081.5941 007.5003 0284757 314.7693 043.0606 14.31683973298361 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 24138 1440.0000 -2644.46044349 711.84109121 6816.75919791 -6.75946029 -1.13271605 -2.41488958 24138 0.0000 6960.83886410 916.39636032 -0.35227799 -0.00283653 1.12124163 7.53007134 24138 360.0000 -6575.12211937 -1248.69051257 -2759.35846726 3.12213925 -0.59446457 -6.66420362 24138 1080.0000 -1260.83554616 -1167.51006970 -6834.94030738 7.42952369 0.68738953 -1.31119896 24138 720.0000 3833.67603624 1356.98826915 5941.80488966 -6.10632098 -0.12493292 4.21033574 24138 719.0000 4192.46252358 1361.87543045 5677.91444852 -5.84922873 -0.03784350 4.58349220 24138 -2880.0000 -5379.10149179 -140.63187806 5028.19534452 -4.82984652 -1.57769725 -5.17470482 1 24151U 94029HA 02078.68402149 +.00005369 +00000-0 +11280-2 0 09568 2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 24151 1440.0000 -6968.30877120 -929.52208053 1034.74250336 -0.80683886 -1.16641712 -7.35690383 24151 0.0000 7014.87041848 1202.45499306 0.64793182 -0.04978932 1.04323018 7.40731873 24151 360.0000 -5152.24684405 -1503.50157586 -4520.64323072 5.06324107 0.04552212 -5.64402671 24151 1080.0000 4676.15154374 -1.83212884 -5242.49623820 5.59390309 1.57561016 4.85528798 24151 720.0000 375.74981624 1074.45821808 7136.52489241 -7.25295787 -1.10477013 0.54189381 24151 719.0000 809.94234149 1138.67114921 7090.50151769 -7.21557010 -1.03498445 0.99168739 24151 -2880.0000 6855.50805677 1570.68533265 1126.34405871 -1.25338851 0.78905151 7.32628675 1 24156U 94029HF 02079.22959898 .00078684 00000-0 11005-1 0 3773 2 24156 82.1981 162.4355 0207818 237.7291 120.3519 14.63437792297579 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 24156 1440.0000 4989.95103335 -991.96048211 -4694.62995315 -4.75190381 2.38718598 -5.51355655 24156 0.0000 -6806.70697240 2154.76645090 -1.33970314 -0.42401840 -0.92331212 7.36329634 24156 360.0000 4072.30634864 -505.08877357 -5572.74271903 -5.74652368 2.48040035 -4.41726446 24156 1080.0000 -6585.62318688 2365.50422511 -1320.83679750 -1.76181618 -0.46018527 7.22613299 24156 720.0000 1912.45112070 -1587.96387581 6693.48120108 6.74703272 -1.83838893 -2.49156574 24156 719.0000 1504.14766273 -1474.62142897 6829.72995099 6.85862162 -1.93841503 -2.04871469 24156 -2880.0000 275.97078586 901.03018894 -6871.10287465 -7.35389459 2.07610218 -0.09182194 1 07276U 74026A 02076.77143097 -.00000040 +00000-0 +10000-3 0 09926 2 07276 064.4058 003.2732 6887090 270.2374 017.8845 02.24788602077456 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 7276 1440.0000 11679.26473158 15504.27588846 31001.50846115 -1.44086891 0.74720602 1.72238289 7276 0.0000 12929.17822712 737.31896601 -0.93772206 3.66517540 2.61185503 5.01178899 7276 360.0000 -8415.24017849 16549.03285541 35497.28095363 -1.60749073 -0.53051900 -0.91712311 7276 1080.0000 -15186.86012009 12823.64831401 28488.61304853 -1.17308269 -1.05510209 -2.06539757 7276 720.0000 16901.65208240 10351.66308625 19617.59800800 -0.59625193 1.47294504 3.14133784 7276 719.0000 16936.85061239 10262.95529869 19428.46499770 -0.57745384 1.48439746 3.16303871 7276 -2880.0000 -4858.50529290 17447.37140077 36997.57469065 -1.69260423 -0.31621755 -0.44065120 1 07191U 73086EZ 02076.63595131 -.00000031 00000-0 10000-3 0 9045 2 07191 102.1490 334.0121 0253607 54.7437 307.7251 12.10431881825991 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 7191 1440.0000 5197.68379504 -3532.40188648 4642.94280275 -4.52981807 0.79135016 5.57991435 7191 0.0000 7095.62534394 -3459.54977223 2.65766867 -0.79920008 -1.28572174 6.99886910 7191 360.0000 6876.82395295 -3614.28549445 1242.00621199 -1.80950045 -0.77766069 6.90918302 7191 1080.0000 5917.38908180 -3650.54888754 3593.33932356 -3.70227403 0.27502090 6.19225553 7191 720.0000 6481.41080392 -3678.31238626 2449.51628455 -2.78652792 -0.25275130 6.63881188 7191 719.0000 6638.89853614 -3657.68854846 2047.73362803 -2.46167103 -0.43439031 6.75039533 7191 -2880.0000 3051.67782955 225.45893096 -7549.72713780 5.53973976 -3.48618620 2.28442985 1 07192U 73086FA 02077.02656547 -.00000031 00000-0 10000-3 0 6091 2 07192 102.1562 252.5740 0443592 220.8713 135.8303 11.59390485200610 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 7192 1440.0000 2735.71311905 6452.74755933 -3628.13173013 0.29924939 -3.70237520 -6.23622609 7192 0.0000 -2551.23569421 -8127.44238653 0.87987080 -1.40913462 0.23779939 6.57529016 7192 360.0000 -1039.48185564 -6786.59096842 -4710.94007931 -2.34291375 -3.69510078 5.31633780 7192 1080.0000 2333.10979200 2362.32508796 -7199.46956782 -1.35450606 -6.54040439 -2.74857242 7192 720.0000 848.53025315 -2793.33496212 -7554.32974481 -2.36766420 -6.41802990 1.80245163 7192 719.0000 989.38243489 -2404.65801357 -7652.21700164 -2.32628871 -6.53504480 1.45945499 7192 -2880.0000 230.46423657 -3990.71376868 -7081.27099350 -2.68412105 -5.84908358 2.87113784 1 11054U 78093A 02069.41465182 -.00000030 00000-0 00000+0 0 7661 2 11054 62.4872 198.8383 0057450 192.9653 166.8284 1.93504662169140 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 11054 1440.0000 -25388.34079710 -3403.40500597 -9531.93443534 -0.90793207 -2.01537981 3.10310036 11054 0.0000 -25891.75008739 -8827.17634454 -7.11543233 0.56259784 -1.66520502 3.37653764 11054 360.0000 25891.99345977 7409.73868823 2577.67984854 -0.18479992 1.80406882 -3.39491593 11054 1080.0000 25613.56652955 4649.87873299 7410.72787690 0.56951792 1.98044940 -3.24876400 11054 720.0000 -26175.21689483 -6242.60712582 -4868.83227692 -0.17573206 -1.87873379 3.30745510 11054 719.0000 -26163.75460152 -6129.66517687 -5067.10706616 -0.20634035 -1.88596920 3.30164344 11054 -2880.0000 -14933.48469609 -14743.73993336 17504.98769057 3.00456330 -0.25365150 2.32952622 1 11057U 78095A 02077.63138055 .00000149 00000-0 10000-3 0 5309 2 11057 63.7422 203.7241 6717022 282.5815 14.8175 2.34176921180342 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 11057 1440.0000 7487.87611216 -15287.29467029 34493.07590041 1.86179494 0.47483499 0.62605389 11057 0.0000 -10507.51193223 -4611.31451017 0.19783099 -2.18140918 -4.00411475 5.66127724 11057 360.0000 20970.88101088 -7510.36265833 31022.77017800 0.99148126 1.17155611 -1.36986859 11057 1080.0000 23275.29351376 644.95594684 17692.25539266 -0.47864173 1.36230389 -2.92144014 11057 720.0000 -4731.80410387 -15608.68113270 25160.74838285 1.90090167 -0.49586326 2.46683975 11057 719.0000 -4845.71559110 -15578.51400495 25012.06196398 1.89663033 -0.50977445 2.48923053 11057 -2880.0000 5859.91793323 -15633.31281630 33804.16988953 1.90326131 0.38485257 0.85574795 1 25989U 99066A 02080.37500000 .00000000 00000-0 00000+0 0 4082 2 25989 34.6613 172.6861 7995138 107.4591 3.7150 0.50103982 1159 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 25989 1440.0000 -25416.31130478 97972.48561814 -65060.88827233 -0.78954842 -0.14306696 0.17001526 25989 0.0000 13446.88594396 -7340.90106369 3841.96143867 5.25343486 3.15492823 -2.63964361 25989 360.0000 27779.82527478 52562.51176863 -38630.70190521 -0.55664353 1.86219779 -1.23020626 25989 1080.0000 -7306.20327748 95752.02176493 -65174.16312805 -0.87298261 0.35585797 -0.16572419 25989 720.0000 11590.33887760 81938.35349884 -57377.90111303 -0.85404887 0.94997888 -0.57613966 25989 719.0000 11641.57460814 81881.29555505 -57343.27701803 -0.85377652 0.95189906 -0.57748483 25989 -2880.0000 11272.09487443 -8515.50280915 4864.79909744 5.79043510 2.81320525 -2.44112033 1 25990U 99066B 02082.00000000 .00000000 00000-0 00000+0 0 3260 2 25990 44.2277 151.3168 8216838 138.1402 356.4324 0.55308221 1445 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 25990 1440.0000 -52618.00493523 87304.59689650 -50015.63309567 -0.57621934 -0.27322941 0.50620611 25990 0.0000 -7091.02772480 -7636.71306513 9880.83142057 6.57104133 -2.04322973 -1.34121281 25990 360.0000 6941.12895930 47572.57987853 -44038.73006094 -1.08656024 1.87029231 -1.09113296 25990 1080.0000 -37147.20354816 87628.63341664 -57597.53860234 -0.84368212 0.24906550 0.18441609 25990 720.0000 -16647.88296791 75877.09428533 -57189.29131723 -1.04294149 0.86860516 -0.25294533 25990 719.0000 -16585.29849772 75824.91738346 -57174.06802345 -1.04338459 0.87062791 -0.25447089 25990 -2880.0000 -51622.41035047 33904.99902952 -4876.35838422 1.10240406 -1.97705598 1.17712115 1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 87 2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 1058 Near-Earth type Ephemeris (SGP ) selected: Ephem:SGP Tsince X/Xdot Y/Ydot Z/Zdot 88888 1440.0000 2742.85536090 -6079.13550910 -328.86284909 1.94707845 1.21346317 -7.35499922 88888 0.0000 2328.96608372 -5995.21604006 1719.97873587 2.91110102 -0.98164033 -7.09049933 88888 360.0000 2456.00667036 -6071.94243808 1222.95881238 2.67852067 -0.44705739 -7.22800586 88888 1080.0000 2663.03277851 -6115.37384735 195.73637767 2.19531669 0.65334243 -7.36169154 88888 720.0000 2567.39619789 -6112.49733464 713.97377545 2.43952312 0.09885196 -7.31889680 88888 719.0000 2414.88066271 -6103.56671515 1151.08195539 2.64086106 -0.39464444 -7.24352530 88888 -2880.0000 777.57978207 -4273.14463448 4985.36908569 4.38355864 -4.57458675 -4.51353652 1 07191U 73086EZ 02076.63595131 -.00000031 00000-0 10000-3 0 9045 2 07191 102.1490 334.0121 0253607 54.7437 307.7251 12.10431881825991 Near-Earth type Ephemeris (SGP ) selected: Ephem:SGP Tsince X/Xdot Y/Ydot Z/Zdot 7191 1440.0000 5197.66873608 -3532.31805398 4643.01987432 -4.52897204 0.79041016 5.58191897 7191 0.0000 7095.62120595 -3459.54927659 2.66401972 -0.79910050 -1.28559271 6.99812407 7191 360.0000 6876.82512150 -3614.26385689 1242.03845145 -1.80869892 -0.77793181 6.90867534 7191 1080.0000 5917.38393015 -3650.48327444 3593.40814162 -3.70098826 0.27411893 6.19332356 7191 720.0000 6481.41138382 -3678.26827404 2449.56983740 -2.78527757 -0.25340334 6.63896277 7191 719.0000 6638.90133477 -3657.64485481 2047.78825011 -2.46053716 -0.43492114 6.75028306 7191 -2880.0000 3051.43789296 225.52678160 -7549.82076196 5.54201639 -3.48810492 2.28652461 1 24151U 94029HA 02078.68402149 +.00005369 +00000-0 +11280-2 0 09568 2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215 Near-Earth type Ephemeris (SGP ) selected: Ephem:SGP Tsince X/Xdot Y/Ydot Z/Zdot 24151 1440.0000 -6968.32932565 -929.40448044 1034.64126944 -0.80580417 -1.16615531 -7.35630983 24151 0.0000 7014.86418693 1202.45521289 0.65699104 -0.04981154 1.04310209 7.40643748 24151 360.0000 -5152.23932194 -1503.47537247 -4520.65312876 5.06208677 0.04482191 -5.64742354 24151 1080.0000 4676.17981006 -1.89119338 -5242.47744425 5.59374094 1.57609633 4.85948272 24151 720.0000 375.73095005 1074.44783207 7136.51617056 -7.25840064 -1.10546387 0.54275292 24151 719.0000 809.92451644 1138.65695987 7090.49457660 -7.22083372 -1.03555127 0.99323935 24151 -2880.0000 6855.43779076 1570.95203705 1126.35885487 -1.25231806 0.78915162 7.32573752 1 11801U 80230.29629788 .01431103 00000-0 14311-1 2 2 11801U 46.7916 230.4354 7318036 47.4722 10.4117 2.28537848 2 Deep-Space type Ephemeris (SDP8) selected: Ephem:SDP8 Tsince X/Xdot Y/Ydot Z/Zdot 11801 1440.0000 9420.07877124 33847.21210751 -15391.07637268 -1.11986083 0.85410171 -1.49506971 11801 0.0000 7469.47832570 415.99601049 5829.64070389 5.11402275 6.44403160 -0.18296530 11801 360.0000 -3337.39439914 32351.38232011 -24658.64280341 -1.30200749 -1.15603002 -0.28164871 11801 1080.0000 -10151.60323755 22223.69587529 -23392.40546558 -1.00112488 -2.33532743 0.76987728 11801 720.0000 14226.54285494 24236.08413793 -4856.20981132 -0.33951710 2.65315365 -2.08114247 11801 719.0000 14246.55591480 24076.39086885 -4731.31979056 -0.32472236 2.67823919 -2.08611617 11801 -2880.0000 -223.35624900 35795.86991125 -24125.87317194 -1.28307664 -0.69669553 -0.59333659 1 23581U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214 2 23581 1.1236 93.7945 0005741 214.4722 151.5103 1.00270260 23672 Deep-Space type Ephemeris (SDP8) selected: Ephem:SDP8 Tsince X/Xdot Y/Ydot Z/Zdot 23581 1440.0000 -7871.56853853 41445.01028733 99.12526295 -3.01873843 -0.57266832 0.05898135 23581 0.0000 -7160.41019110 41573.61598173 85.48240526 -3.02812759 -0.52082969 0.05893423 23581 360.0000 -41536.21579580 -7273.04718857 809.12524992 0.53157531 -3.02755942 -0.00641868 23581 1080.0000 41434.84287574 7708.64529580 -808.83999233 -0.56086098 3.02392827 0.00697913 23581 720.0000 7464.73793892 -41476.91032627 -91.39443550 3.02688554 0.54571792 -0.05902770 23581 719.0000 7283.05704075 -41509.25513334 -87.85184603 3.02924171 0.53245970 -0.05905626 23581 -2880.0000 -5731.90933914 41794.16078256 56.75309470 -3.04423869 -0.41669898 0.05871630 1 11871U 80057A 01309.36911127 -.00000499 +00000-0 +10000-3 0 08380 2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886 Deep-Space type Ephemeris (SDP8) selected: Ephem:SDP8 Tsince X/Xdot Y/Ydot Z/Zdot 11871 1440.0000 43302.80205821 1671.33337009 708.84290227 -0.00007483 0.70004183 1.69580723 11871 0.0000 43281.63009884 1435.59001959 3.14486765 0.08891078 0.70328553 1.69655546 11871 360.0000 -8889.54166788 1785.40677378 5032.32456780 -2.42809846 -2.92244080 -6.88563770 11871 1080.0000 -9330.69533866 1153.81200018 3522.58325526 -1.69743737 -3.03901748 -7.22910421 11871 720.0000 43296.78149493 1553.63423509 356.07045290 0.04438893 0.70172180 1.69636906 11871 719.0000 43293.73109183 1511.51575886 254.29738462 0.05712382 0.70217258 1.69645888 11871 -2880.0000 43129.19936626 959.66710935 -1409.52575892 0.26766933 0.70838993 1.69353546 1 09931U 77029A 01309.17453186 -.00000329 +00000-0 +10000-3 0 05967 2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451 Deep-Space type Ephemeris (SDP8) selected: Ephem:SDP8 Tsince X/Xdot Y/Ydot Z/Zdot 9931 1440.0000 -9778.48987133 -16465.90211417 -3968.19260205 4.55266487 0.54373338 2.22551193 9931 0.0000 -1449.49218871 -13892.08044897 -3.10530650 5.30771421 2.82012032 2.48432921 9931 360.0000 -37387.46937492 14074.89826166 -19247.30542923 -0.81611892 -1.55548092 -0.32311993 9931 1080.0000 -36654.75223572 15309.13449317 -18957.41610972 -0.95886046 -1.49951537 -0.39542419 9931 720.0000 -5779.28997617 -15646.31216122 -2044.68222537 4.98442796 1.44324070 2.39333905 9931 719.0000 -6077.47588018 -15730.57952632 -2187.96602288 4.95506237 1.36555165 2.38284936 9931 -2880.0000 7311.67417756 7867.60640719 3240.01029370 -2.39893349 6.94310651 -1.52787421 1 12032U 80085A 01309.42683181 .00000182 00000-0 10000-3 0 3499 2 12032 65.2329 86.7607 7086222 172.0967 212.4632 2.00879501101699 Deep-Space type Ephemeris (SDP8) selected: Ephem:SDP8 Tsince X/Xdot Y/Ydot Z/Zdot 12032 1440.0000 2332.67684843 44018.77243136 560.22303913 -0.72056504 -0.57801149 1.48601722 12032 0.0000 2511.95138081 44229.85772465 6.90735682 -0.71520971 -0.50292459 1.48609834 12032 360.0000 8456.13178456 7596.47415381 -17345.64200705 0.98145741 4.47229845 -1.56375719 12032 1080.0000 8642.68187872 8419.98150257 -17628.39595336 0.91731844 4.40681207 -1.42358978 12032 720.0000 2422.62111468 44127.86585308 283.58703413 -0.71788553 -0.54036416 1.48617770 12032 719.0000 2465.67705412 44159.91318851 194.42947846 -0.71720917 -0.52814665 1.48624365 12032 -2880.0000 2862.79943427 44567.42649491 -1098.68468651 -0.70456283 -0.35496244 1.48350597 1 13446U 82083E 01283.10818257 .00098407 45745-7 54864-3 0 6240 2 13446 62.1717 83.8458 7498877 273.9677 320.2568 2.06357523137203 Deep-Space type Ephemeris (SDP8) selected: Ephem:SDP8 Tsince X/Xdot Y/Ydot Z/Zdot 13446 1440.0000 -2125.88142754 -12947.66502817 1283.41745547 2.99883067 3.45765109 -4.92196263 13446 0.0000 -8853.93990719 -17327.36676474 13139.86856630 2.05271983 0.46128459 -3.77277615 13446 360.0000 -20299.70180013 4920.93352717 39241.59207738 -0.47132845 -1.43645540 0.59062252 13446 1080.0000 -20841.89139093 2979.73374086 39863.34261809 -0.34977240 -1.46022047 0.35241917 13446 720.0000 -5846.21876690 -16107.67410473 7674.94185833 2.47196537 1.42588337 -4.36101922 13446 719.0000 -5993.90664751 -16191.56920641 7935.72755383 2.45095181 1.36858556 -4.33328807 13446 -2880.0000 -16514.44453543 -15495.04420318 28136.69038241 1.07537700 -0.89797779 -2.19970996 1 23246U 91015G 01311.70347086 .00004957 00000-0 43218-2 0 8190 2 23246 7.1648 263.6949 5661268 241.8299 50.5793 4.44333001129208 Deep-Space type Ephemeris (SDP8) selected: Ephem:SDP8 Tsince X/Xdot Y/Ydot Z/Zdot 23246 1440.0000 21838.75432359 -9448.92449345 2882.78618298 0.50386494 2.73950392 0.01915119 23246 0.0000 -1570.27461571 -14416.86458350 7.00840579 4.09713683 -3.51695816 0.56345678 23246 360.0000 7130.55602310 -18534.41345052 1164.06716757 3.73258667 -0.64402643 0.47713833 23246 1080.0000 19322.51132279 -14694.47518853 2646.50178227 1.75313528 2.00940835 0.18819521 23246 720.0000 14302.94654416 -18022.87491164 2062.60799238 2.82489986 0.95613920 0.33936666 23246 719.0000 14132.58308794 -18079.18162736 2042.12566658 2.85260295 0.92097151 0.34336744 23246 -2880.0000 6673.73527112 -18825.43286661 1027.53767406 3.68910987 -0.72788220 0.47136626 1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 87 2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 1058 Near-Earth type Ephemeris (SGP8) selected: Ephem:SGP8 Tsince X/Xdot Y/Ydot Z/Zdot 88888 1440.0000 2743.29435330 -6078.90669382 -329.74076507 1.94680613 1.21500821 -7.35625567 88888 0.0000 2328.87330160 -5995.21266135 1720.04860850 2.91210628 -0.98353806 -7.09081552 88888 360.0000 2456.04639018 -6071.90492842 1222.83939295 2.67936164 -0.44820689 -7.22888597 88888 1080.0000 2663.49578474 -6115.18153477 194.62560022 2.19525089 0.65453937 -7.36308979 88888 720.0000 2567.68504071 -6112.40874085 713.29056655 2.43992421 0.09894177 -7.32018816 88888 719.0000 2415.18557698 -6103.52890192 1150.40884796 2.64142685 -0.39506444 -7.24458496 88888 -2880.0000 820.91948977 -4319.03528436 4940.20423767 4.37073001 -4.51683717 -4.57704872 1 00553U 63004A 77069.11186343 -.00000050 +00000-0 +00000-0 0 00056 2 00553 033.5060 020.0500 0337500 270.6300 085.5000 01.00948389000106 Deep-Space type Ephemeris (SDP8) selected: Ephem:SDP8 Tsince X/Xdot Y/Ydot Z/Zdot 553 1440.0000 38665.99209639 16320.09343680 1390.38386144 -0.95534613 2.38459407 1.69997574 553 0.0000 39371.87433765 14357.71188366 3.67164552 -0.78417677 2.45189100 1.70301485 553 360.0000 -10430.23639914 34654.47913595 23921.22576863 -2.83879331 -0.90289819 0.08194935 553 1080.0000 10636.57831004 -32115.05510514 -22388.58911206 3.01460753 1.03226846 -0.04110713 553 720.0000 -40584.37656205 -10916.48555626 2409.05615369 0.67193646 -2.47531248 -1.69216320 553 719.0000 -40624.30145743 -10767.86568235 2510.56100536 0.65892114 -2.47878785 -1.69137464 553 -2880.0000 40358.15660775 10286.79429280 -2763.13836900 -0.43103967 2.56116578 1.69061427 1 98003U 01079.82102009 0.00000000 00000-0 +00000+0 0 01 2 98003 15.5935 354.8013 0015600 263.1079 96.8986 0.99816000 03 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 98003 1440.0000 42094.43874734 -4162.63317801 -92.11251765 0.29731062 2.94167365 0.82466681 98003 0.0000 42138.92218986 -3695.09300700 36.83858364 0.26211534 2.94493828 0.82474875 98003 360.0000 3816.94976897 40619.28630698 11380.14550089 -3.05212619 0.28557745 0.00228001 98003 1080.0000 -4017.34782962 -40477.81421130 -11345.46738327 3.05972108 -0.30236485 -0.00667726 98003 720.0000 -42078.79869336 4169.84032031 95.55774114 -0.28895565 -2.94353633 -0.82500563 98003 719.0000 -42061.06251637 4346.40846620 145.05553205 -0.30226340 -2.94218937 -0.82496759 98003 -2880.0000 42211.20422977 -2757.46203195 295.45498207 0.19153724 2.95030553 0.82456479 1 99331U 95586 A 01083.00000000 .00000000 00000-0 00000+0 0 02 2 99331 2.0413 66.6424 0042740 166.0448 281.2000 1.00273142 04 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 99331 1440.0000 -37967.20007787 18188.54928284 1490.04691741 -1.31786034 -2.78108633 0.00497201 99331 0.0000 -37650.29239730 18842.51288431 1488.61754716 -1.36547168 -2.75777441 0.00690546 99331 360.0000 -18244.13481977 -37816.78394398 78.83403095 2.77788965 -1.34339543 -0.10919525 99331 1080.0000 18205.05681771 38226.11783331 -71.55581790 -2.76381418 1.31337921 0.10832465 99331 720.0000 38182.58653158 -17913.55930782 -1493.30077039 1.31619112 2.77567162 -0.00502478 99331 719.0000 38103.25227752 -18079.92503503 -1492.98404546 1.32832823 2.76994505 -0.00549993 99331 -2880.0000 -36982.00226763 20135.38091665 1484.30297813 -1.45957012 -2.70863518 0.01067441 1 99337U 96750A 01088.00000000 .00000000 00000-0 00000+0 0 07 2 99337 6.1829 349.1711 0918991 187.9440 347.1192 1.00269427 03 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 99337 1440.0000 -36655.10977909 11362.03173985 455.96900256 -0.92608924 -3.21432746 -0.35997337 99337 0.0000 -36430.83156122 12108.20282917 539.55631417 -0.98617001 -3.19517122 -0.35913115 99337 360.0000 -3574.53338013 -41263.36760245 -4450.55948116 3.06244248 -0.55314863 0.00394174 99337 1080.0000 18299.51337777 39008.25661897 4512.66010157 -2.81152115 1.02034041 0.05076290 99337 720.0000 44763.86907633 -10494.10728220 -197.95581429 0.68873260 2.70568360 0.30113860 99337 719.0000 44722.21536405 -10656.36841511 -216.02165984 0.69974345 2.70308111 0.30108769 99337 -2880.0000 -35941.07714058 13582.00600072 706.72604147 -1.10477292 -3.15343148 -0.35704600 1 24873U 97034E 02082.49700151 .00007707 00000-0 80741-3 0 6717 2 24873 86.3901 131.0630 0010160 338.7719 21.3095 14.89420428254310 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 24873 1440.0000 -3170.36218705 4147.28608625 -4630.10112111 -3.53759777 3.58053394 5.63682354 24873 0.0000 -4581.41284523 5258.61084472 0.13120073 -0.35532106 -0.31679238 7.55020536 24873 360.0000 1154.60502384 -670.29871734 -6855.07085843 -4.79972247 5.66175625 -1.35462785 24873 1080.0000 -2633.05049869 2482.40755471 5958.60901423 4.03798855 -5.06780188 3.89613866 24873 720.0000 4157.11952736 -5047.38360362 2456.26467926 2.08212434 -1.72942731 -7.05069815 24873 719.0000 4023.52709308 -4933.06375726 2873.82317083 2.36940346 -2.07990992 -6.86301350 24873 -2880.0000 -844.68212043 286.22703339 6914.24678007 4.98354396 -5.61903649 0.84742770 1 24875U 96051J 02082.46969260 .00045815 00000-0 12983-1 0 540 2 24875 71.1295 245.1704 0037175 68.5321 292.0237 14.44819470284117 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 24875 1440.0000 3727.97388868 5657.05158335 2196.20530149 -0.91008388 3.24025401 -6.68298737 24875 0.0000 -2985.35799132 -6456.57434435 5.43144043 2.21461091 -0.98130257 7.09050046 24875 360.0000 954.42359398 5539.03869291 -4419.41213767 -3.73131396 -3.60392820 -5.34789246 24875 1080.0000 -3385.36425424 -2309.38161890 -5850.47516091 -1.78554526 -6.30701412 3.55127838 24875 720.0000 1525.50746008 -2036.85257183 6614.96286888 3.59899132 6.48806129 1.17208193 24875 719.0000 1306.65238603 -2421.78994445 6531.38238635 3.69374045 6.33889898 1.61303838 24875 -2880.0000 -433.70797548 -5513.40720019 4444.41457039 3.44271869 4.03963766 5.31126150 1 24876U 97035A 02078.28561105 +.00000010 +00000-0 +00000-0 0 09256 2 24876 055.7160 084.3160 0019362 356.8391 003.1921 02.00573292034149 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 24876 1440.0000 2099.41349937 26412.90516367 804.49807544 -2.18932651 0.07620057 3.20431913 24876 0.0000 2625.97440180 26378.11936794 4.93598359 -2.17691797 0.21563137 3.20640808 24876 360.0000 -2497.91488385 -26492.75727038 -215.05161132 2.17194116 -0.17926961 -3.19391301 24876 1080.0000 -2235.67544642 -26509.99535031 -613.34346165 2.17808366 -0.11008692 -3.19285598 24876 720.0000 2363.06016716 26399.81509304 404.83568315 -2.18346040 0.14593024 3.20589849 24876 719.0000 2493.97473666 26390.04248290 212.46848448 -2.18034211 0.17982242 3.20629487 24876 -2880.0000 3668.52647508 26205.35817905 -1594.36971909 -2.14399113 0.49367668 3.19776010 1 24138U 94029GM 02078.77267326 +.00026592 +00000-0 +63509-2 0 09707 2 24138 081.5941 007.5003 0284757 314.7693 043.0606 14.31683973298361 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 24138 1440.0000 -2644.46044349 711.84109121 6816.75919791 -6.75946029 -1.13271605 -2.41488958 24138 0.0000 6960.83886410 916.39636032 -0.35227799 -0.00283653 1.12124163 7.53007134 24138 360.0000 -6575.12211937 -1248.69051257 -2759.35846726 3.12213925 -0.59446457 -6.66420362 24138 1080.0000 -1260.83554616 -1167.51006970 -6834.94030738 7.42952369 0.68738953 -1.31119896 24138 720.0000 3833.67603624 1356.98826915 5941.80488966 -6.10632098 -0.12493292 4.21033574 24138 719.0000 4192.46252358 1361.87543045 5677.91444852 -5.84922873 -0.03784350 4.58349220 24138 -2880.0000 -5379.10149179 -140.63187806 5028.19534452 -4.82984652 -1.57769725 -5.17470482 1 24151U 94029HA 02078.68402149 +.00005369 +00000-0 +11280-2 0 09568 2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 24151 1440.0000 -6968.30877120 -929.52208053 1034.74250336 -0.80683886 -1.16641712 -7.35690383 24151 0.0000 7014.87041848 1202.45499306 0.64793182 -0.04978932 1.04323018 7.40731873 24151 360.0000 -5152.24684405 -1503.50157586 -4520.64323072 5.06324107 0.04552212 -5.64402671 24151 1080.0000 4676.15154374 -1.83212884 -5242.49623820 5.59390309 1.57561016 4.85528798 24151 720.0000 375.74981624 1074.45821808 7136.52489241 -7.25295787 -1.10477013 0.54189381 24151 719.0000 809.94234149 1138.67114921 7090.50151769 -7.21557010 -1.03498445 0.99168739 24151 -2880.0000 6855.50805677 1570.68533265 1126.34405871 -1.25338851 0.78905151 7.32628675 1 24156U 94029HF 02079.22959898 .00078684 00000-0 11005-1 0 3773 2 24156 82.1981 162.4355 0207818 237.7291 120.3519 14.63437792297579 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 24156 1440.0000 4989.95103335 -991.96048211 -4694.62995315 -4.75190381 2.38718598 -5.51355655 24156 0.0000 -6806.70697240 2154.76645090 -1.33970314 -0.42401840 -0.92331212 7.36329634 24156 360.0000 4072.30634864 -505.08877357 -5572.74271903 -5.74652368 2.48040035 -4.41726446 24156 1080.0000 -6585.62318688 2365.50422511 -1320.83679750 -1.76181618 -0.46018527 7.22613299 24156 720.0000 1912.45112070 -1587.96387581 6693.48120108 6.74703272 -1.83838893 -2.49156574 24156 719.0000 1504.14766273 -1474.62142897 6829.72995099 6.85862162 -1.93841503 -2.04871469 24156 -2880.0000 275.97078586 901.03018894 -6871.10287465 -7.35389459 2.07610218 -0.09182194 1 07276U 74026A 02076.77143097 -.00000040 +00000-0 +10000-3 0 09926 2 07276 064.4058 003.2732 6887090 270.2374 017.8845 02.24788602077456 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 7276 1440.0000 11679.26473158 15504.27588846 31001.50846115 -1.44086891 0.74720602 1.72238289 7276 0.0000 12929.17822712 737.31896601 -0.93772206 3.66517540 2.61185503 5.01178899 7276 360.0000 -8415.24017849 16549.03285541 35497.28095363 -1.60749073 -0.53051900 -0.91712311 7276 1080.0000 -15186.86012009 12823.64831401 28488.61304853 -1.17308269 -1.05510209 -2.06539757 7276 720.0000 16901.65208240 10351.66308625 19617.59800800 -0.59625193 1.47294504 3.14133784 7276 719.0000 16936.85061239 10262.95529869 19428.46499770 -0.57745384 1.48439746 3.16303871 7276 -2880.0000 -4858.50529290 17447.37140077 36997.57469065 -1.69260423 -0.31621755 -0.44065120 1 07191U 73086EZ 02076.63595131 -.00000031 00000-0 10000-3 0 9045 2 07191 102.1490 334.0121 0253607 54.7437 307.7251 12.10431881825991 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 7191 1440.0000 5197.68379504 -3532.40188648 4642.94280275 -4.52981807 0.79135016 5.57991435 7191 0.0000 7095.62534394 -3459.54977223 2.65766867 -0.79920008 -1.28572174 6.99886910 7191 360.0000 6876.82395295 -3614.28549445 1242.00621199 -1.80950045 -0.77766069 6.90918302 7191 1080.0000 5917.38908180 -3650.54888754 3593.33932356 -3.70227403 0.27502090 6.19225553 7191 720.0000 6481.41080392 -3678.31238626 2449.51628455 -2.78652792 -0.25275130 6.63881188 7191 719.0000 6638.89853614 -3657.68854846 2047.73362803 -2.46167103 -0.43439031 6.75039533 7191 -2880.0000 3051.67782955 225.45893096 -7549.72713780 5.53973976 -3.48618620 2.28442985 1 07192U 73086FA 02077.02656547 -.00000031 00000-0 10000-3 0 6091 2 07192 102.1562 252.5740 0443592 220.8713 135.8303 11.59390485200610 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 7192 1440.0000 2735.71311905 6452.74755933 -3628.13173013 0.29924939 -3.70237520 -6.23622609 7192 0.0000 -2551.23569421 -8127.44238653 0.87987080 -1.40913462 0.23779939 6.57529016 7192 360.0000 -1039.48185564 -6786.59096842 -4710.94007931 -2.34291375 -3.69510078 5.31633780 7192 1080.0000 2333.10979200 2362.32508796 -7199.46956782 -1.35450606 -6.54040439 -2.74857242 7192 720.0000 848.53025315 -2793.33496212 -7554.32974481 -2.36766420 -6.41802990 1.80245163 7192 719.0000 989.38243489 -2404.65801357 -7652.21700164 -2.32628871 -6.53504480 1.45945499 7192 -2880.0000 230.46423657 -3990.71376868 -7081.27099350 -2.68412105 -5.84908358 2.87113784 1 11054U 78093A 02069.41465182 -.00000030 00000-0 00000+0 0 7661 2 11054 62.4872 198.8383 0057450 192.9653 166.8284 1.93504662169140 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 11054 1440.0000 -25388.34079710 -3403.40500597 -9531.93443534 -0.90793207 -2.01537981 3.10310036 11054 0.0000 -25891.75008739 -8827.17634454 -7.11543233 0.56259784 -1.66520502 3.37653764 11054 360.0000 25891.99345977 7409.73868823 2577.67984854 -0.18479992 1.80406882 -3.39491593 11054 1080.0000 25613.56652955 4649.87873299 7410.72787690 0.56951792 1.98044940 -3.24876400 11054 720.0000 -26175.21689483 -6242.60712582 -4868.83227692 -0.17573206 -1.87873379 3.30745510 11054 719.0000 -26163.75460152 -6129.66517687 -5067.10706616 -0.20634035 -1.88596920 3.30164344 11054 -2880.0000 -14933.48469609 -14743.73993336 17504.98769057 3.00456330 -0.25365150 2.32952622 1 11057U 78095A 02077.63138055 .00000149 00000-0 10000-3 0 5309 2 11057 63.7422 203.7241 6717022 282.5815 14.8175 2.34176921180342 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 11057 1440.0000 7487.87611216 -15287.29467029 34493.07590041 1.86179494 0.47483499 0.62605389 11057 0.0000 -10507.51193223 -4611.31451017 0.19783099 -2.18140918 -4.00411475 5.66127724 11057 360.0000 20970.88101088 -7510.36265833 31022.77017800 0.99148126 1.17155611 -1.36986859 11057 1080.0000 23275.29351376 644.95594684 17692.25539266 -0.47864173 1.36230389 -2.92144014 11057 720.0000 -4731.80410387 -15608.68113270 25160.74838285 1.90090167 -0.49586326 2.46683975 11057 719.0000 -4845.71559110 -15578.51400495 25012.06196398 1.89663033 -0.50977445 2.48923053 11057 -2880.0000 5859.91793323 -15633.31281630 33804.16988953 1.90326131 0.38485257 0.85574795 1 25989U 99066A 02080.37500000 .00000000 00000-0 00000+0 0 4082 2 25989 34.6613 172.6861 7995138 107.4591 3.7150 0.50103982 1159 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 25989 1440.0000 -25416.31130478 97972.48561814 -65060.88827233 -0.78954842 -0.14306696 0.17001526 25989 0.0000 13446.88594396 -7340.90106369 3841.96143867 5.25343486 3.15492823 -2.63964361 25989 360.0000 27779.82527478 52562.51176863 -38630.70190521 -0.55664353 1.86219779 -1.23020626 25989 1080.0000 -7306.20327748 95752.02176493 -65174.16312805 -0.87298261 0.35585797 -0.16572419 25989 720.0000 11590.33887760 81938.35349884 -57377.90111303 -0.85404887 0.94997888 -0.57613966 25989 719.0000 11641.57460814 81881.29555505 -57343.27701803 -0.85377652 0.95189906 -0.57748483 25989 -2880.0000 11272.09487443 -8515.50280915 4864.79909744 5.79043510 2.81320525 -2.44112033 1 25990U 99066B 02082.00000000 .00000000 00000-0 00000+0 0 3260 2 25990 44.2277 151.3168 8216838 138.1402 356.4324 0.55308221 1445 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 25990 1440.0000 -52618.00493523 87304.59689650 -50015.63309567 -0.57621934 -0.27322941 0.50620611 25990 0.0000 -7091.02772480 -7636.71306513 9880.83142057 6.57104133 -2.04322973 -1.34121281 25990 360.0000 6941.12895930 47572.57987853 -44038.73006094 -1.08656024 1.87029231 -1.09113296 25990 1080.0000 -37147.20354816 87628.63341664 -57597.53860234 -0.84368212 0.24906550 0.18441609 25990 720.0000 -16647.88296791 75877.09428533 -57189.29131723 -1.04294149 0.86860516 -0.25294533 25990 719.0000 -16585.29849772 75824.91738346 -57174.06802345 -1.04338459 0.87062791 -0.25447089 25990 -2880.0000 -51622.41035047 33904.99902952 -4876.35838422 1.10240406 -1.97705598 1.17712115 1 95990U 99066B 02082.00000000 .00000000 00000-0 00000+0 0 3260 2 95990 00.0000 151.3168 8216838 138.1402 356.4324 0.55308221 1445 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 95990 1440.0000 -43028.16265150 105027.52135728 -86.44476862 -0.67725863 -0.45493814 0.00148717 95990 0.0000 -9276.06994652 -11192.55362548 19.74681299 6.77723696 -1.59630165 -0.00494890 95990 360.0000 15617.58408603 63138.16468711 -83.51392335 -0.87578308 2.26526301 -0.00149824 95990 1080.0000 -26002.93639782 108081.44788857 -104.30468723 -0.88306083 0.18258216 0.00083816 95990 720.0000 -5490.19722222 96188.86013527 -105.41918111 -0.99754948 0.95972033 0.00001050 95990 719.0000 -5430.31436335 96131.20728971 -105.40170659 -0.99769520 0.96229431 0.00000772 95990 -2880.0000 -50722.91908053 35425.12742795 6.30125070 0.87609338 -2.39985300 0.00042810 1 95991U 99066B 02082.00000000 .00000000 00000-0 00000+0 0 3260 2 95991 90.0000 151.3168 8216838 138.1402 356.4324 0.55308221 1445 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 95991 1440.0000 -77142.85325007 42202.12222005 -71827.26078425 -0.32460531 0.18262205 0.72425332 95991 0.0000 -1449.02321392 866.63307741 13838.48853373 5.94468426 -3.28999887 -2.15892181 95991 360.0000 -14921.77469094 7923.57455850 -63270.62053583 -1.61750897 0.88474033 -1.55440657 95991 1080.0000 -65469.72451903 35711.05750889 -82650.53346374 -0.74960338 0.41480084 0.26296217 95991 720.0000 -44860.22328081 24346.42005898 -82042.76539914 -1.16282667 0.63970290 -0.36210062 95991 719.0000 -44790.44228688 24308.02618737 -82020.95956739 -1.16401610 0.64034860 -0.36427748 95991 -2880.0000 -53712.53773042 29617.54568095 -6866.35842176 1.68961372 -0.92423244 1.68808926 1 91054U 78093A 02069.41465182 -.00000030 00000-0 00000+0 0 7661 2 91054 00.0000 198.8383 0057450 192.9653 166.8284 1.93504662169140 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 91054 1440.0000 -27251.20268469 2043.78342172 0.38495984 -0.29786110 -3.79880308 0.00056321 91054 0.0000 -25895.34937682 -8812.84472596 1.89447853 1.22184076 -3.60538998 0.00043874 91054 360.0000 26402.04813901 5902.77390263 -1.53149539 -0.84680670 3.75471305 -0.00048255 91054 1080.0000 27064.51621164 386.20845725 -0.76370949 -0.06617333 3.84655206 -0.00054573 91054 720.0000 -27124.61157219 -3454.74894822 1.21377642 0.47213367 -3.77884190 0.00051050 91054 719.0000 -27151.98713483 -3227.90076505 1.18311173 0.44038313 -3.78275109 0.00051191 91054 -2880.0000 -11579.82311401 -24757.46950263 3.18227069 3.45616853 -1.60319653 0.00008863 1 91055U 78093A 02069.41465182 -.00000030 00000-0 00000+0 0 7661 2 91055 90.0000 198.8383 0057450 192.9653 166.8284 1.93504662169140 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 91055 1440.0000 -23774.39057753 -8118.55368062 -10765.98615008 -1.43090377 -0.48768145 3.49704597 91055 0.0000 -25888.60922614 -8837.19928921 -13.17971782 -0.00559055 -0.00121123 3.80658803 91055 360.0000 25455.19401338 8689.98537421 2911.95169589 0.38654168 0.13120863 -3.82717489 91055 1080.0000 24359.72673396 8317.58804582 8368.10574508 1.11669867 0.38041827 -3.66183508 91055 720.0000 -25350.66900120 -8655.03112525 -5502.22898025 -0.73280552 -0.24939182 3.72828528 91055 719.0000 -25305.81182186 -8639.76373862 -5725.73068288 -0.76242834 -0.25950543 3.72171824 91055 -2880.0000 -17870.58154378 -6096.60286646 19755.09881530 2.61518678 0.89286753 2.62338324 1 93581U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214 2 93581 0.0000 93.7945 0005741 214.4722 151.5103 1.00270260 23672 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 93581 1440.0000 -7872.43690338 41444.98000544 1.73095095 -3.01930947 -0.57268874 0.00082631 93581 0.0000 -7161.07115751 41573.60354164 1.47785784 -3.02869742 -0.52084436 0.00099181 93581 360.0000 -41544.22550350 -7273.12667506 12.99813118 0.53160320 -3.02755079 -0.00011053 93581 1080.0000 41442.56532942 7708.77148721 -11.85036610 -0.56090269 3.02393927 0.00012095 93581 720.0000 7464.92402276 -41476.96198751 -1.57677016 3.02746245 0.54571529 -0.00090532 93581 719.0000 7283.20850156 -41509.30663531 -1.52257255 3.02981868 0.53245704 -0.00090590 93581 -2880.0000 -5732.21333730 41794.16566212 1.56528295 -3.04480278 -0.41670821 0.00139395 1 93582U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214 2 93582 90.0000 93.7945 0005741 214.4722 151.5103 1.00270260 23672 Deep-Space type Ephemeris (SDP4) selected: Ephem:SDP4 Tsince X/Xdot Y/Ydot Z/Zdot 93582 1440.0000 -2770.54805468 41776.11141003 5173.24086584 0.02451285 -0.37579865 3.04996048 93582 0.0000 -2776.44858404 41857.32053783 4464.56890610 0.02121236 -0.32425422 3.05590006 93582 360.0000 299.47358533 -4581.23336275 41918.44819986 0.20224452 -3.04912682 -0.33617946 93582 1080.0000 -327.46314733 5015.63189813 -41860.76379322 -0.20204370 3.04638074 0.36506868 93582 720.0000 2771.04009467 -41779.25011765 -4798.74748702 -0.02285229 0.34979589 -3.05608468 93582 719.0000 2772.38478802 -41799.83758401 -4615.33487681 -0.02196673 0.33644423 -3.05758868 93582 -2880.0000 -2784.64498244 41983.35866717 3050.79000694 0.01439633 -0.22144006 3.06511691 1 94156U 94029HF 02079.22959898 .00078684 00000-0 11005-1 0 3773 2 94156 00.0000 162.4355 0207818 237.7291 120.3519 14.63437792297579 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 94156 1440.0000 5611.69171535 4037.13827498 -0.00000000 -4.50730682 6.21223117 0.00000000 94156 0.0000 -6804.97205785 2139.79607495 0.00000000 -2.35704590 -7.05634698 0.00000000 94156 360.0000 5349.62740749 4374.60192400 -0.00000000 -4.86600327 5.93916554 0.00000000 94156 1080.0000 -6659.26209827 2501.79806026 0.00000000 -2.75655222 -6.93228196 0.00000000 94156 720.0000 793.05328945 -7099.56293484 0.00000000 7.36795516 0.94789425 -0.00000000 94156 719.0000 349.69283690 -7142.42833287 0.00000000 7.40546107 0.48068087 -0.00000000 94156 -2880.0000 4272.35249129 5450.08922859 -0.00000000 -5.98845596 4.78095723 -0.00000000 1 94156U 94029HF 02079.22959898 .00078684 00000-0 11005-1 0 3773 2 94156 90.0000 162.4355 0207818 237.7291 120.3519 14.63437792297579 Near-Earth type Ephemeris (SGP4) selected: Ephem:SGP4 Tsince X/Xdot Y/Ydot Z/Zdot 94156 1440.0000 4836.62295892 -1530.96953882 -4710.41747366 -4.98875992 1.57912650 -5.59417055 94156 0.0000 -6806.87343390 2154.62647604 -1.48232172 -0.11981850 0.03792697 7.43187690 94156 360.0000 3851.26201663 -1219.06646095 -5619.40500022 -5.93318266 1.87807113 -4.46693858 94156 1080.0000 -6663.81947089 2109.34462099 -1362.72069332 -1.48665647 0.47058160 7.28686744 94156 720.0000 2184.42260844 -691.45031602 6762.52952941 6.66557554 -2.10990048 -2.49518094 94156 719.0000 1780.47096881 -563.58472454 6898.86094376 6.79505660 -2.15088601 -2.04785706 94156 -2880.0000 -52.48990110 16.61498950 -6935.32865533 -7.28529619 2.30606492 -0.00738959 ================================================ FILE: test3.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ /* * test3.cpp 8 October 2002 * * A skeleton main() function to test the speed of SxPx * functions, and the positional differences between SGPx and SDPx. * (I was curious as to whether the extra code in SDPx made any really * significant difference; it was starting to look as if SDPx was a * waste of code. Turns out that it depends greatly on the satellite * in question; I'll post results in a bit, when I find the time.) * * Also, this demonstrates how to use the new dynamically-loaded * functions in 'dynamic.cpp'. This way, if your program can't * find 'sat_code.dll', it'll gracefully give you a message about it. * In the case of my own _Guide_ planetarium software, if it can't * find the DLL, it can fall back on its own SGP4-only code. */ #include #include #include #include #include #include "norad.h" /* Main program */ int main( int argc, char **argv) { FILE *ifile = fopen( (argc == 1) ? "test.tle" : argv[1], "rb"); tle_t tle; /* Pointer to two-line elements set for satellite */ char line1[100], line2[100]; double t_since = atof( argv[2]); double rms_diff = 0.; int verbose = 0, n_found = 0, n_runs = 1, show_sdp = 1; int ephem = 1; /* default to SGP4 */ int i, j; /* Index for loops etc */ clock_t t0; if( !ifile) { printf( "Couldn't open input TLE file\n"); exit( -1); } for( i = 3; i < argc; i++) if( argv[i][0] == '-') switch( argv[i][1]) { case 'v': verbose = atoi( argv[i] + 2); break; case 'n': n_runs = atoi( argv[i] + 2); break; case 's': show_sdp = atoi( argv[i] + 2); break; default: printf( "Option '%s' ignored\n", argv[i]); break; } fgets( line1, sizeof( line1), ifile); t0 = clock( ); while( fgets( line2, sizeof( line2), ifile)) { if( line1[0] == '1' && line2[0] == '2' && parse_elements( line1, line2, &tle) >= 0) /* hey! we got a TLE! */ if( select_ephemeris( &tle) && show_sdp < 2) { double sgp_sat_params[N_SAT_PARAMS]; double sdp_sat_params[N_SAT_PARAMS]; double perigee; #ifdef STATIC_LINK SGP4_init( sgp_sat_params, &tle); if( show_sdp) SDP4_init( sdp_sat_params, &tle); #else if( SXPX_init( sgp_sat_params, &tle, 1)) { printf( "Couldn't load 'sat_code.dll'\n"); exit( -1); } if( show_sdp) SXPX_init( sdp_sat_params, &tle, 3); #endif perigee = sgp_sat_params[9] * (1. - tle.eo) - 1.; line2[63] = '\0'; if( verbose) printf( "%5ld %s", (long)( perigee * 6378.14), line2); for( j = 0; j <= n_runs; j++) { double posn2[3], pos[3], vel[3]; double delta, d2 = 0.; #ifdef STATIC_LINK SGP4( t_since * (double)j, &tle, sgp_sat_params, posn2, vel); if( show_sdp) SDP4( t_since * (double)j, &tle, sdp_sat_params, pos, vel); #else SXPX( t_since * (double)j, &tle, sgp_sat_params, posn2, vel, 1); if( show_sdp) SXPX( t_since * (double)j, &tle, sdp_sat_params, pos, vel, 3); #endif if( !show_sdp) memcpy( pos, posn2, 3 * sizeof( double)); for( i = 0; i < 3; i++) { delta = pos[i] - posn2[i]; d2 += delta * delta; } if( j == 1) rms_diff += d2; if( verbose) printf( "%8.1lf%s", sqrt( d2), (j == n_runs ? "\n" : "")); } n_found++; } strcpy( line1, line2); } fclose( ifile); t0 = clock( ) - t0; printf( "%d deep-space objects found\n", n_found); printf( "%lf seconds elapsed\n", (double)t0 / (double)CLOCKS_PER_SEC); if( n_found) printf( "RMS difference = %.1lf\n", sqrt( rms_diff / (double)n_found)); return(0); } /* End of main() */ /*------------------------------------------------------------------*/ ================================================ FILE: test_des.cpp ================================================ /* Copyright (C) 2021, Project Pluto. See LICENSE. */ /* Code to test Alpha-5 and 'extended' Alpha-5 (Super-5) designation schemes. These enable us to have nine-digit NORAD numbers while still fitting (most of the) requirements of the current TLE format. See comments about the Alpha-5 and Super-5 schemes in 'get_el.cpp'. Run without command-line arguments, this tries out all possible Super-5 designations with a "round-trip" test : encode, then decode to ensure we get the original number. A couple past 34^5 are run to make sure that they fail as expected. In this scheme, the lexical order of Super-5 designations should mismatch the numerical order at several points, and this test code verifies that. */ #include #include #include #include #include #include "norad.h" int main( const int argc, const char **argv) { char buff[160], *line1 = buff, line2[80]; int n; tle_t tle; const int one_billion = 1000000000; strcpy( line1, "1 00005U 58002B 21124.86416743 -.00000116 00000-0 -00000-0 0 9998"); strcpy( line2, "2 00005 34.2496 318.0626 1847159 358.7427 0.8906 10.84838792240168"); parse_elements( line1, line2, &tle); if( argc > 1) { n = tle.norad_number = atoi( argv[1]); write_elements_in_tle_format( buff, &tle); printf( "Super-5 version : '%.5s'\n", buff + 2); parse_elements( line1, line2, &tle); printf( "Got back NORAD %d\n", tle.norad_number); assert( n == tle.norad_number); return( 0); } printf( "Testing all Super-5 designations. This should take an hour or two.\n"); for( n = 0; n < one_billion + 2; n++) { tle.norad_number = n; write_elements_in_tle_format( buff, &tle); parse_elements( line1, line2, &tle); if( tle.norad_number != n) printf( "Got back NORAD %d (should be %d)\n%s\n", tle.norad_number, n, buff); if( !(n % 1000000)) /* progress indicator */ { printf( "."); fflush( stdout); } } return( 0); } ================================================ FILE: test_out.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include #include #include "norad.h" #include "norad_in.h" #define PI \ 3.1415926535897932384626433832795028841971693993751058209749445923 void greg_day_to_dmy( const long jd, int *day, int *month, long *year) { const long mar_1_year_0 = 1721120L; /* JD 1721120 = 1.5 Mar 0 Greg */ const long one_year = 365L; const long four_years = 4 * one_year + 1; const long century = 25 * four_years - 1L; /* days in 100 'normal' yrs */ const long quad_cent = century * 4 + 1; /* days in 400 years */ long days = jd - mar_1_year_0; long day_in_cycle = days % quad_cent; if( day_in_cycle < 0) day_in_cycle += quad_cent; *year = ((days - day_in_cycle) / quad_cent) * 400L; *year += (day_in_cycle / century) * 100L; if( day_in_cycle == quad_cent - 1) /* extra leap day every 400 years */ { *month = 2; *day = 29; return; } day_in_cycle %= century; *year += (day_in_cycle / four_years) * 4L; day_in_cycle %= four_years; *year += day_in_cycle / one_year; if( day_in_cycle == four_years - 1) /* extra leap day every 4 years */ { *month = 2; *day = 29; return; } day_in_cycle %= one_year; *month = 5 * (day_in_cycle / 153L); day_in_cycle %= 153L; *month += 2 * (day_in_cycle / 61L); day_in_cycle %= 61L; if( day_in_cycle >= 31) { (*month)++; day_in_cycle -= 31; } *month += 3; *day = day_in_cycle + 1; if( *month > 12) { *month -= 12; (*year)++; } } /* Example code to add BSTAR data using Ted Molczan's method. It just reads in TLEs, computes BSTAR if possible, then writes out the resulting modified TLE. Add the '-v' (verbose) switch, and it also writes out the orbital period and perigee/apogee distances. Eventually, I'll probably set it up to dump other data that are not immediately obvious just by looking at the TLEs... */ int main( const int argc, const char **argv) { FILE *ifile; const char *filename; char line0[100], line1[100], line2[100]; int i, verbose = 0; const char *norad = NULL, *intl = NULL; int legend_shown = 0; for( i = 2; i < argc; i++) if( argv[i][0] == '-') switch( argv[i][1]) { case 'v': verbose = 1; break; case 'n': norad = argv[i] + 2; if( !*norad && i < argc - 1) norad = argv[++i]; printf( "Looking for NORAD %s\n", norad); break; case 'i': intl = argv[i] + 2; if( !*intl && i < argc - 1) intl = argv[++i]; printf( "Looking for international ID %s\n", intl); break; default: printf( "'%s': unrecognized option\n", argv[i]); return( -1); break; } filename = (argc == 1 ? "all_tle.txt" : argv[1]); ifile = fopen( filename, "rb"); if( !ifile) { fprintf( stderr, "Couldn't open '%s': ", filename); perror( ""); return( -1); } *line0 = *line1 = '\0'; while( fgets( line2, sizeof( line2), ifile)) { if( *line1 == '1' && (!norad || !memcmp( line1 + 2, norad, 5)) && (!intl || !memcmp( line1 + 9, intl, strlen( intl))) && *line2 == '2') { tle_t tle; const int err_code = parse_elements( line1, line2, &tle); if( err_code >= 0) { char obuff[200]; double params[N_SGP4_PARAMS], c2; if( verbose && !legend_shown) { legend_shown = 1; printf( "1 NoradU COSPAR Epoch.epoch dn/dt/2 d2n/dt2/6 BSTAR T El# C\n" "2 NoradU Inclina RAAscNode Eccent ArgPeri MeanAno MeanMotion Rev# C\n"); } SGP4_init( params, &tle); c2 = params[0]; if( c2 && tle.xno) tle.bstar = tle.xndt2o / (tle.xno * c2 * 1.5); write_elements_in_tle_format( obuff, &tle); if( strlen( line0) < 60) printf( "%s", line0); printf( "%s", obuff); if( verbose) { const double a1 = pow(xke / tle.xno, two_thirds); /* in Earth radii */ long year, ijd; int month, day; double frac; printf( "Inclination: %8.4f ", tle.xincl * 180. / PI); printf( " Perigee: %.4f km\n", (a1 * (1. - tle.eo) - 1.) * earth_radius_in_km); printf( "Asc node: %8.4f ", tle.xnodeo * 180. / PI); printf( " Apogee: %.4f km\n", (a1 * (1. + tle.eo) - 1.) * earth_radius_in_km); printf( "Arg perigee: %8.4f ", tle.omegao * 180. / PI); printf( " Orbital period: %.4f min\n", 2. * pi / tle.xno); printf( "Mean anom: %8.4f ", tle.xmo * 180. / PI); ijd = (long)( tle.epoch + 0.5); frac = tle.epoch + 0.5 - ijd; greg_day_to_dmy( ijd, &day, &month, &year); printf( " Epoch: JD %.5f = %ld-%02d-%02d.%05d\n", tle.epoch, year, month, day, (int)( frac * 100000.)); printf( "NORAD number %7d ", tle.norad_number); printf( "Semimajor axis : %4f km\n", a1 * earth_radius_in_km); } if( err_code) printf( "Checksum error %d\n", err_code); } } strcpy( line0, line1); strcpy( line1, line2); } fclose( ifile); return( 0); } ================================================ FILE: test_sat.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ /* * main.c April 10 2001 * * A skeleton main() function to demonstrate the use of * the various NORAD ephimerides in the norad.c file. * The TLE sets used are those provided in NORAD's spacetrack * report #3 so that a comparison with their own results * can be made. The results produced by these software agree * with NORAD's to the 5th or 6th figure, whatever this means! * (But please note that NORAD uses mostly 'float' types with * only a few 'doubles' for the critical variables). */ #include #include #include "norad.h" #define INTENTIONALLY_UNUSED_PARAMETER( param) (void)(param) static double test_data[5 * 6 * 5] = { /* SGP: */ 2328.96594238, -5995.21600342, 1719.97894287, 2.91110113, -0.98164053, -7.09049922, 2456.00610352, -6071.94232177, 1222.95977784, 2.67852119, -0.44705850, -7.22800565, 2567.39477539, -6112.49725342, 713.97710419, 2.43952477, 0.09884824, -7.31899641, 2663.03179932, -6115.37414551, 195.73919105, 2.19531813, 0.65333930, -7.36169147, 2742.85470581, -6079.13580322, -328.86091614, 1.94707947, 1.21346101, -7.35499924, /* SGP4: */ 2328.97048951, -5995.22076416, 1719.97067261, 2.91207230, -0.98341546, -7.09081703, 2456.10705566, -6071.93853760, 1222.89727783, 2.67938992, -0.44829041, -7.22879231, 2567.56195068, -6112.50384522, 713.96397400, 2.44024599, 0.09810869, -7.31995916, 2663.09078980, -6115.48229980, 196.39640427, 2.19611958, 0.65241995, -7.36282432, 2742.55133057, -6079.67144775, -326.38095856, 1.94850229, 1.21106251, -7.35619372, /* SGP8: */ 2328.87265015, -5995.21289063, 1720.04884338, 2.91210661, -0.98353850, -7.09081554, 2456.04577637, -6071.90490722, 1222.84086609, 2.67936245, -0.44820847, -7.22888553, 2567.68383789, -6112.40881348, 713.29282379, 2.43992555, 0.09893919, -7.32018769, 2663.49508667, -6115.18182373, 194.62816810, 2.19525236, 0.65453661, -7.36308974, 2743.29238892, -6078.90783691, -329.73434067, 1.94680957, 1.21500109, -7.35625595, /* SDP4: */ 7473.37066650, 428.95261765, 5828.74786377, 5.10715413, 6.44468284, -0.18613096, -3305.22537232, 32410.86328125, -24697.1767581, -1.30113538, -1.15131518, -0.28333528, 14271.28759766, 24110.46411133, -4725.76837158, -0.32050445, 2.67984074, -2.08405289, -9990.05883789, 22717.35522461, -23616.89062501, -1.01667246, -2.29026759, 0.72892364, 9787.86975097, 33753.34667969, -15030.81176758, -1.09425066, 0.92358845, -1.52230928, /* SDP8: (gotta fix) */ 7469.47631836, 415.99390792, 5829.64318848, 5.11402285, 6.44403201, -0.18296110, -3337.38992310, 32351.39086914, -24658.63037109, -1.30200730, -1.15603013, -0.28164955, 14226.54333496, 24236.08740234, -4856.19744873, -0.33951668, 2.65315416, -2.08114153, -10151.59838867, 22223.69848633, -23392.39770508, -1.00112480, -2.33532837, 0.76987664, 9420.08203125, 33847.21875000, -15391.06469727, -1.11986055, 0.85410149, -1.49506933 }; /* Main program */ int main( const int argc, const char **unused_argv) { double vel[3], pos[3]; /* Satellite position and velocity vectors */ double *test_ptr = test_data; tle_t tle; /* Pointer to two-line elements set for satellite */ /* Data for the prediction type and time period */ double ts = 0.; /* Time since TLE epoch to start predictions */ double tf = 1440.; /* Time over which predictions are required */ double delt = 360.; /* Time interval between predictions */ double tsince; /* Time since epoch (in minutes) */ int i; /* Index for loops etc */ const char *tle_data[16] = { "1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 87", "2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 1058", "1 11801U 80230.29629788 .01431103 00000-0 14311-1 2", "2 11801U 46.7916 230.4354 7318036 47.4722 10.4117 2.28537848 2", /* GOES 9 */ "1 23581U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214", "2 23581 1.1236 93.7945 0005741 214.4722 151.5103 1.00270260 23672", /* Cosmos 1191 */ "1 11871U 80057A 01309.36911127 -.00000499 +00000-0 +10000-3 0 08380", "2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886", /* ESA-GEOS 1 */ "1 09931U 77029A 01309.17453186 -.00000329 +00000-0 +10000-3 0 05967", "2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451", /* Cosmos 1217 */ "1 12032U 80085A 01309.42683181 .00000182 00000-0 10000-3 0 3499", "2 12032 65.2329 86.7607 7086222 172.0967 212.4632 2.00879501101699", /* Molniya 3-19Rk */ "1 13446U 82083E 01283.10818257 .00098407 45745-7 54864-3 0 6240", "2 13446 62.1717 83.8458 7498877 273.9677 320.2568 2.06357523137203", /* Ariane Deb */ "1 23246U 91015G 01311.70347086 .00004957 00000-0 43218-2 0 8190", "2 23246 7.1648 263.6949 5661268 241.8299 50.5793 4.44333001129208" }; INTENTIONALLY_UNUSED_PARAMETER( unused_argv); for( i = 1; i <= 17; i++) /* Loop for each type of ephemeris */ { int tle_idx = ((i - 2) / 2) * 2, err_code; int ephem, is_deep; const char *ephem_names[6] = { NULL, "SGP ", "SGP4", "SGP8", "SDP4", "SDP8" }; double sat_params[N_SAT_PARAMS]; /* Select the sgp or sdp TLE set for use below */ if( tle_idx < 0) tle_idx = 0; printf( "\n%s\n%s", tle_data[tle_idx], tle_data[tle_idx + 1]); err_code = parse_elements( tle_data[tle_idx], tle_data[tle_idx + 1], &tle); if( err_code) printf( "\nError parsing elements: %d\n", err_code); if( i <= 5) ephem = i; else ephem = 4 + ((i - 5) % 2); /* Select ephemeris type */ /* Will select a "deep" (SDPx) or "general" (SGPx) ephemeris */ /* depending on the TLE parameters of the satellite: */ is_deep = select_ephemeris( &tle); /* printf( "BStar: %.8lf\n", tle.bstar); */ if( is_deep) printf("\nDeep-Space type Ephemeris (SDP*) selected:"); else printf("\nNear-Earth type Ephemeris (SGP*) selected:"); /* Print some titles for the results */ printf("\nEphem:%s Tsince " "X/Xdot Y/Ydot Z/Zdot\n", ephem_names[ephem]); /* Calling of NORAD routines */ /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8) */ /* will be called in turn with the appropriate TLE set */ switch( ephem) { case 1: SGP_init( sat_params, &tle); break; case 2: SGP4_init( sat_params, &tle); break; case 3: SGP8_init( sat_params, &tle); break; case 4: SDP4_init( sat_params, &tle); break; case 5: SDP8_init( sat_params, &tle); break; } for( tsince = ts; tsince <= tf; tsince += delt) { switch( ephem) { case 1: SGP(tsince, &tle, sat_params, pos, vel); break; case 2: SGP4(tsince, &tle, sat_params, pos, vel); break; case 3: SGP8(tsince, &tle, sat_params, pos, vel); break; case 4: SDP4(tsince, &tle, sat_params, pos, vel); break; case 5: SDP8(tsince, &tle, sat_params, pos, vel); break; } /* Calculate and print results */ vel[0] /= 60.; /* cvt km/minute to km/second */ vel[1] /= 60.; vel[2] /= 60.; if( argc > 1 && i <= 5) /* wanna show _difference from test data_ */ { pos[0] -= *test_ptr++; pos[1] -= *test_ptr++; pos[2] -= *test_ptr++; vel[0] -= *test_ptr++; vel[1] -= *test_ptr++; vel[2] -= *test_ptr++; } printf(" %12.4f %16.8f %16.8f %16.8f \n", tsince,pos[0],pos[1],pos[2]); printf(" %16.8f %16.8f %16.8f \n", vel[0],vel[1],vel[2]); } /* End of for(tsince = ts; tsince <= tf; tsince += delt) */ } /* End of for (i=1; i<=17; i++) */ return(0); } /* End of main() */ /*------------------------------------------------------------------*/ ================================================ FILE: tle2mpc.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include #include #include #include "watdefs.h" #include "norad.h" #include "afuncs.h" /* Code to read TLEs of the sort I post on github.com/Bill-Gray/tles, where there's one TLE per day, and output ephemerides of the sort the Minor Planet Center wants for their Distant Artsat Observation Page (DASO), at https://www.minorplanetcenter.net/iau/artsats/artsats.html. For DASO, we need geocentric ephems with positions only, in equatorial J2000, in AU, at 0.1-day intervals. So each time we read a TLE, ten positions are output. */ static void error_exit( void) { fprintf( stderr, "'tle2mpc' needs the name of an input TLE file and an output\n"); fprintf( stderr, "MPC ephemeris file. See 'tle2mpc.cpp' for details.\n"); exit( -1); } static const char *err_fgets( char *buff, size_t buffsize, FILE *ifile) { const char *rval = fgets( buff, (int)buffsize, ifile); assert( rval); return( rval); } int main( const int argc, const char **argv) { char buff[200]; FILE *ifile, *ofile; bool object_name_shown = false; if( argc < 3) error_exit( ); ifile = fopen( argv[1], "rb"); if( !ifile) { fprintf( stderr, "Couldn't open input file '%s'\n", argv[1]); error_exit( ); } ofile = fopen( argv[2], "wb"); if( !ofile) { fprintf( stderr, "Couldn't open output file '%s'\n", argv[2]); error_exit( ); } while( fgets( buff, sizeof( buff), ifile)) if( !memcmp( buff, "# Ephem range:", 14)) { const double mjd1 = atof( buff + 15); const double mjd2 = atof( buff + 28); fprintf( ofile, "%f 0.1 %d 0,1,1 ", 2400000.5 + mjd1, (int)( (mjd2 - mjd1) * 10. + .5)); } else if( !memcmp( buff, "# MJD ", 6)) { double mjd = atof( buff + 6); char line2[80]; tle_t tle; /* Structure for two-line elements set for satellite */ bool is_a_tle; err_fgets( buff, sizeof( buff), ifile); if( !object_name_shown) fprintf( ofile, "%s", buff); object_name_shown = true; err_fgets( buff, sizeof( buff), ifile); err_fgets( line2, sizeof( line2), ifile); is_a_tle = (parse_elements( buff, line2, &tle) >= 0); assert( is_a_tle); if( is_a_tle) { int i, j; double sat_params[N_SAT_PARAMS], precess_matrix[9]; const bool is_deep = select_ephemeris( &tle); const double year = 2000. + (mjd - 51545.) / 365.25; if( is_deep) SDP4_init( sat_params, &tle); else SGP4_init( sat_params, &tle); setup_precession( precess_matrix, year, 2000.); for( i = 0; i < 10; i++, mjd += 0.1) { double posn[3], j2000_posn[3]; const double t_since = (mjd - (tle.epoch - 2400000.5)) * minutes_per_day; if( is_deep) SDP4( t_since, &tle, sat_params, posn, NULL); else SGP4( t_since, &tle, sat_params, posn, NULL); for( j = 0; j < 3; j++) posn[j] /= AU_IN_KM; precess_vector( precess_matrix, posn, j2000_posn); fprintf( ofile, "%.5f%16.10f%16.10f%16.10f\n", mjd + 2400000.5, j2000_posn[0], j2000_posn[1], j2000_posn[2]); } } } /* Rewind to start of input & re-read, looking for comments */ fseek( ifile, 0L, SEEK_SET); while( fgets( buff, sizeof( buff), ifile)) if( !memcmp( buff, "# Ephem range: ", 14)) while( fgets( buff, sizeof( buff), ifile) && memcmp( buff, "# 1 NoradU", 10)) fprintf( ofile, "%s", buff); fclose( ifile); fclose( ofile); return( 0); } ================================================ FILE: tle_date.c ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. */ #include #include #include #include #include #include "watdefs.h" #include "afuncs.h" #include "date.h" /* Code to extract TLEs for a particular date (MJD). It looks through all TLEs listed in 'tle_list.txt' and outputs those matching the MJD given on the command line. */ char path[100]; int verbose; static void extract_tle_for_date( const char *fname, const double mjd) { char prev_line[100], buff[100]; FILE *ifile = fopen( fname, "rb"); double step = 0.; assert( strlen( fname) < 20); assert( strlen( path) < 60); strcpy( buff, path); strcat( buff, fname); ifile = fopen( buff, "rb"); if( !ifile) { printf( "'%s' not opened\n", fname); exit( -1); } if( verbose) printf( "Looking at '%s' for %f\n", fname, mjd); *prev_line = '\0'; while( fgets( buff, sizeof( buff), ifile)) { if( !memcmp( buff, "# MJD ", 6) && atof( buff + 6) <= mjd && atof( buff + 6) + step > mjd) { size_t i; printf( "%s", prev_line); /* show 'worst residual' data */ for( i = 0; i < 3 && fgets( buff, sizeof( buff), ifile); i++) printf( "%s", buff); /* obj name, lines 1 and 2 of TLE */ fclose( ifile); return; } else if( !memcmp( buff, "# Ephem range: ", 15)) { double mjd1, mjd2; if( sscanf( buff + 15, "%lf %lf %lf", &mjd1, &mjd2, &step) != 3) { printf( "Ephem step problem in '%s'\n'%s'\n", fname, buff); exit( -2); } if( mjd < mjd1 || mjd > mjd2) { if( verbose) printf( "'%s': outside range\n", fname); fclose( ifile); return; } } else if( !memcmp( buff, "# Include ", 10)) { int i = 0; char *filename = buff + 10; while( buff[i] >= ' ') i++; buff[i] = '\0'; if( memcmp( filename, "classfd", 7) && memcmp( filename, "inttles", 7) && memcmp( filename, "all_tle", 7) && memcmp( filename, "old_tle", 7)) extract_tle_for_date( filename, mjd); } strcpy( prev_line, buff); } fclose( ifile); } static void err_exit( void) { printf( "tle_date (MJD)\n"); exit( -1); } #ifdef ON_LINE_VERSION int dummy_main( const int argc, const char **argv) #else int main( const int argc, const char **argv) #endif { const double jan_1_1970 = 2440587.5; const double curr_t = jan_1_1970 + (double)time( NULL) / seconds_per_day; double mjd; if( argc < 2) err_exit( ); mjd = atof( argv[1]); mjd = get_time_from_string( curr_t, argv[1], FULL_CTIME_YMD, NULL) - 2400000.5; if( mjd < 40000 || mjd > 65000) err_exit( ); if( argc > 2) strcpy( path, argv[2]); if( argc > 3) verbose = 1; extract_tle_for_date( "tle_list.txt", mjd); return( 0); } #ifdef ON_LINE_VERSION #include int main( void) { const char *argv[20]; const size_t max_buff_size = 40000; char *buff = (char *)malloc( max_buff_size); char field[30], date_text[80]; FILE *lock_file = fopen( "lock.txt", "w"); extern char **environ; int cgi_status; avoid_runaway_process( 15); printf( "Content-type: text/html\n\n"); printf( "
\n");
   if( !lock_file)
      {
      printf( "

Server is busy. Try again in a minute or two.

"); printf( "

Your TLEs are very important to us!

"); return( 0); } fprintf( lock_file, "We're in\n"); for( size_t i = 0; environ[i]; i++) fprintf( lock_file, "%s\n", environ[i]); cgi_status = initialize_cgi_reading( ); strcpy( date_text, "now"); fprintf( lock_file, "CGI status %d\n", cgi_status); if( cgi_status <= 0) { printf( "

CGI data reading failed : error %d ", cgi_status); printf( "This isn't supposed to happen.

\n"); return( 0); } while( !get_cgi_data( field, buff, NULL, max_buff_size)) { if( !strcmp( field, "date") && strlen( buff) < 70) { strncpy( date_text, buff, sizeof( date_text)); date_text[sizeof( date_text) - 1] = '\0'; } } free( buff); argv[0] = "tle_date"; argv[1] = date_text; argv[2] = "/home/projectp/public_html/tles/"; argv[3] = NULL; dummy_main( 3, argv); printf( "
"); fclose( lock_file); return( 0); } #endif ================================================ FILE: tle_out.cpp ================================================ /* Copyright (C) 2018, Project Pluto. See LICENSE. tle_out.cpp: code to convert the in-memory artificial satellite elements into the "standard" TLE (Two-Line Element) form described at https://en.wikipedia.org/wiki/Two-line_elements */ #include #include #include #include #include #include "norad.h" /* Useful constants to define, in case the value of PI or the number of minutes in a day should change: */ #define PI 3.141592653589793238462643383279502884197169399375105 #define MINUTES_PER_DAY 1440. #define MINUTES_PER_DAY_SQUARED (MINUTES_PER_DAY * MINUTES_PER_DAY) #define MINUTES_PER_DAY_CUBED (MINUTES_PER_DAY_SQUARED * MINUTES_PER_DAY) #define AE 1.0 #define J1900 (2451545.5 - 36525. - 1.) static int add_tle_checksum_data( char *buff) { int count = 69, rval = 0; if( (*buff != '1' && *buff != '2') || buff[1] != ' ') return( 0); /* not a .TLE */ while( --count) { if( *buff < ' ' || *buff > 'z') return( 0); /* wups! those shouldn't happen in .TLEs */ if( *buff > '0' && *buff <= '9') rval += *buff - '0'; else if( *buff == '-') rval++; buff++; } if( *buff != 10 && buff[1] != 10 && buff[2] != 10) return( 0); /* _still_ not a .TLE */ *buff++ = (char)( '0' + (rval % 10)); *buff++ = 13; *buff++ = 10; *buff++ = '\0'; return( 1); } static double zero_to_two_pi( double angle_in_radians) { angle_in_radians = fmod( angle_in_radians, PI + PI); if( angle_in_radians < 0.) angle_in_radians += PI + PI; return( angle_in_radians); } /* See comments for get_high_value() in 'get_el.cpp'. Essentially, we are writing out a state vector in a convoluted base-36 form. */ static void set_high_value( char *obuff, const double value) { int64_t oval = (int64_t)fabs( value); int i; *obuff++ = (value >= 0. ? '+' : '-'); for( i = 7; i >= 0; i--, oval /= (int64_t)36) { obuff[i] = (char)( oval % (int64_t)36); if( obuff[i] < 10) obuff[i] += '0'; else obuff[i] += 'A' - 10; } obuff[8] = ' '; } /* The second derivative of the mean motion, 'xnddo6', and the 'bstar' drag term, are stored in a simplified scientific notation. To save valuable bytes, the decimal point and 'E' are assumed. */ static void put_sci( char *obuff, double ival) { if( !ival) memcpy( obuff, " 00000-0", 7); else { int oval, exponent = 0; if( ival > 0.) *obuff++ = ' '; else { *obuff++ = '-'; ival = -ival; } while( 1) { if( ival > 1.) /* avoid integer overflow */ oval = 100000; else oval = (int)( ival * 100000. + .5); if( oval > 99999) { ival /= 10; exponent++; } else if( oval < 10000) { ival *= 10; exponent--; } else break; } snprintf( obuff, 7, "%5d", oval); assert( 5 == strlen( obuff)); if( exponent > 0) { obuff[5] = '+'; obuff[6] = (char)( '0' + exponent); } else { obuff[5] = '-'; obuff[6] = (char)( '0' - exponent); } } } /* See comments for get_norad_number( ) in get_el.cpp. This performs the reverse function of setting the five bytes corresponding to a NORAD number, using the 'standard' Alpha-5 method for numbers 0 to 339000 and the nonstandard Super-5 method beyond that. */ static char int_to_base64( const int digit) { int rval; assert( digit >= 0 && digit < 64); if( digit < 0 || digit >= 64) rval = ' '; else if( digit < 10) rval = '0' + digit; else if( digit < 36) rval = 'A' + digit - 10; else if( digit < 62) rval = 'a' + digit - 36; else rval = (digit == 62 ? '+' : '-'); return( rval); } static void store_norad_number_in_alpha5( char *obuff, const int norad_number) { const int N_TYPE_STANDARD = 340000; /* five digits plus Alpha-5 */ const int N_TYPE_2 = 64 * 64 * 64 * 64 * 54; /* xxxxL */ /* const int N_TYPE_3 = 64 * 64 * 64 * 54 * 10; xxxLd; we don't actually use this */ const int one_billion = 1000000000; int i, tval = norad_number; assert( norad_number >= 0 && norad_number < one_billion); if( norad_number < 0 || norad_number >= one_billion) strcpy( obuff, " "); /* outside representable range */ else if( norad_number < N_TYPE_STANDARD) { for( i = 4; i > 0; i--, tval /= 10) obuff[i] = '0' + (tval % 10); *obuff = int_to_base64( tval); if( *obuff >= 'I') (*obuff)++; if( *obuff >= 'O') (*obuff)++; } else if( norad_number < N_TYPE_STANDARD + N_TYPE_2) { tval -= N_TYPE_STANDARD; obuff[4] = int_to_base64( tval % 54 + 10); tval /= 54; for( i = 3; i >= 0; i--, tval >>= 6) obuff[i] = int_to_base64( tval & 0x3f); } else { tval -= N_TYPE_STANDARD + N_TYPE_2; obuff[4] = int_to_base64( tval % 10); obuff[3] = int_to_base64( (tval / 10) % 54 + 10); tval /= 540; for( i = 2; i >= 0; i--, tval >>= 6) obuff[i] = int_to_base64( tval & 0x3f); } obuff[5] = '\0'; } /* SpaceTrack TLEs have, on the second line, leading zeroes in front of the inclination, ascending node, argument of perigee, and mean motion. Which is why I've used this format string : snprintf( line2 + 8, 57, "%08.4f %08.4f %07ld %08.4f %08.4f %011.8f", ...) 'classfd.tle' and some other sources don't use leading zeroes. For them, one should use the following format string for those four quantities : snprintf( line2 + 8, 57, "%8.4f %8.4f %07ld %8.4f %8.4f %11.8f", ...) */ void DLL_FUNC write_elements_in_tle_format( char *buff, const tle_t *tle) { long year = (long)( tle->epoch - J1900) / 365 + 1; double day_of_year; char *line2, norad_num_text[6]; do { double start_of_year; year--; start_of_year = J1900 + (double)year * 365. + (double)((year - 1) / 4); day_of_year = tle->epoch - start_of_year; } while( day_of_year < 1.); if( year < 0 || year > 200) /* bogus input */ { year = 56; day_of_year = 0.; } store_norad_number_in_alpha5( norad_num_text, tle->norad_number); snprintf( buff, 72, /* xndt2o xndd6o bstar eph bull */ "1 %5s%c %-8s %02ld%12.8f -.000hit00 +00000-0 +00000-0 %c %4dZ\n", norad_num_text, tle->classification, tle->intl_desig, year % 100L, day_of_year, tle->ephemeris_type, tle->bulletin_number); if( buff[20] == ' ') /* fill in leading zeroes for day of year */ buff[20] = '0'; if( buff[21] == ' ') buff[21] = '0'; if( tle->ephemeris_type != 'H') /* "normal", standard TLEs */ { double deriv_mean_motion = tle->xndt2o * MINUTES_PER_DAY_SQUARED / (2. * PI); unsigned long lderiv; if( deriv_mean_motion >= 0) buff[33] = ' '; lderiv = (unsigned long)( fabs( deriv_mean_motion * 100000000.) + .5); assert( lderiv < 100000000); snprintf( buff + 35, 10, "%08lu", lderiv); assert( 8 == strlen( buff + 35)); buff[43] = ' '; put_sci( buff + 44, tle->xndd6o * MINUTES_PER_DAY_CUBED / (2. * PI)); put_sci( buff + 53, tle->bstar / AE); } else { size_t i; const double *posn = &tle->xincl; for( i = 0; i < 3; i++) set_high_value( buff + 33 + i * 10, posn[i]); buff[62] = 'H'; } add_tle_checksum_data( buff); assert( 71 == strlen( buff)); line2 = buff + 71; snprintf( line2, 10, "2 %5s ", norad_num_text); assert( 8 == strlen( line2)); if( tle->ephemeris_type != 'H') /* "normal", standard TLEs */ { const double revs_per_day = tle->xno * MINUTES_PER_DAY / (2. * PI); assert( revs_per_day > 0. && revs_per_day < 99.); snprintf( line2 + 8, 57, "%08.4f %08.4f %07ld %08.4f %08.4f %011.8f", zero_to_two_pi( tle->xincl) * 180. / PI, zero_to_two_pi( tle->xnodeo) * 180. / PI, (long)( tle->eo * 10000000. + .5), zero_to_two_pi( tle->omegao) * 180. / PI, zero_to_two_pi( tle->xmo) * 180. / PI, revs_per_day); assert( 55 == strlen( line2 + 8)); } else { size_t i; const double *vel = &tle->xincl + 3; memset( line2 + 8, ' ', 25); /* reserved for future use */ for( i = 0; i < 3; i++) set_high_value( line2 + 33 + i * 10, vel[i] * 1e+4); } assert( tle->revolution_number >= 0 && tle->revolution_number < 100000); snprintf( line2 + 63, 8, "%5dZ\n", tle->revolution_number); add_tle_checksum_data( line2); } ================================================ FILE: watcom.mak ================================================ # Makefile for OpenWATCOM all: test2.exe test_sat.exe obs_test.exe obs_tes2.exe sat_id.exe test_out.exe out_comp.exe out_comp.exe: out_comp.cpp wcl386 -zq -W4 -Ox out_comp.cpp test2.exe: test2.obj wsatlib.lib wcl386 -zq -k10000 test2.obj wsatlib.lib test_sat.exe: test_sat.obj wsatlib.lib wcl386 -zq -k10000 test_sat.obj wsatlib.lib obs_test.exe: obs_test.obj wsatlib.lib wcl386 -zq -k10000 obs_test.obj wsatlib.lib obs_tes2.exe: obs_tes2.obj wsatlib.lib wcl386 -zq -k10000 obs_tes2.obj wsatlib.lib WAT_LIB=../watlib sat_id.exe: sat_id.obj sat_util.obj wsatlib.lib $(WAT_LIB)/wafuncs.lib wcl386 -zq -k10000 sat_id.obj sat_util.obj wsatlib.lib $(WAT_LIB)/wafuncs.lib test_out.exe: test_out.obj wsatlib.lib wcl386 -zq -k10000 test_out.obj wsatlib.lib #CFLAGS=-W4 -Ox -j -zq -DRETAIN_PERTURBATION_VALUES_AT_EPOCH CFLAGS=-W4 -Ox -j -zq -i=..\include wsatlib.lib: sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj & basics.obj get_el.obj observe.obj common.obj tle_out.obj wlib -q wsatlib.lib +sgp.obj +sgp4.obj +sgp8.obj +sdp4.obj +sdp8.obj wlib -q wsatlib.lib +deep.obj +basics.obj +get_el.obj +observe.obj wlib -q wsatlib.lib +common.obj +tle_out.obj .cpp.obj: wcc386 $(CFLAGS) $< .c.obj: wcc386 $(CFLAGS) $< basics.obj: deep.obj: get_el.obj: observe.obj: common.obj: obs_test.obj: obs_tes2.obj: sat_id.obj: sat_util.obj: tle_out.obj: test_sat.obj: test2.obj: sgp.obj: sgp4.obj: sgp8.obj: sdp4.obj: sdp8.obj: test_out.obj: clean: -del *.obj -del *.exe -del *.o -del *.map -del *.lib -del *.exp -del *.dll -del *.a ================================================ FILE: zlibstub.h ================================================ #define gzFile FILE * #define gzopen fopen #define gzgets( ifile, buff, buffsize) fgets( buff, buffsize, ifile) #define gzclose fclose