├── .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 /.github/workflows/github_actions_build.yml: -------------------------------------------------------------------------------- 1 | # Shameless copy of @AlastairUK's fine work for the jpl_eph repo. 2 | # Aside from the comment, this is almost a byte-for-byte copy. 3 | name: Build 4 | 5 | on: [push, pull_request] 6 | 7 | jobs: 8 | buildUbuntu: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: make 13 | run: | 14 | git clone https://github.com/Bill-Gray/lunar 15 | cd lunar 16 | make ERRORS=Y liblunar.a 17 | make install 18 | cd .. 19 | make ERRORS=Y 20 | 21 | buildOSX: 22 | runs-on: macOS-latest 23 | steps: 24 | - uses: actions/checkout@master 25 | - name: make 26 | run: | 27 | git clone https://github.com/Bill-Gray/lunar 28 | cd lunar 29 | make CC=clang ERRORS=Y liblunar.a 30 | make install 31 | cd .. 32 | make CC=clang ERRORS=Y 33 | 34 | buildWindows: 35 | runs-on: windows-latest 36 | steps: 37 | - uses: actions/checkout@master 38 | - name: make 39 | run: | 40 | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 41 | git clone https://github.com/Bill-Gray/lunar 42 | mkdir myincl 43 | cd lunar 44 | nmake -f lunar.mak lunar64.lib 45 | nmake -f lunar.mak install 46 | cd .. 47 | set CL=/I"./myincl" 48 | copy lunar\*.lib . 49 | nmake -f msvc.mak 50 | shell: cmd 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | *.cgi 30 | get_high 31 | mergetle 32 | obs_test 33 | obs_tes2 34 | out_comp 35 | sat_id 36 | sat_id2 37 | test2 38 | test_out 39 | test_sat 40 | tle_date 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020, Project Pluto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sat_code 2 | 3 | C/C++ code for the SGP4/SDP4 satellite motion model, and for manipulating TLEs 4 | (Two-Line Elements). Full details at 5 | [http://www.projectpluto.com/sat_code.htm](http://www.projectpluto.com/sat_code.htm). 6 | 7 | The only dependency is on the [`lunar`](https://github.com/Bill-Gray/lunar) (basic 8 | astronomical ephemeris/time functions) library. Make and `make install` that 9 | library before attempting to build this code. 10 | 11 | On Linux, run `make` to build the library and various test executables. 12 | (You can also do this with MinGW under Windows.) In Linux, you 13 | can then run `make install` to put libraries in `/usr/local/lib` and some 14 | include files in `/usr/local/include`. (You will probably have to make that 15 | `sudo make install`.) For BSD, and probably OS/X, run `gmake CLANG=Y` 16 | (GNU make, with the clang compiler), then `sudo gmake install`. 17 | 18 | On Windows, run `nmake -f msvc.mak` with MSVC++. Optionally, add 19 | `-BITS_32=Y` for 32-bit code. 20 | -------------------------------------------------------------------------------- /basics.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #include 4 | #include "norad.h" 5 | #include "norad_in.h" 6 | 7 | /*------------------------------------------------------------------*/ 8 | 9 | /* FMOD2P */ 10 | double FMod2p( const double x) 11 | { 12 | double rval = fmod( x, twopi); 13 | 14 | if( rval < 0.) 15 | rval += twopi; 16 | return( rval); 17 | } /* fmod2p */ 18 | 19 | #define EPHEM_TYPE_DEFAULT '0' 20 | #define EPHEM_TYPE_SGP '1' 21 | #define EPHEM_TYPE_SGP4 '2' 22 | #define EPHEM_TYPE_SDP4 '3' 23 | #define EPHEM_TYPE_SGP8 '4' 24 | #define EPHEM_TYPE_SDP8 '5' 25 | #define EPHEM_TYPE_HIGH 'H' 26 | 27 | /*------------------------------------------------------------------*/ 28 | 29 | void sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg); 30 | /* common.c */ 31 | 32 | /* Selects the type of ephemeris to be used (SGP*-SDP*) */ 33 | int DLL_FUNC select_ephemeris( const tle_t *tle) 34 | { 35 | int rval; 36 | 37 | if( tle->ephemeris_type == EPHEM_TYPE_HIGH) 38 | rval = 1; /* force high-orbit state vector model */ 39 | else if( tle->xno <= 0. || tle->eo > 1. || tle->eo < 0.) 40 | rval = -1; /* error in input data */ 41 | else if( tle->ephemeris_type == EPHEM_TYPE_SGP4 42 | || tle->ephemeris_type == EPHEM_TYPE_SGP8) 43 | rval = 0; /* specifically marked non-deep */ 44 | else if( tle->ephemeris_type == EPHEM_TYPE_SDP4 45 | || tle->ephemeris_type == EPHEM_TYPE_SDP8) 46 | rval = 1; /* specifically marked deep */ 47 | else 48 | { 49 | deep_arg_t deep_arg; 50 | 51 | sxpall_common_init( tle, &deep_arg); 52 | /* Select a deep-space/near-earth ephemeris */ 53 | /* If the orbital period is greater than 225 minutes... */ 54 | if (twopi / deep_arg.xnodp >= 225.) 55 | rval = 1; /* yes, it should be a deep-space (SDPx) ephemeris */ 56 | else 57 | rval = 0; /* no, you can go with an SGPx ephemeris */ 58 | } 59 | return( rval); 60 | } /* End of select_ephemeris() */ 61 | 62 | /*------------------------------------------------------------------*/ 63 | 64 | long DLL_FUNC sxpx_library_version( void) 65 | { 66 | return( 0x100); 67 | } 68 | -------------------------------------------------------------------------------- /common.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #include 4 | #include 5 | #include "norad.h" 6 | #include "norad_in.h" 7 | 8 | /* params[1] and [6]-[9] were used in earlier implementations, but are 9 | now unused */ 10 | 11 | #define c2 params[0] 12 | #define c1 params[2] 13 | #define c4 params[3] 14 | #define xnodcf params[4] 15 | #define t2cof params[5] 16 | 17 | void sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg) 18 | { 19 | const double a1 = pow(xke / tle->xno, two_thirds); /* in Earth radii */ 20 | double del1, ao, delo, tval; 21 | 22 | /* Recover original mean motion (xnodp) and */ 23 | /* semimajor axis (aodp) from input elements. */ 24 | deep_arg->cosio = cos( tle->xincl); 25 | deep_arg->cosio2 = deep_arg->cosio * deep_arg->cosio; 26 | deep_arg->eosq = tle->eo*tle->eo; 27 | deep_arg->betao2 = 1-deep_arg->eosq; 28 | deep_arg->betao = sqrt(deep_arg->betao2); 29 | tval = 1.5 * ck2 * (3. * deep_arg->cosio2 - 1.) / (deep_arg->betao * deep_arg->betao2); 30 | del1 = tval / (a1 * a1); 31 | ao = a1 * (1. - del1 * (1. / 3. + del1 * ( 1. + 134./81. * del1))); 32 | delo = tval / (ao * ao); 33 | deep_arg->xnodp = tle->xno / (1+delo); /* in radians/minute */ 34 | deep_arg->aodp = ao / (1-delo); 35 | } 36 | 37 | void sxpx_common_init( double *params, const tle_t *tle, 38 | init_t *init, deep_arg_t *deep_arg) 39 | { 40 | double 41 | eeta, etasq, perige, pinv, pinvsq, 42 | psisq, qoms24, temp1, temp2, temp3, 43 | cosio4, tsi_squared, x3thm1, xhdot1; 44 | 45 | sxpall_common_init( tle, deep_arg); 46 | x3thm1 = 3. * deep_arg->cosio2 - 1.; 47 | /* For perigee below 156 km, the values */ 48 | /* of s and qoms2t are altered. */ 49 | init->s4 = s_const; 50 | qoms24 = qoms2t; 51 | perige = (deep_arg->aodp * (1-tle->eo) - ae) * earth_radius_in_km; 52 | if( perige < 156.) 53 | { 54 | double temp_val, temp_val_squared; 55 | 56 | if(perige <= 98.) 57 | init->s4 = 20; 58 | else 59 | init->s4 = perige-78.; 60 | temp_val = (120. - init->s4) * ae / earth_radius_in_km; 61 | temp_val_squared = temp_val * temp_val; 62 | qoms24 = temp_val_squared * temp_val_squared; 63 | init->s4 = init->s4 / earth_radius_in_km + ae; 64 | } /* End of if(perige <= 156) */ 65 | 66 | pinv = 1. / (deep_arg->aodp * deep_arg->betao2); 67 | pinvsq = pinv * pinv; 68 | init->tsi = 1. / (deep_arg->aodp - init->s4); 69 | init->eta = deep_arg->aodp*tle->eo*init->tsi; 70 | etasq = init->eta*init->eta; 71 | eeta = tle->eo*init->eta; 72 | psisq = fabs(1-etasq); 73 | tsi_squared = init->tsi * init->tsi; 74 | init->coef = qoms24 * tsi_squared * tsi_squared; 75 | init->coef1 = init->coef / pow(psisq,3.5); 76 | c2 = init->coef1 * deep_arg->xnodp * (deep_arg->aodp*(1+1.5*etasq+eeta* 77 | (4+etasq))+0.75*ck2*init->tsi/psisq*x3thm1*(8+3*etasq*(8+etasq))); 78 | c1 = tle->bstar*c2; 79 | deep_arg->sinio = sin(tle->xincl); 80 | c4 = 2*deep_arg->xnodp*init->coef1*deep_arg->aodp*deep_arg->betao2* 81 | (init->eta*(2+0.5*etasq)+tle->eo*(0.5+2*etasq)-2*ck2*init->tsi/ 82 | (deep_arg->aodp*psisq)*(-3*x3thm1*(1-2*eeta+etasq* 83 | (1.5-0.5*eeta))+0.75*(1. - deep_arg->cosio2) *(2*etasq-eeta*(1+etasq))* 84 | cos(2*tle->omegao))); 85 | cosio4 = deep_arg->cosio2 * deep_arg->cosio2; 86 | temp1 = 3*ck2*pinvsq*deep_arg->xnodp; 87 | temp2 = temp1 * ck2 * pinvsq; 88 | temp3 = 1.25 * ck4 * pinvsq * pinvsq * deep_arg->xnodp; 89 | deep_arg->xmdot = deep_arg->xnodp 90 | + temp1 * deep_arg->betao* x3thm1 / 2. 91 | + temp2 * deep_arg->betao* 92 | (13-78*deep_arg->cosio2+137*cosio4) / 16.; 93 | deep_arg->omgdot = -temp1 * (1. - 5 * deep_arg->cosio2) / 2. 94 | + temp2 * (7-114*deep_arg->cosio2+395*cosio4) / 16. 95 | + temp3 * (3-36*deep_arg->cosio2+49*cosio4); 96 | xhdot1 = -temp1*deep_arg->cosio; 97 | deep_arg->xnodot = xhdot1+(temp2*(4-19*deep_arg->cosio2) / 2. 98 | + 2*temp3*(3-7*deep_arg->cosio2))*deep_arg->cosio; 99 | xnodcf = 3.5*deep_arg->betao2*xhdot1*c1; 100 | t2cof = 1.5*c1; 101 | } 102 | 103 | inline double centralize_angle( const double ival) 104 | { 105 | double rval = fmod( ival, twopi); 106 | 107 | if( rval > pi) 108 | rval -= twopi; 109 | else if( rval < - pi) 110 | rval += twopi; 111 | return( rval); 112 | } 113 | 114 | #define MAX_KEPLER_ITER 10 115 | 116 | int sxpx_posn_vel( const double xnode, const double a, const double ecc, 117 | const double cosio, const double sinio, 118 | const double xincl, const double omega, 119 | const double xl, double *pos, double *vel) 120 | { 121 | /* Long period periodics */ 122 | const double axn = ecc*cos(omega); 123 | double temp = 1/(a*(1.-ecc*ecc)); 124 | const double xlcof = .125 * a3ovk2 * sinio * (3+5*cosio)/ (1. + cosio); 125 | const double aycof = 0.25 * a3ovk2 * sinio; 126 | const double xll = temp*xlcof*axn; 127 | const double aynl = temp*aycof; 128 | const double xlt = xl+xll; 129 | const double ayn = ecc*sin(omega)+aynl; 130 | const double elsq = axn*axn+ayn*ayn; 131 | const double capu = centralize_angle( xlt - xnode); 132 | const double chicken_factor_on_eccentricity = 1.e-6; 133 | double epw = capu; 134 | double temp1, temp2; 135 | double ecosE, esinE, pl, r; 136 | double betal; 137 | double u, sinu, cosu, sin2u, cos2u; 138 | double rk, uk, xnodek, xinck; 139 | double sinuk, cosuk, sinik, cosik, sinnok, cosnok, xmx, xmy; 140 | double sinEPW, cosEPW; 141 | double ux, uy, uz; 142 | int i, rval = 0; 143 | 144 | /* Dundee changes: items dependent on cosio get recomputed: */ 145 | const double cosio_squared = cosio * cosio; 146 | const double x3thm1 = 3.0 * cosio_squared - 1.0; 147 | const double sinio2 = 1.0 - cosio_squared; 148 | const double x7thm1 = 7.0 * cosio_squared - 1.0; 149 | 150 | /* Added 29 Mar 2003, modified 26 Sep 2006: extremely */ 151 | /* decayed satellites can end up "orbiting" within the */ 152 | /* earth. Eventually, the semimajor axis becomes zero, */ 153 | /* then negative. In that case, or if the orbit is near */ 154 | /* to parabolic, we zero the posn/vel and quit. If the */ 155 | /* object has a perigee or apogee indicating a crash, we */ 156 | /* just flag it. Revised 28 Oct 2006. */ 157 | 158 | if( a < 0.) 159 | rval = SXPX_ERR_NEGATIVE_MAJOR_AXIS; 160 | if( elsq > 1. - chicken_factor_on_eccentricity) 161 | rval = SXPX_ERR_NEARLY_PARABOLIC; 162 | for( i = 0; i < 3; i++) 163 | { 164 | pos[i] = 0.; 165 | if( vel) 166 | vel[i] = 0.; 167 | } 168 | if( rval) 169 | return( rval); 170 | if( a * (1. - ecc) < 1. && a * (1. + ecc) < 1.) /* entirely within earth */ 171 | rval = SXPX_WARN_ORBIT_WITHIN_EARTH; /* remember, e can be negative */ 172 | if( a * (1. - ecc) < 1. || a * (1. + ecc) < 1.) /* perigee within earth */ 173 | rval = SXPX_WARN_PERIGEE_WITHIN_EARTH; 174 | /* Solve Kepler's' Equation */ 175 | for( i = 0; i < MAX_KEPLER_ITER; i++) 176 | { 177 | const double newton_raphson_epsilon = 1e-12; 178 | double f, fdot, delta_epw; 179 | int do_second_order_newton_raphson = 1; 180 | 181 | sinEPW = sin( epw); 182 | cosEPW = cos( epw); 183 | ecosE = axn * cosEPW + ayn * sinEPW; 184 | esinE = axn * sinEPW - ayn * cosEPW; 185 | f = capu - epw + esinE; 186 | if (fabs(f) < newton_raphson_epsilon) break; 187 | fdot = 1. - ecosE; 188 | delta_epw = f / fdot; 189 | if( !i) 190 | { 191 | const double max_newton_raphson = 1.25 * fabs( ecc); 192 | 193 | do_second_order_newton_raphson = 0; 194 | if( delta_epw > max_newton_raphson) 195 | delta_epw = max_newton_raphson; 196 | else if( delta_epw < -max_newton_raphson) 197 | delta_epw = -max_newton_raphson; 198 | else 199 | do_second_order_newton_raphson = 1; 200 | } 201 | if( do_second_order_newton_raphson) 202 | delta_epw = f / (fdot + 0.5*esinE*delta_epw); 203 | /* f/(fdot - 0.5*fdotdot * f / fdot) */ 204 | epw += delta_epw; 205 | } 206 | 207 | if( i == MAX_KEPLER_ITER) 208 | return( SXPX_ERR_CONVERGENCE_FAIL); 209 | 210 | /* Short period preliminary quantities */ 211 | temp = 1-elsq; 212 | pl = a*temp; 213 | r = a*(1-ecosE); 214 | temp2 = a / r; 215 | betal = sqrt(temp); 216 | temp = esinE/(1+betal); 217 | cosu = temp2 * (cosEPW - axn + ayn * temp); 218 | sinu = temp2 * (sinEPW - ayn - axn * temp); 219 | u = atan2( sinu, cosu); 220 | sin2u = 2*sinu*cosu; 221 | cos2u = 2*cosu*cosu-1; 222 | temp1 = ck2 / pl; 223 | temp2 = temp1 / pl; 224 | 225 | /* Update for short periodics */ 226 | rk = r*(1-1.5*temp2*betal*x3thm1)+0.5*temp1*sinio2*cos2u; 227 | uk = u-0.25*temp2*x7thm1*sin2u; 228 | xnodek = xnode+1.5*temp2*cosio*sin2u; 229 | xinck = xincl+1.5*temp2*cosio*sinio*cos2u; 230 | 231 | /* Orientation vectors */ 232 | sinuk = sin(uk); 233 | cosuk = cos(uk); 234 | sinik = sin(xinck); 235 | cosik = cos(xinck); 236 | sinnok = sin(xnodek); 237 | cosnok = cos(xnodek); 238 | xmx = -sinnok*cosik; 239 | xmy = cosnok*cosik; 240 | ux = xmx*sinuk+cosnok*cosuk; 241 | uy = xmy*sinuk+sinnok*cosuk; 242 | uz = sinik*sinuk; 243 | 244 | /* Position and velocity */ 245 | pos[0] = rk * ux * earth_radius_in_km; 246 | pos[1] = rk * uy * earth_radius_in_km; 247 | pos[2] = rk * uz * earth_radius_in_km; 248 | if( vel) 249 | { 250 | const double rdot = xke * sqrt(a) * esinE / r; 251 | const double rfdot = xke * sqrt(pl) / r; 252 | const double xn = xke / (a * sqrt(a)); 253 | const double rdotk = rdot - xn * temp1 * sinio2 * sin2u; 254 | const double rfdotk = rfdot + xn * temp1 * (sinio2 * cos2u + 1.5 * x3thm1); 255 | const double vx = xmx * cosuk - cosnok * sinuk; 256 | const double vy = xmy * cosuk - sinnok * sinuk; 257 | const double vz = sinik*cosuk; 258 | 259 | vel[0] = (rdotk * ux + rfdotk * vx) * earth_radius_in_km; 260 | vel[1] = (rdotk * uy + rfdotk * vy) * earth_radius_in_km; 261 | vel[2] = (rdotk * uz + rfdotk * vz) * earth_radius_in_km; 262 | } 263 | return( rval); 264 | } /*SGP4*/ 265 | -------------------------------------------------------------------------------- /dropouts.c: -------------------------------------------------------------------------------- 1 | /* Code to check for the existence of certain artsats in Space-Track's 2 | master TLE list. Occasionally, they've dropped objects and I didn't 3 | realize it. The objects ended up on NEOCP and I didn't ID them as 4 | quickly as might be desired, because I assumed they must be "new". 5 | This should warn me if certain artsats get dropped from 'all_tle.txt'. 6 | 7 | The absence of certain artsats is essentially routine. But for some 8 | objects (marked with an !), Space-Track is our only source of TLEs. 9 | (Or at least, I've been relying on them. I _could_ generate TLES for 10 | CXO, for example, based on _Horizons_ ephems. Since I don't, I want 11 | this program to squawk loudly if Space-Track stops supplying CXO TLEs.) 12 | 13 | As of 2024 Aug 31, the program can also be used for updating the 14 | Space-Track TLEs in a slightly more cautious manner. If you have 15 | downloaded new TLEs as the (default) ALL_TLE.TXT, and your "usual" 16 | TLEs are at all_tle.txt, then 17 | 18 | ./dropouts ALL_TLE.TXT 25000 all_tle.txt 19 | 20 | will check to see if ALL_TLE.TXT actually has 25000 or more TLEs in it. 21 | If it does, the download is presumed to have succeeded; all_tle.txt is 22 | unlinked and replaced with ALL_TLE.TXT. If it fails, we leave both files 23 | undisturbed. 24 | 25 | This should help in the increasingly frequent situations where new 26 | TLE files are downloaded and then have only an error message, or a 27 | drastically reduced number of TLEs. */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #ifdef _WIN32 34 | #include 35 | #else 36 | #include 37 | #endif 38 | 39 | #define VT_NORMAL "\033[0m" 40 | #define VT_REVERSE "\033[7m" 41 | 42 | int main( const int argc, const char **argv) 43 | { 44 | static const char *sats[] = { 45 | "00041A ! Cluster II-FM7", 46 | "00045A Cluster II-FM5", 47 | "00045B ! Cluster II-FM8", 48 | "02048A ! INTEGRAL", 49 | "07004A ! THEMIS-A", 50 | "07004D ! THEMIS-D", 51 | "07004E ! THEMIS-E", 52 | "09017B ! Atlas 5 Centaur R/B", 53 | "09068B ! Delta 4 R/B", 54 | "12003B ! Delta 4 R/B", 55 | "12011B ! Breeze-M R/B", 56 | "13024B ! WGS-5 R/B", 57 | "13026B ! Breeze-M R/B", 58 | "15005B ! Inmarsat 5F2 booster", 59 | "15011A ! MMS 1", 60 | "15011B ! MMS 2", 61 | "15011C ! MMS 3", 62 | "15011D ! MMS 4", 63 | "15019C ! Yuanzheng-1 Y1", 64 | "15042B ! Breeze-M R/B", 65 | "16041A ! MUOS 5", 66 | "18038A TESS", 67 | "22110B ! Ariane 5 R/B", 68 | "22146B ! Falcon 9 R/B", 69 | "22134B ! Falcon 9 R/B", 70 | "24048E DRO R/B", 71 | "24059B ! Falcon 9 R/B", 72 | "24127B ! Falcon 9 R/B", 73 | "24233A ! Proba-3", 74 | "24233B ! Proba-3 booster", 75 | "63039A ! Vela 1A", 76 | "64040B ! Vela 2B", 77 | "65058A Vela 3A", 78 | "65058B Vela 6", 79 | "67040A Vela 4A", 80 | "67040F ! Titan 3C transtage booster", 81 | "69046F ! Titan 3C transtage booster", 82 | "69046G ! Vela 9/10 interstage", 83 | "70027C ! Vela 6 booster", 84 | "72073A IMP-7", 85 | "76023C ! SOLRAD-11A", 86 | "76023H ! SOLRAD-11 debris", 87 | "77093E SL-6 R/B(2)", 88 | "83020A ! ASTRON", 89 | "83020D ! ASTRON booster", 90 | "92044A GEOTAIL", 91 | "95062A ! ISO", 92 | "95062C ! ISO debris", 93 | "97075B ! Equator S", 94 | "99040B ! Chandra X-Ray Observatory", 95 | "99040D ! IUS (for CXO)", 96 | "99066A ! XMM/Newton", 97 | "99066B ! XMM/Newton booster", 98 | NULL }; 99 | FILE *ifile = fopen( argc == 1 ? "all_tle.txt" : argv[1], "rb"); 100 | char buff[100]; 101 | size_t i; 102 | int trouble_found = 0, n_found = 0; 103 | 104 | assert( ifile); 105 | while( fgets( buff, sizeof( buff), ifile)) 106 | if( *buff == '1' && buff[1] == ' ' && buff[7] == 'U') 107 | { 108 | size_t len = strlen( buff); 109 | 110 | while( len && buff[len - 1] < ' ') 111 | len--; 112 | if( 69 == len) 113 | { 114 | n_found++; 115 | for( i = 0; sats[i]; i++) 116 | if( sats[i][0] == buff[9] && !memcmp( sats[i], buff + 9, 7)) 117 | sats[i] = ""; 118 | } 119 | } 120 | fclose( ifile); 121 | printf( "This will list high-flying artsats for which TLEs are not provided :\n"); 122 | for( i = 0; sats[i]; i++) 123 | if( sats[i][0]) 124 | { 125 | printf( "%s\n", sats[i]); 126 | if( sats[i][7] == '!') 127 | { 128 | trouble_found = 1; 129 | printf( VT_REVERSE); 130 | printf( "DANGER!!! We do NOT have an independent source of TLEs\n"); 131 | printf( "for this object. Please report to " 132 | "pluto\x40\x70roject\x70luto\x2e\x63om.\n"); 133 | /* Above is (slightly) obfuscated address to foil spambots */ 134 | printf( "This needs to be fixed.\n"); 135 | printf( VT_NORMAL); 136 | } 137 | } 138 | if( !trouble_found) 139 | printf( "Any missing objects are covered by other sources. Nothing\n" 140 | "to worry about here.\n"); 141 | printf( "%d found\n", n_found); 142 | if( 4 == argc && n_found > atoi( argv[2])) 143 | { 144 | printf( "Replacing '%s' with '%s'\n", argv[3], argv[1]); 145 | #ifdef _WIN32 146 | _unlink( argv[3]); 147 | #else 148 | unlink( argv[3]); 149 | #endif 150 | rename( argv[1], argv[3]); 151 | } 152 | return( 0); 153 | } 154 | -------------------------------------------------------------------------------- /dynamic.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | /* Hooks to allow the satellite code to be accessed from a DLL, 4 | with the DLL not loaded at startup; instead, LoadLibrary is 5 | used at the time you decide you actually want satellite functions. 6 | Not something likely to be useful to many people. I used it some 7 | years back for my desktop planetarium software; I could check for 8 | the existence of the DLL, use it if available, or fall back to 9 | some built-in code if it wasn't. (The DLL was, by standards of 10 | the day, a little bit large. Not everybody had enough interest 11 | in artsats to download it.) */ 12 | 13 | #include 14 | #include 15 | #include "windows.h" 16 | #include "norad.h" 17 | 18 | typedef void (__stdcall *sxpx_init_fn)( double *params, const tle_t *tle); 19 | typedef int (__stdcall *sxpx_fn)( const double tsince, const tle_t *tle, 20 | const double *params, double *pos, double *vel); 21 | typedef long (__stdcall *sxpx_version_fn)( void); 22 | 23 | static HINSTANCE load_sat_code_lib( const int unload) 24 | { 25 | static HINSTANCE h_sat_code_lib = (HINSTANCE)0; 26 | static int first_time = 1; 27 | 28 | if( unload) 29 | { 30 | if( h_sat_code_lib) 31 | FreeLibrary( h_sat_code_lib); 32 | h_sat_code_lib = NULL; 33 | first_time = 1; 34 | } 35 | else if( first_time) 36 | { 37 | h_sat_code_lib = LoadLibrary( "sat_code.dll"); 38 | first_time = 0; 39 | } 40 | return( h_sat_code_lib); 41 | } 42 | 43 | /* 26 Nov 2002: revised following two functions slightly so that the 44 | return values distinguish between "didn't get the function" and 45 | "didn't get the library" */ 46 | 47 | int SXPX_init( double *params, const tle_t *tle, const int sxpx_num) 48 | { 49 | static sxpx_init_fn func[5]; 50 | static char already_done[5]; 51 | int rval = 0; 52 | HINSTANCE h_sat_code_lib; 53 | 54 | if( !params) /* flag to unload library */ 55 | { 56 | int i; 57 | 58 | load_sat_code_lib( -1); 59 | for( i = 0; i < 5; i++) 60 | already_done[i] = 0; 61 | return( 0); 62 | } 63 | h_sat_code_lib = load_sat_code_lib( 0); 64 | if( !already_done[sxpx_num]) 65 | { 66 | if( h_sat_code_lib) 67 | func[sxpx_num] = (sxpx_init_fn)GetProcAddress( h_sat_code_lib, 68 | (LPCSTR)( sxpx_num + 1)); 69 | already_done[sxpx_num] = 1; 70 | } 71 | if( func[sxpx_num]) 72 | (*func[sxpx_num])( params, tle); 73 | else 74 | rval = -1; 75 | if( !h_sat_code_lib) 76 | rval = -2; 77 | return( rval); 78 | } 79 | 80 | int SXPX( const double tsince, const tle_t *tle, const double *params, 81 | double *pos, double *vel, const int sxpx_num) 82 | { 83 | static sxpx_fn func[5]; 84 | static char already_done[5]; 85 | int rval = 0; 86 | HINSTANCE h_sat_code_lib = load_sat_code_lib( 0); 87 | 88 | if( !already_done[sxpx_num]) 89 | { 90 | if( h_sat_code_lib) 91 | func[sxpx_num] = (sxpx_fn)GetProcAddress( h_sat_code_lib, 92 | (LPCSTR)( sxpx_num + 6)); 93 | already_done[sxpx_num] = 1; 94 | } 95 | if( func[sxpx_num]) 96 | rval = (*func[sxpx_num])( tsince, tle, params, pos, vel); 97 | else 98 | rval = -1; 99 | if( !h_sat_code_lib) 100 | rval = -2; 101 | return( rval); 102 | } 103 | 104 | long get_sat_code_lib_version( void) 105 | { 106 | HINSTANCE h_sat_code_lib = load_sat_code_lib( 0); 107 | long rval; 108 | 109 | if( !h_sat_code_lib) 110 | rval = -2; 111 | else 112 | { 113 | sxpx_version_fn func = 114 | (sxpx_version_fn)GetProcAddress( h_sat_code_lib, (LPCSTR)20); 115 | 116 | if( !func) 117 | rval = -1; 118 | else 119 | rval = (*func)( ); 120 | } 121 | return( rval); 122 | } 123 | -------------------------------------------------------------------------------- /fake_ast.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. 2 | 3 | This program will generate simulated geocentric observations 4 | for a given object from a TLE. In theory, one can then fit these 5 | pseudo-observations to a higher-quality physical model to get a 6 | considerably more accurate ephemeris for the object. */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include "norad.h" 12 | #include "observe.h" 13 | 14 | #define PI 3.1415926535897932384626433832795028841971693993751058209749445923 15 | 16 | int main( const int argc, const char **argv) 17 | { 18 | FILE *ifile = fopen( argv[1], "rb"); 19 | char line1[100], line2[100]; 20 | const char *intl_id = NULL; 21 | double step_size = .1; 22 | int i, n_steps = 100; 23 | bool show_vectors = false; 24 | 25 | if( !ifile) 26 | { 27 | printf( "Couldn't open input file\n"); 28 | exit( -1); 29 | } 30 | for( i = 1; i < argc; i++) 31 | if( argv[i][0] == '-') 32 | switch( argv[i][1]) 33 | { 34 | case 'i': 35 | intl_id = argv[i] + 2; 36 | break; 37 | case 'n': 38 | n_steps = atoi( argv[i] + 2); 39 | break; 40 | case 's': 41 | step_size = atof( argv[i] + 2); 42 | break; 43 | case 'v': 44 | show_vectors = true; 45 | break; 46 | default: 47 | printf( "Unrecognized option '%s'\n", argv[i]); 48 | break; 49 | } 50 | *line1 = '\0'; 51 | sxpx_set_implementation_param( SXPX_DUNDEE_COMPLIANCE, 1); 52 | while( fgets( line2, sizeof( line2), ifile)) 53 | { 54 | tle_t tle; /* Pointer to two-line elements set for satellite */ 55 | int err_val; 56 | 57 | if( (!intl_id || !memcmp( intl_id, line1 + 9, 6)) 58 | && (err_val = parse_elements( line1, line2, &tle)) >= 0) 59 | { /* hey! we got a TLE! */ 60 | int is_deep = select_ephemeris( &tle); 61 | double sat_params[N_SAT_PARAMS], observer_loc[3]; 62 | double prev_pos[3]; 63 | 64 | if( err_val) 65 | printf( "WARNING: TLE parsing error %d\n", err_val); 66 | for( i = 0; i < 3; i++) 67 | observer_loc[i] = 0.; 68 | if( is_deep) 69 | SDP4_init( sat_params, &tle); 70 | else 71 | SGP4_init( sat_params, &tle); 72 | for( i = 0; i < n_steps; i++) 73 | { 74 | double pos[3]; /* Satellite position vector */ 75 | double t_since = (double)( i - n_steps / 2) * step_size; 76 | double jd = tle.epoch + t_since; 77 | 78 | t_since *= 1440.; 79 | if( is_deep) 80 | err_val = SDP4( t_since, &tle, sat_params, pos, NULL); 81 | else 82 | err_val = SGP4( t_since, &tle, sat_params, pos, NULL); 83 | if( err_val) 84 | printf( "Ephemeris error %d\n", err_val); 85 | if( show_vectors) 86 | { 87 | if( i) 88 | printf( "%14.6f %14.6f %14.6f - ", pos[0] - prev_pos[0], 89 | pos[1] - prev_pos[1], 90 | pos[2] - prev_pos[2]); 91 | printf( "%14.6f %14.6f %14.6f\n", pos[0], pos[1], pos[2]); 92 | memcpy( prev_pos, pos, 3 * sizeof( double)); 93 | } 94 | else 95 | { 96 | double ra, dec, dist_to_satellite; 97 | 98 | get_satellite_ra_dec_delta( observer_loc, pos, 99 | &ra, &dec, &dist_to_satellite); 100 | epoch_of_date_to_j2000( jd, &ra, &dec); 101 | printf( "%-14sC%13.5f %08.4f %+08.4f", 102 | intl_id, jd, ra * 180. / PI, dec * 180. / PI); 103 | printf( " TLEs 500\n"); 104 | } 105 | } 106 | } 107 | strcpy( line1, line2); 108 | } 109 | fclose( ifile); 110 | return( 0); 111 | } /* End of main() */ 112 | 113 | -------------------------------------------------------------------------------- /fix_tles.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. 2 | 3 | fix_tles : change TLE data and replace with correct checksums 4 | 5 | If one alters a TLE, one will normally cause a change in 6 | the checksum data at the end of the line. This program reads 7 | in successive lines from an input file; if a line and the 8 | preceding line make a TLE, the checksum is computed for 9 | both lines and is suitably reset. 10 | 11 | I've had instances where I realized that either the COSPAR 12 | or NORAD designation was incorrectly set, and added options 13 | that let you specify which designation should be used for all 14 | TLEs in the output. (This is specific to my use case : I usually 15 | compute many TLEs for a particular object, which each TLE 16 | being fitted to give good state vectors over one day.) 17 | 18 | At some point, I may need to similarly batch-correct bulletin 19 | numbers or something of that ilk, but at present, only the 20 | designations (and checksums) can be batch-corrected with this 21 | program. */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "norad.h" 28 | 29 | /* After being modified, the checksum byte in TLEs must be recomputed : */ 30 | 31 | static void set_checksum( char *line) 32 | { 33 | const int csum = tle_checksum( line); 34 | 35 | assert( csum >= 0); 36 | line[68] += csum; 37 | if( line[68] > '9') 38 | line[68] -= 10; 39 | } 40 | 41 | static void usage( void) 42 | { 43 | fprintf( stderr, 44 | "'fix_tles' reads in TLEs and outputs TLEs with corrected checksums.\n" 45 | "Options are :\n" 46 | " -i YYNNNA Replace COSPAR designation\n" 47 | " -n NNNNN Replace five-digit NORAD designation\n" 48 | " -f filename Specify input file (default = stdin)\n" 49 | " -o filename Specify output file (default = stdout)\n" ); 50 | exit( -1); 51 | } 52 | 53 | int main( const int argc, const char **argv) 54 | { 55 | int i, line_no = 0; 56 | const char *norad_desig = NULL; 57 | char intl_desig[10]; 58 | FILE *ifile = stdin, *ofile = stdout; 59 | char line1[200], line2[200]; 60 | 61 | *intl_desig = '\0'; 62 | for( i = 1; i < argc; i++) 63 | if( argv[i][0] == '-') 64 | { 65 | const char *arg = argv[i] + 2; 66 | 67 | if( !*arg && i < argc - 1) 68 | arg = argv[i + 1]; 69 | switch( argv[i][1]) 70 | { 71 | case 'i': 72 | assert( strlen( arg) < 9); 73 | snprintf( intl_desig, sizeof( intl_desig), "%-9s", arg); 74 | break; 75 | case 'n': 76 | norad_desig = arg; 77 | break; 78 | case 'o': 79 | ofile = fopen( arg, "wb"); 80 | if( !ofile) 81 | { 82 | fprintf( stderr, "'%s' not opened\n", arg); 83 | usage( ); 84 | } 85 | break; 86 | case 'f': 87 | ifile = fopen( arg, "rb"); 88 | if( !ifile) 89 | { 90 | fprintf( stderr, "'%s' not opened\n", arg); 91 | usage( ); 92 | } 93 | break; 94 | default: 95 | fprintf( stderr, "Unrecognized option '%s'\n", argv[i]); 96 | usage( ); 97 | break; 98 | } 99 | } 100 | *line1 = '\0'; 101 | while( fgets( line2, sizeof( line2), ifile)) 102 | { 103 | tle_t unused_tle; 104 | 105 | if( parse_elements( line1, line2, &unused_tle) >= 0) 106 | { 107 | if( norad_desig) 108 | { 109 | memcpy( line1 + 2, norad_desig, 5); 110 | memcpy( line2 + 2, norad_desig, 5); 111 | } 112 | if( *intl_desig) 113 | memcpy( line1 + 9, intl_desig, 8); 114 | set_checksum( line1); 115 | set_checksum( line2); 116 | } 117 | if( line_no) 118 | fputs( line1, ofile); 119 | line_no++; 120 | strcpy( line1, line2); 121 | } 122 | if( line_no) 123 | fputs( line1, ofile); 124 | return( 0); 125 | } 126 | -------------------------------------------------------------------------------- /get_high.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | /* Code to extract elements for high-flying artsats. Give */ 4 | /* it the name of the input file of TLEs and a cutoff of */ 5 | /* the mean motion, and only TLEs with a lower motion */ 6 | /* will be output. */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "norad.h" 13 | 14 | int main( const int argc, const char **argv) 15 | { 16 | FILE *ifile = fopen((argc > 1 ? argv[1] : "all_tle.txt"), "rb"); 17 | FILE *ofile; 18 | char line0[200], line1[200], line2[200]; 19 | const double cutoff = (argc > 2 ? atof( argv[2]) : .6); 20 | const time_t t0 = time( NULL); 21 | 22 | if( !ifile) 23 | perror( "Input file not opened"); 24 | ofile = (argc > 3 ? fopen( argv[3], "a") : stdout); 25 | if( !ofile) 26 | perror( "Output file not opened"); 27 | if( !ifile || !ofile) 28 | return( -1); 29 | *line0 = *line1 = '\0'; 30 | fprintf( ofile, "# Added %.24s UTC\n", asctime( gmtime( &t0))); 31 | while( fgets( line2, sizeof( line2), ifile)) 32 | { 33 | if( *line2 == '2' && *line1 == '1' 34 | && !tle_checksum( line1) && !tle_checksum( line2) 35 | && atof( line2 + 52) < cutoff) 36 | fprintf( ofile, "%s%s%s", line0, line1, line2); 37 | strcpy( line0, line1); 38 | strcpy( line1, line2); 39 | } 40 | fclose( ifile); 41 | return( 0); 42 | } 43 | -------------------------------------------------------------------------------- /get_vect.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. 2 | 3 | This code can read a TLE and compute a geocentric state vector, 4 | formatted such that Find_Orb can then read it in. I did this 5 | partly to test the hypothesis that if you compute a state vector 6 | from Space-Track TLEs at their epoch, you get the "actual" motion. 7 | That is to say, you could numerically integrate it to get a better 8 | result. This turns out not to be the case. Space-Track TLEs may 9 | be a best-fit to a set of observations or (as with my own TLEs) 10 | a best fit to a numerically integrated ephemeris, but there 11 | doesn't seem to be a way to improve them by doing a numerical 12 | integration. 13 | 14 | My second purpose was to be able to feed the state vector created 15 | by this program into Find_Orb as an initial orbit guess. For that 16 | purpose, it seems to work. You see large residuals as a result 17 | of the difference between numerical integration and SGP4/SDP4. 18 | But it gets you close enough that you can then do differential 19 | corrections (least-squares fitting). */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include "norad.h" 25 | #include "watdefs.h" 26 | #include "afuncs.h" 27 | #include "date.h" 28 | 29 | #define PI \ 30 | 3.1415926535897932384626433832795028841971693993751058209749445923 31 | 32 | int main( const int argc, const char **argv) 33 | { 34 | FILE *ifile; 35 | const char *filename = "all_tle.txt"; 36 | char line0[100], line1[100], line2[100]; 37 | int i; 38 | const char *norad = NULL, *intl = NULL; 39 | double jd = 0.; 40 | int is_j2000 = 1, is_equatorial = 1; 41 | 42 | for( i = 1; i < argc; i++) 43 | if( argv[i][0] == '-') 44 | { 45 | const char *arg = (i < argc - 1 && !argv[i][2] 46 | ? argv[i + 1] : argv[i] + 2); 47 | 48 | switch( argv[i][1]) 49 | { 50 | case 'n': 51 | norad = arg; 52 | printf( "Looking for NORAD %s\n", norad); 53 | break; 54 | case 'i': 55 | intl = arg; 56 | printf( "Looking for international ID %s\n", intl); 57 | break; 58 | case 't': 59 | jd = get_time_from_string( 0., arg, FULL_CTIME_YMD, NULL); 60 | break; 61 | case 'd': 62 | is_j2000 = 0; 63 | break; 64 | default: 65 | printf( "'%s': unrecognized option\n", argv[i]); 66 | return( -1); 67 | break; 68 | } 69 | } 70 | if( argc > 1 && argv[1][0] != '-') 71 | filename = argv[1]; 72 | ifile = fopen( filename, "rb"); 73 | if( !ifile) 74 | { 75 | fprintf( stderr, "Couldn't open '%s': ", filename); 76 | perror( ""); 77 | return( -1); 78 | } 79 | *line0 = *line1 = '\0'; 80 | while( fgets( line2, sizeof( line2), ifile)) 81 | { 82 | if( *line1 == '1' && (!norad || !memcmp( line1 + 2, norad, 5)) 83 | && (!intl || !memcmp( line1 + 9, intl, strlen( intl))) 84 | && *line2 == '2') 85 | { 86 | tle_t tle; 87 | const int err_code = parse_elements( line1, line2, &tle); 88 | 89 | if( err_code >= 0) 90 | { 91 | const int is_deep = select_ephemeris( &tle); 92 | double state[6], state_j2000[6], precess_matrix[9]; 93 | double params[N_SAT_PARAMS], t_since; 94 | const double epoch_tdt = 95 | tle.epoch + td_minus_utc( tle.epoch) / seconds_per_day; 96 | const double J2000 = 2451545.; 97 | double *state_to_show; 98 | 99 | if( !jd) 100 | jd = epoch_tdt; 101 | t_since = (jd - epoch_tdt) * minutes_per_day; 102 | if( is_deep) 103 | { 104 | SDP4_init( params, &tle); 105 | SDP4( t_since, &tle, params, state, state + 3); 106 | } 107 | else 108 | { 109 | SGP4_init( params, &tle); 110 | SGP4( t_since, &tle, params, state, state + 3); 111 | } 112 | if( strlen( line0) < 60) 113 | printf( "%s", line0); 114 | setup_precession( precess_matrix, 2000. + (jd - J2000) / 365.25, 2000.); 115 | precess_vector( precess_matrix, state, state_j2000); 116 | precess_vector( precess_matrix, state + 3, state_j2000 + 3); 117 | state_to_show = (is_j2000 ? state_j2000 : state); 118 | printf( " %.6f %.6s\n", jd, line1 + 9); 119 | printf( " %.5f %.5f %.5f 0408 # Ctr 3 km sec %s %s\n", 120 | state_to_show[0], state_to_show[1], state_to_show[2], 121 | is_equatorial ? "eq" : "ecl", 122 | is_j2000 ? "" : "of_date"); 123 | printf( " %.5f %.5f %.5f 0 0 0\n", 124 | state_to_show[3] / seconds_per_minute, 125 | state_to_show[4] / seconds_per_minute, 126 | state_to_show[5] / seconds_per_minute); 127 | } 128 | } 129 | strcpy( line0, line1); 130 | strcpy( line1, line2); 131 | } 132 | fclose( ifile); 133 | return( 0); 134 | } 135 | -------------------------------------------------------------------------------- /line2.cpp: -------------------------------------------------------------------------------- 1 | /* See LICENSE. NOTE that this has been obsoleted by the 'add_off.c' 2 | program in the 'lunar' repository (q.v.). The only use this code would 3 | have would be for a spacecraft getting astrometric data for which we 4 | don't have Horizons data. I don't expect that to happen. With that 5 | disclaimer : 6 | 7 | The Minor Planet Center accepts astrometry from spacecraft using 8 | a modification of their usual 80-column "punched-card" format. A 9 | second line is used to tell you where the spacecraft was, relative 10 | to the geocenter. 11 | 12 | https://minorplanetcenter.net/iau/info/SatelliteObs.html 13 | 14 | This code can reset those positions for Earth-orbiting spacecraft using 15 | TLEs ('two-line elements'; https://www.projectpluto.com/tle_info.htm.) 16 | This helps when accurate positions are not provided or completely trusted 17 | or appear to be bad. 18 | 19 | This code will read the 80-column astrometry and, when it finds a 20 | spacecraft position report (the "second line"), look through the TLE 21 | file for a matching TLE. If it finds one, it'll compute the spacecraft 22 | location for that time and modify the position accordingly. 23 | 24 | This was written specifically to handle a problem with a satellite in 25 | LEO. TLEs don't exist for heliocentric objects, but I may revise this 26 | code eventually to add positions for them (this could be done by, for 27 | example, requesting them from JPL Horizons). TLEs exist for TESS, 28 | computed by me, but some work would be required to make those actually 29 | function properly (the numbers are larger and the format is somewhat 30 | different as a result). So this really works, at present, only for 31 | (C51) WISE, (C52) Swift, and (C53) NEOSSat. */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include "norad.h" 40 | #include "watdefs.h" 41 | #include "afuncs.h" 42 | #include "mpc_func.h" 43 | #include "stringex.h" 44 | 45 | #define PI \ 46 | 3.1415926535897932384626433832795028841971693993751058209749445923 47 | 48 | int find_tle( tle_t *tle, const char *filename, const int norad_no) 49 | { 50 | FILE *ifile = fopen( filename, "rb"); 51 | char line0[100], line1[100], line2[100]; 52 | int rval = -1; 53 | 54 | if( !ifile) 55 | { 56 | fprintf( stderr, "Couldn't open TLE file '%s'\n", filename); 57 | exit( -1); 58 | } 59 | *line0 = *line1 = '\0'; 60 | while( rval && fgets( line2, sizeof( line2), ifile)) 61 | { 62 | if( *line1 == '1' && *line2 == '2' && atoi( line1 + 2) == norad_no) 63 | if( parse_elements( line1, line2, tle) >= 0) 64 | rval = 0; 65 | strlcpy_error( line0, line1); 66 | strlcpy_error( line1, line2); 67 | } 68 | fclose( ifile); 69 | if( rval) 70 | { 71 | fprintf( stderr, "Couldn't find TLEs for %5d in TLE file '%s'\n", 72 | norad_no, filename); 73 | exit( -1); 74 | } 75 | return( rval); 76 | } 77 | 78 | /* 79 | C49 = 29510 = 2006-047A = STEREO-A 80 | C50 = 29511 = 2006-047B = STEREO-B 81 | C51 = 36119 = 2009-071A = WISE * 82 | C52 = 28485 = 2004-047A = Swift * 83 | C53 = 39089 = 2013-009D = NEOSSat * 84 | C54 = 28928 = 2006-001A = New Horizons 85 | C55 = 34380 = 2009-011A = Kepler 86 | C56 = 41043 = 2015-070A = LISA-Pathfinder 87 | C57 = 43435 = 2018-038A = TESS * 88 | Note that only the asterisked objects are actually in earth orbit 89 | and therefore have TLEs. But I may try to figure out some way to add 90 | in positions for the heliocentric spacecraft later. */ 91 | 92 | static int mpc_code_to_norad_number( const char *mpc_code) 93 | { 94 | const char *codes = "C49 C50 C51 C52 C53 C54 C55 C56 C57 "; 95 | const int norad_numbers[] = { 29510, 29511, 36119, 28485, 39089, 96 | 28928, 34380, 41043, 43435 }; 97 | int i; 98 | 99 | for( i = 0; codes[i * 4]; i++) 100 | if( !memcmp( codes + i * 4, mpc_code, 3)) 101 | return( norad_numbers[i]); 102 | assert( 1); /* not supposed to ever happen, unless a new */ 103 | return( 0); /* satellite has been added */ 104 | } 105 | 106 | int main( const int argc, const char **argv) 107 | { 108 | const char *tle_filename = (argc > 2 ? argv[2] : "all_tle.txt"); 109 | const char *astrometry_filename = argv[1]; 110 | FILE *ifile = (argc > 1 ? fopen( astrometry_filename, "rb") : NULL); 111 | int is_deep = 0, curr_norad = 0; 112 | char buff[200]; 113 | double params[N_SAT_PARAMS]; 114 | const double J2000 = 2451545.; 115 | tle_t tle; 116 | 117 | if( argc > 1 && !ifile) 118 | fprintf( stderr, "'%s' not found : %s\n", astrometry_filename, strerror( errno)); 119 | if( !ifile) 120 | { 121 | fprintf( stderr, "'line2' takes as a command-line argument the name of the input\n" 122 | "astrometry file. It sends astrometry with corrected/added\n" 123 | "spacecraft locations to stdout.\n"); 124 | exit( -1); 125 | } 126 | while( fgets( buff, sizeof( buff), ifile)) 127 | { 128 | double jd; 129 | 130 | if( strlen( buff) > 80 && buff[14] == 's' 131 | && (jd = extract_date_from_mpc_report( buff, NULL)) > 2400000.) 132 | { 133 | double state[6], state_j2000[6], precess_matrix[9]; 134 | double t_since; 135 | const int norad_number = mpc_code_to_norad_number( buff + 77); 136 | int i; 137 | 138 | if( curr_norad != norad_number) 139 | { 140 | curr_norad = norad_number; 141 | find_tle( &tle, tle_filename, norad_number); 142 | is_deep = select_ephemeris( &tle); 143 | if( is_deep) 144 | SDP4_init( params, &tle); 145 | else 146 | SGP4_init( params, &tle); 147 | } 148 | t_since = (jd - tle.epoch) * minutes_per_day; 149 | if( is_deep) 150 | SDP4( t_since, &tle, params, state, state + 3); 151 | else 152 | SGP4( t_since, &tle, params, state, state + 3); 153 | setup_precession( precess_matrix, 2000. + (jd - J2000) / 365.25, 2000.); 154 | precess_vector( precess_matrix, state, state_j2000); 155 | precess_vector( precess_matrix, state + 3, state_j2000 + 3); 156 | for( i = 0; i < 3; i++) 157 | { 158 | char *tptr = buff + 34 + i * 12; 159 | 160 | snprintf_err( tptr, 12, "%11.4f", fabs( state_j2000[i])); 161 | *tptr = (state_j2000[i] > 0. ? '+' : '-'); 162 | tptr[11] = ' '; 163 | } 164 | } 165 | printf( "%s", buff); 166 | } 167 | fclose( ifile); 168 | return( 0); 169 | } 170 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # GNU MAKE Makefile for artsat code and utilities 2 | # 3 | # Usage: make [W64=Y] [W32=Y] [MSWIN=Y] [tgt] 4 | # 5 | # where tgt can be any of: 6 | # [all|get_high|mergetle|obs_tes2|...] 7 | # 8 | # ...see below for complete list. Note that you have to 'make tle_date' and/or 9 | # 'make tle_date.cgi' separately; they have a dependency on the 'lunar' 10 | # library (also on my GitHub site). 11 | # 12 | # 'W64'/'W32' = cross-compile for 64- or 32-bit Windows, using MinGW, 13 | # on a Linux box 14 | # 'MSWIN' = compile for Windows, using MinGW and PDCurses, on a Windows machine 15 | # 'CC=clang' or 'CC=g++-4.8' = use clang or older GCC 16 | # (I've used gmake CLANG=Y on PC-BSD; probably works on OS/X too) 17 | # None of these: compile using default g++ on Linux, for Linux 18 | # 19 | 20 | # As CC is an implicit variable, a simple CC?=g++ doesn't work. 21 | # We have to use this trick from https://stackoverflow.com/a/42958970 22 | ifeq ($(origin CC),default) 23 | CC=gcc 24 | CXX=g++ 25 | endif 26 | 27 | ifeq ($(shell uname -s),FreeBSD) 28 | CC=cc 29 | CXX=c++ 30 | endif 31 | 32 | EXE= 33 | RM=rm -f 34 | 35 | # I'm using 'mkdir -p' to avoid error messages if the directory exists. 36 | # It may fail on very old systems, and will probably fail on non-POSIX 37 | # systems. If so, change to '-mkdir' and ignore errors. 38 | 39 | ifdef MSWIN 40 | EXE=.exe 41 | MKDIR=-mkdir 42 | else 43 | MKDIR=mkdir -p 44 | endif 45 | 46 | LIB_DIR=$(INSTALL_DIR)/lib 47 | 48 | ifdef W64 49 | CC=x86_64-w64-mingw32-gcc 50 | CXX=x86_64-w64-mingw32-g++ 51 | EXE=.exe 52 | LIB_DIR=$(INSTALL_DIR)/win_lib 53 | endif 54 | 55 | ifdef W32 56 | CC=i686-w64-mingw32-gcc 57 | CXX=i686-w64-mingw32-g++ 58 | EXE=.exe 59 | LIB_DIR=$(INSTALL_DIR)/win_lib32 60 | endif 61 | 62 | # You can have your include files in ~/include and libraries in 63 | # ~/lib, in which case only the current user can use them; or 64 | # (with root privileges) you can install them to /usr/local/include 65 | # and /usr/local/lib for all to enjoy. 66 | 67 | PREFIX?=~ 68 | ifdef GLOBAL 69 | INSTALL_DIR=/usr/local 70 | else 71 | INSTALL_DIR=$(PREFIX) 72 | endif 73 | 74 | INCL=$(INSTALL_DIR)/include 75 | 76 | all: dropouts$(EXE) fake_ast$(EXE) fix_tles$(EXE) get_high$(EXE) \ 77 | line2$(EXE) mergetle$(EXE) obs_tes2$(EXE) obs_test$(EXE) \ 78 | out_comp$(EXE) sat_cgi$(EXE) sat_eph$(EXE) sat_id$(EXE) \ 79 | sat_id2$(EXE) sat_id3$(EXE) summarize$(EXE) \ 80 | test_des$(EXE) test_out$(EXE) test_sat$(EXE) test2$(EXE) tle2mpc$(EXE) 81 | 82 | CFLAGS+=-Wextra -Wall -O3 -pedantic -Wshadow 83 | 84 | ifdef UCHAR 85 | CFLAGS += -funsigned-char 86 | endif 87 | 88 | ifdef DEBUG 89 | CFLAGS += -g 90 | endif 91 | 92 | ifndef NO_ERRORS 93 | CFLAGS += -Werror 94 | endif 95 | 96 | 97 | clean: 98 | $(RM) *.o 99 | $(RM) dropouts$(EXE) 100 | $(RM) fake_ast$(EXE) 101 | $(RM) fix_tles$(EXE) 102 | $(RM) get_high$(EXE) 103 | $(RM) get_vect$(EXE) 104 | $(RM) libsatell.a 105 | $(RM) line2$(EXE) 106 | $(RM) mergetle$(EXE) 107 | $(RM) obs_tes2$(EXE) 108 | $(RM) obs_test$(EXE) 109 | $(RM) out_comp$(EXE) 110 | $(RM) sat_cgi$(EXE) 111 | $(RM) sat_eph$(EXE) 112 | $(RM) sat_id$(EXE) 113 | $(RM) sat_id2$(EXE) 114 | $(RM) sat_id3$(EXE) 115 | $(RM) summarize$(EXE) 116 | $(RM) test2$(EXE) 117 | $(RM) test_des$(EXE) 118 | $(RM) test_out$(EXE) 119 | $(RM) test_sat$(EXE) 120 | $(RM) tle2mpc$(EXE) 121 | $(RM) tle_date$(EXE) 122 | $(RM) tle_date.cgi 123 | 124 | install: 125 | $(MKDIR) $(LIB_DIR) 126 | cp libsatell.a $(LIB_DIR) 127 | cp norad.h $(INSTALL_DIR)/include 128 | $(MKDIR) $(INSTALL_DIR)/bin 129 | cp sat_id$(EXE) $(INSTALL_DIR)/bin 130 | 131 | install_lib: 132 | $(MKDIR) $(LIB_DIR) 133 | cp libsatell.a $(LIB_DIR) 134 | cp norad.h $(INSTALL_DIR)/include 135 | 136 | uninstall: 137 | rm $(INSTALL_DIR)/lib/libsatell.a 138 | rm $(INSTALL_DIR)/include/norad.h 139 | rm $(INSTALL_DIR)/bin/sat_id 140 | 141 | uninstall_lib: 142 | rm $(INSTALL_DIR)/lib/libsatell.a 143 | rm $(INSTALL_DIR)/include/norad.h 144 | 145 | OBJS= sgp.o sgp4.o sgp8.o sdp4.o sdp8.o deep.o basics.o get_el.o common.o tle_out.o 146 | 147 | get_high$(EXE): get_high.o get_el.o 148 | $(CC) $(CFLAGS) -o get_high$(EXE) get_high.o get_el.o 149 | 150 | mergetle$(EXE): mergetle.o 151 | $(CC) $(CFLAGS) -o mergetle$(EXE) mergetle.o -lm 152 | 153 | dropouts$(EXE): dropouts.o 154 | $(CC) $(CFLAGS) -o dropouts$(EXE) dropouts.o 155 | 156 | obs_tes2$(EXE): obs_tes2.o observe.o libsatell.a 157 | $(CC) $(CFLAGS) -o obs_tes2$(EXE) obs_tes2.o observe.o libsatell.a -lm 158 | 159 | obs_test$(EXE): obs_test.o observe.o libsatell.a 160 | $(CC) $(CFLAGS) -o obs_test$(EXE) obs_test.o observe.o libsatell.a -lm 161 | 162 | fake_ast$(EXE): fake_ast.o observe.o libsatell.a 163 | $(CC) $(CFLAGS) -o fake_ast$(EXE) fake_ast.o observe.o libsatell.a -lm 164 | 165 | fix_tles$(EXE): fix_tles.o libsatell.a 166 | $(CC) $(CFLAGS) -o fix_tles$(EXE) fix_tles.o libsatell.a -lm 167 | 168 | get_vect$(EXE): get_vect.cpp observe.o libsatell.a 169 | $(CXX) $(CFLAGS) -o get_vect$(EXE) -I $(INCL) get_vect.cpp observe.o libsatell.a -lm -L $(LIB_DIR) -llunar 170 | 171 | line2$(EXE): line2.cpp libsatell.a 172 | $(CXX) $(CFLAGS) -o line2$(EXE) -I $(INCL) line2.cpp libsatell.a -lm -L $(LIB_DIR) -llunar 173 | 174 | out_comp$(EXE): out_comp.o 175 | $(CC) $(CFLAGS) -o out_comp$(EXE) out_comp.o -lm 176 | 177 | libsatell.a: $(OBJS) 178 | rm -f libsatell.a 179 | ar rv libsatell.a $(OBJS) 180 | 181 | sat_eph$(EXE): sat_eph.c observe.o libsatell.a 182 | $(CC) $(CFLAGS) -o sat_eph$(EXE) -I $(INCL) sat_eph.c observe.o libsatell.a -lm -L $(LIB_DIR) -llunar 183 | 184 | sat_cgi$(EXE): sat_eph.c observe.o libsatell.a 185 | $(CC) $(CFLAGS) -o sat_cgi$(EXE) -I $(INCL) sat_eph.c observe.o -DON_LINE_VERSION libsatell.a -lm -L $(LIB_DIR) -llunar 186 | 187 | sat_id$(EXE): sat_id.cpp sat_util.o observe.o libsatell.a 188 | $(CXX) $(CFLAGS) -o sat_id$(EXE) -I $(INCL) sat_id.cpp sat_util.o observe.o libsatell.a -lm -L $(LIB_DIR) -llunar 189 | 190 | sat_id2$(EXE): sat_id2.cpp sat_id.cpp sat_util.o observe.o libsatell.a 191 | $(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 192 | 193 | sat_id3$(EXE): sat_id3.cpp sat_id.cpp sat_util.o observe.o libsatell.a 194 | $(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 195 | 196 | summarize$(EXE): summarize.c observe.o libsatell.a 197 | $(CC) $(CFLAGS) -o summarize$(EXE) -I $(INCL) summarize.c observe.o libsatell.a -lm -L $(LIB_DIR) -llunar 198 | 199 | test2$(EXE): test2.o sgp.o libsatell.a 200 | $(CC) $(CFLAGS) -o test2$(EXE) test2.o sgp.o libsatell.a -lm 201 | 202 | tle_date$(EXE): tle_date.o 203 | $(CC) $(CFLAGS) -o tle_date$(EXE) tle_date.o -L $(LIB_DIR) -llunar -lm 204 | 205 | tle_date.o: tle_date.c 206 | $(CC) $(CFLAGS) -o tle_date.o -c -I../include tle_date.c 207 | 208 | tle_date.cgi: tle_date.c 209 | $(CC) $(CFLAGS) -o tle_date.cgi -I../include -DON_LINE_VERSION tle_date.c -L $(LIB_DIR) -llunar 210 | 211 | tle2mpc$(EXE): tle2mpc.cpp libsatell.a 212 | $(CXX) $(CFLAGS) -o tle2mpc$(EXE) -I $(INCL) tle2mpc.cpp libsatell.a -lm -L $(LIB_DIR) -llunar 213 | 214 | test_des$(EXE): test_des.o libsatell.a 215 | $(CC) $(CFLAGS) -o test_des$(EXE) test_des.o libsatell.a -lm 216 | 217 | test_out$(EXE): test_out.o tle_out.o get_el.o sgp4.o common.o 218 | $(CC) $(CFLAGS) -o test_out$(EXE) test_out.o tle_out.o get_el.o sgp4.o common.o -lm 219 | 220 | test_sat$(EXE): test_sat.o libsatell.a 221 | $(CC) $(CFLAGS) -o test_sat$(EXE) test_sat.o libsatell.a -lm 222 | 223 | .cpp.o: 224 | $(CXX) $(CFLAGS) -c $< 225 | 226 | .c.o: 227 | $(CC) $(CFLAGS) -c $< 228 | -------------------------------------------------------------------------------- /mergetle.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. 2 | 3 | Code to read one or more files of TLEs, remove duplicates, 4 | and sort them according to various possible criteria (eccentricity, 5 | orbital period, etc.) */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define TLE struct tle 13 | #define MAX_TLES 200000 14 | 15 | /* As of late 2020, NORAD numbers are stored in five digits. There's 16 | some possibility of that being bumped up to nine digits, in which 17 | case I'll have to revisit this code. */ 18 | 19 | #define MAX_NORAD_NUMBER 100000 20 | 21 | TLE 22 | { 23 | char name_line[80], line1[80], line2[80]; 24 | }; 25 | 26 | int n_duplicates = 0, heavens_above_html_tles = 0; 27 | 28 | int load_tles_from_file( FILE *ifile, TLE *tles, char *already_found) 29 | { 30 | int rval = 0; 31 | char buff[80], prev_line[80]; 32 | 33 | *prev_line = '\0'; 34 | while( fgets( buff, sizeof( buff), ifile)) 35 | { 36 | if( *buff == '1' && heavens_above_html_tles && 37 | !memcmp( buff + 69, "", 7)) 38 | strcpy( buff + 69, "\n"); 39 | if( *buff == '1' && strlen( buff) > 69 && buff[69] < ' ') 40 | { 41 | char buff2[80]; 42 | const int norad_number = atoi( buff + 2); 43 | 44 | if( fgets( buff2, sizeof( buff2), ifile)) 45 | if( *buff2 == '2' && strlen( buff2) > 69 && buff2[69] < ' ') 46 | { 47 | if( !already_found[norad_number]) 48 | { 49 | if( heavens_above_html_tles) /* can't use HA names */ 50 | *prev_line = '\0'; 51 | strcpy( tles[rval].name_line, prev_line); 52 | strcpy( tles[rval].line1, buff); 53 | strcpy( tles[rval].line2, buff2); 54 | already_found[norad_number] = 1; 55 | rval++; 56 | *buff = '\0'; 57 | } 58 | else 59 | n_duplicates++; 60 | } 61 | } 62 | strcpy( prev_line, buff); 63 | } 64 | return( rval); 65 | } 66 | 67 | FILE *test_fopen( const char *filename, const char *permits) 68 | { 69 | FILE *rval = fopen( filename, permits); 70 | 71 | if( !rval) 72 | { 73 | printf( "%s not opened\n", filename); 74 | exit( -1); 75 | } 76 | return( rval); 77 | } 78 | 79 | void show_tle( FILE *ofile, const TLE *tle) 80 | { 81 | fprintf( ofile, "%s", tle->name_line); 82 | fprintf( ofile, "%s", tle->line1); 83 | fprintf( ofile, "%s", tle->line2); 84 | } 85 | 86 | static double get_perigee( const TLE *tle) 87 | { 88 | const double ecc = atof( tle->line2 + 26) * 1e-7; 89 | const double revs_per_day = atof( tle->line2 + 52); 90 | const double semimajor = pow( revs_per_day, -2. / 3.); 91 | 92 | return( semimajor * (1. - ecc)); 93 | } 94 | 95 | static int compare_doubles( const char *buff1, const char *buff2) 96 | { 97 | const double d1 = atof( buff1); 98 | const double d2 = atof( buff2); 99 | int rval; 100 | 101 | if( d1 < d2) 102 | rval = -1; 103 | else if( d1 > d2) 104 | rval = 1; 105 | else 106 | rval = 0; 107 | return( rval); 108 | } 109 | 110 | int tle_compare( const TLE *tle1, const TLE *tle2, const char sort_method) 111 | { 112 | int i, rval = 0; 113 | 114 | switch( sort_method) 115 | { 116 | case 'n': case 'N': /* sort by NORAD number */ 117 | rval = atoi( tle1->line1 + 2) - atoi( tle2->line1 + 2); 118 | break; 119 | case 'c': case 'C': /* sort by COSPAR (international) desig */ 120 | if( tle1->line1[9] >= '5' && tle2->line1[9] < '5') 121 | rval = -1; 122 | else if( tle2->line1[9] >= '5' && tle1->line1[9] < '5') 123 | rval = 1; 124 | else /* COSPAR IDs are from the same century */ 125 | rval = memcmp( tle1->line1 + 9, tle2->line1 + 9, 8); 126 | break; 127 | case 'm': case 'M': /* sort by mean motion */ 128 | rval = compare_doubles( tle1->line2 + 52, tle2->line2 + 52); 129 | break; 130 | case 'e': case 'E': /* sort by eccentricity */ 131 | for( i = 26; !rval && i < 33; i++) 132 | rval = tle1->line2[i] - tle2->line2[i]; 133 | break; 134 | case 'p': case 'P': /* sort by epoch */ 135 | for( i = 18; !rval && i < 32; i++) 136 | rval = tle1->line1[i] - tle2->line1[i]; 137 | break; 138 | case 'i': case 'I': /* sort by incl */ 139 | rval = compare_doubles( tle1->line2 + 8, tle2->line2 + 8); 140 | break; 141 | case 'o': case 'O': /* sort by ascending node */ 142 | rval = compare_doubles( tle1->line2 + 17, tle2->line2 + 17); 143 | break; 144 | case 'q': 145 | { 146 | const double q1 = get_perigee( tle1); 147 | const double q2 = get_perigee( tle2); 148 | 149 | rval = ( q1 > q2 ? 1 : -1); 150 | } 151 | break; 152 | } 153 | if( sort_method >= 'A' && sort_method <= 'Z') 154 | rval = -rval; 155 | return( rval); 156 | } 157 | 158 | /* MS Windows lacks the re-entrant qsort_r; we have to use */ 159 | /* plain old non-re-entrant qsort and a global variable. */ 160 | /* BSD has it, but with the wrong order of arguments. */ 161 | #ifdef __linux 162 | #define HAVE_REENTRANT_QSORT 163 | #endif 164 | 165 | #ifdef HAVE_REENTRANT_QSORT 166 | int tle_compare_for_qsort_r( const void *a, const void *b, void *c) 167 | { 168 | return( tle_compare( (const TLE *)a, (const TLE *)b, *(char *)c)); 169 | } 170 | #else 171 | static char comparison_method; 172 | 173 | int tle_compare_for_qsort( const void *a, const void *b) 174 | { 175 | return( tle_compare( (const TLE *)a, (const TLE *)b, comparison_method)); 176 | } 177 | #endif 178 | 179 | 180 | static void error_exit( void) 181 | { 182 | printf( "'mergetle' will merge TLEs from one or more files. Optionally,\n"); 183 | printf( "the output can be sorted. For example:\n\n"); 184 | printf( "mergetle geosynch.tle molniya.tle visual.tle -se -oall.tle\n\n"); 185 | printf( "would create a file 'all.tle', containing elements from the three\n"); 186 | printf( "input .tle files, sorted by NORAD number. If a satellite appears\n"); 187 | printf( "in more than one .tle, the .tle from the first file on the command\n"); 188 | printf( "line is used. Options are:\n\n"); 189 | printf( "-sn, -sN Sort output by ascending/descending NORAD number\n"); 190 | printf( "-sc, -sC Sort output by ascending/descending COSPAR desig\n"); 191 | printf( "-sm, -sM Sort output by ascending/descending mean motion\n"); 192 | printf( "-se, -sE Sort output by ascending/descending eccentricity\n"); 193 | printf( "-sp, -sP Sort output by ascending/descending epoch\n"); 194 | printf( "-si, -sI Sort output by ascending/descending inclination\n"); 195 | printf( "-so, -sO Sort output by ascending/descending ascending node\n"); 196 | printf( "-sq Sort output by ascending perigee\n"); 197 | printf( "-o(filename) Set name of output .tle file (default is out.tle)\n"); 198 | printf( "-n Remove names from input TLEs\n"); 199 | printf( "-h Remove HTML tags from input. This allows you to extract\n"); 200 | printf( " TLEs from certain Web pages.\n"); 201 | exit( -1); 202 | } 203 | 204 | int main( const int argc, const char **argv) 205 | { 206 | TLE *tles = (TLE *)calloc( MAX_TLES, sizeof( TLE)); 207 | char *already_found = (char *)calloc( MAX_NORAD_NUMBER, sizeof( char)); 208 | int n_found = 0, i, strip_names = 0; 209 | char sort_method = 0; 210 | const char *output_filename = "out.tle"; 211 | FILE *ofile; 212 | 213 | if( argc < 2) 214 | error_exit( ); 215 | for( i = 1; i < argc; i++) 216 | if( argv[i][0] == '-') 217 | switch( argv[i][1]) 218 | { 219 | case 'o': 220 | output_filename = argv[i] + 2; 221 | break; 222 | case 's': 223 | sort_method = argv[i][2]; 224 | break; 225 | case 'n': 226 | strip_names = 1; 227 | printf( "Names will be removed in output\n"); 228 | break; 229 | case 'h': 230 | heavens_above_html_tles = 1; 231 | printf( "HTML tags will be removed from input\n"); 232 | break; 233 | default: 234 | printf( "Ignoring unknown option '%s'\n", argv[i]); 235 | break; 236 | } 237 | else 238 | { 239 | FILE *ifile = test_fopen( argv[i], "rb"); 240 | int n; 241 | 242 | n_duplicates = 0; 243 | n = load_tles_from_file( ifile, tles + n_found, already_found); 244 | printf( "%d TLEs added from %s, with %d duplicates found\n", 245 | n, argv[i], n_duplicates); 246 | n_found += n; 247 | fclose( ifile); 248 | } 249 | free( already_found); 250 | if( strip_names) 251 | for( i = 0; i < n_found; i++) 252 | tles[i].name_line[0] = '\0'; 253 | #ifndef HAVE_REENTRANT_QSORT 254 | comparison_method = sort_method; 255 | qsort( tles, n_found, sizeof( TLE), tle_compare_for_qsort); 256 | #else 257 | qsort_r( tles, n_found, sizeof( TLE), tle_compare_for_qsort_r, &sort_method); 258 | #endif 259 | ofile = test_fopen( output_filename, "wb"); 260 | for( i = 0; i < n_found; i++) 261 | show_tle( ofile, tles + i); 262 | } 263 | -------------------------------------------------------------------------------- /msvc.mak: -------------------------------------------------------------------------------- 1 | # Makefile for MSVC 2 | all: dropouts.exe fix_tles.exe line2.exe mergetle.exe obs_test.exe \ 3 | obs_tes2.exe out_comp.exe sat_eph.exe sat_id.exe \ 4 | test2.exe test_out.exe test_sat.exe tle2mpc.exe 5 | 6 | COMMON_FLAGS=-nologo -W3 -EHsc -c -FD -D_CRT_SECURE_NO_WARNINGS 7 | RM=del 8 | 9 | !ifdef BITS_32 10 | BITS=32 11 | !else 12 | BITS=64 13 | !endif 14 | 15 | CFLAGS=-MT -O1 -D "NDEBUG" $(COMMON_FLAGS) 16 | LINK=link /nologo /stack:0x8800 17 | 18 | OBJS= sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj \ 19 | basics.obj get_el.obj common.obj tle_out.obj 20 | 21 | dropouts.exe: dropouts.obj 22 | $(LINK) dropouts.obj 23 | 24 | fix_tles.exe: fix_tles.obj sat_code$(BITS).lib 25 | $(LINK) fix_tles.obj sat_code$(BITS).lib 26 | 27 | line2.exe: line2.obj observe.obj sat_code$(BITS).lib 28 | $(LINK) line2.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib 29 | 30 | mergetle.exe: mergetle.obj 31 | $(LINK) mergetle.obj 32 | 33 | obs_test.exe: obs_test.obj observe.obj sat_code$(BITS).lib 34 | $(LINK) obs_test.obj observe.obj sat_code$(BITS).lib 35 | 36 | obs_tes2.exe: obs_tes2.obj observe.obj sat_code$(BITS).lib 37 | $(LINK) obs_tes2.obj observe.obj sat_code$(BITS).lib 38 | 39 | out_comp.exe: out_comp.obj 40 | $(LINK) out_comp.obj 41 | 42 | sat_code$(BITS).lib: $(OBJS) 43 | del sat_code$(BITS).lib 44 | lib /OUT:sat_code$(BITS).lib $(OBJS) 45 | 46 | sat_id.exe: sat_id.obj sat_util.obj observe.obj sat_code$(BITS).lib 47 | $(LINK) sat_id.obj sat_util.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib 48 | 49 | sat_eph.exe: sat_eph.obj sat_util.obj observe.obj sat_code$(BITS).lib 50 | $(LINK) sat_eph.obj sat_util.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib 51 | 52 | test2.exe: test2.obj sat_code$(BITS).lib 53 | $(LINK) test2.obj sat_code$(BITS).lib 54 | 55 | test_out.exe: test_out.obj sat_code$(BITS).lib 56 | $(LINK) test_out.obj sat_code$(BITS).lib 57 | 58 | test_sat.exe: test_sat.obj sat_code$(BITS).lib 59 | $(LINK) test_sat.obj sat_code$(BITS).lib 60 | 61 | tle2mpc.exe: tle2mpc.obj observe.obj sat_code$(BITS).lib 62 | $(LINK) tle2mpc.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib 63 | 64 | .cpp.obj: 65 | cl $(CFLAGS) $< 66 | 67 | clean: 68 | del *.obj 69 | del *.exe 70 | del *.idb 71 | del sat_code$(BITS).lib 72 | 73 | install: 74 | copy norad.h ..\myincl 75 | copy sat_code$(BITS).lib ..\lib 76 | -------------------------------------------------------------------------------- /msvc_dll.mak: -------------------------------------------------------------------------------- 1 | # MSVC makefile for a DLL version 2 | # NOTE: hasn't been used or updated in years; some work required 3 | # before it would be fit for purpose. 4 | 5 | all: test2.exe test_sat.exe obs_test.exe obs_tes2.exe sat_id.exe test_out.exe sm_sat.dll out_comp.exe 6 | 7 | out_comp.exe: out_comp.cpp 8 | cl -W3 -Ox -nologo out_comp.cpp 9 | 10 | test2.exe: test2.obj sat_code.lib 11 | link test2.obj sat_code.lib 12 | 13 | test_sat.exe: test_sat.obj sat_code.lib 14 | cl -nologo test_sat.obj sat_code.lib 15 | 16 | test_out.exe: test_out.obj tle_out.obj sat_code.lib 17 | cl -nologo test_out.obj tle_out.obj sat_code.lib 18 | 19 | obs_test.exe: obs_test.obj observe.obj sat_code.lib 20 | cl -nologo obs_test.obj observe.obj sat_code.lib 21 | 22 | obs_tes2.exe: obs_tes2.obj observe.obj sat_code.lib 23 | cl -nologo obs_tes2.obj observe.obj sat_code.lib 24 | 25 | sat_id.exe: sat_id.obj sat_util.obj sat_code.lib 26 | cl -nologo sat_id.obj sat_util.obj sat_code.lib 27 | 28 | sat_code.lib: sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj \ 29 | basics.obj get_el.obj observe.obj common.obj 30 | del sat_code.lib 31 | del sat_code.dll 32 | link /DLL /IMPLIB:sat_code.lib /DEF:sat_code.def /MAP:sat_code.map \ 33 | sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj \ 34 | basics.obj get_el.obj observe.obj common.obj 35 | 36 | sm_sat.dll: sgp4.obj basics.obj get_el.obj common.obj 37 | del sm_sat.lib 38 | del sm_sat.dll 39 | link /DLL /IMPLIB:sm_sat.lib /DEF:sm_sat.def /MAP:sm_sat.map \ 40 | sgp4.obj basics.obj get_el.obj common.obj 41 | 42 | #CFLAGS=-W3 -c -LD -Ox -DRETAIN_PERTURBATION_VALUES_AT_EPOCH 43 | CFLAGS=-W3 -c -LD -Ox -nologo -D_CRT_SECURE_NO_WARNINGS 44 | 45 | .cpp.obj: 46 | cl $(CFLAGS) $< 47 | 48 | common.obj: 49 | 50 | sgp.obj: 51 | 52 | sgp4.obj: 53 | 54 | sgp8.obj: 55 | 56 | sdp4.obj: 57 | 58 | sdp8.obj: 59 | 60 | deep.obj: 61 | 62 | basics.obj: 63 | 64 | get_el.obj: 65 | 66 | observe.obj: 67 | 68 | test2.obj: 69 | 70 | test_out.obj: 71 | 72 | test_sat.obj: 73 | 74 | tle_out.obj: 75 | 76 | obs_test.obj: 77 | 78 | obs_tes2.obj: 79 | 80 | sat_id.obj: 81 | 82 | clean: 83 | del sat_code.dll 84 | del *.obj 85 | del *.exe 86 | del *.idb 87 | del sat_code.exp 88 | del sat_code.map 89 | del sat_code$(BITS).lib 90 | -------------------------------------------------------------------------------- /norad.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | /* 4 | * norad.h v. 01.beta 03/17/2001 5 | * 6 | * Header file for norad.c 7 | */ 8 | 9 | #ifndef NORAD_H 10 | #define NORAD_H 1 11 | 12 | /* #define RETAIN_PERTURBATION_VALUES_AT_EPOCH 1 */ 13 | 14 | /* Two-line-element satellite orbital data */ 15 | typedef struct 16 | { 17 | double epoch, xndt2o, xndd6o, bstar; 18 | double xincl, xnodeo, eo, omegao, xmo, xno; 19 | int norad_number, bulletin_number, revolution_number; 20 | char classification; /* "U" = unclassified; only type I've seen */ 21 | char ephemeris_type; 22 | char intl_desig[9]; 23 | } tle_t; 24 | 25 | /* NOTE: xndt2o and xndt6o are used only in the "classic" SGP, */ 26 | /* not in SxP4 or SxP8. */ 27 | /* epoch is a Julian Day, UTC */ 28 | /* xmo = mean anomaly at epoch, radians */ 29 | /* xno = mean motion at epoch, radians/minute*/ 30 | 31 | #ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH 32 | #define DEEP_ARG_T_PARAMS 87 33 | #else 34 | #define DEEP_ARG_T_PARAMS 81 35 | #endif 36 | 37 | #define N_SGP_PARAMS 11 38 | #define N_SGP4_PARAMS 30 39 | #define N_SGP8_PARAMS 25 40 | #define N_SDP4_PARAMS (10 + DEEP_ARG_T_PARAMS) 41 | #define N_SDP8_PARAMS (11 + DEEP_ARG_T_PARAMS) 42 | 43 | /* 94 = maximum possible size of the 'deep_arg_t' structure, in 8-byte units */ 44 | /* You can use the above constants to minimize the amount of memory used, 45 | but if you use the following constant, you can be assured of having 46 | enough memory for any of the five models: */ 47 | 48 | #define N_SAT_PARAMS (11 + DEEP_ARG_T_PARAMS) 49 | 50 | /* Byte 63 of the first line of a TLE contains the ephemeris type. The */ 51 | /* following five values are recommended, but it seems the non-zero */ 52 | /* values are only used internally; "published" TLEs all have type 0. */ 53 | /* However, I've had occasion to produce SGP4 TLEs for high-fliers, in */ 54 | /* cases where I couldn't get an SDP4 fit. */ 55 | 56 | #define TLE_EPHEMERIS_TYPE_DEFAULT 0 57 | #define TLE_EPHEMERIS_TYPE_SGP 1 58 | #define TLE_EPHEMERIS_TYPE_SGP4 2 59 | #define TLE_EPHEMERIS_TYPE_SDP4 3 60 | #define TLE_EPHEMERIS_TYPE_SGP8 4 61 | #define TLE_EPHEMERIS_TYPE_SDP8 5 62 | 63 | #define SXPX_DPSEC_INTEGRATION_ORDER 0 64 | #define SXPX_DUNDEE_COMPLIANCE 1 65 | #define SXPX_ZERO_PERTURBATIONS_AT_EPOCH 2 66 | 67 | 68 | /* SDP4 and SGP4 can return zero, or any of the following error/warning codes. 69 | The 'warnings' result in a mathematically reasonable value being returned, 70 | and perigee within the earth is completely reasonable for an object that's 71 | just left the earth or is about to hit it. The 'errors' mean that no 72 | reasonable position/velocity was determined. */ 73 | 74 | #define SXPX_ERR_NEARLY_PARABOLIC -1 75 | #define SXPX_ERR_NEGATIVE_MAJOR_AXIS -2 76 | #define SXPX_WARN_ORBIT_WITHIN_EARTH -3 77 | #define SXPX_WARN_PERIGEE_WITHIN_EARTH -4 78 | #define SXPX_ERR_NEGATIVE_XN -5 79 | #define SXPX_ERR_CONVERGENCE_FAIL -6 80 | 81 | /* Function prototypes */ 82 | /* norad.c */ 83 | 84 | /* The Win32 version can be compiled to make a .DLL, if the */ 85 | /* functions are declared to be of type __stdcall... _and_ the */ 86 | /* functions must be declared to be extern "C", something I */ 87 | /* overlooked and added 24 Sep 2002. The DLL_FUNC macro lets */ 88 | /* this coexist peacefully with other OSes. */ 89 | 90 | #ifdef _WIN32 91 | #define DLL_FUNC __stdcall 92 | #else 93 | #define DLL_FUNC 94 | #endif 95 | 96 | #ifdef __cplusplus 97 | extern "C" { 98 | #endif 99 | 100 | 101 | void DLL_FUNC SGP_init( double *params, const tle_t *tle); 102 | int DLL_FUNC SGP( const double tsince, const tle_t *tle, const double *params, 103 | double *pos, double *vel); 104 | 105 | void DLL_FUNC SGP4_init( double *params, const tle_t *tle); 106 | int DLL_FUNC SGP4( const double tsince, const tle_t *tle, const double *params, 107 | double *pos, double *vel); 108 | 109 | void DLL_FUNC SGP8_init( double *params, const tle_t *tle); 110 | int DLL_FUNC SGP8( const double tsince, const tle_t *tle, const double *params, 111 | double *pos, double *vel); 112 | 113 | void DLL_FUNC SDP4_init( double *params, const tle_t *tle); 114 | int DLL_FUNC SDP4( const double tsince, const tle_t *tle, const double *params, 115 | double *pos, double *vel); 116 | 117 | void DLL_FUNC SDP8_init( double *params, const tle_t *tle); 118 | int DLL_FUNC SDP8( const double tsince, const tle_t *tle, const double *params, 119 | double *pos, double *vel); 120 | 121 | int DLL_FUNC select_ephemeris( const tle_t *tle); 122 | int DLL_FUNC parse_elements( const char *line1, const char *line2, tle_t *sat); 123 | int DLL_FUNC tle_checksum( const char *buff); 124 | void DLL_FUNC write_elements_in_tle_format( char *buff, const tle_t *tle); 125 | 126 | void DLL_FUNC sxpx_set_implementation_param( const int param_index, 127 | const int new_param); 128 | void DLL_FUNC sxpx_set_dpsec_integration_step( const double new_step_size); 129 | void DLL_FUNC lunar_solar_position( const double jd, 130 | double *lunar_xyzr, double *solar_xyzr); 131 | 132 | #ifdef __cplusplus 133 | } /* end of 'extern "C"' section */ 134 | #endif 135 | 136 | /* Following are in 'dynamic.cpp', for C/C++ programs that want */ 137 | /* to load 'sat_code.dll' and use its functions at runtime. They */ 138 | /* only make sense in the Win32 world: */ 139 | #ifdef _WIN32 140 | int SXPX_init( double *params, const tle_t *tle, const int sxpx_num); 141 | int SXPX( const double tsince, const tle_t *tle, const double *params, 142 | double *pos, double *vel, const int sxpx_num); 143 | #endif 144 | #endif 145 | -------------------------------------------------------------------------------- /norad_in.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #ifndef NORAD_IN_H 4 | #define NORAD_IN_H 5 | 6 | /* Common "internal" arguments between deep-space functions; users of */ 7 | /* the satellite routines shouldn't need to bother with any of this */ 8 | 9 | typedef struct 10 | { 11 | double 12 | /* Common between SGP4 and SDP4: */ 13 | aodp, cosio, sinio, omgdot, xmdot, xnodot, xnodp, 14 | /* Used by dpinit part of Deep() */ 15 | eosq, betao, cosio2, sing, cosg, betao2, 16 | 17 | /* Used by dpsec and dpper parts of Deep() */ 18 | xll, omgadf, xnode, em, xinc, xn, t, 19 | 20 | /* 'd####' secular coeffs for 12-hour, e>.5 orbits: */ 21 | d2201, d2211, d3210, d3222, d4410, d4422, d5220, d5232, d5421, d5433, 22 | /* formerly static to Deep( ), but more logically part of this struct: */ 23 | atime, del1, del2, del3, e3, ee2, omegaq, pe, pgh, ph, pinc, pl, preep, 24 | savtsn, se2, se3, sgh2, sgh3, sgh4, sh2, sh3, si2, si3, sl2, sl3, 25 | sl4, sse, ssg, ssh, ssi, ssl, thgr, xfact, xgh2, xgh3, xgh4, xh2, 26 | xh3, xi2, xi3, xl2, xl3, xl4, xlamo, xli, xni, xnq, 27 | zmol, zmos; 28 | 29 | /* Epoch offsets, described by Rob Matson, added by BJG, */ 30 | /* then commented out; I don't think they really ought to */ 31 | /* be used... */ 32 | #ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH 33 | double pe0, pinc0, pl0, pgh0, ph0; 34 | int solar_lunar_init_flag; 35 | #endif 36 | int resonance_flag, synchronous_flag; 37 | } deep_arg_t; 38 | 39 | double FMod2p( const double x); 40 | void Deep_dpinit( const tle_t *tle, deep_arg_t *deep_arg); 41 | void Deep_dpsec( const tle_t *tle, deep_arg_t *deep_arg); 42 | void Deep_dpper( const tle_t *tle, deep_arg_t *deep_arg); 43 | 44 | int sxpx_posn_vel( const double xnode, const double a, const double e, 45 | const double cosio, const double sinio, 46 | const double xincl, const double omega, 47 | const double xl, double *pos, double *vel); 48 | 49 | typedef struct 50 | { 51 | double coef, coef1, tsi, s4, unused_a3ovk2, eta; 52 | } init_t; 53 | 54 | void sxpx_common_init( double *params, const tle_t *tle, 55 | init_t *init, deep_arg_t *deep_arg); 56 | 57 | /* Table of constant values */ 58 | #define pi 3.141592653589793238462643383279502884197 59 | #define twopi (pi*2.) 60 | #define e6a 1.0E-6 61 | #define two_thirds (2. / 3.) 62 | #define xj3 -2.53881E-6 63 | #define minus_xj3 2.53881E-6 64 | #define earth_radius_in_km 6378.135 65 | #ifndef minutes_per_day 66 | #define minutes_per_day 1440. 67 | #endif 68 | #define ae 1.0 69 | #define xj2 1.082616e-3 70 | #define ck2 (.5 * xj2 * ae * ae) 71 | 72 | /* xke^2 = earth GM, in (earth radii)^3/minutes^2. */ 73 | #ifdef OLD_CONSTANTS 74 | #define ck4 6.209887E-7 75 | #define s 1.012229 76 | #define qoms2t 1.880279E-09 77 | #define xke 7.43669161E-2 78 | #else 79 | #define xj4 (-1.65597e-6) 80 | #define ck4 (-.375 * xj4 * ae * ae * ae * ae) 81 | #define s_const (ae * (1. + 78. / earth_radius_in_km)) 82 | #define qoms2t 1.880279159015270643865e-9 83 | #define xke 0.0743669161331734132 84 | #endif 85 | 86 | #define a3ovk2 (minus_xj3/ck2*ae*ae*ae) 87 | 88 | #endif /* #ifndef NORAD_IN_H */ 89 | -------------------------------------------------------------------------------- /nu2vect.c: -------------------------------------------------------------------------------- 1 | /* Code to read in the Spektr-RG state vectors from .nu files at 2 | 3 | ftp://ftp.kiam1.rssi.ru/pub/gps/spectr-rg/nu/ 4 | 5 | and output into a form ingestible by Find_Orb. (Note that these 6 | haven't been updated for a while; we've been tracking Spektr-RG 7 | solely through observed astrometry.) Input lines give 8 | 9 | 0.000 (always zero, means J2000) 10 | 1 (loop number; ignore) 11 | 2.021030100000E+7 ( = 20210301 = 2021 Mar 01) 12 | 1.649335000000E+05 ( = 16:49:33.5 Moscow time (three hours ahead of UTC)) 13 | -1.190093387974E+03 (x-coord, geocentric, equatorial J2000, in thousands of km) 14 | 9.733125144680E+02 (y-coord, same system) 15 | 4.956578219661E+02 (z-coord, same system) 16 | -7.639675611173E-02 (x-velocity, in km/s) 17 | -6.678070845750E-02 (y-velocity) 18 | -2.372125806637E-01 (z-velocity) 19 | 0.000000000000E+00 (ballistic coefficient) 20 | 1.246012245177E-05 (solar radiation coefficient, unitless) 21 | 22 | Note that the file name gives the UTC, and the time given within 23 | the file is three hours ahead of that. The positions match those 24 | determined by optical astrometry to within a few kilometers. 25 | 26 | The above solar radiation coefficient means that the sun's gravity 27 | is counteracted by a force 1.24601e-5 times as great. It corresponds 28 | approximately to an area/mass ratio of 0.015 m^2/kg, which is 29 | quite close to what we've been getting from the optical astrometry 30 | orbit solution. 31 | 32 | Compile with 33 | 34 | gcc -Wextra -Wall -O3 -pedantic nu2vect.c -I../include -L../lib -o nu2vect -llunar 35 | 36 | Python code to convert .nu files to STK ephemeris files is at 37 | 38 | https://github.com/Satsir/STK/blob/main/nuToEph.py */ 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include "stringex.h" 45 | 46 | int main( const int argc, const char **argv) 47 | { 48 | FILE *ifile = (argc == 2 ? fopen( argv[1], "rb") : NULL); 49 | char buff[90]; 50 | char command[200]; 51 | int i; 52 | 53 | if( !ifile) 54 | fprintf( stderr, "See comments at start of 'nu2vect.c' for usage\n"); 55 | assert( ifile); 56 | for( i = 0; i < 10; i++) 57 | if( fgets( buff, sizeof( buff), ifile)) 58 | { 59 | const double ival = atof( buff); 60 | 61 | switch( i) 62 | { 63 | case 2: 64 | { 65 | const int day = (int)ival; 66 | 67 | snprintf_err( buff, sizeof( buff), "%04d-%02d-%02d", 68 | day / 10000, (day / 100) % 100, day % 100); 69 | printf( "Date : %s\n", buff); 70 | snprintf_err( command, sizeof( command), 71 | "find_orb \"-oSpektr-RG = 2019-040A = NORAD 44432\" -v%sT", buff); 72 | } 73 | break; 74 | case 3: 75 | { 76 | const int millisec = (int)( ival * 1000.); 77 | 78 | snprintf_err( buff, sizeof( buff), "%02d:%02d:%02d.%03d", 79 | millisec / 10000000, 80 | (millisec / 100000) % 100, 81 | (millisec / 1000) % 100, millisec % 100); 82 | printf( "Time : %s\n", buff); 83 | strlcat_error( command, buff); 84 | strlcat_error( command, "-3h"); /* correction for Moscow time */ 85 | } 86 | break; 87 | case 4: case 5: case 6: 88 | printf( " Posn %f km\n", ival * 1000.); 89 | snprintf_append( command, sizeof( command), ",%.2f", ival * 1000.); 90 | break; 91 | case 7: case 8: case 9: 92 | printf( " Vel %f km/s\n", ival); 93 | snprintf_append( command, sizeof( command), ",%.7f", ival); 94 | break; 95 | break; 96 | } 97 | } 98 | fclose( ifile); 99 | printf( "%s,eq,geo,km,s\n", command); 100 | return( 0); 101 | } 102 | -------------------------------------------------------------------------------- /nu_readme.txt: -------------------------------------------------------------------------------- 1 | (Translation/additions to ftp://ftp.kiam1.rssi.ru/pub/gps/spectr-rg/nu/00_read.me . 2 | This also applies to the files for Spektr-R in the 3 | ftp://ftp.kiam1.rssi.ru/pub/gps/spectr-r/nu/ directory.) 4 | 5 | Format of .nu files : 6 | 7 | 8 | Line 1: 0.000000000000E+00 // 0 = coordinates are in J2000, geocentric, equatorial 9 | Line 2: 1.000000000000E+00 // orbit number (always zero for Spektr-RG) 10 | Line 3: 2.011111500000E+07 // Epoch date, YYYYMMDD (example is 2011 11 15) 11 | Line 4: 1.492674329226E+04 // Epoch time, HH:MM:SS (example is 01:49:26.74329226) 12 | Line 5: 5.174588077311E+00 // x-coordinate, in thousands of kilometers 13 | Line 6: -2.277098498781E+00 // y-coordinate 14 | Line 7: -3.461034587339E+00 // z-coordinate 15 | Line 8: 4.762109598043E+00 // x-velocity, in kilometers/second 16 | Line 9: 4.109503042673E+00 // y-velocity 17 | Line 10: 4.560902129453E+00 // z-velocity 18 | Line 11: 3.000000000000E-02 // Ballistic coefficient, m^3/seconds^2/kilograms (?) 19 | Line 12: 1.248000000000E-05 // Solar radiation coefficient, unitless 20 | 21 | For Spektr-R, the orbit number is non-zero and gives the number 22 | of completed orbits. It is not defined for Spektr-RG. 23 | 24 | Note that the file name gives the epoch in UTC. The epoch given 25 | within the file is three hours ahead of that (Moscow time). The 26 | positions match those determined by optical astrometry to within about 27 | 20 kilometers. It is possible that the epoch is in TT (plus three 28 | hours); the difference is below what I can detect. 29 | 30 | The above solar radiation coefficient means that the sun's gravity 31 | is counteracted by a force 1.248e-5 times as great. It corresponds 32 | approximately to an area/mass ratio of 0.015 m^2/kg, which is 33 | quite close to what we've been getting from the optical astrometry 34 | orbit solution. 35 | -------------------------------------------------------------------------------- /obs_tes2.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | /* 4 | obs_tes2.cpp 12 December 2002 5 | 6 | (Revised slightly December 2012 to fix compiler warning errors.) 7 | 8 | An example 'main' function illustrating how to find which satellite(s) 9 | are within a given radius of a given RA/dec, as seen from a given 10 | point. The code reads in a TLE file (name provided as the first 11 | command-line argument). Details of the observer position, search 12 | radius, date/time, and RA/dec are provided on the command line. 13 | For example: 14 | 15 | obs_tes2 alldat.tle -l44.01,-69.9,10 -p90,30 -j2452623.5 -r10 16 | 17 | would hunt through the TLE element file 'alldat.tle' for satellites 18 | visible from latitude +44.01, longitude -69.9, altitude 10 metres; 19 | at RA=90 degrees (6h), dec=+30; on JD 2452623.5 (UTC); within a 20 | ten-degree search radius. (All of these are the default values.) 21 | The output looks like this: 22 | 23 | NORAD Int'l RA (J2000) dec Delta Radius PA Speed 24 | 08593U 74089DG 88.7235 22.9622 2293.0 7.15 225 10.75 25 | 15830U 85049D 82.5051 34.9711 32143.6 8.99 34 0.24 26 | 17642U 81053LQ 88.1471 32.6585 1428.5 3.24 213 17.60 27 | 21833U 91088A 80.6400 27.9649 36216.6 9.58 87 0.17 28 | 29 | ...with 'delta'=distance to satellite in km, 'radius'=angular 30 | distance in degrees from the search point, 'PA' = position angle 31 | of motion, 'Speed' = apparent angular rate of motion in 32 | arcminutes/second (or degrees/minute). */ 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "norad.h" 39 | #include "observe.h" 40 | 41 | #define PI 3.141592653589793238462643383279 42 | #define TIME_EPSILON (1./86400.) 43 | 44 | int main( const int argc, const char **argv) 45 | { 46 | const char *tle_file_name = ((argc == 1) ? "alldat.tle" : argv[1]); 47 | FILE *ifile = fopen( tle_file_name, "rb"); 48 | char line1[100], line2[100]; 49 | double lat = 44.01, lon = -69.9, ht_in_meters = 10.; 50 | double jd = 2452623.5; /* 15 Dec 2002 0h UT */ 51 | double search_radius = 10.; /* default to ten-degree search */ 52 | double target_ra = 90., target_dec = 30.; /* default search is at RA=6h, dec=+30 */ 53 | double rho_sin_phi, rho_cos_phi, observer_loc[3], observer_loc2[3]; 54 | int i, header_line_shown = 0; 55 | 56 | if( !ifile) 57 | { 58 | printf( "Couldn't open input file %s\n", tle_file_name); 59 | exit( -1); 60 | } 61 | 62 | for( i = 1; i < argc; i++) 63 | if( argv[i][0] == '-') 64 | switch( argv[i][1]) 65 | { 66 | case 'l': 67 | sscanf( argv[i] + 2, "%lf,%lf,%lf", &lat, &lon, &ht_in_meters); 68 | break; 69 | case 'p': 70 | sscanf( argv[i] + 2, "%lf,%lf", &target_ra, &target_dec); 71 | break; 72 | case 'j': 73 | jd = atof( argv[i] + 2); 74 | break; 75 | case 'r': 76 | search_radius = atof( argv[i] + 2); 77 | break; 78 | default: 79 | printf( "Unrecognized command-line option '%s'\n", argv[i]); 80 | exit( -2); 81 | break; 82 | } 83 | 84 | /* Figure out where the observer _really_ is, in Cartesian */ 85 | /* coordinates of date: */ 86 | earth_lat_alt_to_parallax( lat * PI / 180., ht_in_meters, &rho_cos_phi, 87 | &rho_sin_phi); 88 | observer_cartesian_coords( jd, 89 | lon * PI / 180., rho_cos_phi, rho_sin_phi, observer_loc); 90 | observer_cartesian_coords( jd + TIME_EPSILON, 91 | lon * PI / 180., rho_cos_phi, rho_sin_phi, observer_loc2); 92 | target_ra *= PI / 180.; 93 | target_dec *= PI / 180.; 94 | 95 | if( fgets( line1, sizeof( line1), ifile)) 96 | while( fgets( line2, sizeof( line2), ifile)) 97 | { 98 | tle_t tle; /* Structure for two-line elements set for satellite */ 99 | 100 | if( !parse_elements( line1, line2, &tle)) /* hey! we got a TLE! */ 101 | { 102 | int is_deep = select_ephemeris( &tle); 103 | double sat_params[N_SAT_PARAMS], radius, d_ra, d_dec; 104 | double ra, dec, dist_to_satellite, t_since; 105 | double pos[3]; /* Satellite position vector */ 106 | double unused_delta2; 107 | 108 | t_since = (jd - tle.epoch) * 1440.; 109 | if( is_deep) 110 | { 111 | SDP4_init( sat_params, &tle); 112 | SDP4( t_since, &tle, sat_params, pos, NULL); 113 | } 114 | else 115 | { 116 | SGP4_init( sat_params, &tle); 117 | SGP4( t_since, &tle, sat_params, pos, NULL); 118 | } 119 | get_satellite_ra_dec_delta( observer_loc, pos, 120 | &ra, &dec, &dist_to_satellite); 121 | epoch_of_date_to_j2000( jd, &ra, &dec); 122 | d_ra = (ra - target_ra + PI * 4.); 123 | while( d_ra > PI) 124 | d_ra -= PI + PI; 125 | d_dec = dec - target_dec; 126 | radius = sqrt( d_ra * d_ra + d_dec * d_dec) * 180. / PI; 127 | if( radius < search_radius) /* good enough for us! */ 128 | { 129 | double speed, posn_ang_of_motion; 130 | 131 | line1[16] = '\0'; 132 | 133 | if( !header_line_shown) 134 | { 135 | printf( "NORAD Int'l RA (J2000) dec Delta Radius PA Speed\n"); 136 | header_line_shown = 1; 137 | } 138 | /* Compute position one second later, so we */ 139 | /* can show speed/PA of motion: */ 140 | t_since += TIME_EPSILON * 1440.; 141 | if( is_deep) 142 | SDP4( t_since, &tle, sat_params, pos, NULL); 143 | else 144 | SGP4( t_since, &tle, sat_params, pos, NULL); 145 | get_satellite_ra_dec_delta( observer_loc2, pos, 146 | &d_ra, &d_dec, &unused_delta2); 147 | epoch_of_date_to_j2000( jd, &d_ra, &d_dec); 148 | d_ra -= ra; 149 | d_dec -= dec; 150 | while( d_ra > PI) 151 | d_ra -= PI + PI; 152 | while( d_ra < -PI) 153 | d_ra += PI + PI; 154 | d_ra *= cos( dec); 155 | posn_ang_of_motion = atan2( d_ra, d_dec); 156 | if( posn_ang_of_motion < 0.) 157 | posn_ang_of_motion += PI + PI; 158 | speed = sqrt( d_ra * d_ra + d_dec * d_dec) * 180. / PI; 159 | /* Put RA into 0 to 2pi range: */ 160 | ra = fmod( ra + PI * 10., PI + PI); 161 | printf( "%s %8.4f %8.4f %8.1f %5.2f %3d %5.2f\n", 162 | line1 + 2, ra * 180. / PI, dec * 180. / PI, 163 | dist_to_satellite, radius, 164 | (int)(posn_ang_of_motion * 180 / PI), 165 | speed * 60.); 166 | /* "Speed" is displayed in arcminutes/second, 167 | or in degrees/minute */ 168 | } 169 | } 170 | strcpy( line1, line2); 171 | } 172 | fclose( ifile); 173 | return( 0); 174 | } /* End of main() */ 175 | 176 | -------------------------------------------------------------------------------- /obs_test.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | /* 4 | obs_test.cpp 23 September 2002 5 | 6 | (Revised slightly December 2012 to fix compiler warning errors.) 7 | 8 | An example 'main' function illustrating how to get topocentric 9 | ephemerides for artificial satellites, using the basic satellite 10 | code plus the add-on topocentric functions. The code reads the 11 | file 'obs_test.txt', getting commands setting the observer lat/lon 12 | and altitude and time of observation. When it gets a command 13 | setting a particular JD, it computes the topocentric RA/dec/dist 14 | and prints them out. 15 | 16 | At present, 'obs_test.txt' sets up a lat/lon/height in Bowdoinham, 17 | Maine, corporate headquarters of Project Pluto, and computes the 18 | position of one low-orbit satellite (ISS) and one high-orbit satellite 19 | (Cosmos 1966 rocket booster). You should get : 20 | 21 | Near-Earth type Ephemeris (SGP4) selected: 22 | Object 25544U 98067A, as seen from lat 44.01000 lon -69.90000, JD 2452541.50000 23 | RA 350.1615 (J2000) dec -24.0241 dist 1867.97481 km 24 | Deep-Space type Ephemeris (SDP4) selected: 25 | Object 19448U 88076D, as seen from lat 44.01000 lon -69.90000, JD 2452541.50000 26 | RA 3.5743 (J2000) dec 30.4293 dist 32114.83370 km 27 | */ 28 | 29 | 30 | #include 31 | #include 32 | #include 33 | #include "norad.h" 34 | #include "observe.h" 35 | 36 | #define PI 3.141592653589793238462643383279 37 | 38 | int main( const int argc, const char **argv) 39 | { 40 | FILE *ifile = fopen( (argc == 1) ? "obs_test.txt" : argv[1], "rb"); 41 | tle_t tle; /* Pointer to two-line elements set for satellite */ 42 | char line1[100], line2[100]; 43 | double lat = 0., lon = 0., ht_in_meters = 0., jd = 0.; 44 | int ephem = 1; /* default to SGP4 */ 45 | 46 | if( !ifile) 47 | { 48 | printf( "Couldn't open input OBS_TEST.TXT file\n"); 49 | exit( -1); 50 | } 51 | if( fgets( line1, sizeof( line1), ifile)) 52 | while( fgets( line2, sizeof( line2), ifile)) 53 | { 54 | int err_val; 55 | 56 | if( !memcmp( line2, "Ephem ", 6)) 57 | ephem = (line2[6] - '0'); 58 | else if( !memcmp( line2, "JD ", 3)) 59 | jd = atof( line2 + 3); 60 | else if( !memcmp( line2, "ht ", 3)) 61 | ht_in_meters = atof( line2 + 3); 62 | else if( !memcmp( line2, "lat ", 4)) 63 | lat = atof( line2 + 4) * PI / 180.; /* cvt degrees to radians */ 64 | else if( !memcmp( line2, "lon ", 4)) 65 | lon = atof( line2 + 4) * PI / 180.; 66 | else if( (err_val = parse_elements( line1, line2, &tle)) >= 0) 67 | { /* hey! we got a TLE! */ 68 | int is_deep = select_ephemeris( &tle); 69 | const char *ephem_names[5] = { "SGP ", "SGP4", "SGP8", "SDP4", "SDP8" }; 70 | double sat_params[N_SAT_PARAMS], observer_loc[3]; 71 | double rho_sin_phi, rho_cos_phi; 72 | double ra, dec, dist_to_satellite, t_since; 73 | double pos[3]; /* Satellite position vector */ 74 | 75 | if( err_val) 76 | printf( "WARNING: TLE parsing error %d\n", err_val); 77 | earth_lat_alt_to_parallax( lat, ht_in_meters, &rho_cos_phi, 78 | &rho_sin_phi); 79 | observer_cartesian_coords( jd, lon, rho_cos_phi, rho_sin_phi, 80 | observer_loc); 81 | if( is_deep && (ephem == 1 || ephem == 2)) 82 | ephem += 2; /* switch to an SDx */ 83 | if( !is_deep && (ephem == 3 || ephem == 4)) 84 | ephem -= 2; /* switch to an SGx */ 85 | if( is_deep) 86 | printf("Deep-Space type Ephemeris (%s) selected:\n", 87 | ephem_names[ephem]); 88 | else 89 | printf("Near-Earth type Ephemeris (%s) selected:\n", 90 | ephem_names[ephem]); 91 | 92 | /* Calling of NORAD routines */ 93 | /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8) */ 94 | /* will be called in turn with the appropriate TLE set */ 95 | t_since = (jd - tle.epoch) * 1440.; 96 | switch( ephem) 97 | { 98 | case 0: 99 | SGP_init( sat_params, &tle); 100 | err_val = SGP( t_since, &tle, sat_params, pos, NULL); 101 | break; 102 | case 1: 103 | SGP4_init( sat_params, &tle); 104 | err_val = SGP4( t_since, &tle, sat_params, pos, NULL); 105 | break; 106 | case 2: 107 | SGP8_init( sat_params, &tle); 108 | err_val = SGP8( t_since, &tle, sat_params, pos, NULL); 109 | break; 110 | case 3: 111 | SDP4_init( sat_params, &tle); 112 | err_val = SDP4( t_since, &tle, sat_params, pos, NULL); 113 | break; 114 | case 4: 115 | SDP8_init( sat_params, &tle); 116 | err_val = SDP8( t_since, &tle, sat_params, pos, NULL); 117 | break; 118 | default: 119 | printf( "? How did we get here? ephem = %d\n", ephem); 120 | err_val = 0; 121 | break; 122 | } 123 | if( err_val) 124 | printf( "Ephemeris error %d\n", err_val); 125 | line1[15] = '\0'; 126 | printf( "Object %s, as seen from lat %.5f lon %.5f, JD %.5f\n", 127 | line1 + 2, lat * 180. / PI, lon * 180. / PI, jd); 128 | get_satellite_ra_dec_delta( observer_loc, pos, 129 | &ra, &dec, &dist_to_satellite); 130 | epoch_of_date_to_j2000( jd, &ra, &dec); 131 | printf( "RA %.4f (J2000) dec %.4f dist %.5f km\n", 132 | ra * 180. / PI, dec * 180. / PI, dist_to_satellite); 133 | } 134 | strcpy( line1, line2); 135 | } 136 | fclose( ifile); 137 | return( 0); 138 | } /* End of main() */ 139 | 140 | -------------------------------------------------------------------------------- /obs_test.txt: -------------------------------------------------------------------------------- 1 | # Input file for 'obs_test' 2 | # Set up desired lat/lon/ht in meters/JD, specify a TLE, and 3 | # the topocentric RA/dec/distance will be shown. 4 | lat 44.01 5 | lon -69.9 6 | # Western longitudes are negative, eastern positive. The above 7 | # lat/lon corresponds to my location in Bowdoinham, Maine, in the 8 | # northeastern United States. 9 | ht 100 10 | JD 2452541.5 /* 24 Sep 2002 0h UT */ 11 | ISS 12 | 1 25544U 98067A 02256.70033192 .00045618 00000-0 57184-3 0 1499 13 | 2 25544 51.6396 328.6851 0018421 253.2171 244.7656 15.59086742217834 14 | 15 | # Above should give RA = 350.1615 deg, dec = -24.0241, dist = 1867.97542 km 16 | 17 | # Now compute a second, higher satellite for the same place/time: 18 | Cosmos 1966 Rk 19 | 1 19448U 88076D 02255.52918163 -.00000002 00000-0 10000-3 0 4873 20 | 2 19448 65.7943 338.1906 7142558 193.4853 125.7046 2.04085818104610 21 | 22 | # Above should give RA = 3.5743, dec = 30.4293, dist = 32114.83063 km 23 | -------------------------------------------------------------------------------- /observe.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #include 4 | #include "observe.h" 5 | 6 | /* Assorted functions useful in conjunction with the satellite code 7 | library for determining where the _observer_, as well as the _target_, 8 | happens to be. Combine the two positions, and you can get the 9 | distance/RA/dec of the target as seen by the observer. */ 10 | 11 | #define PI 3.141592653589793238462643383279 12 | #define EARTH_MAJOR_AXIS 6378140. 13 | #define EARTH_MINOR_AXIS 6356755. 14 | #define EARTH_AXIS_RATIO (EARTH_MINOR_AXIS / EARTH_MAJOR_AXIS) 15 | 16 | /* function for Greenwich sidereal time, ripped from 'deep.cpp' */ 17 | 18 | static inline double ThetaG( double jd) 19 | { 20 | /* Reference: The 1992 Astronomical Almanac, page B6. */ 21 | const double omega_E = 1.00273790934; 22 | /* Earth rotations per sidereal day (non-constant) */ 23 | const double UT = fmod( jd + .5, 1.); 24 | const double seconds_per_day = 86400.; 25 | const double jd_2000 = 2451545.0; /* 1.5 Jan 2000 = JD 2451545. */ 26 | double t_cen, GMST, rval; 27 | 28 | t_cen = (jd - UT - jd_2000) / 36525.; 29 | GMST = 24110.54841 + t_cen * (8640184.812866 + t_cen * 30 | (0.093104 - t_cen * 6.2E-6)); 31 | GMST = fmod( GMST + seconds_per_day * omega_E * UT, seconds_per_day); 32 | if( GMST < 0.) 33 | GMST += seconds_per_day; 34 | rval = 2. * PI * GMST / seconds_per_day; 35 | 36 | return( rval); 37 | } /*Function thetag*/ 38 | 39 | void DLL_FUNC observer_cartesian_coords( const double jd, const double lon, 40 | const double rho_cos_phi, const double rho_sin_phi, 41 | double *vect) 42 | { 43 | const double angle = lon + ThetaG( jd); 44 | 45 | *vect++ = cos( angle) * rho_cos_phi * EARTH_MAJOR_AXIS / 1000.; 46 | *vect++ = sin( angle) * rho_cos_phi * EARTH_MAJOR_AXIS / 1000.; 47 | *vect++ = rho_sin_phi * EARTH_MAJOR_AXIS / 1000.; 48 | } 49 | 50 | void DLL_FUNC earth_lat_alt_to_parallax( const double lat, 51 | const double ht_in_meters, 52 | double *rho_cos_phi, double *rho_sin_phi) 53 | { 54 | const double u = atan( sin( lat) * EARTH_AXIS_RATIO / cos( lat)); 55 | 56 | *rho_sin_phi = EARTH_AXIS_RATIO * sin( u) + 57 | (ht_in_meters / EARTH_MAJOR_AXIS) * sin( lat); 58 | *rho_cos_phi = cos( u) + (ht_in_meters / EARTH_MAJOR_AXIS) * cos( lat); 59 | } 60 | 61 | void DLL_FUNC get_satellite_ra_dec_delta( const double *observer_loc, 62 | const double *satellite_loc, double *ra, 63 | double *dec, double *delta) 64 | { 65 | double vect[3], dist2 = 0.; 66 | int i; 67 | 68 | for( i = 0; i < 3; i++) 69 | { 70 | vect[i] = satellite_loc[i] - observer_loc[i]; 71 | dist2 += vect[i] * vect[i]; 72 | } 73 | *delta = sqrt( dist2); 74 | *ra = atan2( vect[1], vect[0]); 75 | if( *ra < 0.) 76 | *ra += PI + PI; 77 | *dec = asin( vect[2] / *delta); 78 | } 79 | 80 | /* Formulae from Meeus' _Astronomical Algorithms_ for approximate precession. 81 | More than accurate enough for our purposes. */ 82 | 83 | static void precess( const double t_centuries, double *ra, double *dec) 84 | { 85 | const double m = (3.07496 + .00186 * t_centuries / 2.) * (PI / 180.) / 240.; 86 | const double n = (1.33621 - .00057 * t_centuries / 2.) * (PI / 180.) / 240.; 87 | const double ra_rate = m + n * sin( *ra) * tan( *dec); 88 | const double dec_rate = n * cos( *ra); 89 | 90 | *ra -= t_centuries * ra_rate * 100.; 91 | *dec -= t_centuries * dec_rate * 100.; 92 | } 93 | 94 | void DLL_FUNC epoch_of_date_to_j2000( const double jd, double *ra, double *dec) 95 | { 96 | const double t_centuries = (jd - 2451545.) / 36525.; 97 | 98 | precess( t_centuries, ra, dec); 99 | } 100 | 101 | void DLL_FUNC j2000_to_epoch_of_date( const double jd, double *ra, double *dec) 102 | { 103 | const double t_centuries = (jd - 2451545.) / 36525.; 104 | 105 | precess( -t_centuries, ra, dec); 106 | } 107 | -------------------------------------------------------------------------------- /observe.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #ifdef _WIN32 4 | #define DLL_FUNC __stdcall 5 | #else 6 | #define DLL_FUNC 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | void DLL_FUNC earth_lat_alt_to_parallax( const double lat, 14 | const double ht_in_meters, 15 | double *rho_cos_phi, double *rho_sin_phi); 16 | void DLL_FUNC observer_cartesian_coords( const double jd, const double lon, 17 | const double rho_cos_phi, const double rho_sin_phi, 18 | double *vect); 19 | void DLL_FUNC get_satellite_ra_dec_delta( const double *observer_loc, 20 | const double *satellite_loc, double *ra, 21 | double *dec, double *delta); 22 | void DLL_FUNC epoch_of_date_to_j2000( const double jd, double *ra, double *dec); 23 | void DLL_FUNC j2000_to_epoch_of_date( const double jd, double *ra, double *dec); 24 | 25 | #ifdef __cplusplus 26 | } /* end of 'extern "C"' section */ 27 | #endif 28 | -------------------------------------------------------------------------------- /out_comp.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static int is_position_line( const char *buff) 9 | { 10 | int rval = 0; 11 | 12 | if( strlen( buff) > 73 && buff[73] < ' ' && 13 | buff[29] == '.' && buff[46] == '.' && buff[63] == '.') 14 | rval = 1; 15 | return( rval); 16 | } 17 | 18 | int main( const int argc, const char **argv) 19 | { 20 | FILE *ifile1 = fopen( argv[1], "rb"); 21 | FILE *ifile2 = fopen( argv[2], "rb"); 22 | char buff1[80], buff2[80]; 23 | int line = 0, n_valid = 0, worst_line = -1; 24 | double max_diff2 = 0.; 25 | 26 | if( !ifile1) 27 | printf( "%s not opened\n", argv[1]); 28 | if( !ifile2) 29 | printf( "%s not opened\n", argv[2]); 30 | if( argc < 3 || !ifile1 || !ifile2) 31 | { 32 | printf( "out_comp needs two files of output from test_sat to compare.\n"); 33 | exit( -1); 34 | } 35 | 36 | while( fgets( buff1, sizeof( buff1), ifile1) && 37 | fgets( buff2, sizeof( buff2), ifile2)) 38 | { 39 | line++; 40 | if( is_position_line( buff1) && is_position_line( buff2)) 41 | { 42 | double diff2 = 0., delta; 43 | int i; 44 | 45 | n_valid++; 46 | for( i = 22; i < 60; i += 17) 47 | { 48 | delta = atof( buff1 + i) - atof( buff2 + i); 49 | diff2 += delta * delta; 50 | } 51 | if( diff2 > max_diff2) 52 | { 53 | max_diff2 = diff2; 54 | worst_line = line; 55 | } 56 | } 57 | } 58 | fclose( ifile1); 59 | fclose( ifile2); 60 | printf( "%d lines read in; %d had positions\n", line, n_valid); 61 | printf( "Max difference: %.8f km at line %d\n", 62 | sqrt( max_diff2), worst_line); 63 | 64 | return( 0); 65 | } 66 | -------------------------------------------------------------------------------- /sat_code.def: -------------------------------------------------------------------------------- 1 | LIBRARY sat_code 2 | EXPORTS 3 | SGP_init @1 4 | SGP4_init @2 5 | SGP8_init @3 6 | SDP4_init @4 7 | SDP8_init @5 8 | SGP @6 9 | SGP4 @7 10 | SGP8 @8 11 | SDP4 @9 12 | SDP8 @10 13 | select_ephemeris @11 14 | parse_elements @12 15 | observer_cartesian_coords @14 16 | get_satellite_ra_dec_delta @15 17 | epoch_of_date_to_j2000 @16 18 | tle_checksum @17 19 | sxpx_set_implementation_param @18 20 | sxpx_set_dpsec_integration_step @19 21 | sxpx_library_version @20 22 | j2000_to_epoch_of_date @21 23 | -------------------------------------------------------------------------------- /sat_id.txt: -------------------------------------------------------------------------------- 1 | -a(date) : only consider observations after (date) 2 | -b(date) : only consider observations before (date) 3 | -c : check all TLEs 4 | -l(num) : set "lookahead" warning on expiring TLEs (default=7 days) 5 | -m(num) : ignore TLEs in orbits lower than (num) revs/day (default=6) 6 | -n(num) : only look for NORAD ID (num) 7 | -o(filename) : output astrometry with IDs added 8 | -r(num) : set tolerance for computed-observed dist 9 | -t(filename) : set input TLE file name 10 | -u : show a summary of results 11 | -y(num) : set tolerance for apparent motion mismatch 12 | -z(num) : ignore objects slower than (num) arcmin/sec 13 | 14 | -a(date) tells Sat_ID to ignore observations from after a certain 15 | date; -b(date) tells it to ignore observations from _before_ a 16 | certain time. The date format is flexible, but something like 17 | "-a2021Jan13" is a good choice (four-digit year and month names 18 | mean there's no confusion about what's the date, what's the month, 19 | and what's the year... dates such as "11/09/08" can lead to madness.) 20 | 21 | For example, I might check my large, multi-year NEOCP archives 22 | for just the objects found in late January 2021 by running 23 | 24 | sat_id neocp.old -a2021jan15 -b2021feb1 25 | 26 | -c is basically for debugging purposes. Normally, Sat_ID gets its 27 | idea of which TLEs to check from the file 'tle_list.txt', which gives 28 | it a long list of files. And normally, it'll ignore most of them; 29 | give it some observations from 2021, and it's bright enough to realize 30 | that the TLEs for 2020 can be skipped. -c causes Sat_ID to check those 31 | files anyway, which sometimes lets me see my blunders (files that don't 32 | exist or are corrupted or don't actually contain TLEs.) 33 | 34 | -l sets a "lookahead" time for expiring TLEs. I can usually only 35 | compute TLEs for just so far into the future. If they're about to run 36 | out for a particular object in (by default) a week, you get a warning 37 | message to that effect. Except that on my own machine, I run it with 38 | -l10, so I'll know three days ahead of everybody else and can post 39 | updated TLEs before they even get a warning. 40 | 41 | On my own machine, though, I run it with -l10. That way, I'll know 42 | about expiring TLEs three days ahead of everybody else and can post 43 | updated TLEs before anybody else even sees a warning. At least, that's 44 | the theory... once in a while, I've been on vacation or something and 45 | people have seen those errors. 46 | 47 | -m(num) tells Sat_ID to neglect low-earth orbiters : anything making 48 | more than (num) revolutions around the earth per day, with (num) 49 | defaulting to 6 (i.e., Sat_ID won't identify objects in orbits lower 50 | than four hours). 51 | 52 | -n(num) restricts output to the specified NORAD five-digit number. 53 | Handy when you're just wondering when/where object X got caught. 54 | 55 | -o(filename) causes the input astrometry to be written to the 56 | output file with COM lines specifying to what the objects were matched. 57 | The COM lines, just ahead of the first observation for each object, 58 | look like 59 | 60 | COM 44432U = 2019-040A 61 | COM 37175U = 2010-050B 62 | 63 | The above lines are a convenience for Find_Orb. The first would tell 64 | it than the next object it finds is actually NORAD 44432 = 2019-040A 65 | = Spektr-RG; it would "remap" the designation of the next observation 66 | it found to that. The second would do the same thing, except for 67 | NORAD 37175 = 2010-050B = Chang'e 2 booster. This lets me avoid 68 | having to alter the original IDs in the astrometry, but means that 69 | I want to compute an orbit for 2010-050B, I'll get the data for it 70 | despite different designations being used. 71 | 72 | -r(num) says that the observed position of an object and the computed 73 | position from a TLE are considered to be 'matching' if the positions are 74 | within (num) degrees. Defaults to four degrees, which is probably too 75 | large... but see above comments : even with the very loose cutoffs, we 76 | rarely falsely ID a rock as an artsat. 77 | 78 | -t(filename) resets the input TLE filename. This defaults to 79 | ~/tles/tle_list.txt. But let's say I'm wondering what Space-Track can 80 | identify without my help. I might then set -t all_tle.txt, where 81 | 'all_tle.txt' is the master set of Space-Track TLEs. Or I may have 82 | created some batch of TLEs for a new object, and I'll run the MPC's 83 | ITF file or my accumulated NEOCP observations against it to see if I 84 | can spot any other finds of the new object. 85 | 86 | -u causes Sat_ID to emit a "summary" at the end, listing the 87 | objects it found in the input file and any matches. 88 | 89 | -y(num) says that the observed _motion_ of an object and the computed 90 | _motion_ from a TLE are considered to be a match if the motion is within 91 | 20 arcseconds. That is to say, if the observations say the object 92 | moved 1300 arcseconds in RA between first and last observation, and 93 | the TLEs compute a motion of 1319 arcsec over that time, it is (just 94 | barely) a match. 95 | 96 | 20 arcseconds may seem like a lot, and I used to have it set much 97 | lower than that. The problem is that if timing is off slightly, or the 98 | object fades in and out a bit, you can easily have more of a motion 99 | mismatch than you'd expect. And in general, setting this 'loose' 100 | tolerance only rarely results in objects being falsely identified as 101 | artsats. 102 | 103 | -z(speed) sets a lower limit on speed. By default, anything 104 | moving slower than 0.001'/sec is simply tossed out as too slow to 105 | possibly be an artsat; that would be -z.001. As with the -y option 106 | described above, this default is probably excessively low (could result 107 | in mistakenly identifying some rocks as artsats). But -- as with that 108 | option -- it appears to be unusual for such false detections to occur. 109 | I originally set both limts them to be tighter, but I found that Sat_ID 110 | would occasionally fail to identify real artsats. Generally speaking, 111 | it's hard for a rock to move so fast, and in the same direction and 112 | part of the sky, as an artsat that it would fool you. 113 | 114 | Note that this comes from the standpoint of a guy fed a steady 115 | diet of asteroid data, trying to filter out the occasional artsat. 116 | If you are deliberately targeting artsats and only rarely seeing 117 | rocks, you could set a still lower speed limit with -z, and a higher 118 | limit on observed motion (-y), _and_ a higher position mismatch 119 | tolerance (-r), and not get a flood of rocks misidentified as 120 | artsats. In fact, I do set looser limits when examining the NEOCP; 121 | I get some rocks misidentified as artsats as a result, which I then 122 | am usually able to rubbish. But those looser limits also allow me to 123 | occasionally say "this really is an artsat; it's just wandered 124 | further than expected from where the TLEs expected it to be." 125 | -------------------------------------------------------------------------------- /sat_id2.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #ifdef __has_include 8 | #if __has_include() 9 | #include "cgi_func.h" 10 | #else 11 | #error \ 12 | 'cgi_func.h' not found. This project depends on the 'lunar'\ 13 | library. See www.github.com/Bill-Gray/lunar .\ 14 | Clone that repository, 'make' and 'make install' it. 15 | #endif 16 | #else 17 | #include "cgi_func.h" 18 | #endif 19 | #include "watdefs.h" 20 | #include "stringex.h" 21 | 22 | int sat_id_main( const int argc, const char **argv); 23 | 24 | int main( const int unused_argc, const char **unused_argv) 25 | { 26 | const char *argv[20]; 27 | const size_t max_buff_size = 400000; /* room for 5000 obs */ 28 | char *buff = (char *)malloc( max_buff_size), *tptr; 29 | char field[30]; 30 | const char *temp_obs_filename = "sat_obs.txt"; 31 | double search_radius = 4.; /* look 4 degrees for matches */ 32 | double motion_cutoff = 60.; /* up to 60" discrepancy OK */ 33 | double low_speed_cutoff = 0.001; /* anything slower than this is almost */ 34 | int argc = 0; /* certainly not an artsat */ 35 | FILE *lock_file = fopen( "lock.txt", "w"); 36 | size_t bytes_written = 0, i; 37 | int cgi_status, show_summary = 0; 38 | extern int verbose; 39 | #ifndef _WIN32 40 | extern char **environ; 41 | 42 | avoid_runaway_process( 15); 43 | #endif /* _WIN32 */ 44 | setbuf( lock_file, NULL); 45 | INTENTIONALLY_UNUSED_PARAMETER( unused_argc); 46 | INTENTIONALLY_UNUSED_PARAMETER( unused_argv); 47 | printf( "Content-type: text/html\n\n"); 48 | printf( "
\n");
 49 |    if( !lock_file)
 50 |       {
 51 |       printf( "

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

"); 52 | printf( "

Your orbit is very important to us!

"); 53 | return( 0); 54 | } 55 | fprintf( lock_file, "We're in\n"); 56 | #ifndef _WIN32 57 | for( i = 0; environ[i]; i++) 58 | fprintf( lock_file, "%s\n", environ[i]); 59 | #endif 60 | cgi_status = initialize_cgi_reading( ); 61 | fprintf( lock_file, "CGI status %d\n", cgi_status); 62 | if( cgi_status <= 0) 63 | { 64 | printf( "

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

\n"); 66 | return( 0); 67 | } 68 | while( !get_cgi_data( field, buff, NULL, max_buff_size)) 69 | { 70 | fprintf( lock_file, "Field '%s'\n", field); 71 | if( !strcmp( field, "TextArea") || !strcmp( field, "upfile")) 72 | { 73 | if( strlen( buff) > 70) 74 | { 75 | FILE *ofile = fopen( temp_obs_filename, 76 | (bytes_written ? "ab" : "wb")); 77 | 78 | fprintf( lock_file, "File opened : %p\n", (void *)ofile); 79 | if( !ofile) 80 | { 81 | printf( "

Couldn't open %s : %s

\n", temp_obs_filename, strerror( errno)); 82 | fprintf( lock_file, "Couldn't open %s: %s\n", temp_obs_filename, strerror( errno)); 83 | return( -1); 84 | } 85 | bytes_written += fwrite( buff, 1, strlen( buff), ofile); 86 | fclose( ofile); 87 | } 88 | } 89 | if( !strcmp( field, "radius")) 90 | { 91 | const char *verbosity = strchr( buff, 'v'); 92 | 93 | search_radius = atof( buff); 94 | if( verbosity) 95 | verbose = atoi( verbosity + 1) + 1; 96 | } 97 | if( !strcmp( field, "motion")) 98 | motion_cutoff = atof( buff); 99 | if( !strcmp( field, "low_speed")) 100 | low_speed_cutoff = atof( buff); 101 | if( !strcmp( field, "summary")) 102 | show_summary = 1; 103 | } 104 | fprintf( lock_file, "Fields read\n"); 105 | // printf( "

Fields read

\n"); 106 | if( verbose) 107 | printf( "Searching to %f degrees; %u bytes read from input\n", 108 | search_radius, (unsigned)bytes_written); 109 | argv[argc++] = "sat_id"; 110 | argv[argc++] = temp_obs_filename; 111 | argv[argc++] = "-t../../tles/tle_list.txt"; 112 | snprintf_err( field, sizeof( field), "-r%.2f", search_radius); 113 | argv[argc++] = field; 114 | snprintf_err( buff, max_buff_size, "-y%f", motion_cutoff); 115 | argv[argc++] = buff; 116 | tptr = buff + strlen( buff) + 1; 117 | snprintf( tptr, 15, "-z%f", low_speed_cutoff); 118 | argv[argc++] = tptr; 119 | if( show_summary) 120 | argv[argc++] = "-u"; 121 | argv[argc] = NULL; 122 | for( i = 0; argv[i]; i++) 123 | fprintf( lock_file, "arg %d: '%s'\n", (int)i, argv[i]); 124 | sat_id_main( argc, argv); 125 | fprintf( lock_file, "sat_id_main called\n"); 126 | free( buff); 127 | printf( "On-line Sat_ID compiled " __DATE__ " " __TIME__ " UTC-5h\n"); 128 | printf( "See " 129 | "https://www.github.com/Bill-Gray/sat_code for source code\n"); 130 | printf( "
"); 131 | return( 0); 132 | } 133 | -------------------------------------------------------------------------------- /sat_id3.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #ifdef __has_include 8 | #if __has_include() 9 | #include "cgi_func.h" 10 | #else 11 | #error \ 12 | 'cgi_func.h' not found. This project depends on the 'lunar'\ 13 | library. See www.github.com/Bill-Gray/lunar .\ 14 | Clone that repository, 'make' and 'make install' it. 15 | #endif 16 | #else 17 | #include "cgi_func.h" 18 | #endif 19 | #include "watdefs.h" 20 | #include "stringex.h" 21 | 22 | /* This is the code behind 23 | 24 | https://www.projectpluto.com/sat_img.htm 25 | 26 | It takes the input data, creates a temporary file containing a 27 | single 'image field data' line, then uses the Sat_ID code (see sat_id.cpp) 28 | to figure out which artsats are in that field. */ 29 | 30 | int sat_id_main( const int argc, const char **argv); 31 | 32 | int main( const int unused_argc, const char **unused_argv) 33 | { 34 | const char *argv[20]; 35 | char buff[80]; 36 | char field[30]; 37 | char search_radius[10], date[50], latitude[30], longitude[30]; 38 | char altitude[10], ra[20], dec[20]; 39 | const int argc = 3; 40 | const char *output_file_name = "field.txt"; 41 | FILE *ofile; 42 | FILE *lock_file = fopen( "lock.txt", "w"); 43 | size_t i; 44 | int cgi_status; 45 | #ifndef _WIN32 46 | extern char **environ; 47 | 48 | avoid_runaway_process( 15); 49 | #endif /* _WIN32 */ 50 | setbuf( lock_file, NULL); 51 | INTENTIONALLY_UNUSED_PARAMETER( unused_argc); 52 | INTENTIONALLY_UNUSED_PARAMETER( unused_argv); 53 | printf( "Content-type: text/html\n\n"); 54 | printf( "
\n");
 55 |    if( !lock_file)
 56 |       {
 57 |       printf( "

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

"); 58 | printf( "

Your orbit is very important to us!

"); 59 | return( 0); 60 | } 61 | fprintf( lock_file, "We're in\n"); 62 | #ifndef _WIN32 63 | for( i = 0; environ[i]; i++) 64 | fprintf( lock_file, "%s\n", environ[i]); 65 | #endif 66 | cgi_status = initialize_cgi_reading( ); 67 | fprintf( lock_file, "CGI status %d\n", cgi_status); 68 | if( cgi_status <= 0) 69 | { 70 | printf( "

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

\n"); 72 | return( 0); 73 | } 74 | *search_radius = *date = *latitude = *longitude = *altitude = '\0'; 75 | *ra = *dec = '\0'; 76 | while( !get_cgi_data( field, buff, NULL, sizeof( buff))) 77 | { 78 | fprintf( lock_file, "Field '%s'\n", field); 79 | if( !strcmp( field, "radius")) 80 | { 81 | strlcpy( search_radius, buff, sizeof( search_radius)); 82 | if( !atof( search_radius)) 83 | printf( "Search radius must be non-zero\n"); 84 | } 85 | else if( !strcmp( field, "time")) 86 | strlcpy( date, buff, sizeof( date)); 87 | else if( !strcmp( field, "lat")) 88 | strlcpy( latitude, buff, sizeof( latitude)); 89 | else if( !strcmp( field, "lon")) 90 | strlcpy( longitude, buff, sizeof( longitude)); 91 | else if( !strcmp( field, "alt")) 92 | strlcpy( altitude, buff, sizeof( altitude)); 93 | else if( !strcmp( field, "ra")) 94 | strlcpy( ra, buff, sizeof( ra)); 95 | else if( !strcmp( field, "dec")) 96 | strlcpy( dec, buff, sizeof( dec)); 97 | } 98 | ofile = fopen( output_file_name, "w"); 99 | fprintf( ofile, "COD XXX\n"); 100 | fprintf( ofile, "COM Long. %s, Lat. %s, Alt. %s, unspecified\n", 101 | longitude, latitude, altitude); 102 | fprintf( ofile, "Field,%s,%s,%s,XXX\n", date, ra, dec); 103 | fclose( ofile); 104 | argv[0] = "sat_id"; 105 | argv[1] = output_file_name; 106 | argv[2] = "-t../../tles/tle_list.txt"; 107 | argv[3] = NULL; 108 | for( i = 0; argv[i]; i++) 109 | fprintf( lock_file, "arg %d: '%s'\n", (int)i, argv[i]); 110 | sat_id_main( argc, argv); 111 | fprintf( lock_file, "sat_id_main called\n"); 112 | printf( "On-line artsats-in-field-finder compiled" 113 | __DATE__ " " __TIME__ " UTC-5h\n"); 114 | printf( "See " 115 | "https://www.github.com/Bill-Gray/sat_code for source code\n"); 116 | printf( "
"); 117 | return( 0); 118 | } 119 | -------------------------------------------------------------------------------- /sat_util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "sat_util.h" 5 | 6 | char *fgets_trimmed( char *buff, const int buffsize, FILE *ifile) 7 | { 8 | char *rval = fgets( buff, buffsize, ifile); 9 | 10 | if( rval) 11 | { 12 | size_t i = 0; 13 | 14 | while( rval[i] != 10 && rval[i] != 13 && rval[i]) 15 | i++; 16 | while( i && rval[i - 1] == ' ') 17 | i--; /* drop trailing spaces */ 18 | rval[i] = '\0'; 19 | } 20 | return( rval); 21 | } 22 | 23 | #if !defined( _WIN32) 24 | void make_config_dir_name( char *oname, const char *iname) 25 | { 26 | #ifdef ON_LINE_VERSION 27 | strcpy( oname, getenv( "DOCUMENT_ROOT")); 28 | strcat( oname, "/"); 29 | #else 30 | const char *home_dir = getenv( "HOME"); 31 | 32 | if( home_dir) 33 | { 34 | strcpy( oname, home_dir); 35 | strcat( oname, "/.find_orb/"); 36 | } 37 | else 38 | *oname = '\0'; 39 | #endif 40 | strcat( oname, iname); 41 | } 42 | 43 | FILE *local_then_config_fopen( const char *filename, const char *permits) 44 | { 45 | FILE *rval = fopen( filename, permits); 46 | 47 | if( !rval) 48 | { 49 | char ext_filename[255]; 50 | 51 | make_config_dir_name( ext_filename, filename); 52 | rval = fopen( ext_filename, "rb"); 53 | } 54 | return( rval); 55 | } 56 | #else 57 | FILE *local_then_config_fopen( const char *filename, const char *permits) 58 | { 59 | return( fopen( filename, permits)); 60 | } 61 | #endif 62 | -------------------------------------------------------------------------------- /sat_util.h: -------------------------------------------------------------------------------- 1 | #ifndef SAT_UTIL_H_INCLUDED 2 | #define SAT_UTIL_H_INCLUDED 3 | 4 | /* A few functions that are used in common by sat_id, sat_id2, and sat_eph. */ 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif /* #ifdef __cplusplus */ 9 | 10 | FILE *local_then_config_fopen( const char *filename, const char *permits); 11 | char *fgets_trimmed( char *buff, const int buffsize, FILE *ifile); 12 | 13 | #if !defined( _WIN32) 14 | void make_config_dir_name( char *oname, const char *iname); 15 | #endif 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif /* #ifdef __cplusplus */ 20 | #endif /* #ifndef SAT_UTIL_H_INCLUDED */ 21 | -------------------------------------------------------------------------------- /sdp8.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #include 4 | #include "norad.h" 5 | #include "norad_in.h" 6 | 7 | #define tthmun params[0] 8 | #define sini2 params[1] 9 | #define cosi2 params[2] 10 | #define unm5th params[3] 11 | #define unmth2 params[4] 12 | #define xmdt1 params[5] 13 | #define xgdt1 params[6] 14 | #define xhdt1 params[7] 15 | #define xndt params[8] 16 | #define edot params[9] 17 | 18 | void sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg); 19 | void sxp8_common_init( double *params, const tle_t *tle, deep_arg_t *deep_arg); 20 | 21 | void DLL_FUNC SDP8_init( double *params, const tle_t *tle) 22 | { 23 | const double rho = .15696615; 24 | const double b = tle->bstar*2./rho; 25 | double 26 | alpha2, b1, b2, b3, c0, 27 | c1, c4, c5, cos2g, d1, d2, d3, d4, 28 | d5, eeta, eta, eta2, 29 | po, psim2, r1, tsi, xndtn; 30 | deep_arg_t *deep_arg = ((deep_arg_t *)( params + 10)); 31 | 32 | sxpall_common_init( tle, deep_arg); 33 | sxp8_common_init( params, tle, deep_arg); 34 | deep_arg->sinio = sin( tle->xincl); 35 | 36 | /* Initialization */ 37 | po = deep_arg->aodp*deep_arg->betao2; 38 | tsi = 1./(po-s_const); 39 | eta = tle->eo*s_const*tsi; 40 | eta2 = eta * eta; 41 | psim2 = (r1 = 1./(1.-eta2), fabs(r1)); 42 | alpha2 = deep_arg->eosq+1.; 43 | eeta = tle->eo*eta; 44 | cos2g = deep_arg->cosg * deep_arg->cosg * 2. - 1.; 45 | d5 = tsi*psim2; 46 | d1 = d5/po; 47 | d2 = eta2*(eta2*4.5+36.)+12.; 48 | d3 = eta2*(eta2*2.5+15.); 49 | d4 = eta*(eta2*3.75+5.); 50 | b1 = ck2*tthmun; 51 | b2 = -ck2*unmth2; 52 | b3 = a3ovk2*deep_arg->sinio; 53 | r1 = tsi, r1 *= r1; 54 | c0 = b*.5*rho*qoms2t*deep_arg->xnodp*deep_arg->aodp* 55 | (r1*r1)*pow( psim2, 3.5)/sqrt(alpha2); 56 | r1 = alpha2; 57 | c1 = deep_arg->xnodp*1.5*(r1*r1)*c0; 58 | c4 = d1*d3*b2; 59 | c5 = d5*d4*b3; 60 | xndt = c1*(eta2*(deep_arg->eosq*34.+3.)+2.+eeta*5.*(eta2+4.)+ 61 | deep_arg->eosq*8.5+d1*d2*b1+c4*cos2g+c5*deep_arg->sing); 62 | xndtn = xndt/deep_arg->xnodp; 63 | edot = -two_thirds*xndtn*(1.-tle->eo); 64 | 65 | /* initialize Deep() */ 66 | Deep_dpinit( tle, deep_arg); 67 | #ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH 68 | /* initialize lunisolar perturbations: */ 69 | deep_arg->t = 0.; /* added 30 Dec 2003 */ 70 | deep_arg->solar_lunar_init_flag = 1; 71 | Deep_dpper( tle, deep_arg); 72 | deep_arg->solar_lunar_init_flag = 0; 73 | #endif 74 | } /* End of SDP8() initialization */ 75 | 76 | int DLL_FUNC SDP8( const double tsince, const tle_t *tle, const double *params, 77 | double *pos, double *vel) 78 | { 79 | double 80 | am, aovr, axnm, aynm, beta, beta2m, 81 | cose, cosos, cs2f2g, csf, csfg, cslamb, di, 82 | diwc, dr, ecosf, fm, g1, g10, g13, g14, g2, 83 | g3, g4, g5, pm, r1, rdot, rm, rr, rvdot, sine, 84 | sinos, sn2f2g, snf, snfg, sni2du, sinio2, 85 | snlamb, temp, ux, uy, uz, vx, vy, vz, xlamb, 86 | xmam, xmamdf, y4, y5, z1, z7, zc2, zc5; 87 | int i; 88 | deep_arg_t *deep_arg = ((deep_arg_t *)( params + 10)); 89 | 90 | /* Update for secular gravity and atmospheric drag */ 91 | z1 = xndt*.5*tsince*tsince; 92 | z7 = two_thirds*3.5*z1/deep_arg->xnodp; 93 | xmamdf = tle->xmo+deep_arg->xmdot*tsince; 94 | deep_arg->omgadf = tle->omegao+deep_arg->omgdot*tsince+z7*xgdt1; 95 | deep_arg->xnode = tle->xnodeo+deep_arg->xnodot*tsince+z7*xhdt1; 96 | deep_arg->xn = deep_arg->xnodp; 97 | 98 | /* Update for deep-space secular effects */ 99 | deep_arg->xll = xmamdf; 100 | deep_arg->t = tsince; 101 | Deep_dpsec( tle, deep_arg); 102 | xmamdf = deep_arg->xll; 103 | deep_arg->xn += xndt*tsince; 104 | deep_arg->em += edot*tsince; 105 | xmam = xmamdf+z1+z7*xmdt1; 106 | 107 | /* Update for deep-space periodic effects */ 108 | deep_arg->xll = xmam; 109 | Deep_dpper( tle, deep_arg); 110 | xmam = deep_arg->xll; 111 | xmam = FMod2p(xmam); 112 | 113 | /* Solve Kepler's equation */ 114 | zc2 = xmam+deep_arg->em*sin(xmam)*(deep_arg->em*cos(xmam)+1.); 115 | 116 | i = 0; 117 | do 118 | { 119 | double cape; 120 | 121 | sine = sin(zc2); 122 | cose = cos(zc2); 123 | zc5 = 1./(1.-deep_arg->em*cose); 124 | cape = (xmam+deep_arg->em*sine-zc2)*zc5+zc2; 125 | r1 = cape-zc2; 126 | if (fabs(r1) <= e6a) break; 127 | zc2 = cape; 128 | } 129 | while(i++ < 10); 130 | 131 | /* Short period preliminary quantities */ 132 | am = pow( xke / deep_arg->xn, two_thirds); 133 | beta2m = 1.f-deep_arg->em*deep_arg->em; 134 | sinos = sin(deep_arg->omgadf); 135 | cosos = cos(deep_arg->omgadf); 136 | axnm = deep_arg->em*cosos; 137 | aynm = deep_arg->em*sinos; 138 | pm = am*beta2m; 139 | g1 = 1./pm; 140 | g2 = ck2*.5*g1; 141 | g3 = g2*g1; 142 | beta = sqrt(beta2m); 143 | g4 = a3ovk2*.25*deep_arg->sinio; 144 | g5 = a3ovk2*.25*g1; 145 | snf = beta*sine*zc5; 146 | csf = (cose-deep_arg->em)*zc5; 147 | fm = atan2(snf, csf); 148 | if( fm < 0.) 149 | fm += pi + pi; 150 | snfg = snf*cosos+csf*sinos; 151 | csfg = csf*cosos-snf*sinos; 152 | sn2f2g = snfg*2.*csfg; 153 | r1 = csfg; 154 | cs2f2g = r1*r1*2.-1.; 155 | ecosf = deep_arg->em*csf; 156 | g10 = fm-xmam+deep_arg->em*snf; 157 | rm = pm/(ecosf+1.); 158 | aovr = am/rm; 159 | g13 = deep_arg->xn*aovr; 160 | g14 = -g13*aovr; 161 | dr = g2*(unmth2*cs2f2g-tthmun*3.)-g4*snfg; 162 | diwc = g3*3.*deep_arg->sinio*cs2f2g-g5*aynm; 163 | di = diwc*deep_arg->cosio; 164 | sinio2 = sin(deep_arg->xinc*.5); 165 | 166 | /* Update for short period periodics */ 167 | sni2du = sini2*(g3*((1.-deep_arg->cosio2*7.)*.5*sn2f2g-unm5th* 168 | 3.*g10)-g5*deep_arg->sinio*csfg*(ecosf+2.))-g5*.5* 169 | deep_arg->cosio2*axnm/cosi2; 170 | xlamb = fm+deep_arg->omgadf+deep_arg->xnode+g3*((deep_arg->cosio*6.+ 171 | 1.-deep_arg->cosio2*7.)*.5*sn2f2g-(unm5th+deep_arg->cosio*2.)* 172 | 3.*g10)+g5*deep_arg->sinio*(deep_arg->cosio*axnm/ 173 | (deep_arg->cosio+1.)-(ecosf+2.)*csfg); 174 | y4 = sinio2*snfg+csfg*sni2du+snfg*.5*cosi2*di; 175 | y5 = sinio2*csfg-snfg*sni2du+csfg*.5*cosi2*di; 176 | rr = rm+dr; 177 | rdot = deep_arg->xn*am*deep_arg->em*snf/beta+g14*(g2*2.*unmth2*sn2f2g+g4*csfg); 178 | r1 = am; 179 | rvdot = deep_arg->xn*(r1*r1)*beta/rm+g14*dr+am*g13*deep_arg->sinio*diwc; 180 | 181 | /* Orientation vectors */ 182 | snlamb = sin(xlamb); 183 | cslamb = cos(xlamb); 184 | temp = (y5*snlamb-y4*cslamb)*2.; 185 | ux = y4*temp+cslamb; 186 | vx = y5*temp-snlamb; 187 | temp = (y5*cslamb+y4*snlamb)*2.; 188 | uy = -y4*temp+snlamb; 189 | vy = -y5*temp+cslamb; 190 | temp = sqrt(1.-y4*y4-y5*y5)*2.; 191 | uz = y4*temp; 192 | vz = y5*temp; 193 | 194 | /* Position and velocity */ 195 | pos[0] = rr*ux*earth_radius_in_km; 196 | pos[1] = rr*uy*earth_radius_in_km; 197 | pos[2] = rr*uz*earth_radius_in_km; 198 | if( vel) 199 | { 200 | vel[0] = (rdot*ux+rvdot*vx)*earth_radius_in_km; 201 | vel[1] = (rdot*uy+rvdot*vy)*earth_radius_in_km; 202 | vel[2] = (rdot*uz+rvdot*vz)*earth_radius_in_km; 203 | } 204 | return( 0); 205 | } /* SDP8 */ 206 | -------------------------------------------------------------------------------- /sgp.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #include 4 | #include "norad.h" 5 | #include "norad_in.h" 6 | 7 | #define ao params[0] 8 | #define qo params[1] 9 | #define xlo params[2] 10 | #define d1o params[3] 11 | #define d2o params[4] 12 | #define d3o params[5] 13 | #define d4o params[6] 14 | #define omgdt params[7] 15 | #define xnodot params[8] 16 | #define c5 params[9] 17 | #define c6 params[10] 18 | 19 | void DLL_FUNC SGP_init( double *params, const tle_t *tle) 20 | { 21 | double c1, c2, c3, c4, r1, cosio, sinio, a1, d1, po, po2no; 22 | 23 | c1 = ck2*1.5; 24 | c2 = ck2/4.; 25 | c3 = ck2/2.; 26 | r1 = ae; 27 | c4 = xj3*(r1*(r1*r1))/(ck2*4.); 28 | cosio = cos(tle->xincl); 29 | sinio = sin(tle->xincl); 30 | a1 = pow( xke / tle->xno, two_thirds); 31 | d1 = c1/a1/a1*(cosio*3.*cosio-1.)/pow( 1.-tle->eo*tle->eo, 1.5); 32 | ao = a1*(1.-d1*.33333333333333331-d1*d1-d1* 33 | 1.654320987654321*d1*d1); 34 | po = ao*(1.-tle->eo*tle->eo); 35 | qo = ao*(1.-tle->eo); 36 | xlo = tle->xmo+tle->omegao+tle->xnodeo; 37 | d1o = c3*sinio*sinio; 38 | d2o = c2*(cosio*7.*cosio-1.); 39 | d3o = c1*cosio; 40 | d4o = d3o*sinio; 41 | po2no = tle->xno/(po*po); 42 | omgdt = c1*po2no*(cosio*5.*cosio-1.); 43 | xnodot = d3o*-2.*po2no; 44 | c5 = c4*.5*sinio*(cosio*5.+3.)/(cosio+1.); 45 | c6 = c4*sinio; 46 | } 47 | 48 | 49 | int DLL_FUNC SGP( const double tsince, const tle_t *tle, const double *params, 50 | double *pos, double *vel) 51 | { 52 | double 53 | temp, rdot, cosu, sinu, cos2u, sin2u, a, e, 54 | p, rr, u, ecose, esine, omgas, cosik, xinck, 55 | sinik, axnsl, aynsl, 56 | sinuk, rvdot, cosuk, coseo1, sineo1, pl, 57 | rk, uk, xl, su, ux, uy, uz, vx, vy, vz, pl2, 58 | xnodek, cosnok, xnodes, el2, eo1, r1, sinnok, 59 | xls, xmx, xmy, tem2, tem5; 60 | const double chicken_factor_on_eccentricity = 1.e-6; 61 | 62 | int i, rval = 0; 63 | 64 | /* Update for secular gravity and atmospheric drag */ 65 | a = tle->xno+(tle->xndt2o*2.+tle->xndd6o*3.*tsince)*tsince; 66 | if( a < 0.) 67 | rval = SXPX_ERR_NEGATIVE_MAJOR_AXIS; 68 | e = e6a; 69 | if( e > 1. - chicken_factor_on_eccentricity) 70 | rval = SXPX_ERR_NEARLY_PARABOLIC; 71 | if( rval) 72 | { 73 | for( i = 0; i < 3; i++) 74 | { 75 | pos[i] = 0.; 76 | if( vel) 77 | vel[i] = 0.; 78 | } 79 | return( rval); 80 | } 81 | a = ao * pow( tle->xno / a, two_thirds); 82 | if( a * (1. - e) < 1. && a * (1. + e) < 1.) /* entirely within earth */ 83 | rval = SXPX_WARN_ORBIT_WITHIN_EARTH; /* remember, e can be negative */ 84 | if( a * (1. - e) < 1. || a * (1. + e) < 1.) /* perigee within earth */ 85 | rval = SXPX_WARN_PERIGEE_WITHIN_EARTH; 86 | if (a > qo) e = 1.-qo/a; 87 | p = a*(1.-e*e); 88 | xnodes = tle->xnodeo+xnodot*tsince; 89 | omgas = tle->omegao+omgdt*tsince; 90 | r1 = xlo+(tle->xno+omgdt+xnodot+ 91 | (tle->xndt2o+tle->xndd6o*tsince)*tsince)*tsince; 92 | xls = FMod2p(r1); 93 | 94 | /* Long period periodics */ 95 | axnsl = e*cos(omgas); 96 | aynsl = e*sin(omgas)-c6/p; 97 | r1 = xls-c5/p*axnsl; 98 | xl = FMod2p(r1); 99 | 100 | /* Solve Kepler's equation */ 101 | r1 = xl-xnodes; 102 | u = FMod2p(r1); 103 | eo1 = u; 104 | tem5 = 1.; 105 | 106 | i = 0; 107 | do 108 | { 109 | sineo1 = sin(eo1); 110 | coseo1 = cos(eo1); 111 | if (fabs(tem5) < e6a) break; 112 | tem5 = 1.-coseo1*axnsl-sineo1*aynsl; 113 | tem5 = (u-aynsl*coseo1+axnsl*sineo1-eo1)/tem5; 114 | tem2 = fabs(tem5); 115 | if (tem2 > 1.) tem5 = tem2/tem5; 116 | eo1 += tem5; 117 | } 118 | while(i++ < 10); 119 | 120 | /* Short period preliminary quantities */ 121 | ecose = axnsl*coseo1+aynsl*sineo1; 122 | esine = axnsl*sineo1-aynsl*coseo1; 123 | el2 = axnsl*axnsl+aynsl*aynsl; 124 | pl = a*(1.-el2); 125 | pl2 = pl*pl; 126 | rr = a*(1.-ecose); 127 | rdot = xke*sqrt(a)/rr*esine; 128 | rvdot = xke*sqrt(pl)/rr; 129 | temp = esine/(sqrt(1.-el2)+1.); 130 | sinu = a/rr*(sineo1-aynsl-axnsl*temp); 131 | cosu = a/rr*(coseo1-axnsl+aynsl*temp); 132 | su = atan2(sinu, cosu); 133 | 134 | /* Update for short periodics */ 135 | sin2u = (cosu+cosu)*sinu; 136 | cos2u = 1.-2.*sinu*sinu; 137 | rk = rr+d1o/pl*cos2u; 138 | uk = su-d2o/pl2*sin2u; 139 | xnodek = xnodes+d3o*sin2u/pl2; 140 | xinck = tle->xincl+d4o/pl2*cos2u; 141 | 142 | /* Orientation vectors */ 143 | sinuk = sin(uk); 144 | cosuk = cos(uk); 145 | sinnok = sin(xnodek); 146 | cosnok = cos(xnodek); 147 | sinik = sin(xinck); 148 | cosik = cos(xinck); 149 | xmx = -sinnok*cosik; 150 | xmy = cosnok*cosik; 151 | ux = xmx*sinuk+cosnok*cosuk; 152 | uy = xmy*sinuk+sinnok*cosuk; 153 | uz = sinik*sinuk; 154 | vx = xmx*cosuk-cosnok*sinuk; 155 | vy = xmy*cosuk-sinnok*sinuk; 156 | vz = sinik*cosuk; 157 | 158 | /* Position and velocity */ 159 | pos[0] = rk*ux*earth_radius_in_km; 160 | pos[1] = rk*uy*earth_radius_in_km; 161 | pos[2] = rk*uz*earth_radius_in_km; 162 | if( vel) 163 | { 164 | vel[0] = (rdot*ux + rvdot * vx)*earth_radius_in_km; 165 | vel[1] = (rdot*uy + rvdot * vy)*earth_radius_in_km; 166 | vel[2] = (rdot*uz + rvdot * vz)*earth_radius_in_km; 167 | } 168 | return( rval); 169 | } /* SGP */ 170 | -------------------------------------------------------------------------------- /sgp4.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #include 4 | #include 5 | #include "norad.h" 6 | #include "norad_in.h" 7 | 8 | #define c1 params[2] 9 | #define c4 params[3] 10 | #define xnodcf params[4] 11 | #define t2cof params[5] 12 | #define p_aodp params[10] 13 | #define p_cosio params[11] 14 | #define p_sinio params[12] 15 | #define p_omgdot params[13] 16 | #define p_xmdot params[14] 17 | #define p_xnodot params[15] 18 | #define p_xnodp params[16] 19 | #define c5 params[17] 20 | #define d2 params[18] 21 | #define d3 params[19] 22 | #define d4 params[20] 23 | #define delmo params[21] 24 | #define p_eta params[22] 25 | #define omgcof params[23] 26 | #define sinmo params[24] 27 | #define t3cof params[25] 28 | #define t4cof params[26] 29 | #define t5cof params[27] 30 | #define xmcof params[28] 31 | #define simple_flag *((int *)( params + 29)) 32 | #define MINIMAL_E 1.e-4 33 | #define ECC_EPS 1.e-6 /* Too low for computing further drops. */ 34 | 35 | void DLL_FUNC SGP4_init( double *params, const tle_t *tle) 36 | { 37 | deep_arg_t deep_arg; 38 | init_t init; 39 | double eeta, etasq; 40 | 41 | sxpx_common_init( params, tle, &init, &deep_arg); 42 | p_aodp = deep_arg.aodp; 43 | p_cosio = deep_arg.cosio; 44 | p_sinio = deep_arg.sinio; 45 | p_omgdot = deep_arg.omgdot; 46 | p_xmdot = deep_arg.xmdot; 47 | p_xnodot = deep_arg.xnodot; 48 | p_xnodp = deep_arg.xnodp; 49 | p_eta = deep_arg.aodp*tle->eo*init.tsi; 50 | // p_eta = init.eta; 51 | 52 | eeta = tle->eo*p_eta; 53 | /* For perigee less than 220 kilometers, the "simple" flag is set */ 54 | /* and the equations are truncated to linear variation in sqrt a */ 55 | /* and quadratic variation in mean anomaly. Also, the c3 term, */ 56 | /* the delta omega term, and the delta m term are dropped. */ 57 | simple_flag = ((p_aodp*(1-tle->eo)/ae) < (220./earth_radius_in_km+ae)); 58 | if( !simple_flag) 59 | { 60 | const double c1sq = c1*c1; 61 | double temp; 62 | 63 | simple_flag = 0; 64 | delmo = 1. + p_eta * cos(tle->xmo); 65 | delmo *= delmo * delmo; 66 | d2 = 4*p_aodp*init.tsi*c1sq; 67 | temp = d2*init.tsi*c1/3; 68 | d3 = (17*p_aodp+init.s4)*temp; 69 | d4 = 0.5*temp*p_aodp*init.tsi*(221*p_aodp+31*init.s4)*c1; 70 | t3cof = d2+2*c1sq; 71 | t4cof = 0.25*(3*d3+c1*(12*d2+10*c1sq)); 72 | t5cof = 0.2*(3*d4+12*c1*d3+6*d2*d2+15*c1sq*(2*d2+c1sq)); 73 | sinmo = sin(tle->xmo); 74 | if( tle->eo < MINIMAL_E) 75 | omgcof = xmcof = 0.; 76 | else 77 | { 78 | const double c3 = 79 | init.coef * init.tsi * a3ovk2 * p_xnodp * ae * p_sinio / tle->eo; 80 | 81 | xmcof = -two_thirds * init.coef * tle->bstar * ae / eeta; 82 | omgcof = tle->bstar*c3*cos(tle->omegao); 83 | } 84 | } /* End of if (isFlagClear(SIMPLE_FLAG)) */ 85 | etasq = p_eta * p_eta; 86 | c5 = 2*init.coef1*p_aodp * deep_arg.betao2*(1+2.75*(etasq+eeta)+eeta*etasq); 87 | } /* End of SGP4() initialization */ 88 | 89 | int DLL_FUNC SGP4( const double tsince, const tle_t *tle, const double *params, 90 | double *pos, double *vel) 91 | { 92 | double 93 | a, e, omega, omgadf, 94 | temp, tempa, tempe, templ, tsq, 95 | xl, xmdf, xmp, xnoddf, xnode; 96 | 97 | /* Update for secular gravity and atmospheric drag. */ 98 | xmdf = tle->xmo+p_xmdot*tsince; 99 | omgadf = tle->omegao+p_omgdot*tsince; 100 | xnoddf = tle->xnodeo+p_xnodot*tsince; 101 | omega = omgadf; 102 | xmp = xmdf; 103 | tsq = tsince*tsince; 104 | xnode = xnoddf+xnodcf*tsq; 105 | tempa = 1-c1*tsince; 106 | tempe = tle->bstar*c4*tsince; 107 | templ = t2cof*tsq; 108 | if( !simple_flag) 109 | { 110 | const double delomg = omgcof*tsince; 111 | double delm = 1. + p_eta * cos(xmdf); 112 | double tcube, tfour; 113 | 114 | delm = xmcof * (delm * delm * delm - delmo); 115 | temp = delomg+delm; 116 | xmp = xmdf+temp; 117 | omega = omgadf-temp; 118 | tcube = tsq*tsince; 119 | tfour = tsince*tcube; 120 | tempa = tempa-d2*tsq-d3*tcube-d4*tfour; 121 | tempe = tempe+tle->bstar*c5*(sin(xmp)-sinmo); 122 | templ = templ+t3cof*tcube+tfour*(t4cof+tsince*t5cof); 123 | }; /* End of if (isFlagClear(SIMPLE_FLAG)) */ 124 | 125 | a = p_aodp*tempa*tempa; 126 | e = tle->eo-tempe; 127 | /* A highly arbitrary lower limit on e, of 1e-6: */ 128 | if( e < ECC_EPS) 129 | e = ECC_EPS; 130 | xl = xmp+omega+xnode+p_xnodp*templ; 131 | if( tempa < 0.) /* force negative a, to indicate error condition */ 132 | a = -a; 133 | return( sxpx_posn_vel( xnode, a, e, p_cosio, p_sinio, tle->xincl, 134 | omega, xl, pos, vel)); 135 | } /*SGP4*/ 136 | -------------------------------------------------------------------------------- /sm_sat.def: -------------------------------------------------------------------------------- 1 | LIBRARY sm_sat 2 | EXPORTS 3 | SGP4_init @2 4 | SGP4 @7 5 | select_ephemeris @11 6 | parse_elements @12 7 | tle_checksum @17 8 | 9 | -------------------------------------------------------------------------------- /ssc_eph.c: -------------------------------------------------------------------------------- 1 | /* Code to convert ephems from SSCWeb into the format 'eph2tle' 2 | uses to generate TLEs. The usefulness here is that you can 3 | get TLEs for the Magnetospheric Multiscale (MMS) satellites that 4 | are good enough to ID the individual satellites, even though 5 | the four of them are in a tight cluster. This does fail if the 6 | satellites maneuver during the course of a day; the TLEs are 7 | then unable to fit the (non-gravitational) motion. Compile with 8 | 9 | gcc -I../include -Wextra -Wall -O3 -pedantic ssc_eph.c -o ssc_eph ../lib/liblunar.a -lm 10 | 11 | Go to the SSCWeb Locator page : 12 | 13 | https://sscweb.gsfc.nasa.gov/cgi-bin/Locator.cgi 14 | 15 | Select the 'Standard' interface. Turn on MMS-1, 2, 3, and 4. 16 | Set the output frequency to 144 minutes (= 0.1 day). Under 17 | 'Optional Settings', select kilometers and yy/mm/dd output. 18 | Select the desired time span. 19 | 20 | Under 'Output Options', select GEI/J2000 XYZ. 21 | 22 | Generate the output and save it as /tmp/mms.txt. The result 23 | can be fed through 'eph2tle' (see the 'find_orb' repository) to 24 | generate TLEs. */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "watdefs.h" 31 | #include "date.h" 32 | 33 | #define is_power_of_two( X) (!((X) & ((X) - 1))) 34 | 35 | static void dump_to_file( const int mms_no, const double t0, const double dt, 36 | const int n_found, const double *xyz) 37 | { 38 | FILE *ofile; 39 | char buff[200]; 40 | int i, j; 41 | 42 | snprintf( buff, sizeof( buff), "mms%d.txt", mms_no); 43 | ofile = fopen( buff, "wb"); 44 | assert( ofile); 45 | fprintf( ofile, "%f %f %d 0,149597870.700000,86400.000000,2000" 46 | " (500) Geocentric: MMS-%d = 2015-011%c = NORAD %d\n", 47 | t0, dt, n_found, mms_no, mms_no + '@', mms_no + 40481); 48 | for( i = 0; i < n_found; i++) 49 | { 50 | double vel[3]; 51 | const double *tptr = xyz + i * 3; 52 | 53 | for( j = 0; j < 3; j++) 54 | vel[j] = (i == n_found - 1 ? tptr : tptr + 3)[j]; 55 | for( j = 0; j < 3; j++) 56 | { 57 | vel[j] -= (i ? tptr - 3 : tptr)[j]; 58 | vel[j] /= 86400. * dt; 59 | if( i && i != n_found - 1) 60 | vel[j] /= 2.; 61 | } 62 | fprintf( ofile, "%f %11.2f %11.2f %11.2f %11.6f %11.6f %11.6f\n", 63 | t0 + (double)i * dt, 64 | tptr[0], tptr[1], tptr[2], vel[0], vel[1], vel[2]); 65 | } 66 | fclose( ofile); 67 | } 68 | 69 | int main( const int argc, const char **argv) 70 | { 71 | FILE *ifile = fopen( "/tmp/mms.txt", "rb"); 72 | char buff[100]; 73 | long header_offset = 0; 74 | int mms_no; 75 | 76 | assert( ifile); 77 | while( !header_offset && fgets( buff, sizeof( buff), ifile)) 78 | if( !memcmp( buff, "yy/mm/dd", 8)) 79 | header_offset = ftell( ifile); 80 | if( !header_offset) 81 | { 82 | fprintf( stderr, "Couldn't find yy/mm/dd header\n"); 83 | return( -1); 84 | } 85 | for( mms_no = 1; mms_no <= 4; mms_no++) 86 | { 87 | int n_found = 0; 88 | double t0 = 0., step = 0., *xyz = NULL; 89 | const double td_minus_utc = 69.184 / 86400.; 90 | char *tptr; 91 | 92 | fseek( ifile, header_offset, SEEK_SET); 93 | buff[0] = '2'; /* assume 2nd millennium */ 94 | buff[1] = '0'; /* assume 21st century */ 95 | while( fgets( buff + 2, sizeof( buff) - 2, ifile)) 96 | if( (tptr = strstr( buff, "mms")) != NULL && tptr[3] == '0' + mms_no) 97 | { 98 | int n_fields_read; 99 | 100 | tptr[-1] = '\0'; 101 | if( !n_found) 102 | t0 = get_time_from_string( 0., buff, FULL_CTIME_YMD, NULL); 103 | if( n_found == 1) 104 | step = get_time_from_string( 0., buff, FULL_CTIME_YMD, NULL) - t0; 105 | n_found++; 106 | if( is_power_of_two( n_found)) 107 | xyz = (double *)realloc( xyz, 2 * n_found * 3 * sizeof( double)); 108 | n_fields_read = sscanf( tptr + 4, "%lf %lf %lf", 109 | xyz + n_found * 3 - 3, 110 | xyz + n_found * 3 - 2, 111 | xyz + n_found * 3 - 1); 112 | assert( 3 == n_fields_read); 113 | } 114 | dump_to_file( mms_no, t0 + td_minus_utc, step, n_found, xyz); 115 | free( xyz); 116 | } 117 | fclose( ifile); 118 | return( 0); 119 | } 120 | -------------------------------------------------------------------------------- /summarize.c: -------------------------------------------------------------------------------- 1 | /* Code to read 'tle_list.txt' and insert # Range: lines for 2 | files that lack them, and (for files containing only one 3 | object) NORAD/COSPAR identifiers. The former can be used to 4 | speed up some programs (by skipping 'included' files that 5 | won't cover a desired time span). The latter can be used in 6 | an ephemeris program to quickly find a desired object. 7 | 8 | Important notes : 9 | 10 | -- tle_list.txt will require occasional updating, of course, 11 | as new objects are added and archival Space-Track and other TLE 12 | sets are added. My hope is to run this once and then add 13 | # Range: and # ID: lines as needed. 14 | 15 | -- THEMIS-A, D, and E are oddball cases. They maneuver; I 16 | get state vectors from UC Berkeley and compute TLEs from those 17 | (see 'up_them' in the 'tles' repository). Those are updated 18 | roughly weekly. I don't want to have to update 'tle_list' each 19 | time for that, so I've pushed the day of reckoning for those 20 | off by a year. */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "watdefs.h" 27 | #include "afuncs.h" 28 | #include "date.h" 29 | #include "norad.h" 30 | 31 | static void get_range_info( const char *filename, 32 | const int range_already_set) 33 | { 34 | char buff[200], *tptr, id[100]; 35 | int n_ids_found = 0; 36 | const int is_themis = !memcmp( filename, "07004", 5) 37 | && filename[5] < 'g'; 38 | FILE *ifile; 39 | 40 | strcpy( buff, filename); 41 | tptr = strchr( buff, '\n'); 42 | assert( tptr); 43 | *tptr = '\0'; 44 | ifile = fopen( buff, "rb"); 45 | assert( ifile); 46 | *id = '\0'; 47 | while( 2 > n_ids_found && fgets( buff, sizeof( buff), ifile)) 48 | { 49 | if( !memcmp( buff, "# Ephem range:", 14) && !range_already_set) 50 | { 51 | double mjd[2]; 52 | size_t i; 53 | const int n_read = sscanf( buff + 14, "%lf %lf", mjd, mjd + 1); 54 | 55 | assert( n_read == 2); 56 | if( is_themis) /* THEMIS sats get updated/extended */ 57 | mjd[1] += 365.; /* regularly; 'pad' the end date accordingly */ 58 | printf( "# Range:"); 59 | for( i = 0; i < 2; i++) 60 | { 61 | full_ctime( buff, 2400000.5 + mjd[i], 62 | FULL_CTIME_YMD | FULL_CTIME_DATE_ONLY 63 | | FULL_CTIME_LEADING_ZEROES | FULL_CTIME_MONTHS_AS_DIGITS); 64 | buff[4] = buff[7] = '-'; 65 | printf( " %s", buff); 66 | } 67 | printf( "\n"); 68 | } 69 | else if( *buff == '1' && !tle_checksum( buff)) 70 | { 71 | buff[7] = ' '; 72 | buff[17] = '\0'; 73 | if( strcmp( id, buff + 2)) 74 | n_ids_found++; 75 | strcpy( id, buff + 2); 76 | } 77 | } 78 | if( n_ids_found == 1) 79 | printf( "# ID: %s\n", id); 80 | fclose( ifile); 81 | } 82 | 83 | int main( const int unused_argc, const char **unused_argv) 84 | { 85 | char prev_line[100], line[100]; 86 | FILE *ifile = fopen( "tle_list.txt", "rb"); 87 | 88 | INTENTIONALLY_UNUSED_PARAMETER( unused_argc); 89 | INTENTIONALLY_UNUSED_PARAMETER( unused_argv); 90 | assert( ifile); 91 | *prev_line = '\0'; 92 | while( fgets( line, sizeof( line), ifile)) 93 | { 94 | if( !memcmp( line, "# Include ", 10) 95 | && memcmp( line + 10, "old_tles", 8)) 96 | get_range_info( line + 10, !memcmp( prev_line, "# Range:", 8)); 97 | printf( "%s", line); 98 | strcpy( prev_line, line); 99 | } 100 | fclose( ifile); 101 | return( 0); 102 | } 103 | -------------------------------------------------------------------------------- /test.tle: -------------------------------------------------------------------------------- 1 | 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 2 | 3 | 1 11801U 80230.29629788 .01431103 00000-0 14311-1 2 4 | 2 11801U 46.7916 230.4354 7318036 47.4722 10.4117 2.28537848 2 5 | 6 | Times: 0 360 720 1080 1440 7 | 8 | # I played around with some zero-e satellites to check that bug: 9 | #1 11801U 80230.29629788 .01431103 00000-0 14311-1 2 10 | #2 11801U 46.7916 230.4354 0000000 47.4722 10.4117 2.28537848 4 11 | 12 | 1 23581U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214 13 | 2 23581 1.1236 93.7945 0005741 214.4722 151.5103 1.00270260 23672 14 | /* above is GOES 9 */ 15 | 1 11871U 80057A 01309.36911127 -.00000499 +00000-0 +10000-3 0 08380 16 | 2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886 17 | /* above is Cosmos 1191 */ 18 | 1 09931U 77029A 01309.17453186 -.00000329 +00000-0 +10000-3 0 05967 19 | 2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451 20 | 21 | /* Switch around to different times to test: */ 22 | 23 | Times: 1440 0 360 1080 720 719 -2880 24 | /* Cosmos 1217 */ 25 | 1 12032U 80085A 01309.42683181 .00000182 00000-0 10000-3 0 3499 26 | 2 12032 65.2329 86.7607 7086222 172.0967 212.4632 2.00879501101699 27 | /* Molniya 3-19Rk */ 28 | 1 13446U 82083E 01283.10818257 .00098407 45745-7 54864-3 0 6240 29 | 2 13446 62.1717 83.8458 7498877 273.9677 320.2568 2.06357523137203 30 | /* Ariane Deb */ 31 | 1 23246U 91015G 01311.70347086 .00004957 00000-0 43218-2 0 8190 32 | 2 23246 7.1648 263.6949 5661268 241.8299 50.5793 4.44333001129208 33 | 1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 87 34 | 2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 1058 35 | 36 | 1 00553U 63004A 77069.11186343 -.00000050 +00000-0 +00000-0 0 00056 37 | 2 00553 033.5060 020.0500 0337500 270.6300 085.5000 01.00948389000106 38 | /* above is Syncom 1 */ 39 | Unknown 98003 40 | 1 98003U 01079.82102009 0.00000000 00000-0 +00000+0 0 01 41 | 2 98003 15.5935 354.8013 0015600 263.1079 96.8986 0.99816000 03 42 | 95586 A 43 | 1 99331U 95586 A 01083.00000000 .00000000 00000-0 00000+0 0 02 44 | 2 99331 2.0413 66.6424 0042740 166.0448 281.2000 1.00273142 04 45 | 96750A 46 | 1 99337U 96750A 01088.00000000 .00000000 00000-0 00000+0 0 07 47 | 2 99337 6.1829 349.1711 0918991 187.9440 347.1192 1.00269427 03 48 | Iridium 921tum 5.5 6.6 49 | 1 24873U 97034E 02082.49700151 .00007707 00000-0 80741-3 0 6717 50 | 2 24873 86.3901 131.0630 0010160 338.7719 21.3095 14.89420428254310 51 | 96051J 52 | 1 24875U 96051J 02082.46969260 .00045815 00000-0 12983-1 0 540 53 | 2 24875 71.1295 245.1704 0037175 68.5321 292.0237 14.44819470284117 54 | 97035A 55 | 1 24876U 97035A 02078.28561105 +.00000010 +00000-0 +00000-0 0 09256 56 | 2 24876 055.7160 084.3160 0019362 356.8391 003.1921 02.00573292034149 57 | 94029GM 58 | 1 24138U 94029GM 02078.77267326 +.00026592 +00000-0 +63509-2 0 09707 59 | 2 24138 081.5941 007.5003 0284757 314.7693 043.0606 14.31683973298361 60 | 94029HA 61 | 1 24151U 94029HA 02078.68402149 +.00005369 +00000-0 +11280-2 0 09568 62 | 2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215 63 | 94029HF 64 | 1 24156U 94029HF 02079.22959898 .00078684 00000-0 11005-1 0 3773 65 | 2 24156 82.1981 162.4355 0207818 237.7291 120.3519 14.63437792297579 66 | Molniya 2-9 4.5 15 67 | 1 07276U 74026A 02076.77143097 -.00000040 +00000-0 +10000-3 0 09926 68 | 2 07276 064.4058 003.2732 6887090 270.2374 017.8845 02.24788602077456 69 | 73086EZ 70 | 1 07191U 73086EZ 02076.63595131 -.00000031 00000-0 10000-3 0 9045 71 | 2 07191 102.1490 334.0121 0253607 54.7437 307.7251 12.10431881825991 72 | 73086FA 73 | 1 07192U 73086FA 02077.02656547 -.00000031 00000-0 10000-3 0 6091 74 | 2 07192 102.1562 252.5740 0443592 220.8713 135.8303 11.59390485200610 75 | GPS-0003 5.0 .53 76 | 1 11054U 78093A 02069.41465182 -.00000030 00000-0 00000+0 0 7661 77 | 2 11054 62.4872 198.8383 0057450 192.9653 166.8284 1.93504662169140 78 | Molniya 3-10 5.0 0.0 0.0 4.5 15 79 | 1 11057U 78095A 02077.63138055 .00000149 00000-0 10000-3 0 5309 80 | 2 11057 63.7422 203.7241 6717022 282.5815 14.8175 2.34176921180342 81 | 99066A 82 | 1 25989U 99066A 02080.37500000 .00000000 00000-0 00000+0 0 4082 83 | 2 25989 34.6613 172.6861 7995138 107.4591 3.7150 0.50103982 1159 84 | XMM Ari Rk 3.0 20 85 | 1 25990U 99066B 02082.00000000 .00000000 00000-0 00000+0 0 3260 86 | 2 25990 44.2277 151.3168 8216838 138.1402 356.4324 0.55308221 1445 87 | 88 | Now do some tests of "plain, ordinary" SGP: 89 | 90 | Ephem 1 91 | 92 | 1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 87 93 | 2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 1058 94 | 73086EZ 95 | 1 07191U 73086EZ 02076.63595131 -.00000031 00000-0 10000-3 0 9045 96 | 2 07191 102.1490 334.0121 0253607 54.7437 307.7251 12.10431881825991 97 | 94029HA 98 | 1 24151U 94029HA 02078.68402149 +.00005369 +00000-0 +11280-2 0 09568 99 | 2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215 100 | 101 | Now test some SxP8 cases: 102 | 103 | Ephem 4 104 | 105 | 1 11801U 80230.29629788 .01431103 00000-0 14311-1 2 106 | 2 11801U 46.7916 230.4354 7318036 47.4722 10.4117 2.28537848 2 107 | Times: 1440 0 360 1080 720 719 -2880 108 | 1 23581U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214 109 | 2 23581 1.1236 93.7945 0005741 214.4722 151.5103 1.00270260 23672 110 | /* above is GOES 9 */ 111 | 1 11871U 80057A 01309.36911127 -.00000499 +00000-0 +10000-3 0 08380 112 | 2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886 113 | /* above is Cosmos 1191 */ 114 | 1 09931U 77029A 01309.17453186 -.00000329 +00000-0 +10000-3 0 05967 115 | 2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451 116 | /* Cosmos 1217 */ 117 | 1 12032U 80085A 01309.42683181 .00000182 00000-0 10000-3 0 3499 118 | 2 12032 65.2329 86.7607 7086222 172.0967 212.4632 2.00879501101699 119 | /* Molniya 3-19Rk */ 120 | 1 13446U 82083E 01283.10818257 .00098407 45745-7 54864-3 0 6240 121 | 2 13446 62.1717 83.8458 7498877 273.9677 320.2568 2.06357523137203 122 | /* Ariane Deb */ 123 | 1 23246U 91015G 01311.70347086 .00004957 00000-0 43218-2 0 8190 124 | 2 23246 7.1648 263.6949 5661268 241.8299 50.5793 4.44333001129208 125 | 1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 87 126 | 2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 1058 127 | 128 | 1 00553U 63004A 77069.11186343 -.00000050 +00000-0 +00000-0 0 00056 129 | 2 00553 033.5060 020.0500 0337500 270.6300 085.5000 01.00948389000106 130 | /* above is Syncom 1 */ 131 | 132 | Now switch back to SxP4: 133 | Ephem 2 134 | Unknown 98003 135 | 1 98003U 01079.82102009 0.00000000 00000-0 +00000+0 0 01 136 | 2 98003 15.5935 354.8013 0015600 263.1079 96.8986 0.99816000 03 137 | 95586 A 138 | 1 99331U 95586 A 01083.00000000 .00000000 00000-0 00000+0 0 02 139 | 2 99331 2.0413 66.6424 0042740 166.0448 281.2000 1.00273142 04 140 | 96750A 141 | 1 99337U 96750A 01088.00000000 .00000000 00000-0 00000+0 0 07 142 | 2 99337 6.1829 349.1711 0918991 187.9440 347.1192 1.00269427 03 143 | Iridium 921tum 5.5 6.6 144 | 1 24873U 97034E 02082.49700151 .00007707 00000-0 80741-3 0 6717 145 | 2 24873 86.3901 131.0630 0010160 338.7719 21.3095 14.89420428254310 146 | 96051J 147 | 1 24875U 96051J 02082.46969260 .00045815 00000-0 12983-1 0 540 148 | 2 24875 71.1295 245.1704 0037175 68.5321 292.0237 14.44819470284117 149 | 97035A 150 | 1 24876U 97035A 02078.28561105 +.00000010 +00000-0 +00000-0 0 09256 151 | 2 24876 055.7160 084.3160 0019362 356.8391 003.1921 02.00573292034149 152 | 94029GM 153 | 1 24138U 94029GM 02078.77267326 +.00026592 +00000-0 +63509-2 0 09707 154 | 2 24138 081.5941 007.5003 0284757 314.7693 043.0606 14.31683973298361 155 | 94029HA 156 | 1 24151U 94029HA 02078.68402149 +.00005369 +00000-0 +11280-2 0 09568 157 | 2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215 158 | 94029HF 159 | 1 24156U 94029HF 02079.22959898 .00078684 00000-0 11005-1 0 3773 160 | 2 24156 82.1981 162.4355 0207818 237.7291 120.3519 14.63437792297579 161 | Molniya 2-9 4.5 15 162 | 1 07276U 74026A 02076.77143097 -.00000040 +00000-0 +10000-3 0 09926 163 | 2 07276 064.4058 003.2732 6887090 270.2374 017.8845 02.24788602077456 164 | 73086EZ 165 | 1 07191U 73086EZ 02076.63595131 -.00000031 00000-0 10000-3 0 9045 166 | 2 07191 102.1490 334.0121 0253607 54.7437 307.7251 12.10431881825991 167 | 73086FA 168 | 1 07192U 73086FA 02077.02656547 -.00000031 00000-0 10000-3 0 6091 169 | 2 07192 102.1562 252.5740 0443592 220.8713 135.8303 11.59390485200610 170 | GPS-0003 5.0 .53 171 | 1 11054U 78093A 02069.41465182 -.00000030 00000-0 00000+0 0 7661 172 | 2 11054 62.4872 198.8383 0057450 192.9653 166.8284 1.93504662169140 173 | Molniya 3-10 5.0 0.0 0.0 4.5 15 174 | 1 11057U 78095A 02077.63138055 .00000149 00000-0 10000-3 0 5309 175 | 2 11057 63.7422 203.7241 6717022 282.5815 14.8175 2.34176921180342 176 | 99066A 177 | 1 25989U 99066A 02080.37500000 .00000000 00000-0 00000+0 0 4082 178 | 2 25989 34.6613 172.6861 7995138 107.4591 3.7150 0.50103982 1159 179 | XMM Ari Rk 3.0 20 180 | 1 25990U 99066B 02082.00000000 .00000000 00000-0 00000+0 0 3260 181 | 2 25990 44.2277 151.3168 8216838 138.1402 356.4324 0.55308221 1445 182 | 28 Jun 2007: test some imaginary 0-incl and 90-incl cases 183 | (Just taking some above cases and resetting incl) 184 | XMM Ari Rk 3.0 20 185 | 1 95990U 99066B 02082.00000000 .00000000 00000-0 00000+0 0 3260 186 | 2 95990 00.0000 151.3168 8216838 138.1402 356.4324 0.55308221 1445 187 | XMM Ari Rk 3.0 20 188 | 1 95991U 99066B 02082.00000000 .00000000 00000-0 00000+0 0 3260 189 | 2 95991 90.0000 151.3168 8216838 138.1402 356.4324 0.55308221 1445 190 | GPS-0003 5.0 .53 191 | 1 91054U 78093A 02069.41465182 -.00000030 00000-0 00000+0 0 7661 192 | 2 91054 00.0000 198.8383 0057450 192.9653 166.8284 1.93504662169140 193 | GPS-0003 5.0 .53 194 | 1 91055U 78093A 02069.41465182 -.00000030 00000-0 00000+0 0 7661 195 | 2 91055 90.0000 198.8383 0057450 192.9653 166.8284 1.93504662169140 196 | GOES-9 197 | 1 93581U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214 198 | 2 93581 0.0000 93.7945 0005741 214.4722 151.5103 1.00270260 23672 199 | GOES-9 200 | 1 93582U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214 201 | 2 93582 90.0000 93.7945 0005741 214.4722 151.5103 1.00270260 23672 202 | 94029HF 203 | 1 94156U 94029HF 02079.22959898 .00078684 00000-0 11005-1 0 3773 204 | 2 94156 00.0000 162.4355 0207818 237.7291 120.3519 14.63437792297579 205 | 94029HF 206 | 1 94156U 94029HF 02079.22959898 .00078684 00000-0 11005-1 0 3773 207 | 2 94156 90.0000 162.4355 0207818 237.7291 120.3519 14.63437792297579 208 | -------------------------------------------------------------------------------- /test2.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | /* 4 | * test2.cpp 5 July 2002 5 | * 6 | * A skeleton main() function to demonstrate the use of 7 | * the various NORAD ephemerides. It reads in the file 8 | * 'test.tle' and computes ephemerides at assorted times for 9 | * all elements in the file. The times used, and the choice 10 | * of SxP4 vs. SxP8, can be switched with various keywords in 11 | * the 'test.tle' file. The result should match 'test2.txt'. 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include "norad.h" 18 | 19 | /* Main program */ 20 | int main( int argc, char **argv) 21 | { 22 | double vel[3], pos[3]; /* Satellite position and velocity vectors */ 23 | FILE *ifile = fopen( (argc == 1) ? "test.tle" : argv[1], "rb"); 24 | tle_t tle; /* Pointer to two-line elements set for satellite */ 25 | char line1[100], line2[100]; 26 | int n_times = 5, dundee_output = 0; 27 | double times[400]; 28 | int ephem = TLE_EPHEMERIS_TYPE_SGP4; /* default to SGP4 */ 29 | int i; /* Index for loops etc */ 30 | 31 | for( i = 2; i < argc; i++) 32 | switch( argv[i][0]) 33 | { 34 | case 's': case 'S': 35 | sxpx_set_dpsec_integration_step( atof( argv[i] + 1)); 36 | break; 37 | case 'd': case 'D': 38 | sxpx_set_implementation_param( SXPX_DUNDEE_COMPLIANCE, 1); 39 | break; 40 | case 'o': case 'O': 41 | { 42 | const int new_order = atoi( argv[i] + 1); 43 | 44 | sxpx_set_implementation_param( 45 | SXPX_DPSEC_INTEGRATION_ORDER, new_order); 46 | printf( "Set to order %d\n", new_order); 47 | } 48 | break; 49 | default: 50 | break; 51 | } 52 | 53 | if( !ifile) 54 | { 55 | printf( "Couldn't open input TLE file\n"); 56 | exit( -1); 57 | } 58 | for( i = 0; i < n_times; i++) 59 | times[i] = (double)(i * 30); 60 | if( fgets( line1, sizeof( line1), ifile)) 61 | while( fgets( line2, sizeof( line2), ifile)) 62 | { 63 | if( !strncmp( line2, "Ephem ", 6)) 64 | ephem = (line2[6] - '0'); 65 | else if( !strncmp( line2, "Dundee ", 7)) 66 | { 67 | double t0, step; 68 | 69 | sscanf( line2 + 7, "%lf %lf %d", &t0, &step, &n_times); 70 | for( i = 0; i < n_times; i++) 71 | times[i] = t0 + (double)i * step; 72 | sxpx_set_implementation_param( SXPX_DUNDEE_COMPLIANCE, 1); 73 | dundee_output = 1; 74 | } 75 | else if( !strncmp( line2, "Times: ", 7)) 76 | { 77 | int loc = 7, bytes_read; 78 | 79 | n_times = 0; 80 | while( sscanf( line2 + loc, "%lf%n", times + n_times, &bytes_read) == 1) 81 | { 82 | loc += bytes_read; 83 | n_times++; 84 | } 85 | } 86 | else if( parse_elements( line1, line2, &tle) >= 0) 87 | { /* hey! we got a TLE! */ 88 | int is_deep = select_ephemeris( &tle); 89 | const char *ephem_names[6] = { "???", "SGP ", "SGP4", "SDP4", "SGP8", "SDP8" }; 90 | double sat_params[N_SAT_PARAMS]; 91 | 92 | if( is_deep) 93 | if( ephem == TLE_EPHEMERIS_TYPE_SGP4 || ephem == TLE_EPHEMERIS_TYPE_SGP8) 94 | ephem++; /* switch to an SDPx model */ 95 | if( !is_deep) 96 | if( ephem == TLE_EPHEMERIS_TYPE_SDP4 || ephem == TLE_EPHEMERIS_TYPE_SDP8) 97 | ephem--; /* switch to an SGPx model */ 98 | line1[69] = line2[69] = '\0'; 99 | if( dundee_output) 100 | printf( "#%s\n#%s\n", line1, line2); 101 | else 102 | printf( "%s\n%s\n", line1, line2); 103 | if( is_deep) 104 | printf("Deep-Space type Ephemeris (%s) selected:", 105 | ephem_names[ephem]); 106 | else 107 | printf("Near-Earth type Ephemeris (%s) selected:", 108 | ephem_names[ephem]); 109 | 110 | /* Print some titles for the results */ 111 | printf("\nEphem:%s Tsince " 112 | "X/Xdot Y/Ydot Z/Zdot\n", ephem_names[ephem]); 113 | 114 | /* Calling of NORAD routines */ 115 | /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8) */ 116 | /* will be called in turn with the appropriate TLE set */ 117 | switch( ephem) 118 | { 119 | case TLE_EPHEMERIS_TYPE_SGP: 120 | SGP_init( sat_params, &tle); 121 | break; 122 | case TLE_EPHEMERIS_TYPE_SGP4: 123 | SGP4_init( sat_params, &tle); 124 | break; 125 | case TLE_EPHEMERIS_TYPE_SGP8: 126 | SGP8_init( sat_params, &tle); 127 | break; 128 | case TLE_EPHEMERIS_TYPE_SDP4: 129 | SDP4_init( sat_params, &tle); 130 | break; 131 | case TLE_EPHEMERIS_TYPE_SDP8: 132 | SDP8_init( sat_params, &tle); 133 | break; 134 | } 135 | 136 | for( i = 0; i < n_times; i++) 137 | { 138 | switch( ephem) 139 | { 140 | case TLE_EPHEMERIS_TYPE_SGP: 141 | SGP(times[i], &tle, sat_params, pos, vel); 142 | break; 143 | case TLE_EPHEMERIS_TYPE_SGP4: 144 | SGP4(times[i], &tle, sat_params, pos, vel); 145 | break; 146 | case TLE_EPHEMERIS_TYPE_SGP8: 147 | SGP8(times[i], &tle, sat_params, pos, vel); 148 | break; 149 | case TLE_EPHEMERIS_TYPE_SDP4: 150 | SDP4(times[i], &tle, sat_params, pos, vel); 151 | break; 152 | case TLE_EPHEMERIS_TYPE_SDP8: 153 | SDP8(times[i], &tle, sat_params, pos, vel); 154 | break; 155 | } 156 | 157 | /* Calculate and print results */ 158 | vel[0] /= 60.; /* cvt km/minute to km/second */ 159 | vel[1] /= 60.; 160 | vel[2] /= 60.; 161 | 162 | if( dundee_output) 163 | { 164 | printf("%12.4f %16.8f %16.8f %16.8f", 165 | times[i],pos[0],pos[1],pos[2]); 166 | printf(" %16.8f %16.8f %16.8f\n", 167 | vel[0],vel[1],vel[2]); 168 | } 169 | else 170 | { 171 | printf("%5d %12.4f %16.8f %16.8f %16.8f\n", tle.norad_number, 172 | times[i],pos[0],pos[1],pos[2]); 173 | printf(" %16.8f %16.8f %16.8f\n", 174 | vel[0],vel[1],vel[2]); 175 | } 176 | } /* End of for( i = 0; i < n_times; i++) */ 177 | printf( "\n"); 178 | } 179 | strcpy( line1, line2); 180 | } 181 | fclose( ifile); 182 | return(0); 183 | } /* End of main() */ 184 | 185 | /*------------------------------------------------------------------*/ 186 | -------------------------------------------------------------------------------- /test3.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | /* 4 | * test3.cpp 8 October 2002 5 | * 6 | * A skeleton main() function to test the speed of SxPx 7 | * functions, and the positional differences between SGPx and SDPx. 8 | * (I was curious as to whether the extra code in SDPx made any really 9 | * significant difference; it was starting to look as if SDPx was a 10 | * waste of code. Turns out that it depends greatly on the satellite 11 | * in question; I'll post results in a bit, when I find the time.) 12 | * 13 | * Also, this demonstrates how to use the new dynamically-loaded 14 | * functions in 'dynamic.cpp'. This way, if your program can't 15 | * find 'sat_code.dll', it'll gracefully give you a message about it. 16 | * In the case of my own _Guide_ planetarium software, if it can't 17 | * find the DLL, it can fall back on its own SGP4-only code. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "norad.h" 26 | 27 | /* Main program */ 28 | int main( int argc, char **argv) 29 | { 30 | FILE *ifile = fopen( (argc == 1) ? "test.tle" : argv[1], "rb"); 31 | tle_t tle; /* Pointer to two-line elements set for satellite */ 32 | char line1[100], line2[100]; 33 | double t_since = atof( argv[2]); 34 | double rms_diff = 0.; 35 | int verbose = 0, n_found = 0, n_runs = 1, show_sdp = 1; 36 | int ephem = 1; /* default to SGP4 */ 37 | int i, j; /* Index for loops etc */ 38 | clock_t t0; 39 | 40 | if( !ifile) 41 | { 42 | printf( "Couldn't open input TLE file\n"); 43 | exit( -1); 44 | } 45 | 46 | for( i = 3; i < argc; i++) 47 | if( argv[i][0] == '-') 48 | switch( argv[i][1]) 49 | { 50 | case 'v': 51 | verbose = atoi( argv[i] + 2); 52 | break; 53 | case 'n': 54 | n_runs = atoi( argv[i] + 2); 55 | break; 56 | case 's': 57 | show_sdp = atoi( argv[i] + 2); 58 | break; 59 | default: 60 | printf( "Option '%s' ignored\n", argv[i]); 61 | break; 62 | } 63 | 64 | fgets( line1, sizeof( line1), ifile); 65 | t0 = clock( ); 66 | while( fgets( line2, sizeof( line2), ifile)) 67 | { 68 | if( line1[0] == '1' && line2[0] == '2' && 69 | parse_elements( line1, line2, &tle) >= 0) /* hey! we got a TLE! */ 70 | if( select_ephemeris( &tle) && show_sdp < 2) 71 | { 72 | double sgp_sat_params[N_SAT_PARAMS]; 73 | double sdp_sat_params[N_SAT_PARAMS]; 74 | double perigee; 75 | 76 | #ifdef STATIC_LINK 77 | SGP4_init( sgp_sat_params, &tle); 78 | if( show_sdp) 79 | SDP4_init( sdp_sat_params, &tle); 80 | #else 81 | if( SXPX_init( sgp_sat_params, &tle, 1)) 82 | { 83 | printf( "Couldn't load 'sat_code.dll'\n"); 84 | exit( -1); 85 | } 86 | if( show_sdp) 87 | SXPX_init( sdp_sat_params, &tle, 3); 88 | #endif 89 | perigee = sgp_sat_params[9] * (1. - tle.eo) - 1.; 90 | line2[63] = '\0'; 91 | if( verbose) 92 | printf( "%5ld %s", (long)( perigee * 6378.14), line2); 93 | for( j = 0; j <= n_runs; j++) 94 | { 95 | double posn2[3], pos[3], vel[3]; 96 | double delta, d2 = 0.; 97 | 98 | #ifdef STATIC_LINK 99 | SGP4( t_since * (double)j, &tle, sgp_sat_params, posn2, vel); 100 | if( show_sdp) 101 | SDP4( t_since * (double)j, &tle, sdp_sat_params, pos, vel); 102 | #else 103 | SXPX( t_since * (double)j, &tle, sgp_sat_params, posn2, vel, 1); 104 | if( show_sdp) 105 | SXPX( t_since * (double)j, &tle, sdp_sat_params, pos, vel, 3); 106 | #endif 107 | if( !show_sdp) 108 | memcpy( pos, posn2, 3 * sizeof( double)); 109 | 110 | for( i = 0; i < 3; i++) 111 | { 112 | delta = pos[i] - posn2[i]; 113 | d2 += delta * delta; 114 | } 115 | if( j == 1) 116 | rms_diff += d2; 117 | if( verbose) 118 | printf( "%8.1lf%s", sqrt( d2), (j == n_runs ? "\n" : "")); 119 | } 120 | n_found++; 121 | } 122 | strcpy( line1, line2); 123 | } 124 | fclose( ifile); 125 | t0 = clock( ) - t0; 126 | printf( "%d deep-space objects found\n", n_found); 127 | printf( "%lf seconds elapsed\n", (double)t0 / (double)CLOCKS_PER_SEC); 128 | if( n_found) 129 | printf( "RMS difference = %.1lf\n", sqrt( rms_diff / (double)n_found)); 130 | return(0); 131 | } /* End of main() */ 132 | 133 | /*------------------------------------------------------------------*/ 134 | -------------------------------------------------------------------------------- /test_des.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2021, Project Pluto. See LICENSE. */ 2 | /* Code to test Alpha-5 and 'extended' Alpha-5 (Super-5) designation 3 | schemes. These enable us to have nine-digit NORAD numbers 4 | while still fitting (most of the) requirements of the current TLE 5 | format. See comments about the Alpha-5 and Super-5 schemes in 6 | 'get_el.cpp'. 7 | 8 | Run without command-line arguments, this tries out all possible 9 | Super-5 designations with a "round-trip" test : encode, then 10 | decode to ensure we get the original number. A couple past 11 | 34^5 are run to make sure that they fail as expected. In this 12 | scheme, the lexical order of Super-5 designations should 13 | mismatch the numerical order at several points, and this test 14 | code verifies that. */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "norad.h" 22 | 23 | int main( const int argc, const char **argv) 24 | { 25 | char buff[160], *line1 = buff, line2[80]; 26 | int n; 27 | tle_t tle; 28 | const int one_billion = 1000000000; 29 | 30 | strcpy( line1, 31 | "1 00005U 58002B 21124.86416743 -.00000116 00000-0 -00000-0 0 9998"); 32 | strcpy( line2, 33 | "2 00005 34.2496 318.0626 1847159 358.7427 0.8906 10.84838792240168"); 34 | parse_elements( line1, line2, &tle); 35 | if( argc > 1) 36 | { 37 | n = tle.norad_number = atoi( argv[1]); 38 | write_elements_in_tle_format( buff, &tle); 39 | printf( "Super-5 version : '%.5s'\n", buff + 2); 40 | parse_elements( line1, line2, &tle); 41 | printf( "Got back NORAD %d\n", tle.norad_number); 42 | assert( n == tle.norad_number); 43 | return( 0); 44 | } 45 | 46 | printf( "Testing all Super-5 designations. This should take an hour or two.\n"); 47 | for( n = 0; n < one_billion + 2; n++) 48 | { 49 | tle.norad_number = n; 50 | write_elements_in_tle_format( buff, &tle); 51 | parse_elements( line1, line2, &tle); 52 | if( tle.norad_number != n) 53 | printf( "Got back NORAD %d (should be %d)\n%s\n", 54 | tle.norad_number, n, buff); 55 | if( !(n % 1000000)) /* progress indicator */ 56 | { 57 | printf( "."); 58 | fflush( stdout); 59 | } 60 | } 61 | return( 0); 62 | } 63 | -------------------------------------------------------------------------------- /test_out.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include "norad.h" 7 | #include "norad_in.h" 8 | 9 | #define PI \ 10 | 3.1415926535897932384626433832795028841971693993751058209749445923 11 | 12 | void greg_day_to_dmy( const long jd, int *day, 13 | int *month, long *year) 14 | { 15 | const long mar_1_year_0 = 1721120L; /* JD 1721120 = 1.5 Mar 0 Greg */ 16 | const long one_year = 365L; 17 | const long four_years = 4 * one_year + 1; 18 | const long century = 25 * four_years - 1L; /* days in 100 'normal' yrs */ 19 | const long quad_cent = century * 4 + 1; /* days in 400 years */ 20 | long days = jd - mar_1_year_0; 21 | long day_in_cycle = days % quad_cent; 22 | 23 | if( day_in_cycle < 0) 24 | day_in_cycle += quad_cent; 25 | *year = ((days - day_in_cycle) / quad_cent) * 400L; 26 | *year += (day_in_cycle / century) * 100L; 27 | if( day_in_cycle == quad_cent - 1) /* extra leap day every 400 years */ 28 | { 29 | *month = 2; 30 | *day = 29; 31 | return; 32 | } 33 | day_in_cycle %= century; 34 | *year += (day_in_cycle / four_years) * 4L; 35 | day_in_cycle %= four_years; 36 | *year += day_in_cycle / one_year; 37 | if( day_in_cycle == four_years - 1) /* extra leap day every 4 years */ 38 | { 39 | *month = 2; 40 | *day = 29; 41 | return; 42 | } 43 | day_in_cycle %= one_year; 44 | *month = 5 * (day_in_cycle / 153L); 45 | day_in_cycle %= 153L; 46 | *month += 2 * (day_in_cycle / 61L); 47 | day_in_cycle %= 61L; 48 | if( day_in_cycle >= 31) 49 | { 50 | (*month)++; 51 | day_in_cycle -= 31; 52 | } 53 | *month += 3; 54 | *day = day_in_cycle + 1; 55 | if( *month > 12) 56 | { 57 | *month -= 12; 58 | (*year)++; 59 | } 60 | } 61 | 62 | /* Example code to add BSTAR data using Ted Molczan's method. It just 63 | reads in TLEs, computes BSTAR if possible, then writes out the 64 | resulting modified TLE. 65 | 66 | Add the '-v' (verbose) switch, and it also writes out the orbital 67 | period and perigee/apogee distances. Eventually, I'll probably 68 | set it up to dump other data that are not immediately obvious 69 | just by looking at the TLEs... */ 70 | 71 | int main( const int argc, const char **argv) 72 | { 73 | FILE *ifile; 74 | const char *filename; 75 | char line0[100], line1[100], line2[100]; 76 | int i, verbose = 0; 77 | const char *norad = NULL, *intl = NULL; 78 | int legend_shown = 0; 79 | 80 | for( i = 2; i < argc; i++) 81 | if( argv[i][0] == '-') 82 | switch( argv[i][1]) 83 | { 84 | case 'v': 85 | verbose = 1; 86 | break; 87 | case 'n': 88 | norad = argv[i] + 2; 89 | if( !*norad && i < argc - 1) 90 | norad = argv[++i]; 91 | printf( "Looking for NORAD %s\n", norad); 92 | break; 93 | case 'i': 94 | intl = argv[i] + 2; 95 | if( !*intl && i < argc - 1) 96 | intl = argv[++i]; 97 | printf( "Looking for international ID %s\n", intl); 98 | break; 99 | default: 100 | printf( "'%s': unrecognized option\n", argv[i]); 101 | return( -1); 102 | break; 103 | } 104 | filename = (argc == 1 ? "all_tle.txt" : argv[1]); 105 | ifile = fopen( filename, "rb"); 106 | if( !ifile) 107 | { 108 | fprintf( stderr, "Couldn't open '%s': ", filename); 109 | perror( ""); 110 | return( -1); 111 | } 112 | *line0 = *line1 = '\0'; 113 | while( fgets( line2, sizeof( line2), ifile)) 114 | { 115 | if( *line1 == '1' && (!norad || !memcmp( line1 + 2, norad, 5)) 116 | && (!intl || !memcmp( line1 + 9, intl, strlen( intl))) 117 | && *line2 == '2') 118 | { 119 | tle_t tle; 120 | const int err_code = parse_elements( line1, line2, &tle); 121 | 122 | if( err_code >= 0) 123 | { 124 | char obuff[200]; 125 | double params[N_SGP4_PARAMS], c2; 126 | 127 | if( verbose && !legend_shown) 128 | { 129 | legend_shown = 1; 130 | printf( 131 | "1 NoradU COSPAR Epoch.epoch dn/dt/2 d2n/dt2/6 BSTAR T El# C\n" 132 | "2 NoradU Inclina RAAscNode Eccent ArgPeri MeanAno MeanMotion Rev# C\n"); 133 | 134 | } 135 | SGP4_init( params, &tle); 136 | c2 = params[0]; 137 | if( c2 && tle.xno) 138 | tle.bstar = tle.xndt2o / (tle.xno * c2 * 1.5); 139 | write_elements_in_tle_format( obuff, &tle); 140 | if( strlen( line0) < 60) 141 | printf( "%s", line0); 142 | printf( "%s", obuff); 143 | if( verbose) 144 | { 145 | const double a1 = pow(xke / tle.xno, two_thirds); /* in Earth radii */ 146 | long year, ijd; 147 | int month, day; 148 | double frac; 149 | 150 | printf( "Inclination: %8.4f ", tle.xincl * 180. / PI); 151 | printf( " Perigee: %.4f km\n", 152 | (a1 * (1. - tle.eo) - 1.) * earth_radius_in_km); 153 | 154 | printf( "Asc node: %8.4f ", tle.xnodeo * 180. / PI); 155 | printf( " Apogee: %.4f km\n", 156 | (a1 * (1. + tle.eo) - 1.) * earth_radius_in_km); 157 | 158 | printf( "Arg perigee: %8.4f ", tle.omegao * 180. / PI); 159 | printf( " Orbital period: %.4f min\n", 160 | 2. * pi / tle.xno); 161 | 162 | printf( "Mean anom: %8.4f ", tle.xmo * 180. / PI); 163 | ijd = (long)( tle.epoch + 0.5); 164 | frac = tle.epoch + 0.5 - ijd; 165 | greg_day_to_dmy( ijd, &day, &month, &year); 166 | printf( " Epoch: JD %.5f = %ld-%02d-%02d.%05d\n", tle.epoch, 167 | year, month, day, (int)( frac * 100000.)); 168 | printf( "NORAD number %7d ", tle.norad_number); 169 | printf( "Semimajor axis : %4f km\n", a1 * earth_radius_in_km); 170 | } 171 | if( err_code) 172 | printf( "Checksum error %d\n", err_code); 173 | } 174 | } 175 | strcpy( line0, line1); 176 | strcpy( line1, line2); 177 | } 178 | fclose( ifile); 179 | return( 0); 180 | } 181 | -------------------------------------------------------------------------------- /test_sat.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | /* 4 | * main.c April 10 2001 5 | * 6 | * A skeleton main() function to demonstrate the use of 7 | * the various NORAD ephimerides in the norad.c file. 8 | * The TLE sets used are those provided in NORAD's spacetrack 9 | * report #3 so that a comparison with their own results 10 | * can be made. The results produced by these software agree 11 | * with NORAD's to the 5th or 6th figure, whatever this means! 12 | * (But please note that NORAD uses mostly 'float' types with 13 | * only a few 'doubles' for the critical variables). 14 | */ 15 | 16 | #include 17 | #include 18 | #include "norad.h" 19 | 20 | #define INTENTIONALLY_UNUSED_PARAMETER( param) (void)(param) 21 | 22 | static double test_data[5 * 6 * 5] = { 23 | 24 | /* SGP: */ 25 | 2328.96594238, -5995.21600342, 1719.97894287, 26 | 2.91110113, -0.98164053, -7.09049922, 27 | 2456.00610352, -6071.94232177, 1222.95977784, 28 | 2.67852119, -0.44705850, -7.22800565, 29 | 2567.39477539, -6112.49725342, 713.97710419, 30 | 2.43952477, 0.09884824, -7.31899641, 31 | 2663.03179932, -6115.37414551, 195.73919105, 32 | 2.19531813, 0.65333930, -7.36169147, 33 | 2742.85470581, -6079.13580322, -328.86091614, 34 | 1.94707947, 1.21346101, -7.35499924, 35 | 36 | /* SGP4: */ 37 | 2328.97048951, -5995.22076416, 1719.97067261, 38 | 2.91207230, -0.98341546, -7.09081703, 39 | 2456.10705566, -6071.93853760, 1222.89727783, 40 | 2.67938992, -0.44829041, -7.22879231, 41 | 2567.56195068, -6112.50384522, 713.96397400, 42 | 2.44024599, 0.09810869, -7.31995916, 43 | 2663.09078980, -6115.48229980, 196.39640427, 44 | 2.19611958, 0.65241995, -7.36282432, 45 | 2742.55133057, -6079.67144775, -326.38095856, 46 | 1.94850229, 1.21106251, -7.35619372, 47 | 48 | /* SGP8: */ 49 | 2328.87265015, -5995.21289063, 1720.04884338, 50 | 2.91210661, -0.98353850, -7.09081554, 51 | 2456.04577637, -6071.90490722, 1222.84086609, 52 | 2.67936245, -0.44820847, -7.22888553, 53 | 2567.68383789, -6112.40881348, 713.29282379, 54 | 2.43992555, 0.09893919, -7.32018769, 55 | 2663.49508667, -6115.18182373, 194.62816810, 56 | 2.19525236, 0.65453661, -7.36308974, 57 | 2743.29238892, -6078.90783691, -329.73434067, 58 | 1.94680957, 1.21500109, -7.35625595, 59 | 60 | /* SDP4: */ 61 | 7473.37066650, 428.95261765, 5828.74786377, 62 | 5.10715413, 6.44468284, -0.18613096, 63 | -3305.22537232, 32410.86328125, -24697.1767581, 64 | -1.30113538, -1.15131518, -0.28333528, 65 | 14271.28759766, 24110.46411133, -4725.76837158, 66 | -0.32050445, 2.67984074, -2.08405289, 67 | -9990.05883789, 22717.35522461, -23616.89062501, 68 | -1.01667246, -2.29026759, 0.72892364, 69 | 9787.86975097, 33753.34667969, -15030.81176758, 70 | -1.09425066, 0.92358845, -1.52230928, 71 | 72 | /* SDP8: (gotta fix) */ 73 | 7469.47631836, 415.99390792, 5829.64318848, 74 | 5.11402285, 6.44403201, -0.18296110, 75 | -3337.38992310, 32351.39086914, -24658.63037109, 76 | -1.30200730, -1.15603013, -0.28164955, 77 | 14226.54333496, 24236.08740234, -4856.19744873, 78 | -0.33951668, 2.65315416, -2.08114153, 79 | -10151.59838867, 22223.69848633, -23392.39770508, 80 | -1.00112480, -2.33532837, 0.76987664, 81 | 9420.08203125, 33847.21875000, -15391.06469727, 82 | -1.11986055, 0.85410149, -1.49506933 }; 83 | 84 | /* Main program */ 85 | int main( const int argc, const char **unused_argv) 86 | { 87 | double vel[3], pos[3]; /* Satellite position and velocity vectors */ 88 | double *test_ptr = test_data; 89 | 90 | tle_t tle; /* Pointer to two-line elements set for satellite */ 91 | 92 | /* Data for the prediction type and time period */ 93 | double ts = 0.; /* Time since TLE epoch to start predictions */ 94 | double tf = 1440.; /* Time over which predictions are required */ 95 | double delt = 360.; /* Time interval between predictions */ 96 | 97 | double tsince; /* Time since epoch (in minutes) */ 98 | 99 | int i; /* Index for loops etc */ 100 | const char *tle_data[16] = { 101 | "1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 87", 102 | "2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 1058", 103 | "1 11801U 80230.29629788 .01431103 00000-0 14311-1 2", 104 | "2 11801U 46.7916 230.4354 7318036 47.4722 10.4117 2.28537848 2", 105 | /* GOES 9 */ 106 | "1 23581U 95025A 01311.43599209 -.00000094 00000-0 00000+0 0 8214", 107 | "2 23581 1.1236 93.7945 0005741 214.4722 151.5103 1.00270260 23672", 108 | /* Cosmos 1191 */ 109 | "1 11871U 80057A 01309.36911127 -.00000499 +00000-0 +10000-3 0 08380", 110 | "2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886", 111 | /* ESA-GEOS 1 */ 112 | "1 09931U 77029A 01309.17453186 -.00000329 +00000-0 +10000-3 0 05967", 113 | "2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451", 114 | /* Cosmos 1217 */ 115 | "1 12032U 80085A 01309.42683181 .00000182 00000-0 10000-3 0 3499", 116 | "2 12032 65.2329 86.7607 7086222 172.0967 212.4632 2.00879501101699", 117 | /* Molniya 3-19Rk */ 118 | "1 13446U 82083E 01283.10818257 .00098407 45745-7 54864-3 0 6240", 119 | "2 13446 62.1717 83.8458 7498877 273.9677 320.2568 2.06357523137203", 120 | /* Ariane Deb */ 121 | "1 23246U 91015G 01311.70347086 .00004957 00000-0 43218-2 0 8190", 122 | "2 23246 7.1648 263.6949 5661268 241.8299 50.5793 4.44333001129208" }; 123 | 124 | INTENTIONALLY_UNUSED_PARAMETER( unused_argv); 125 | for( i = 1; i <= 17; i++) /* Loop for each type of ephemeris */ 126 | { 127 | int tle_idx = ((i - 2) / 2) * 2, err_code; 128 | int ephem, is_deep; 129 | const char *ephem_names[6] = { NULL, "SGP ", "SGP4", "SGP8", "SDP4", "SDP8" }; 130 | double sat_params[N_SAT_PARAMS]; 131 | 132 | /* Select the sgp or sdp TLE set for use below */ 133 | if( tle_idx < 0) tle_idx = 0; 134 | printf( "\n%s\n%s", tle_data[tle_idx], tle_data[tle_idx + 1]); 135 | err_code = parse_elements( tle_data[tle_idx], tle_data[tle_idx + 1], &tle); 136 | if( err_code) 137 | printf( "\nError parsing elements: %d\n", err_code); 138 | 139 | if( i <= 5) 140 | ephem = i; 141 | else 142 | ephem = 4 + ((i - 5) % 2); 143 | 144 | /* Select ephemeris type */ 145 | /* Will select a "deep" (SDPx) or "general" (SGPx) ephemeris */ 146 | /* depending on the TLE parameters of the satellite: */ 147 | is_deep = select_ephemeris( &tle); 148 | 149 | /* printf( "BStar: %.8lf\n", tle.bstar); */ 150 | if( is_deep) 151 | printf("\nDeep-Space type Ephemeris (SDP*) selected:"); 152 | else 153 | printf("\nNear-Earth type Ephemeris (SGP*) selected:"); 154 | 155 | /* Print some titles for the results */ 156 | printf("\nEphem:%s Tsince " 157 | "X/Xdot Y/Ydot Z/Zdot\n", ephem_names[ephem]); 158 | 159 | /* Calling of NORAD routines */ 160 | /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8) */ 161 | /* will be called in turn with the appropriate TLE set */ 162 | switch( ephem) 163 | { 164 | case 1: 165 | SGP_init( sat_params, &tle); 166 | break; 167 | case 2: 168 | SGP4_init( sat_params, &tle); 169 | break; 170 | case 3: 171 | SGP8_init( sat_params, &tle); 172 | break; 173 | case 4: 174 | SDP4_init( sat_params, &tle); 175 | break; 176 | case 5: 177 | SDP8_init( sat_params, &tle); 178 | break; 179 | } 180 | 181 | for( tsince = ts; tsince <= tf; tsince += delt) 182 | { 183 | switch( ephem) 184 | { 185 | case 1: 186 | SGP(tsince, &tle, sat_params, pos, vel); 187 | break; 188 | case 2: 189 | SGP4(tsince, &tle, sat_params, pos, vel); 190 | break; 191 | case 3: 192 | SGP8(tsince, &tle, sat_params, pos, vel); 193 | break; 194 | case 4: 195 | SDP4(tsince, &tle, sat_params, pos, vel); 196 | break; 197 | case 5: 198 | SDP8(tsince, &tle, sat_params, pos, vel); 199 | break; 200 | } 201 | 202 | /* Calculate and print results */ 203 | vel[0] /= 60.; /* cvt km/minute to km/second */ 204 | vel[1] /= 60.; 205 | vel[2] /= 60.; 206 | 207 | if( argc > 1 && i <= 5) /* wanna show _difference from test data_ */ 208 | { 209 | pos[0] -= *test_ptr++; 210 | pos[1] -= *test_ptr++; 211 | pos[2] -= *test_ptr++; 212 | vel[0] -= *test_ptr++; 213 | vel[1] -= *test_ptr++; 214 | vel[2] -= *test_ptr++; 215 | } 216 | printf(" %12.4f %16.8f %16.8f %16.8f \n", 217 | tsince,pos[0],pos[1],pos[2]); 218 | printf(" %16.8f %16.8f %16.8f \n", 219 | vel[0],vel[1],vel[2]); 220 | 221 | } /* End of for(tsince = ts; tsince <= tf; tsince += delt) */ 222 | } /* End of for (i=1; i<=17; i++) */ 223 | 224 | return(0); 225 | } /* End of main() */ 226 | 227 | /*------------------------------------------------------------------*/ 228 | -------------------------------------------------------------------------------- /tle2mpc.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "watdefs.h" 8 | #include "norad.h" 9 | #include "afuncs.h" 10 | 11 | /* Code to read TLEs of the sort I post on github.com/Bill-Gray/tles, 12 | where there's one TLE per day, and output ephemerides of the sort 13 | the Minor Planet Center wants for their Distant Artsat Observation 14 | Page (DASO), at https://www.minorplanetcenter.net/iau/artsats/artsats.html. 15 | For DASO, we need geocentric ephems with positions only, in equatorial 16 | J2000, in AU, at 0.1-day intervals. So each time we read a TLE, ten 17 | positions are output. */ 18 | 19 | static void error_exit( void) 20 | { 21 | fprintf( stderr, "'tle2mpc' needs the name of an input TLE file and an output\n"); 22 | fprintf( stderr, "MPC ephemeris file. See 'tle2mpc.cpp' for details.\n"); 23 | exit( -1); 24 | } 25 | 26 | static const char *err_fgets( char *buff, size_t buffsize, FILE *ifile) 27 | { 28 | const char *rval = fgets( buff, (int)buffsize, ifile); 29 | 30 | assert( rval); 31 | return( rval); 32 | } 33 | 34 | int main( const int argc, const char **argv) 35 | { 36 | char buff[200]; 37 | FILE *ifile, *ofile; 38 | bool object_name_shown = false; 39 | 40 | if( argc < 3) 41 | error_exit( ); 42 | ifile = fopen( argv[1], "rb"); 43 | if( !ifile) 44 | { 45 | fprintf( stderr, "Couldn't open input file '%s'\n", argv[1]); 46 | error_exit( ); 47 | } 48 | ofile = fopen( argv[2], "wb"); 49 | if( !ofile) 50 | { 51 | fprintf( stderr, "Couldn't open output file '%s'\n", argv[2]); 52 | error_exit( ); 53 | } 54 | while( fgets( buff, sizeof( buff), ifile)) 55 | if( !memcmp( buff, "# Ephem range:", 14)) 56 | { 57 | const double mjd1 = atof( buff + 15); 58 | const double mjd2 = atof( buff + 28); 59 | 60 | fprintf( ofile, "%f 0.1 %d 0,1,1 ", 2400000.5 + mjd1, 61 | (int)( (mjd2 - mjd1) * 10. + .5)); 62 | } 63 | else if( !memcmp( buff, "# MJD ", 6)) 64 | { 65 | double mjd = atof( buff + 6); 66 | char line2[80]; 67 | tle_t tle; /* Structure for two-line elements set for satellite */ 68 | bool is_a_tle; 69 | 70 | err_fgets( buff, sizeof( buff), ifile); 71 | if( !object_name_shown) 72 | fprintf( ofile, "%s", buff); 73 | object_name_shown = true; 74 | err_fgets( buff, sizeof( buff), ifile); 75 | err_fgets( line2, sizeof( line2), ifile); 76 | is_a_tle = (parse_elements( buff, line2, &tle) >= 0); 77 | assert( is_a_tle); 78 | if( is_a_tle) 79 | { 80 | int i, j; 81 | double sat_params[N_SAT_PARAMS], precess_matrix[9]; 82 | const bool is_deep = select_ephemeris( &tle); 83 | const double year = 2000. + (mjd - 51545.) / 365.25; 84 | 85 | if( is_deep) 86 | SDP4_init( sat_params, &tle); 87 | else 88 | SGP4_init( sat_params, &tle); 89 | setup_precession( precess_matrix, year, 2000.); 90 | for( i = 0; i < 10; i++, mjd += 0.1) 91 | { 92 | double posn[3], j2000_posn[3]; 93 | const double t_since = (mjd - (tle.epoch - 2400000.5)) * minutes_per_day; 94 | 95 | if( is_deep) 96 | SDP4( t_since, &tle, sat_params, posn, NULL); 97 | else 98 | SGP4( t_since, &tle, sat_params, posn, NULL); 99 | for( j = 0; j < 3; j++) 100 | posn[j] /= AU_IN_KM; 101 | precess_vector( precess_matrix, posn, j2000_posn); 102 | fprintf( ofile, "%.5f%16.10f%16.10f%16.10f\n", 103 | mjd + 2400000.5, j2000_posn[0], j2000_posn[1], j2000_posn[2]); 104 | } 105 | } 106 | } 107 | /* Rewind to start of input & re-read, looking for comments */ 108 | fseek( ifile, 0L, SEEK_SET); 109 | while( fgets( buff, sizeof( buff), ifile)) 110 | if( !memcmp( buff, "# Ephem range: ", 14)) 111 | while( fgets( buff, sizeof( buff), ifile) && memcmp( buff, "# 1 NoradU", 10)) 112 | fprintf( ofile, "%s", buff); 113 | 114 | fclose( ifile); 115 | fclose( ofile); 116 | return( 0); 117 | } 118 | -------------------------------------------------------------------------------- /tle_date.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "watdefs.h" 9 | #include "afuncs.h" 10 | #include "date.h" 11 | 12 | /* Code to extract TLEs for a particular date (MJD). It looks 13 | through all TLEs listed in 'tle_list.txt' and outputs those matching 14 | the MJD given on the command line. */ 15 | 16 | char path[100]; 17 | int verbose; 18 | 19 | static void extract_tle_for_date( const char *fname, const double mjd) 20 | { 21 | char prev_line[100], buff[100]; 22 | FILE *ifile = fopen( fname, "rb"); 23 | double step = 0.; 24 | 25 | assert( strlen( fname) < 20); 26 | assert( strlen( path) < 60); 27 | strcpy( buff, path); 28 | strcat( buff, fname); 29 | ifile = fopen( buff, "rb"); 30 | if( !ifile) 31 | { 32 | printf( "'%s' not opened\n", fname); 33 | exit( -1); 34 | } 35 | if( verbose) 36 | printf( "Looking at '%s' for %f\n", fname, mjd); 37 | *prev_line = '\0'; 38 | while( fgets( buff, sizeof( buff), ifile)) 39 | { 40 | if( !memcmp( buff, "# MJD ", 6) && atof( buff + 6) <= mjd 41 | && atof( buff + 6) + step > mjd) 42 | { 43 | size_t i; 44 | 45 | printf( "%s", prev_line); /* show 'worst residual' data */ 46 | for( i = 0; i < 3 && fgets( buff, sizeof( buff), ifile); i++) 47 | printf( "%s", buff); /* obj name, lines 1 and 2 of TLE */ 48 | fclose( ifile); 49 | return; 50 | } 51 | else if( !memcmp( buff, "# Ephem range: ", 15)) 52 | { 53 | double mjd1, mjd2; 54 | 55 | if( sscanf( buff + 15, "%lf %lf %lf", &mjd1, &mjd2, &step) != 3) 56 | { 57 | printf( "Ephem step problem in '%s'\n'%s'\n", 58 | fname, buff); 59 | exit( -2); 60 | } 61 | if( mjd < mjd1 || mjd > mjd2) 62 | { 63 | if( verbose) 64 | printf( "'%s': outside range\n", fname); 65 | fclose( ifile); 66 | return; 67 | } 68 | } 69 | else if( !memcmp( buff, "# Include ", 10)) 70 | { 71 | int i = 0; 72 | char *filename = buff + 10; 73 | 74 | while( buff[i] >= ' ') 75 | i++; 76 | buff[i] = '\0'; 77 | if( memcmp( filename, "classfd", 7) && memcmp( filename, "inttles", 7) 78 | && memcmp( filename, "all_tle", 7) 79 | && memcmp( filename, "old_tle", 7)) 80 | extract_tle_for_date( filename, mjd); 81 | } 82 | strcpy( prev_line, buff); 83 | } 84 | fclose( ifile); 85 | } 86 | 87 | static void err_exit( void) 88 | { 89 | printf( "tle_date (MJD)\n"); 90 | exit( -1); 91 | } 92 | 93 | #ifdef ON_LINE_VERSION 94 | int dummy_main( const int argc, const char **argv) 95 | #else 96 | int main( const int argc, const char **argv) 97 | #endif 98 | { 99 | const double jan_1_1970 = 2440587.5; 100 | const double curr_t = jan_1_1970 + (double)time( NULL) / seconds_per_day; 101 | double mjd; 102 | 103 | if( argc < 2) 104 | err_exit( ); 105 | mjd = atof( argv[1]); 106 | mjd = get_time_from_string( curr_t, argv[1], FULL_CTIME_YMD, NULL) 107 | - 2400000.5; 108 | if( mjd < 40000 || mjd > 65000) 109 | err_exit( ); 110 | if( argc > 2) 111 | strcpy( path, argv[2]); 112 | if( argc > 3) 113 | verbose = 1; 114 | extract_tle_for_date( "tle_list.txt", mjd); 115 | return( 0); 116 | } 117 | 118 | #ifdef ON_LINE_VERSION 119 | #include 120 | 121 | int main( void) 122 | { 123 | const char *argv[20]; 124 | const size_t max_buff_size = 40000; 125 | char *buff = (char *)malloc( max_buff_size); 126 | char field[30], date_text[80]; 127 | FILE *lock_file = fopen( "lock.txt", "w"); 128 | extern char **environ; 129 | int cgi_status; 130 | 131 | avoid_runaway_process( 15); 132 | printf( "Content-type: text/html\n\n"); 133 | printf( "
\n");
134 |    if( !lock_file)
135 |       {
136 |       printf( "

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

"); 137 | printf( "

Your TLEs are very important to us!

"); 138 | return( 0); 139 | } 140 | fprintf( lock_file, "We're in\n"); 141 | for( size_t i = 0; environ[i]; i++) 142 | fprintf( lock_file, "%s\n", environ[i]); 143 | cgi_status = initialize_cgi_reading( ); 144 | strcpy( date_text, "now"); 145 | fprintf( lock_file, "CGI status %d\n", cgi_status); 146 | if( cgi_status <= 0) 147 | { 148 | printf( "

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

\n"); 150 | return( 0); 151 | } 152 | while( !get_cgi_data( field, buff, NULL, max_buff_size)) 153 | { 154 | if( !strcmp( field, "date") && strlen( buff) < 70) 155 | { 156 | strncpy( date_text, buff, sizeof( date_text)); 157 | date_text[sizeof( date_text) - 1] = '\0'; 158 | } 159 | } 160 | free( buff); 161 | argv[0] = "tle_date"; 162 | argv[1] = date_text; 163 | argv[2] = "/home/projectp/public_html/tles/"; 164 | argv[3] = NULL; 165 | dummy_main( 3, argv); 166 | printf( "
"); 167 | fclose( lock_file); 168 | return( 0); 169 | } 170 | #endif 171 | -------------------------------------------------------------------------------- /tle_out.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018, Project Pluto. See LICENSE. 2 | 3 | tle_out.cpp: code to convert the in-memory artificial satellite 4 | elements into the "standard" TLE (Two-Line Element) form described at 5 | 6 | https://en.wikipedia.org/wiki/Two-line_elements */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "norad.h" 14 | 15 | /* Useful constants to define, in case the value of PI or the number 16 | of minutes in a day should change: */ 17 | #define PI 3.141592653589793238462643383279502884197169399375105 18 | #define MINUTES_PER_DAY 1440. 19 | #define MINUTES_PER_DAY_SQUARED (MINUTES_PER_DAY * MINUTES_PER_DAY) 20 | #define MINUTES_PER_DAY_CUBED (MINUTES_PER_DAY_SQUARED * MINUTES_PER_DAY) 21 | 22 | #define AE 1.0 23 | #define J1900 (2451545.5 - 36525. - 1.) 24 | 25 | static int add_tle_checksum_data( char *buff) 26 | { 27 | int count = 69, rval = 0; 28 | 29 | if( (*buff != '1' && *buff != '2') || buff[1] != ' ') 30 | return( 0); /* not a .TLE */ 31 | while( --count) 32 | { 33 | if( *buff < ' ' || *buff > 'z') 34 | return( 0); /* wups! those shouldn't happen in .TLEs */ 35 | if( *buff > '0' && *buff <= '9') 36 | rval += *buff - '0'; 37 | else if( *buff == '-') 38 | rval++; 39 | buff++; 40 | } 41 | if( *buff != 10 && buff[1] != 10 && buff[2] != 10) 42 | return( 0); /* _still_ not a .TLE */ 43 | *buff++ = (char)( '0' + (rval % 10)); 44 | *buff++ = 13; 45 | *buff++ = 10; 46 | *buff++ = '\0'; 47 | return( 1); 48 | } 49 | 50 | static double zero_to_two_pi( double angle_in_radians) 51 | { 52 | angle_in_radians = fmod( angle_in_radians, PI + PI); 53 | if( angle_in_radians < 0.) 54 | angle_in_radians += PI + PI; 55 | return( angle_in_radians); 56 | } 57 | 58 | /* See comments for get_high_value() in 'get_el.cpp'. Essentially, we are 59 | writing out a state vector in a convoluted base-36 form. */ 60 | 61 | static void set_high_value( char *obuff, const double value) 62 | { 63 | int64_t oval = (int64_t)fabs( value); 64 | int i; 65 | 66 | *obuff++ = (value >= 0. ? '+' : '-'); 67 | for( i = 7; i >= 0; i--, oval /= (int64_t)36) 68 | { 69 | obuff[i] = (char)( oval % (int64_t)36); 70 | if( obuff[i] < 10) 71 | obuff[i] += '0'; 72 | else 73 | obuff[i] += 'A' - 10; 74 | } 75 | obuff[8] = ' '; 76 | } 77 | 78 | 79 | /* The second derivative of the mean motion, 'xnddo6', and the 'bstar' 80 | drag term, are stored in a simplified scientific notation. To save 81 | valuable bytes, the decimal point and 'E' are assumed. */ 82 | 83 | static void put_sci( char *obuff, double ival) 84 | { 85 | if( !ival) 86 | memcpy( obuff, " 00000-0", 7); 87 | else 88 | { 89 | int oval, exponent = 0; 90 | 91 | if( ival > 0.) 92 | *obuff++ = ' '; 93 | else 94 | { 95 | *obuff++ = '-'; 96 | ival = -ival; 97 | } 98 | while( 1) 99 | { 100 | if( ival > 1.) /* avoid integer overflow */ 101 | oval = 100000; 102 | else 103 | oval = (int)( ival * 100000. + .5); 104 | if( oval > 99999) 105 | { 106 | ival /= 10; 107 | exponent++; 108 | } 109 | else if( oval < 10000) 110 | { 111 | ival *= 10; 112 | exponent--; 113 | } 114 | else 115 | break; 116 | } 117 | snprintf( obuff, 7, "%5d", oval); 118 | assert( 5 == strlen( obuff)); 119 | if( exponent > 0) 120 | { 121 | obuff[5] = '+'; 122 | obuff[6] = (char)( '0' + exponent); 123 | } 124 | else 125 | { 126 | obuff[5] = '-'; 127 | obuff[6] = (char)( '0' - exponent); 128 | } 129 | } 130 | } 131 | 132 | /* See comments for get_norad_number( ) in get_el.cpp. This 133 | performs the reverse function of setting the five bytes corresponding 134 | to a NORAD number, using the 'standard' Alpha-5 method for numbers 135 | 0 to 339000 and the nonstandard Super-5 method beyond that. */ 136 | 137 | static char int_to_base64( const int digit) 138 | { 139 | int rval; 140 | 141 | assert( digit >= 0 && digit < 64); 142 | if( digit < 0 || digit >= 64) 143 | rval = ' '; 144 | else if( digit < 10) 145 | rval = '0' + digit; 146 | else if( digit < 36) 147 | rval = 'A' + digit - 10; 148 | else if( digit < 62) 149 | rval = 'a' + digit - 36; 150 | else 151 | rval = (digit == 62 ? '+' : '-'); 152 | return( rval); 153 | } 154 | 155 | static void store_norad_number_in_alpha5( char *obuff, const int norad_number) 156 | { 157 | const int N_TYPE_STANDARD = 340000; /* five digits plus Alpha-5 */ 158 | const int N_TYPE_2 = 64 * 64 * 64 * 64 * 54; /* xxxxL */ 159 | /* const int N_TYPE_3 = 64 * 64 * 64 * 54 * 10; xxxLd; we don't actually use this */ 160 | const int one_billion = 1000000000; 161 | int i, tval = norad_number; 162 | 163 | assert( norad_number >= 0 && norad_number < one_billion); 164 | if( norad_number < 0 || norad_number >= one_billion) 165 | strcpy( obuff, " "); /* outside representable range */ 166 | else if( norad_number < N_TYPE_STANDARD) 167 | { 168 | for( i = 4; i > 0; i--, tval /= 10) 169 | obuff[i] = '0' + (tval % 10); 170 | *obuff = int_to_base64( tval); 171 | if( *obuff >= 'I') 172 | (*obuff)++; 173 | if( *obuff >= 'O') 174 | (*obuff)++; 175 | } 176 | else if( norad_number < N_TYPE_STANDARD + N_TYPE_2) 177 | { 178 | tval -= N_TYPE_STANDARD; 179 | obuff[4] = int_to_base64( tval % 54 + 10); 180 | tval /= 54; 181 | for( i = 3; i >= 0; i--, tval >>= 6) 182 | obuff[i] = int_to_base64( tval & 0x3f); 183 | } 184 | else 185 | { 186 | tval -= N_TYPE_STANDARD + N_TYPE_2; 187 | obuff[4] = int_to_base64( tval % 10); 188 | obuff[3] = int_to_base64( (tval / 10) % 54 + 10); 189 | tval /= 540; 190 | for( i = 2; i >= 0; i--, tval >>= 6) 191 | obuff[i] = int_to_base64( tval & 0x3f); 192 | } 193 | obuff[5] = '\0'; 194 | } 195 | 196 | /* SpaceTrack TLEs have, on the second line, leading zeroes in front of the 197 | inclination, ascending node, argument of perigee, and mean motion. Which 198 | is why I've used this format string : 199 | 200 | snprintf( line2 + 8, 57, "%08.4f %08.4f %07ld %08.4f %08.4f %011.8f", ...) 201 | 202 | 'classfd.tle' and some other sources don't use leading zeroes. For them, 203 | one should use the following format string for those four quantities : 204 | 205 | snprintf( line2 + 8, 57, "%8.4f %8.4f %07ld %8.4f %8.4f %11.8f", ...) */ 206 | 207 | void DLL_FUNC write_elements_in_tle_format( char *buff, const tle_t *tle) 208 | { 209 | long year = (long)( tle->epoch - J1900) / 365 + 1; 210 | double day_of_year; 211 | char *line2, norad_num_text[6]; 212 | 213 | do 214 | { 215 | double start_of_year; 216 | 217 | year--; 218 | start_of_year = J1900 + (double)year * 365. + (double)((year - 1) / 4); 219 | day_of_year = tle->epoch - start_of_year; 220 | } 221 | while( day_of_year < 1.); 222 | if( year < 0 || year > 200) /* bogus input */ 223 | { 224 | year = 56; 225 | day_of_year = 0.; 226 | } 227 | store_norad_number_in_alpha5( norad_num_text, tle->norad_number); 228 | snprintf( buff, 72, 229 | /* xndt2o xndd6o bstar eph bull */ 230 | "1 %5s%c %-8s %02ld%12.8f -.000hit00 +00000-0 +00000-0 %c %4dZ\n", 231 | norad_num_text, tle->classification, tle->intl_desig, 232 | year % 100L, day_of_year, 233 | tle->ephemeris_type, tle->bulletin_number); 234 | if( buff[20] == ' ') /* fill in leading zeroes for day of year */ 235 | buff[20] = '0'; 236 | if( buff[21] == ' ') 237 | buff[21] = '0'; 238 | if( tle->ephemeris_type != 'H') /* "normal", standard TLEs */ 239 | { 240 | double deriv_mean_motion = tle->xndt2o * MINUTES_PER_DAY_SQUARED / (2. * PI); 241 | unsigned long lderiv; 242 | 243 | if( deriv_mean_motion >= 0) 244 | buff[33] = ' '; 245 | lderiv = (unsigned long)( fabs( deriv_mean_motion * 100000000.) + .5); 246 | assert( lderiv < 100000000); 247 | snprintf( buff + 35, 10, "%08lu", lderiv); 248 | assert( 8 == strlen( buff + 35)); 249 | buff[43] = ' '; 250 | put_sci( buff + 44, tle->xndd6o * MINUTES_PER_DAY_CUBED / (2. * PI)); 251 | put_sci( buff + 53, tle->bstar / AE); 252 | } 253 | else 254 | { 255 | size_t i; 256 | const double *posn = &tle->xincl; 257 | 258 | for( i = 0; i < 3; i++) 259 | set_high_value( buff + 33 + i * 10, posn[i]); 260 | buff[62] = 'H'; 261 | } 262 | add_tle_checksum_data( buff); 263 | assert( 71 == strlen( buff)); 264 | line2 = buff + 71; 265 | snprintf( line2, 10, "2 %5s ", norad_num_text); 266 | assert( 8 == strlen( line2)); 267 | if( tle->ephemeris_type != 'H') /* "normal", standard TLEs */ 268 | { 269 | const double revs_per_day = tle->xno * MINUTES_PER_DAY / (2. * PI); 270 | 271 | assert( revs_per_day > 0. && revs_per_day < 99.); 272 | snprintf( line2 + 8, 57, "%08.4f %08.4f %07ld %08.4f %08.4f %011.8f", 273 | zero_to_two_pi( tle->xincl) * 180. / PI, 274 | zero_to_two_pi( tle->xnodeo) * 180. / PI, 275 | (long)( tle->eo * 10000000. + .5), 276 | zero_to_two_pi( tle->omegao) * 180. / PI, 277 | zero_to_two_pi( tle->xmo) * 180. / PI, 278 | revs_per_day); 279 | assert( 55 == strlen( line2 + 8)); 280 | } 281 | else 282 | { 283 | size_t i; 284 | const double *vel = &tle->xincl + 3; 285 | 286 | memset( line2 + 8, ' ', 25); /* reserved for future use */ 287 | for( i = 0; i < 3; i++) 288 | set_high_value( line2 + 33 + i * 10, vel[i] * 1e+4); 289 | } 290 | assert( tle->revolution_number >= 0 && tle->revolution_number < 100000); 291 | snprintf( line2 + 63, 8, "%5dZ\n", tle->revolution_number); 292 | add_tle_checksum_data( line2); 293 | } 294 | -------------------------------------------------------------------------------- /watcom.mak: -------------------------------------------------------------------------------- 1 | # Makefile for OpenWATCOM 2 | 3 | all: test2.exe test_sat.exe obs_test.exe obs_tes2.exe sat_id.exe test_out.exe out_comp.exe 4 | 5 | out_comp.exe: out_comp.cpp 6 | wcl386 -zq -W4 -Ox out_comp.cpp 7 | 8 | test2.exe: test2.obj wsatlib.lib 9 | wcl386 -zq -k10000 test2.obj wsatlib.lib 10 | 11 | test_sat.exe: test_sat.obj wsatlib.lib 12 | wcl386 -zq -k10000 test_sat.obj wsatlib.lib 13 | 14 | obs_test.exe: obs_test.obj wsatlib.lib 15 | wcl386 -zq -k10000 obs_test.obj wsatlib.lib 16 | 17 | obs_tes2.exe: obs_tes2.obj wsatlib.lib 18 | wcl386 -zq -k10000 obs_tes2.obj wsatlib.lib 19 | 20 | WAT_LIB=../watlib 21 | 22 | sat_id.exe: sat_id.obj sat_util.obj wsatlib.lib $(WAT_LIB)/wafuncs.lib 23 | wcl386 -zq -k10000 sat_id.obj sat_util.obj wsatlib.lib $(WAT_LIB)/wafuncs.lib 24 | 25 | test_out.exe: test_out.obj wsatlib.lib 26 | wcl386 -zq -k10000 test_out.obj wsatlib.lib 27 | 28 | #CFLAGS=-W4 -Ox -j -zq -DRETAIN_PERTURBATION_VALUES_AT_EPOCH 29 | CFLAGS=-W4 -Ox -j -zq -i=..\include 30 | 31 | wsatlib.lib: sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj & 32 | basics.obj get_el.obj observe.obj common.obj tle_out.obj 33 | wlib -q wsatlib.lib +sgp.obj +sgp4.obj +sgp8.obj +sdp4.obj +sdp8.obj 34 | wlib -q wsatlib.lib +deep.obj +basics.obj +get_el.obj +observe.obj 35 | wlib -q wsatlib.lib +common.obj +tle_out.obj 36 | 37 | .cpp.obj: 38 | wcc386 $(CFLAGS) $< 39 | 40 | .c.obj: 41 | wcc386 $(CFLAGS) $< 42 | 43 | basics.obj: 44 | 45 | deep.obj: 46 | 47 | get_el.obj: 48 | 49 | observe.obj: 50 | 51 | common.obj: 52 | 53 | obs_test.obj: 54 | 55 | obs_tes2.obj: 56 | 57 | sat_id.obj: 58 | 59 | sat_util.obj: 60 | 61 | tle_out.obj: 62 | 63 | test_sat.obj: 64 | 65 | test2.obj: 66 | 67 | sgp.obj: 68 | 69 | sgp4.obj: 70 | 71 | sgp8.obj: 72 | 73 | sdp4.obj: 74 | 75 | sdp8.obj: 76 | 77 | test_out.obj: 78 | 79 | clean: 80 | -del *.obj 81 | -del *.exe 82 | -del *.o 83 | -del *.map 84 | -del *.lib 85 | -del *.exp 86 | -del *.dll 87 | -del *.a 88 | --------------------------------------------------------------------------------